IOS
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).
Kernel
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) sub_FFFF0060() { 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 init_svc_stack(0xFFFF0904); // MSR CPSR_c, #0xD2 -> Enter IRQ mode, FIQ/IRQ disabled // SP == 0xFFFF1104 init_irq_stack(0xFFFF1104); // MSR CPSR_c, #0xD1 -> Enter FIQ mode, FIQ/IRQ disabled // SP == 0xFFFF1504 init_fiq_stack(0xFFFF1504); // MSR CPSR_c, #0xD7 -> Enter abort mode, FIQ/IRQ disabled // SP == 0xFFFF0D04 init_abort_stack(0xFFFF0D04); // MSR CPSR_c, #0xDB -> Enter undefined mode, FIQ/IRQ disabled // SP == 0xFFFF1904 init_undefined_stack(0xFFFF1904); // MSR CPSR_c, #0xDF -> Enter system mode, FIQ/IRQ disabled // SP == 0xFFFF1904 init_user_stack(0xFFFF1904); // Jump to MEM0 check lr <- pc pc <- sub_FFFFDA38 while(1); }
MEM0's integrity is checked before going any further:
// Check MEM0 for IOS-KERNEL (firmware 5.5.1) sub_FFFFDA38() { r1 <- 0x08143008 // IOSU kernel data region r3 <- 0xA5A51212 r2 <- 0x00(0x08143008) // Deadlock if (0x00(0x08143008) != 0xA5A51212) { r3 <- 0xDEAD1111 sp <- 0xDEAD1111 while(1); } r12 <- 0xFFFF0500 // IOSU boot heap region r2 <- 0x00(0xFFFF0500) // Deadlock if (0x00(0xFFFF0500) != 0xA5A51212) { r3 <- 0xDEAD1111 sp <- 0xDEAD1111 while(1); } // Set init magic r2 <- 0x5A5AEDED 0x00(0xFFFF0500) <- 0x5A5AEDED 0x00(0x08143008) <- 0x5A5AEDED // Flush AHB for Starbuck ahbMemFlush(0); ahb_flush_to(1); // Load IOS_KERNEL r2 <- sub_8121B18 sub_8121B18(); // Deadlock r3 <- 0xDEAD2222 sp <- 0xDEAD2222 while(1); }
And, finally, execution switches over to MEM0 where the IOS-KERNEL module will be running:
// Load IOS-KERNEL (firmware 5.5.1) sub_8121B18() { // Read LT_DEBUG register // and store it's value at 0x08150000 sub_8120970(); // Clear LT_DEBUG register sub_8120988(); // 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" debug_printf("\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 sub_8120C58(); // Reset GPIOs and IRQs sub_8120510(); // Setup IRQ handlers sub_8120488(); // Clear and set some kernel structures sub_812B308(); // Setup iobuf sub_81235E8(); // Re-map shared_user_ro sub_81294AC(); // Clear IOS_KERNEL module's thread stack and run it sub_812CD08(); return; }
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:
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 get_stack_arg 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 find_syscall_and_jump MOV SP, R8 LDR R11, =syscall_table LDR R11, [R11,R10,LSL#2] MOV LR, PC BX R11 return_to_caller 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}^ NOP MOV R0, LR LDR LR, [SP,#0x40] MOVS PC, LR ; Return invalid_syscall 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 bad_stack 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 |
Nothing |
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
SVC 0xAB
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 |
AT_MEM_PERM_MASK | 0x80 |
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
Modules
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 |
---|---|
0 | IOS-KERNEL |
1 | IOS-MCP |
2 | IOS-BSP |
3 | IOS-CRYPTO |
4 | IOS-USB |
5 | IOS-FS |
6 | IOS-PAD |
7 | IOS-NET |
8 | IOS-ACP |
9 | IOS-NSEC |
10 | IOS-AUXIL |
11 | IOS-NIM-BOSS |
12 | IOS-FPD |
13 | IOS-TEST |
14 | COS-KERNEL |
15 | COS-ROOT |
16 | COS-02 |
17 | COS-03 |
18 | COS-OVERLAY |
19 | COS-HBM |
20 | COS-ERROR |
21 | COS-MASTER |
PIDs 14-21 are used for PPC side processes.
IOS-CRYPTO
Cryptography services.
- /dev/crypto - Cryptography API
IOS-MCP
Master title operations such as title launching and cafe2wii booting.
- /dev/mcp - Master title launching (also encapsulates ES from the Wii)
- /dev/mcp_recovery - Master title launching (recovery mode)
- /dev/volflush - Volume cache flushing service
- /dev/pm - Power management
- /dev/syslog - System logging
- /dev/usb_syslog - USB system logging
- /dev/dk_syslog - DevKit system logging
- /dev/ppc_app - PPC application service
- /dev/ppc_kernel - PPC kernel service
IOS-USB
USB controllers and devices.
- /dev/usbproc1 - USB internal process
- /dev/usbproc2 - USB internal recovery process
- /dev/uhs/0 - USB host stack (external ports)
- /dev/usb_cdc - USB communications device class
- /dev/usb_hid - USB human interface device
- /dev/usb_uac - USB audio class
- /dev/usb_midi - USB musical instrument digital interface
IOS-FS
File system services.
- /dev/fsa - Virtual file system API
- /dev/dk - DevKit file system API
- /dev/odm - Optical disk manager
- /dev/ramdisk_svc - RAM disk service
- /dev/ums - USB mass storage
- /dev/df - Disk format
- /dev/atfs - Optical disk file system
- /dev/isfs - Internal storage file system
- /dev/wfs - Wii file system
- /dev/pcfs - PC file system (only available in DEBUG or TEST mode)
- /dev/rbfs - Raw file system
- /dev/fat - FAT file system
- /dev/fla - Flash file system
- /dev/ahcimgr - Advanced host controller interface manager
- /dev/shdd - SATA hard disk drive
- /dev/md - Raw memory device
- /dev/scfm - SLC cache for MLC
- /dev/mmc - eMMC storage
- /dev/timetrace - File IO time tracer
IOS-PAD
Gamepad controllers and devices.
- /dev/uhs/1 - USB host stack (internal devices)
- /dev/ccr_io - Gamepad main service
- /dev/ccr_cdc - Gamepad RPC (CDC = Communications Device Class)
- /dev/ccr_hid - Gamepad input (HID = Human Interface Device)
- /dev/ccr_nfc - Gamepad NFC reader
- /dev/ccr_uac - Gamepad microphone (UAC = USB Audio Class)
- /dev/ccr_uvc - Gamepad camera (UVC = USB Video Class)
- /dev/usb/btrm - Bluetooth module (for Wii Remote and Pro Controller)
- /dev/usb/early_btrm - Secondary Bluetooth module
IOS-NET
Network services.
- /dev/network - Network main service
- /dev/socket - BSD sockets API
- /dev/ifnet - Network interface
- /dev/net/ifmgr - Network interface manager
- /dev/net/ifmgr/wd - Wireless device?
- /dev/net/ifmgr/ncl - Network configuration
- /dev/net/ifmgr/usbeth - Ethernet over USB
- /dev/ifuds - UDS interface
- /dev/udscntrl - UDS control. Used by nn_uds.rpl(see here).
- /dev/wl0 - Wireless interface
- /dev/wifidata - ?????
- /dev/wifi24 - WiFi driver
- /dev/ac_main - Auto Connection, used by nn_ac.rpl.
- /dev/ndm - ?????
- /dev/dlp - 3DS Download Play hosting, used by nn_dlp.rpl.
IOS-ACP
User level application management.
- /dev/acpproc - Application management internal process
- /dev/acp_main - Application management main service
- /dev/emd - ?????
- /dev/pdm - Play data manager? (stores applications' statistics)
- /dev/nnsm - Nintendo Network service manager?
- /dev/nnmisc - Nintendo Network miscellaneous?
IOS-NSEC
Network security services. This uses OpenSSL, as of 5.5.0 this is: "OpenSSL 1.0.0f 4 Jan 2012".
- /dev/nsec - Network security
- /dev/nsec/nss - Network security services
- /dev/nsec/nssl - Network SSL API
IOS-NIM-BOSS
Nintendo's proprietary online services such as update installations. This uses statically-linked libcurl.
IOS-FPD
Nintendo's proprietary friend system. This uses statically-linked libcurl.
IOS-TEST
Debugging and testing services.
- /dev/testproc1 - Test process
- /dev/testproc2 - Test process
- /dev/iopsh - IOP shell?
- /dev/cbl - Cafe OS block log
- /debug/prof - Profiler (DEBUG mode only)
- /test/ppcprotviol - PPC protocol violation (TEST mode only)
- /test/sp - System profiler (TEST mode only)
- /test/test_rm - Resource manager test (TEST mode only)
IOS-AUXIL
Auxiliary services.
- /dev/auxilproc - Auxiliary service's internal process
- /dev/im - Home menu
- /dev/usr_cfg - User configuration
IOS-BSP
Hardware.
- /dev/bsp - Board support package? (hardware interface)
Others
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).
- /dev/si01 - ?????
- /dev/slccmpt01 - NAND SLC (vWii compatible)
- /dev/slc01 - NAND SLC
- /dev/ramdisk01 - RAM disk
- /dev/mlc01 - NAND MLC
- /dev/hfio01 - Host file IO
- /dev/odd01 - Optical disk drive
- /dev/sdcard01 - SD card
- /dev/sd01 - ?????
- /dev/usb01 - USB device
- /dev/mlcorig01 - NAND MLC original? (Used for board types ID and IH instead of mlc, disables scfm)
- /dev/unknown01 - Unrecognized
Threads
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 struct { 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.
Heaps
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 struct { 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 struct { 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.
IPC
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 0x00 -> IOS_COMMAND_INVALID 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 0xFFFFFFFF -> IOS_ERROR_ACCESS 0xFFFFFFFE -> IOS_ERROR_EXISTS 0xFFFFFFFD -> IOS_ERROR_INTR 0xFFFFFFFC -> IOS_ERROR_INVALID 0xFFFFFFFB -> IOS_ERROR_MAX 0xFFFFFFFA -> IOS_ERROR_NOEXISTS 0xFFFFFFF9 -> IOS_ERROR_QEMPTY 0xFFFFFFF8 -> IOS_ERROR_QFULL 0xFFFFFFF7 -> IOS_ERROR_UNKNOWN 0xFFFFFFF6 -> IOS_ERROR_NOTREADY 0xFFFFFFF5 -> IOS_ERROR_ECC 0xFFFFFFF4 -> IOS_ERROR_ECC_CRIT 0xFFFFFFF3 -> IOS_ERROR_BADBLOCK 0xFFFFFFF2 -> IOS_ERROR_INVALID_OBJTYPE 0xFFFFFFF1 -> IOS_ERROR_INVALID_RNG 0xFFFFFFF0 -> IOS_ERROR_INVALID_FLAG 0xFFFFFFEF -> IOS_ERROR_INVALID_FORMAT 0xFFFFFFEE -> IOS_ERROR_INVALID_VERSION 0xFFFFFFED -> IOS_ERROR_INVALID_SIGNER 0xFFFFFFEC -> IOS_ERROR_FAIL_CHECKVALUE 0xFFFFFFEB -> IOS_ERROR_FAIL_INTERNAL 0xFFFFFFEA -> IOS_ERROR_FAIL_ALLOC 0xFFFFFFE9 -> IOS_ERROR_INVALID_SIZE 0xFFFFFFE8 -> IOS_ERROR_NO_LINK 0xFFFFFFE7 -> IOS_ERROR_AN_FAILED 0xFFFFFFE6 -> IOS_ERROR_MAX_SEM_COUNT 0xFFFFFFE5 -> IOS_ERROR_SEM_UNAVAILABLE 0xFFFFFFE4 -> IOS_ERROR_INVALID_HANDLE 0xFFFFFFE3 -> IOS_ERROR_INVALID_ARG 0xFFFFFFE2 -> IOS_ERROR_NO_RESOURCE 0xFFFFFFE1 -> IOS_ERROR_BUSY 0xFFFFFFE0 -> IOS_ERROR_TIMEOUT 0xFFFFFFDF -> IOS_ERROR_ALIGNMENT 0xFFFFFFDE -> IOS_ERROR_BSP 0xFFFFFFDD -> IOS_ERROR_DATA_PENDING 0xFFFFFFDC -> IOS_ERROR_EXPIRED 0xFFFFFFDB -> IOS_ERROR_NO_R_ACCESS 0xFFFFFFDA -> IOS_ERROR_NO_W_ACCESS 0xFFFFFFD9 -> IOS_ERROR_NO_RW_ACCESS 0xFFFFFFD8 -> IOS_ERROR_CLIENT_TXN_LIMIT 0xFFFFFFD7 -> IOS_ERROR_STALE_HANDLE 0xFFFFFFD6 -> IOS_ERROR_UNKNOWN_VALUE
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.