Difference between revisions of "HIPC"

From Nintendo Switch Brew
Jump to navigation Jump to search
(→‎IPC buffers: Add details on C/X descriptors)
m (→‎Header1Tag: Fix bits)
 
(23 intermediate revisions by 7 users not shown)
Line 1: Line 1:
== IPC Command Structure ==
+
HIPC (Horizon Inter-Process Communication) is a custom IPC implementation tailored for the Horizon OS.
This is an array of u32's, usually located in [[Thread Local Storage]].
+
 
 +
= Structure =
 +
This is a buffer located in the [[Thread Local Region]].
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
 
 
|-
 
|-
| 0 || 15-0 || [[#Type|Type]].
+
! Offset || Size || Description
 
|-
 
|-
| 0 || 19-16 || Number of buf X descriptors (each: 2 words).
+
| 0x0 || 0x8 || [[#HeaderData|HeaderData]]
 
|-
 
|-
| 0 || 23-20 || Number of buf A descriptors (each: 3 words).
+
| 0x8 || Variable || [[#SpecialData|SpecialData]] (if HasSpecial is set)
 
|-
 
|-
| 0 || 27-24 || Number of buf B descriptors (each: 3 words).
+
| Variable || Variable || Array of [[#PointerData|PointerData]]
 
|-
 
|-
| 0 || 31-28 || Number of buf W desciptors (each: 3 words), not observed
+
| Variable || Variable || Array of [[#MapData|SendData]]
 
|-
 
|-
| 1 || 9-0 || Size of [[#Raw data section|raw data]] in u32s.
+
| Variable || Variable || Array of [[#MapData|ReceiveData]]
 
|-
 
|-
| 1 || 13-10 || Flags for buf C descriptor.
+
| Variable || Variable || Array of [[#MapData|ExchangeData]]
 
|-
 
|-
| 1 || 30-20 || ?
+
| Variable || Variable || [[#RawData|RawData]]
 +
|-
 +
| Variable || Variable || Array of [[#ReceiveListData|ReceiveListData]]
 +
|}
 +
 
 +
[[#HeaderData|HeaderData]] and [[#SpecialTag|SpecialHeaderData]] (if available) are copied as-is from one process to another.
 +
 
 +
Sysmodules load the last u64 of rawdata when handling the [[#SpecialData|SpecialData]] ProcessId. This is not written by kernel. For sysmodule handling:
 +
* In some cases: these commands require a placeholder u64 value passed in the input parameters, as mentioned above. In these cases the OverwriteClientProcessId method is called to replace the value before it is used.
 +
* In other cases: The rawdata_u64 is compared with the ProcessId from [[#SpecialData|SpecialData]]. On mismatch and when rawdata_u64!=0, error 0x60A is returned. The ProcessId value passed to the cmdhandler vtable funcptr is the rawdata_u64.
 +
 
 +
Handle 0 is allowed, and just means no handle was sent.
 +
 
 +
A reply must not use Send/Receive/Exchange data buffers, svcReplyAndReceive will return 0xE801. [[SVC|MemoryAttribute]] IsBorrowed and IsUncached are never allowed for the source address. "Send" means buffer is sent from source process into service process, "Receive" means that data is copied from service process into user process and "Exchange" means both "Send" and "Receive".
 +
 
 +
[[#HeaderData|HeaderData]]'s ReceiveListCount field controls the behavior of [[#ReceiveListData|ReceiveListData]].
 +
 
 +
If ReceiveListCount is 0, [[#ReceiveListData|ReceiveListData]] is disabled. If ReceiveListCount is 1, there is an "inlined" [[#ReceiveListData|ReceiveListData]] buffer after the raw data (received data is copied to ROUND_UP(cmdbuf+raw_size+index, 16)). If ReceiveListCount is 2, there is a single [[#ReceiveListData|ReceiveListData]] buffer. Otherwise it has (ReceiveListCount-2) [[#ReceiveListData|ReceiveListData]] buffers. In this case, ReceiveIndex picks which [[#ReceiveListData|ReceiveListData]] buffer to copy received data to [instead of picking the offset into the buffer]. Data sent with this method must have MemoryState 0x4000000 mask set.
 +
 
 +
After reply, [[#PointerData|PointerData]] buffers are written to the sender containing the AddressValue, Size and ReceiveIndex that were copied to.
 +
 
 +
== RawData ==
 +
[[File:Ipc msg buffer type a example.png|thumb|An example of an IPC message with a type 0xA (OutPointer) buffer in it. Red is headers/descriptors, yellow is padding, and blue is data/buffer lengths. Note that the size of the u16 array for type 0xA lengths is padded to fill up a whole word.]]
 +
 
 +
{| class="wikitable" border="1"
 
|-
 
|-
| 1 || 31 || Enable handle descriptor.
+
! Offset || Size || Description
 
|-
 
|-
| ... || || [[#Handle descriptor|Handle descriptor]], if enabled.
+
| Variable || Variable || Reserved (padding to align to 16 bytes)
 
|-
 
|-
| ... || || [[#Buffer descriptor X "Pointer"|Buf X descriptors]], each one 2 words.
+
| Variable || Variable || [[#Payload|Payload]] or [[#Domain|Domain]]  
 
|-
 
|-
| ... || || [[#Buffer descriptor A/B/W "Send"/"Receive"/"Exchange"|Buf A descriptors]], each one 3 words.
+
| Variable || Variable || Reserved (padding to align to 16 bytes)
 
|-
 
|-
| ... || || [[#Buffer descriptor A/B/W "Send"/"Receive"/"Exchange"|Buf B descriptors]], each one 3 words.
+
| Variable || Variable || Buffer type 0xA (OutPointer) lengths (u16 array)
 +
|}
 +
 
 +
The total amount of padding within the raw data section is always 0x10 bytes. This means that if no padding is required before the message, there will be 0x10 bytes of padding after the message (before the buffer type 0xA (OutPointer) - lengths).
 +
 
 +
=== Payload ===
 +
This is an array of u32s, but individual parameters are generally stored as u64s.
 +
 
 +
{| class="wikitable" border="1"
 
|-
 
|-
| ... || || [[#Buffer descriptor A/B/W "Send"/"Receive"/"Exchange"|Type W descriptors]], each one 3 words.
+
! Offset || Size || Description
 
|-
 
|-
| ... || || [[#Raw_data_section|Raw data section]] (including padding before and after aligned data section).
+
| 0x0 || 0x10 || [[#CmifInHeader|CmifInHeader]] or [[#CmifOutHeader|CmifOutHeader]]
 
|-
 
|-
| ... || || [[#Buffer descriptor C "ReceiveList"|Buf C descriptors]], each one 2 words.
+
| 0x10 || Variable || Input parameters or return values
 
|}
 
|}
  
First two header u32's and handle descriptor (if enabled) are copied as-is from one process to the other.
+
The rawdata struct for input parameters/return values is generated by stable-sorting function parameters by alignment, from low to high. It is likely this is a mistake, as it generates structs with suboptimal possible padding -- Nintendo probably meant to sort from high to low (which would give minimized padding), but couldn't/can't change this without breaking backwards compatibility.
  
=== Type ===
+
=== Domain ===
IPC commands can have different types which influence how the IPC server processes requests in "nn::sf::hipc::server::HipcServerSessionManagerBase::ProcessRequest".
+
Because the switch has relatively low limits on the total number of sessions available to the system (Kernel slabheap limits, sysmodule handle table size limits), HIPC supports a "Domains" feature that allows multiplexing multiple service sessions through a single handle. Domains store (effectively) a mapping from u32 object id to a SharedPointer<IServiceObject> -- When messages are sent to a domain, an extra header is sent in the raw data section (before anything else) with information about what object in the domain is being acted on; responses similarly contain an additional header. Official session code implements this by just using the dispatch table for the object in the map with the appropriate ID, instead of the dispatch table the session was initialized with.
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
 
|-
 
|-
! Value || Name
+
! Offset || Size || Description
 
|-
 
|-
| 0 || Invalid
+
| 0x0 || 0x10 || [[#CmifDomainMessageInHeader|CmifDomainMessageInHeader]] or [[#CmifDomainMessageOutHeader|CmifDomainMessageOutHeader]]
 +
|-
 +
| 0x10 || Variable || [[#Payload|Payload]] (size must be InRawSize, only for input domain messages)
 
|-
 
|-
| 1 || [[#LegacyRequest, LegacyControl|LegacyRequest]]
+
| Variable || Variable || Array of [[#CmifDomainObjectId|CmifDomainObjectId]] (count must be InObjectCount, only for input domain messages)
 +
|}
 +
 
 +
= Messages =
 +
IPC messages can have different types which influence how the IPC server processes requests. The message type value is passed in the lower 16 bits of the first [[#HeaderData|HeaderData]] word (see [[#Header0Tag|Header0Tag]]).
 +
 
 +
== InvokeMethod ==
 +
This is a normal message to be processed using the regular marshalling system. Now deprecated, this type of message would be copied first to an intermediary set of internal structures by the server.
 +
 
 +
== Release ==
 +
This is a message that tells to close and destroy the server session.
 +
 
 +
== InvokeManagerMethod ==
 +
This is a message that requests a server manager operation. Now deprecated, this type of message would be copied first to an intermediary set of internal structures by the server.
 +
 
 +
{| class="wikitable" border="1"
 
|-
 
|-
| 2 || [[#Close|Close]]
+
! Cmd || Name
 
|-
 
|-
| 3 || [[#LegacyRequest, LegacyControl|LegacyControl]]
+
| 0 || [[#ConvertCurrentObjectToDomain]]
 
|-
 
|-
| 4 || [[#Request, Control|Request]]
+
| 1 || [[#CopyFromCurrentDomain]]
 
|-
 
|-
| 5 || [[#Request, Control|Control]]
+
| 2 || [[#CloneCurrentObject]]
 
|-
 
|-
| 6 || [5.0.0+] [[#RequestWithContext, ControlWithContext|RequestWithContext]]
+
| 3 || [[#QueryPointerBufferSize]]
 
|-
 
|-
| 7 || [5.0.0+] [[#RequestWithContext, ControlWithContext|ControlWithContext]]
+
| 4 || [[#CloneCurrentObjectEx]]  
 
|}
 
|}
  
==== Close ====
+
=== ConvertCurrentObjectToDomain ===
When processing a request of this type, the IPC server calls:
+
No input. Returns an output [[#CmifDomainObjectId|CmifDomainObjectId]].
* "nn::sf::hipc::server::HipcServerSessionManager::DestroyServerSession"
 
* "nn::sf::hipc::CloseServerSessionHandle"
 
  
This ensures that the server session is destroyed internally and properly closed.
+
=== CopyFromCurrentDomain ===
 +
Takes an input [[#CmifDomainObjectId|CmifDomainObjectId]]. Returns an output handle.
  
==== LegacyRequest, LegacyControl ====
+
=== CloneCurrentObject ===
These types are handled by calling:
+
No input. Returns an output handle.
* "nn::sf::hipc::detail::HipcMessageBufferAccessor::ParseHeader"
 
* "nn::sf::hipc::server::HipcServerSessionManager::ProcessMessage"
 
* "nn::sf::hipc::Reply"
 
* "nn::sf::hipc::server::HipcServerSessionManager::RegisterServerSessionToWaitBase"
 
  
It is speculated that these are part of an older message processing system where headers were further partitioned.
+
=== QueryPointerBufferSize ===
 +
No input. Returns an output u16 '''PointerBufferSize'''.
  
==== Request, Control ====
+
=== CloneCurrentObjectEx ===
These types are handled by calling:
+
Takes an input u32 '''Tag'''. Returns an output handle.
* "nn::sf::hipc::server::HipcServerSessionManager::ProcessMessage2"
 
* "nn::sf::hipc::server::HipcServerSessionManager::RegisterServerSessionToWaitBase"
 
  
This represents a more modern message handling system where contents follow the general marshalling structure.
+
== Invoke2Method ==
 +
Same as [[#InvokeMethod|InvokeMethod]] but using a more streamlined logic that no longer requires additional internal copying and parsing.
  
==== RequestWithContext, ControlWithContext ====
+
== Invoke2ManagerMethod ==
These are identical to normal Request and Control types, but with the additional requirement of suppling a token in their [[#Data payload|data payload]].
+
Same as [[#InvokeManagerMethod|InvokeManagerMethod]] but using a more streamlined logic that no longer requires additional internal copying and parsing.
 +
 
 +
== Invoke2MethodWithContext ==
 +
Same as [[#Invoke2Method|Invoke2Method]] but with the additional requirement of suppling a token in the [[#Data payload|data payload]].
  
 
This token is used by "nn::sf::cmif::SetInlineContext" which has the sole purpose of saving it into the TLS in order for it to be distributed to any IPC commands that are made while processing the current command. It's unknown if this token serves any purpose or if it's just a debug-tool to figure out what IPC command caused a particular chain of commands.
 
This token is used by "nn::sf::cmif::SetInlineContext" which has the sole purpose of saving it into the TLS in order for it to be distributed to any IPC commands that are made while processing the current command. It's unknown if this token serves any purpose or if it's just a debug-tool to figure out what IPC command caused a particular chain of commands.
  
=== Handle descriptor ===
+
== Invoke2ManagerMethodWithContext ==
There can only be one of this descriptor type. It is enabled by bit31 of the second word.
+
Same as [[#Invoke2ManagerMethod|Invoke2ManagerMethod]] but with the additional requirement of suppling a token in the [[#Data payload|data payload]].
 +
 
 +
This token is used by "nn::sf::cmif::SetInlineContext" which has the sole purpose of saving it into the TLS in order for it to be distributed to any IPC commands that are made while processing the current command. It's unknown if this token serves any purpose or if it's just a debug-tool to figure out what IPC command caused a particular chain of commands.
 +
 
 +
= Server =
 +
Send/Receive/Exchange data buffers map memory into the sysmodule process. For the mapped memory in the sysmodule the permissions are: Send = R--, Receive = RW-. The buffer is automatically unmapped while the kernel handles the cmdreply, the sysmodule doesn't need to specify anything in the cmdreply to trigger this.
 +
 
 +
This memory is mapped in the sysmodule to the same vaddr from the original user-process cmd-request, except with with bits >=(~28(?)) changed to a different ASLR'd region.
 +
 
 +
No user-process->sysmodule memcpy is done for outbufs, only sysmodule->user-process.
 +
 
 +
ReceiveList/Pointer data buffers are somewhat different. Rather than mapping new memory into the server process, ReceiveList/Pointer data buffers copy data between existing buffers in different processes. Each Pointer data buffer in a message has its data copied into a ReceiveList data buffer on the other side. Each Pointer data buffer in a message is used to reserve space for the other side's ReceiveList data buffer to copy into.
 +
 
 +
When the kernel processes Pointer data buffers, it must determine where to copy the data to. If the destination used ReceiveList data buffers with flags >= 3, each Pointer data buffer from the source is matched to a ReceiveList data buffer in the destination by the Pointer data buffer's ReceiveIndex field. If the destination used a single ReceiveList data buffer, the data from all the Pointer data buffers is copied into the same buffer specified by the destination's ReceiveList data buffer (causing error 0xce01 if there is not enough space) and the Pointer data buffer's ReceiveIndex is ignored. The kernel then modifies the addresses in the Pointer data buffers to indicate where the data was copied to in the destination.
 +
 
 +
Before receiving a request, if the IPC server is expecting Pointer data buffers, it prepares a message with a single ReceiveList data buffer (flags=2) in its message buffer before calling svcReplyAndReceive so that Pointer data buffers from the client have a place to copy their data to. The usage of the flag-2 ReceiveList data buffer allows the server to receive an arbitrary number of Pointer data buffers, since they're all packed into the same buffer. If the server had used flag-3+ ReceiveList data buffers, it would be limited in how many Pointer data buffers it could receive since the Pointer data buffers would have to be matched to distinct ReceiveList data buffers. The buffer that the server's ReceiveList data buffer points to is called the '''pointer buffer'''.
 +
 
 +
When the client sends Pointer data buffers, data is copied into the server's pointer buffer. When the client sends ReceiveList data buffers, no data is copied automatically. The server needs to use Pointer data buffers to copy the data back to the client's ReceiveList data buffers (using the index field to match Pointer data buffers in the response back to the correct ReceiveList data buffers).
 +
 
 +
= Client =
 +
The official marshalling function for the client is called "nn::sf::hipc::client::Hipc2ClientCoreProcessorImpl::WriteBufferDataImpl" and takes:
 +
* A pointer to a "nn::sf::hipc::detail::HipcMessageWriter" context;
 +
* The number of (buf_ptr, size) pairs;
 +
* An array of (buf_ptr, size) pairs (called "nn::sf::detail::PointerAndSize");
 +
* A pointer to a type bitfield for each such pair;
 +
* The offset of the main IPC command structure;
 +
* The size of the IPC command's raw data payload.
 +
 
 +
Pointer and ReceiveList data buffers are backed by the "pointer buffer", a buffer in the service process. Its size is a u16, which is retrieved using the "QueryPointerBufferSize" control message. If the client code determines all buffers with [[#BufferAttribute|BufferAttribute]] HipcPointer do not fit in the pointer buffer, it returns error 0x11A0B.
 +
 
 +
For buffers with [[#BufferAttribute|BufferAttribute]] HipcAutoSelect, it creates two data buffers (Send+Pointer or Receive+ReceiveList), but one of them is NULL (zero size and pointer), while the other holds the expected values. Pointer/ReceiveList data buffers are used as the non-NULL buffer where possible, but if they don't fit in the pointer buffer, Send/Receive descriptors are used instead. The code defers processing of HipcAutoSelect buffers with sizes that fit in a u16 (and may therefore fit in the pointer buffer), which ensures all HipcPointer buffers get pointer-buffer space before any HipcAutoSelect. The order in which the deferred HipcAutoSelect buffers are processed is determined by a convoluted loop.
 +
 
 +
= HipcMessageDataInfo =
 +
This is "nn::sf::hipc::detail::HipcMessageDataInfo". This is a 0x38-byte struct.
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
 
 
|-
 
|-
| 0 || 0 || Send current PID.
+
! Offset || Size || Description
 
|-
 
|-
| 0 || 4-1 || Number of handles to copy
+
| 0x0 || 0x1 || Valid
 
|-
 
|-
| 0 || 8-5 || Number of handles to move
+
| 0x1 || 0x1 || HasSpecial
 
|-
 
|-
| ... || || 8-byte PID if enabled
+
| 0x2 || 0x2 || Reserved
 
|-
 
|-
| ... || || Handles to copy
+
| 0x4 || 0x8 || [[#HeaderData|HeaderData]]
 
|-
 
|-
| ... || || Handles to move
+
| 0xC || 0x4 || [[#SpecialTag|SpecialHeaderData]]
 +
|-
 +
| 0x10 || 0x4 || [[#HipcMessageDataOffsetInfo|DataOffsetInfo]]
 
|}
 
|}
  
Sysmodules load the last u64 of rawdata when handling the PID. This is not written by kernel. For sysmodule handling:
+
= HeaderData =
* In some cases: these commands require a placeholder u64 value passed in the input parameters, as mentioned above. In these cases the OverwriteClientProcessId method is called to replace the value before it is used.
+
This is "nn::sf::hipc::detail::HipcFormat::HeaderData". This is a 0x8-byte struct.
* In other cases: The rawdata_u64 is compared with the PID from the descriptor. On mismatch and when rawdata_u64!=0, error 0x60A is returned. The PID value passed to the cmdhandler vtable funcptr is the rawdata_u64.
 
  
Handle 0 is allowed, and just means no handle was sent.
+
{| class="wikitable" border="1"
 +
|-
 +
! Offset || Size || Description
 +
|-
 +
| 0x0 || 0x4 || [[#Header0Tag|Header0]]
 +
|-
 +
| 0x4 || 0x4 || [[#Header1Tag|Header1]]
 +
|}
  
=== Buffer descriptor X "Pointer" ===
+
= MapData =
This one is packed even worse than A, they inserted the bit38-36 of the address ''on top'' of the counter field.
+
This is "nn::sf::hipc::detail::HipcFormat::MapData". This is a 0xC-byte struct.
  
Officially, the counter is known as "receive index". This one writes to the buffer described in the ReceiveList.
+
{| class="wikitable" border="1"
 +
|-
 +
! Offset || Size || Description
 +
|-
 +
| 0x0 || 0x4 || [[#Map0Tag|Data0]]
 +
|-
 +
| 0x4 || 0x4 || [[#Map1Tag|Data1]]
 +
|-
 +
| 0x8 || 0x4 || [[#Map2Tag|Data2]]
 +
|}
 +
 
 +
= PointerData =
 +
This is "nn::sf::hipc::detail::HipcFormat::PointerData". This is a 0x8-byte struct.
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
 
 
|-
 
|-
| 0 || 5-0 || Bits 5-0 of counter.
+
! Offset || Size || Description
 
|-
 
|-
| 0 || 8-6 || Bit 38-36 of address.
+
| 0x0 || 0x4 || [[#Pointer0Tag|Data0]]
 
|-
 
|-
| 0 || 11-9 || Bits 11-9 of counter.
+
| 0x4 || 0x4 || [[#Pointer1Tag|Data1]]
 +
|}
 +
 
 +
= ReceiveListData =
 +
{| class="wikitable" border="1"
 
|-
 
|-
| 0 || 15-12 || Bit 35-32 of address.
+
! Offset || Size || Description
 
|-
 
|-
| 0 || 31-16 || Size
+
| 0x0 || 0x4 || [[#ReceiveList0Tag|Data0]]
 
|-
 
|-
| 1 || || Lower 32-bits of address.
+
| 0x4 || 0x4 || [[#ReceiveList1Tag|Data1]]
 
|}
 
|}
  
=== Buffer descriptor A/B/W "Send"/"Receive"/"Exchange" ===
+
= SpecialData =
This packing is so unnecessarily complex.
 
 
 
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
 
|-
 
| 0 || || Lower 32-bits of size.
 
 
|-
 
|-
| 1 || || Lower 32-bits of address.
+
! Offset || Size || Description
 
|-
 
|-
| 2 || 1-0 || Flags. Always set to 0, 1 or 3.
+
| 0x0 || 0x4 || [[#SpecialTag|SpecialHeaderData]]
 
|-
 
|-
| 2 || 4-2 || Bit 38-36 of address.
+
| 0x4 || 0x8 || ProcessId
 
|-
 
|-
| 2 || 27-24 || Bit 35-32 of size.
+
| 0xC || Variable || Array of CopyHandle
 
|-
 
|-
| 2 || 31-28 || Bit 35-32 of address.
+
| Variable || Variable || Array of MoveHandle
 
|}
 
|}
  
A reply must not use A/B/W, svcReplyAndReceive will return 0xE801.
+
= Header0Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::Header0Tag". This is a 32-bit flag.
  
[[SVC|MemoryAttribute]] IsBorrowed and IsUncached are never allowed for the source address.
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-15
 +
| [[#MessageType|MessageType]]
 +
|-
 +
| 16-19
 +
| PointerCount
 +
|-
 +
| 20-23
 +
| SendCount
 +
|-
 +
| 24-27
 +
| ReceiveCount
 +
|-
 +
| 28-31
 +
| ExchangeCount
 +
|}
  
"Send" means buffer is sent from source process into service process.
+
= Header1Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::Header1Tag". This is a 32-bit flag.
  
"Receive" means that data is copied from service process into user process.
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-9
 +
| RawDataByteSize
 +
|-
 +
| 10-13
 +
| ReceiveListCount
 +
|-
 +
| 14-30
 +
| Reserved
 +
|-
 +
| 31
 +
| HasSpecial
 +
|}
  
"Exchange" means both "Send" and "Receive".
+
= SpecialTag =
 +
This is "nn::sf::hipc::detail::HipcFormat::SpecialTag". This is a 32-bit flag.
  
==== Flags ====
+
{| class="wikitable" border="1"
Determines what [[SVC|MemoryState]] to use with the mapped memory in the sysmodule.
+
!  Bits
 +
!  Description
 +
|-
 +
| 0
 +
| HasPid
 +
|-
 +
| 1-4
 +
| CopyHandleCount
 +
|-
 +
| 5-8
 +
| MoveHandleCount
 +
|-
 +
| 9-31
 +
| Reserved
 +
|}
  
Used to enforce whether or not device mapping is allowed for src and dst buffers respectively.
+
= Map0Tag =
+
This is "nn::sf::hipc::detail::HipcFormat::Map0Tag". This is a 32-bit flag.
* Flag0: Device mapping *not* allowed for src or dst.
 
* Flag1: Device mapping allowed for src and dst.
 
* Flag3: Device mapping allowed for src but not for dst.
 
  
=== Buffer descriptor C "ReceiveList" ===
+
{| class="wikitable" border="1"
There's a 4-bit flag in the main header controlling the behavior of C descriptors.
+
!  Bits
 +
!  Description
 +
|-
 +
| 0-31
 +
| Size (bits 0 to 31)
 +
|}
  
If it has value 0, the C descriptor functionality is disabled.
+
= Map1Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::Map1Tag". This is a 32-bit flag.
  
If it has value 1, there is an "inlined" C buffer after the raw data. Received data is copied to ROUND_UP(cmdbuf+raw_size+index, 16)
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-31
 +
| AddressValue (bits 0 to 31)
 +
|}
  
If it has value 2, there is a single C descriptor.
+
= Map2Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::Map2Tag". This is a 32-bit flag.
  
Otherwise it has (flag-2) C descriptors. In this case, index picks which C descriptor to copy received data to [instead of picking the offset into the buffer].
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-1
 +
| [[#MapTransferAttribute|MapTransferAttribute]]
 +
|-
 +
| 2-4
 +
| AddressValue (bits 36 to 38)
 +
|-
 +
| 5-23
 +
| Reserved
 +
|-
 +
| 24-27
 +
| Size (bits 32 to 35)
 +
|-
 +
| 28-31
 +
| AddressValue (bits 32 to 35)
 +
|}
  
Data sent with this method must have MemoryState 0x4000000 mask set.
+
= Pointer0Tag =
 
+
This is "nn::sf::hipc::detail::HipcFormat::Pointer0Tag". This is a 32-bit flag.
After reply, X descriptors are written to the sender containing the address, size and index that were copied to.
 
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
+
! Bits
 +
Description
 
|-
 
|-
| 0 || || Lower 32-bits of address.
+
| 0-3
 +
| ReceiveIndex
 
|-
 
|-
| 1 || 15-0 || Rest of address.
+
| 4-5
 +
| Reserved
 
|-
 
|-
| 1 || 31-16 || Size
+
| 6-8
 +
| AddressValue (bits 36 to 38)
 +
|-
 +
| 9-11
 +
| Reserved
 +
|-
 +
| 12-15
 +
| AddressValue (bits 32 to 35)
 +
|-
 +
| 16-31
 +
| Size
 
|}
 
|}
  
=== IPC buffers ===
+
= Pointer1Tag =
Buffer descriptor A/B/... map memory into the sysmodule process. For the mapped memory in the sysmodule the permissions are: desc-A = R--, desc-B = RW-. The buffer is automatically unmapped while the kernel handles the cmdreply, the sysmodule doesn't need to specify anything in the cmdreply to trigger this.
+
This is "nn::sf::hipc::detail::HipcFormat::Pointer1Tag". This is a 32-bit flag.
  
This memory is mapped in the sysmodule to the same vaddr from the original user-process cmd-request, except with with bits >=(~28(?)) changed to a different ASLR'd region.
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-31
 +
| AddressValue (bits 0 to 31)
 +
|}
  
No user-process->sysmodule memcpy is done for outbufs, only sysmodule->user-process.
+
= ReceiveList0Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::ReceiveList0Tag". This is a 32-bit flag.
  
Buffer descriptors C/X are somewhat different. Rather than mapping new memory into the server process, C/X descriptors copy data between existing buffers in different processes. Each X descriptor in a message has its data copied into a C descriptor on the other side. Each C descriptor in a message is used to reserve space for the other side's X descriptors to copy into.
+
{| class="wikitable" border="1"
 +
!  Bits
 +
!  Description
 +
|-
 +
| 0-31
 +
| AddressValue (bits 0 to 31)
 +
|}
  
When the kernel processes X descriptors, it must determine where to copy the data to. If the destination used C descriptors with flags >= 3, each X descriptor from the source is matched to a C descriptor in the destination by the X descriptor's index field. If the destination used a "single" C descriptor, the data from all the X descriptors is copied into the same buffer specified by the destination's C descriptor (causing error 0xce01 if there is not enough space) and the X descriptor index is ignored. The kernel then modifies the addresses in the X descriptors to indicate where the data was copied to in the destination.
+
= ReceiveList1Tag =
 +
This is "nn::sf::hipc::detail::HipcFormat::ReceiveList1Tag". This is a 32-bit flag.
  
Before receiving a request, if the IPC server is expecting X descriptors, it prepares a message with a "single" C descriptor (flags=2) in its message buffer before calling svcReplyAndReceive so that X descriptors from the client have a place to copy their data to. The usage of the flag-2 C descriptor allows the server to receive an arbitrary number of X descriptors, since they're all packed into the same buffer. If the server had used flag-3+ C descriptors, it would be limited in how many X descriptors it could receive since the X descriptors would have to be matched to distinct C descriptors. The buffer that the server's C descriptor points to is called the **pointer buffer**.
+
{| class="wikitable" border="1"
 +
!  Bits
 +
! Description
 +
|-
 +
| 0-6
 +
| AddressValue (bits 32 to 38)
 +
|-
 +
| 7-15
 +
| Reserved
 +
|-
 +
| 16-31
 +
| Size
 +
|}
  
When the client sends X descriptors, data is copied into the server's pointer buffer. When the client sends C descriptors, no data is copied automatically. The server needs to use X descriptors to copy the data back to the client's C descriptors (using the index field to match X descriptors in the response back to the correct C descriptors).
+
= MapTransferAttribute =
 +
{| class="wikitable" border="1"
 +
|-
 +
! Bit
 +
! Description
 +
|-
 +
| 0
 +
| AllowsNonSecure
 +
|-
 +
| 1
 +
| AllowsNonDevice
 +
|}
  
== Raw data section ==
+
= MessageType =
[[File:Ipc msg buffer type a example.png|thumb|An example of an IPC message with a type 0xA buffer in it. Red is headers/descriptors, yellow is padding, and blue is data/buffer lengths. Note that the size of the u16 array for type A lengths is padded to fill up a whole word.]]
+
This is "nn::sf::hipc::detail::MessageType".
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Description
+
! Value
 +
!  Description
 +
|-
 +
| 0 || Invalid
 +
|-
 +
| 1 || InvokeMethod
 +
|-
 +
| 2 || Release
 
|-
 
|-
| ... || Padding to align to 16 bytes.
+
| 3 || InvokeManagerMethod
 
|-
 
|-
| ... || If sent to an object domain, a [[#Domain_message|domain message]], otherwise a [[#Data payload|data payload]]
+
| 4 || Invoke2Method
 
|-
 
|-
| ... || Padding
+
| 5 || Invoke2ManagerMethod
 
|-
 
|-
| ... || Buffer type 0xA lengths (u16 array)
+
| 6 || [5.0.0+] Invoke2MethodWithContext
 +
|-
 +
| 7 || [5.0.0+] Invoke2ManagerMethodWithContext
 
|}
 
|}
  
The total amount of padding within the raw data section is always 0x10 bytes. This means that if no padding is required before the message, there will be 0x10 bytes of padding after the message (before the buffer type 0xA lengths).
+
= HipcMessageDataOffsetInfo =
 
+
This is "nn::sf::hipc::detail::HipcMessageDataOffsetInfo". This is a 0x28-byte struct.
=== Domain message ===
 
This header is used to wrap up requests sent to domains instead of sessions.
 
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Bits || Description
 
 
|-
 
|-
| 0 || 7-0 || Command. 1=send message, 2=close virtual handle
+
! Offset || Size || Description
 
|-
 
|-
| 0 || 8-15 || Input object count
+
| 0x0 || 0x4 || PidOffset
 
|-
 
|-
| 0 || 31-16 || Length of [[IPC_Marshalling#Data_payload|data payload]] in bytes.
+
| 0x4 || 0x4 || CopyHandleOffset
 
|-
 
|-
| 1 || || Object ID (from cmd 0 in [[IPC_Marshalling#Control|Control]]).
+
| 0x8 || 0x4 || MoveHandleOffset
 
|-
 
|-
| 2 || || Padding
+
| 0xC || 0x4 || PointerOffset
 
|-
 
|-
| 3 || || [5.0.0+] Token for (NewRequest only)
+
| 0x10 || 0x4 || SendOffset
 
|-
 
|-
| 4... || || [[#Data payload|Data payload]]
+
| 0x14 || 0x4 || ReceiveOffset
 
|-
 
|-
| ... || || Input object IDs (u32s, not aligned)
+
| 0x18 || 0x4 || ExchangeCount
 +
|-
 +
| 0x1C || 0x4 || RawOffset
 +
|-
 +
| 0x20 || 0x4 || ReceiveListOffset
 +
|-
 +
| 0x24 || 0x4 || AllCount
 
|}
 
|}
  
=== Data payload ===
+
= CmifInHeader =
This is an array of u32's, but individual parameters are generally stored as u64's.
+
This is "nn::sf::cmif::CmifInHeader". This is a 0x10-byte struct.
 +
 
 +
[5.0.0+] Version was incremented from 0 to 1.
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Word || Description
 
 
|-
 
|-
| 0 || Magic ("SFCI" for requests, "SFCO" for responses) as u64.
+
! Offset || Size || Description
 
|-
 
|-
| 2 || Command id as u64 for requests, [[Error_codes|error code]] as u64 for responses.
+
| 0x0 || 0x4 || Signature ("SFCI")
 
|-
 
|-
| 3 || [5.0.0+] Token (for NewRequest only, non-domain messages).
+
| 0x4 || 0x2 || Version
 
|-
 
|-
| 4... || Input parameters or return values
+
| 0x6 || 0x2 || Reserved
 +
|-
 +
| 0x8 || 0x4 || MethodId
 +
|-
 +
| 0xC || 0x4 || [5.0.0+] Token ([1.0.0-4.1.0] Reserved)
 
|}
 
|}
  
[5.0.0+] A token value was introduced into raw_data+12 (regardless of domain or not, in either case it overlaps with padding).
+
= CmifOutHeader =
 +
This is "nn::sf::cmif::CmifOutHeader". This is a 0x10-byte struct.
  
== Official marshalling code ==
+
[14.0.0+] Reserved field at +0xC is now InterfaceId, generated as little endian first four bytes of sha256(<fully qualified interface name>). The version field was not incremented.
The official marshalling function takes an array of (buf_ptr, size) pairs and a type-field for each such pair.
 
 
 
Bitmask 0x10 seems to indicate null-terminated strings.
 
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
! Type Mask || Description || Direction
 
 
|-
 
|-
| 4 + 1 || Creates a A descriptor with flags=0. || In
+
! Offset || Size || Description
 
|-
 
|-
| 0x40 + 4 + 1 || Creates a A descriptor with flags=1. || In
+
| 0x0 || 0x4 || Signature ("SFCO")
 
|-
 
|-
| 0x80 + 4 + 1 || Creates a A descriptor with flags=3. || In
+
| 0x4 || 0x2 || Version
 
|-
 
|-
| 4 + 2 || Creates a B descriptor with flags=0. || Out
+
| 0x6 || 0x2 || Reserved
 
|-
 
|-
| 0x40 + 4 + 2 || Creates a B descriptor with flags=1. || Out
+
| 0x8 || 0x4 || Result
 +
|-
 +
| 0xC || 0x4 || [14.0.0+] InterfaceId ([1.0.0-13.2.1] Reserved)
 +
|}
 +
 
 +
= CmifDomainMessageInHeader =
 +
This is "nn::sf::cmif::detail::CmifDomainMessageInHeader". This is a 0x10-byte struct.
 +
 
 +
{| class="wikitable" border="1"
 
|-
 
|-
| 0x80 + 4 + 2 || Creates a B descriptor with flags=3. || Out
+
! Offset || Size || Description
 
|-
 
|-
| 8 + 1 || Creates an X descriptor || In
+
| 0x0 || 0x1 || [[#RequestKind|RequestKind]]
 
|-
 
|-
| 8 + 2 || Creates a C descriptor, and writes the u16 size to an offset into raw data. || Out
+
| 0x1 || 0x1 || InObjectCount
 
|-
 
|-
| 0x10 + 8 + 2 || Creates a C descriptor || Out
+
| 0x2 || 0x2 || InRawSize
 
|-
 
|-
| 0x20 + 1 || Creates both an A and X descriptor || In
+
| 0x4 || 0x4 || [[#CmifDomainObjectId|TargetObjectId]]
 
|-
 
|-
| 0x20 + 2 || Creates both an B and C descriptor || Out
+
| 0x8 || 0x4 || Reserved
 
|-
 
|-
| 0x20 + 2 + 0x40 || Same as 0x20 + 2, except a certain value is set to hard-coded 0x1 instead. || Out
+
| 0xC || 0x4 || [5.0.0+] Token ([1.0.0-4.1.0] Reserved)
 
|}
 
|}
  
C and X (Pointer and ReceiveList) descriptors are backed by the "pointer buffer", a buffer in the service process. Its size is a u16, which is retrieved using the "QueryPointerBufferSize" control message. If the client code determines all buffers with flag 8 do not fit in the pointer buffer, it returns error 0x11A0B.
+
= CmifDomainMessageOutHeader =
 +
This is "nn::sf::cmif::detail::CmifDomainMessageOutHeader". This is a 0x10-byte struct.
  
For buffers with flag 0x20 it creates two descriptors (A+X or B+C), but one descriptor is NULL (zero size and pointer), while the other holds the expected values. X/C descriptors are used as the non-NULL descriptor where possible, but if they don't fit in the pointer buffer, A/B descriptors are used instead. The code defers processing of type 0x20 buffers with sizes that fit in a u16 (and may therefore fit in the pointer buffer). This ensures all type 8 buffers get pointer-buffer space before any type 0x20.
+
{| class="wikitable" border="1"
 +
|-
 +
! Offset || Size || Description
 +
|-
 +
| 0x0 || 0x4 || OutObjectCount
 +
|-
 +
| 0x4 || 0xC || Reserved
 +
|}
  
(The order in which the deferred type 0x20 buffers are processed is determined by a convoluted loop.)
+
= CmifDomainObjectId =
 +
This is "nn::sf::cmif::CmifDomainObjectId". This is a 4 byte value.
  
== Official IPC Cmd Structure ==
+
= BufferAttribute =
Official struct that is stored for each IPC command. It contains precalculated offsets for different portions of the command structure.
+
This is "nn::sf::cmif::BufferAttribute".
 
 
All offsets are given is in number of u32 words.
 
 
 
struct IpcCmdStruct {
 
  u8  unk0;
 
  u8  has_handle_descriptor;
 
  u8  pad0[2];
 
  u32 cmd0;
 
  u32 cmd1;
 
  u32 offset_handle_descriptor;
 
  u32 pad1;
 
  u32 offset_handles;         
 
  u32 pad2;
 
  u32 offset_x_descriptors;
 
  u32 offset_a_descriptors;
 
  u32 offset_b_descriptors;
 
  u32 offset_w_descriptors; /* this is a guess */
 
  u32 offset_raw_data;
 
  u32 offset_c_descriptors;
 
  u32 unk2;
 
  u32 unk3;
 
}
 
 
 
== Control ==
 
When type == 5 you are talking to the IPC manager. These are processed by the sysmodule.
 
  
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
 +
!  Value
 +
!  Description
 
|-
 
|-
! Cmd || Name || Arguments || Output
+
| 0x0 || None
 
|-
 
|-
| 0 || ConvertCurrentObjectToDomain || None || u32 CmifDomainObjectId
+
| 0x1 || In
 
|-
 
|-
| 1 || CopyFromCurrentDomain || u32 CmifDomainObjectId || u32 NativeHandle
+
| 0x2 || Out
 
|-
 
|-
| 2 || CloneCurrentObject || None || u32 NativeHandle
+
| 0x4 || HipcMapAlias
 +
|-
 +
| 0x8 || HipcPointer
 +
|-
 +
| 0x10 || FixedSize
 +
|-
 +
| 0x20 || HipcAutoSelect
 +
|-
 +
| 0x40 || HipcMapTransferAllowsNonSecure
 +
|-
 +
| 0x80 || HipcMapTransferAllowsNonDevice
 +
|}
 +
 
 +
= RequestKind =
 +
{| class="wikitable" border="1"
 +
!  Value
 +
!  Description
 +
|-
 +
| 0 || Invalid
 
|-
 
|-
| 3 || QueryPointerBufferSize || None || u16 size
+
| 1 || Send
 
|-
 
|-
| 4 || CloneCurrentObjectEx || u32 unknown || u32 NativeHandle
+
| 2 || Close
 
|}
 
|}

Latest revision as of 16:58, 10 August 2024

HIPC (Horizon Inter-Process Communication) is a custom IPC implementation tailored for the Horizon OS.

Structure

This is a buffer located in the Thread Local Region.

Offset Size Description
0x0 0x8 HeaderData
0x8 Variable SpecialData (if HasSpecial is set)
Variable Variable Array of PointerData
Variable Variable Array of SendData
Variable Variable Array of ReceiveData
Variable Variable Array of ExchangeData
Variable Variable RawData
Variable Variable Array of ReceiveListData

HeaderData and SpecialHeaderData (if available) are copied as-is from one process to another.

Sysmodules load the last u64 of rawdata when handling the SpecialData ProcessId. This is not written by kernel. For sysmodule handling:

  • In some cases: these commands require a placeholder u64 value passed in the input parameters, as mentioned above. In these cases the OverwriteClientProcessId method is called to replace the value before it is used.
  • In other cases: The rawdata_u64 is compared with the ProcessId from SpecialData. On mismatch and when rawdata_u64!=0, error 0x60A is returned. The ProcessId value passed to the cmdhandler vtable funcptr is the rawdata_u64.

Handle 0 is allowed, and just means no handle was sent.

A reply must not use Send/Receive/Exchange data buffers, svcReplyAndReceive will return 0xE801. MemoryAttribute IsBorrowed and IsUncached are never allowed for the source address. "Send" means buffer is sent from source process into service process, "Receive" means that data is copied from service process into user process and "Exchange" means both "Send" and "Receive".

HeaderData's ReceiveListCount field controls the behavior of ReceiveListData.

If ReceiveListCount is 0, ReceiveListData is disabled. If ReceiveListCount is 1, there is an "inlined" ReceiveListData buffer after the raw data (received data is copied to ROUND_UP(cmdbuf+raw_size+index, 16)). If ReceiveListCount is 2, there is a single ReceiveListData buffer. Otherwise it has (ReceiveListCount-2) ReceiveListData buffers. In this case, ReceiveIndex picks which ReceiveListData buffer to copy received data to [instead of picking the offset into the buffer]. Data sent with this method must have MemoryState 0x4000000 mask set.

After reply, PointerData buffers are written to the sender containing the AddressValue, Size and ReceiveIndex that were copied to.

RawData

An example of an IPC message with a type 0xA (OutPointer) buffer in it. Red is headers/descriptors, yellow is padding, and blue is data/buffer lengths. Note that the size of the u16 array for type 0xA lengths is padded to fill up a whole word.
Offset Size Description
Variable Variable Reserved (padding to align to 16 bytes)
Variable Variable Payload or Domain
Variable Variable Reserved (padding to align to 16 bytes)
Variable Variable Buffer type 0xA (OutPointer) lengths (u16 array)

The total amount of padding within the raw data section is always 0x10 bytes. This means that if no padding is required before the message, there will be 0x10 bytes of padding after the message (before the buffer type 0xA (OutPointer) - lengths).

Payload

This is an array of u32s, but individual parameters are generally stored as u64s.

Offset Size Description
0x0 0x10 CmifInHeader or CmifOutHeader
0x10 Variable Input parameters or return values

The rawdata struct for input parameters/return values is generated by stable-sorting function parameters by alignment, from low to high. It is likely this is a mistake, as it generates structs with suboptimal possible padding -- Nintendo probably meant to sort from high to low (which would give minimized padding), but couldn't/can't change this without breaking backwards compatibility.

Domain

Because the switch has relatively low limits on the total number of sessions available to the system (Kernel slabheap limits, sysmodule handle table size limits), HIPC supports a "Domains" feature that allows multiplexing multiple service sessions through a single handle. Domains store (effectively) a mapping from u32 object id to a SharedPointer<IServiceObject> -- When messages are sent to a domain, an extra header is sent in the raw data section (before anything else) with information about what object in the domain is being acted on; responses similarly contain an additional header. Official session code implements this by just using the dispatch table for the object in the map with the appropriate ID, instead of the dispatch table the session was initialized with.

Offset Size Description
0x0 0x10 CmifDomainMessageInHeader or CmifDomainMessageOutHeader
0x10 Variable Payload (size must be InRawSize, only for input domain messages)
Variable Variable Array of CmifDomainObjectId (count must be InObjectCount, only for input domain messages)

Messages

IPC messages can have different types which influence how the IPC server processes requests. The message type value is passed in the lower 16 bits of the first HeaderData word (see Header0Tag).

InvokeMethod

This is a normal message to be processed using the regular marshalling system. Now deprecated, this type of message would be copied first to an intermediary set of internal structures by the server.

Release

This is a message that tells to close and destroy the server session.

InvokeManagerMethod

This is a message that requests a server manager operation. Now deprecated, this type of message would be copied first to an intermediary set of internal structures by the server.

Cmd Name
0 #ConvertCurrentObjectToDomain
1 #CopyFromCurrentDomain
2 #CloneCurrentObject
3 #QueryPointerBufferSize
4 #CloneCurrentObjectEx

ConvertCurrentObjectToDomain

No input. Returns an output CmifDomainObjectId.

CopyFromCurrentDomain

Takes an input CmifDomainObjectId. Returns an output handle.

CloneCurrentObject

No input. Returns an output handle.

QueryPointerBufferSize

No input. Returns an output u16 PointerBufferSize.

CloneCurrentObjectEx

Takes an input u32 Tag. Returns an output handle.

Invoke2Method

Same as InvokeMethod but using a more streamlined logic that no longer requires additional internal copying and parsing.

Invoke2ManagerMethod

Same as InvokeManagerMethod but using a more streamlined logic that no longer requires additional internal copying and parsing.

Invoke2MethodWithContext

Same as Invoke2Method but with the additional requirement of suppling a token in the data payload.

This token is used by "nn::sf::cmif::SetInlineContext" which has the sole purpose of saving it into the TLS in order for it to be distributed to any IPC commands that are made while processing the current command. It's unknown if this token serves any purpose or if it's just a debug-tool to figure out what IPC command caused a particular chain of commands.

Invoke2ManagerMethodWithContext

Same as Invoke2ManagerMethod but with the additional requirement of suppling a token in the data payload.

This token is used by "nn::sf::cmif::SetInlineContext" which has the sole purpose of saving it into the TLS in order for it to be distributed to any IPC commands that are made while processing the current command. It's unknown if this token serves any purpose or if it's just a debug-tool to figure out what IPC command caused a particular chain of commands.

Server

Send/Receive/Exchange data buffers map memory into the sysmodule process. For the mapped memory in the sysmodule the permissions are: Send = R--, Receive = RW-. The buffer is automatically unmapped while the kernel handles the cmdreply, the sysmodule doesn't need to specify anything in the cmdreply to trigger this.

This memory is mapped in the sysmodule to the same vaddr from the original user-process cmd-request, except with with bits >=(~28(?)) changed to a different ASLR'd region.

No user-process->sysmodule memcpy is done for outbufs, only sysmodule->user-process.

ReceiveList/Pointer data buffers are somewhat different. Rather than mapping new memory into the server process, ReceiveList/Pointer data buffers copy data between existing buffers in different processes. Each Pointer data buffer in a message has its data copied into a ReceiveList data buffer on the other side. Each Pointer data buffer in a message is used to reserve space for the other side's ReceiveList data buffer to copy into.

When the kernel processes Pointer data buffers, it must determine where to copy the data to. If the destination used ReceiveList data buffers with flags >= 3, each Pointer data buffer from the source is matched to a ReceiveList data buffer in the destination by the Pointer data buffer's ReceiveIndex field. If the destination used a single ReceiveList data buffer, the data from all the Pointer data buffers is copied into the same buffer specified by the destination's ReceiveList data buffer (causing error 0xce01 if there is not enough space) and the Pointer data buffer's ReceiveIndex is ignored. The kernel then modifies the addresses in the Pointer data buffers to indicate where the data was copied to in the destination.

Before receiving a request, if the IPC server is expecting Pointer data buffers, it prepares a message with a single ReceiveList data buffer (flags=2) in its message buffer before calling svcReplyAndReceive so that Pointer data buffers from the client have a place to copy their data to. The usage of the flag-2 ReceiveList data buffer allows the server to receive an arbitrary number of Pointer data buffers, since they're all packed into the same buffer. If the server had used flag-3+ ReceiveList data buffers, it would be limited in how many Pointer data buffers it could receive since the Pointer data buffers would have to be matched to distinct ReceiveList data buffers. The buffer that the server's ReceiveList data buffer points to is called the pointer buffer.

When the client sends Pointer data buffers, data is copied into the server's pointer buffer. When the client sends ReceiveList data buffers, no data is copied automatically. The server needs to use Pointer data buffers to copy the data back to the client's ReceiveList data buffers (using the index field to match Pointer data buffers in the response back to the correct ReceiveList data buffers).

Client

The official marshalling function for the client is called "nn::sf::hipc::client::Hipc2ClientCoreProcessorImpl::WriteBufferDataImpl" and takes:

  • A pointer to a "nn::sf::hipc::detail::HipcMessageWriter" context;
  • The number of (buf_ptr, size) pairs;
  • An array of (buf_ptr, size) pairs (called "nn::sf::detail::PointerAndSize");
  • A pointer to a type bitfield for each such pair;
  • The offset of the main IPC command structure;
  • The size of the IPC command's raw data payload.

Pointer and ReceiveList data buffers are backed by the "pointer buffer", a buffer in the service process. Its size is a u16, which is retrieved using the "QueryPointerBufferSize" control message. If the client code determines all buffers with BufferAttribute HipcPointer do not fit in the pointer buffer, it returns error 0x11A0B.

For buffers with BufferAttribute HipcAutoSelect, it creates two data buffers (Send+Pointer or Receive+ReceiveList), but one of them is NULL (zero size and pointer), while the other holds the expected values. Pointer/ReceiveList data buffers are used as the non-NULL buffer where possible, but if they don't fit in the pointer buffer, Send/Receive descriptors are used instead. The code defers processing of HipcAutoSelect buffers with sizes that fit in a u16 (and may therefore fit in the pointer buffer), which ensures all HipcPointer buffers get pointer-buffer space before any HipcAutoSelect. The order in which the deferred HipcAutoSelect buffers are processed is determined by a convoluted loop.

HipcMessageDataInfo

This is "nn::sf::hipc::detail::HipcMessageDataInfo". This is a 0x38-byte struct.

Offset Size Description
0x0 0x1 Valid
0x1 0x1 HasSpecial
0x2 0x2 Reserved
0x4 0x8 HeaderData
0xC 0x4 SpecialHeaderData
0x10 0x4 DataOffsetInfo

HeaderData

This is "nn::sf::hipc::detail::HipcFormat::HeaderData". This is a 0x8-byte struct.

Offset Size Description
0x0 0x4 Header0
0x4 0x4 Header1

MapData

This is "nn::sf::hipc::detail::HipcFormat::MapData". This is a 0xC-byte struct.

Offset Size Description
0x0 0x4 Data0
0x4 0x4 Data1
0x8 0x4 Data2

PointerData

This is "nn::sf::hipc::detail::HipcFormat::PointerData". This is a 0x8-byte struct.

Offset Size Description
0x0 0x4 Data0
0x4 0x4 Data1

ReceiveListData

Offset Size Description
0x0 0x4 Data0
0x4 0x4 Data1

SpecialData

Offset Size Description
0x0 0x4 SpecialHeaderData
0x4 0x8 ProcessId
0xC Variable Array of CopyHandle
Variable Variable Array of MoveHandle

Header0Tag

This is "nn::sf::hipc::detail::HipcFormat::Header0Tag". This is a 32-bit flag.

Bits Description
0-15 MessageType
16-19 PointerCount
20-23 SendCount
24-27 ReceiveCount
28-31 ExchangeCount

Header1Tag

This is "nn::sf::hipc::detail::HipcFormat::Header1Tag". This is a 32-bit flag.

Bits Description
0-9 RawDataByteSize
10-13 ReceiveListCount
14-30 Reserved
31 HasSpecial

SpecialTag

This is "nn::sf::hipc::detail::HipcFormat::SpecialTag". This is a 32-bit flag.

Bits Description
0 HasPid
1-4 CopyHandleCount
5-8 MoveHandleCount
9-31 Reserved

Map0Tag

This is "nn::sf::hipc::detail::HipcFormat::Map0Tag". This is a 32-bit flag.

Bits Description
0-31 Size (bits 0 to 31)

Map1Tag

This is "nn::sf::hipc::detail::HipcFormat::Map1Tag". This is a 32-bit flag.

Bits Description
0-31 AddressValue (bits 0 to 31)

Map2Tag

This is "nn::sf::hipc::detail::HipcFormat::Map2Tag". This is a 32-bit flag.

Bits Description
0-1 MapTransferAttribute
2-4 AddressValue (bits 36 to 38)
5-23 Reserved
24-27 Size (bits 32 to 35)
28-31 AddressValue (bits 32 to 35)

Pointer0Tag

This is "nn::sf::hipc::detail::HipcFormat::Pointer0Tag". This is a 32-bit flag.

Bits Description
0-3 ReceiveIndex
4-5 Reserved
6-8 AddressValue (bits 36 to 38)
9-11 Reserved
12-15 AddressValue (bits 32 to 35)
16-31 Size

Pointer1Tag

This is "nn::sf::hipc::detail::HipcFormat::Pointer1Tag". This is a 32-bit flag.

Bits Description
0-31 AddressValue (bits 0 to 31)

ReceiveList0Tag

This is "nn::sf::hipc::detail::HipcFormat::ReceiveList0Tag". This is a 32-bit flag.

Bits Description
0-31 AddressValue (bits 0 to 31)

ReceiveList1Tag

This is "nn::sf::hipc::detail::HipcFormat::ReceiveList1Tag". This is a 32-bit flag.

Bits Description
0-6 AddressValue (bits 32 to 38)
7-15 Reserved
16-31 Size

MapTransferAttribute

Bit Description
0 AllowsNonSecure
1 AllowsNonDevice

MessageType

This is "nn::sf::hipc::detail::MessageType".

Value Description
0 Invalid
1 InvokeMethod
2 Release
3 InvokeManagerMethod
4 Invoke2Method
5 Invoke2ManagerMethod
6 [5.0.0+] Invoke2MethodWithContext
7 [5.0.0+] Invoke2ManagerMethodWithContext

HipcMessageDataOffsetInfo

This is "nn::sf::hipc::detail::HipcMessageDataOffsetInfo". This is a 0x28-byte struct.

Offset Size Description
0x0 0x4 PidOffset
0x4 0x4 CopyHandleOffset
0x8 0x4 MoveHandleOffset
0xC 0x4 PointerOffset
0x10 0x4 SendOffset
0x14 0x4 ReceiveOffset
0x18 0x4 ExchangeCount
0x1C 0x4 RawOffset
0x20 0x4 ReceiveListOffset
0x24 0x4 AllCount

CmifInHeader

This is "nn::sf::cmif::CmifInHeader". This is a 0x10-byte struct.

[5.0.0+] Version was incremented from 0 to 1.

Offset Size Description
0x0 0x4 Signature ("SFCI")
0x4 0x2 Version
0x6 0x2 Reserved
0x8 0x4 MethodId
0xC 0x4 [5.0.0+] Token ([1.0.0-4.1.0] Reserved)

CmifOutHeader

This is "nn::sf::cmif::CmifOutHeader". This is a 0x10-byte struct.

[14.0.0+] Reserved field at +0xC is now InterfaceId, generated as little endian first four bytes of sha256(<fully qualified interface name>). The version field was not incremented.

Offset Size Description
0x0 0x4 Signature ("SFCO")
0x4 0x2 Version
0x6 0x2 Reserved
0x8 0x4 Result
0xC 0x4 [14.0.0+] InterfaceId ([1.0.0-13.2.1] Reserved)

CmifDomainMessageInHeader

This is "nn::sf::cmif::detail::CmifDomainMessageInHeader". This is a 0x10-byte struct.

Offset Size Description
0x0 0x1 RequestKind
0x1 0x1 InObjectCount
0x2 0x2 InRawSize
0x4 0x4 TargetObjectId
0x8 0x4 Reserved
0xC 0x4 [5.0.0+] Token ([1.0.0-4.1.0] Reserved)

CmifDomainMessageOutHeader

This is "nn::sf::cmif::detail::CmifDomainMessageOutHeader". This is a 0x10-byte struct.

Offset Size Description
0x0 0x4 OutObjectCount
0x4 0xC Reserved

CmifDomainObjectId

This is "nn::sf::cmif::CmifDomainObjectId". This is a 4 byte value.

BufferAttribute

This is "nn::sf::cmif::BufferAttribute".

Value Description
0x0 None
0x1 In
0x2 Out
0x4 HipcMapAlias
0x8 HipcPointer
0x10 FixedSize
0x20 HipcAutoSelect
0x40 HipcMapTransferAllowsNonSecure
0x80 HipcMapTransferAllowsNonDevice

RequestKind

Value Description
0 Invalid
1 Send
2 Close