Revolution OS
Revolution OS is a part of the SDK that is always included. It does not handle security; IOS is responsible for that.
Public functions
Name | Prototype | Description |
---|---|---|
OSGetConsoleType | int OSGetConsoleType(void)
|
Returns the platform ID. See below for more details. |
OSInit | void OSInit(void)
|
Initializes all parts of the library. |
OSSaveFPUContext | void OSSaveFPUContext(OSContext *ctx)
|
Saves the floating point registers to ctx .
|
OSSetCurrentContext | void OSSetCurrentContext(OSContext *ctx)
|
Sets the context to ctx , causing future register saving to go there.
|
OSGetCurrentContext | OSContext *OSGetCurrentContext(void)
|
Gets the OSContext instance currently being used (not the floating point one).
|
OSSaveContext | bool OSSaveContext(OSContext *ctx)
|
Saves nonessential registers to ctx , since essential registers were saved by the exception vector if needed. Returns true if the function is returning after ctx was reloaded back to the state, which was saved as being in this function.
|
OSLoadContext | void OSLoadContext(OSContext *ctx)
|
Loads the registers in ctx . This function is also responsible for ensuring OSDisableInterrupts runs atomically to prevent MSR corruption.
|
OSGetStackPointer | void *OSGetStackPointer(void)
|
Returns the current stack pointer. |
OSSwitchFiber | void OSSwitchFiber(void *code, void *stack)
|
Swaps the stack and jumps to code .
|
OSSwitchFiberEx | void OSSwitchFiberEx(int param1, int param2, int param3, int param4, void *code, void *stack)
|
Swaps the stack and jumps to code , passing up to 4 args.
|
OSClearContext | void OSClearContext(OSContext *ctx)
|
Marks ctx as having nothing saved to it.
|
OSInitContext | void OSInitContext(OSContext *ctx)
|
Sets all saved registers in ctx to 0.
|
OSDumpContext | void OSDumpContext(OSContext *ctx)
|
Prints all saved registers in ctx .
|
OSReport | void OSReport(char *format, ...)
|
Calls vprintf ; the formatted text gets sent through some MetroTRK-related SerialIO port
|
OSPanic | void OSPanic(char *sourceFile, int lineNo, char *format, ...)
|
Prints the message similar to OSReport , then prints the crash location and a stack trace.
|
OSDisableInterrupts | bool OSDisableInterrupts(void)
|
Disables interrupts, allowing code to run atomically. Returns the previous interrupt state for a call to OSRestoreInterrupts. |
OSEnableInterrupts | bool OSEnableInterrupts(void)
|
Enables interrupts, returning the previous interrupt state. This function should be used with care, since it may have unintended effects if a calling function disabled interrupts. |
OSRestoreInterrupts | bool OSRestoreInterrupts(bool interruptState)
|
Sets the interrupt state to the state specified by the parameter. Typically used at the end of an atomic segment. Returns the previous interrupt state. |
__OSSetInterruptHandler | void (*OSSetInterruptHandler(int interrupt, void (*handler)(void)))(void)
|
Sets the handler for a particular interrupt, returning the old handler. |
__OSGetInterruptHandler | void (*OSGetInterruptHandler(int interrupt))(void)
|
Reads the appropriate handler from a table. |
OSInitMessageQueue | void OSInitMessageQueue(OSMessageQueue *queue, int *buf, int capacity)
|
Initializes the fields of the OSMessageQueue |
OSSendMessage | bool OSSendMessage(OSMessageQueue *queue, int msg, bool waitForSpace)
|
Adds a message to the end of the queue. Returns whether this was successful. |
OSReceiveMessage | bool OSReceiveMessage(OSMessageQueue *queue, int *msg, bool waitForMsg)
|
Removes a message from the front of the queue, returning whether the operation was successful. |
OSJamMessage | bool OSJamMessage(OSMessageQueue *queue, int msg, bool waitForSpace)
|
Adds a message to the front of the queue. Returns whether this was successful. |
OSGetPhysicalMem1Size | int OSGetPhysicalMem1Size(void)
|
Reads the MEM1 size from lomem. |
OSGetPhysicalMem2Size | int OSGetPhysicalMem2Size(void)
|
Reads the MEM2 size from lomem. |
OSGetConsoleSimulatedMem1Size | int OSGetConsoleSimulatedMem1Size(void)
|
Reads the simulated MEM1 size from lomem. |
OSGetConsoleSimulatedMem2Size | int OSGetConsoleSimulatedMem2Size(void)
|
Reads the simulated MEM2 size from lomem. |
OSInitMutex | void OSInitMutex(OSMutex *mutex)
|
Initializes the fields of a mutex |
OSLockMutex | void OSLockMutex(OSMutex *mutex)
|
Locks a mutex, blocking if needed. Supports recursive locking. |
OSUnlockMutex | void OSUnlockMutex(OSMutex *mutex)
|
Unlocks a mutex. Supports recursive locking. |
OSTryLockMutex | bool OSTryLockMutex(OSMutex *mutex)
|
Attempts to lock a mutex, returning whether the operation was successful. |
OSYieldThread | void OSYieldThread(void)
|
Switches to another thread |
OSCreateThread | bool OSCreateThread(OSThread *thread, void (*main)(void *arg), void *arg, void *stackPtr, int stackSize, int priority, bool detached)
|
Creates and starts a thread using the given OSThread. |
OSCancelThread | void OSCancelThread(OSThread *thread)
|
Stops a thread |
OSSleepThread | void OSSleepThread(OSThreadQueue *waitingQueue)
|
Pauses the current thread until OSResumeThread is called with this thread queue |
OSResumeThread | void OSResumeThread(OSThreadQueue *waitingQueue)
|
Resumes all threads in the queue |
OSGetConsoleType
OSInit
calls a function to log platform information, which gets the platform ID from a function called OSGetConsoleType
.
OSGetConsoleType
priorities the masked DVD device code address first, MEM2 size second, and Hollywood size third. If an entry in the table is marked with an X, then any value can work, unless that value matches an explicitly listed console type.
Name | ID (hex) | Hollywood version (hex) | Masked DVD device code address (hex) | MEM2 size | Notes |
---|---|---|---|---|---|
Pre-production board 1 | 00000011 | 1 | 2 3 203 |
64MB | |
Pre-production board 2-1 | 00000012 | 2 | 2 3 203 |
64MB | |
Pre-production board 2-2 | 00000020 | 10 | 2 3 203 |
64MB | |
RVA 1 | 00000100 | X | 300 | X | This did not exist in launch day versions of OSGetConsoleType. |
Retail # | 0XXXXXXX | X | 2 3 203 |
X | Always followed by the ID |
NDEV 2.1 | 10000021 | 11 | 202 | 128MB | This does not exist in the June 23 2006 version of Revolution OS (found in MIOS), but it does exist in the July 27 2006 version (found in NandIOS2 in RVL_DIAG), which still has support for RVL0 apploaders. |
NDEV 2.0 | 10000020 | 10 | 202 | 128MB | |
NDEV 1.2 | 10000012 | 2 | 202 | 128MB | |
NDEV 1.1 | 10000011 | 1 | 202 | 128MB | |
NDEV 1.0 | 10000010 | 0 | 202 | 128MB | |
Revolution Emulator | 10000008 | XXXXXXXX | X | X | |
Emulation platform (#) | 1XXXXXXX | X | 202 | X | Always printed with the console type ID. |
TDEV-based emulation HW# | 2XXXXXXX | X | X | X | Always printed with the console type ID. |
OSDBIntegrator
There is a string saying Installing OSDBIntegrator
. OSDBIntegrator seems to be a debug stub installed at 0x80000060 that branches to the address stored at 0x80000048. It is not known how this code is reached, as it is not jumped to by any retail code.
The code itself seems to be printing DBExceptionDestination
, printing the current OSContext
, and hanging the Broadway.
Exceptions
With the exception of a system call, all exceptions have identical vectors, which loads the appropriate exception-specific handler from the array at 0x80003000, or the default handler if the is no specific handler or the exception is not recoverable. Exception types are given IDs by Revolution OS; these numbers are used when setting an exception handler.
Type | ID | Vector location | Notes |
---|---|---|---|
System Reset | 0 | 80000100 | Hard resets boot from the EXI buffer, and the boot vector placed by IOS boots to 80003400. As a result, only soft resets can be handled here. |
Machine Check | 1 | 80000200 | |
DSI | 2 | 80000300 | |
ISI | 3 | 80000400 | |
External (IRQ) | 4 | 80000500 | |
Alignment | 5 | 80000600 | |
Program | 6 | 80000700 | |
FP unavailable | 7 | 80000800 | |
Decrementer | 8 | 80000900 | |
System call | 9 | 80000C00 | This exception cannot be handled unless __OSInitSystemCall (normally called by OSInit ) does not execute, since the system call vector is overwritten.
|
Trace | 10 | 80000D00 | |
Performance Monitor | 11 | 80000F00 | |
IABR | 12 | 80001300 | |
Reserved | 13 | 80001400 | |
Thermal | 14 | 80001700 | |
Memory interface | 15 | N/A | Memory IRQs are sent to this exception handler. Unlike other exception handlers, this should be NULL to indicate that it should be unhandled (not a pointer to the default handler). |
IRQs
IRQs are processed by handlers in the __OSInterrupt
table; the bit index of the each source is the same as the index in the table; integers built from these bits are used for functions that mask interrupts.
Index | Source |
---|---|
0 | MEM0 |
1 | MEM1 |
2 | MEM2 |
3 | MEM3 |
4 | All MI |
5 | DSP ADINT |
6 | DSP ARINT |
7 | DSP DSPINT |
8 | AI |
9 | EXI EXTINT low (channel 0) |
10 | EXI TCINT (channel 0) |
11 | EXI EXTINT high (channel 0) |
12 | EXI EXTINT low (channel 1) |
13 | EXI TCINT (channel 1) |
14 | EXI EXTINT high (channel 1) |
15 | EXI EXTINT low (channel 2) |
16 | EXI TCINT (channel 2) |
26 | HSP |
25 | DEBUG |
20 | PE FINISH |
19 | PE TOKEN |
24 | VI |
21 | Serial |
22 | DVD |
23 | Reset switch |
18 | CP FIFO |
27 | GP Runtime Error |
Context saving
When a non-syscall exception occurs, the current state is stored in a struct called OSContext
; the struct is 0x2c8 bytes long.
struct OSContext { u32 gprs[0x20]; // r0-r31 u32 cr; // 0x80 u32 lr; // 0x84 u32 ctr; // 0x88 u32 xer; // 0x8c u64 fprs[0x20]; // f0-f31 u64 fpscr; // 0x190 u32 srr0; // 0x198 - saved PC u32 srr1; // 0x19c - saved MSR u16 state; // 0x1a2; last bit means OSSaveFPUContext was called, second last bit means the GPRs were saved by the exception handler u64 gqrs[4]; // 0x1a4 u64 pairedSingles[0x20]; // starting at 0x1c8 }
Most OSContext
instances belong to threads, and are at the beginning of the OSThread struct. However, there are also some standalone OSContext
instances, such as the one used while waiting for an available thread.
FPU
Revolution OS keeps the FPU disabled by default, and enables it when an FPU unavailable exception triggers. When other exceptions trigger, Revolution OS disables the FPU in the previous OSContext
.
Threads
Threads are stored in the OSThread
struct.
struct OSThread { struct OSContext ctx; u16 state; // 0x2c8; 0 = stopped, 1 = inactive, 2 = active, 4 = sleeping, 8 = returned result? u16 detached; // 0x2ca; zero = false, nonzero = true u32 suspend; // seems to be a balancing counter. 0 = active, 1 = suspended u32 priority; // 0x2d0; can range from 0-31 u32 basePriority; // 0x2d4 u32 returnValue; // 0x2d8 struct OSThreadQueue *queue; // 0x2dc struct OSThreadLink linkQueue; // 0x2e0 struct OSThreadQueue queueJoin; // 0x2e8 struct OSMutex *mutex; // 0x2f0; mutex currently waiting for; used for deadlock detection struct OSMutexQueue queueMutex; // 0x2f4 struct OSThreadLink linkActive; // 0x2fc void *stackStart; // 0x304 void *stackEnd; // 0x308 u32 unknown; // 0x30c u32 threadSpecifics[2]; // 0x310 } struct OSThreadQueue { struct OSThread *head; struct OSThread *tail; } struct OSThreadLink { struct OSThread *next; struct OSThread *prev; } struct OSMutex { struct OSThreadQueue waitingQueue; struct OSThread *holder; u32 timesLocked; // used if a mutex is locked multiple times by the same thread struct OSMutex *next; struct OSMutex *prev; } struct OSMutexQueue { struct OSMutex *head; struct OSMutex *tail; } struct OSThreadInfo { // fields are sometimes directly accessed struct OSThread initialThread; struct OSThreadQueue RunQueue[0x20]; struct OSContext idleCtx; }
When rescheduling, threads are taken from the queue corresponding to the smallest priority. Additionally, unless the rescheduling is done by OSYieldThread
, the new priority must be smaller than the current one for a thread switch to occur.
Memory allocation
Memory can be allocated from a heap or the MEM1 arena.
struct OSHeapCell { struct OSHeapCell *prev; struct OSHeapCell *next; u32 size; u8 unknown[0x14]; } struct OSHeapData { u32 size; struct OSHeapCell *free; struct OSHeapCell *allocated; }
Message queues
Message queues are very similar to IOS, but with no process isolation.
struct OSMessageQueue { struct OSThreadQueue waitForSend; struct OSThreadQueue waitForReceive; void *buf; u32 messageCapacity; u32 rotation; u32 messagesEnqueued; }
Shutdown handlers
Shutdown handlers only seem to be called when an error occurs, not on normal shutdown or return to System Menu.
struct OSShutdownFunction { void *func; // 2 params, unknown type u32 priority; // lower priority goes first struct OSShutdownFunction *next; struct OSShutdownFunction *prev; }
Dynamic linking
REL files are loaded through OSLink
. At each relocation address, the instruction is patched with information about the base address depending on the relocation type. Types 1, 3, and 4 are identical and are used for 32-bit data, while type 2 is used for absolute branching instructions, type 5 is used for 16-bit data, type 6 is used for swapped endian, types 7-9 are used for absolute conditional branches, type 10 is used for relative branches, and types 11-13 are used for relative conditional branches. There is also a type 109 (0x6d - 'm') that has an unknown purpose.
__OSModuleInit
writes NULL pointers to the module linked list head and tail, but no code in main binaries seems to write real pointers here; this is presumably done by the modules themselves.