Difference between revisions of "HIPC"
(Massive re-write) |
m (Hexkyz moved page IPC Marshalling to HIPC) |
(No difference)
|
Revision as of 00:46, 28 November 2023
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 SpecialData (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
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 |
13-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 |