A simple “bare-metal” program (ARM)

Caution: Here be dragons!
For those who feel like writing C code with nice and fancy algorithm, this is not for you. This is for them who wants to dig deeper at the core to see how things run and work.
A “bare-metal” program is simplest of all which does not need special services from underlying OS. It is capable enough to run on its own.

Without much ado, lets get it on, shall we?

This tutorial can be used on both real hardware and an emulator. I have used QEMU for this purpose.

Architecture to emulate: ARM926EJ-S
This is well supported by QEMU and has four UART
serial ports. The first serial port (UART0) works
as a terminal when we will use '-nographic' or
'-serial stdio' option in QMEU

Goal: This program will run inside QEMU emulator and will pass few characters to the UART0 which will, then be redirected by QEMU to the standard console.
Note: On real board (ARM926EJ-S) you have to connect to serial port (Minicom).

Required:
QEMU, ARM toolchain, little patience and willingness to learn.

Install QEMU on ubuntu:
sudo apt-get install qemu qemu-kvm-extras

Get ARM toolchain from:
ARM toolchain download link

Note: Do not download the installer (it sucks!)
Instead, get the .tar.gz archieve and unpack it your working directory
and set your PATH accordingly.
If you’re using login shell, modify the .bash_profile,
else modify .bashrc.
————————————————————————–

No interrupt handling and other fancy stuffs.

The code is well-explained and do contact me if you need more information.

The command to run the final binary in QEMU is:
qemu-system-arm -M versatilepb -m 128M -nographic -kernel output.bin

"-M versatilepb" specifies the machine type: Here we are using ARM9 versatile board.
"-nographic" mode tells to redirect UART0 messages to console.

Note: Without the option "-m 128M" QEMU crashes badly. A bug has been already filed regarding this.

Here we go!!!

File: simple-startup.s


/* ===========================================
Start up routines for bare-metal program
===========================================
*/

.global _Reset
_Reset:
B Reset_Handler /* Reset handler */
B Undefined /* Undefined mode handler */
B SWI /* Software interrupt */
B Prfetch_Abort /* Prefetch abort handler */
B Data_Abort /* Data abort handler */
B IRQ_Handler /* IRQ handler */
B FIQ_Handler /* FIQ handler */

Reset_Handler:
LDR sp, =stack_top /*set up env for C function */
BL simple_init
B .
Undefined: /* do nothing */
B .
SWI: /* do nothing */
B .
Prfetch_Abort: /* do nothing */
B .
Data_Abort: /* do nothing */
B .
IRQ_Handler: /* do nothing */
B .
FIQ_Handler: /* do nothing */
B .

File: simple-init.c


/*******************************************/
/* Simple Bare metal program init */
/*******************************************/

/* Note: QEMU model of PL011 serial port ignores the transmit
FIFO capabilities. When writing on a real SOC, the
"Transmit FIFO Full" flag must be checked in UARTFR register
before writing on the UART register*/

volatile unsigned int* const UART0 = (unsigned int*)0x0101F1000;

static void uart_print(const char *s)
{
while(*s != '') {
*UART0 = (unsigned int)(*s); /* send to UART */
s++;
}
}

/* Main entry point */
void simple_init()
{
uart_print("Welcome to Simple bare-metal program\n");
uart_print("If you're running in QEMU, press Ctrl+a\n");
uart_print("and then x to stop me...\n");
}

File: simple-linker.ld


/* Simple Bare metal program - linker script
*
* Note: This is written for QEMU
* QEMU loads the kernel address at 0x00010000
* The emulator starts at 0x00000000 and peripheral interrupts
* are disabled at startup.
*
* The code must be compiled and linked to be placed at
* 0x00010000
* IRQ handling can be ignored.
*
*/

ENTRY(_Reset)
SECTIONS
{
. = 0x10000;
.startup . : { simple-startup.o(.text) }
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
. = . + 0x1000; /* move by 4kbyte (our stack memory) */
stack_top = .;
}

Build commands:


arm-none-eabi-as -mcpu=arm926ej-s -g simple-startup.s -o simple-startup.o
arm-none-eabi-gcc -c -mcpu=arm926ej-s -g simple-init.c -o simple-init.o
arm-none-eabi-ld -T simple-linker.ld simple-init.o simple-startup.o -o output.elf
arm-none-eabi-objcopy -O binary output.elf output.bin

And here is the output:

sarbojit@Phrozen:~/timepass/assembly-fun$ qemu-system-arm -M versatilepb -m 128M -nographic -kernel output.bin
Welcome to Simple bare-metal program
If you’re running in QEMU, press Ctrl+a
and then x to stop me…
QEMU: Terminated
sarbojit@Phrozen:~/timepass/assembly-fun$

Inspired by:
http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/

Advertisements

7 Responses to “A simple “bare-metal” program (ARM)”

  1. Patch simple_init.c:15
    while ( *s != ”) {

    Works great! Definitely need to expand on this one and make something useful 😀

  2. Thanks for the post! FYI, I had to modify simple_init.c to this…

    while (*s != ”) {

    How did you know that QEMU would load the kernel binary to offset 0x10000 (linker)? I can’t find any documentation that discusses this in QEMU. It seems strange that QEMU would do this since the processor will boot from 0x0 (not 0x10000) in a real system right? This means you would have to re-build the code with a different linker file to run it on a real hardware platform right?

    • Hello Shane,

      The QEMU emulator is written especially to emulate Linux guest systems.
      For this reason its startup procedure is implemented specifically:
      the -kernel option loads a binary file (usually a Linux kernel) inside the system memory starting at address 0x00010000.
      The emulator starts the execution at address 0x00000000, where few instructions (already in place) are used to jump at the beginning of the kernel image. The interrupt table of ARM cores, usually placed at address 0x00000000, is not present, and the peripheral interrupts are disabled at startup, as needed to boot a Linux kernel. Knowing this, to implement a working emulation consider the following:
      The software must be compiled and linked to be placed at 0x00010000

      Yes, for the real platform, we need to re-adjust the linker to 0x0.

      Let me know if you need more information.

  3. There are a lot of testimonials about Meladerm available that often
    tend to be doing not have in their details concerning the item.

  4. sagar Says:

    I am trying to compile simple-init.c but its giving an error,
    gcc: error trying to exec ‘cc1’: execvp: No such file or directory ,
    help plz.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: