Chapter 2: Build infrastructure

aarch64-none-elf toolchain

If you’ve done other bare-metal ARM projects, you might be used to the arm-none-eabi toolchain. For this project, we’ll need the arm64 equivalent: aarch64-none-elf. You’ll probably need to fetch this from the ARM toolchain download page and add it to your PATH. I unpacked it into ~/.local/aarch64-none-elf, and added the bin dir to my PATH.

Makefile

Later on, this might get more complicated; for now, a short Makefile will suffice. The code will be laid out as

.
├── build
├── inc
└── src

First, we’ll set up some basic tooling and variables:

## VARIABLES
#  B -      build dir
#  D -      disk (SD) image
#  H -      header file / include dir
#  I -      output image (place this on µSD card)
#  L -      linker script
#  M -      map file
#  P -      path to sources
#  S -      assembler listing
#  T -      toolchain
B ?=                build
D ?=                $(B)/sd.img
H ?=                inc
I ?=                $(B)/kernel8.img
L ?=                pi4.ld
M ?=                $(B)/kernel8.map
P ?=                src
S ?=                $(B)/kernel8.list
T ?=                aarch64-none-elf

PLATFORM :=         pi4
ELF :=              $(B)/kospi64.elf

AS :=               $(T)-as
CC :=               $(T)-gcc
CXX :=              $(T)-g++
LD :=               $(T)-ld
OC :=               $(T)-objcopy
OD :=               $(T)-objdump

The build flags will be fairly standard using compile flags targeting the Cortex A72; we will be providing out own startfile and standard library.

BUILD_FLAGS := -O2 -Wall -Werror -ffreestanding -march=armv8-a+crc  \
               -nostartfiles -nostdinc -nostdlib                    \
               -Wno-unused-command-line-argument -I$(H)
LDFLAGS :=  -nostdlib --no-undefined
CFLAGS :=   $(BUILD_FLAGS)
CXXFLAGS := -std=c++17 $(BUILD_FLAGS)

Next, collect all the source files and collect them into a variable describing the objects that need to be built.

SRCXX :=            $(wildcard $(P)/*.cc)
SRCCC :=            $(wildcard $(P)/*.c)
SRCAS :=            $(wildcard $(P)/*.S)
OBJS :=             $(patsubst $(P)/%.S,$(B)/%.o,$(SRCAS))
OBJS +=             $(patsubst $(P)/%.cc,$(B)/%.o,$(SRCXX))
OBJS +=             $(patsubst $(P)/%.c,$(B)/%.o,$(SRCCC))

Later, we’ll use podman to build and test this with an image I’ve prepared with the development tooling needed to cross-compile.

PODMAN ?=   podman
PIMAGE ?=   git.wntrmute.dev/kyle/armdev:latest

Make’s default target should be the kernel image we’re building.

all:        $(I)

The list target produces an assembly language listing of the kernel.

list:       $(S)

$(S):       $(ELF)
        $(OD) -d $(ELF) > $@

The contents of the ELF will be dumped into a memory dump. All of its symbols and relocation information are discarded.

$(I):       $(ELF)
        $(OC) $(ELF) -O binary $@

The ELF file is the linked output of all the object files; the build should also produce a remapping list. In order to build it, we need a linker map describing how memory on the Raspberry Pi is laid out. The object files are built in a pretty standard way.

$(ELF):     $(OBJS) $(L)
        $(LD) -o $@ $(LDFLAGS) $(OBJS) -Map $(M) -T $(L)

$(B)/%.o: $(P)/%.S $(B)
        $(CC) -o $@ $(BUILD_FLAGS) -c -I $(P) $<

$(B)/%.o: $(P)/%.cc $(B)
        $(CXX) -o $@ -c $(CXXFLAGS) -I $(P) $<

$(B)/%.o: $(P)/%.c $(B)
        $(CC) -o $@ -c $(CFLAGS) -I $(P) $<

$(B):
        mkdir $@

Finally, we have a few utility targets. Right now, the disk image isn’t actually being used for anything — later on, I’d like to get qemu emulation support working. The print- target shows the value of whatever variable is supplied; e.g.

% make print-OBJS
OBJS= build/main.o

The devbox targets use podman to build a development image for the kernel. The devdeps target assumes a Debian-like system.

$(D): $(B)
        dd if=/dev/zero of=$@ bs=128k count=8192
        mkfs.msdos -n boot $@

print-%: ; @echo '$(subst ','\'',$*=$($*))'

clean :
        -rm -rf $(B) $(I) $(S) $(M) $(D)
devbox-build:
        $(PODMAN) build -t $(PIMAGE) -f Containerfile

devbox-run:
        $(PODMAN) run -i -t -v $(PWD):/build $(PIMAGE)

devdeps:
        sudo apt install binutils-arm-none-eabi gcc-arm-none-eabi   \
                libnewlib-arm-none-eabi libstdc++-arm-none-eabi-dev \
                libstdc++-arm-none-eabi-newlib                              \
                libstdc++-arm-none-eabi-picolibc picolibc-arm-none-eabi

.PHONY: all list clean emulate emulate-vga devbox-build devbox-run devdeps

The next step is to supply the bare minimum to build the kernel:

  • a main souce file

  • an assembly-language bootloader

  • a linker script