Code Review: RISC-V Assembly Syntax

#![no_std]
#![no_main]

extern crate panic_halt;

use riscv_rt::entry;
use hifive1::hal::prelude::*;
use hifive1::hal::DeviceResources;
use hifive1::{sprintln, pin};
//use core::mem;
use riscv::register::{mepc,mstatus,mcause,mtvec,misa};

// Function to use as entry for user mode
fn user_mode(){
    //sprintln!("User Mode Entered!");  // Verify that user mode has been entered
    let _attempt = mepc::read();
    loop{};
}
// This function handles machine traps from user mode due to interrupts or exceptions
fn trap_handler(){
    sprintln!("Machine Trap Occurred!");
    //let reason = mcause::read();
    let ext = misa::read();
    sprintln!("{:032b}", ext.map_or(0, |v| v.bits()));

}

#[entry]
fn main() -> ! {
    let dr = DeviceResources::take().unwrap();
    let p = dr.peripherals;
    let pins = dr.pins;

    // Configure clocks
    let clocks = hifive1::clock::configure(p.PRCI, p.AONCLK, 320.mhz().into());

    // Configure UART for stdoutcar
    hifive1::stdout::configure(p.UART0, pin!(pins, uart0_tx), pin!(pins, uart0_rx), 115_200.bps(), clocks);

    // Top of stack using all 16k of ram
    let stack_ptr: usize = 0x80004000;

    // Setup machine trap vector
    let trap_address = trap_handler as *const();
    unsafe{mtvec::write(trap_address as usize,mtvec::TrapMode::Direct)};

    // prepare to entry user mode
    let user_entry = user_mode as *const();
    mepc::write(user_entry as usize);  // Entry point for user mode
    unsafe{mstatus::set_mpp(mstatus::MPP::User)}; // Set MPP bit to enter user mode (00)

    //Set return address to 0, set stack pointer, mret to enter user mode 
    #[cfg(all(riscv, feature = "inline-asm"))]
    unsafe{
        asm!("mov ra, 0",
             "mov sp, $0" : "=r" (stack_ptr) ,
             "mret");
    }


    loop{};
}

Did you compile it ?
The more-up-to-date documentation for asm! is https://doc.rust-lang.org/nightly/unstable-book/library-features/asm.html .

Yeah I started to think that I was using older syntax, I changed my inline to this

 asm!("mov ra, 0",
      "mov sp, {0}",
      "mret",
      in(reg) stack_ptr);

I don't have any issues building the program, but I do get a warning that stack_ptr is unused?

Is the instruction valid ?
You could quick compile here: https://rust.godbolt.org/z/Y8s3Kf

Seems like there are some mistakes: https://rust.godbolt.org/z/z5o399

1 Like

Very nice, I had heard of godbolt, but I have never tried to use it. Looks like it can be very useful, thank you for the links. I will give the new syntax a try.

EDIT:
Also, do I need to add the #[no_mangle] and pub unsafe fn, or was that just for godbolt?

Yeah, just for godbolt.

1 Like

Any ideas on the syntax for using modules, I cannot figure it out.
I've tried

let cause = mcause::read();
let cause = mcause::Mcause::Trap();
let cause = mcause::Mcause::code();
let cause = mcause::Mcause.code();
etc

Just trying to get the exception enum result

I think I figured it out, LOL.

mcause::read().code()

My inline assembly isn't working. The SiFive guys suggested I use the assembly I have setup in godbolt. It compiles fine on godbolt, but I get an error when I try to build the program

error: linking with rust-lld failed: exit code: 1  ....
undefined symbol a0

When I try

#![no_std]
#![feature(asm, start)]

#[no_mangle]
pub unsafe fn foo() {
    let stack_ptr: usize = 0x80003b80;
    // #[cfg(all(riscv, feature = "inline-asm"))]
    asm!(
        "mv ra, zero",
        "la t0, {0}",
        "lw sp, 0(t0)", 
        "mret",
        in(reg) stack_ptr ,
    );

}

#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[start]
fn start(_: isize, _: *const *const u8) -> isize {
    unsafe { foo(); }
    0
}

with -Clinker=rust-lld on godbolt it compiles without problem. (you have to enable the "compile to binary" option under "output")

Still getting that same error when I try to build on my laptop. I see that it compiles on godbolt with -Clinker=rust-lld. I'm using the riscv_rt crate which uses link.x as a link argument, but I'm not sure that has anything to do with it.

"/home/dkhayes117/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32imac-unknown-none-elf/lib/libcompiler_builtins-a966de93028a50b8.rlib" "-Tmachine-memory.x" "-Tlink.x" "-Bdynamic"
  = note: rust-lld: error: undefined symbol: a0
      	>>> referenced by user_stack.rs:98 (examples/user_stack.rs:98)
      	>>>     

Does riscv have other linkers?

The error is weird because a0 is a valid register.

I'm not sure, I thought it was strange as well, but any assembly that expands into something with a0 fails. I based the project off of the rust-riscv-quickstart template on github, which is the only setup I know of to use Rust with the HiFive1 Rev B microcontroller. I modified the link files and arguments but even using the template defaults I get the same error.

EDIT: Maybe I can precompile the assembly portion and include it as a module or something in my code. The riscv crate that I am using does this, but I have no idea on how to do this

What is even weirder is that the original assembly I used expands to use a0 and builds fine

  asm!(
        "mv ra, zero",
        "mv sp, {0}",
        "mret",
        in(reg) stack_ptr ,
    );
foo:
        lui     a0, 524292
        addi    a0, a0, -1152
        mv      ra, zero
        add     sp, zero, a0
        mret    

        j       .LBB0_1
.LBB0_1:
        ret

what is your lld version, also did you try rust-lld ?

https://github.com/riscv/riscv-lld is a forked of LLD, but I expect most patches are merged back upstream.

1 Like

I don't think the assembly was correct for my operation. I determined my original assembly that you helped me with was correct and was not the issue. It turns out that when going into user mode any memory accesses have to match one of the physical memory protection (PMP) registers. I never configured them because they were all turned off, but this doesn't matter, while in u-mode PMPs are always checked. Once I fixed that, all went well :wink:

1 Like