Bootloader

From NaWiki

Jump to: navigation, search

Contents

Overview

DSerial Flash Memory Map

DSerial bootloader is the program that is the first to run once the C8051F320 microcontroller is powered. It responds to commands from DS on the SPI port and allows to boot DSerial firmware amongst other things.

Description

The purpose of DSerial bootloader is to:

  • Boot DSerial firmware
  • Write firmware to internal flash via SPI (other ports may be supported by the bootloader in the future)
  • Read internal flash
  • Send and receive UART data without booting into firmware (baud rate fixed at 115200 bps)

The following sections will describe how the bootloader functions.

Files

Go to downloads to get the bootloader source code.

Detecting DSerial

Check whether DSerial is inserted using a SPI Flash compatible command:

  1. DS sends the byte 0x9F (RDID)
  2. DSerial responds with 0x01 0xAB


This is command is non-destructive to DS game cards. See also DSerial protocol.

Interrupt Vector Table (Currently Used Method)

Interrupt vector table on the C8051F320 is always at offset 0 in FLASH. Unfortunately, we cannot re-target it from bootloader interrupt vector table into the program interrupt vector table with a magic register, since no such register exists in the architecture. Instead, we have to re-target each interrupt separately.

The User Bit in PSW register (also called F1) is used to select bootloader table or firmware table.

// Code in bootloader, interrupt.h
 
/* UART0 interrupt */
void redirS0() __interrupt (4) _naked {
	_asm
				push psw
				jnb  psw.1, 00001$	; if user bit not set, jump to firmware irq
		
				pop  psw
				lcall _uartInterrupt	; otherwise jump to bootloader irq
				reti
		
		00001$:
				pop  psw
				ljmp #FIRMWARE_OFFSET   IRQ_S0	; firmware irq
	_endasm;
}
 
// repeat for the other irqs

This code jumps to bootloader interrupt if PSW.1 (also called F1) is set to 1, otherwise it jumps to firmware.

// Code in bootloader
 
void main() {
	// ...
	F1 = 1;		// relocate interrupts to bootloader
	EA = 1; 	// global interrupt enable
	// ...
}
// Code in firmware
 
void main() {
	// ...
	F1 = 0;		// relocate interrupts to FIRMWARE_OFFSET (0x0800)
	EA = 1; 	// global interrupt enable
	// ...
}

Interrupt Vector Table (Not Used Method)

The following code shows another way that could have been used. Its advantage is that the interrupt vector can be relocated to any address dynamically. The disadvantage is that there's bigger overhead.

// Code in bootloader
 
// Redirects UART IRQ either into our (bootloader's) handler or into firmware handler.
void R_uartInterrupt() __interrupt (4) _naked {
	_asm
				mov a, _IrqVector	; compare IrqVector to 0
				jnz 00001$
				mov a, (_IrqVector 1)
				jz 00002$
		00001$:					; if not 0, then we need to jump to it
				mov dpl, _IrqVector
				mov dph, (_IrqVector 1)
				mov a, #0x23		; this is the offset for uart irq
				jmp @a dptr		; call the interrupt handler in firmware
		00002$:					; if it's 0, then we'll handle the irq
				lcall _uartInterrupt	; call our own interrupt handler
				reti
	_endasm;
}
 
// repeat for the other irqs

IrqVector is a global variable that is set to the location of interrupt vector before enabling interrupts. The bootloader should set IrqVector to 0 while the program should set it to it's location.

// Code in firmware
 
__data __at (0x7e) unsigned int IrqVector;
 
void main() {
	// ...
	IrqVector = 0x0800;				// we're relocated to 0x0800
	EA = 1; 					// global interrupt enable
	// ...
}
Personal tools