Previous edition
Present code in Github repository is v0.1.0.
In this edition, lets find out how to include RISC-V assembly code with rust. Embedded programming includes bootstrapping (pulling processor right from reset). This means, boot code in assembly is inevitable.
Here is a list of things to do before the system can perform any meaningful work. Based on the HW features present on the RISC-V core, this list might vary. This is for FE310 SoC.
Set Stack Pointer
Implement Trap Handler
Set Machine Trap Vector
Configure Physical Memory Protection Unit
Switch from Machine(M) to User(U) mode
There are many ways to write & use assembly code with rust. In-line assembly code can exist with in a rust function or rust module body [refer asm!]. But, I like to keep assembly in a separate file if possible and bring the entire file to global scope with global_asm! macro from core arch crate.
Once its brought into global scope, its accessible from other rust modules. At times, I would also use asm! macro for inline assembly.
Create boot.S
Create boot.S and start writing boot code in assembly. Though few of these initial boot code can be written in rust, lets stick to RISC-V assembly as much as possible for boot code and jump into rust. In this way, its cleaner, & readable.
First, where to create the boot.S file in the cargo workspace? . If created inside execs binary package, it can’t be re-used, however if created as part of library crate libs, the library can be used across many executables.
bootstrap-ws $touch libs/src/boot.SFirst disable all interrupts while setting up the boot code to prevent it from interfering at boot time. Then set the stack pointer as below. The __stack_start_ comes from linker script. Let me know in comments if you can set the stack pointer with the la instruction without use of assembler directives %hi & %lo.
bootstrap-ws $cat libs/src/boot.S
.globl __boot_; /* To be visible to linker for entry */
.section .boot /* To keep in a particular location */
__boot_:
# Disable INDIVIDUAL Machine software(bit#3), timer(bit#7) &
# external(bit#11) machine interrupts while we set boot code
csrw mie, zero;
/* set stack pointer */
lui sp, %hi(__stack_start_);
addi sp, sp, %lo(__stack_start_);
Cargo should succeed to build with above changes. Lets inspect if the library has the function __boot_. Cargo builds .rlib file for library.
riscv64-unknown-elf-objdump -dC target/riscv32imac-unknown-none-elf/debug/liblibs.rlib
In archive target/riscv32imac-unknown-none-elf/debug/liblibs.rlib:
lib.rmeta: file format elf32-littleriscv
libs-49cd470ca24674d0.17nxpthld8fimcwx.rcgu.o: file format elf32-littleriscv
Disassembly of section .text._ZN4libs3add17hfb4a8ee3b5261a92E:
00000000 <libs::add>:
0: 1141 addi sp,sp,-16
2: c02e sw a1,0(sp)
4: 85aa mv a1,a0
6: 4502 lw a0,0(sp)
8: c42e sw a1,8(sp)
a: c62a sw a0,12(sp)
c: 952e add a0,a0,a1
e: c22a sw a0,4(sp)
10: 00b56663 bltu a0,a1,1c <libs::add+0x1c>
14: a009 j 16 <libs::add+0x16>
16: 4512 lw a0,4(sp)
18: 0141 addi sp,sp,16
1a: 8082 ret
1c: 00000537 lui a0,0x0
20: 00050513 mv a0,a0
24: 000005b7 lui a1,0x0
28: 00058613 mv a2,a1
2c: 45f1 li a1,28
2e: 00000097 auipc ra,0x0
32: 000080e7 jalr ra # 2e <libs::add+0x2e>
...
bootstrap-ws $
The objdump shows that the library liblibs.rlib built has ONLY a unrelated function named add. Hmm puzzled !! ? me too at first look. Then after inspecting the lib.rs file it became clear.
Library content are based on lib.rs file and executable contents are based on main.rs file. Since our lib.rs has no links to the new file boot.S, its NOT included in the library. Since the present lib.rs has following code, .rlib file has generated riscv assembly code for addition.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
Lets remove the present content of lib.rs and add the boot.S to library. Two handy macros for these are the global_asm! to bring the assembly function to global scope and the include_str! to include the assembly file into the rust program.
bootstrap-ws $cat libs/src/lib.rs
#![no_std]
#![no_main]
use core::arch::global_asm;
global_asm!(include_str!("boot.S"));Build (cargo build) and confirm if the liblibs.rlib has the __boot_ assembly function.
$riscv64-unknown-elf-nm target/riscv32imac-unknown-none-elf/debug/liblibs.rlib
lib.rmeta:
riscv64-unknown-elf-nm: lib.rmeta: no symbols
libs-49cd470ca24674d0.17nxpthld8fimcwx.rcgu.o:
00000000 N __boot_
U __stack_start_
bootstrap-ws $
Changes are submitted as part of release v0.2.0