citra/src/core/hle/kernel/hle_ipc.h
Weiyi Wang f565ea80f0 HLE/IPC: HLEContext can memorize the client thread and use it for SleepClientThread
This reduces the boilerplate that services have to write out the current thread explicitly. Using current thread instead of client thread is also semantically incorrect, and will be a problem when we implement multicore (at which time there will be multiple current threads)
2019-04-02 13:23:39 -04:00

251 lines
9.8 KiB
C++

// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
namespace Service {
class ServiceFrameworkBase;
}
namespace Memory {
class MemorySystem;
}
namespace Kernel {
class HandleTable;
class Process;
class Thread;
class Event;
class HLERequestContext;
class KernelSystem;
/**
* Interface implemented by HLE Session handlers.
* This can be provided to a ServerSession in order to hook into several relevant events
* (such as a new connection or a SyncRequest) so they can be implemented in the emulator.
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
virtual ~SessionRequestHandler() = default;
/**
* Handles a sync request from the emulated application.
* @param context holds all the information relevant to his request (ServerSession, Translated
* command buffer, etc).
*/
virtual void HandleSyncRequest(Kernel::HLERequestContext& context) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
* associated ServerSession alive for the duration of the connection.
* @param server_session Owning pointer to the ServerSession associated with the connection.
*/
virtual void ClientConnected(std::shared_ptr<ServerSession> server_session);
/**
* Signals that a client has just disconnected from this HLE handler and releases the
* associated ServerSession.
* @param server_session ServerSession associated with the connection.
*/
virtual void ClientDisconnected(std::shared_ptr<ServerSession> server_session);
/// Empty placeholder structure for services with no per-session data. The session data classes
/// in each service must inherit from this.
struct SessionDataBase {
virtual ~SessionDataBase() = default;
};
protected:
/// Creates the storage for the session data of the service.
virtual std::unique_ptr<SessionDataBase> MakeSessionData() const = 0;
/// Returns the session data associated with the server session.
template <typename T>
T* GetSessionData(std::shared_ptr<ServerSession> session) {
static_assert(std::is_base_of<SessionDataBase, T>(),
"T is not a subclass of SessionDataBase");
auto itr = std::find_if(connected_sessions.begin(), connected_sessions.end(),
[&](const SessionInfo& info) { return info.session == session; });
ASSERT(itr != connected_sessions.end());
return static_cast<T*>(itr->data.get());
}
struct SessionInfo {
SessionInfo(std::shared_ptr<ServerSession> session, std::unique_ptr<SessionDataBase> data);
std::shared_ptr<ServerSession> session;
std::unique_ptr<SessionDataBase> data;
};
/// List of sessions that are connected to this handler. A ServerSession whose server endpoint
/// is an HLE implementation is kept alive by this list for the duration of the connection.
std::vector<SessionInfo> connected_sessions;
};
class MappedBuffer {
public:
MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor,
VAddr address, u32 id);
// interface for service
void Read(void* dest_buffer, std::size_t offset, std::size_t size);
void Write(const void* src_buffer, std::size_t offset, std::size_t size);
std::size_t GetSize() const {
return size;
}
// interface for ipc helper
u32 GenerateDescriptor() const {
return IPC::MappedBufferDesc(size, perms);
}
u32 GetId() const {
return id;
}
private:
friend class HLERequestContext;
Memory::MemorySystem* memory;
u32 id;
VAddr address;
const Process* process;
std::size_t size;
IPC::MappedBufferPermissions perms;
};
/**
* Class containing information about an in-flight IPC request being handled by an HLE service
* implementation.
*
* HLE handle protocol
* ===================
*
* To avoid needing HLE services to keep a separate handle table, or having to directly modify the
* requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel
* will decode the incoming handles into object pointers and insert a id in the buffer where the
* handle would normally be. The service then calls GetIncomingHandle() with that id to get the
* pointer to the object. Similarly, instead of inserting a handle into the command buffer, the
* service calls AddOutgoingHandle() and stores the returned id where the handle would normally go.
*
* The end result is similar to just giving services their own real handle tables, but since these
* ids are local to a specific context, it avoids requiring services to manage handles for objects
* across multiple calls and ensuring that unneeded handles are cleaned up.
*
* HLE mapped buffer protocol
* ==========================
*
* HLE services don't have their own virtual memory space, a tweaked protocol is used to simulate
* memory mapping. The kernel will wrap the incoming buffers into a memory interface on which HLE
* services can operate, and insert a id in the buffer where the vaddr would normally be. The
* service then calls GetMappedBuffer with that id to get the memory interface. On response, like
* real services pushing back the mapped buffer address to unmap it, HLE services push back the
* id of the memory interface and let kernel convert it back to client vaddr. No real unmapping is
* needed in this case, though.
*/
class HLERequestContext {
public:
HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session, Thread* thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
u32* CommandBuffer() {
return cmd_buf.data();
}
/**
* Returns the session through which this request was made. This can be used as a map key to
* access per-client data on services.
*/
std::shared_ptr<ServerSession> Session() const {
return session;
}
using WakeupCallback = std::function<void(
std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>;
/**
* Puts the specified guest thread to sleep until the returned event is signaled or until the
* specified timeout expires.
* @param reason Reason for pausing the thread, to be used for debugging purposes.
* @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
* invoked with a Timeout reason.
* @param callback Callback to be invoked when the thread is resumed. This callback must write
* the entire command response once again, regardless of the state of it before this function
* was called.
* @returns Event that when signaled will resume the thread and call the callback function.
*/
std::shared_ptr<Event> SleepClientThread(const std::string& reason,
std::chrono::nanoseconds timeout,
WakeupCallback&& callback);
/**
* Resolves a object id from the request command buffer into a pointer to an object. See the
* "HLE handle protocol" section in the class documentation for more details.
*/
std::shared_ptr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const;
/**
* Adds an outgoing object to the response, returning the id which should be used to reference
* it. See the "HLE handle protocol" section in the class documentation for more details.
*/
u32 AddOutgoingHandle(std::shared_ptr<Object> object);
/**
* Discards all Objects from the context, invalidating all ids. This may be called after reading
* out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but
* this is not required.
*/
void ClearIncomingObjects();
/**
* Retrieves the static buffer identified by the input buffer_id. The static buffer *must* have
* been created in PopulateFromIncomingCommandBuffer by way of an input StaticBuffer descriptor.
*/
const std::vector<u8>& GetStaticBuffer(u8 buffer_id) const;
/**
* Sets up a static buffer that will be copied to the target process when the request is
* translated.
*/
void AddStaticBuffer(u8 buffer_id, std::vector<u8> data);
/**
* Gets a memory interface by the id from the request command buffer. See the "HLE mapped buffer
* protocol" section in the class documentation for more details.
*/
MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf);
/// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process);
/// Writes data from this context back to the requesting process/thread.
ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
private:
KernelSystem& kernel;
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
std::shared_ptr<ServerSession> session;
Thread* thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<std::shared_ptr<Object>, 8> request_handles;
// The static buffers will be created when the IPC request is translated.
std::array<std::vector<u8>, IPC::MAX_STATIC_BUFFERS> static_buffers;
// The mapped buffers will be created when the IPC request is translated
boost::container::small_vector<MappedBuffer, 8> request_mapped_buffers;
};
} // namespace Kernel