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 globally accessible points exposed by the server using CreatePort. Clients can start IPC sessions by connect 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).

Services

Services are anonymous ports: They can not be connected to through ConnectToPort because they don't have any name recognized by that function. Instead, a service is registered to the service manager port ("srv:") using the IPC command RegisterService. Clients then open sessions to services using the service manager command GetServiceHandle.

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.