// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "common/common_types.h" #include "common/serialization/boost_small_vector.hpp" #include "common/swap.h" #include "core/hle/ipc.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_session.h" BOOST_SERIALIZATION_ASSUME_ABSTRACT(Kernel::SessionRequestHandler) 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 { 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 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 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; private: template void serialize(Archive& ar, const unsigned int file_version) {} friend class boost::serialization::access; }; protected: /// Creates the storage for the session data of the service. virtual std::unique_ptr MakeSessionData() = 0; /// Returns the session data associated with the server session. template T* GetSessionData(std::shared_ptr session) { static_assert(std::is_base_of(), "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(itr->data.get()); } struct SessionInfo { SessionInfo(std::shared_ptr session, std::unique_ptr data); SessionInfo() = default; std::shared_ptr session; std::unique_ptr data; private: template void serialize(Archive& ar, const unsigned int file_version) { ar& session; ar& data; } friend class boost::serialization::access; }; /// 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 connected_sessions; private: template void serialize(Archive& ar, const unsigned int file_version) { ar& connected_sessions; } friend class boost::serialization::access; }; // NOTE: The below classes are ephemeral and don't need serialization class MappedBuffer { public: MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr 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; std::shared_ptr process; u32 size; IPC::MappedBufferPermissions perms; MappedBuffer(); template void serialize(Archive& ar, const unsigned int) { ar& id; ar& address; ar& process; ar& size; ar& perms; } friend class boost::serialization::access; }; /** * 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 std::enable_shared_from_this { public: HLERequestContext(KernelSystem& kernel, std::shared_ptr session, std::shared_ptr 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 Session() const { return session; } class WakeupCallback { public: virtual ~WakeupCallback() = default; virtual void WakeUp(std::shared_ptr thread, HLERequestContext& context, ThreadWakeupReason reason) = 0; }; /** * 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 SleepClientThread(const std::string& reason, std::chrono::nanoseconds timeout, std::shared_ptr 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 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); /** * 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& 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 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, std::shared_ptr src_process); /// Writes data from this context back to the requesting process/thread. ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const; /// Reports an unimplemented function. void ReportUnimplemented() const; class ThreadCallback; friend class ThreadCallback; private: KernelSystem& kernel; std::array cmd_buf; std::shared_ptr session; std::shared_ptr thread; // TODO(yuriks): Check common usage of this and optimize size accordingly boost::container::small_vector, 8> request_handles; // The static buffers will be created when the IPC request is translated. std::array, IPC::MAX_STATIC_BUFFERS> static_buffers; // The mapped buffers will be created when the IPC request is translated boost::container::small_vector request_mapped_buffers; HLERequestContext(); template void serialize(Archive& ar, const unsigned int) { ar& cmd_buf; ar& session; ar& thread; ar& request_handles; ar& static_buffers; ar& request_mapped_buffers; } friend class boost::serialization::access; }; } // namespace Kernel BOOST_CLASS_EXPORT_KEY(Kernel::SessionRequestHandler::SessionDataBase) BOOST_CLASS_EXPORT_KEY(Kernel::HLERequestContext::ThreadCallback)