Hello World

This is probably one of the more complicated examples, because it introduces a few new ideas. You will need a recent version of Nasm to compile this, because I just can't get on with gcc syntax. I'm really sorry about that.

For a start, we are going to need some assembler, because to run C programs you need a stack. The stack position used by the bootloader may not be appropriate for your needs. Don't worry - this is one of the simpler bits of assembler :-).

File "head.S"

ORG 0x400000 ; We're going to be loaded here

BITS 32 ; 32-bit code

ALIGN 8

main:

cli ; Avoid any interrupts - Good practice.

cld

mov al, 0x80 ; disable NMI

out 0x70, al

mov esp, 0xf00 ; set the stack - useful for C programs :)

jmp 0x400100 ; jump to the start of C code

TIMES 0x100-($-$$) DB 0xff ; pad up to 0x100

That's about all we need. For the moment Biffboot doesn't enable interrupts, but you've no idea if I won't mess that up in future versions of the bootloader, so no need to trust me on that one. I've setup a tiny stack of 4k. Move it elsewhere if you like.

We are going to arrange things so that the C code starts at 0x400100. That's throwing away almost 256 bytes, but we might want to expand this start-up assembler later, so we pad it up to there.

Unless you want to write the rest of your app in assembler you need a main(). I'm going to cheat a bit here, by putting the main() at the start of the file, its body appears at the start of the binary, so you can jump there in assembler and get it executed (we don't need any parameters to be passed to it). Edit: This should be done with a linker script and a symbol, but I hadn't learnt about them when I wrote this.

file "main.c":

#include <sys/io.h>

void print(char* p);

int main()

{

print("Hello world\r\n");

while (1); // no point to return from here, it's undefined

return 0;

}

void put_char(char p)

{

while ((inb(0x3f8+5) & (1<<5))==0);

outb(p, 0x3f8);

}

void print(char* p)

{

while (*p)

{

put_char(*p);

p++;

}

}

My program does nothing more than echo a few characters to the serial port. We can assume the bootloader has set the speed at 115200 baud. For an excellent introduction to PC serial ports, and an explanation of why "0x3f8+5" is significant, check out http://www.beyondlogic.org/serial/serial.htm. The great thing about writing programs like this is that many useful functions (like outb/inb) are implemented as inlines in standard Linux header files, and since it's x86 architecture this stuff will just work. We don't have to configure and link in C libraries to do something useful.

The most complicated part in all this is the Makefile because there are several steps to putting all the pieces together:

file "Makefile":

all: firmware.bin

firmware.bin: head.bin main.bin

./merge.py

head.bin : head.S

nasm -o head.bin head.S

CFLAGS = -Wall

objects = main.o

$(objects): %.o: %.c

$(CC) -Wall -c $(CFLAGS) $< -o $@

main.elf: $(objects)

ld -o main.elf -Ttext 0x400100 -e main $(objects)

main.bin: main.elf

objcopy -R .note -R .comment -S -O binary main.elf main.bin

clean:

rm -f main.bin firmware.bin head.bin *.elf *.o *~

The most important part is the -Ttext argument. This has to correspond to the end of the assembler section.

Finally, I have a script to join the two pieces into the file that you upload to Bifferboard (firmware.bin):

File "merge.py"

#!/usr/bin/env python

head = file("head.bin").read()

main = file("main.bin").read()

fw = head + main

file("firmware.bin","wb").write(fw)

If you would like to try this example under Qemu, please follow the Qemu instructions. You will need to interrupt the boot process by pressing <ESC> and then changing the boot type to 'flat binary', or else change the qemu source in en29lv640.c to permanently set that.