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.

  1. Set Stack Pointer

  2. Implement Trap Handler

  3. Set Machine Trap Vector

  4. Configure Physical Memory Protection Unit

  5. 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.S

First 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

Keep reading