libnx  v4.8.0
service.h
Go to the documentation of this file.
1 /**
2  * @file service.h
3  * @brief Service wrapper object
4  * @author fincs
5  * @author SciresM
6  * @copyright libnx Authors
7  */
8 #pragma once
9 #include <assert.h>
10 #include "hipc.h"
11 #include "cmif.h"
12 
13 /// Service object structure
14 typedef struct Service {
15  Handle session;
16  u32 own_handle;
17  u32 object_id;
18  u16 pointer_buffer_size;
19 } Service;
20 
21 enum {
22  SfBufferAttr_In = BIT(0),
23  SfBufferAttr_Out = BIT(1),
24  SfBufferAttr_HipcMapAlias = BIT(2),
25  SfBufferAttr_HipcPointer = BIT(3),
26  SfBufferAttr_FixedSize = BIT(4),
27  SfBufferAttr_HipcAutoSelect = BIT(5),
28  SfBufferAttr_HipcMapTransferAllowsNonSecure = BIT(6),
29  SfBufferAttr_HipcMapTransferAllowsNonDevice = BIT(7),
30 };
31 
32 typedef struct SfBufferAttrs {
33  u32 attr0;
34  u32 attr1;
35  u32 attr2;
36  u32 attr3;
37  u32 attr4;
38  u32 attr5;
39  u32 attr6;
40  u32 attr7;
42 
43 typedef struct SfBuffer {
44  const void* ptr;
45  size_t size;
46 } SfBuffer;
47 
48 typedef enum SfOutHandleAttr {
49  SfOutHandleAttr_None = 0,
50  SfOutHandleAttr_HipcCopy = 1,
51  SfOutHandleAttr_HipcMove = 2,
52 } SfOutHandleAttr;
53 
54 typedef struct SfOutHandleAttrs {
55  SfOutHandleAttr attr0;
56  SfOutHandleAttr attr1;
57  SfOutHandleAttr attr2;
58  SfOutHandleAttr attr3;
59  SfOutHandleAttr attr4;
60  SfOutHandleAttr attr5;
61  SfOutHandleAttr attr6;
62  SfOutHandleAttr attr7;
64 
65 typedef struct SfDispatchParams {
66  Handle target_session;
67  u32 context;
68 
69  SfBufferAttrs buffer_attrs;
70  SfBuffer buffers[8];
71 
72  bool in_send_pid;
73 
74  u32 in_num_objects;
75  const Service* in_objects[8];
76 
77  u32 in_num_handles;
78  Handle in_handles[8];
79 
80  u32 out_num_objects;
81  Service* out_objects;
82 
83  SfOutHandleAttrs out_handle_attrs;
84  Handle* out_handles;
86 
87 /**
88  * @brief Returns whether a service has been initialized.
89  * @param[in] s Service object.
90  * @return true if initialized.
91  */
93  return s->session != INVALID_HANDLE;
94 }
95 
96 /**
97  * @brief Returns whether a service is overriden in the homebrew environment.
98  * @param[in] s Service object.
99  * @return true if overriden.
100  */
102  return serviceIsActive(s) && !s->own_handle && !s->object_id;
103 }
104 
105 /**
106  * @brief Returns whether a service is a domain.
107  * @param[in] s Service object.
108  * @return true if a domain.
109  */
111  return serviceIsActive(s) && s->own_handle && s->object_id;
112 }
113 
114 /**
115  * @brief Returns whether a service is a domain subservice.
116  * @param[in] s Service object.
117  * @return true if a domain subservice.
118  */
120  return serviceIsActive(s) && !s->own_handle && s->object_id;
121 }
122 
123 /**
124  * @brief For a domain/domain subservice, return the associated object ID.
125  * @param[in] s Service object, necessarily a domain or domain subservice.
126  * @return The object ID.
127  */
129  return s->object_id;
130 }
131 
132 /**
133  * @brief Creates a service object from an IPC session handle.
134  * @param[out] s Service object.
135  * @param[in] h IPC session handle.
136  */
138 {
139  s->session = h;
140  s->own_handle = 1;
141  s->object_id = 0;
142  s->pointer_buffer_size = 0;
143  cmifQueryPointerBufferSize(h, &s->pointer_buffer_size);
144 }
145 
146 /**
147  * @brief Creates a non-domain subservice object from a parent service.
148  * @param[out] s Service object.
149  * @param[in] parent Parent service.
150  * @param[in] h IPC session handle for this subservice.
151  */
153 {
154  if (h != INVALID_HANDLE) {
155  s->session = h;
156  s->own_handle = 1;
157  s->object_id = 0;
158  s->pointer_buffer_size = parent->pointer_buffer_size;
159  } else {
160  *s = (Service){};
161  }
162 }
163 
164 /**
165  * @brief Creates a domain subservice object from a parent service.
166  * @param[out] s Service object.
167  * @param[in] parent Parent service, necessarily a domain or domain subservice.
168  * @param[in] object_id Object ID for this subservice.
169  */
171 {
172  if (object_id != 0) {
173  s->session = parent->session;
174  s->own_handle = 0;
175  s->object_id = object_id;
176  s->pointer_buffer_size = parent->pointer_buffer_size;
177  } else {
178  *s = (Service){};
179  }
180 }
181 
182 /**
183  * @brief Hints the compiler that a service will always contain a domain object.
184  * @param[in] _s Service object.
185  */
186 #define serviceAssumeDomain(_s) do { \
187  if (!(_s)->object_id) \
188  __builtin_unreachable(); \
189 } while(0)
190 
191 /**
192  * @brief Closes a service.
193  * @param[in] s Service object.
194  */
196 {
197 #if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
198  if (s->object_id)
199  __builtin_unreachable();
200 #endif
201 
202  if (s->own_handle || s->object_id) {
203  cmifMakeCloseRequest(armGetTls(), s->own_handle ? 0 : s->object_id);
204  svcSendSyncRequest(s->session);
205  if (s->own_handle)
206  svcCloseHandle(s->session);
207  }
208  *s = (Service){};
209 }
210 
211 /**
212  * @brief Clones a service.
213  * @param[in] s Service object.
214  * @param[out] out_s Output service object.
215  */
217 {
218 #if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
219  if (s->object_id)
220  __builtin_unreachable();
221 #endif
222 
223  out_s->session = 0;
224  out_s->own_handle = 1;
225  out_s->object_id = s->object_id;
226  out_s->pointer_buffer_size = s->pointer_buffer_size;
227  return cmifCloneCurrentObject(s->session, &out_s->session);
228 }
229 
230 /**
231  * @brief Clones a service with a session manager tag.
232  * @param[in] s Service object.
233  * @param[in] tag Session manager tag (unused in current official server code)
234  * @param[out] out_s Output service object.
235  */
237 {
238 #if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
239  if (s->object_id)
240  __builtin_unreachable();
241 #endif
242 
243  out_s->session = 0;
244  out_s->own_handle = 1;
245  out_s->object_id = s->object_id;
246  out_s->pointer_buffer_size = s->pointer_buffer_size;
247  return cmifCloneCurrentObjectEx(s->session, tag, &out_s->session);
248 }
249 
250 /**
251  * @brief Converts a regular service to a domain.
252  * @param[in] s Service object.
253  * @return Result code.
254  */
256 {
257  if (!s->own_handle) {
258  // For overridden services, create a clone first.
259  Result rc = cmifCloneCurrentObjectEx(s->session, 0, &s->session);
260  if (R_FAILED(rc))
261  return rc;
262  s->own_handle = 1;
263  }
264 
265  return cmifConvertCurrentObjectToDomain(s->session, &s->object_id);
266 }
267 
268 NX_CONSTEXPR void _serviceRequestFormatProcessBuffer(CmifRequestFormat* fmt, u32 attr)
269 {
270  if (!attr) return;
271  const bool is_in = (attr & SfBufferAttr_In) != 0;
272  const bool is_out = (attr & SfBufferAttr_Out) != 0;
273 
274  if (attr & SfBufferAttr_HipcAutoSelect) {
275  if (is_in)
276  fmt->num_in_auto_buffers ++;
277  if (is_out)
278  fmt->num_out_auto_buffers ++;
279  } else if (attr & SfBufferAttr_HipcPointer) {
280  if (is_in)
281  fmt->num_in_pointers ++;
282  if (is_out) {
283  if (attr & SfBufferAttr_FixedSize)
284  fmt->num_out_fixed_pointers ++;
285  else
286  fmt->num_out_pointers ++;
287  }
288  } else if (attr & SfBufferAttr_HipcMapAlias) {
289  if (is_in && is_out)
290  fmt->num_inout_buffers ++;
291  else if (is_in)
292  fmt->num_in_buffers ++;
293  else if (is_out)
294  fmt->num_out_buffers ++;
295  }
296 }
297 
298 NX_CONSTEXPR void _serviceRequestProcessBuffer(CmifRequest* req, const SfBuffer* buf, u32 attr)
299 {
300  if (!attr) return;
301  const bool is_in = (attr & SfBufferAttr_In);
302  const bool is_out = (attr & SfBufferAttr_Out);
303 
304  if (attr & SfBufferAttr_HipcAutoSelect) {
305  HipcBufferMode mode = HipcBufferMode_Normal;
306  if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure)
307  mode = HipcBufferMode_NonSecure;
308  if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice)
309  mode = HipcBufferMode_NonDevice;
310  if (is_in)
311  cmifRequestInAutoBuffer(req, buf->ptr, buf->size, mode);
312  if (is_out)
313  cmifRequestOutAutoBuffer(req, (void*)buf->ptr, buf->size, mode);
314  } else if (attr & SfBufferAttr_HipcPointer) {
315  if (is_in)
316  cmifRequestInPointer(req, buf->ptr, buf->size);
317  if (is_out) {
318  if (attr & SfBufferAttr_FixedSize)
319  cmifRequestOutFixedPointer(req, (void*)buf->ptr, buf->size);
320  else
321  cmifRequestOutPointer(req, (void*)buf->ptr, buf->size);
322  }
323  } else if (attr & SfBufferAttr_HipcMapAlias) {
324  HipcBufferMode mode = HipcBufferMode_Normal;
325  if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure)
326  mode = HipcBufferMode_NonSecure;
327  if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice)
328  mode = HipcBufferMode_NonDevice;
329 
330  if (is_in && is_out)
331  cmifRequestInOutBuffer(req, (void*)buf->ptr, buf->size, mode);
332  else if (is_in)
333  cmifRequestInBuffer(req, buf->ptr, buf->size, mode);
334  else if (is_out)
335  cmifRequestOutBuffer(req, (void*)buf->ptr, buf->size, mode);
336  }
337 }
338 
339 NX_INLINE void* serviceMakeRequest(
340  Service* s, u32 request_id, u32 context, u32 data_size, bool send_pid,
341  const SfBufferAttrs buffer_attrs, const SfBuffer* buffers,
342  u32 num_objects, const Service* const* objects,
343  u32 num_handles, const Handle* handles
344 ) {
345 #if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
346  if (s->object_id)
347  __builtin_unreachable();
348 #endif
349 
350  CmifRequestFormat fmt = {};
351  fmt.object_id = s->object_id;
352  fmt.request_id = request_id;
353  fmt.context = context;
354  fmt.data_size = data_size;
355  fmt.server_pointer_size = s->pointer_buffer_size;
356  fmt.num_objects = num_objects;
357  fmt.num_handles = num_handles;
358  fmt.send_pid = send_pid;
359 
360  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr0);
361  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr1);
362  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr2);
363  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr3);
364  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr4);
365  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr5);
366  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr6);
367  _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr7);
368 
369  CmifRequest req = cmifMakeRequest(armGetTls(), fmt);
370 
371  if (s->object_id) // TODO: Check behavior of input objects in non-domain sessions
372  for (u32 i = 0; i < num_objects; i ++)
373  cmifRequestObject(&req, objects[i]->object_id);
374 
375  for (u32 i = 0; i < num_handles; i ++)
376  cmifRequestHandle(&req, handles[i]);
377 
378  _serviceRequestProcessBuffer(&req, &buffers[0], buffer_attrs.attr0);
379  _serviceRequestProcessBuffer(&req, &buffers[1], buffer_attrs.attr1);
380  _serviceRequestProcessBuffer(&req, &buffers[2], buffer_attrs.attr2);
381  _serviceRequestProcessBuffer(&req, &buffers[3], buffer_attrs.attr3);
382  _serviceRequestProcessBuffer(&req, &buffers[4], buffer_attrs.attr4);
383  _serviceRequestProcessBuffer(&req, &buffers[5], buffer_attrs.attr5);
384  _serviceRequestProcessBuffer(&req, &buffers[6], buffer_attrs.attr6);
385  _serviceRequestProcessBuffer(&req, &buffers[7], buffer_attrs.attr7);
386 
387  return req.data;
388 }
389 
390 NX_CONSTEXPR void _serviceResponseGetHandle(CmifResponse* res, SfOutHandleAttr type, Handle* out)
391 {
392  switch (type) {
393  default:
394  case SfOutHandleAttr_None:
395  break;
396  case SfOutHandleAttr_HipcCopy:
397  *out = cmifResponseGetCopyHandle(res);
398  break;
399  case SfOutHandleAttr_HipcMove:
400  *out = cmifResponseGetMoveHandle(res);
401  break;
402  }
403 }
404 
405 NX_INLINE Result serviceParseResponse(
406  Service* s, u32 out_size, void** out_data,
407  u32 num_out_objects, Service* out_objects,
408  const SfOutHandleAttrs out_handle_attrs, Handle* out_handles
409 ) {
410 #if defined(NX_SERVICE_ASSUME_NON_DOMAIN)
411  if (s->object_id)
412  __builtin_unreachable();
413 #endif
414 
415  CmifResponse res = {};
416  bool is_domain = s->object_id != 0;
417  Result rc = cmifParseResponse(&res, armGetTls(), is_domain, out_size);
418  if (R_FAILED(rc))
419  return rc;
420 
421  if (out_size)
422  *out_data = res.data;
423 
424  for (u32 i = 0; i < num_out_objects; i ++) {
425  if (is_domain)
426  serviceCreateDomainSubservice(&out_objects[i], s, cmifResponseGetObject(&res));
427  else // Output objects are marshalled as move handles at the beginning of the list.
428  serviceCreateNonDomainSubservice(&out_objects[i], s, cmifResponseGetMoveHandle(&res));
429  }
430 
431  _serviceResponseGetHandle(&res, out_handle_attrs.attr0, &out_handles[0]);
432  _serviceResponseGetHandle(&res, out_handle_attrs.attr1, &out_handles[1]);
433  _serviceResponseGetHandle(&res, out_handle_attrs.attr2, &out_handles[2]);
434  _serviceResponseGetHandle(&res, out_handle_attrs.attr3, &out_handles[3]);
435  _serviceResponseGetHandle(&res, out_handle_attrs.attr4, &out_handles[4]);
436  _serviceResponseGetHandle(&res, out_handle_attrs.attr5, &out_handles[5]);
437  _serviceResponseGetHandle(&res, out_handle_attrs.attr6, &out_handles[6]);
438  _serviceResponseGetHandle(&res, out_handle_attrs.attr7, &out_handles[7]);
439 
440  return 0;
441 }
442 
443 NX_INLINE Result serviceDispatchImpl(
444  Service* s, u32 request_id,
445  const void* in_data, u32 in_data_size,
446  void* out_data, u32 out_data_size,
447  SfDispatchParams disp
448 )
449 {
450  // Make a copy of the service struct, so that the compiler can assume that it won't be modified by function calls.
451  Service srv = *s;
452 
453  void* in = serviceMakeRequest(&srv, request_id, disp.context,
454  in_data_size, disp.in_send_pid,
455  disp.buffer_attrs, disp.buffers,
456  disp.in_num_objects, disp.in_objects,
457  disp.in_num_handles, disp.in_handles);
458 
459  if (in_data_size)
460  __builtin_memcpy(in, in_data, in_data_size);
461 
462  Result rc = svcSendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session);
463  if (R_SUCCEEDED(rc)) {
464  void* out = NULL;
465  rc = serviceParseResponse(&srv,
466  out_data_size, &out,
467  disp.out_num_objects, disp.out_objects,
468  disp.out_handle_attrs, disp.out_handles);
469 
470  if (R_SUCCEEDED(rc) && out_data && out_data_size)
471  __builtin_memcpy(out_data, out, out_data_size);
472  }
473 
474  return rc;
475 }
476 
477 #ifndef __cplusplus
478 
479 #define serviceMacroDetectIsSameType(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
480 #define serviceMacroDetectIsPointerOrArray(p) (__builtin_classify_type(p) == 5)
481 #define serviceMacroDecay(p) (&*__builtin_choose_expr(serviceMacroDetectIsPointerOrArray(p), p, NULL))
482 #define serviceMacroDetectIsPointer(p) serviceMacroDetectIsSameType(p, serviceMacroDecay(p))
483 
484 #else
485 
486 extern "C++" {
487 
488 namespace libnx::impl {
489 
490  template<typename T> struct is_pointer { static constexpr bool value = false; };
491  template<typename T> struct is_pointer<T*> { static constexpr bool value = true; };
492  template<typename T> struct is_pointer<T* const> { static constexpr bool value = true; };
493  template<typename T> struct is_pointer<T* volatile> { static constexpr bool value = true; };
494  template<typename T> struct is_pointer<T* const volatile> { static constexpr bool value = true; };
495 
496 }
497 
498 }
499 
500 #define serviceMacroDetectIsPointer(p) (::libnx::impl::is_pointer<decltype(p)>::value)
501 
502 #endif
503 
504 #define serviceDispatch(_s,_rid,...) \
505  serviceDispatchImpl((_s),(_rid),NULL,0,NULL,0,(SfDispatchParams){ __VA_ARGS__ })
506 
507 #define serviceDispatchIn(_s,_rid,_in,...) \
508  ({ static_assert(!(serviceMacroDetectIsPointer(_in))); \
509  serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),NULL,0,(SfDispatchParams){ __VA_ARGS__ }); })
510 
511 #define serviceDispatchOut(_s,_rid,_out,...) \
512  ({ static_assert(!(serviceMacroDetectIsPointer(_out))); \
513  serviceDispatchImpl((_s),(_rid),NULL,0,&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }); })
514 
515 #define serviceDispatchInOut(_s,_rid,_in,_out,...) \
516  ({ static_assert(!(serviceMacroDetectIsPointer(_in))); \
517  static_assert(!(serviceMacroDetectIsPointer(_out))); \
518  serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }); })
Common Message Interface Framework protocol.
Horizon Inter-Process Communication protocol.
#define R_FAILED(res)
Checks whether a result code indicates failure.
Definition: result.h:12
#define R_SUCCEEDED(res)
Checks whether a result code indicates success.
Definition: result.h:10
static bool serviceIsOverride(Service *s)
Returns whether a service is overriden in the homebrew environment.
Definition: service.h:101
static Result serviceClone(Service *s, Service *out_s)
Clones a service.
Definition: service.h:216
static bool serviceIsDomainSubservice(Service *s)
Returns whether a service is a domain subservice.
Definition: service.h:119
static Result serviceCloneEx(Service *s, u32 tag, Service *out_s)
Clones a service with a session manager tag.
Definition: service.h:236
static void serviceCreateNonDomainSubservice(Service *s, Service *parent, Handle h)
Creates a non-domain subservice object from a parent service.
Definition: service.h:152
static Result serviceConvertToDomain(Service *s)
Converts a regular service to a domain.
Definition: service.h:255
static bool serviceIsActive(Service *s)
Returns whether a service has been initialized.
Definition: service.h:92
static void serviceCreateDomainSubservice(Service *s, Service *parent, u32 object_id)
Creates a domain subservice object from a parent service.
Definition: service.h:170
static bool serviceIsDomain(Service *s)
Returns whether a service is a domain.
Definition: service.h:110
static u32 serviceGetObjectId(Service *s)
For a domain/domain subservice, return the associated object ID.
Definition: service.h:128
static void serviceClose(Service *s)
Closes a service.
Definition: service.h:195
static void serviceCreate(Service *s, Handle h)
Creates a service object from an IPC session handle.
Definition: service.h:137
Definition: cmif.h:59
Definition: cmif.h:78
Definition: cmif.h:87
Service object structure.
Definition: service.h:14
Definition: service.h:32
Definition: service.h:43
Definition: service.h:65
Definition: service.h:54
Result svcCloseHandle(Handle handle)
Closes a handle, decrementing the reference count of the corresponding kernel object.
Result svcSendSyncRequest(Handle session)
Sends an IPC synchronization request to a session.
static void * armGetTls(void)
Gets the thread local storage buffer.
Definition: tls.h:14
#define NX_INLINE
Flags a function as (always) inline.
Definition: types.h:86
#define BIT(n)
Creates a bitmask from a bit number.
Definition: types.h:54
#define INVALID_HANDLE
Invalid handle.
Definition: types.h:96
u32 Handle
Kernel object handle.
Definition: types.h:43
uint16_t u16
16-bit unsigned integer.
Definition: types.h:20
u32 Result
Function error code result type.
Definition: types.h:44
#define NX_CONSTEXPR
Flags a function as constexpr in C++14 and above; or as (always) inline otherwise.
Definition: types.h:92
uint32_t u32
32-bit unsigned integer.
Definition: types.h:21