From WiiUBrew
(Redirected from IOSU)
Jump to navigation Jump to search

IOS (unofficially known as IOSU for distinguishing between the Wii and Wii U variants) is the operating system running on the Starbuck coprocessor in Wii U mode. It is the Wii U equivalent of IOS on the Wii, and similar in some regards, but it is a complete rewrite with many changes. IOSU implements the Wii U's security policy, which includes titles and hardware access. One of its primary responsibilities is enforcing code signing, verifying all titles before installation and launch. Another one of its jobs is managing access to most hardware, such as storage, network, USB, and the Gamepad. The PowerPC can talk to IOSU through an IPC interface, and make security and hardware requests.

The IOSU is an embedded operating system written by Nintendo, with a microkernel architecture. It contains a simple kernel that implements memory management and process and thread management. Device drivers and security handlers run as processes in the ARM user mode. These processes, called resource managers (RMs), can register as request handlers for resources, which are represented as nodes under /dev in a virtual filesystem. They communicate with each other through the kernel, using standard Unix file operations (open/close/read/write/seek/ioctl/ioctlv).

ELF loader

The IOSU firmware image file (fw.img), when decrypted, contains two distinguishable pieces of code: a small ELF loader and the actual firmware binary (ELF file). Each time the IOSU starts, the ELF loader is the first portion of code that runs in order to make preparations for the actual IOSU binary. During the console's initial boot, boot1 is responsible for fetching the IOSU's image and launch it (cold boot). However, IOS-MCP also has to do this when handling a system restart (warm boot). The IOS-MCP module begins by clearing up MEM1 then fetches the fw.img file from NAND. It verifies the Ancast header, decrypts it using the Starbuck Ancast Key and finally makes use of the execute_privileged system call in order to disable memory protections and jump to the IOSU's ELF loader code.

This loader then does the following:

- Clear it's own small stack;
- Set 0x20 in HW_SRNPROT register (if not set already);
- Fetch e_phnum from the IOSU's ELF header;
- Loop through the ELF's program header structs;
- For each program marked with PT_LOAD, copy it's code into the target physical memory;
- Wipe the IOSU's ELF binary from MEM1;
- Wipe itself from MEM1 (excluding the then running subroutine);
- Branch to 0xFFFF0000 (IOSU kernel boot).


After being parsed by it's own ELF loader, the IOSU's kernel is launched from the Wii U's SRAM (0xFFFF0000). IOSU's kernel follows a standard ARM microkernel architecture:

// IOSU kernel entry-point
// ARM vector table (firmware 5.5.1)
start()  // 0xFFFF0000
  // Reset handler
  pc <- sub_FFFF0060
  // Undefined handler
  pc <- loc_812DD6C
  // SWI handler
  pc <- sub_812DD20
  // Prefetch handler
  pc <- sub_812E74C
  // Abort handler
  pc <- sub_812E720
  // NULL
  pc <- loc_FFFF0040
  // IRQ handler
  pc <- loc_FFFF0044
  // FIQ handler
  pc <- loc_FFFF0048

Execution follows into the reset handler:

// Reset handler (firmware 5.5.1)
  r0 <- 0
  // Invalidate ICache
  MCR p15, 0, R0,c7,c5, 0
  // Invalidate DCache
  MCR p15, 0, R0,c7,c6, 0
  // Read control register
  MRC p15, 0, R0,c1,c0, 0
  // Set replacement strategy to Round-Robin
  r0 <- r0 | 0x1000
  // Enable alignment fault checking
  r0 <- r0 | 0x2
  // Write control register
  MCR p15, 0, R0,c1,c0, 0
  // Clear the IOS-KERNEL stack
  memset_range(stack_start, stack_end, 0, 0x04);
  // Disable boot0 mapping
  r0 <- 0x0D80018C   // HW_SPARE1
  r1 <- 0x00(HW_SPARE1)
  r1 <- r1 | 0x1000
  0x00(HW_SPARE1) <- r1
  // MSR CPSR_c, #0xD3 -> Enter supervisor mode, FIQ/IRQ disabled
  // SP == 0xFFFF0904
  // MSR CPSR_c, #0xD2 -> Enter IRQ mode, FIQ/IRQ disabled
  // SP == 0xFFFF1104
  // MSR CPSR_c, #0xD1 -> Enter FIQ mode, FIQ/IRQ disabled
  // SP == 0xFFFF1504
  // MSR CPSR_c, #0xD7 -> Enter abort mode, FIQ/IRQ disabled
  // SP == 0xFFFF0D04
  // MSR CPSR_c, #0xDB -> Enter undefined mode, FIQ/IRQ disabled
  // SP == 0xFFFF1904
  // MSR CPSR_c, #0xDF -> Enter system mode, FIQ/IRQ disabled
  // SP == 0xFFFF1904
  // Jump to MEM0 check
  lr <- pc
  pc <- sub_FFFFDA38


MEM0's integrity is checked before going any further:

// Check MEM0 for IOS-KERNEL (firmware 5.5.1)
  r1 <- 0x08143008  // IOSU kernel data region
  r3 <- 0xA5A51212
  r2 <- 0x00(0x08143008)
  // Deadlock
  if (0x00(0x08143008) != 0xA5A51212)
     r3 <- 0xDEAD1111
     sp <- 0xDEAD1111

  r12 <- 0xFFFF0500	// IOSU boot heap region
  r2 <- 0x00(0xFFFF0500)
  // Deadlock
  if (0x00(0xFFFF0500) != 0xA5A51212)
     r3 <- 0xDEAD1111
     sp <- 0xDEAD1111
  // Set init magic
  r2 <- 0x5A5AEDED
  0x00(0xFFFF0500) <- 0x5A5AEDED
  0x00(0x08143008) <- 0x5A5AEDED
  // Flush AHB for Starbuck
  // Load IOS_KERNEL
  r2 <- sub_8121B18
  // Deadlock
  r3 <- 0xDEAD2222
  sp <- 0xDEAD2222

And, finally, execution switches over to MEM0 where the IOS-KERNEL module will be running:

// Load IOS-KERNEL (firmware 5.5.1)
  // Read LT_DEBUG register
  // and store it's value at 0x08150000
  // Clear LT_DEBUG register
  // Do a bitwise AND with the LT_DEBUG value
  r0 <- 0x80000000
  r0 <- sub_81209B8(0x80000000);
  // If LT_DEBUG is 0x80000000
  // the hardware is using RealView
  if (r0 != 0)
    r4 <- (sp + 0x04)
    r3 <- 0
    0x00(sp) <- 0
    // Wait for user input
    // to start the kernel
    while (r3 != 0x01)
	r0 <- "\n\n\n\n\n\n\n\n\n\n"
	r0 <- "Enter '1' to proceed with kernel startup. "
	debug_printf("Enter '1' to proceed with kernel startup. ");
	r0 <- "%d"
        r1 <- sp
	debug_read("%d", sp);			
       r3 <- 0x00(sp)
  // Initialize the MMU and map memory regions
  // Reset GPIOs and IRQs
  // Setup IRQ handlers
  // Clear and set some kernel structures
  // Setup iobuf
  // Re-map shared_user_ro
  // Clear IOS_KERNEL module's thread stack and run it

At this point, the IOS-KERNEL module is running on it's own thread and becomes responsible for launching and controlling all the other IOSU modules. The kernel is responsible for tasks such as as IPC handling, permissions' control, resource managers (/dev nodes) and much more.

System calls are handled through the ARM undefined handler and mapped into their respective kernel functions.

There are 2 types of syscalls:

  • Syscalls using undefined ARM instruction
  • Syscalls using ARM syscall instruction

Syscalls (via undefined instructions)

Similarly to the Wii's IOS, the IOSU uses a syscall table that is stored toward the end of the kernel area inside the main ARM binary (fw.img).

The second vector is the invalid instruction handler, which is used to implement syscalls:

fw:FFFF0000               LDR     PC, =_reset
fw:FFFF0004               LDR     PC, =starbuck_syscall_handler

The Starbuck syscall handler:

   STMFA           SP, {R0-LR}^
   MRS             R8, SPSR
   STR             R8, [SP]
   STR             LR, [SP,#0x40]
   LDR             R10, [LR,#-4]   ; R10 = E7F0XXXX  (the invalid instruction)
   BIC             R9, R10, #0xFF00
   LDR             R8, =0xE7F000F0   ; Syscall base
   CMP             R9, R8            ; Were any bits set other than the syscall number
   BNE             invalid_syscall
   MOV             R10, R10,ASR#8
   AND             R10, R10, #0xFF
   CMP             R10, #0x94        ; Max index of syscall (can possibly vary)
   BGT             return_to_caller
   MOV             R8, SP
   MOV             R11, #0x1F
   MSR             CPSR_c, R11       ; Switch to system mode and disable FIQ/IRQ
   LDR             R9, [R8,#0x48]    ; Added in 5.5.0: Check for invalid stack
   CMP             SP, R9
   BLCS            bad_stack
   LDR             R11, [R8,#0x4C]
   SUB             R9, R9, R11
   CMP             SP, R9
   BCC             bad_stack
   LDR             R8, [R8,#0x44]
   LDR             R11, =syscall_stack_arg_counts
   LDR             R11, [R11,R10,LSL#2]  ; Number of args on stack for this syscall
   ADD             SP, SP, R11,LSL#2
   CMP             R11, #0
   BEQ             find_syscall_and_jump
   LDR             R9, [SP,#-4]!	 ; Copy argument value
   STR             R9, [R8,#-4]!
   SUB             R11, R11, #1
   B               get_stack_arg
   MOV             SP, R8
   LDR             R11, =syscall_table
   LDR             R11, [R11,R10,LSL#2]
   MOV             LR, PC
   BX              R11
   MOV             R11, #0xDB   ; Switch to undefined mode and re-enable FIQ/IRQ
   MSR             CPSR_c, R11
   LDR             R11, [SP]
   MSR             SPSR_cxsf, R11
   MOV             LR, R0
   LDMED           SP, {R0-LR}^
   MOV             R0, LR
   LDR             LR, [SP,#0x40]
   MOVS            PC, LR	      ; Return
   LDR             SP, =current_thread_ctx_addr
   LDR             SP, [SP]
   STR             LR, [SP,#0x40]
   ADD             SP, SP, #0x40
   STMFD           SP, {R0-LR}^
   SUB             R0, SP, #0x40
   MOV             LR, #6	 ; STATE_FAULTED
   STR             LR, [R0,#0x50]
   LDR             SP, =debug_args_addr
   BL              debug_print  ; Illegal Instruction:tid=%d,pid=%d,pc=0x%08x,sp=0x%08x
   B               schedule_yield
   BL      disable_interrupts
   LDR     R0, =current_thread_ctx_addr
   LDR     R0, [R0]
   MOV     LR, #6               ; STATE_FAULTED
   STR     LR, [R0,#0x50]
   MOV     R1, SP
   MOV     R2, R10
   LDR     SP, =debug_args_addr
   BL      debug_print_bad_stack ; Bad stack upon making system call:tid=%d,pid=%d,sp=0x%08x,sysCallNum=%d\n
   B       schedule_yield

Syscalls are invoked by way of the invalid instruction handler; syscalls take the form 0xE7F000F0 | (syscall_num << 8). (E.g. E7F000F0 is syscall 0, E7F036F0 is syscall 0x36, etc.).
The IOSU has 0x94 available syscalls with 5.3.2 (the number of installed syscalls can vary between system versions).

With 5.5.0 sp(user-mode/system-mode) is now bounds-checked right after the switch to system-mode. When out-of-bounds it will execute code similar to "invalid_syscall" described above. Hence, userland sp has to be within the current thread userland stackbottom/stacktop at the time a syscall is used, otherwise the fault code will be executed.

NOTE: Official syscall names begin with "IOS_", the rest are merely educated guesses.

These syscall numbers were last verified on 5.3.2 debug OSv10 and 5.5.0 retail OSv10. They may vary for newer/older versions or OSv9.

Debug ID# Retail ID# Internal name Description Return value
0x00 0x00 int IOS_CreateThread(u32 (*proc)(void* arg), void* arg, u32* stack_top, u32 stacksize, int priority, u32 flags) Creates a thread (in paused state) New threadid or error (negative value)
0x01 0x01 int thread_join(int threadid, u32 *returned_value) Waits for a thread to finish executing 0 on success
0x02 0x02 int thread_cancel(int threadid, u32 return_value) Cancels an active thread. Threadid 0 will use the current thread. 0 on success
0x03 0x03 int get_tid() Get the current thread's ID Current threadid
0x04 0x04 void* access_ipc_buffer_pool() Gets the per-thread IPC buffer pool's address The IPC buffer pool address value
0x05 0x05 int get_pid() Get the current process' ID Current processid
0x06 0x06 int get_process_name(int pid, char *out_buffer) Get the specified process' name string 0 on success
0x07 0x07 int IOS_StartThread(int threadid) Resume the specified thread 0 on success
0x08 0x08 int thread_suspend(int threadid) Suspend the specified thread. Threadid 0 will use the current thread. 0 on success
0x09 0x09 int thread_yield() Yield execution to any higher priority threads 0 on success
0x0A 0x0A int IOS_GetThreadPriority(int threadid) Get the priority of the specified thread The thread's priority or error (negative value)
0x0B 0x0B int IOS_SetThreadPriority(int threadid, int priority) Set the priority of the specified thread 0 on success
0x0C 0x0C int IOS_CreateMessageQueue(u32 *ptr, u32 n_msgs) Create a queue at ptr, for n_msgs messages The queue ID
0x0D 0x0D int IOS_DestroyMessageQueue(int queueid) Destroy a message queue 0 on success
0x0E 0x0E int IOS_SendMessage(int queueid, u32 message, u32 flags) Add a message to the end queue 0 on success
0x0F 0x0F int IOS_JamMessage(int queueid, u32 message, u32 flags) Add a message to the front of a queue 0 on success
0x10 0x10 int IOS_ReceiveMessage(int queueid, u32 *message, u32 flags) Fetch a message from the front of a queue 0 on success
0x11 0x11 int IOS_HandleEvent(int device, int queueid, int message) Register queueid as a handler for interrupts generated by device (sends message to queueid when device's interrupt is triggered) 0 on success
0x12 0x12 int unregister_event_handler(int device) Unregister handler for device 0 on success
0x13 0x13 int IOS_CreateTimer(int time_us, int repeat_time_us, int queueid, u32 message) Create a timer that sends a message to a queue after the elapsed period(s) Timerid or error (negative value)
0x14 0x14 int IOS_RestartTimer(int timerid, int time_us, int repeat_time_us) Restart a timer using the specified period(s) 0 on success
0x15 0x15 int IOS_StopTimer(int timerid) Pauses the specified timer 0 on success
0x16 0x16 int IOS_DestroyTimer(int timerid) Destroys the specified timer 0 on success
0x17 0x17 u32 get_timestamp() Get the current timestamp The current timestamp value
0x18 0x18 u32 time_now() Fetch the current value of the Starbuck's timer The current value of the timer register
0x19 0x19 int IOS_GetUpTimeStruct(???) 0 on success
0x1A 0x1A int IOS_GetUpTime64(u64 *out_buf) Returns the current time in wide format 0 on success
0x1B 0x1B int set_rtc_counter(u64 *in_buf) Sets the RTC counter used by MCP (can only be called by MCP) 0 on success
0x1C 0x1C int IOS_GetAbsTimeCalendar(void *out_buf) Returns the current date and time in a 0x18 sized struct (0x00: year; 0x04: day; 0x08: month; 0x0C: hour; 0x10: minute; 0x14: second) 0 on success
0x1D 0x1D int IOS_GetAbsTime64(???) 0 on success
0x1E 0x1E int IOS_GetAbsTimeStruct(???) 0 on success
0x1F 0x1F int set_fault_behavior(int pid, u32 flag) Enables or disables raising a panic when a system fault occurs in the specified process. This is only usable from the IOS-MCP process. Once finished, this will always set the flag for PID0(IOS-KERNEL) to value 1. This is the same field mentioned here for exception handling. 0 on success, -1 when curpid is not 1, and -29 when the input pid is >13.
0x20 0x20 int check_debug_mode() Checks if we are in debug mode by looking at a flag in the OTP 0 if in debug mode
0x21 0x21 int check_jtag() Get the current status of the JTAG 0 if JTAG is enabled or -4 if disabled
0x22 0x22 int read_otp(u32 wordindex, void *out_buf, u32 size) Read data from the OTP, this can only be used from the IOS-CRYPTO process. 0 on success, -1 when IOSU's PID isn't 3.
0x23 0x23 int heap_create(void *ptr, int size) Create a new heap at ptr of size bytes The heapid or error (negative value)
0x24 0x24 int IOS_CreateLocalProcessHeap(void *ptr, int size) Create a new local process heap of size bytes The heap ID or error (negative value)
0x25 0x25 int IOS_CreateCrossProcessHeap(int size) Create a new cross process heap of size bytes The heap ID or error (negative value)
0x26 0x26 int heap_destroy(int heapid) Destroy the specified heap 0 on success
0x27 0x27 void* IOS_Alloc(int heapid, u32 size) Allocate size bytes from the specified heap Pointer to memory
0x28 0x28 void* heap_alloc_aligned(int heapid, u32 size, u32 align) Allocate size bytes from the specified heap with the requested alignment Pointer to aligned memory
0x29 0x29 void IOS_Free(int heapid, void *ptr) Release allocated memory back to the heap 0 on success
0x2A 0x2A void heap_free_and_clear(int heapid, void *ptr) Same as IOS_Free, but clears the contents of ptr 0 on success
0x2B 0x2B int heap_expand(int heapid, void *ptr, u32 size) Expands an allocated memory block by size bytes 0 on success
0x2C 0x2C int IOS_RegisterResourceManager(const char* device, int queueid) Registers device to the device tree, so it can be opened (from ARM and PPC) 0 on success
0x2D 0x2D int device_associate(const char* device, int internal_id) Associates a device to the specified internal IOS ID. This ID appears to correspond to the cos.xml permissions groupid? This syscall isn't used with devices that don't require any permissions(and are PowerPC-accessible) it seems. It appears when this ID isn't listed in the cos.xml groupids at all, the device is ARM-only. 0 on success
0x2E 0x2E int device_set_flags(const char* device, u32 flags) Sets some flags in the device's internal structure 0 on success
0x2F 0x2F int set_client_capabilities(int client_pid, int feature_id, u32 *masks) Sets the client's capability masks/permissions (can only be called by MCP) 0 on success
0x30 0x30 int clear_client_capabilities(int client_pid) Clears the client's capability masks/permissions (can only be called by MCP) 0 on success
0x31 0x31 int query_client_capabilities(int client_pid, int feature_id, void *out_buffer) Gets the client's capability masks/permissions (out_buffer gets 0x08 bytes; mask1 and mask2) 0 on success
0x32 0x32 int query_feature_id(int feature_id, int out_count, void *out_buffer) Retrieves information associated with a feature_id (out_buffer receives out_count * 0x34 bytes structures with the client pid, busy close violations count, active TXN count and open handles count) 0 on success
0x33 0x33 int IOS_Open(const char* device, int mode) Similar to IOS_Open on PPC, except now internal to the IOSU system Returns an fd or error (negative)
0x34 0x34 int IOS_Close(int fd) Close a previously opened fd 0 on success
0x35 0x35 int IOS_Read(int fd, void *buf, u32 len) Read len bytes from fd into buf The number of bytes read or error
0x36 0x36 int IOS_Write(int fd, const void *buf, u32 len) Write len bytes to fd from buf The number of bytes written or error
0x37 0x37 int IOS_Seek(int fd, int offset, int origin) Seek to offset relative to origin The new absolute offset or error
0x38 0x38 int IOS_Ioctl(int fd, u32 request, void *input_buffer, u32 input_buffer_len, void *output_buffer, u32 output_buffer_len) Perform the requested IOCTL Return value from IOCTL
0x39 0x39 int IOS_Ioctlv(int fd, u32 request, u32 vector_count_in, u32 vector_count_out, struct iovec *vector) Perform the requested IOCTL Return value from IOCTL
0x3A 0x3A int IOS_OpenAsync(const char* device, int mode, int queueid, ipcmessage *message) Async implementation of IOS_Open 0 on success, ipcmessage is sent to the queue with the command's return value
0x3B 0x3B int IOS_CloseAsync(int fd, int queueid, ipcmessage *message) Async implementation of IOS_Close 0 on success
0x3C 0x3C int IOS_ReadAsync(int fd, void *buf, u32 len, int queueid, ipcmessage *message) Async implementation of IOS_Read 0 on success
0x3D 0x3D int IOS_WriteAsync(int fd, const void *buf, u32 len, int queueid, ipcmessage *message) Async implementation of IOS_Write 0 on success
0x3E 0x3E int IOS_SeekAsync(int fd, int offset, int origin, int queueid, ipcmessage *message) Async implementation of IOS_Seek 0 on success
0x3F 0x3F int IOS_IoctlAsync(int fd, u32 request, void *input_buffer, u32 input_buffer_len, void *output_buffer, u32 output_buffer_len, int queueid, ipcmessage *message) Async implementation of IOS_Ioctl 0 on success
0x40 0x40 int IOS_IoctlvAsync(int fd, u32 request, u32 vector_count_in, u32 vector_count_out, struct iovec *vector, int queueid, ipcmessage *message) Async implementation of IOS_Ioctlv 0 on success
0x41 0x41 int open_as_async(???)
0x42 0x42 int write_as_async(???)
0x43 0x43 int ipc_resume(???)
0x44 0x44 int ipc_suspend(???)
0x45 0x45 int ipc_svcmsg(???)
0x46 0x46 int ipc_resume_async(???)
0x47 0x47 int ipc_suspend_async(???)
0x48 0x48 int ipc_svcmsg_async(???)
0x49 0x49 int IOS_ResourceReply(void *ipc_handle, u32 result) Reply back through the IPC handle 0 on success
0x4A 0x4A int set_proc_unk_params(int pid, u32 param1, u32 param2, u32 param3) Sets some unknown parameters on the specified process' internal structure 0 on success
0x4B 0x4B int get_proc_unk_params(int pid, u32 *out_buf1, u32 *out_buf2) Gets some unknown parameters from the specified process' internal structure 0 on success
0x4C 0x4C int ahbMemFlush(int ahb_dev) Performs some additional checks and calls ahb_flush_from 0 on success
0x4D 0x4D int ahb_flush_from(int ahb_dev) Performs AHB memory flushing 0 on success
0x4E 0x4E int ahb_flush_check(int ahb_dev) Checks for valid device masks 0 on success
0x4F 0x4F int ahb_flush_to(int ahb_dev) Performs AHB memory flushing 0 on success
0x50 0x50 int IOS_ClearandEnable(int id) Enables hardware interrupts (IRQs) for the specified device 0 on success
0x51 int access_iobuf_pool(int unk) Unused Always 0
0x52 struct iobuf *alloc_iobuf(int unk, u32 buf_size) Allocate an iobuf NULL on error
0x53 int free_iobuf(struct iobuf *iob) Free an allocated iobuf 0 on success
0x54 void iobuf_log_header_info() Unused Nothing
0x55 void iobuf_log_buffer_info() Unused Nothing
0x56 void *extend_iobuf(struct iobuf *iob, unsigned short num) Extend the data in the buffer by num bytes Pointer to extended area
0x57 void *IOS_PushIob(struct iobuf *iob, unsigned short num) Move head pointer num bytes towards the buffer end Old head pointer
0x58 void *IOS_PullIob(struct iobuf *iob, unsigned short num) Move head pointer num bytes towards the buffer start Old head pointer
0x59 int verify_iobuf(struct iobuf *iob) Verify if the argument points to an iobuf 0 on success
0x5A struct iobuf *copy_iobuf(struct iobuf *iob) Copy an iobuf into the pool Itself
0x5B 0x51 void IOS_InvalidateDCache(void *ptr, unsigned int len) Invalidate data cache Nothing
0x5C 0x52 void IOS_FlushDCache(void *ptr, unsigned int len) Flush data cache Nothing
0x5D 0x53 int execute_privileged(void *address) Disables memory protection, cleans up executable memory areas and branches to the specified address. This can only be called by MCP, and will infinite loop on return 0 on success
0x5E 0x54 int get_unk_flags1(u32 *out_buf1, u16 *out_buf2) Gets (u32*)out_buf1 = 0x03; (u16*)out_buf2 = 0x00 0 on success
0x5F 0x55 int get_unk_flags2(u32 *out_buf1, u16 *out_buf2) Gets (u32*)out_buf1 = 0x01; (u16*)out_buf2 = 0x00 0 on success
0x60 0x56 void* virt_to_phys(void *address) Translate a virtual address to physical The translated address
0x61 0x57 int IOS_CreateSemaphore(???)
0x62 0x58 int IOS_WaitSemaphore(???)
0x63 0x59 int IOS_SignalSemaphore(???)
0x64 0x5A int IOS_DestroySemaphore(???)
0x65 0x5B int flush_ipc_server() Resets the ARM IPC control register's flags 0 on success
0x66 0x5C int set_bsp_ready() Tells the IOSU that BSP is ready 0 on success
0x67 0x5D int check_ios_addr_range(void *address, u32 size, u32 rw_flags) Checks an IOSU address range for read/write permissions 0 on success
0x68 0x5E int check_ppc_addr_range(void *address, u32 size) Checks if a PPC address range is registered in the IOSU's address table 0 on success
0x69 0x5F int init_mem1_ppc() Fills range 0x00000000 to 0x00002000 in MEM1 with empty PPC branches Always 0
0x6A 0x60 int get_iop_cpu_utilization() IOP CPU utilization
0x6B 0x61 int get_thread_stack_info(int tid, u32 *out_buf) Gets information on the specified thread's stack:
0x00(out_buf) == sys stack base
0x04(out_buf) == sys stack size (0x400)
0x08(out_buf) == sys stack used space
0x0C(out_buf) == user stack base
0x10(out_buf) == user stack size
0x14(out_buf) == user stack used space
0 on success
0x6C 0x62 int IOS_ThreadProfileCommand(int command, u32 unk) Issues a command to the thread profiling system. Valid commands are:
67h : Start profiling
64h : Stop profiling
65h : Stop profiling interval (disabled on retail?)
66h, 6Eh, 6Fh : Unknown
0 on success, when in PROD mode unavailable (NOTREADY)
0x6D 0x63 int IOS_GetThreadUtilization(void *out_buf) Dumps the current thread's utilization structure 0 on success
0x6E 0x64 int dump_thread_context(int tid, u32 *out_buf) Dumps the specified thread's context structure 0 on success
0x6F 0x65 int dump_thread_profile(int tid_count, u32 *out_buf) Dumps profiling information for each thread up to a specified count Number of dumped threads
0x70 int dump_iobuf_context(int iobuf_id, u32 *out_buf) Dumps the specified iobuf's context structure 0 on success
0x71 int IOS_GetIobPoolsUtilization(int iobuf_count, u32 *out_buf) Dumps iobuf's pools utilization for each iobuf up to a specified count Size of each used pool
0x72 0x66 int IOS_GetMessageUtilization(???) 0 on success
0x73 0x67 int get_aggregate_resource_utilization(???) 0 on success
0x74 0x68 int get_per_process_resource_utilization(???) 0 on success
0x75 0x69 int IOS_GetTimerUtilization(???) 0 on success
0x76 0x6A int IOS_GetSemaphoreUtilization(???) 0 on success
0x77 0x6B int get_heap_profile(???) 0 on success
0x78 0x6C int set_iop2x_state(u32 state) Sets the state of an unknown feature from BSP 0 on success
0x79 0x6D int set_ppc_boot_params(void *params) Registers the supplied address as a pointer for setting up the PPC boot parameters 0 on success
0x7A 0x6E void get_debug_register_value() Stores the value from LT_DEBUG register in the IOSU heap Nothing
0x7B 0x6F void clear_debug_register_value() Clears the LT_DEBUG register Nothing
0x7C 0x70 void set_debug_register_value(u32 value) Sets the value of the LT_DEBUG register Nothing
0x7D 0x71 int check_debug_flag(u32 flag) Checks if the supplied flag is enabled in the LT_DEBUG register copy on the IOSU heap The flag value if enabled or 0 if disabled
0x7E 0x72 void ios_shutdown(bool reset) Issues a system shutdown or reset depending on the input arg Nothing
0x7F 0x73 void ios_panic(char *panic_desc, u32 panic_desc_size) Issues a system panic (with optional description string) Nothing
0x80 0x74 void ios_reset() Issues a system reset Nothing
0x81 0x75 void set_panic_behavior(int flag) Changes the system behavior on panic:
flag is 0 -> crash on panic
flag is 1 -> reset on panic
flag is 2 -> reset EXI as well
0x82 0x76 int set_syslog_buffer(void *log_buf) Sets up the system log buffer in the IOSU heap 0 on success
0x83 0x77 int load_ppc_kernel(u32 address, u32 size) Maps the PPC kernel image memory:
address == 0x08000000
size == 0x00120000
0 on success
0x84 0x78 int load_ppc_app(int mem_id, u32 addr1, u32 size1, u32 addr2, u32 size2) Maps the PPC user application memory:
mem_id == 0x02
addr1 == 0x00
size1 == 0x00
addr2 == 0x28000000
size2 == 0xA8000000
0 on success
0x85 0x79 int set_security_level(int level) Sets the master title security level:
0x1E is normal
0x0A is TEST
0x14 is unknown
0 on success
0x86 0x7A int get_security_level() Gets the master title security level The security level
0x87 0x7B int get_open_resource_handles(int out_count, void *out_buffer, int pid) Finds open resource handles for the specified pid (out_buffer receives out_count * 0x2C bytes structures with the handle number, handle path and more) 0 on success
0x88 0x7C int set_main_title_sdk_version(int version) Sets the master title's SDK/kernel version 0 on success
0x89 0x7D int get_main_title_sdk_version() Gets the master title's SDK/kernel version The SDK/kernel version
0x8A 0x7E int get_dynamic_heap_access(???) 0 on success
0x8B 0x7F int start_gpu_config_validation(void *out_buf, u32 size, int queue_id, int unk) Validates the current GPU configuration using a message queue. Sends a pointer that is populated by the IOSU with the GPU configuration parameters (using GPIO) 0 on success
0x8C 0x80 int finish_gpu_config_validation(int queue_id, bool do_panic) Resets the buffer sent to the IOSU and invalidates the associated queue ID. Also looks for any errors raised due to a bad configuration state and throws a panic if specifically told to do so 0 on success
0x8D 0x81 int return_null() Unused Always 0
0x8E 0x82 int get_resource_violations(???) 0 on success
0x8F 0x83 int device_get_client_handles(char *dev_node) Returns the number of client handles created in the specified dev node The number of active client handles
0x90 0x84 int device_disable_registration(bool do_disable) Prevents any future dev node from being registered An error code (0xFFFFFFE3)
0x91 0x85 int get_pending_resource_requests(???) 0 on success
0x92 0x86 int load_ios_kernel() Maps the IOS Kernel image memory 0 on success
0x93 0x87 void exi_reset() Resets the EXI for the PPC side Nothing

Syscalls (via syscall instruction)

These types of syscalls are created with the thumb syscall instruction. When the u16 from retaddr-0x2 matches 0xdfab(intended as thumb "svc 0xab" but ARM "svc 0xdfab" would pass too), it will just return from the exception-handler, otherwise it will do the same thing described here for exceptions. These syscalls are RealView semihosting operations that allow communication with a debugger.
Currently only syscall 0x04 is still used in production versions of IOSU. Syscall 0x06 is only used for a scanf call in some kind of debug configuration, with the following prompt: "Enter '1' to proceed with kernel startup."

MOVS r0, #syscall_number

Register r0 takes the syscall number.
Register r1 takes the first parameter.

ID # Internal name Description Return value
0x01 __sys_open unused
0x02 __sys_close unused
0x03 __sys_writec unused
0x04 __sys_write0 Prints a null-terminated debug message None
0x05 __sys_write unused
0x06 __sys_read Reads input from debugger stdin
0x07 __sys_readc unused
0x08 __sys_iserror unused
0x09 __sys_istty unused
0x0A __sys_seek unused
0x0C __sys_flen unused
0x0D __sys_tmpnam unused
0x0E __sys_remove unused
0x0F __sys_rename unused
0x10 __sys_clocK unused
0x11 __sys_time unused
0x12 __sys_system unused
0x13 __sys_errno unused
0x15 __sys_get_cmdline unused
0x16 __sys_heapinfo unused
0x30 __sys_elapsed unused
0x31 __sys_tickfreq unused

Auxiliary vectors

The IOSU elf has a PH_NOTES section which contains a so called "mrg file". This "mrg file" contains auxiliary vectors for IOSU modules.

The vectors are parsed by IOS-KERNEL, before launching the modules.

The first 0xc bytes of the notes section make up a Elf32_Nhdr. After that there are 6 auxv_t for each module (14 in 5.5.X).

The following auxiliary vector types are used:

Name Value Description
AT_ENTRY 0x09 Entry point address
AT_UID 0x0b Module ID
AT_PRIORITY 0x7d Main thread priority
AT_STACK_SIZE 0x7e Main thread stack size
AT_STACK_ADDR 0x7f Main thread stack address

Auxiliary vectors from 5.5.X:

 AT_UID:                0
 AT_ENTRY:              0xFFFF0000
 AT_PRIORITY:           0x0
 AT_STACK_SIZE:         0x0
 AT_STACK_ADDR:         0x00000000
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                1
 AT_ENTRY:              0x05056718
 AT_PRIORITY:           0x7C
 AT_STACK_SIZE:         0x2000
 AT_STACK_ADDR:         0x050BA4A0
 AT_MEM_PERM_MASK:      0x000C0030
 AT_UID:                2
 AT_ENTRY:              0xE600F848
 AT_PRIORITY:           0x7D
 AT_STACK_SIZE:         0x1000
 AT_STACK_ADDR:         0xE7000000
 AT_MEM_PERM_MASK:      0x00100000
 AT_UID:                3
 AT_ENTRY:              0x04015EA4
 AT_PRIORITY:           0x7B
 AT_STACK_SIZE:         0x1000
 AT_STACK_ADDR:         0x04028628
 AT_MEM_PERM_MASK:      0x000C0030
 AT_UID:                4
 AT_ENTRY:              0x1012E9E8
 AT_PRIORITY:           0x6B
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0x104B92C8
 AT_MEM_PERM_MASK:      0x00038600
 AT_UID:                5
 AT_ENTRY:              0x107F6830
 AT_PRIORITY:           0x55
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0x1114117C
 AT_MEM_PERM_MASK:      0x001C5870
 AT_UID:                6
 AT_ENTRY:              0x11F82D94
 AT_PRIORITY:           0x75
 AT_STACK_SIZE:         0x2000
 AT_STACK_ADDR:         0x1214AB4C
 AT_MEM_PERM_MASK:      0x00008180
 AT_UID:                7
 AT_ENTRY:              0x123E4174
 AT_PRIORITY:           0x50
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0x12804498
 AT_MEM_PERM_MASK:      0x00002000
 AT_UID:                11
 AT_ENTRY:              0xE22602FC
 AT_PRIORITY:           0x32
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0xE22CB000
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                9
 AT_ENTRY:              0xE108E930
 AT_PRIORITY:           0x32
 AT_STACK_SIZE:         0x1000
 AT_STACK_ADDR:         0xE12E71A4
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                12
 AT_ENTRY:              0xE3166B34
 AT_PRIORITY:           0x32
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0xE31AF000
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                8
 AT_ENTRY:              0xE00D8290
 AT_PRIORITY:           0x32
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0xE0125390
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                10
 AT_ENTRY:              0xE500D720
 AT_PRIORITY:           0x46
 AT_STACK_SIZE:         0x4000
 AT_STACK_ADDR:         0xE506A900
 AT_MEM_PERM_MASK:      0x00000000
 AT_UID:                13
 AT_ENTRY:              0xE40168A4
 AT_PRIORITY:           0x4B
 AT_STACK_SIZE:         0x2000
 AT_STACK_ADDR:         0xE415623C
 AT_MEM_PERM_MASK:      0x00000000


Similarly to the Wii, IOS modules roughly map to processes and drivers inside the kernel. Modules have a locked PID associated with them:

PID Name
16 COS-02
17 COS-03

PIDs 14-21 are used for PPC side processes.


Cryptography services.


Master title operations such as title launching and cafe2wii booting.


USB controllers and devices.


File system services.


Gamepad controllers and devices.


Network services.


User level application management.


Network security services. This uses OpenSSL, as of 5.5.0 this is: "OpenSSL 1.0.0f 4 Jan 2012".


Nintendo's proprietary online services such as update installations. This uses statically-linked libcurl.

  • /dev/nim - Nintendo installation manager? (installs updates)
  • /dev/boss - BOSS service


Nintendo's proprietary friend system. This uses statically-linked libcurl.


Debugging and testing services.


Auxiliary services.



  • /dev/bsp - Board support package? (hardware interface)


These are not real /dev nodes. Instead, they represent internal mappings of system volumes created by the IOS-FS process as part of the virtual file system API's initialization.
The virtual file system API is able to map more than one instance of such volumes, whence why the final node name always has an integer representing it's instance (e.g.: 01).


The IOSU is able to create and handle up to 0xB4 threads. Each thread has a corresponding internal structure stored in kernel SRAM (0xFFFF4D78 in firmware 5.5.1).

// Thread struct in memory
  0x00: saved_cpsr
  0x04: saved_r0
  0x08: saved_r1
  0x0C: saved_r2
  0x10: saved_r3
  0x14: saved_r4
  0x18: saved_r5
  0x1C: saved_r6
  0x20: saved_r7
  0x24: saved_r8
  0x28: saved_r9
  0x2C: saved_r10
  0x30: saved_r11
  0x34: saved_r12
  0x38: saved_r13
  0x3C: saved_lr
  0x40: thread_pc
  0x44: thread_queue_next
  0x48: thread_min_priority
  0x4C: thread_max_priority
  0x50: thread_state
  0x54: owner_pid
  0x58: thread_id
  0x5C: flags
  0x60: exit_value
  0x64: join_thread_queue_head
  0x68: current_thread_queue
  0x6C: ?????
  0x70: ?????
  0x74: ?????
  0x78: ?????
  0x7C: ?????
  0x80: ?????
  0x84: ?????
  0x88: ?????
  0x8C: ?????
  0x90: ?????
  0x94: ?????
  0x98: ?????
  0x9C: ?????
  0xA0: ?????
  0xA4: thread_sp
  0xA8: ?????
  0xAC: ?????
  0xB0: sys_stack_addr
  0xB4: user_stack_addr
  0xB8: user_stack_size
  0xBC: ipc_buffer_pool
  0xC0: profiled_count
  0xC4: profiled_time
} thread_t;   // sizeof() = 0xC8
Thread states

0x00 -> Available
0x01 -> Ready
0x02 -> Running
0x03 -> Stopped
0x04 -> Waiting
0x05 -> Dead
0x06 -> Faulted
0x07 -> Unknown

Bit 2 in the flags determines whether or not the thread has an IPC buffer pool.


The IOSU is able to create and handle up to 0x30 heaps. Each heap has a corresponding descriptor structure stored in the kernel's BSS section (0x08150008 in firmware 5.5.1).

// Heap descriptor
  0x00: base
  0x04: owner_pid
  0x08: size
  0x0C: first_free
  0x10: error_count_out_of_memory
  0x14: error_count_free_block_not_in_heap
  0x18: error_count_expand_invalid_block
  0x1C: error_count_corrupted_block
  0x20: flags
  0x24: total_allocated_size
  0x28: largest_allocated_size
  0x2C: total_allocation_count
  0x30: total_freed_count
  0x34: error_count_free_unallocated_block
  0x38: error_count_alloc_invalid_heap
  0x3C: heap_id
} heap_descriptor_t;   // sizeof() = 0x40

All accesses to heaps are verified using owner PID and active PID. Heaps are referenced using IDs that are used as indices into the heap descriptor array. There are 3 special heap IDs:

Heap ID Purpose
0x0001 Shared heap
0xCAFE Local process heap for active PID
0xCAFF Cross process heap for active PID

Access to special heap IDs is redirected to the appropriate heap.

Each process can allocate a cross process heap for multiple processes to use and a local process heap for itself. These are kept tracked of using two arrays following the heap descriptor array in kernel BSS:

int32 local_process_heaps[14];
int32 cross_process_heaps[14];

They are initialized to IOS_ERROR_INVALID within the IOSU kernel and are set to the appropriate heap ID when created using IOS_CreateLocalProcessHeap or IOS_CreateCrossProcessHeap. There may only be one cross/local process heap for each PID.

Each heap descriptor contains a flag field that contains information about the heap:

0x1: Local process heap
0x2: Cross process heap

Each heap is created from memory of the shared heap. It is initialized as one big seperate memory chunk. Memory chunks have the following structure:

// Heap chunk header
  0x00: magic
  0x04: size
  0x08: back
  0x0C: next
} heap_chunk_header_t;   // sizeof() = 0x10

There are 3 valid magic values:

Magic Meaning
0xBABE0000 Chunk is free
0xBABE0001 Chunks is used
0xBABE0002 Chunk is inner chunk and used

When memory is allocated to a heap, the linked list (terminated using nullptr's) is traversed to find a large enough chunk, chunks are split and back and forward pointers are cleared for the allocated chunk. When a chunk is allocated aligned, a chunk bigger than the needed one may be allocated. Inside this chunk, a second heap chunk is set up in a fashion that the beginning of the memory block described by this "inner" chunk is aligned according to the specified alignment. It's magic is set to 0xBABE0002 and the back pointer is set to the chunk containing it. These inner chunks can not be expanded.


PowerPC code is able to call IOSU drivers through an IPC interface. It uses the same call interface as IOSU does internally. Userspace code submits IOSU requests with the IPCKDriver_SubmitRequest() syscall in the Cafe OS kernel. The kernel includes information to identify which Cafe OS process sent the request, allowing IOSU to check permissions on a per-app basis. Requests are contained in a struct, sent through a hardware interface, and marshalled by the IOSU kernel to a target process. An example of IOSU IPC from the PowerPC can be found here.

IPC request struct (size = 0x48, align = 0x20)

0x00: CMD (1=open, 2=close, 3=read, 4=write, 5=seek, 6=ioctl, 7=ioctlv)
0x04: Reply to client
0x08: Client FD
0x0C: Flags (always 0)
0x10: Client CPU (0=ARM internal, 1-3=PPC cores 0-2)
0x14: Client PID (PFID in older versions, RAMPID more recently?)
0x18: Client group ID (Title ID, upper)
0x1C: Client group ID (Title ID, lower)
0x20: Server handle (written by IOSU)
0x24: Arg0
0x28: Arg1
0x2C: Arg2
0x30: Arg3
0x34: Arg4
0x38: CMD (previous)
0x3C: Client FD (previous)
0x40: Virt0 (PPC virtual addresses to be translated)
0x44: Virt1 (PPC virtual addresses to be translated)
IPC commands

0x01 -> IOS_OPEN
0x02 -> IOS_CLOSE
0x03 -> IOS_READ
0x04 -> IOS_WRITE
0x05 -> IOS_SEEK
0x06 -> IOS_IOCTL
0x07 -> IOS_IOCTLV
0x08 -> IOS_REPLY (internal to IOSU)
0x09 -> IOS_IPC_MSG0 (internal to IOSU)
0x0A -> IOS_IPC_MSG1 (internal to IOSU)
0x0B -> IOS_IPC_MSG2 (internal to IOSU)
0x0C -> IOS_SUSPEND (internal to IOSU)
0x0D -> IOS_RESUME (internal to IOSU)
0x0E -> IOS_SVCMSG (internal to IOSU)
IPC client PIDs

On older versions of IOSU, it seems to match the PFID list. More recently, it appears to use the RAMPID. See the Cafe OS PID tables.
IPC arguments

Open CMD:   Client FD == 0
            Arg0 = name
            Arg1 = name_size
            Arg2 = mode (0 = none, 1 = read, 2 = write)
            Arg3-Arg4 = u64 permissions_bitmask for the target IOSU process, loaded by the target IOSU process during fd init. With PPC this originates from the cos.xml of the source process.

Close CMD:  Client FD != 0

Read CMD:   Client FD != 0
            Arg0 = outPtr
            Arg1 = outLen

Write CMD:  Client FD != 0
            Arg0 = inPtr
            Arg1 = inLen

Seek CMD:   Client FD != 0
            Arg0 = where
            Arg1 = whence

IOCtl CMD:  Client FD != 0
            Arg0 = cmd
            Arg1 = inPtr
            Arg2 = inLen
            Arg3 = outPtr
            Arg4 = outLen

IOCtlv CMD: Client FD != 0
            Arg0 = cmd
            Arg1 = readCount
            Arg2 = writeCount
            Arg3 = vector

Error codes

Kernel errors

0x00000000 -> IOS_ERROR_OK

Virtual Memory Map

Virtual address range Physical address range Size Description
0x04000000 - 0x04030000 0x08280000 - 0x082B0000 0x30000 IOS-CRYPTO
0x05000000 - 0x050C0000 0x081C0000 - 0x08280000 0xC0000 IOS-MCP
0x05100000 - 0x05120000 0x13D80000 - 0x13DA0000 0x20000 IOS-MCP (debug and recovery mode)
0x08120000 - 0x081C0000 0x08120000 - 0x081C0000 0xA0000 IOS-KERNEL
0x10000000 - 0x10100000 0x10000000 - 0x10100000 0x100000 PRSH/PRST
0x10100000 - 0x104D0000 0x10100000 - 0x104D0000 0x3D0000 IOS-USB
0x10700000 - 0x11C40000 0x10700000 - 0x11C40000 0x1540000 IOS-FS (5.5.1 retail)
0x10800000 - 0x11EE0000 0x10800000 - 0x11EE0000 0x16E0000 IOS-FS (5.3.2 debug)
0x11F00000 - 0x12160000 0x11F00000 - 0x12160000 0x260000 IOS-PAD
0x12300000 - 0x12890000 0x12300000 - 0x12890000 0x590000 IOS-NET
0x1D000000 - 0x1FB00000 0x1D000000 - 0x1FB00000 0x2B00000 Global heap
0x1FB00000 - 0x1FE00000 0x1FB00000 - 0x1FE00000 0x300000 Global IOB (input/output block)
0x1FE00000 - 0x1FE20000 0x1FE00000 - 0x1FE20000 0x40000 IOS-MCP (shared region)
0x1FE40000 - 0x20000000 0x1FE40000 - 0x20000000 0x1C0000 IOS-MCP (setup region)
0x20000000 - 0x28000000 0x20000000 - 0x28000000 0x8000000 RAMDISK
0xE0000000 - 0xE0270000 0x12900000 - 0x12B70000 0x270000 IOS-ACP
0xE1000000 - 0xE12F0000 0x12BC0000 - 0x12EB0000 0x2F0000 IOS-NSEC
0xE2000000 - 0xE26D0000 0x12EC0000 - 0x13590000 0x6D0000 IOS-NIM-BOSS
0xE3000000 - 0xE3300000 0x13640000 - 0x13940000 0x300000 IOS-FPD
0xE4000000 - 0xE4160000 0x13A40000 - 0x13BA0000 0x160000 IOS-TEST
0xE5000000 - 0xE5070000 0x13C00000 - 0x13C70000 0x70000 IOS-AUXIL
0xE6000000 - 0xE6050000 0x13CC0000 - 0x13D80000 0xC0000 IOS-BSP
0xE7000000 - 0xE7001000
0xEFF00000 - 0xEFF08000 0xFFF00000 - 0xFFF08000 0x8000 C2W (cafe2wii) boot heap
0xFFFF0000 - 0xFFFFFFFF 0xFFFF0000 - 0xFFFFFFFF 0x10000 Kernel SRAM / C2W (cafe2wii)

The Starbuck MMU itself only has R/W permissions for data/instruction memory access, no XN. However, there is XN implemented via separate hardware registers at 0x0d8b0XXX. The register relative-offset is calculated with the physaddr of the memory being protected. Each u32 register corresponds to a different block of physical memory. Among other things, this controls whether the ARM is allowed to access the memory for instruction-access, and in what ARM-mode(userland/privileged) the instruction-access is permitted.

Hence, userland .text is only executable from userland. From userland, the only executable memory is the process .text. In privileged-mode, the only executable memory is the main kernel .text(0x08120000) and 0xffff0000(the latter is also RWX).

Exception handling

The data-abort and prefetch-abort exception handlers will first check whether a certain flag is clear(flagsfield & (1<<PID)). When that bit is clear and the PID is <=13(highest IOSU PID value that exists), it will just return from the function then do a context-switch. Otherwise, iosPanic() is called.