PMP registers and User Mode

There are several calling conventions defined in the RISC-V ELF psABI. Some people refer to this document as (part of) the “Unix” ABI, to distinguish it from the “Embedded” ABI. Both documents define multiple calling conventions, including three different stack pointer alignment constraints (4-byte, 8-byte, 16-byte).

In my opinion, your safest bet is to start with 16-byte stack alignment: your code will be compatible, at least in this regard, with all the “official” extant ABIs. Then, if you find you are really hurting for stack space and you think this alignment is the culprit, it’s time to familiarize yourself with the various embedded ABIs and (perhaps) participate in the EABI Task Group.

I don’t know your background. If all this “(ps)ABI” and “calling convention” stuff sounds confusing — it is — and the best way to really understand the state of the art is to understand how we got here. If you are interested in getting mired in this morass, here’s some reading to get you started:

https://refspecs.linuxfoundation.org/
http://www.sco.com/developers/gabi/
https://wiki.osdev.org/System_V_ABI
https://stackoverflow.com/questions/18133812/where-is-the-x86-64-system-v-abi-documented
https://en.wikipedia.org/wiki/UNIX_System_V

1 Like

I’ve got my rust code setup to create a 16-byte aligned array ( [u8;768] ), and I have the stack pointer calculated. Crates.io has a riscv crate that gives access to CSR reading and writing, but there is not any functionality for setting the sp/ra, or issuing a mret instruction. Rust does not have stable inline assembly. To use inline assembly you have to use the nightly compilier. I was told that I can write the assembly, precompile it, then link it in somehow to use the stable Rust. Anyways the assembly should be pretty simple for writing to ra,sp, and then mret. I’m going to try the precompiling, and use inline/nightly as a backup.

Assembly needed:

mv ra, 0
mv sp, 0x80003EEC //This will be a parameter value
mret

Question:
The user mode example has a function trying to read the MISA register labeled as user_mode_entry_point() I take it that this function exists inside the 16 byte aligned user stack created? It seems that the entry point’s address is set in the MEPC register.

Or does the function exist outside of the user stack, and the user stack is used for user function parameters etc?

The user mode function is in the text section, alongside the machine mode functions. This is just an example to show how to switch into and out of user mode. It isn’t an example of how to write an OS.

So to fully sandbox a user function from machine mode functions I need an OS or kernel of some sort?

I’ve never tried to write a RISC-V OS. I would guess that you need machine mode and user mode code in different address ranges, with PMP registers set to allow user mode access to only the one address range. A kernel is the traditional way to solve that problem, but probably not the only way to solve it. You might be able to do it with separate elf sections for machine mode code and user mode code that get loaded to different addresses. But that assumes you have a proper ELF loader, and not just a loader for bin or hex files. There may also be other solutions.

1 Like

I’m working on modifying the memory definition and linker script for the board. I should be able to sandbox m and u code this way. I believe the rust toolchain setup for HiFive1 Rev B produces ELFs not hex or bin. I’ll update the thread on progress made. :crossed_fingers:

I think I have my program setup to enter user mode, but I’m not sure I have MTVEC setup correctly. Once I go to user mode entry point function, I don’t seem to be getting to my trap handler function. Any ideas on what I might be missing? I know this is Rust and not C, but the logic can still be followed.

#![no_std]
#![no_main]
#![feature(asm)]

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 misa = misa::read();
    
}
// This function handles machine traps from user mode due to interrupts or exceptions
fn trap_handler(){
    sprintln!("Machine Trap Occurred!");
    let code = mcause::read().code();
    sprintln!("Trap Code: {:032b}", code);
    loop{};
}

#[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 
    unsafe{
        asm!("mov ra, zero",
             "mov sp, {0}" ,
             "mret",
             in(reg) stack_ptr);
    }


    loop{};
}

EDIT: I read the register contents after writing to them with my code to check the values. The user function address matches the MEPC register so that seems fine. However, my trap function is at 0x2001102A and after writing the MTVEC address comes back as 0x20011028. This has to be from the MTVEC register having the trap mode bits?

It seems my trap_handler address of 0x2001102A is not 4byte aligned which is why the MTVEC reads as 0x20011028. Now how to make sure the function is at 4byte aligned address

I was able to enter user mode, attempt to read a CSR and trap into a function defined with MTVEC :slight_smile:
My problem was the 4 byte alignment for MTVEC which I solved by using a compiler flag. Rust’s compiler is built on LLVM not GCC so

llvm-args=-align-all-functions=2

where 2 means 2^2 so 4byte aligned functions.