mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-21 20:42:44 +02:00
496 lines
16 KiB
C
496 lines
16 KiB
C
/**
|
|
* @file service.h
|
|
* @brief Service wrapper object
|
|
* @author fincs
|
|
* @author SciresM
|
|
* @copyright libnx Authors
|
|
*/
|
|
#pragma once
|
|
#include "hipc.h"
|
|
#include "cmif.h"
|
|
|
|
/// Service object structure
|
|
typedef struct Service {
|
|
Handle session;
|
|
u32 own_handle;
|
|
u32 object_id;
|
|
u16 pointer_buffer_size;
|
|
} Service;
|
|
|
|
enum {
|
|
SfBufferAttr_In = BIT(0),
|
|
SfBufferAttr_Out = BIT(1),
|
|
SfBufferAttr_HipcMapAlias = BIT(2),
|
|
SfBufferAttr_HipcPointer = BIT(3),
|
|
SfBufferAttr_FixedSize = BIT(4),
|
|
SfBufferAttr_HipcAutoSelect = BIT(5),
|
|
SfBufferAttr_HipcMapTransferAllowsNonSecure = BIT(6),
|
|
SfBufferAttr_HipcMapTransferAllowsNonDevice = BIT(7),
|
|
};
|
|
|
|
typedef struct SfBufferAttrs {
|
|
u32 attr0;
|
|
u32 attr1;
|
|
u32 attr2;
|
|
u32 attr3;
|
|
u32 attr4;
|
|
u32 attr5;
|
|
u32 attr6;
|
|
u32 attr7;
|
|
} SfBufferAttrs;
|
|
|
|
typedef struct SfBuffer {
|
|
const void* ptr;
|
|
size_t size;
|
|
} SfBuffer;
|
|
|
|
typedef enum SfOutHandleAttr {
|
|
SfOutHandleAttr_None = 0,
|
|
SfOutHandleAttr_HipcCopy = 1,
|
|
SfOutHandleAttr_HipcMove = 2,
|
|
} SfOutHandleAttr;
|
|
|
|
typedef struct SfOutHandleAttrs {
|
|
SfOutHandleAttr attr0;
|
|
SfOutHandleAttr attr1;
|
|
SfOutHandleAttr attr2;
|
|
SfOutHandleAttr attr3;
|
|
SfOutHandleAttr attr4;
|
|
SfOutHandleAttr attr5;
|
|
SfOutHandleAttr attr6;
|
|
SfOutHandleAttr attr7;
|
|
} SfOutHandleAttrs;
|
|
|
|
typedef struct SfDispatchParams {
|
|
Handle target_session;
|
|
u32 context;
|
|
|
|
SfBufferAttrs buffer_attrs;
|
|
SfBuffer buffers[8];
|
|
|
|
bool in_send_pid;
|
|
|
|
u32 in_num_objects;
|
|
const Service* in_objects[8];
|
|
|
|
u32 in_num_handles;
|
|
Handle in_handles[8];
|
|
|
|
u32 out_num_objects;
|
|
Service* out_objects;
|
|
|
|
SfOutHandleAttrs out_handle_attrs;
|
|
Handle* out_handles;
|
|
} SfDispatchParams;
|
|
|
|
/**
|
|
* @brief Returns whether a service has been initialized.
|
|
* @param[in] s Service object.
|
|
* @return true if initialized.
|
|
*/
|
|
NX_CONSTEXPR bool serviceIsActive(Service* s) {
|
|
return s->session != INVALID_HANDLE;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns whether a service is overriden in the homebrew environment.
|
|
* @param[in] s Service object.
|
|
* @return true if overriden.
|
|
*/
|
|
NX_CONSTEXPR bool serviceIsOverride(Service* s) {
|
|
return serviceIsActive(s) && !s->own_handle && !s->object_id;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns whether a service is a domain.
|
|
* @param[in] s Service object.
|
|
* @return true if a domain.
|
|
*/
|
|
NX_CONSTEXPR bool serviceIsDomain(Service* s) {
|
|
return serviceIsActive(s) && s->own_handle && s->object_id;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns whether a service is a domain subservice.
|
|
* @param[in] s Service object.
|
|
* @return true if a domain subservice.
|
|
*/
|
|
NX_CONSTEXPR bool serviceIsDomainSubservice(Service* s) {
|
|
return serviceIsActive(s) && !s->own_handle && s->object_id;
|
|
}
|
|
|
|
/**
|
|
* @brief For a domain/domain subservice, return the associated object ID.
|
|
* @param[in] s Service object, necessarily a domain or domain subservice.
|
|
* @return The object ID.
|
|
*/
|
|
NX_CONSTEXPR u32 serviceGetObjectId(Service* s) {
|
|
return s->object_id;
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a service object from an IPC session handle.
|
|
* @param[out] s Service object.
|
|
* @param[in] h IPC session handle.
|
|
*/
|
|
NX_INLINE void serviceCreate(Service* s, Handle h)
|
|
{
|
|
s->session = h;
|
|
s->own_handle = 1;
|
|
s->object_id = 0;
|
|
s->pointer_buffer_size = 0;
|
|
cmifQueryPointerBufferSize(h, &s->pointer_buffer_size);
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a non-domain subservice object from a parent service.
|
|
* @param[out] s Service object.
|
|
* @param[in] parent Parent service.
|
|
* @param[in] h IPC session handle for this subservice.
|
|
*/
|
|
NX_INLINE void serviceCreateNonDomainSubservice(Service* s, Service* parent, Handle h)
|
|
{
|
|
if (h != INVALID_HANDLE) {
|
|
s->session = h;
|
|
s->own_handle = 1;
|
|
s->object_id = 0;
|
|
s->pointer_buffer_size = parent->pointer_buffer_size;
|
|
} else {
|
|
*s = (Service){};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a domain subservice object from a parent service.
|
|
* @param[out] s Service object.
|
|
* @param[in] parent Parent service, necessarily a domain or domain subservice.
|
|
* @param[in] object_id Object ID for this subservice.
|
|
*/
|
|
NX_CONSTEXPR void serviceCreateDomainSubservice(Service* s, Service* parent, u32 object_id)
|
|
{
|
|
if (object_id != 0) {
|
|
s->session = parent->session;
|
|
s->own_handle = 0;
|
|
s->object_id = object_id;
|
|
s->pointer_buffer_size = parent->pointer_buffer_size;
|
|
} else {
|
|
*s = (Service){};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Hints the compiler that a service will always contain a domain object.
|
|
* @param[in] _s Service object.
|
|
*/
|
|
#define serviceAssumeDomain(_s) do { \
|
|
if (!(_s)->object_id) \
|
|
__builtin_unreachable(); \
|
|
} while(0)
|
|
|
|
/**
|
|
* @brief Closes a service.
|
|
* @param[in] s Service object.
|
|
*/
|
|
NX_INLINE void serviceClose(Service* s)
|
|
{
|
|
#if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
|
|
if (s->object_id)
|
|
__builtin_unreachable();
|
|
#endif
|
|
|
|
if (s->own_handle || s->object_id) {
|
|
cmifMakeCloseRequest(armGetTls(), s->own_handle ? 0 : s->object_id);
|
|
svcSendSyncRequest(s->session);
|
|
if (s->own_handle)
|
|
svcCloseHandle(s->session);
|
|
}
|
|
*s = (Service){};
|
|
}
|
|
|
|
/**
|
|
* @brief Clones a service.
|
|
* @param[in] s Service object.
|
|
* @param[out] out_s Output service object.
|
|
*/
|
|
NX_INLINE Result serviceClone(Service* s, Service* out_s)
|
|
{
|
|
#if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
|
|
if (s->object_id)
|
|
__builtin_unreachable();
|
|
#endif
|
|
|
|
out_s->session = 0;
|
|
out_s->own_handle = 1;
|
|
out_s->object_id = s->object_id;
|
|
out_s->pointer_buffer_size = s->pointer_buffer_size;
|
|
return cmifCloneCurrentObject(s->session, &out_s->session);
|
|
}
|
|
|
|
/**
|
|
* @brief Clones a service with a session manager tag.
|
|
* @param[in] s Service object.
|
|
* @param[in] tag Session manager tag (unused in current official server code)
|
|
* @param[out] out_s Output service object.
|
|
*/
|
|
NX_INLINE Result serviceCloneEx(Service* s, u32 tag, Service* out_s)
|
|
{
|
|
#if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
|
|
if (s->object_id)
|
|
__builtin_unreachable();
|
|
#endif
|
|
|
|
out_s->session = 0;
|
|
out_s->own_handle = 1;
|
|
out_s->object_id = s->object_id;
|
|
out_s->pointer_buffer_size = s->pointer_buffer_size;
|
|
return cmifCloneCurrentObjectEx(s->session, tag, &out_s->session);
|
|
}
|
|
|
|
/**
|
|
* @brief Converts a regular service to a domain.
|
|
* @param[in] s Service object.
|
|
* @return Result code.
|
|
*/
|
|
NX_INLINE Result serviceConvertToDomain(Service* s)
|
|
{
|
|
if (!s->own_handle) {
|
|
// For overridden services, create a clone first.
|
|
Result rc = cmifCloneCurrentObjectEx(s->session, 0, &s->session);
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
s->own_handle = 1;
|
|
}
|
|
|
|
return cmifConvertCurrentObjectToDomain(s->session, &s->object_id);
|
|
}
|
|
|
|
NX_CONSTEXPR void _serviceRequestFormatProcessBuffer(CmifRequestFormat* fmt, u32 attr)
|
|
{
|
|
if (!attr) return;
|
|
const bool is_in = (attr & SfBufferAttr_In) != 0;
|
|
const bool is_out = (attr & SfBufferAttr_Out) != 0;
|
|
|
|
if (attr & SfBufferAttr_HipcAutoSelect) {
|
|
if (is_in)
|
|
fmt->num_in_auto_buffers ++;
|
|
if (is_out)
|
|
fmt->num_out_auto_buffers ++;
|
|
} else if (attr & SfBufferAttr_HipcPointer) {
|
|
if (is_in)
|
|
fmt->num_in_pointers ++;
|
|
if (is_out) {
|
|
if (attr & SfBufferAttr_FixedSize)
|
|
fmt->num_out_fixed_pointers ++;
|
|
else
|
|
fmt->num_out_pointers ++;
|
|
}
|
|
} else if (attr & SfBufferAttr_HipcMapAlias) {
|
|
if (is_in && is_out)
|
|
fmt->num_inout_buffers ++;
|
|
else if (is_in)
|
|
fmt->num_in_buffers ++;
|
|
else if (is_out)
|
|
fmt->num_out_buffers ++;
|
|
}
|
|
}
|
|
|
|
NX_CONSTEXPR void _serviceRequestProcessBuffer(CmifRequest* req, const SfBuffer* buf, u32 attr)
|
|
{
|
|
if (!attr) return;
|
|
const bool is_in = (attr & SfBufferAttr_In);
|
|
const bool is_out = (attr & SfBufferAttr_Out);
|
|
|
|
if (attr & SfBufferAttr_HipcAutoSelect) {
|
|
HipcBufferMode mode = HipcBufferMode_Normal;
|
|
if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure)
|
|
mode = HipcBufferMode_NonSecure;
|
|
if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice)
|
|
mode = HipcBufferMode_NonDevice;
|
|
if (is_in)
|
|
cmifRequestInAutoBuffer(req, buf->ptr, buf->size, mode);
|
|
if (is_out)
|
|
cmifRequestOutAutoBuffer(req, (void*)buf->ptr, buf->size, mode);
|
|
} else if (attr & SfBufferAttr_HipcPointer) {
|
|
if (is_in)
|
|
cmifRequestInPointer(req, buf->ptr, buf->size);
|
|
if (is_out) {
|
|
if (attr & SfBufferAttr_FixedSize)
|
|
cmifRequestOutFixedPointer(req, (void*)buf->ptr, buf->size);
|
|
else
|
|
cmifRequestOutPointer(req, (void*)buf->ptr, buf->size);
|
|
}
|
|
} else if (attr & SfBufferAttr_HipcMapAlias) {
|
|
HipcBufferMode mode = HipcBufferMode_Normal;
|
|
if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure)
|
|
mode = HipcBufferMode_NonSecure;
|
|
if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice)
|
|
mode = HipcBufferMode_NonDevice;
|
|
|
|
if (is_in && is_out)
|
|
cmifRequestInOutBuffer(req, (void*)buf->ptr, buf->size, mode);
|
|
else if (is_in)
|
|
cmifRequestInBuffer(req, buf->ptr, buf->size, mode);
|
|
else if (is_out)
|
|
cmifRequestOutBuffer(req, (void*)buf->ptr, buf->size, mode);
|
|
}
|
|
}
|
|
|
|
NX_INLINE void* serviceMakeRequest(
|
|
Service* s, u32 request_id, u32 context, u32 data_size, bool send_pid,
|
|
const SfBufferAttrs buffer_attrs, const SfBuffer* buffers,
|
|
u32 num_objects, const Service* const* objects,
|
|
u32 num_handles, const Handle* handles
|
|
) {
|
|
#if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
|
|
if (s->object_id)
|
|
__builtin_unreachable();
|
|
#endif
|
|
|
|
CmifRequestFormat fmt = {};
|
|
fmt.object_id = s->object_id;
|
|
fmt.request_id = request_id;
|
|
fmt.context = context;
|
|
fmt.data_size = data_size;
|
|
fmt.server_pointer_size = s->pointer_buffer_size;
|
|
fmt.num_objects = num_objects;
|
|
fmt.num_handles = num_handles;
|
|
fmt.send_pid = send_pid;
|
|
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr0);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr1);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr2);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr3);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr4);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr5);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr6);
|
|
_serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr7);
|
|
|
|
CmifRequest req = cmifMakeRequest(armGetTls(), fmt);
|
|
|
|
if (s->object_id) // TODO: Check behavior of input objects in non-domain sessions
|
|
for (u32 i = 0; i < num_objects; i ++)
|
|
cmifRequestObject(&req, objects[i]->object_id);
|
|
|
|
for (u32 i = 0; i < num_handles; i ++)
|
|
cmifRequestHandle(&req, handles[i]);
|
|
|
|
_serviceRequestProcessBuffer(&req, &buffers[0], buffer_attrs.attr0);
|
|
_serviceRequestProcessBuffer(&req, &buffers[1], buffer_attrs.attr1);
|
|
_serviceRequestProcessBuffer(&req, &buffers[2], buffer_attrs.attr2);
|
|
_serviceRequestProcessBuffer(&req, &buffers[3], buffer_attrs.attr3);
|
|
_serviceRequestProcessBuffer(&req, &buffers[4], buffer_attrs.attr4);
|
|
_serviceRequestProcessBuffer(&req, &buffers[5], buffer_attrs.attr5);
|
|
_serviceRequestProcessBuffer(&req, &buffers[6], buffer_attrs.attr6);
|
|
_serviceRequestProcessBuffer(&req, &buffers[7], buffer_attrs.attr7);
|
|
|
|
return req.data;
|
|
}
|
|
|
|
NX_CONSTEXPR void _serviceResponseGetHandle(CmifResponse* res, SfOutHandleAttr type, Handle* out)
|
|
{
|
|
switch (type) {
|
|
default:
|
|
case SfOutHandleAttr_None:
|
|
break;
|
|
case SfOutHandleAttr_HipcCopy:
|
|
*out = cmifResponseGetCopyHandle(res);
|
|
break;
|
|
case SfOutHandleAttr_HipcMove:
|
|
*out = cmifResponseGetMoveHandle(res);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NX_INLINE Result serviceParseResponse(
|
|
Service* s, u32 out_size, void** out_data,
|
|
u32 num_out_objects, Service* out_objects,
|
|
const SfOutHandleAttrs out_handle_attrs, Handle* out_handles
|
|
) {
|
|
#if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
|
|
if (s->object_id)
|
|
__builtin_unreachable();
|
|
#endif
|
|
|
|
CmifResponse res = {};
|
|
bool is_domain = s->object_id != 0;
|
|
Result rc = cmifParseResponse(&res, armGetTls(), is_domain, out_size);
|
|
if (R_FAILED(rc))
|
|
return rc;
|
|
|
|
if (out_size)
|
|
*out_data = res.data;
|
|
|
|
for (u32 i = 0; i < num_out_objects; i ++) {
|
|
if (is_domain)
|
|
serviceCreateDomainSubservice(&out_objects[i], s, cmifResponseGetObject(&res));
|
|
else // Output objects are marshalled as move handles at the beginning of the list.
|
|
serviceCreateNonDomainSubservice(&out_objects[i], s, cmifResponseGetMoveHandle(&res));
|
|
}
|
|
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr0, &out_handles[0]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr1, &out_handles[1]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr2, &out_handles[2]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr3, &out_handles[3]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr4, &out_handles[4]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr5, &out_handles[5]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr6, &out_handles[6]);
|
|
_serviceResponseGetHandle(&res, out_handle_attrs.attr7, &out_handles[7]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
NX_INLINE Result serviceDispatchImpl(
|
|
Service* s, u32 request_id,
|
|
const void* in_data, u32 in_data_size,
|
|
void* out_data, u32 out_data_size,
|
|
SfDispatchParams disp
|
|
)
|
|
{
|
|
// Make a copy of the service struct, so that the compiler can assume that it won't be modified by function calls.
|
|
Service srv = *s;
|
|
|
|
void* in = serviceMakeRequest(&srv, request_id, disp.context,
|
|
in_data_size, disp.in_send_pid,
|
|
disp.buffer_attrs, disp.buffers,
|
|
disp.in_num_objects, disp.in_objects,
|
|
disp.in_num_handles, disp.in_handles);
|
|
|
|
if (in_data_size)
|
|
__builtin_memcpy(in, in_data, in_data_size);
|
|
|
|
Result rc = svcSendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session);
|
|
if (R_SUCCEEDED(rc)) {
|
|
void* out = NULL;
|
|
rc = serviceParseResponse(&srv,
|
|
out_data_size, &out,
|
|
disp.out_num_objects, disp.out_objects,
|
|
disp.out_handle_attrs, disp.out_handles);
|
|
|
|
if (R_SUCCEEDED(rc) && out_data && out_data_size)
|
|
__builtin_memcpy(out_data, out, out_data_size);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define serviceMacroDetectIsSameType(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
|
|
#define serviceMacroDetectIsPointerOrArray(p) (__builtin_classify_type(p) == 5)
|
|
#define serviceMacroDecay(p) (&*__builtin_choose_expr(serviceMacroDetectIsPointerOrArray(p), p, NULL))
|
|
#define serviceMacroDetectIsPointer(p) serviceMacroDetectIsSameType(p, serviceMacroDecay(p))
|
|
|
|
#define serviceDispatch(_s,_rid,...) \
|
|
serviceDispatchImpl((_s),(_rid),NULL,0,NULL,0,(SfDispatchParams){ __VA_ARGS__ })
|
|
|
|
#define serviceDispatchIn(_s,_rid,_in,...) \
|
|
({ _Static_assert(!(serviceMacroDetectIsPointer(_in))); \
|
|
serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),NULL,0,(SfDispatchParams){ __VA_ARGS__ }); })
|
|
|
|
#define serviceDispatchOut(_s,_rid,_out,...) \
|
|
({ _Static_assert(!(serviceMacroDetectIsPointer(_out))); \
|
|
serviceDispatchImpl((_s),(_rid),NULL,0,&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }); })
|
|
|
|
#define serviceDispatchInOut(_s,_rid,_in,_out,...) \
|
|
({ _Static_assert(!(serviceMacroDetectIsPointer(_in))); \
|
|
_Static_assert(!(serviceMacroDetectIsPointer(_out))); \
|
|
serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }); })
|