diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6e602b0c5..b16a89990 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -385,4 +385,4 @@ set(HEADERS create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic) +target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 0922b3f47..a60b8ef00 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -21,4 +21,6 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr server_s boost::range::remove_erase(connected_sessions, server_session); } +HLERequestContext::~HLERequestContext() = default; + } // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 5de9d59d3..c30184eab 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -7,11 +7,14 @@ #include #include #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/server_session.h" + +namespace Service { +class ServiceFrameworkBase; +} namespace Kernel { -class ServerSession; - /** * Interface implemented by HLE Session handlers. * This can be provided to a ServerSession in order to hook into several relevant events @@ -52,4 +55,33 @@ protected: std::vector> connected_sessions; }; +/** + * Class containing information about an in-flight IPC request being handled by an HLE service + * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and + * when possible use the APIs in this class to service the request. + */ +class HLERequestContext { +public: + ~HLERequestContext(); + + /// Returns a pointer to the IPC command buffer for this request. + u32* CommandBuffer() const { + return cmd_buf; + } + + /** + * Returns the session through which this request was made. This can be used as a map key to + * access per-client data on services. + */ + SharedPtr Session() const { + return session; + } + +private: + friend class Service::ServiceFrameworkBase; + + u32* cmd_buf = nullptr; + SharedPtr session; +}; + } // namespace Kernel diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 0d443aa44..10f2b3ee3 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/logging/log.h" #include "common/string_util.h" #include "core/hle/kernel/client_port.h" @@ -45,6 +46,11 @@ #include "core/hle/service/ssl_c.h" #include "core/hle/service/y2r_u.h" +using Kernel::ClientPort; +using Kernel::ServerPort; +using Kernel::ServerSession; +using Kernel::SharedPtr; + namespace Service { std::unordered_map> g_kernel_named_ports; @@ -102,9 +108,84 @@ void Interface::Register(const FunctionInfo* functions, size_t n) { } } +//////////////////////////////////////////////////////////////////////////////////////////////////// + +ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, + InvokerFn* handler_invoker) + : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} + +ServiceFrameworkBase::~ServiceFrameworkBase() = default; + +void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { + ASSERT(port == nullptr); + port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); + port->SetHleHandler(shared_from_this()); +} + +void ServiceFrameworkBase::InstallAsNamedPort() { + ASSERT(port == nullptr); + SharedPtr server_port; + SharedPtr client_port; + std::tie(server_port, client_port) = ServerPort::CreatePortPair(max_sessions, service_name); + server_port->SetHleHandler(shared_from_this()); + AddNamedPort(service_name, std::move(client_port)); +} + +void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, size_t n) { + handlers.reserve(handlers.size() + n); + for (size_t i = 0; i < n; ++i) { + // Usually this array is sorted by id already, so hint to insert at the end + handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]); + } +} + +void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info) { + IPC::Header header{cmd_buf[0]}; + int num_params = header.normal_params_size + header.translate_params_size; + std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name; + + fmt::MemoryWriter w; + w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name, + cmd_buf[0]); + for (int i = 1; i <= num_params; ++i) { + w.write(", [{}]={:#x}", i, cmd_buf[i]); + } + w << '}'; + + LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str()); + // TODO(bunnei): Hack - ignore error + cmd_buf[1] = 0; +} + +void ServiceFrameworkBase::HandleSyncRequest(SharedPtr server_session) { + u32* cmd_buf = Kernel::GetCommandBuffer(); + + // TODO(yuriks): The kernel should be the one handling this as part of translation after + // everything else is migrated + Kernel::HLERequestContext context; + context.cmd_buf = cmd_buf; + context.session = std::move(server_session); + + u32 header_code = cmd_buf[0]; + auto itr = handlers.find(header_code); + const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; + if (info == nullptr || info->handler_callback == nullptr) { + return ReportUnimplementedFunction(cmd_buf, info); + } + + LOG_TRACE(Service, "%s", + MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str()); + handler_invoker(this, info->handler_callback, context); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Module interface +// TODO(yuriks): Move to kernel +void AddNamedPort(std::string name, SharedPtr port) { + g_kernel_named_ports.emplace(std::move(name), std::move(port)); +} + static void AddNamedPort(Interface* interface_) { Kernel::SharedPtr server_port; Kernel::SharedPtr client_port; @@ -112,7 +193,7 @@ static void AddNamedPort(Interface* interface_) { Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName()); server_port->SetHleHandler(std::shared_ptr(interface_)); - g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port)); + AddNamedPort(interface_->GetPortName(), std::move(client_port)); } void AddService(Interface* interface_) { diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 8933d57cc..281ff99bb 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -18,11 +18,16 @@ namespace Kernel { class ClientPort; +class ServerPort; class ServerSession; } namespace Service { +namespace SM { +class ServiceManager; +} + static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters) /// Arbitrary default number of maximum connections to an HLE service. static const u32 DefaultMaxSessions = 10; @@ -30,6 +35,9 @@ static const u32 DefaultMaxSessions = 10; /** * Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a * table mapping header ids to handler functions. + * + * @deprecated Use ServiceFramework for new services instead. It allows services to be stateful and + * is more extensible going forward. */ class Interface : public Kernel::SessionRequestHandler { public: @@ -101,6 +109,146 @@ private: boost::container::flat_map m_functions; }; +/** + * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it + * is not meant to be used directly. + * + * @see ServiceFramework + */ +class ServiceFrameworkBase : public Kernel::SessionRequestHandler { +public: + /// Returns the string identifier used to connect to the service. + std::string GetServiceName() const { + return service_name; + } + + /** + * Returns the maximum number of sessions that can be connected to this service at the same + * time. + */ + u32 GetMaxSessions() const { + return max_sessions; + } + + /// Creates a port pair and registers this service with the given ServiceManager. + void InstallAsService(SM::ServiceManager& service_manager); + /// Creates a port pair and registers it on the kernel's global port registry. + void InstallAsNamedPort(); + + void HandleSyncRequest(Kernel::SharedPtr server_session) override; + +protected: + /// Member-function pointer type of SyncRequest handlers. + template + using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); + +private: + template + friend class ServiceFramework; + + struct FunctionInfoBase { + u32 expected_header; + HandlerFnP handler_callback; + const char* name; + }; + + using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP member, + Kernel::HLERequestContext& ctx); + + ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker); + ~ServiceFrameworkBase(); + + void RegisterHandlersBase(const FunctionInfoBase* functions, size_t n); + void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info); + + /// Identifier string used to connect to the service. + std::string service_name; + /// Maximum number of concurrent sessions that this service can handle. + u32 max_sessions; + + /** + * Port where incoming connections will be received. Only created when InstallAsService() or + * InstallAsNamedPort() are called. + */ + Kernel::SharedPtr port; + + /// Function used to safely up-cast pointers to the derived class before invoking a handler. + InvokerFn* handler_invoker; + boost::container::flat_map handlers; +}; + +/** + * Framework for implementing HLE services. Dispatches on the header id of incoming SyncRequests + * based on a table mapping header ids to handler functions. Service implementations should inherit + * from ServiceFramework using the CRTP (`class Foo : public ServiceFramework { ... };`) and + * populate it with handlers by calling #RegisterHandlers. + * + * In order to avoid duplicating code in the binary and exposing too many implementation details in + * the header, this class is split into a non-templated base (ServiceFrameworkBase) and a template + * deriving from it (ServiceFramework). The functions in this class will mostly only erase the type + * of the passed in function pointers and then delegate the actual work to the implementation in the + * base class. + */ +template +class ServiceFramework : public ServiceFrameworkBase { +protected: + /// Contains information about a request type which is handled by the service. + struct FunctionInfo : FunctionInfoBase { + // TODO(yuriks): This function could be constexpr, but clang is the only compiler that + // doesn't emit an ICE or a wrong diagnostic because of the static_cast. + + /** + * Constructs a FunctionInfo for a function. + * + * @param expected_header request header in the command buffer which will trigger dispatch + * to this handler + * @param handler_callback member function in this service which will be called to handle + * the request + * @param name human-friendly name for the request. Used mostly for logging purposes. + */ + FunctionInfo(u32 expected_header, HandlerFnP handler_callback, const char* name) + : FunctionInfoBase{ + expected_header, + // Type-erase member function pointer by casting it down to the base class. + static_cast>(handler_callback), name} {} + }; + + /** + * Initializes the handler with no functions installed. + * @param max_sessions Maximum number of sessions that can be + * connected to this service at the same time. + */ + ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions) + : ServiceFrameworkBase(service_name, max_sessions, Invoker) {} + + /// Registers handlers in the service. + template + void RegisterHandlers(const FunctionInfo (&functions)[N]) { + RegisterHandlers(functions, N); + } + + /** + * Registers handlers in the service. Usually prefer using the other RegisterHandlers + * overload in order to avoid needing to specify the array size. + */ + void RegisterHandlers(const FunctionInfo* functions, size_t n) { + RegisterHandlersBase(functions, n); + } + +private: + /** + * This function is used to allow invocation of pointers to handlers stored in the base class + * without needing to expose the type of this derived class. Pointers-to-member may require a + * fixup when being up or downcast, and thus code that does that needs to know the concrete type + * of the derived class in order to invoke one of it's functions through a pointer. + */ + static void Invoker(ServiceFrameworkBase* object, HandlerFnP member, + Kernel::HLERequestContext& ctx) { + // Cast back up to our original types and call the member function + (static_cast(object)->*static_cast>(member))(ctx); + } +}; + /// Initialize ServiceManager void Init(); @@ -110,6 +258,8 @@ void Shutdown(); /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC. extern std::unordered_map> g_kernel_named_ports; +/// Adds a port to the named port table +void AddNamedPort(std::string name, Kernel::SharedPtr port); /// Adds a service to the services table void AddService(Interface* interface_);