Post I made explaining how i made a basic blink in Bare Metal Rust on a Raspberry PI. (Wouldnt mind some feedback)
Videos
Hello there,
I've started my journey on bare metal Rust written on a Respberry (simulated with QEMU for now, I'm using the raspberry pi 2B model), and i'm still trying to make a "Hello world" program to run.
So far, I have written rust code that activates the mini UART, activates the GPIO 14/15 accordingly, and fills the send buffer of the mini UART. The main code looks like this :
let mut selector = GPFSEL1::read();
selector &= !(7<<12); // clean gpio14
selector |= 2<<12; // set alt5 for gpio14
selector &= !(7<<15); // clean gpio15
selector |= 2<<15; // set alt5 for gpio 15
GPFSEL1::write(selector);
GPPUD::write(0);
delay(150);
GPPUDCLK0::write((1<<14)|(1<<15));
delay(150);
GPPUDCLK0::write(0);
AUX_ENABLES::write(1); // Enable mini uart (this also enables access to its registers)
AUX_MU_CNTL_REG::write(0); // Disable auto flow control and disable receiver and transmitter (for now)
AUX_MU_IER_REG::write(0); // Disable receive and transmit interrupts
AUX_MU_LCR_REG::write(3); // Enable 8 bit mode
AUX_MU_MCR_REG::write(0); // Set RTS line to be always high
AUX_MU_BAUD_REG::write(270); // Set baud rate to 115200
AUX_MU_CNTL_REG::write(3); // Finally, enable transmitter and receiver
loop {
AUX_MU_IO_REG::write('c' as u32);
// print("Hello, World !")
}I've made a linker file, and explored the assembly, my code properly starts ar 0x8000:
target/armv7a-none-eabi/debug/harmony: file format elf32-littlearm
Disassembly of section .text:
00008000 <_start>:
8000: e24dd010 sub sp, sp, #16
8004: eb000061 bl 8190 <_ZN717Register$LT$_$GT$4read17ha9d176502c8214e9E>
8008: e58d000c str r0, [sp, #12]
800c: e59d000c ldr r0, [sp, #12]
8010: e3c00a07 bic r0, r0, #28672 @ 0x7000
8014: e58d000c str r0, [sp, #12]
8018: e59d000c ldr r0, [sp, #12]
801c: e3800a02 orr r0, r0, #8192 @ 0x2000
....
I then copy my elf file into a flat binary. Now, I'm running a simulated raspberry on QEMU, using the following command: qemu-system-arm -M raspi2b -kernel target/binary/my_custom_kernel.img -nographic
unfortunately, nothing happens. I've tried connecting to the QEMU process with the -s option on QEMU, and with gdb and the target remote localhost:1234 command, but then the stack pointer is shown to be at 0x0000, and when I try to see the registers they do not match the register names of the BCM2837-ARM. This makes me wondering if I'm using the QEMU+GDB thinhy properly.
I've been using this datasheet, following partly this tutorial.
Now, I'm out running out of ideas on what could go wrong, and I'm asking for help here. I have no clue where to look next !
If you guys need any more details, please ask !
Thanks in advance
Hi there,
I've been doing some embedded Raspberry Pi stuff with Rust and I'm having some trouble getting the system timer on the RPi2 to work. Here is my kernel:
#![feature(lang_items, start, asm)]
#![no_std]
const GPIO_SET: u32 = 0x3F200020; //location of gpio set register
const GPIO_CLR: u32 = 0x3F20002C; //location of gpio clear register
const GPIO47: u32 = 0x8000; //location of gpio47, which is bit 16 in either register
const SYS_TIMER: u32 = 0x4000001c; //location of sys timer afaik
fn gpio_set(port: u32, enabled: bool) {
let fun = match enabled {
true => GPIO_SET as *mut u32, //fun = pointer to GPIO set or GPIO clear
false => GPIO_CLR as *mut u32,
};
unsafe {
*fun = port; //1 is written to bit 16 of either set or clear register.
}
}
fn get_time() -> u32 {
let pointer = SYS_TIMER as *mut u32;
unsafe { *pointer } //dereference pointer to
}
#[start]
fn main(argc: isize, argv: *const *const u8) -> isize {
let mut old = get_time();
loop {
if get_time() - old > 1000000 {
gpio_set(GPIO47, true); // This loop here should blink the light every second using the systimer
old = get_time();
};
};
}
//lang features, need to be implemented or compiler shits itself.
#[lang = "eh_personality"]
extern fn eh_personality() {}
#[lang = "panic_fmt"]
extern fn panic_fmt() {}This code does not make the led light up at all. I've based my kernel off of this bare metal Rust tutorial. After I completed that tutorial I've been trying to port Step 03 of the Valvers C tutorial to Rust, but it doesn't seem to be working. Any suggestions?
I'm building a bare metal project for the Raspberry Pi 4 which is like a small kernel, game engine, and game together in a single raw binary. The idea is to go through a similar experience of what DOS game developers went through long ago by developing everything from scratch, entertain myself since I'm totally blind on disability benefits, and gain experience in embedded Rust along the way. After implementing the kernel part with drivers and an executor to take advantage of cooperative multitasking with the async / await coroutines supported by Rust, I have now built a software 3D rasterizer which is grinding to a halt after just a couple of frames. My belief is that deadlocks are to blame for the problem, but unfortunately since this is a bare metal project and qemu does not support emulating the Raspberry Pi 4, I'm kinda stuck debugging on actual hardware with messages sent over a serial cable. My locks are capable of catching some deadlocks, particularly those caused by locking twice in the same core, but this is not the only possible kind of deadlock.
My problem is that I cannot find an easy way to change my spin-lock to print the file and line where it was called, which would allow me to easily check where it got locked and not unlocked. I did build a backtrace function that displays the return addresses of all the functions in the call stack all the way back to the function called by the boot code, but matching its output to the contents of the raw binary is quite cumbersome since I have to link the final binary as an ELF instead of a raw binary, and then manually objcopy that to the final raw binary since Cargo's build scripts aren't powerful enough, and to make things worse there is no binutils targeting AArch64 on MacPorts, so I've been doing the objcopy step from a Docker container. If I was coding in C the solution would be easy: I'd just make a macro to replace all lock and unlock calls and send the file and line from the caller over the serial cable, but unfortunately since Rust macros require an extra bang, and since to my knowledge it is not possible to create a macro that simulates an associated function, this would never be a viable solution in Rust.
Has anyone here faced a similar problem with embedded Rust? And if so, how did you address it? I'm posting my backtrace function, its generated output, and my custom target file below just in case someone has suggestions for changes that could be made to improve the situation.
The backtrace function:
/// Sends the return addresses of all the function calls from this function all the way to the start function through the UART.
fn backtrace() {
let mut uart = UART.lock();
let mut fp: usize;
let mut lr: usize;
unsafe {asm!("mov {fp}, fp", "mov {lr}, lr", fp = out (reg) fp, lr = out (reg) lr, options (nomem, nostack, preserves_flags))};
let mut frame = 0usize;
writeln!(uart, "Backtrace:").unwrap();
while fp != 0x0 {
writeln!(uart, "#{frame}: 0x{lr:X}").unwrap();
unsafe {asm!("ldp {fp}, {lr}, [{fp}]", fp = inout (reg) fp, lr = out (reg) lr, options (preserves_flags))};
frame += 1;
}
}Example of the serial output that I get in a session with a forced panic to display the backtrace (core #3 was already sailing on weeds at this point since it didn't catch the software generated interrupt that was supposed to halt it on a panic):
Booted core #0 Booted core #1 Booted core #2 Tick in! Booted core #3 Starting tasks... Frame #0 Frame #1 Tasks finished! Core #0 panicked at src/gfx/mod.rs:201: Test! Backtrace: #0: 0x854EC #1: 0x85450 #2: 0x8FB08 #3: 0x8973C #4: 0x880D0 #5: 0x872A8 #6: 0x84D84 Halted core #0 Halted core #2 Halted core #1
My custom target file:
{
"cpu": "cortex-a72",
"arch": "aarch64",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
"disable-redzone": true,
"features": "+strict-align",
"is-builtin": false,
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"pre-link-args": {
"ld.lld": ["-Tlink.ld", "-nostdlib", "--oformat=binary"]
},
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"panic-strategy": "abort",
"relocation-model": "static",
"target-pointer-width": "64",
"frame-pointer": "always"
}If you’ve worked on any projects using Rust and a Raspberry Pi I would be interested to hear your story.
Cross-compiling to Raspberry PI is easy, and works great. Compiling on a Raspberry PI is rather painful due to long compile times.
(I'm assuming crosscompiling then running on Linux, not bare-metal (but that also should be possible)) Not a big project, but I've run simple HTTP server on one. To be honest, with Rust it mostly just:
rustup target add arm-unknown-linux-musleabihf
cargo build --target arm-unknown-linux-musleabihf
You might need to adjust target, depending on which Raspberry Pi version you're using.
In my case, because I've fairly old one (RPi 1 B+), I had to additionally download gcc toolchain (for linking) from arm's website, as the one from distro didn't support armv6.