title: Raspberry 5 baremetal Hello World example keywords:raspi5,asm,c,kernel,arm64 # Raspberry 5 baremetal Hello World example ## Intro Here is base example that will allow to print out message to serial console, and debug application. This example is picked up from variouse resources to make easy start into baremetal programming for raspi5, similar route is for all other models. All commands are tested from raspi4, adjust sources for your system. ## Source code ### boot.S ``` .section ".text.boot" .global _start _start: // read cpu id, stop slave cores mrs x1, mpidr_el1 and x1, x1, #3 cbz x1, 2f // cpu id > 0, stop 1: wfe b 1b 2: // cpu id == 0 // set top of stack just before our code (stack grows to a lower address per AAPCS64) ldr x1, =_start mov sp, x1 // clear bss ldr x1, =__bss_start ldr w2, =__bss_size 3: cbz w2, 4f str xzr, [x1], #8 sub w2, w2, #1 cbnz w2, 3b // jump to C code, should not return 4: bl main // for failsafe, halt this core too b 1b ``` ### kernel.c ```c void main() { while (1) { } } ``` ### Makefile ```Makefile TOOLCHAIN=aarch64-linux-gnu- AS = ${TOOLCHAIN}as LD = ${TOOLCHAIN}ld SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) CFLAGS = -Wall -O2 -ffreestanding CC=$(TOOLCHAIN)gcc all: clean kernel8.img boot.o: boot.S ${CC} ${CFLAGS} -c boot.S -o boot.o %.o: %.c ${CC} ${CFLAGS} -c $< -o $@ kernel8.img: boot.o ${OBJS} ${LD} boot.o ${OBJS} -T linker.ld -o kernel8.elf ${TOOLCHAIN}objcopy -O binary kernel8.elf kernel8.img clean: rm -rf *.o *.img *.elf ``` ### linker.ld ```ld SECTIONS { . = 0x80000; /* Kernel load address for AArch64 */ .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } PROVIDE(_data = .); .data : { *(.data .data.* .gnu.linkonce.d*) } .bss (NOLOAD) : { . = ALIGN(16); __bss_start = .; *(.bss .bss.*) *(COMMON) __bss_end = .; } _end = .; /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } } __bss_size = (__bss_end - __bss_start)>>3; ``` ### config.txt ``` arm_64bit=1 kernel=kernel8.img enable_jtag_gpio=1 ``` ### Compile source code ```sh make ``` ### Prepare for a boot Create usual distro of your like. Mount sdcard and backup original configs and original kernel. ```sh cd /mnt/dir cp config.txt config.txt.bkp cp kernel8.img kernel8.img.original ``` Copy kerne8.img and config.txt ```sh cp /project/dir/config.txt /mnt/dir cp /project/dir/kernel8.img /mnt/dir ``` ## Debugging with OpenOCD+GDB The default openocd doesn't configration for raspi5. ### OpenOCD config for bcm2712.cfg If OpenOCD doesn't have the raspi5 config by default here is one that suppose to work. Copy it for example in default openocd script location in: ``` /usr/share/openocd/scripts/target/ ``` ```tcl # SPDX-License-Identifier: GPL-2.0-or-later # The Broadcom BCM2712 used in Raspberry Pi 5 # No documentation was found on Broadcom website # Partial information is available in Raspberry Pi website: # https://www.raspberrypi.com/documentation/computers/processors.html#bcm2712 # v1.0 initial revision - trejan on forums.raspberrypi.com if { [info exists CHIPNAME] } { set _CHIPNAME $CHIPNAME } else { set _CHIPNAME bcm2712 } if { [info exists CHIPCORES] } { set _cores $CHIPCORES } else { set _cores 4 } if { [info exists USE_SMP] } { set _USE_SMP $USE_SMP } else { set _USE_SMP 0 } if { [info exists DAP_TAPID] } { set _DAP_TAPID $DAP_TAPID } else { set _DAP_TAPID 0x4ba00477 } transport select swd swd newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID -irlen 4 adapter speed 4000 dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu # MEM-AP for direct access target create $_CHIPNAME.ap mem_ap -dap $_CHIPNAME.dap -ap-num 0 # these addresses are obtained from the ROM table via 'dap info 0' command set _DBGBASE {0x80010000 0x80110000 0x80210000 0x80310000} set _CTIBASE {0x80020000 0x80120000 0x80220000 0x80320000} set _smp_command "target smp" for { set _core 0 } { $_core < $_cores } { incr _core } { set _CTINAME $_CHIPNAME.cti$_core set _TARGETNAME $_CHIPNAME.cpu$_core cti create $_CTINAME -dap $_CHIPNAME.dap -ap-num 0 -baseaddr [lindex $_CTIBASE $_core] target create $_TARGETNAME aarch64 -dap $_CHIPNAME.dap -ap-num 0 -dbgbase [lindex $_DBGBASE $_core] -cti $_CTINAME set _smp_command "$_smp_command $_TARGETNAME" } if {$_USE_SMP} { eval $_smp_command } # default target is cpu0 targets $_CHIPNAME.cpu0 ``` Connecting pico probe and doint step by step analysis of basic loop ### Running OpenOCD Start openocd server ```sh openocd -f interface/cmsis-dap.cfg -f target/bcm2712.cfg ``` Start gdb or gdb-multiarch ```sh gdb ``` Restart the raspi5 with prepared files on sdcard and run inside gdb shell ```sh tar ext :3333 ``` you should be connected to the openocd jtag session now press Ctrl+C ## Links https://www.raspberrypi.com/documentation/computers/config_txt.html https://wiki.osdev.org/Raspberry_Pi_Bare_Bones https://www.rpi4os.com/part1-bootstrapping/ https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/ https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html https://macoy.me/blog/programming/RaspberryPi5Debugging https://gist.github.com/tnishinaga/219122a5f1e3973668ee78c0fb1c7bf9 https://forums.raspberrypi.com/viewtopic.php?p=2172522#p2172522 https://github.com/DSERIOUSGUY/HobOS https://developer.arm.com/documentation/ka006096/latest/ https://github.com/raspberrypi/firmware https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC6