milk-v duo 开机引导进入 rust 程序

milk-v duo 还是去年买的,又是esp32 又是rp2040 的,搞搞lcd 又搞搞墨水屏 又弄弄 memory lcd,乱得狠,啥都想玩玩,玩不过来,最近抽空看看 这块duo 想裸机跑下rust 试试。

找了一圈总算是有些眉目,找到一些在 uboot 或 opensbi 后引导的方法,随即尝试了一下,成功通过串口打印出信息,记录一下。

主要两个部分,先用rust 加汇编构建出程序的bin文件,后在 duo-buildroot-sdk 中修改fsbl 中的构建 fip.bin 的.mk文件后 再构建fip.bin 。

目录

通过 rust + 汇编 创建自己的程序

修改 duo-buildroot-sdk 相关

通过 rust + 汇编 创建自己的程序

1、先用cargo 生成一个rust 项目,修改 config.toml 文件,设置target 和指定链接脚本

​
// .cargo/config.toml
[build]
target = "riscv64gc-unknown-none-elf"

[target.riscv64gc-unknown-none-elf]
rustflags = [
     "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
]
​

2、增加链接脚本 src/linker.ld 如下

OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80200000;
 
SECTIONS
{
    . = BASE_ADDRESS;
    skernel = .;
 
    stext = .;
    .text : {
        *(.text.entry)
        *(.text .text.*)
    }
 
    . = ALIGN(4K);
    etext = .;
    srodata = .;
    .rodata : {
        *(.rodata .rodata.*)
        *(.srodata .srodata.*)
    }
 
    . = ALIGN(4K);
    erodata = .;
    sdata = .;
    .data : {
        *(.data .data.*)
        *(.sdata .sdata.*)
    }
 
    . = ALIGN(4K);
    edata = .;
    .bss : {
        *(.bss.stack)
        sbss = .;
        *(.bss .bss.*)
        *(.sbss .sbss.*)
    }
 
    . = ALIGN(4K);
    ebss = .;
    ekernel = .;
 
    /DISCARD/ : {
        *(.eh_frame)
    }
}

3、修改main.rs 、增加 panic_handler ,panic_handler 内部没有做处理,但没有panic_handler 无法编译

#![no_std]
#![no_main]
 
use core::panic::PanicInfo;
 
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
 
use core::arch::global_asm;
global_asm!(include_str!("entry.asm"));
 
 
 
#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();
    loop {
        prints("hello rust in milkv duo \r\n".as_bytes());
    }
}
 
const UART0_THR: u32 = 0x04140000;
const UART0_LSR: u32 = 0x04140014;
 
fn prints(bytes:&[u8]){
    for byte in bytes {
        print(byte);
    }
}
 
fn print(byte: &u8) {
    unsafe {
        let state_ptr = UART0_LSR as *const u32;
        let ptr = UART0_THR as *mut u8;
 
        let mut state = 0;
        while state == 0 {
            state =  core::ptr::read_volatile(state_ptr) & 0x20 ;
        } 
 
        core::ptr::write_volatile(ptr, *byte); 
    }
}
 
fn clear_bss() {
    unsafe extern "C" {
        fn sbss();
        fn ebss();
    }
    (sbss as usize..ebss as usize).for_each(|a| unsafe { (a as *mut u8).write_volatile(0) });
}

4、创建 entry.asm 并加入如下代码, _start 通过 .global 导出符号以将其放置到编译后的二进制程序的最开始处,其中包含bl33相关的验证信息要保持位置正确。这段代码功能只是通过串口打印一段字符 ,输出字符串后跳转到 rust_main 进入到 rust 程序中 ,在最后定义了一段 栈区。

.equ UART0_THR,0x04140000
.equ UART0_LSR,0x04140014
 
    .section .text
    .global _start
_start:
    /* BL33 information */
    j real_start
    .balign 4
    .word 0x33334c42  /* b'BL33' */
    .word 0xdeadbeea  /* CKSUM */
    .word 0xdeadbeeb  /* SIZE */
    .quad 0x80200000  /* RUNADDR */
    .word 0xdeadbeec
    .balign 4
    j real_start
    .balign 4
    /* Information end */
 
real_start:
    la s0, str
1:
    lbu a0, (s0)
    beqz a0, exit
    jal ra, uart_send
    addi s0, s0, 1
    j 1b
 
exit:
    la sp, boot_stack_top
    call rust_main
    j exit
 
uart_send:
    /* Wait for tx idle */
    li t0, UART0_LSR
    lw t1, (t0)
    andi t1, t1, 0x20
    beqz t1, uart_send
    /* Send a char */
    li t0, UART0_THR
    sw a0, (t0)
    ret
 
.section .rodata
str: 
    .asciz "Hello Milkv-duo!\n"
 
 
.section .bss.stack
    .globl boot_stack_lower_bound
boot_stack_lower_bound:
    .space 4096 * 16
    .globl boot_stack_top
boot_stack_top:

修改 duo-buildroot-sdk 相关

1、修改 fsbl/make_helpers/fip.mk 下的 fip-all 构建目标,将其中的 –LOADER_2ND 指向自己的程序 ,如下   

--LOADER_2ND='/home/ubuntu/workspace/rust_boot/target/riscv64gc-unknown-none-elf/release/rust_boot.bin' \

2、执行 fip 构建 ,可以创建一个 build_fip.sh 保存如下内容,或直接执行这些命令,构建完成后会在 fsbl/build/cv1800b_milkv_duo_sd 中生成 fip.bin ,将这个复制到sd卡中再启动开发板则可以通过串口查看到我们的打印信息

#!/bin/bash
source device/milkv-duo-sd/boardconfig.sh
source build/milkvsetup.sh
defconfig cv1800b_milkv_duo_sd
build_fsbl

3、最后会持续打印一段字符串