IPC
The 3DS software stack follows a server/client architecture, where common functionality is provided by global server processes through interfaces called ports and services. Applications can access this functionality by creating sessions to the provided services. Technically, this is implemented using interprocess communication (IPC).
Concepts
Ports
Ports are IPC interfaces exposed by the server using CreatePort. Clients can start IPC sessions by connecting to a server through a port using ConnectToPort. On the client-side, a port is identified by a fixed string, while on the server-side it is represented by a kernel handle using which the server can wait for incoming connections (using svcWaitSynchronization on that handle). "Anonymous" ports can be created by not providing a port name to the kernel during creation. In this case, it is not possible to create a session through ConnectToPort - sessions can only be created if one has a handle to the port itself.
Services
Services are an abstraction of ports that can not be connected to through ConnectToPort, as the underlying port is an anonymous port created by service manager. Clients are instead expected to open a session using the service manager ("srv:") port command GetServiceHandle. A service is registered with service manager using the command RegisterService.
Sessions
Sessions are communication channels opened on a specific port. Clients use their session handle to send IPC commands to the server using SendSyncRequest, while servers use ReplyAndReceive to reply. In both cases, the kernel takes care of transferring IPC command data from the TLS of the sending process to the TLS of the receiving process.
Command Structure
IPC commands are written to the Thread Local Storage at offset 0x80 and submitted using svcSendSyncRequest. If the kernel was able to dispatch the request, the server reply will be written to TLS+0x80. Often the second word of the response data is the error code (or 0 if success).
Every IPC command sent to IPC server processes starts with a u32 header code. Parameters, if any, are written following this header. There are "normal parameters", which are fixed-size words, and there are "translate parameters", which are of flexible size and each of which begins with a header. The entire command has the following structure:
Word | Size | Description |
---|---|---|
0 | 1 | Header code |
1 | x | Normal parameters |
x | y | Translate parameters |
The IPC command header has the following structure:
Bits | Description |
---|---|
0-5 | Total size in words of the translate parameters (=y). Note that this is in general different from the number of translate parameters |
6-11 | Number of normal parameters (=x) |
12-15 | Unused |
16-31 | Command ID |
Each translate parameter starts with a translation descriptor:
Bits | Description |
---|---|
1-3 | type |
Translate parameters are modified/translated transparently by the kernel. They are used to transfer handles/buffers between the different processes.
The type of parameter is described by the bits 1-3 in the translation descriptor. Parameter types accepted for sending by the kernel are: 0, 1, 2, 5, 6, 7. Type 0 is used to send handles across processes:
if desc & 0x30 == 0x20: write process id to value else: translate handle if desc & 0x30 == 0x10: close handle for caller
For replies, only 0, 1, 5, 6, 7 are allowed. In other words any type 2 fields must be zeroed before calling svcReplyAndReceive on the server-side. For replies, type 0, 1, and 2 are ignored. Types 5, 6, and 7 do something with the mem pointer upon reply. The type 0 descriptor can be used to ignore parameters. The number of parameters covered by a type-0 descriptor is (desc >> 26) + 1.
Type | Usual form | Description |
---|---|---|
0 | 0x00000000 | ((num_handles-1)<<26)
<handle 0> <handle 1> ... |
The corresponding values are KHandles, that should be closed in calling process. Or zero otherwise. |
0 | 0x00000010 | ((num_handles-1)<<26)
<handle 0> <handle 1> ... |
The corresponding values are KHandles, that will be duplicated. Or zero otherwise. |
0 | 0x00000020
<placeholder> |
Let kernel set value to calling process ProcessID. |
1 | 0x00000002 | (size<<14) | (static_buffer_id<<10)
<ptr> |
The corresponding value contains a ptr to a buffer of said size, that should be copied to an already set-up buffer in destination process at Thread Local Storage offset 0x180 + static_buffer_id*8. The static_buffer_id is only 4 bits, making it possible for at most up to 16 buffers in total per thread. |
2 | 0x00000004 | (size<<8) | (id<<4)
<ptr> |
This is used for RW buffers over PXI. The address written to the destination cmd-buf is a phys-addr for a table located in the BASE memregion. This table contains the phys-addrs for the actual data, the array entries have the following format: {u32 *datachunk_physaddr, u32 datachunk_bytesize}. |
3 | 0x00000006 | (size<<8) | (id<<4)
<ptr> |
Same as above except for read-only buffer. |
4 | 0x00000008 | This command will cause a kernelpanic. |
5 | 0x0000000A | (size<<4)
<ptr> |
The corresponding value contains a ptr to a buffer of said size. It is mapped R- in the destination process?? |
6 | 0x0000000C | (size<<4)
<ptr> |
The corresponding value contains a ptr to a buffer of said size. It is mapped -W in the destination process?? |
7 | 0x0000000E | (size<<4)
<ptr> |
The corresponding value contains a ptr to a buffer of said size. It is mapped RW in the destination process?? |
Buffers from commands 5,6,7 will get mapped at virtual address 0x04000000+ in destination process.