├── .gitignore ├── firmware ├── fixup.dat ├── start.elf ├── bootcode.bin └── LICENCE.broadcom ├── README.md ├── LICENSE.txt ├── simplest.ld └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | kernel7.img 2 | zig-cache/ 3 | zig-out/ 4 | -------------------------------------------------------------------------------- /firmware/fixup.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmbarros/pi3-zig-simplest-bare-metal/HEAD/firmware/fixup.dat -------------------------------------------------------------------------------- /firmware/start.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmbarros/pi3-zig-simplest-bare-metal/HEAD/firmware/start.elf -------------------------------------------------------------------------------- /firmware/bootcode.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmbarros/pi3-zig-simplest-bare-metal/HEAD/firmware/bootcode.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplest Raspberry Pi 3 bare metal program in Zig 2 | 3 | Not literally *the* simplest possible bare metal [Pi 4 | 3](https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/) program in 5 | [Zig](https://ziglang.org/), but a very simple one, and pretty well-documented. 6 | 7 | This program will alternate the GPIO pin 16 between 0 and 1, which is good for 8 | blinking an LED. High-tech stuff here, uh?! 9 | 10 | I have written a blog post, [From Bare Docs to Bare 11 | Metal](https://stackedboxes.org/2021/12/30/from-bare-docs-to-bare-metal/), 12 | explaining some of the fundamentals of how to program the Raspberry Pi GPIO. 13 | 14 | ## How to run 15 | 16 | Build with `zig build`, copy the resulting `zig-out/bin/kernel7.img` file to an 17 | SD card, along with all files under `firmware`. 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 Leandro Motta Barros 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /firmware/LICENCE.broadcom: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Broadcom Corporation. 2 | Copyright (c) 2015, Raspberry Pi (Trading) Ltd 3 | All rights reserved. 4 | 5 | Redistribution. Redistribution and use in binary form, without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * This software may only be used for the purposes of developing for, 10 | running or using a Raspberry Pi device, or authorised derivative 11 | device manufactured via the element14 Raspberry Pi Customization Service 12 | * Redistributions must reproduce the above copyright notice and the 13 | following disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | * Neither the name of Broadcom Corporation nor the names of its suppliers 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 20 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 21 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 22 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 26 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 28 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 29 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 30 | DAMAGE. 31 | -------------------------------------------------------------------------------- /simplest.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Bare metal programming a Pi 3 in Zig. 3 | * By Leandro Motta Barros 4 | * Licensed under the MIT license. See LICENSE.txt. 5 | */ 6 | 7 | /* 8 | * The important thing I do here is defining the layout of the binary -- in 9 | * other words, what will appear in the final executable image and in what 10 | * order. 11 | */ 12 | SECTIONS { 13 | /* 14 | * The Pi 3 will start executing the image from its first byte, so I need to 15 | * make sure the first section of the image contains that small piece of 16 | * assembly defined in `main.zig` meant to be the program entry point. There 17 | * in `main.zig` we say the assembly code is on a section called 18 | * `.text.boot`. Here I am just saying that we want this `.text.boot` 19 | * section to be the very first thing on the image. 20 | * 21 | * This also says to place this section at the 0x8000 address, which is 22 | * where the image will be loaded to by the Pi 3 bootloader. I think this is 23 | * not necessary in this case, because this information will be lost on the 24 | * final binary image. This 0x8000 address will be available on the ELF 25 | * executable (check with `zig build dump-elf`), but is gone from the final 26 | * binary image (check with `zig build dump-bin`). Still, I decided to keep 27 | * the reference to 0x8000 here: at least it works as a sort of 28 | * documentation, reminding me of where this code will be placed in the 29 | * Pi 3 memory when running. 30 | * 31 | * Also worth noting that I don't understand these things very well. If I 32 | * look at the ELF executable that is created first , `.text.boot` is not 33 | * the very first section -- but I discard all bogus sections when 34 | * generating the final raw image with `objcopy`). 35 | */ 36 | .text.boot 0x8000 : { 37 | *(.text.boot) 38 | } 39 | 40 | /* 41 | * And here I am saying that all other code is to be placed right after 42 | * that. "Text" is a synonym of "code", and by default all code is placed in 43 | * a section called `.text`. I think this line is not really necessary. If 44 | * I understand correctly, if I don't say where I want the `.text` section 45 | * to be placed, the linker is free to place it wherever it wants. That'd 46 | * be fine, because it doesn't matter. (I only care about the placement of 47 | * the `.text.boot` section, which I dealt with above.) 48 | */ 49 | .text : { 50 | *(.text) 51 | } 52 | 53 | /* 54 | * It's usual to also include the `.data` section in the linker script, but 55 | * I decided to leave it out, since this program does not define any data, 56 | * it's pure executable code. 57 | * 58 | * But there's one thing I do here: I discard all sections that I don't want 59 | * but for some reason the compiler generates. I inspected the generated 60 | * ELF found them with `zig build dump-elf` to find everything I wanted to 61 | * discard. 62 | */ 63 | /DISCARD/ : { 64 | *(.ARM.*) 65 | *(.debug_*) 66 | *(.comment) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | // 2 | // Bare metal programming a Pi 3 in Zig. 3 | // By Leandro Motta Barros 4 | // Licensed under the MIT license. See LICENSE.txt. 5 | // 6 | 7 | // These are the addresses where some GPIO control registers are mapped to. They 8 | // are marked as `volatile` to let the compiler know that accessing these 9 | // addresses has side effects (and therefore these accesses will not be 10 | // reordered or optimized away -- a property I'll explicitly make use of below). 11 | const GPFSEL1 = @intToPtr(*volatile u32, 0x3F20_0004); 12 | const GPSET0 = @intToPtr(*volatile u32, 0x3F20_001C); 13 | const GPCLR0 = @intToPtr(*volatile u32, 0x3F20_0028); 14 | 15 | // This is the real entry point for our program, and the only part of it in 16 | // assembly. That's just one instruction! It simply jumps (or branches, using 17 | // the `b` instruction) to our main function, `simplestMain`, written in Zig 18 | // below. 19 | // 20 | // One important thing here is that I place this code in the `.text.boot` 21 | // section of the resulting object. The linker script, `simplest.ld`, makes sure 22 | // this section is placed right at the beginning of the resulting binary. That's 23 | // what I want, because the Raspberry Pi 3 will start running from the beginning 24 | // of the binary. 25 | // 26 | // Maybe important: the linker will look (by default) for the `_start` symbol as 27 | // the program entry point. As far as I understand, though, this isn't relevant 28 | // for this program, because the Pi 3 will start running from the first byte of 29 | // the image. I am really defining the entry point by using the `.text.boot`, 30 | // and `_start` is effectivelly ignored. However, the linker will complain if it 31 | // can find `_start`, so I define it here to make our tools happy. There's 32 | // probably a more elegant way to do this. 33 | comptime { 34 | asm ( 35 | \\.section .text.boot 36 | \\.global _start 37 | \\_start: 38 | \\b simplestMain 39 | ); 40 | } 41 | 42 | // This is the "Zig entry point" of our program. The "real entry point" is 43 | // written above in assembly; but it doesn't to anything interesting, it just 44 | // jumps to here. 45 | export fn simplestMain() noreturn { 46 | // Configures the GPIO pin 16 as a digital output. 47 | GPFSEL1.* = 0x0004_0000; 48 | 49 | var ledON = true; 50 | 51 | while (true) { 52 | if (ledON) { 53 | // Set GPIO pin 16 to high. 54 | GPSET0.* = 0x0001_0000; 55 | } else { 56 | // Set GPIO pin 16 to low. 57 | GPCLR0.* = 0x0001_0000; 58 | } 59 | 60 | // I am sure there are prettier ways to make the program sleep for a 61 | // while. Anyway, looping idly for a while is easy to understand and 62 | // works well-enough, especially considering I am targeting a specific 63 | // hardware. 64 | var i: u32 = 2_500_000; 65 | while (i > 0) { 66 | // This assignment is effectively a no-op, as I already configured 67 | // the GPFSEL1 register above. However, it has a reason for being 68 | // here: since `GPFSEL1` is marked as `volatile`, the compiler will 69 | // assume this assignment has side effects. Without this, this whole 70 | // loop would be removed by the compiler optimizer. 71 | GPFSEL1.* = 0x0004_0000; 72 | i -= 1; 73 | } 74 | 75 | ledON = !ledON; 76 | } 77 | } 78 | --------------------------------------------------------------------------------