Add support for the TIPC protocol introduced in HOS 12.0.0

TIPC is a much lighter layer ontop of the Horizon IPC system than CMIF and is used by SM in 12.0.0+. This implementation is slightly hacky since it doesn't really keep a seperation between the underlying kernel IPC stuff and userspace like CMIF/TIPC, this should be fixed eventually, probably together with an IPC dispatch rewrite to avoid the mess of frozen maps.

Tested with Hentai Uni, which now crashes needing 'ldr:ro'.
This commit is contained in:
Billy Laws 2022-09-02 23:01:44 +01:00
parent a40d7c78ad
commit 51f4e7662e
6 changed files with 92 additions and 60 deletions

View File

@ -10,6 +10,7 @@ namespace skyline::kernel::ipc {
u8 *pointer{tls}; u8 *pointer{tls};
header = reinterpret_cast<CommandHeader *>(pointer); header = reinterpret_cast<CommandHeader *>(pointer);
isTipc = static_cast<u16>(header->type) > static_cast<u16>(CommandType::TipcCloseSession);
pointer += sizeof(CommandHeader); pointer += sizeof(CommandHeader);
if (header->handleDesc) { if (header->handleDesc) {
@ -64,39 +65,43 @@ namespace skyline::kernel::ipc {
auto bufCPointer{pointer + header->rawSize * sizeof(u32)}; auto bufCPointer{pointer + header->rawSize * sizeof(u32)};
size_t offset{static_cast<size_t>(pointer - tls)}; // We calculate the relative offset as the absolute one might differ if (isTipc) {
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
pointer += padding;
if (isDomain && (header->type == CommandType::Request || header->type == CommandType::RequestWithContext)) {
domain = reinterpret_cast<DomainHeaderRequest *>(pointer);
pointer += sizeof(DomainHeaderRequest);
payload = reinterpret_cast<PayloadHeader *>(pointer);
pointer += sizeof(PayloadHeader);
cmdArg = pointer;
cmdArgSz = domain->payloadSz - sizeof(PayloadHeader);
pointer += cmdArgSz;
for (u8 index{}; domain->inputCount > index; index++) {
domainObjects.push_back(*reinterpret_cast<KHandle *>(pointer));
pointer += sizeof(KHandle);
}
} else {
payload = reinterpret_cast<PayloadHeader *>(pointer);
pointer += sizeof(PayloadHeader);
cmdArg = pointer; cmdArg = pointer;
cmdArgSz = header->rawSize * sizeof(u32); cmdArgSz = header->rawSize * sizeof(u32);
} else {
size_t offset{static_cast<size_t>(pointer - tls)}; // We calculate the relative offset as the absolute one might differ
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
pointer += padding;
if (isDomain && (header->type == CommandType::Request || header->type == CommandType::RequestWithContext)) {
domain = reinterpret_cast<DomainHeaderRequest *>(pointer);
pointer += sizeof(DomainHeaderRequest);
payload = reinterpret_cast<PayloadHeader *>(pointer);
pointer += sizeof(PayloadHeader);
cmdArg = pointer;
cmdArgSz = domain->payloadSz - sizeof(PayloadHeader);
pointer += cmdArgSz;
for (u8 index{}; domain->inputCount > index; index++) {
domainObjects.push_back(*reinterpret_cast<KHandle *>(pointer));
pointer += sizeof(KHandle);
}
} else {
payload = reinterpret_cast<PayloadHeader *>(pointer);
pointer += sizeof(PayloadHeader);
cmdArg = pointer;
cmdArgSz = header->rawSize * sizeof(u32);
}
} }
payloadOffset = cmdArg; payloadOffset = cmdArg;
if (payload->magic != util::MakeMagic<u32>("SFCI") && (header->type != CommandType::Control && header->type != CommandType::ControlWithContext && header->type != CommandType::Close) && (!domain || domain->command != DomainCommand::CloseVHandle)) // SFCI is the magic in received IPC messages if (!isTipc && payload->magic != util::MakeMagic<u32>("SFCI") && (header->type != CommandType::Control && header->type != CommandType::ControlWithContext && header->type != CommandType::Close) && (!domain || domain->command != DomainCommand::CloseVHandle)) // SFCI is the magic in received IPC messages
Logger::Debug("Unexpected Magic in PayloadHeader: 0x{:X}", static_cast<u32>(payload->magic)); Logger::Debug("Unexpected Magic in PayloadHeader: 0x{:X}", static_cast<u32>(payload->magic));
if (header->cFlag == BufferCFlag::SingleDescriptor) { if (header->cFlag == BufferCFlag::SingleDescriptor) {
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)}; auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
if (bufC->address) { if (bufC->address) {
@ -120,20 +125,25 @@ namespace skyline::kernel::ipc {
Logger::Verbose("Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", static_cast<bool>(handleDesc->sendPid), static_cast<u32>(handleDesc->copyCount), static_cast<u32>(handleDesc->moveCount)); Logger::Verbose("Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", static_cast<bool>(handleDesc->sendPid), static_cast<u32>(handleDesc->copyCount), static_cast<u32>(handleDesc->moveCount));
if (isDomain) if (isDomain)
Logger::Verbose("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->inputCount, domain->objectId); Logger::Verbose("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->inputCount, domain->objectId);
Logger::Verbose("Command ID: 0x{:X}", static_cast<u32>(payload->value));
if (isTipc)
Logger::Verbose("TIPC Command ID: 0x{:X}", static_cast<u16>(header->type));
else
Logger::Verbose("Command ID: 0x{:X}", static_cast<u32>(payload->value));
} }
} }
IpcResponse::IpcResponse(const DeviceState &state) : state(state) {} IpcResponse::IpcResponse(const DeviceState &state) : state(state) {}
void IpcResponse::WriteResponse(bool isDomain) { void IpcResponse::WriteResponse(bool isDomain, bool isTipc) {
auto tls{state.ctx->tpidrroEl0}; auto tls{state.ctx->tpidrroEl0};
u8 *pointer{tls}; u8 *pointer{tls};
memset(tls, 0, constant::TlsIpcSize); memset(tls, 0, constant::TlsIpcSize);
auto header{reinterpret_cast<CommandHeader *>(pointer)}; auto header{reinterpret_cast<CommandHeader *>(pointer)};
header->rawSize = static_cast<u32>((sizeof(PayloadHeader) + payload.size() + (domainObjects.size() * sizeof(KHandle)) + constant::IpcPaddingSum + (isDomain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo size_t sizeBytes{isTipc ? (payload.size() + sizeof(Result)) : (sizeof(PayloadHeader) + constant::IpcPaddingSum + payload.size() + (domainObjects.size() * sizeof(KHandle)) + (isDomain ? sizeof(DomainHeaderRequest) : 0))};
header->rawSize = static_cast<u32>(util::DivideCeil(sizeBytes, sizeof(u32))); // Size is in 32-bit units because Nintendo
header->handleDesc = (!copyHandles.empty() || !moveHandles.empty()); header->handleDesc = (!copyHandles.empty() || !moveHandles.empty());
pointer += sizeof(CommandHeader); pointer += sizeof(CommandHeader);
@ -154,33 +164,39 @@ namespace skyline::kernel::ipc {
} }
} }
size_t offset{static_cast<size_t>(pointer - tls)}; // We calculate the relative offset as the absolute one might differ if (isTipc) {
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front *reinterpret_cast<Result *>(pointer) = errorCode;
pointer += padding; pointer += sizeof(Result);
if (isDomain) {
auto domain{reinterpret_cast<DomainHeaderResponse *>(pointer)};
domain->outputCount = static_cast<u32>(domainObjects.size());
pointer += sizeof(DomainHeaderResponse);
}
auto payloadHeader{reinterpret_cast<PayloadHeader *>(pointer)};
payloadHeader->magic = util::MakeMagic<u32>("SFCO"); // SFCO is the magic in IPC responses
payloadHeader->version = 1;
payloadHeader->value = errorCode;
pointer += sizeof(PayloadHeader);
if (!payload.empty())
std::memcpy(pointer, payload.data(), payload.size()); std::memcpy(pointer, payload.data(), payload.size());
pointer += payload.size(); } else {
size_t offset{static_cast<size_t>(pointer - tls)}; // We calculate the relative offset as the absolute one might differ
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
pointer += padding;
if (isDomain) { if (isDomain) {
for (auto &domainObject : domainObjects) { auto domain{reinterpret_cast<DomainHeaderResponse *>(pointer)};
*reinterpret_cast<KHandle *>(pointer) = domainObject; domain->outputCount = static_cast<u32>(domainObjects.size());
pointer += sizeof(KHandle); pointer += sizeof(DomainHeaderResponse);
}
auto payloadHeader{reinterpret_cast<PayloadHeader *>(pointer)};
payloadHeader->magic = util::MakeMagic<u32>("SFCO"); // SFCO is the magic in IPC responses
payloadHeader->version = 1;
payloadHeader->value = errorCode;
pointer += sizeof(PayloadHeader);
if (!payload.empty())
std::memcpy(pointer, payload.data(), payload.size());
pointer += payload.size();
if (isDomain) {
for (auto &domainObject : domainObjects) {
*reinterpret_cast<KHandle *>(pointer) = domainObject;
pointer += sizeof(KHandle);
}
} }
} }
Logger::Verbose("Output: Raw Size: {}, Result: 0x{:X}, Copy Handles: {}, Move Handles: {}", static_cast<u32>(header->rawSize), static_cast<u32>(payloadHeader->value), copyHandles.size(), moveHandles.size()); Logger::Verbose("Output: Raw Size: {}, Result: 0x{:X}, Copy Handles: {}, Move Handles: {}", static_cast<u32>(header->rawSize), static_cast<u32>(errorCode), copyHandles.size(), moveHandles.size());
} }
} }

View File

@ -26,6 +26,7 @@ namespace skyline {
Control = 5, //!< A transaction between the client and IPC Manager Control = 5, //!< A transaction between the client and IPC Manager
RequestWithContext = 6, //!< Request with Token RequestWithContext = 6, //!< Request with Token
ControlWithContext = 7, //!< Control with Token ControlWithContext = 7, //!< Control with Token
TipcCloseSession = 0xF,
}; };
/** /**
@ -196,6 +197,7 @@ namespace skyline {
CommandHeader *header{}; CommandHeader *header{};
HandleDescriptor *handleDesc{}; HandleDescriptor *handleDesc{};
bool isDomain{}; //!< If this is a domain request bool isDomain{}; //!< If this is a domain request
bool isTipc; //!< If this is request uses the TIPC protocol
DomainHeaderRequest *domain{}; DomainHeaderRequest *domain{};
PayloadHeader *payload{}; PayloadHeader *payload{};
u8 *cmdArg{}; //!< A pointer to the data payload u8 *cmdArg{}; //!< A pointer to the data payload
@ -298,8 +300,9 @@ namespace skyline {
/** /**
* @brief Writes this IpcResponse object's contents into TLS * @brief Writes this IpcResponse object's contents into TLS
* @param isDomain Indicates if this is a domain response * @param isDomain Indicates if this is a domain response
* @param isTipc Indicates if this is a TIPC response
*/ */
void WriteResponse(bool isDomain); void WriteResponse(bool isDomain, bool isTipc = false);
}; };
} }
} }

View File

@ -21,11 +21,13 @@ namespace skyline::service {
Result service::BaseService::HandleRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result service::BaseService::HandleRequest(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
ServiceFunctionDescriptor function; ServiceFunctionDescriptor function;
u32 functionId{request.isTipc ? static_cast<u32>(request.header->type) : request.payload->value};
try { try {
function = GetServiceFunction(request.payload->value); function = GetServiceFunction(functionId, request.isTipc);
Logger::DebugNoPrefix("Service: {}", function.name); Logger::DebugNoPrefix("Service: {}", function.name);
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
Logger::Warn("Cannot find function in service '{0}': 0x{1:X} ({1})", GetName(), static_cast<u32>(request.payload->value)); Logger::Warn("Cannot find {0} function in service '{1}': 0x{2:X} ({2})", request.isTipc ? "TIPC" : "HIPC", GetName(), static_cast<u32>(functionId));
return {}; return {};
} }
TRACE_EVENT("service", perfetto::StaticString{function.name}); TRACE_EVENT("service", perfetto::StaticString{function.name});

View File

@ -5,8 +5,10 @@
#include <kernel/ipc.h> #include <kernel/ipc.h>
constexpr static skyline::u32 TipcFunctionIdFlag{1U << 31}; //!< Flag applied to the stored service function ID to differentiate between TIPC and HIPC functions
#define SERVICE_STRINGIFY(string) #string #define SERVICE_STRINGIFY(string) #string
#define SFUNC(id, Class, Function) std::pair<u32, std::pair<Result(Class::*)(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &), const char*>>{id, {&Class::Function, SERVICE_STRINGIFY(Class::Function)}} #define SFUNC(id, Class, Function) std::pair<u32, std::pair<Result(Class::*)(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &), const char*>>{id, {&Class::Function, SERVICE_STRINGIFY(Class::Function)}}
#define SFUNC_TIPC(id, Class, Function) std::pair<u32, std::pair<Result(Class::*)(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &), const char*>>{TipcFunctionIdFlag | id, {&Class::Function, SERVICE_STRINGIFY(Class::Function)}}
#define SFUNC_BASE(id, Class, BaseClass, Function) std::pair<u32, std::pair<Result(Class::*)(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &), const char*>>{id, {&Class::CallBaseFunction<BaseClass, decltype(&BaseClass::Function), &BaseClass::Function>, SERVICE_STRINGIFY(Class::Function)}} #define SFUNC_BASE(id, Class, BaseClass, Function) std::pair<u32, std::pair<Result(Class::*)(type::KSession &, ipc::IpcRequest &, ipc::IpcResponse &), const char*>>{id, {&Class::CallBaseFunction<BaseClass, decltype(&BaseClass::Function), &BaseClass::Function>, SERVICE_STRINGIFY(Class::Function)}}
#define SERVICE_DECL_AUTO(name, value) decltype(value) name = value #define SERVICE_DECL_AUTO(name, value) decltype(value) name = value
#define SERVICE_DECL(...) \ #define SERVICE_DECL(...) \
@ -15,10 +17,10 @@ template<typename BaseClass, typename BaseFunctionType, BaseFunctionType BaseFun
Result CallBaseFunction(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { \ Result CallBaseFunction(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { \
return (static_cast<BaseClass *>(this)->*BaseFunction)(session, request, response); \ return (static_cast<BaseClass *>(this)->*BaseFunction)(session, request, response); \
} \ } \
SERVICE_DECL_AUTO(functions, frozen::make_unordered_map({__VA_ARGS__})); \ SERVICE_DECL_AUTO(functions, frozen::make_unordered_map({__VA_ARGS__})); \
protected: \ protected: \
ServiceFunctionDescriptor GetServiceFunction(u32 id) override { \ ServiceFunctionDescriptor GetServiceFunction(u32 id, bool isTipc) override { \
auto& function{functions.at(id)}; \ auto& function{functions.at((isTipc ? TipcFunctionIdFlag : 0U) | id)}; \
return ServiceFunctionDescriptor{ \ return ServiceFunctionDescriptor{ \
reinterpret_cast<DerivedService*>(this), \ reinterpret_cast<DerivedService*>(this), \
reinterpret_cast<decltype(ServiceFunctionDescriptor::function)>(function.first), \ reinterpret_cast<decltype(ServiceFunctionDescriptor::function)>(function.first), \
@ -71,7 +73,7 @@ namespace skyline::service {
*/ */
virtual ~BaseService() = default; virtual ~BaseService() = default;
virtual ServiceFunctionDescriptor GetServiceFunction(u32 id) { virtual ServiceFunctionDescriptor GetServiceFunction(u32 id, bool isTipc) {
throw std::out_of_range("GetServiceFunction not implemented"); throw std::out_of_range("GetServiceFunction not implemented");
} }

View File

@ -238,11 +238,18 @@ namespace skyline::service {
break; break;
case ipc::CommandType::Close: case ipc::CommandType::Close:
case ipc::CommandType::TipcCloseSession:
Logger::Debug("Closing Session"); Logger::Debug("Closing Session");
CloseSession(handle); CloseSession(handle);
break; break;
default: default:
throw exception("Unimplemented IPC message type: {}", static_cast<u16>(request.header->type)); // TIPC command ID is encoded in the request type
if (request.isTipc) {
response.errorCode = session->serviceObject->HandleRequest(*session, request, response);
response.WriteResponse(session->isDomain, true);
} else {
throw exception("Unimplemented IPC message type: {}", static_cast<u16>(request.header->type));
}
} }
} else { } else {
Logger::Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle); Logger::Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle);

View File

@ -40,7 +40,9 @@ namespace skyline::service::sm {
SERVICE_DECL( SERVICE_DECL(
SFUNC(0x0, IUserInterface, Initialize), SFUNC(0x0, IUserInterface, Initialize),
SFUNC(0x1, IUserInterface, GetService) SFUNC_TIPC(0x10, IUserInterface, Initialize),
SFUNC(0x1, IUserInterface, GetService),
SFUNC_TIPC(0x11, IUserInterface, GetService)
) )
}; };
} }