vm-larix‎ > ‎

Baremetal "Hello World"

A "Hello World" Stage 2 Boot Loader

This page intends to provide sample code for bare metal ARM programming.

Now that we are able to boot up our beagleboard with the x-loader and u-boot.bin, we attempt to replace the u-boot.bin with our own stage 2 boot loader. The x-loader loads a file called u-boot.bin from the FAT file system on the SD card. So, our stage 2 boot loader must also be named so and must replace the original u-boot.bin on the SD card.

Initially, to test if control is passed over to our u-boot.bin, we create a simple program that just prints "Hello World from Bharath!". If this works, we may proceed to write a more serious boot loader that loads the kernel image and RAMdisk. It must be noted that the simple program we write is not running under any operating system. It is a bare metal program. So -
  • we may not exit it - the program must enter into an infinite loop at the end of the execution.
  • we may not use any standard libraries - the standard libraries have not been defined yet by the non-existent OS
  • we may not call standard devices such as /dev/stty or device files such as stdout, stdin or stderr.
All the code must be from scratch. However, we may still use C and do not have to resort to any assembly for this exercise.

#define UART3_BASE 0x49020000
#define thr rbr
#define iir fcr
#define dll rbr
#define dlm ier

#define FCR_FIFO_EN     0x01 /* Fifo enable */
#define FCR_RXSR        0x02 /* Receiver soft reset */
#define FCR_TXSR        0x04 /* Transmitter soft reset */

#define MCR_DTR         0x01
#define MCR_RTS         0x02
#define MCR_DMA_EN      0x04
#define MCR_TX_DFR      0x08

#define LCR_WLS_MSK 0x03 /* character length slect mask */
#define LCR_WLS_5 0x00 /* 5 bit character length */
#define LCR_WLS_6 0x01 /* 6 bit character length */
#define LCR_WLS_7 0x02 /* 7 bit character length */
#define LCR_WLS_8 0x03 /* 8 bit character length */
#define LCR_STB 0x04 /* Number of stop Bits, off = 1, on = 1.5 or 2) */
#define LCR_PEN 0x08 /* Parity eneble */
#define LCR_EPS 0x10 /* Even Parity Select */
#define LCR_STKP 0x20 /* Stick Parity */
#define LCR_SBRK 0x40 /* Set Break */
#define LCR_BKSE 0x80 /* Bank select enable */

#define LSR_DR 0x01 /* Data ready */
#define LSR_OE 0x02 /* Overrun */
#define LSR_PE 0x04 /* Parity error */
#define LSR_FE 0x08 /* Framing error */
#define LSR_BI 0x10 /* Break */
#define LSR_THRE 0x20 /* Xmit holding register empty */
#define LSR_TEMT 0x40 /* Xmitter empty */
#define LSR_ERR 0x80 /* Error */

/* useful defaults for LCR */
#define LCR_8N1 0x03

#define LCRVAL LCR_8N1 /* 8 data, 1 stop, no parity */
#define MCRVAL (MCR_DTR | MCR_RTS) /* RTS/DTR */
#define FCRVAL (FCR_FIFO_EN | FCR_RXSR | FCR_TXSR) /* Clear & enable FIFOs */

char *message="\r\nHello World from Bharath!\r\n\0";

struct NS16550 {
unsigned char rbr; /* 0 */
int pad1:24;
unsigned char ier; /* 1 */
int pad2:24;
unsigned char fcr; /* 2 */
int pad3:24;
unsigned char lcr; /* 3 */
int pad4:24;
unsigned char mcr; /* 4 */
int pad5:24;
unsigned char lsr; /* 5 */
int pad6:24;
unsigned char msr; /* 6 */
int pad7:24;
unsigned char scr; /* 7 */
int pad8:24;
unsigned char mdr1; /* mode select reset TL16C750*/
} __attribute__ ((packed));

typedef volatile struct NS16550 *NS16550_t;

start_armboot (void)
int baud_divisor=26;
volatile struct NS16550 *com_port=(NS16550_t)UART3_BASE;
com_port->mdr1 = 0x7;   /* mode select reset TL16C750*/
com_port->lcr = LCR_BKSE | LCRVAL;
com_port->dll = baud_divisor & 0xff;
com_port->dlm = (baud_divisor >> 8) & 0xff;
com_port->lcr = LCRVAL;
com_port->mcr = MCRVAL;
com_port->fcr = FCRVAL;
com_port->mdr1 = 0; /* select uart mode */

while (*message)
while ((com_port->lsr & LSR_THRE) == 0);
Program main.c

Most of the macros in the above program is borrowed from various files of the x-loader source code. some of which are not even used in the program. The beagleboard uses the UART3 as the console UART. The UART on the beagleboard is compatible with the 16754 UART which is an upgraded version of the 16550 UART with 64 bytes of input FIFO and 64 bytes of output FIFO.

The above code is compiled as 
arm-none-eabi-gcc -S main.c -o u-boot.s
arm-none-eabi-as -mlittle-endian -o u-boot.o u-boot.s
arm-none-eabi-ld -Ttext 0x80008000 -entry start_armboot u-boot.o -o u-boot
arm-none-eabi-objcopy --gap-fil=0xff -O binary u-boot u-boot.bin

The first line compiles the C code to assembly. The second line assembles the assembly file to object code for a Little Endian machine. The third line links the object code into an ELF file. In this case there is just one file. The fourth line converts the ELF file into a binary file filling the empty memory spaces with 0xFF. The binary file is an exact image of the code as it should execute in the memory space. The binary file is not relocatable. Since it was linked to have it's .text section at 0x80008000, it must be loaded at that location to execute. This location is hard coded in the x-loader source. The x-loader expects the u-boot binary (or any other stage II) to be linked to run off from 0x80008000.

The compilation may also be done using the arm-elf- tool chain as the ELF shall finally be converted to a binary file.

The resulting u-boot.bin may be copied onto the SD card or used in the qemu emulator image.

Screen shot of the stage 2 boot loader on the qemu emulator

Screenshot of the stage 2 boot loader running on the beagleboard hardware. The output is captured from the serial port.