Graeme Winter

Bare Metal π0

Starting to properly bare metal Raspberry Pi 0 (W) - though as bare metal as you can get with a π0 is not very bare, because the VideoCore does quite a bit of stuff. Bare ARM anyway… copied bootcode.bin and start.elf to root of FAT32 µSD card, then built a program to switch on and off GPIO47 which is the power LED.

Getting VideoCore Boot files

git clone -n --depth=1 --filter=tree:0 git@github.com:raspberrypi/firmware.git
cd firmware/
git sparse-checkout set --no-cone boot
git checkout

These are the general purpose firmware files needed to bring any π system to full awakeness - most of this is actually executing on the VideoCore (bootcode.bin and start.elf 👀) and call kernel.img on ARMv6 based π like the π0. Here call == loads program into memory and executes from _start.

Boot

Define _start which just sets the stack pointer to the start of RAM defined in the linker script:

.globl _start
_start:
    mov sp,#0x8000
    bl main

halt: b halt

Linker script:

MEMORY
{
    RAM : ORIGIN = 0x8000, LENGTH = 128M
}

SECTIONS
{
    .text : { *(.text*) } > RAM
    .data : { *(.data) } > RAM
    .bss : { *(.bss*) } > RAM
}

No .data right now but may do in future, so define where it will sit.

Application: main

Main program:

// registers from §6.1 of data sheet
volatile unsigned int *GPIO = (unsigned int *)0x20200000;
#define GPFSEL4 (0x10 / 4)
#define GPSET1 (0x20 / 4)
#define GPCLR1 (0x2C / 4)

// GPIO47 on bank #1
#define LED (47 - 32)

extern int nop(int);

int main(void) {
  unsigned int gpio_reg;

  gpio_reg = GPIO[GPFSEL4];
  gpio_reg &= ~(7 << 21);
  gpio_reg |= 1 << 21;
  GPIO[GPFSEL4] = gpio_reg;

  while (1) {
    GPIO[GPSET1] = 1 << LED;
    for (int j = 0; j < 0x100000; j++)
      nop(j);
    GPIO[GPCLR1] = 1 << LED;
    for (int j = 0; j < 0x100000; j++)
      nop(j);
  }

  return 0;
}

Includes a nop definition in a different file to avoid optimiser doing its stuff:

int nop(int x) { return 0; }

Makefile

ARMGNU ?= arm-none-eabi

AFLAGS = --warn --fatal-warnings
CFLAGS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding

# bespoke optimisation flags
RPI0 = -O2 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s

all: kernel.img

clean:
	rm -f *.o
	rm -f *.bin
	rm -f *.hex
	rm -f *.elf
	rm -f *.img

boot.o: boot.s
	$(ARMGNU)-as $(AFLAGS) boot.s -o boot.o

main.o: main.c
	$(ARMGNU)-gcc $(CFLAGS) $(RPI0) -c main.c -o main.o

nop.o: nop.c
	$(ARMGNU)-gcc $(CFLAGS) $(RPI0) -c nop.c -o nop.o

main.elf: boot.ld boot.o main.o nop.o
	$(ARMGNU)-ld boot.o main.o nop.o -T boot.ld -o main.elf

kernel.img: main.elf
	$(ARMGNU)-objcopy main.elf -O binary kernel.img

It’ll do.