📚 Learn to write an embedded OS in Rust 🦀
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Richard A. Healy cdfce0d01b add openocd argument and raspboot device vars 5 years ago
.githooks Update binaries generated by newer rustc 5 years ago
0A_power add openocd argument and raspboot device vars 5 years ago
0B_hw_debug_JTAG Update 0B_hw_debug_JTAG/Makefile 5 years ago
0C_exception_levels add openocd argument and raspboot device vars 5 years ago
0D_virtual_memory add openocd argument and raspboot device vars 5 years ago
0E_cache_performance add openocd argument and raspboot device vars 5 years ago
0F_globals_synchronization_println add openocd argument and raspboot device vars 5 years ago
01_bareminimum Change to new aarch64-unknown-none-softloat target. 5 years ago
02_multicore_rust Change to new aarch64-unknown-none-softloat target. 5 years ago
03_uart1 Change to new aarch64-unknown-none-softloat target. 5 years ago
04_mailboxes Change to new aarch64-unknown-none-softloat target. 5 years ago
05_uart0 Change to new aarch64-unknown-none-softloat target. 5 years ago
06_raspbootin64 Change to new aarch64-unknown-none-softloat target. 5 years ago
07_abstraction add openocd argument and raspboot device vars 5 years ago
08_random add openocd argument and raspboot device vars 5 years ago
09_delays add openocd argument and raspboot device vars 5 years ago
10_DMA_memory add openocd argument and raspboot device vars 5 years ago
11_exceptions_groundwork add openocd argument and raspboot device vars 5 years ago
12_Spinlocks tutorials++ to account for JTAG tutorial 5 years ago
13_Interrupts tutorials++ to account for JTAG tutorial 5 years ago
X1_JTAG_boot add openocd argument and raspboot device vars 5 years ago
doc Add README for tutorial 10_DMA_memory 5 years ago
docker Add docker folder README 5 years ago
emulation Add code for 0F_DMA_memory 5 years ago
utils Add copyright year checks. Ran Rubocop as well. 5 years ago
.gitignore Ignore cargo built files 5 years ago
.travis.yml Update .travis.yml 6 years ago
LICENSE Formatting didn't work. Try again. 6 years ago
README.md Update README.md 5 years ago
TODO.md New todo entry 6 years ago
contributor_setup.sh Add a pre-commit hook 5 years ago

README.md

Bare-metal and Operating System development tutorials in Rust on the Raspberry Pi 3

Build Status

Notice

The tutorials are being rewritten on the v2 branch, so please have a look from time to time. The master branch will only receive updates if something breaks.

The rewrite also supports both, the Raspberry Pi 3 and the Raspberry Pi 4!

Cheers, Andre

Introduction

This repository aims to provide easy reference code for programming bare metal on the Raspberry Pi 3 in the Rust systems programming language. Emphasis is on leveraging Rust's zero-overhead abstractions to compile lean code that is readable, concise and safe (at least as safe as it gets on bare-metal hardware).

The target audience is hobby OS developers who are new to ARM's 64 bit ARMv8-A architecture. It will give you examples on how to do common Operating Systems tasks, like writing to the serial console, setting up virtual memory and exception handling and using various peripherals like a hardware-backed random number generator.

However, it is not a tutorial on how to write a complete OS from start to finish. Rather, it comprises a set of micro-tutorials that introduce different topics one after the other. Maybe in the distant future, a meta tutorial that combines all the resources to a full-fleged kernel that can multi-task some simple userspace processes is possible, but don't take my word for it.

Environment

This repo tries to put a focus on user friendliness. Therefore, I made some efforts to eliminate the biggest painpoint in embedded development: Toolchain hassles.

Users eager to try the code should not be bothered with complicated toolchain installation/compilation steps. This is achieved by trying to use the standard Rust toolchain as much as possible, and bridge existing gaps with Docker containers. Please install Docker for your distro.

The setup consists of the following components:

  1. Compiler, linker and binutils are used from Rust nightly.
  2. QEMU will be used for emulation, but RPi3 support in QEMU is very fresh and has not landed in most of the pre-packaged versions of popular distributions. This container will provide it ready to go.

Please notice that you won't need to download or prepare the containers upfront. As long as you have docker installed, they will be pulled automatically the first time the Makefile needs them.

If you want to know more about docker and peek at the the containers used in these tutorials, please refer to the repository's docker folder.

Prerequisites

Before you can start, you'll need a suitable Rust toolchain.

curl https://sh.rustup.rs -sSf | sh -s -- \
                                       --default-toolchain nightly \
                                       --component rust-src llvm-tools-preview clippy rustfmt

cargo install cargo-xbuild cargo-binutils

Additionally, a Micro SD card with firmware files on a FAT filesystem is needed.

I recommend to get a Micro SD card USB adapter (many manufacturers ship SD cards with such an adapter), so that you can connect the card to any desktop computer just like an USB stick, no special card reader interface required (although many laptops have those these days).

You can create an MBR partitioning scheme on the SD card with an LBA FAT32 (type 0x0C) partition, format it and copy bootcode.bin, start.elf and fixup.dat onto it. Delete all other files or booting might not work. Alternatively, you can download a raspbian image, dd it to the SD card, mount it and delete the unnecessary .img files. Whichever you prefer. What's important, you'll create kernel8.img with these tutorials which must be copied to the root directory on the SD card, and no other .img files should exists there.

I'd also recommend to get an USB serial debug cable. You connect it to the GPIO pins 14/15.

UART wiring diagram

Then, run screen on your desktop computer like

sudo screen /dev/ttyUSB0 115200

Exit screen again by pressing ctrl-a ctrl-d

About this repository

The tutorial is basically a combination of two awesome resources.

  1. First of all, it is a fork of Zoltan Baldaszti's awesome tutorial on bare metal programming on RPi3 in C.
    1. Rust code will be based on his files, READMEs will be adapted, and I might change things here and there if I think it is beneficial. However, credits to this guy plz!
  2. The second props go to Jorge Aparicio for "The Embedonomicon", from which the boot code is taken.

About the hardware

There are lots of pages on the internet describing the Raspberry Pi 3 hardware in detail, so I'll be brief and cover only the basics.

The board is shipped with a BCM2837 SoC chip. That includes a

  • VideoCore GPU
  • ARM-Cortex-A53 CPU (ARMv8)
  • Some MMIO mapped pheripherals.

Interestingly the CPU is not the main processor on the board. When it's powered up, first GPU runs. When it's finished with the initialization by executing the code in bootcode.bin, it will load and execute the start.elf executable. That's not an ARM executable, but compiled for the GPU. What interests us is that start.elf looks for different ARM executables, all starting with kernel and ending in .img. As we're going to program the CPU in AArch64 mode, we'll need kernel8.img only, which is the last to look for. Once it's loaded, the GPU triggers the reset line on the ARM processor, which starts executing code at address 0x80000 (or more precisely at 0, but the GPU puts an ARM jump code there first).

The RAM (1G for the Raspberry Pi 3) is shared among the CPU and the GPU, meaning one can read what the other has written into memory. To avoid confusion, a well defined, so called mailbox interface is established. The CPU writes a message into the mailbox, and tells the GPU to read it. The GPU (knowing that the message is entirely in memory) interprets it, and places a response message at the same address. The CPU has to poll the memory to know when the GPU is finished, and then it can read the response.

Similarily, all peripherals communicates in memory with the CPU. Each has it's dedicated memory address starting from 0x3F000000, but it's not in real RAM (called Memory Mapped IO). Now there's no mailbox for peripherals, instead each device has it's own protocol. What's common for these devices that their memory must be read and written in 32 bit units at 4 bytes aligned addresses (so called words), and each has control/status and data words. Unfortunately Broadcom (the manufacturer of the SoC chip) is legendary bad at documenting their products. The best we've got is the BCM2835 documentation, which is close enough.

There's also a Memory Management Unit in the CPU which allows creating virtual address spaces. This can be programmed by specific CPU registers, and care must be taken when you map these MMIO addresses into a virtual address space.

Some of the more interesting MMIO addresses are:

0x3F003000 - System Timer
0x3F00B000 - Interrupt controller
0x3F00B880 - VideoCore mailbox
0x3F100000 - Power management
0x3F104000 - Random Number Generator
0x3F200000 - General Purpose IO controller
0x3F201000 - UART0 (serial port, PL011)
0x3F215000 - UART1 (serial port, AUX mini UART)
0x3F300000 - External Mass Media Controller (SD card reader)
0x3F980000 - Universal Serial Bus controller

For more information, see Raspberry Pi firmware wiki and documentation on github.

https://github.com/raspberrypi

Good luck and enjoy hacking with your Raspberry! :-)

Andre

License

Licensed under the MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT).