├── .gitignore ├── README.md ├── demo.gif ├── ldscript ├── main.c ├── make.sh └── start.S /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.elf 3 | *.img 4 | *.o 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Bare Metal Blinker 2 | 3 | Run one command, get a bare metal hello world blinker image that blinks the OK LED. Tested on Ubuntu 16.04 host, Raspberry Pi 2. Usage: 4 | 5 | 1. Insert SD card on host 6 | 7 | 1. Make the image: 8 | 9 | ./make.sh /dev/mmcblk0 p1 10 | 11 | Where: 12 | 13 | - `/dev/mmcblk0` is the device of the SD card 14 | - `p1` is the first partition of the device (`/dev/mmcblk0p1`) 15 | 16 | 1. Inset SD card on PI 17 | 18 | 1. Turn power off and on 19 | 20 | ![demo.gif](demo.gif) 21 | 22 | There are many comprehensive bare metal resources out there, but it was hard to get the first example working, because you have to download some random blobs and put them together. 23 | 24 | Making the first hello world easy is the only goal of this tutorial. The blinker code was adapted from: which was the most comprehensive Raspberry Pi bare metal resource available at the time. 25 | 26 | Also explained at: 27 | 28 | - 29 | - 30 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cirosantilli/raspberry-pi-bare-metal-blinker/35a0b2e8a2ea79c796f7de04844086a6248c3682/demo.gif -------------------------------------------------------------------------------- /ldscript: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* The firmware loads our raw assembly into memory 4 | * at address 0x8000 and then jumps to it. 5 | * 6 | * We must tell that to the linker so that it can do relocation correctly. 7 | */ 8 | ram : ORIGIN = 0x8000, LENGTH = 0x10000 9 | } 10 | 11 | SECTIONS 12 | { 13 | /* .text must be the first thing inside the ram at 0x8000, 14 | * as that is the first thing that is executed. 15 | * 16 | * We must give start.o to the linker on the command line before main.o 17 | * to ensure that _start is the first thing in the output. 18 | */ 19 | .text : { *(.text*) } > ram 20 | .bss : { *(.bss*) } > ram 21 | } 22 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* This is bad. Anything remotely serious should use timers 4 | * provided by the board. But this makes the code simpler. */ 5 | #define BUSY_WAIT __asm__ __volatile__("") 6 | #define BUSY_WAIT_N 0x100000 7 | 8 | int main( void ) { 9 | uint32_t i; 10 | /* At the low level, everything is done by writing to magic memory addresses. 11 | * 12 | * The device tree files (dtb / dts), which are provided by hardware vendors, 13 | * tell the Linux kernel about those magic values. 14 | */ 15 | volatile uint32_t * const GPFSEL4 = (uint32_t *)0x3F200010; 16 | volatile uint32_t * const GPFSEL3 = (uint32_t *)0x3F20000C; 17 | volatile uint32_t * const GPSET1 = (uint32_t *)0x3F200020; 18 | volatile uint32_t * const GPCLR1 = (uint32_t *)0x3F20002C; 19 | 20 | *GPFSEL4 = (*GPFSEL4 & ~(7 << 21)) | (1 << 21); 21 | *GPFSEL3 = (*GPFSEL3 & ~(7 << 15)) | (1 << 15); 22 | while (1) { 23 | *GPSET1 = 1 << (47 - 32); 24 | *GPCLR1 = 1 << (35 - 32); 25 | for (i = 0; i < BUSY_WAIT_N; ++i) { BUSY_WAIT; } 26 | *GPCLR1 = 1 << (47 - 32); 27 | *GPSET1 = 1 << (35 - 32); 28 | for (i = 0; i < BUSY_WAIT_N; ++i) { BUSY_WAIT; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | dev="${1:-/dev/mmcblk0}" 6 | part="${2:-p1}" 7 | part_dev="${dev}${part}" 8 | mnt='/mnt/rpi' 9 | 10 | sudo apt-get install binutils-arm-none-eabi gcc-arm-none-eabi 11 | 12 | # Generate kernel7.img, which is the image with our code. 13 | arm-none-eabi-as start.S -o start.o 14 | # -nostdlib -nostartfiles -ffreestanding tell GCC not to use the C standard library. 15 | arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c main.c -o main.o 16 | arm-none-eabi-ld start.o main.o -T ldscript -o main.elf 17 | # Get the raw assembly out of the generated elf file. 18 | arm-none-eabi-objcopy main.elf -O binary kernel7.img 19 | 20 | # Get the firmware. Those are just magic blobs, likely compiled 21 | # from some Broadcom proprietary C code which we cannot access. 22 | wget -O bootcode.bin https://github.com/raspberrypi/firmware/blob/597c662a613df1144a6bc43e5f4505d83bd748ca/boot/bootcode.bin?raw=true 23 | wget -O start.elf https://github.com/raspberrypi/firmware/blob/597c662a613df1144a6bc43e5f4505d83bd748ca/boot/start.elf?raw=true 24 | 25 | # Prepare the filesystem. 26 | sudo umount "$part_dev" || true 27 | # Create a partition table with a single partition. 28 | # https://superuser.com/questions/332252/creating-and-formating-a-partition-using-a-bash-script/1132834#1132834 29 | echo 'start=2048, type=c' | sudo sfdisk "$dev" 30 | # Create a filesystem in the partition. 31 | sudo mkfs.vfat "$part_dev" 32 | sudo mkdir -p "$mnt" 33 | sudo mount "${part_dev}" "$mnt" 34 | sudo cp kernel7.img bootcode.bin start.elf "$mnt" 35 | 36 | # Cleanup. 37 | sync 38 | sudo umount "$mnt" 39 | -------------------------------------------------------------------------------- /start.S: -------------------------------------------------------------------------------- 1 | /* _start is the default linker entry point symbol name. 2 | * If not present, the linker will complain when generating the elf file. 3 | * This could be configured from the linker script. 4 | */ 5 | .global _start 6 | _start: 7 | /* Set stack at 0x8000, just before where the code will be placed. 8 | * This prevents conflicts, since the stack grows down. 9 | * 10 | * If we could move this to the C code, possibly as inline assembly, 11 | * and get rid of this file, that would be great. 12 | * 13 | * But I haven't found a way to get rid of function prologues on GCC, 14 | * which would set the sack on the function prologue before we can touch it. 15 | */ 16 | mov sp, #0x8000 17 | bl main 18 | /* Go into an infinite loop in case main returns. */ 19 | hang: 20 | b hang 21 | --------------------------------------------------------------------------------