IOSU

From WiiUBrew
Jump to: navigation, search

IOSU is the operating system running on the ARM 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.

Contents

See Also

Architecture

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 image's 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 LT_MEMIRR 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   // LT_BOOT0
  r1 <- 0x00(LT_BOOT0)
  r1 <- r1 | 0x1000
  0x00(LT_BOOT0) <- 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.

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: ?????
  0x48: thread_min_priority
  0x4C: thread_max_priority
  0x50: thread_state
  0x54: owner_pid
  0x58: thread_id
  0x5C: flags
  0x60: ?????
  0x64: ?????
  0x68: ?????
  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: (???) insufficient_memory_error_count
  0x14: ?????
  0x18: (???) error_count
  0x1C: invalid_chunk_count
  0x20: flags
  0x24: total_allocated_size
  0x28: (???) smallest_chunk_size
  0x2C: (???) total_allocation_count
  0x30: total_chunks_freed
  0x34: (???) double_free_error_count
  0x38: ?????
  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

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.

IOS-MCP

Master title operations such as title launching and cafe2wii booting.

IOS-USB

USB controllers and devices.

IOS-FS

File system services.

IOS-PAD

Gamepad controllers and devices.

IOS-NET

Network services.

IOS-ACP

User level application management.

IOS-NSEC

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

IOS-NIM-BOSS

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

IOS-FPD

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

IOS-TEST

Debugging and testing services.

IOS-AUXIL

Auxiliary services.

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).

Virtual Memory Map

  • 0x04000000 - 0x04030000 IOS-CRYPTO
  • 0x05000000 - 0x050C0000 IOS-MCP
  • 0x05100000 - 0x05120000 IOS-MCP (debug and recovery mode)
  • 0x08120000 - 0x081C0000 IOS-KERNEL
  • 0x0D400000 - 0x0D410000 BOOT1
  • 0x0D410000 - 0x0D420000 BOOT0
  • 0x10000000 - 0x10100000 Unknown
  • 0x10100000 - 0x104D0000 IOS-USB
  • 0x10700000 - 0x11C40000 IOS-FS (5.5.1 retail)
  • 0x10800000 - 0x11EE0000 IOS-FS (5.3.2 debug)
  • 0x11F00000 - 0x12160000 IOS-PAD
  • 0x12300000 - 0x12890000 IOS-NET
  • 0x1D000000 - 0x1FB00000 Global heap
  • 0x1FB00000 - 0x1FE00000 Global IOB (input/output block)
  • 0x1FE00000 - 0x1FE20000 Unknown
  • 0x1FE40000 - 0x20000000 Unknown
  • 0x20000000 - 0x28000000 Unknown
  • 0xE0000000 - 0xE0270000 IOS-ACP
  • 0xE1000000 - 0xE12F0000 IOS-NSEC
  • 0xE2000000 - 0xE26D0000 IOS-NIM-BOSS
  • 0xE3000000 - 0xE3300000 IOS-FPD
  • 0xE4000000 - 0xE4160000 IOS-TEST
  • 0xE5000000 - 0xE5070000 IOS-AUXIL
  • 0xE6000000 - 0xE6050000 IOS-BSP
  • 0xE7000000 - 0xE7001000 Unknown
  • 0xEFF00000 - 0xEFF08000 Unknown
  • 0xFFFF0000 - 0xFFFFFFFF Kernel SRAM


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.

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox