#sbi #risc-v

yanked riscvsbi

Minimal RISC-V's SBI implementation library

0.1.4 May 20, 2021
0.1.3 May 19, 2021
0.1.2 May 16, 2021
0.1.1 May 16, 2021
0.1.0 May 16, 2021

#6 in #sbi

MulanPSL-2.0 OR MIT

52KB
687 lines

riscvsbi

RISC-V Supervisor Binary Interface (SBI) implementation in Rust; runs on M-mode. SBI Version 0.2 SBI Version 0.3


lib.rs:

A minimal RISC-V's SBI implementation in Rust.

How to use RustSBI

SBI features include boot sequence and a kernel environment. To bootstrap your kernel, place kernel into RustSBI implementation defined address, then RustSBI will prepare an environment and jump to this address.

Make SBI environment calls

To use the kernel environment, you either use SBI calls or emulated instructions. SBI calls are similar to operating systems' syscalls. RISC-V SBI defined many SBI modules, and in each module there are different functions, you should pick a function before calling. Then, you should prepare some parameters, whose definition are not the same among functions.

Now you have a module number, a function number, and a few SBI call parameters. You invoke a special ecall instruction on supervisor level, and it will trap into machine level SBI implementation. It will handle your ecall, similar to your kernel handling system calls from user level.

SBI functions return two values other than one. First value will be an error number, it will tell if SBI call have succeeded, or which error have occurred. Second value is the real return value, its meaning is different according to which function you calls.

Call SBI in different programming languages

Making SBI calls are similar to making system calls.

Module number is required to put on register a7, function number on a6. Parameters should be placed from a0 to a5, first into a0, second into a1, etc. Unused parameters can be set to any value or leave untouched.

After registers are ready, invoke an instruction called ecall. Then, the return value is placed into a0 and a1 registers. The error value could be read from a0, and return value is placed into a1.

In Rust, here is an example to call SBI functions using inline assembly:

#[inline(always)]
fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize) -> SbiRet {
    let (error, value);
    match () {
        #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
        () => unsafe { asm!(
            "ecall",
            in("a0") arg0, in("a1") arg1,
            in("a6") function, in("a7") extension,
            lateout("a0") error, lateout("a1") value,
        ) },
        #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))]
        () => {
            drop((extension, function, arg0, arg1));
            unimplemented!("not RISC-V instruction set architecture")
        }
    };
    SbiRet { error, value }
}

#[inline]
pub fn get_spec_version() -> usize {
    sbi_call(EXTENSION_BASE, FUNCTION_BASE_GET_SPEC_VERSION, 0, 0).value
}

Complex SBI functions may fail. In this example we only take the value, but in complete designs we should handle the error value returned from SbiRet.

Dependencies

~270KB