# Raspberry Pi 3 Bare Metal in Rust

Happy new year!

I bought a Raspberry Pi 3 several days ago. Playing with Linux seems boring for me, so I decided to pay some attention on ARM architecture. There are a lot of documents about bare metal development of Raspberry Pi, but almost all of them are using C & assembly. Since I’m learning Rust these days, it should be fun to try to implement something using Rust.

To get started, the most simplest task is to turn on the LED. Although there are some excellent resources available on how to turn on the LED for Raspberry Pi 1 & 2, unfortunately, all of them don’t work for Raspberry Pi 3, until I found this site.

Basically, the LED isn’t directly connected to GPIO anymore because GPIO pins are now used for Bluetooth and WiFi. To access the LED, you need to use Virtual GPIO, which is controlled by the VideoCore GPU. The way that ARM communicates with VideoCore is through Mailboxes.

# Assembly Version

According to the website I mentioned above, the assembly version is easy. We know the address of the mailbox, so we check the status of it and send it a message. I don’t want to get into the hardware details, so that’s it. The only thing we need to take care of is that the message itself is 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox.

## Assembly Code

.global _start

.section .data
.align 4
PropertyInfo:
.int PropertyInfoEnd - PropertyInfo
.int 0

.int 0x00038041
.int 8
.int 0

.int 130
.int 1
.int 0
PropertyInfoEnd:

.section .text
_start:
mailbox .req r0
ldr mailbox, =0x3f00b880

wait1$: status .req r1 ldr status, [mailbox, #0x18] tst status, #0x80000000 .unreq status bne wait1$

message .req r1
ldr message, =PropertyInfo
str message, [mailbox, #0x20]
.unreq message

hang:
b hang

The linking script is also very short. It places the .text section at 0x8000 and the .data section follows the .text section.

SECTIONS {
.text 0x8000 : {
*(.text)
}

.data : {
*(.data)
}
}

## Building

Make sure that you have installed arm-none-eabi toolchain.

# compile the assembly to an object file
$arm-none-eabi-as kernel.s -o kernel.o # link the object file using our linking script$ arm-none-eabi-ld kernel.o -o kernel.elf -T kernel.ld
# we need pure binary code, not an ELF file, so strip the ELF header
arm-none-eabi-objcopy kernel.elf -O binary kernel.img  Now we’ve generated a binary image, put it into your SD card together with bootcode.bin and start.elf. # C Version In fact it’s not necessary to use assembly, so I translate it into C. The first item in msg is the length of the message itself. In the assembly version, this length is calculated through PropertyInfoEnd - PropertyInfo. Here I just hard code it. ## C code unsigned int msg[] = {32, 0, 0x00038041, 8, 0, 130, 1, 0}; const unsigned int MAILBOX = 0x3f00b880; void open_led() { unsigned int status; do { status = *(unsigned int *)(MAILBOX + 0x18); } while (status & 0x80000000); *(unsigned int *)(MAILBOX + 0x20) = (unsigned int)msg + 8; while (1) ; } ## Linking There are two differences in the linking script: 1. The entry point is the function open_led. 2. The data section should be 16-byte aligned. ENTRY(open_led) SECTIONS { .text 0x8000 : { *(.text) } . = ALIGN(16); .data : { *(.data) } } ## Building # compile the c code to an object file arm-none-eabi-gcc -O2 -c led.c -o led.o
$arm-none-eabi-ld led.o -o led -T led.ld # we need pure binary code, not an ELF file, so strip the ELF header$ arm-none-eabi-objcopy led -O binary kernel.img

The -O2 flag for gcc is necessary. Without this option, the compiler will try to store variables in the stack, but we don’t have a stack right now! The -O2 flag tells the compiler to use registers thus avoid this problem.

# Rust Version

Finally, we’re now trying to write this program in Rust. The first thing we need to do is installing the appropriate toolchain. Here is a good resource about cross-compilation of Rust. If you don’t know what “cross-compilation” is, please read it first. We use rustup to manage the installation process.

Follow the instruction in the website above to install rustup. Then add arm-unknown-linux-gnueabihf target:

rustup target add arm-unknown-linux-gnueabihf

## Very Basic Example

For simplicity, I want to first construct a minimal bare-metal binary, so let’s begin from a infinite loop:

// test.rs
fn open_led() {
loop {}
}


Compile and get error message:

$rustc --target arm-unknown-linux-gnueabihf --emit=obj -O test.rs error[E0601]: main function not found error: aborting due to previous error By default, the compiler tries to generate a binary, so we need to specify that we want a library: // test.rs #![crate_type = "staticlib"] fn open_led() { loop {} }  But the compiler complains that the open_led is never used. What’s worse, the function seems to be deleted as an optimization. $ rustc --target arm-unknown-linux-gnueabihf --emit=obj -O test.rs
warning: function is never used: open_led
--> test.rs:3:1
|
3 | fn open_led() {
| ^^^^^^^^^^^^^
|
= note: #[warn(dead_code)] on by default

In C, functions are extern by default, this error implies that Rust is different. So we explicitly declare it as global. no_mangle is used to avoid renaming.

// test.rs
#![crate_type = "staticlib"]
#[no_mangle]
pub extern fn open_led() {
loop {}
}


Good, no error! Now try to link it:

$arm-none-eabi-ld -e open_led test.o -o test test.o:(.ARM.exidx.text.open_led+0x0): undefined reference to __aeabi_unwind_cpp_pr0' OK, Rust needs this function. Either way, just define it as empty: // test.rs #![crate_type = "staticlib"] #[no_mangle] pub extern fn open_led() { loop {} } #[no_mangle] pub extern fn __aeabi_unwind_cpp_pr0() {}  Compile and link it again and finally we get a binary. ## Write Code in Rust Now we are ready to implement open_led function in Rust: // test.rs #![crate_type = "staticlib"] const MAILBOX: u32 = 0x3f00b880; const MSG: [u32;8] = [32, 0, 0x00038041, 8, 0, 130, 1, 0]; #[no_mangle] pub extern fn open_led() { let mut status: u32 = 0x80000000u32; while (status & 0x80000000u32) != 0 { status = unsafe{*((MAILBOX + 0x18) as *const u32)}; } unsafe{*((MAILBOX + 0x20) as *mut u32) = (&MSG as *const u32 as *const u8).offset(8) as u32}; loop {} } #[no_mangle] pub extern fn __aeabi_unwind_cpp_pr0() {}  ## Linking The names of the segments that rustc generates are a little complicated: $ readelf -e test.o
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .strtab           STRTAB          00000000 0001d8 0000f5 00      0   0  1
[ 2] .text             PROGBITS        00000000 000034 000000 00  AX  0   0  4
[ 3] .text.open_led    PROGBITS        00000000 000034 000030 00  AX  0   0  4
[ 4] .rel.text.open_le REL             00000000 0001b0 000008 08     13   3  4
[ 5] .ARM.exidx.text.o ARM_EXIDX       00000000 000064 000008 00  AL  3   0  4
[ 6] .rel.ARM.exidx.te REL             00000000 0001b8 000010 08     13   5  4
[ 7] .text.__aeabi_unw PROGBITS        00000000 00006c 000004 00  AX  0   0  4
[ 8] .ARM.exidx.text._ ARM_EXIDX       00000000 000070 000008 00  AL  7   0  4
[ 9] .rel.ARM.exidx.te REL             00000000 0001c8 000010 08     13   8  4
[10] .rodata.cst32     PROGBITS        00000000 000078 000020 20  AM  0   0  4
[11] .note.GNU-stack   PROGBITS        00000000 000098 000000 00      0   0  1
[12] .ARM.attributes   ARM_ATTRIBUTES  00000000 000098 000036 00      0   0  1
[13] .symtab           SYMTAB          00000000 0000d0 0000e0 10      1  12  4

Our code is in section .text.open_led, the data can be found in .rodata.cst32. The linking script is as follows:

ENTRY(open_led)
SECTIONS {
.text 0x8000 : {
*(.text.open_led)
}
. = ALIGN(16);
.rodata : {
*(.rodata.cst32)
}
}

## Building

$rustc --target arm-unknown-linux-gnueabihf --emit=obj -O test.rs$ arm-none-eabi-ld -T link.ld test.o -o test
\$ arm-none-eabi-objcopy test -O binary kernel.img

# Run

Place kernel.img we generate together with bitcode.bin and start.elf` in your SD card. Stick the SD card into your Raspberry Pi 3 and turn it on. You will see the green LED is on!