rust-riscv-os c to rust virtio-drivers lib callback

https://learningos.github.io/rust-based-os-comp2022/chapter8/index.html https://zhuanlan.zhihu.com/p/344819263 https://avacariu.me/writing/2014/calling-rust-from-c http://rcore-os.cn/rCore-Tutorial-deploy/docs/lab-4/guide/part-6.html https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.pdf https://cloud.tencent.com/developer/article/1770529 https://youtu.be/vkvZ7ohx-84 https://mindchasers.com/dev/rv-getting-started https://github.com/riscv-collab/riscv-gnu-toolchain https://ithelp.ithome.com.tw/articles/10257612 https://ppfocus.com/0/au628a911.html https://cloud.tencent.com/developer/article/1770529 https://magiclen.org/rust-no-std/

看了一些文章,原本想從qemu vga動手,後來越看越多可能要整合opensbi,驅動還要是virtio還是找不到有關於最新的opensbi virtio 的驅動範例,不過有找到rust 版本的 https://github.com/rcore-os/virtio-drivers 主要嘗試在 https://github.com/plctlab/riscv-operating-system-mooc 是否有可能在link 階段靜態編譯,讓實驗用的os用c語言開機去call rust函數在返回

參考一下原版的virtio-drivers 可以看到一些步驟我就省略,virtio透過載入opensbi 可以抓到驅動,算是一層interface 這個專案已經寫了一些test driver.

Device Status
Queue
Block
Net
GPU
Input
Console
... ❌ Not implemented

我覺得驅動寫到這邊已經蠻充足的了,今天我想要完成一個gui的介面也只要透過 即可以對qemu的記憶體區段寫入資料, 稍微修改一下 問題來了能不能把它當成強制改成靜態連結進行編譯,用c call rust 的fucntion,來嘗試一下

大概會拆成兩個步驟,但是共同的第一步都要是降成相同位元的compiler riscv-operating-system-mooc virtio-drivers virtio-drivers我們委屈一下,riscv64 降成riscv32

但是riscv64 預設qemu的bios 有支援 opensbi所以又要重編一下 opensbi

git clone https://github.com/riscv/opensbi.git

FW_JUMP_ADDR是我們開機完第一個要跳轉的address我們這邊寫死,因為要順應LD的起始位置0x80200000

這兩個有預設opensbi寫好的載入範例

code
/home/x213212/opensbi/firmware/

code
/home/x213212/opensbi/firmware/payloads

我們在把os riscv-operating-system-mooc第三章的部分的printf 改入這個範例

進行編譯

code
export CROSS_COMPILE=riscv64-unknown-elf-
make PLATFORM=generic clean
make PLATFORM=generic FW_JUMP_ADDR=0x80200000

位於課程的riscv-operating-system-mooc /riscv-operating-system-mooc/code/common.mk 編譯和qemu都要降為32位元

code
CROSS_COMPILE = riscv32-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall
# qemu-system-riscv64 \
#   --machine virt \
#   --nographic \
#   --bios default
#QEMU = qemu-system-riscv32 
#QFLAGS = --nographic -smp 1 -machine  virt -bios none
QEMU = qemu-system-riscv32
QFLAGS = --nographic -smp 1 -machine  virt -bios fw_jump.elf  -device loader,file=test.bin,addr=0x80200000  -device virtio-gpu-device
GDB = gdb-multiarch
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump
~                                    

opensbi編出來的是sivefive的韌體 make run 進行啟動qemu

code
-device loader,file=test.bin,addr=0x80200000

跳轉位置記得設定好 把剛剛編譯出來的test.elf ,test.bin,fw_jump.elf 都copy到其中一個riscv-operating-system-mooc 的章節我們選選01-helloRVOS 章節

到這邊我們就可以讓課程riscv-operating-system-mooc 載入 opensbi進行啟動。

第二個部分就是c在沒有std環境到底能不能去呼叫rust的東西,這邊我直接把 https://github.com/rcore-os/virtio-drivers 改成靜態連結,目前還在嘗試可以編譯的過,唯一要注意的是很多文章都沒有明確指出到底編譯成std lib 會報錯的問題有那些

Cargo.toml

code
[package]
name = "riscv"
version = "0.1.0"
authors = ["Runji Wang <wangrunji0408@163.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "double_input"
crate-type = ["staticlib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

[dependencies]

log = "0.4"
riscv = "0.6"
opensbi-rt = { git = "https://github.com/rcore-os/opensbi-rt.git", rev = "1308cc5" }
device_tree = { git = "https://github.com/rcore-os/device_tree-rs", rev = "2fa8411" }
virtio-drivers = { path = "../.." }
lazy_static = { version = "1.4", features = ["spin_no_std"] }
spin = "0.9.2"
linked_list_allocator = "0.10.1"
spinning_top = "0.2.4"

buddy_system_allocator =  "0.6"

lib.rs

code
#![no_std]
#![no_main]
#![crate_type = "staticlib"]
#![feature(llvm_asm)]
#![feature(default_alloc_error_handler)]

extern crate alloc;

use alloc::vec;
use device_tree::util::SliceRead;
use device_tree::{DeviceTree, Node};
use log::{info, warn};
use virtio_drivers::*;
use virtio_impl::HalImpl;
mod virtio_impl;
use buddy_system_allocator::LockedHeap;
#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
#[no_mangle]

pub extern "C" fn double_input() {
    info!("device tree ");
    let hi = "hi";
    let mut count = 0;

    // let _ret: usize;
    // let arg0: usize = b'O' as usize;
    // let arg1: usize = 0;
    // let arg2: usize = 0;
    // let which: usize = 1;
    // unsafe {
    //     llvm_asm!("ecall"
    //          : "={x10}" (_ret)
    //          : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
    //          : "memory"
    //          : "volatile"
    //     );
    // }
    while count < 100000 {
        // println!("{}, count: {}", hi, count);
        count += 1;
    }
    // loop {}
}
use core::panic::PanicInfo;

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

fn init_dt(dtb: usize) {
    info!("device tree @ {:#x}", dtb);
    #[repr(C)]
    struct DtbHeader {
        be_magic: u32,
        be_size: u32,
    }
    let header = unsafe { &*(dtb as *const DtbHeader) };
    let magic = u32::from_be(header.be_magic);
    const DEVICE_TREE_MAGIC: u32 = 0xd00dfeed;
    assert_eq!(magic, DEVICE_TREE_MAGIC);
    let size = u32::from_be(header.be_size);
    let dtb_data = unsafe { core::slice::from_raw_parts(dtb as *const u8, size as usize) };
    let dt = DeviceTree::load(dtb_data).expect("failed to parse device tree");
    walk_dt_node(&dt.root);
}

fn walk_dt_node(dt: &Node) {
    if let Ok(compatible) = dt.prop_str("compatible") {
        if compatible == "virtio,mmio" {
            virtio_probe(dt);
        }
    }
    for child in dt.children.iter() {
        walk_dt_node(child);
    }
}

fn virtio_probe(node: &Node) {
    if let Some(reg) = node.prop_raw("reg") {
        let paddr = reg.as_slice().read_be_u64(0).unwrap();
        let size = reg.as_slice().read_be_u64(8).unwrap();
        let vaddr = paddr;
        info!("walk dt addr={:#x}, size={:#x}", paddr, size);
        let header = unsafe { &mut *(vaddr as *mut VirtIOHeader) };
        info!(
            "Detected virtio device with vendor id {:#X}, device type {:?}",
            header.vendor_id(),
            header.device_type(),
        );
        info!("Device tree node {:?}", node);
        match header.device_type() {
            DeviceType::Block => virtio_blk(header),
            DeviceType::GPU => virtio_gpu(header),
            DeviceType::Input => virtio_input(header),
            DeviceType::Network => virtio_net(header),
            t => warn!("Unrecognized virtio device: {:?}", t),
        }
    }
}

fn virtio_blk(header: &'static mut VirtIOHeader) {
    let mut blk = VirtIOBlk::<HalImpl>::new(header).expect("failed to create blk driver");
    let mut input = vec![0xffu8; 512];
    let mut output = vec![0; 512];
    for i in 0..32 {
        for x in input.iter_mut() {
            *x = i as u8;
        }
        blk.write_block(i, &input).expect("failed to write");
        blk.read_block(i, &mut output).expect("failed to read");
        assert_eq!(input, output);
    }
    info!("virtio-blk test finished");
}

fn virtio_gpu(header: &'static mut VirtIOHeader) {
    let mut gpu = VirtIOGpu::<HalImpl>::new(header).expect("failed to create gpu driver");
    let fb = gpu.setup_framebuffer().expect("failed to get fb");
    for y in 0..768 {
        for x in 0..1024 {
            let idx = (y * 1024 + x) * 4;
            fb[idx] = x as u8;
            fb[idx + 1] = y as u8;
            fb[idx + 2] = (x + y) as u8;
        }
    }
    gpu.flush().expect("failed to flush");
    info!("virtio-gpu test finished");
}

fn virtio_input(header: &'static mut VirtIOHeader) {
    //let mut event_buf = [0u64; 32];
    let mut _input = VirtIOInput::<HalImpl>::new(header).expect("failed to create input driver");
    // loop {
    //     input.ack_interrupt().expect("failed to ack");
    //     info!("mouse: {:?}", input.mouse_xy());
    // }
    // TODO: handle external interrupt
}

fn virtio_net(header: &'static mut VirtIOHeader) {
    let mut net = VirtIONet::<HalImpl>::new(header).expect("failed to create net driver");
    let mut buf = [0u8; 0x100];
    let len = net.recv(&mut buf).expect("failed to recv");
    info!("recv: {:?}", &buf[..len]);
    net.send(&buf[..len]).expect("failed to send");
    info!("virtio-net test finished");
}

移植會遇到的雷

code
#![feature(default_alloc_error_handler)]
#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();

記得cargo.toml

code
buddy_system_allocator =  "0.6"

這邊主要嘗試把 virtio-drivers編譯成靜態連結,還不理功能是否正常。

最後我們下cargo build 我們會在相對應的平台找到生成的靜態連結檔案

一樣我們選01-helloRVOS 章節

libdouble_input.a -Wl,--gc-sections 記得在link階段進行連結

code
riscv32-unknown-elf-gcc -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall  -c -o start.o start.S
riscv32-unknown-elf-gcc -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall  -c -o kernel.o kernel.c
riscv32-unknown-elf-gcc -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall  -c -o uart.o uart.c
riscv32-unknown-elf-gcc -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall  -Ttext=0x80000000 -o os.elf start.o kernel.o uart.o  libdouble_input.a -Wl,--gc-sections 
riscv32-unknown-elf-objcopy -O binary os.elf os.bin

這邊比較注意的是我目前吧extern crate opensbi_rt; 移除,不然會出現重複 _start,這是位於這個opensbi_rt應該是寫驅動的作者應該也是開機載入跟程式綁再一起,目前前面階段是ok的,那麼我們在rust應該可以直接呼叫相對應的lib應該Ok(吧,這個問題以後再探討,當前主要任務是觸發用kernel.c再去呼叫rust寫的fucntion

這邊要注意又再改回然後預設起始位置是0x80000000 /riscv-operating-system-mooc/code/common.mk

code
CROSS_COMPILE = riscv32-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32imafdc -mabi=ilp32 -g -Wall 
# qemu-system-riscv64 \
#   --machine virt \
#   --nographic \
#   --bios default
QEMU = qemu-system-riscv32 
QFLAGS = --nographic -smp 1 -machine  virt -bios none
#QEMU = qemu-system-riscv32 
#QFLAGS = --nographic -smp 1 -machine  virt -bios fw_jump.elf  -device loader,file=test.bin,addr=0x80200000  -device #virtio-gpu-device 
GDB = gdb-multiarch
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump

double_input function

https://github.com/alexcrichton/rust-ffi-examples 相關靜態連結的範例,可以跟著改修改返回值

可以看到會稍微延遲在觸發第二個print

到這邊我們的c就可以call rust的fucntion,主要還是因為找不到可以有人專門在寫這部分的driver想說能不能移花接木一下進行呼叫,那麼我只要驅動的部分呼叫rust的就好了,這樣改可能可以成功,後面第三部可能要 riscv-operating-system-mooc移植到剛剛的opensbi的範例啟動,再來要考慮的是這樣方式的link是否有可能會產生衝突,因為rust的lib移除掉opensbi_rt不知正常啟用的話lib功能會不會有問題.

References