GBA Peculiarities

As mentioned above, the Linux kernel has to live somewhat in the shadow of the GBA BIOS. The GBA was not designed to run an operating system, and the BIOS therefore doesn't allow Linux to handle low level exceptions directly. Furthermore, the start-up code expects to find certain codes and checksums, as well as a bitmap of the Nintendo™ logo which is displayed in the animation at power-on.

Furthermore, the processor doesn't seem to comply fully to the ARM architecture standard. Normally, the processor ought to set the internal IRQ disable flag in its program status register on an SWI exception. Unfortunately, it is required to have interrupts disabled until the SWI handler was able to save the status of the user program.

The following sections will explain the work-arounds which allow the kernel to provide all the necessary operating system functionalities to a conventional Linux application. The only restriction (apart from the limited hardware resources, of course) is that the application has to be linked against (a slightly modified version of) the uC-libc library, a derivative of the GNU C Standard Library for use with MMU-less systems.

The GBA Linux kernel is currently based on the 2.0 version of the official kernel release. The main reason for this is that this version is smaller than newer kernels. The source tree for the 2.0 version of the linux kernel are placed in the linux-2.0.x subdirectory of the uClinux distribution. If not specified otherwise pathnames will always be relative to this directory.

The ROM Header

This concerns the boot code of the kernel. Valid ROM data requires a special header. Otherwise, the Game Boy BIOS will simply refuse to run the code at all. We write the appropriate data at the head of our kernel code segment, which will be placed at the beginning of the GBA ROM file.

The corresponding assembler code from arch/armnommu/kernel/head-arm-gba.S looks like this:


start:
_start:
	b ram_entry    @TC!IB! jump to RAM entry point

	/* GBA has header info at start of ROM */
	.long 0x51aeff24,0x21a29a69,0x0a82843d
	.long 0xad09e484,0x988b2411,0x217f81c0,0x19be52a3
	.long 0x20ce0993,0x4a4a4610,0xec3127f8,0x33e8c758
	.long 0xbfcee382,0x94dff485,0xc1094bce,0xc08a5694
	.long 0xfca77213,0x734d849f,0x619acaa3,0x27a39758
	.long 0x769803fc,0x61c71d23,0x56ae0403,0x008438bf
	.long 0xfd0ea740,0x03fe52ff,0xf130956f,0x85c0fb97
	.long 0x2580d660,0x03be63a9,0xe2384e01,0xff34a2f9
	.long 0x44033ebb,0xcb900078,0x943a1188,0x637cc065
	.long 0xaf3cf087,0x8be425d6,0x72ac0a38,0x07f8d421

	/* TC!IB!
	* Name: currently 'uClinux-gba ' (max 12 chars) */
	.long 0x696C4375,0x2D78756E,0x20616267,0x20202020
	.long 0x00960000,0x00000000,0x00000000,0x00000000

	/* HSSE: RAM entry point (0xC0/4-2=0x2E) */
ram_entry:
	b _entry
	.long 0x0,0x0,0x0
	.long 0x0,0x0,0x0,0x0

	@ Joybus entry point

/****************************************************************************/

/*
 *      This is where it all starts.
 */
reset:
_entry:

			

The code first jumps over the ROM header to the RAM entry point (this branch is expected by some programs validating a ROM file) and then jumps to the actual kernel entry point (actually the same location as the GBA Joybus entry point), which consequently copies the kernel data segment to RAM.

Most of the data in between is consumed by the Nintendo logo bitmap. This data has to be there because this data is checked by the GBA BIOS and the program in the ROM will not be started if it detects a mismatch.

Apart from the Nintendo logo we only specify a nice name for our "game" in the appropriate header area. Further details about the purpose of other data in the header can be found in the gbatek.htm.

BIOS Interrupt Handling

The interrupt handler can be specified by writing its address to the end of the in-chip WRAM, at address 0x03fffffc. Before the BIOS jumps to this address it saves registers r0-r3,r12 and r14 to the interrupt stack (full decrement starting from address 0x03ffffa0). This is subsequently undone by our modification to the kernel interrupt handler. For further information you can read the code at label vector_IRQ in arch/armnommu/kernel/entry-armv.S.

The address of the interrupt handler (vector_IRQ) is written to the WRAM address in arch/armnommu/kernel/head-arm-gba.S.

BIOS System Call Handling

The swi 0xNN0000 ARM instruction triggers a software interrupt, where NN identifies the interrupt number. To the operating system world software interrupts are more commonly known as system calls.

Unfortunately for us the GBA BIOS specififes its own set of software interrupts, which can be used to execute various arithmetic operations and other special functions. But the Linux kernel needs exactly these occupied software interrupts to implement its own system calls. To make things worse the GBA does not provied any way to define one's own software interrupt handler. Thus a work-around has to be found.

The BIOS does not define all 256 possible SWIs, and when out-of-range SWIs are called the GBA will blindly jump to the address read from the random data behind the SWI vector table in the BIOS ROM. Even though the data is arbitrary, the resulting address for each unused SWI will always be the same.

For the SWI 0xd4 this address is 0x02002040, which lies near the start of the on-board WRAM. There our trap_init function in arch/armnommu/kernel/entry-armv.S writes some code that will jump to the software interrupt kernel handler vector_SWI.

This is nice, but a single system call is not quite enough. Hence we have to change the way user programs trigger syscalls. Each swi 0xNN0000 instruction - where NN is the syscall number - has to be preceded by a swi 0xd40000 intruction. This will ultimately call vector_SWI, which increments the link register, consequently placing its address past the instruction which was originally intended to trigger the system call, but which will never actually execute in our case. The SWI handler can then extract the syscall number from the "calling" instruction in the usual way.

The downside of this work-around is that the user mode libraries (at least the ARM dependent parts) have to be modified accordingly.

Interrupted System Calls

The Game Boy version of the ARM7TDMI processor does not conform to the ARM architecture standard in that it does not care to disable interrupts by setting the interrupt disable flag in the current program status register (CPSR) on SWI exceptions. This is a problem because the SWI handler must not be interrupted until it was able to save the SPSR_svc (saved program status register in supervisor mode) register. This register contains the current program status register of the process which issued the system call and must be used to restore the status register on return from the system call.

Linux executes interrupt handlers in supervisor mode by switching from interrupt mode in the interrupt entry dispatcher (vector_IRQ). On return the SPSR_svc register is used to restore the CPSR of the interrupted program. Therefore, interrupts have to be disabled on system call entry and exit.

This is, again, achieved by modifying the way user mode libraries issue system calls. Before swi 0xd40000 is executed, the Interrupt Master Enable (IME) flag in the GBA's interrupt control register is cleared to globally disable interrupts.

Correspondingly, the system call handler sets the IME flag instead of clearing the processor's internal interrupt disable flag.

As above, the disadvantage is that we have to modify user mode code. A typical system call now looks like this (from include/asm-armnommu/unistd.h):


#undef __syscall
#define __syscall(name)				\
	"stmfd	sp!, {r11, r12}"	"\n\t"	\
	"ldr	r11, =0x04000208"	"\n\t"	\
	"mov	r12, #0"		"\n\t"	\
	"ldmfd	sp!, {r11, r12}"	"\n\t"	\
	"str	r12, [r11]"		"\n\t"	\
	"swi	0xd40000"		"\n\t"	\
	"swi	" __sys1(__NR_##name)	"\n\t"