diff --git a/.gitignore b/.gitignore index 348cd826..92db4fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ /target *.asm +!/**/m_entry*.asm *.bin !/rustsbi-qemu.bin diff --git a/Cargo.lock b/Cargo.lock index cea3274c..d1f5e0d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,16 +79,12 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "ch1" version = "0.0.1" -dependencies = [ - "sbi-rt", -] [[package]] name = "ch1-lab" version = "0.0.1" dependencies = [ "rcore-console", - "sbi-rt", ] [[package]] @@ -99,7 +95,6 @@ dependencies = [ "linker", "rcore-console", "riscv", - "sbi-rt", "syscall", ] diff --git a/ch1-lab/Cargo.toml b/ch1-lab/Cargo.toml index 32650db6..d4340f81 100644 --- a/ch1-lab/Cargo.toml +++ b/ch1-lab/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.1" edition = "2021" authors = ["YdrMaster "] +[features] +default = [] +nobios = [] + [dependencies] -sbi-rt = { version = "0.0.2", features = ["legacy"] } rcore-console = { path = "../console" } diff --git a/ch1-lab/README.md b/ch1-lab/README.md index ef03c04c..099896b1 100644 --- a/ch1-lab/README.md +++ b/ch1-lab/README.md @@ -2,16 +2,38 @@ 第一章实验的示例,展示如何依赖 `rcore_console` crate。 -在 [Cargo.toml](Cargo.toml#L9) 里添加: +在 [Cargo.toml](Cargo.toml#L12) 里添加: ```toml rcore_console = { path = "../rcore_console"} ``` -在 [main.rs](src/main.rs#L38) 里初始化: +在 [main.rs](src/main.rs#L47) 里初始化: ```rust rcore_console::init_console(&Console); +rcore_console::set_timestamp(timer::get_time_ms); +``` + +## 时间戳功能 + +本章实现了 `timer.rs` 模块,提供 `get_time_ms()` 函数用于获取当前时间(毫秒)。通过 `rcore_console::set_timestamp()` 注册后,所有 `println!` 输出都会自动在开头显示时间戳,格式为 `[ X ms]`。 + +## 运行方式 + +### SBI 模式(默认) +```bash +cargo qemu --ch 1 --lab +``` + +### nobios 模式 +```bash +cargo qemu --ch 1 --lab --nobios +``` + +### RV32 nobios 模式 +```bash +cargo qemu --ch 1 --lab --arch riscv32 --nobios ``` 后续的章节都可以这样依赖 `rcore_console`。 diff --git a/ch1-lab/build.rs b/ch1-lab/build.rs index fdf9f7d8..b225d5f0 100644 --- a/ch1-lab/build.rs +++ b/ch1-lab/build.rs @@ -1,13 +1,62 @@ fn main() { use std::{env, fs, path::PathBuf}; - let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); - fs::write(ld, LINKER).unwrap(); println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-link-arg=-T{}", ld.display()); -} + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_NOBIOS"); -const LINKER: &[u8] = b" + let nobios = env::var("CARGO_FEATURE_NOBIOS").is_ok(); + + let linker_script = if nobios { + // nobios mode: M-Mode entry at 0x80000000, then jump to S-Mode at 0x80200000 + r#" +OUTPUT_ARCH(riscv) +ENTRY(_m_start) +M_BASE_ADDRESS = 0x80000000; +S_BASE_ADDRESS = 0x80200000; + +SECTIONS { + . = M_BASE_ADDRESS; + + .text.m_entry : { + *(.text.m_entry) + } + + .text.m_trap : { + *(.text.m_trap) + } + + .bss.m_stack : { + *(.bss.m_stack) + } + + .bss.m_data : { + *(.bss.m_data) + } + + . = S_BASE_ADDRESS; + + .text : { + *(.text.entry) + *(.text .text.*) + } + .rodata : { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + } + .bss : { + *(.bss.uninit) + *(.bss .bss.*) + *(.sbss .sbss.*) + } +} +"# + } else { + // SBI mode: kernel at 0x80200000 + r#" OUTPUT_ARCH(riscv) SECTIONS { .text 0x80200000 : { @@ -27,4 +76,11 @@ SECTIONS { *(.bss .bss.*) *(.sbss .sbss.*) } -}"; +} +"# + }; + + let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); + fs::write(ld, linker_script).unwrap(); + println!("cargo:rustc-link-arg=-T{}", ld.display()); +} diff --git a/ch1-lab/src/m_entry_rv32.asm b/ch1-lab/src/m_entry_rv32.asm new file mode 100644 index 00000000..8fa8c04e --- /dev/null +++ b/ch1-lab/src/m_entry_rv32.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV32) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -64 + + # Save registers (RV32: use sw instead of sd) + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw a0, 16(sp) + sw a1, 20(sp) + sw a2, 24(sp) + sw a3, 28(sp) + sw a4, 32(sp) + sw a5, 36(sp) + sw a6, 40(sp) + sw a7, 44(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw a0, 16(sp) + lw a1, 20(sp) + lw a2, 24(sp) + lw a3, 28(sp) + lw a4, 32(sp) + lw a5, 36(sp) + lw a6, 40(sp) + lw a7, 44(sp) + + addi sp, sp, 64 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch1-lab/src/m_entry_rv64.asm b/ch1-lab/src/m_entry_rv64.asm new file mode 100644 index 00000000..c16a1b04 --- /dev/null +++ b/ch1-lab/src/m_entry_rv64.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV64) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -128 + + # Save registers + sd ra, 0(sp) + sd t0, 8(sp) + sd t1, 16(sp) + sd t2, 24(sp) + sd a0, 32(sp) + sd a1, 40(sp) + sd a2, 48(sp) + sd a3, 56(sp) + sd a4, 64(sp) + sd a5, 72(sp) + sd a6, 80(sp) + sd a7, 88(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + ld ra, 0(sp) + ld t0, 8(sp) + ld t1, 16(sp) + ld t2, 24(sp) + ld a0, 32(sp) + ld a1, 40(sp) + ld a2, 48(sp) + ld a3, 56(sp) + ld a4, 64(sp) + ld a5, 72(sp) + ld a6, 80(sp) + ld a7, 88(sp) + + addi sp, sp, 128 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch1-lab/src/main.rs b/ch1-lab/src/main.rs index 5cde327b..92a3597a 100644 --- a/ch1-lab/src/main.rs +++ b/ch1-lab/src/main.rs @@ -5,7 +5,18 @@ #[macro_use] extern crate rcore_console; -use sbi_rt::*; +mod sbi; +mod timer; + +#[cfg(feature = "nobios")] +mod msbi; + +// nobios 模式下引入 M-Mode 入口汇编 +#[cfg(all(feature = "nobios", target_arch = "riscv64"))] +core::arch::global_asm!(include_str!("m_entry_rv64.asm")); + +#[cfg(all(feature = "nobios", target_arch = "riscv32"))] +core::arch::global_asm!(include_str!("m_entry_rv32.asm")); /// Supervisor 汇编入口。 /// @@ -34,13 +45,14 @@ unsafe extern "C" fn _start() -> ! { extern "C" fn rust_main() -> ! { // 初始化 `console` rcore_console::init_console(&Console); + // 设置时间戳函数 + rcore_console::set_timestamp(timer::get_time_ms); // 设置日志级别 rcore_console::set_log_level(option_env!("LOG")); // 测试各种打印 rcore_console::test_log(); - system_reset(Shutdown, NoReason); - unreachable!() + sbi::shutdown(false) } /// 将传给 `console` 的控制台对象。 @@ -51,8 +63,7 @@ struct Console; /// 为 `Console` 实现 `console::Console` trait。 impl rcore_console::Console for Console { fn put_char(&self, c: u8) { - #[allow(deprecated)] - legacy::console_putchar(c as _); + sbi::console_putchar(c); } } @@ -60,6 +71,5 @@ impl rcore_console::Console for Console { #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("{info}"); - system_reset(Shutdown, SystemFailure); - loop {} + sbi::shutdown(true) } diff --git a/ch1-lab/src/msbi.rs b/ch1-lab/src/msbi.rs new file mode 100644 index 00000000..697bea65 --- /dev/null +++ b/ch1-lab/src/msbi.rs @@ -0,0 +1,143 @@ +//! M-Mode SBI 实现 +//! +//! 在 nobios 模式下,提供一个最小的 SBI 实现,处理 S-Mode 的 ecall。 + +/// QEMU virt UART 基地址 +const UART_BASE: usize = 0x1000_0000; + +/// UART 操作 (16550 兼容) +mod uart { + use super::UART_BASE; + + const THR: usize = UART_BASE; // Transmit Holding Register + const LSR: usize = UART_BASE + 5; // Line Status Register + + /// 检查 UART 是否准备好发送 + #[inline] + fn is_tx_ready() -> bool { + unsafe { + let lsr = (LSR as *const u8).read_volatile(); + (lsr & 0x20) != 0 // THRE bit + } + } + + /// 写入一个字节到 UART + pub fn putchar(c: u8) { + while !is_tx_ready() {} + unsafe { + (THR as *mut u8).write_volatile(c); + } + } +} + +/// SBI Extension IDs +mod eid { + pub const LEGACY_CONSOLE_PUTCHAR: usize = 0x01; + pub const LEGACY_SHUTDOWN: usize = 0x08; + pub const BASE: usize = 0x10; + pub const SRST: usize = 0x53525354; +} + +/// SBI 错误码 +mod error { + pub const SUCCESS: isize = 0; + pub const ERR_NOT_SUPPORTED: isize = -2; +} + +/// SBI 返回值 +#[repr(C)] +pub struct SbiRet { + pub error: isize, + pub value: usize, +} + +impl SbiRet { + fn success(value: usize) -> Self { + SbiRet { error: error::SUCCESS, value } + } + + fn not_supported() -> Self { + SbiRet { error: error::ERR_NOT_SUPPORTED, value: 0 } + } +} + +/// 处理 legacy console putchar (EID 0x01) +fn handle_console_putchar(c: usize) -> SbiRet { + uart::putchar(c as u8); + SbiRet::success(0) +} + +/// 处理系统复位 +fn handle_system_reset(reset_reason: usize) -> SbiRet { + const VIRT_TEST: usize = 0x10_0000; + const FINISHER_PASS: u32 = 0x5555; + const FINISHER_FAIL: u32 = 0x3333; + + let code = if reset_reason == 0 { FINISHER_PASS } else { FINISHER_FAIL }; + unsafe { + (VIRT_TEST as *mut u32).write_volatile(code); + } + loop {} +} + +/// 处理 legacy shutdown (EID 0x08) +fn handle_legacy_shutdown() -> SbiRet { + handle_system_reset(0) +} + +/// 处理 SBI base 扩展 (EID 0x10) +fn handle_base(fid: usize) -> SbiRet { + match fid { + 0 => SbiRet::success(2), // spec_version: SBI 0.2 + 1 => SbiRet::success(0), // impl_id + 2 => SbiRet::success(1), // impl_version + 3 => SbiRet::success(1), // probe_extension + 4 => SbiRet::success(0), // mvendorid + 5 => SbiRet::success(0), // marchid + 6 => SbiRet::success(0), // mimpid + _ => SbiRet::not_supported(), + } +} + +/// M-Mode trap handler,由汇编调用 +/// +/// 参数通过寄存器传递: +/// - a0-a5: SBI 调用参数 +/// - a6: FID (function ID) +/// - a7: EID (extension ID) +#[unsafe(no_mangle)] +pub extern "C" fn m_trap_handler( + a0: usize, + a1: usize, + _a2: usize, + _a3: usize, + _a4: usize, + _a5: usize, + fid: usize, + eid: usize, +) -> SbiRet { + // 检查 mcause,只处理来自 S-Mode 的 ecall (cause = 9) + let mcause: usize; + unsafe { + core::arch::asm!("csrr {}, mcause", out(reg) mcause); + } + + if mcause != 9 { + return SbiRet::not_supported(); + } + + // 根据 EID 处理 SBI 调用 + match eid { + eid::LEGACY_CONSOLE_PUTCHAR => handle_console_putchar(a0), + eid::LEGACY_SHUTDOWN => handle_legacy_shutdown(), + eid::BASE => handle_base(fid), + eid::SRST => { + if fid == 0 { + handle_system_reset(a1) + } else { + SbiRet::not_supported() + } + } + _ => SbiRet::not_supported(), + } +} diff --git a/ch1-lab/src/sbi.rs b/ch1-lab/src/sbi.rs new file mode 100644 index 00000000..e7946c5d --- /dev/null +++ b/ch1-lab/src/sbi.rs @@ -0,0 +1,45 @@ +//! SBI 服务调用封装 +//! +//! 不依赖 sbi_rt,直接通过 ecall 调用 SBI 服务。 + +use core::arch::asm; + +/// SBI 调用封装 +#[inline(always)] +fn sbi_call(eid: usize, fid: usize, arg0: usize, arg1: usize, arg2: usize) -> (isize, usize) { + let error: isize; + let value: usize; + unsafe { + asm!( + "ecall", + inlateout("a0") arg0 => error, + inlateout("a1") arg1 => value, + in("a2") arg2, + in("a6") fid, + in("a7") eid, + ); + } + (error, value) +} + +/// SBI Extension IDs +const SBI_CONSOLE_PUTCHAR: usize = 0x01; +const SBI_SHUTDOWN: usize = 0x08; +const SBI_SRST: usize = 0x53525354; + +/// 输出一个字符到控制台 +pub fn console_putchar(c: u8) { + sbi_call(SBI_CONSOLE_PUTCHAR, 0, c as usize, 0, 0); +} + +/// 关机 +pub fn shutdown(failure: bool) -> ! { + // 尝试 SRST 扩展 (SBI v0.3+) + let reason = if failure { 1usize } else { 0usize }; + sbi_call(SBI_SRST, 0, 0, reason, 0); + + // 回退到 legacy shutdown + sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); + + loop {} +} diff --git a/ch1-lab/src/timer.rs b/ch1-lab/src/timer.rs new file mode 100644 index 00000000..ecbefb3b --- /dev/null +++ b/ch1-lab/src/timer.rs @@ -0,0 +1,46 @@ +//! Timer module for getting system time +//! +//! Uses RISC-V time CSR +//! Supports both RV32 and RV64 + +/// QEMU virt platform clock frequency (10 MHz) +const CLOCK_FREQ: u64 = 10_000_000; + +/// Get current time in clock cycles +#[inline] +pub fn get_time() -> u64 { + #[cfg(target_pointer_width = "64")] + { + let time: u64; + unsafe { + core::arch::asm!("rdtime {}", out(reg) time); + } + time + } + + #[cfg(target_pointer_width = "32")] + { + // RV32: need to read timeh and time separately + // and handle the case where time overflows between reads + loop { + let hi: u32; + let lo: u32; + let hi2: u32; + unsafe { + core::arch::asm!("rdtimeh {}", out(reg) hi); + core::arch::asm!("rdtime {}", out(reg) lo); + core::arch::asm!("rdtimeh {}", out(reg) hi2); + } + // If hi changed between reads, try again + if hi == hi2 { + return ((hi as u64) << 32) | (lo as u64); + } + } + } +} + +/// Get current time in milliseconds +#[inline] +pub fn get_time_ms() -> usize { + (get_time() / (CLOCK_FREQ / 1000)) as usize +} diff --git a/ch1/Cargo.toml b/ch1/Cargo.toml index cad15d2e..512f9f16 100644 --- a/ch1/Cargo.toml +++ b/ch1/Cargo.toml @@ -4,5 +4,8 @@ version = "0.0.1" edition = "2021" authors = ["YdrMaster "] +[features] +default = [] +nobios = [] + [dependencies] -sbi-rt = { version = "0.0.2", features = ["legacy"] } diff --git a/ch1/README.md b/ch1/README.md index b563f0e2..a3cd45dd 100644 --- a/ch1/README.md +++ b/ch1/README.md @@ -2,26 +2,58 @@ 第一章旨在展示一个尽量简单的**特权态裸机应用程序**: -- 只有[一个文件](src/main.rs); +- 核心逻辑在 [main.rs](src/main.rs); - 链接脚本在 [build.rs](build.rs),以免增加依赖; -- 只依赖 [*sbi-rt*](https://crates.io/crates/sbi-rt) 以获得封装好的 SBI 调用; -- 这个程序被 SEE 引导,工作在 S 态; +- 为了支持 nobios 模式和 RV32 架构,还包含: + - `sbi.rs` - SBI 服务调用实现 + - `msbi.rs` - M-Mode SBI 处理器(nobios 模式) + - `m_entry_rv64.asm` / `m_entry_rv32.asm` - M-Mode 入口汇编代码(nobios 模式) +- 支持两种启动模式: + - **SBI 模式**(默认):程序被 SEE 引导,工作在 S 态; + - **nobios 模式**:内核直接启动,包含 M-Mode 入口代码,自行处理 M-Mode 到 S-Mode 的切换; +- 支持两种架构: + - **RV64**(默认):64 位 RISC-V 架构(riscv64gc) + - **RV32**:32 位 RISC-V 架构(riscv32imac),仅支持 nobios 模式 - 这个程序不需要环境: - 从汇编进入并为 Rust 准备栈; - - 依赖 SBI 提供的 `legacy::console_putchar` 打印 `Hello, world!`; - - 依赖 SBI 提供的 `system_reset` 调用关机; + - 依赖 SBI 提供的 `console_putchar` 打印 `Hello, world!`; + - 依赖 SBI 提供的 `shutdown` 调用关机; 它不配被称作一个操作系统,因为它没有操作(硬件),也不构造(执行用户程序的)系统; -## sbi-rt +## 运行方式 -这个库就是 kernel 的 libc。 +### SBI 模式(默认) +```bash +cargo qemu --ch 1 +``` + +### nobios 模式 +```bash +cargo qemu --ch 1 --nobios +``` + +### RV32 nobios 模式 +```bash +cargo qemu --ch 1 --arch riscv32 --nobios +``` + +## SBI 实现 + +本章使用自定义的 SBI 实现(`sbi.rs`)。 -它根据 [SBI 标准](https://github.com/riscv-non-isa/riscv-sbi-doc)封装了一系列函数,通过 `ecall` 命令调用 SBI 提供的响应功能。本章需要使用 `legacy::console_putchar` 向控制台打印字符,以及 `system_reset` 在程序运行完后关机。 +- **SBI 模式**:通过 `ecall` 调用 RustSBI 提供的服务 +- **nobios 模式**:通过 `ecall` 触发 M-Mode 陷阱,由 `msbi.rs` 中的 `m_trap_handler` 处理 SBI 调用 + +在 nobios 模式下,`msbi.rs` 实现了最小化的 M-Mode SBI,提供: +- `console_putchar`:通过 UART MMIO 输出字符 +- `shutdown`:通过 QEMU 测试设备关机 ## 定制链接脚本 -build.rs 的用法见[文档](https://doc.rust-lang.org/cargo/reference/build-scripts.html)。这个定制的链接脚本是特殊的: +build.rs 的用法见[文档](https://doc.rust-lang.org/cargo/reference/build-scripts.html)。根据是否启用 `nobios` feature,会生成不同的链接脚本: + +### SBI 模式链接脚本 ```ld OUTPUT_ARCH(riscv) @@ -46,11 +78,26 @@ SECTIONS { } ``` +### nobios 模式链接脚本 + +nobios 模式在 `0x80000000` 处开始,包含 M-Mode 入口代码、陷阱处理、栈和数据段,然后跳转到 `0x80200000` 处开始执行 S-Mode 内核代码。 + 1. 为了被引导,它的 `.text` 在最前面。一般是 `.rodata` 在最前面。`.text` 的最前面是 `.text.entry`,有且只有一个汇编入口放在这个节,实现引导; 2. 正常情况下,裸机应用程序需要清除自己的 `.bss` 节,所以需要定义全局符号以便动态定位 `.bss`。但这一章的程序并不依赖 清空的 `.bss`,所以没有导出符号。`.bss` 本身仍然需要,因为栈会放在里面。 ## 工作流程解读 +### SBI 模式 + 1. SBI 初始化完成后,将固定跳转到 0x8020_0000 地址; 2. 根据链接脚本,汇编入口函数被放置在这个地址。它叫做 `_start`,这个名字是特殊的!GNU LD 及兼容其脚本的链接器会将这个名字认为是默认的入口,否则需要指定。这个函数是一个 rust 裸函数([`#[naked]`](https://github.com/rust-lang/rust/issues/90957)),编译器不会为它添加任何序言和尾声,因此可以在没有栈的情况下执行。它将栈指针指向预留的栈空间,然后跳转到 `rust_main` 函数; 3. `rust_main` 函数在一个最简单的循环打印调用 sbi 打印 `Hello, world!` 字符串,然后关机。 + +### nobios 模式 + +1. QEMU 使用 `-bios none` 参数,直接从 `0x80000000` 加载内核; +2. M-Mode 入口代码(`_m_start`,在 `m_entry_rv64.asm` 或 `m_entry_rv32.asm` 中)在 `0x80000000` 执行; +3. M-Mode 设置 PMP(物理内存保护)、委托陷阱、配置 CSR(控制状态寄存器),然后通过 `mret` 跳转到 S-Mode; +4. S-Mode 内核在 `0x80200000` 开始执行,入口函数为 `_start`; +5. 当 S-Mode 调用 SBI 服务时,通过 `ecall` 触发 M-Mode 陷阱; +6. M-Mode 陷阱处理器(`m_trap_handler`,在 `msbi.rs` 中)处理 SBI 调用并返回结果。 diff --git a/ch1/build.rs b/ch1/build.rs index fdf9f7d8..b225d5f0 100644 --- a/ch1/build.rs +++ b/ch1/build.rs @@ -1,13 +1,62 @@ fn main() { use std::{env, fs, path::PathBuf}; - let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); - fs::write(ld, LINKER).unwrap(); println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-link-arg=-T{}", ld.display()); -} + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_NOBIOS"); -const LINKER: &[u8] = b" + let nobios = env::var("CARGO_FEATURE_NOBIOS").is_ok(); + + let linker_script = if nobios { + // nobios mode: M-Mode entry at 0x80000000, then jump to S-Mode at 0x80200000 + r#" +OUTPUT_ARCH(riscv) +ENTRY(_m_start) +M_BASE_ADDRESS = 0x80000000; +S_BASE_ADDRESS = 0x80200000; + +SECTIONS { + . = M_BASE_ADDRESS; + + .text.m_entry : { + *(.text.m_entry) + } + + .text.m_trap : { + *(.text.m_trap) + } + + .bss.m_stack : { + *(.bss.m_stack) + } + + .bss.m_data : { + *(.bss.m_data) + } + + . = S_BASE_ADDRESS; + + .text : { + *(.text.entry) + *(.text .text.*) + } + .rodata : { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + } + .bss : { + *(.bss.uninit) + *(.bss .bss.*) + *(.sbss .sbss.*) + } +} +"# + } else { + // SBI mode: kernel at 0x80200000 + r#" OUTPUT_ARCH(riscv) SECTIONS { .text 0x80200000 : { @@ -27,4 +76,11 @@ SECTIONS { *(.bss .bss.*) *(.sbss .sbss.*) } -}"; +} +"# + }; + + let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); + fs::write(ld, linker_script).unwrap(); + println!("cargo:rustc-link-arg=-T{}", ld.display()); +} diff --git a/ch1/src/m_entry_rv32.asm b/ch1/src/m_entry_rv32.asm new file mode 100644 index 00000000..8fa8c04e --- /dev/null +++ b/ch1/src/m_entry_rv32.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV32) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -64 + + # Save registers (RV32: use sw instead of sd) + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw a0, 16(sp) + sw a1, 20(sp) + sw a2, 24(sp) + sw a3, 28(sp) + sw a4, 32(sp) + sw a5, 36(sp) + sw a6, 40(sp) + sw a7, 44(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw a0, 16(sp) + lw a1, 20(sp) + lw a2, 24(sp) + lw a3, 28(sp) + lw a4, 32(sp) + lw a5, 36(sp) + lw a6, 40(sp) + lw a7, 44(sp) + + addi sp, sp, 64 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch1/src/m_entry_rv64.asm b/ch1/src/m_entry_rv64.asm new file mode 100644 index 00000000..c16a1b04 --- /dev/null +++ b/ch1/src/m_entry_rv64.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV64) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -128 + + # Save registers + sd ra, 0(sp) + sd t0, 8(sp) + sd t1, 16(sp) + sd t2, 24(sp) + sd a0, 32(sp) + sd a1, 40(sp) + sd a2, 48(sp) + sd a3, 56(sp) + sd a4, 64(sp) + sd a5, 72(sp) + sd a6, 80(sp) + sd a7, 88(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + ld ra, 0(sp) + ld t0, 8(sp) + ld t1, 16(sp) + ld t2, 24(sp) + ld a0, 32(sp) + ld a1, 40(sp) + ld a2, 48(sp) + ld a3, 56(sp) + ld a4, 64(sp) + ld a5, 72(sp) + ld a6, 80(sp) + ld a7, 88(sp) + + addi sp, sp, 128 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch1/src/main.rs b/ch1/src/main.rs index 5abda478..06529d69 100644 --- a/ch1/src/main.rs +++ b/ch1/src/main.rs @@ -2,6 +2,18 @@ #![no_main] #![deny(warnings)] +mod sbi; + +#[cfg(feature = "nobios")] +mod msbi; + +// nobios 模式下引入 M-Mode 入口汇编 +#[cfg(all(feature = "nobios", target_arch = "riscv64"))] +core::arch::global_asm!(include_str!("m_entry_rv64.asm")); + +#[cfg(all(feature = "nobios", target_arch = "riscv32"))] +core::arch::global_asm!(include_str!("m_entry_rv32.asm")); + /// Supervisor 汇编入口。 /// /// 设置栈并跳转到 Rust。 @@ -27,19 +39,14 @@ unsafe extern "C" fn _start() -> ! { /// /// 打印 `Hello, World!`,然后关机。 extern "C" fn rust_main() -> ! { - use sbi_rt::*; - for c in b"Hello, world!" { - #[allow(deprecated)] - legacy::console_putchar(*c as _); + for c in b"Hello, world!\n" { + sbi::console_putchar(*c); } - system_reset(Shutdown, NoReason); - unreachable!() + sbi::shutdown(false) } /// Rust 异常处理函数,以异常方式关机。 #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { - use sbi_rt::*; - system_reset(Shutdown, SystemFailure); - loop {} + sbi::shutdown(true) } diff --git a/ch1/src/msbi.rs b/ch1/src/msbi.rs new file mode 100644 index 00000000..697bea65 --- /dev/null +++ b/ch1/src/msbi.rs @@ -0,0 +1,143 @@ +//! M-Mode SBI 实现 +//! +//! 在 nobios 模式下,提供一个最小的 SBI 实现,处理 S-Mode 的 ecall。 + +/// QEMU virt UART 基地址 +const UART_BASE: usize = 0x1000_0000; + +/// UART 操作 (16550 兼容) +mod uart { + use super::UART_BASE; + + const THR: usize = UART_BASE; // Transmit Holding Register + const LSR: usize = UART_BASE + 5; // Line Status Register + + /// 检查 UART 是否准备好发送 + #[inline] + fn is_tx_ready() -> bool { + unsafe { + let lsr = (LSR as *const u8).read_volatile(); + (lsr & 0x20) != 0 // THRE bit + } + } + + /// 写入一个字节到 UART + pub fn putchar(c: u8) { + while !is_tx_ready() {} + unsafe { + (THR as *mut u8).write_volatile(c); + } + } +} + +/// SBI Extension IDs +mod eid { + pub const LEGACY_CONSOLE_PUTCHAR: usize = 0x01; + pub const LEGACY_SHUTDOWN: usize = 0x08; + pub const BASE: usize = 0x10; + pub const SRST: usize = 0x53525354; +} + +/// SBI 错误码 +mod error { + pub const SUCCESS: isize = 0; + pub const ERR_NOT_SUPPORTED: isize = -2; +} + +/// SBI 返回值 +#[repr(C)] +pub struct SbiRet { + pub error: isize, + pub value: usize, +} + +impl SbiRet { + fn success(value: usize) -> Self { + SbiRet { error: error::SUCCESS, value } + } + + fn not_supported() -> Self { + SbiRet { error: error::ERR_NOT_SUPPORTED, value: 0 } + } +} + +/// 处理 legacy console putchar (EID 0x01) +fn handle_console_putchar(c: usize) -> SbiRet { + uart::putchar(c as u8); + SbiRet::success(0) +} + +/// 处理系统复位 +fn handle_system_reset(reset_reason: usize) -> SbiRet { + const VIRT_TEST: usize = 0x10_0000; + const FINISHER_PASS: u32 = 0x5555; + const FINISHER_FAIL: u32 = 0x3333; + + let code = if reset_reason == 0 { FINISHER_PASS } else { FINISHER_FAIL }; + unsafe { + (VIRT_TEST as *mut u32).write_volatile(code); + } + loop {} +} + +/// 处理 legacy shutdown (EID 0x08) +fn handle_legacy_shutdown() -> SbiRet { + handle_system_reset(0) +} + +/// 处理 SBI base 扩展 (EID 0x10) +fn handle_base(fid: usize) -> SbiRet { + match fid { + 0 => SbiRet::success(2), // spec_version: SBI 0.2 + 1 => SbiRet::success(0), // impl_id + 2 => SbiRet::success(1), // impl_version + 3 => SbiRet::success(1), // probe_extension + 4 => SbiRet::success(0), // mvendorid + 5 => SbiRet::success(0), // marchid + 6 => SbiRet::success(0), // mimpid + _ => SbiRet::not_supported(), + } +} + +/// M-Mode trap handler,由汇编调用 +/// +/// 参数通过寄存器传递: +/// - a0-a5: SBI 调用参数 +/// - a6: FID (function ID) +/// - a7: EID (extension ID) +#[unsafe(no_mangle)] +pub extern "C" fn m_trap_handler( + a0: usize, + a1: usize, + _a2: usize, + _a3: usize, + _a4: usize, + _a5: usize, + fid: usize, + eid: usize, +) -> SbiRet { + // 检查 mcause,只处理来自 S-Mode 的 ecall (cause = 9) + let mcause: usize; + unsafe { + core::arch::asm!("csrr {}, mcause", out(reg) mcause); + } + + if mcause != 9 { + return SbiRet::not_supported(); + } + + // 根据 EID 处理 SBI 调用 + match eid { + eid::LEGACY_CONSOLE_PUTCHAR => handle_console_putchar(a0), + eid::LEGACY_SHUTDOWN => handle_legacy_shutdown(), + eid::BASE => handle_base(fid), + eid::SRST => { + if fid == 0 { + handle_system_reset(a1) + } else { + SbiRet::not_supported() + } + } + _ => SbiRet::not_supported(), + } +} diff --git a/ch1/src/sbi.rs b/ch1/src/sbi.rs new file mode 100644 index 00000000..e7946c5d --- /dev/null +++ b/ch1/src/sbi.rs @@ -0,0 +1,45 @@ +//! SBI 服务调用封装 +//! +//! 不依赖 sbi_rt,直接通过 ecall 调用 SBI 服务。 + +use core::arch::asm; + +/// SBI 调用封装 +#[inline(always)] +fn sbi_call(eid: usize, fid: usize, arg0: usize, arg1: usize, arg2: usize) -> (isize, usize) { + let error: isize; + let value: usize; + unsafe { + asm!( + "ecall", + inlateout("a0") arg0 => error, + inlateout("a1") arg1 => value, + in("a2") arg2, + in("a6") fid, + in("a7") eid, + ); + } + (error, value) +} + +/// SBI Extension IDs +const SBI_CONSOLE_PUTCHAR: usize = 0x01; +const SBI_SHUTDOWN: usize = 0x08; +const SBI_SRST: usize = 0x53525354; + +/// 输出一个字符到控制台 +pub fn console_putchar(c: u8) { + sbi_call(SBI_CONSOLE_PUTCHAR, 0, c as usize, 0, 0); +} + +/// 关机 +pub fn shutdown(failure: bool) -> ! { + // 尝试 SRST 扩展 (SBI v0.3+) + let reason = if failure { 1usize } else { 0usize }; + sbi_call(SBI_SRST, 0, 0, reason, 0); + + // 回退到 legacy shutdown + sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); + + loop {} +} diff --git a/ch2/Cargo.toml b/ch2/Cargo.toml index 9bb20dec..ddad7215 100644 --- a/ch2/Cargo.toml +++ b/ch2/Cargo.toml @@ -4,8 +4,11 @@ version = "0.0.1" edition = "2021" authors = ["YdrMaster "] +[features] +default = [] +nobios = [] + [dependencies] -sbi-rt = { version = "0.0.2", features = ["legacy"] } riscv = "0.10.1" linker = { path = "../linker" } diff --git a/ch2/README.md b/ch2/README.md index a1efcb4a..b164d79f 100644 --- a/ch2/README.md +++ b/ch2/README.md @@ -2,7 +2,26 @@ 第二章实现的操作系统被称为“邓氏鱼”。邓氏鱼是这样一种生物:在它生存的年代,它最大、最强,非常有效的颌、眼睛和尾巴提供了充分强大的咬合力、视力和游泳能力,这些结构在泥盆纪都堪称先进。然而,在泥盆纪末灭绝事件中,以邓氏鱼为代表的所有盾皮鱼类都迅速而彻底地灭绝了。在今天的海洋里,我们没有发现什么与之在演化上接近的生物。演化就是这样短视。一时非常有效的设计,可能随着环境微妙的一点改变就完全不适用了,设计也是一样。 -因此,在迈向更复杂的系统之前,就让我们任性地实现一个保持简单的单道批处理系统吧。如你所见,第二章依然只有一个 [main.rs](src/main.rs) 文件,而且算上注释也不到 150 行(当然,它使用了更多的依赖)。重点关注 [`syscall`](src/main.rs#L29) 和 [`Context`](src/main.rs#L36) 的用法,这就是邓氏鱼的大脑和脊椎,这些真正成功的结构是不会随着某些物种的灭绝而消失的。 +因此,在迈向更复杂的系统之前,就让我们任性地实现一个保持简单的单道批处理系统吧。如你所见,第二章的核心逻辑依然集中在 [main.rs](src/main.rs) 文件中,算上注释也不到 160 行(当然,它使用了更多的依赖)。 + +重点关注 [`syscall`](src/main.rs#L43) 和 [`Context`](src/main.rs#L52) 的用法,这就是邓氏鱼的大脑和脊椎,这些真正成功的结构是不会随着某些物种的灭绝而消失的。 + +## 运行方式 + +### SBI 模式(默认) +```bash +cargo qemu --ch 2 +``` + +### nobios 模式 +```bash +cargo qemu --ch 2 --nobios +``` + +### RV32 nobios 模式 +```bash +cargo qemu --ch 2 --arch riscv32 --nobios +``` > **注意** 如果修改了用户程序,需要 `cargo clean` 重新编译才能刷新,不知道怎么优化。 diff --git a/ch2/build.rs b/ch2/build.rs index 0bac1dc5..56ad8bc8 100644 --- a/ch2/build.rs +++ b/ch2/build.rs @@ -1,11 +1,20 @@ fn main() { use std::{env, fs, path::PathBuf}; - let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); - fs::write(ld, linker::SCRIPT).unwrap(); - println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=LOG"); println!("cargo:rerun-if-env-changed=APP_ASM"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_NOBIOS"); + + let nobios = env::var("CARGO_FEATURE_NOBIOS").is_ok(); + + let linker_script = if nobios { + linker::NOBIOS_SCRIPT + } else { + linker::SCRIPT + }; + + let ld = &PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("linker.ld"); + fs::write(ld, linker_script).unwrap(); println!("cargo:rustc-link-arg=-T{}", ld.display()); } diff --git a/ch2/src/m_entry_rv32.asm b/ch2/src/m_entry_rv32.asm new file mode 100644 index 00000000..8fa8c04e --- /dev/null +++ b/ch2/src/m_entry_rv32.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV32) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -64 + + # Save registers (RV32: use sw instead of sd) + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw a0, 16(sp) + sw a1, 20(sp) + sw a2, 24(sp) + sw a3, 28(sp) + sw a4, 32(sp) + sw a5, 36(sp) + sw a6, 40(sp) + sw a7, 44(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw a0, 16(sp) + lw a1, 20(sp) + lw a2, 24(sp) + lw a3, 28(sp) + lw a4, 32(sp) + lw a5, 36(sp) + lw a6, 40(sp) + lw a7, 44(sp) + + addi sp, sp, 64 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch2/src/m_entry_rv64.asm b/ch2/src/m_entry_rv64.asm new file mode 100644 index 00000000..c16a1b04 --- /dev/null +++ b/ch2/src/m_entry_rv64.asm @@ -0,0 +1,102 @@ +# M-Mode entry point for -bios none boot (RV64) +# This code runs at 0x80000000 in M-Mode when QEMU starts with -bios none + + .section .text.m_entry + .globl _m_start +_m_start: + # Set up M-Mode stack + la sp, m_stack_top + # Save M-Mode sp to mscratch for trap handler + csrw mscratch, sp + + # Set mstatus: MPP=01 (S-Mode), MPIE=1 + li t0, (1 << 11) | (1 << 7) + csrw mstatus, t0 + + # Set mepc to S-Mode entry point + la t0, _start + csrw mepc, t0 + + # Set mtvec to M-Mode trap handler + la t0, m_trap_vector + csrw mtvec, t0 + + # Delegate interrupts and exceptions to S-Mode (except ecall from S-Mode) + li t0, 0xffff + csrw mideleg, t0 + li t0, 0xffff + li t1, (1 << 9) # Environment call from S-mode + not t1, t1 + and t0, t0, t1 + csrw medeleg, t0 + + # Set up PMP to allow S-Mode full access + li t0, -1 + csrw pmpaddr0, t0 + li t0, 0x0f # TOR, RWX + csrw pmpcfg0, t0 + + # Enable S-Mode to access counters + li t0, -1 + csrw mcounteren, t0 + + # Jump to S-Mode + mret + + .section .text.m_trap + .globl m_trap_vector + .align 4 +m_trap_vector: + # Simple trap handler: handle ecall from S-Mode + csrrw sp, mscratch, sp + addi sp, sp, -128 + + # Save registers + sd ra, 0(sp) + sd t0, 8(sp) + sd t1, 16(sp) + sd t2, 24(sp) + sd a0, 32(sp) + sd a1, 40(sp) + sd a2, 48(sp) + sd a3, 56(sp) + sd a4, 64(sp) + sd a5, 72(sp) + sd a6, 80(sp) + sd a7, 88(sp) + + # Call Rust trap handler + call m_trap_handler + + # Advance mepc past ecall instruction + csrr t0, mepc + addi t0, t0, 4 + csrw mepc, t0 + + # Restore registers + ld ra, 0(sp) + ld t0, 8(sp) + ld t1, 16(sp) + ld t2, 24(sp) + ld a0, 32(sp) + ld a1, 40(sp) + ld a2, 48(sp) + ld a3, 56(sp) + ld a4, 64(sp) + ld a5, 72(sp) + ld a6, 80(sp) + ld a7, 88(sp) + + addi sp, sp, 128 + csrrw sp, mscratch, sp + mret + + .section .bss.m_stack + .globl m_stack_lower_bound +m_stack_lower_bound: + .space 4096 * 4 + .globl m_stack_top +m_stack_top: + + .section .bss.m_data + .space 64 diff --git a/ch2/src/main.rs b/ch2/src/main.rs index 9b9d1651..d2d1811c 100644 --- a/ch2/src/main.rs +++ b/ch2/src/main.rs @@ -5,13 +5,25 @@ #[macro_use] extern crate rcore_console; +mod sbi; +mod timer; + +#[cfg(feature = "nobios")] +mod msbi; + use impls::{Console, SyscallContext}; use kernel_context::LocalContext; use rcore_console::log; use riscv::register::*; -use sbi_rt::*; use syscall::{Caller, SyscallId}; +// nobios 模式下引入 M-Mode 入口汇编 +#[cfg(all(feature = "nobios", target_arch = "riscv64"))] +core::arch::global_asm!(include_str!("m_entry_rv64.asm")); + +#[cfg(all(feature = "nobios", target_arch = "riscv32"))] +core::arch::global_asm!(include_str!("m_entry_rv32.asm")); + // 用户程序内联进来。 core::arch::global_asm!(include_str!(env!("APP_ASM"))); // 定义内核入口。 @@ -22,7 +34,11 @@ extern "C" fn rust_main() -> ! { unsafe { linker::KernelLayout::locate().zero_bss() }; // 初始化 `console` rcore_console::init_console(&Console); + // 设置时间戳函数 + rcore_console::set_timestamp(timer::get_time_ms); + // 设置日志级别 rcore_console::set_log_level(option_env!("LOG")); + // 测试日志 rcore_console::test_log(); // 初始化 syscall syscall::init_io(&SyscallContext); @@ -34,9 +50,10 @@ extern "C" fn rust_main() -> ! { // 初始化上下文 let mut ctx = LocalContext::user(app_base); // 设置用户栈(使用 MaybeUninit 避免 release 模式下零初始化的问题) - let mut user_stack: core::mem::MaybeUninit<[usize; 256]> = core::mem::MaybeUninit::uninit(); + let mut user_stack: core::mem::MaybeUninit<[usize; 512]> = core::mem::MaybeUninit::uninit(); let user_stack_ptr = user_stack.as_mut_ptr() as *mut usize; - *ctx.sp_mut() = unsafe { user_stack_ptr.add(256) } as usize; + *ctx.sp_mut() = unsafe { user_stack_ptr.add(512) } as usize; + loop { unsafe { ctx.execute() }; @@ -61,16 +78,14 @@ extern "C" fn rust_main() -> ! { println!(); } - system_reset(Shutdown, NoReason); - unreachable!() + sbi::shutdown(false) } /// Rust 异常处理函数,以异常方式关机。 #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("{info}"); - system_reset(Shutdown, SystemFailure); - loop {} + sbi::shutdown(true) } enum SyscallResult { @@ -107,8 +122,7 @@ mod impls { impl rcore_console::Console for Console { #[inline] fn put_char(&self, c: u8) { - #[allow(deprecated)] - sbi_rt::legacy::console_putchar(c as _); + crate::sbi::console_putchar(c); } } diff --git a/ch2/src/msbi.rs b/ch2/src/msbi.rs new file mode 100644 index 00000000..697bea65 --- /dev/null +++ b/ch2/src/msbi.rs @@ -0,0 +1,143 @@ +//! M-Mode SBI 实现 +//! +//! 在 nobios 模式下,提供一个最小的 SBI 实现,处理 S-Mode 的 ecall。 + +/// QEMU virt UART 基地址 +const UART_BASE: usize = 0x1000_0000; + +/// UART 操作 (16550 兼容) +mod uart { + use super::UART_BASE; + + const THR: usize = UART_BASE; // Transmit Holding Register + const LSR: usize = UART_BASE + 5; // Line Status Register + + /// 检查 UART 是否准备好发送 + #[inline] + fn is_tx_ready() -> bool { + unsafe { + let lsr = (LSR as *const u8).read_volatile(); + (lsr & 0x20) != 0 // THRE bit + } + } + + /// 写入一个字节到 UART + pub fn putchar(c: u8) { + while !is_tx_ready() {} + unsafe { + (THR as *mut u8).write_volatile(c); + } + } +} + +/// SBI Extension IDs +mod eid { + pub const LEGACY_CONSOLE_PUTCHAR: usize = 0x01; + pub const LEGACY_SHUTDOWN: usize = 0x08; + pub const BASE: usize = 0x10; + pub const SRST: usize = 0x53525354; +} + +/// SBI 错误码 +mod error { + pub const SUCCESS: isize = 0; + pub const ERR_NOT_SUPPORTED: isize = -2; +} + +/// SBI 返回值 +#[repr(C)] +pub struct SbiRet { + pub error: isize, + pub value: usize, +} + +impl SbiRet { + fn success(value: usize) -> Self { + SbiRet { error: error::SUCCESS, value } + } + + fn not_supported() -> Self { + SbiRet { error: error::ERR_NOT_SUPPORTED, value: 0 } + } +} + +/// 处理 legacy console putchar (EID 0x01) +fn handle_console_putchar(c: usize) -> SbiRet { + uart::putchar(c as u8); + SbiRet::success(0) +} + +/// 处理系统复位 +fn handle_system_reset(reset_reason: usize) -> SbiRet { + const VIRT_TEST: usize = 0x10_0000; + const FINISHER_PASS: u32 = 0x5555; + const FINISHER_FAIL: u32 = 0x3333; + + let code = if reset_reason == 0 { FINISHER_PASS } else { FINISHER_FAIL }; + unsafe { + (VIRT_TEST as *mut u32).write_volatile(code); + } + loop {} +} + +/// 处理 legacy shutdown (EID 0x08) +fn handle_legacy_shutdown() -> SbiRet { + handle_system_reset(0) +} + +/// 处理 SBI base 扩展 (EID 0x10) +fn handle_base(fid: usize) -> SbiRet { + match fid { + 0 => SbiRet::success(2), // spec_version: SBI 0.2 + 1 => SbiRet::success(0), // impl_id + 2 => SbiRet::success(1), // impl_version + 3 => SbiRet::success(1), // probe_extension + 4 => SbiRet::success(0), // mvendorid + 5 => SbiRet::success(0), // marchid + 6 => SbiRet::success(0), // mimpid + _ => SbiRet::not_supported(), + } +} + +/// M-Mode trap handler,由汇编调用 +/// +/// 参数通过寄存器传递: +/// - a0-a5: SBI 调用参数 +/// - a6: FID (function ID) +/// - a7: EID (extension ID) +#[unsafe(no_mangle)] +pub extern "C" fn m_trap_handler( + a0: usize, + a1: usize, + _a2: usize, + _a3: usize, + _a4: usize, + _a5: usize, + fid: usize, + eid: usize, +) -> SbiRet { + // 检查 mcause,只处理来自 S-Mode 的 ecall (cause = 9) + let mcause: usize; + unsafe { + core::arch::asm!("csrr {}, mcause", out(reg) mcause); + } + + if mcause != 9 { + return SbiRet::not_supported(); + } + + // 根据 EID 处理 SBI 调用 + match eid { + eid::LEGACY_CONSOLE_PUTCHAR => handle_console_putchar(a0), + eid::LEGACY_SHUTDOWN => handle_legacy_shutdown(), + eid::BASE => handle_base(fid), + eid::SRST => { + if fid == 0 { + handle_system_reset(a1) + } else { + SbiRet::not_supported() + } + } + _ => SbiRet::not_supported(), + } +} diff --git a/ch2/src/sbi.rs b/ch2/src/sbi.rs new file mode 100644 index 00000000..e7946c5d --- /dev/null +++ b/ch2/src/sbi.rs @@ -0,0 +1,45 @@ +//! SBI 服务调用封装 +//! +//! 不依赖 sbi_rt,直接通过 ecall 调用 SBI 服务。 + +use core::arch::asm; + +/// SBI 调用封装 +#[inline(always)] +fn sbi_call(eid: usize, fid: usize, arg0: usize, arg1: usize, arg2: usize) -> (isize, usize) { + let error: isize; + let value: usize; + unsafe { + asm!( + "ecall", + inlateout("a0") arg0 => error, + inlateout("a1") arg1 => value, + in("a2") arg2, + in("a6") fid, + in("a7") eid, + ); + } + (error, value) +} + +/// SBI Extension IDs +const SBI_CONSOLE_PUTCHAR: usize = 0x01; +const SBI_SHUTDOWN: usize = 0x08; +const SBI_SRST: usize = 0x53525354; + +/// 输出一个字符到控制台 +pub fn console_putchar(c: u8) { + sbi_call(SBI_CONSOLE_PUTCHAR, 0, c as usize, 0, 0); +} + +/// 关机 +pub fn shutdown(failure: bool) -> ! { + // 尝试 SRST 扩展 (SBI v0.3+) + let reason = if failure { 1usize } else { 0usize }; + sbi_call(SBI_SRST, 0, 0, reason, 0); + + // 回退到 legacy shutdown + sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); + + loop {} +} diff --git a/ch2/src/timer.rs b/ch2/src/timer.rs new file mode 100644 index 00000000..ecbefb3b --- /dev/null +++ b/ch2/src/timer.rs @@ -0,0 +1,46 @@ +//! Timer module for getting system time +//! +//! Uses RISC-V time CSR +//! Supports both RV32 and RV64 + +/// QEMU virt platform clock frequency (10 MHz) +const CLOCK_FREQ: u64 = 10_000_000; + +/// Get current time in clock cycles +#[inline] +pub fn get_time() -> u64 { + #[cfg(target_pointer_width = "64")] + { + let time: u64; + unsafe { + core::arch::asm!("rdtime {}", out(reg) time); + } + time + } + + #[cfg(target_pointer_width = "32")] + { + // RV32: need to read timeh and time separately + // and handle the case where time overflows between reads + loop { + let hi: u32; + let lo: u32; + let hi2: u32; + unsafe { + core::arch::asm!("rdtimeh {}", out(reg) hi); + core::arch::asm!("rdtime {}", out(reg) lo); + core::arch::asm!("rdtimeh {}", out(reg) hi2); + } + // If hi changed between reads, try again + if hi == hi2 { + return ((hi as u64) << 32) | (lo as u64); + } + } + } +} + +/// Get current time in milliseconds +#[inline] +pub fn get_time_ms() -> usize { + (get_time() / (CLOCK_FREQ / 1000)) as usize +} diff --git a/console/src/lib.rs b/console/src/lib.rs index 2aaad280..d0a979f5 100644 --- a/console/src/lib.rs +++ b/console/src/lib.rs @@ -31,12 +31,25 @@ pub trait Console: Sync { /// 库找到输出的方法:保存一个对象引用,这是一种单例。 static CONSOLE: Once<&'static dyn Console> = Once::new(); +/// 时间戳获取函数类型,返回毫秒数。 +pub type GetTimeMsFn = fn() -> usize; + +/// 全局时间戳获取函数。 +static GET_TIME_MS: Once = Once::new(); + /// 用户调用这个函数设置输出的方法。 pub fn init_console(console: &'static dyn Console) { CONSOLE.call_once(|| console); log::set_logger(&Logger).unwrap(); } +/// 设置时间戳获取函数。 +/// +/// 函数应返回从启动开始的毫秒数。 +pub fn set_timestamp(f: GetTimeMsFn) { + GET_TIME_MS.call_once(|| f); +} + /// 根据环境变量设置日志级别。 pub fn set_log_level(env: Option<&str>) { use log::LevelFilter as Lv; @@ -62,6 +75,16 @@ pub fn test_log() { println!(); } +/// 打印时间戳前缀(如果已设置时间戳函数)。 +#[doc(hidden)] +#[inline] +pub fn _print_timestamp() { + if let Some(f) = GET_TIME_MS.get() { + let ms = f(); + Logger.write_fmt(format_args!("[{:>8} ms] ", ms)).unwrap(); + } +} + /// 打印。 /// /// 给宏用的,用户不会直接调它。 @@ -84,6 +107,7 @@ macro_rules! print { macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => {{ + $crate::_print_timestamp(); $crate::_print(core::format_args!($($arg)*)); $crate::println!(); }} diff --git a/kernel-context/src/lib.rs b/kernel-context/src/lib.rs index bcfa204f..8a89ea27 100644 --- a/kernel-context/src/lib.rs +++ b/kernel-context/src/lib.rs @@ -136,6 +136,8 @@ impl LocalContext { let ctx_ptr = self as *mut Self; let mut sepc = self.sepc; let old_sscratch: usize; + + #[cfg(target_pointer_width = "64")] core::arch::asm!( " csrrw {old_ss}, sscratch, {ctx} csrw sepc , {sepc} @@ -155,6 +157,28 @@ impl LocalContext { sstatus = inlateout(reg) sstatus, execute_naked = sym execute_naked, ); + + #[cfg(target_pointer_width = "32")] + core::arch::asm!( + " csrrw {old_ss}, sscratch, {ctx} + csrw sepc , {sepc} + csrw sstatus , {sstatus} + addi sp, sp, -4 + sw ra, (sp) + call {execute_naked} + lw ra, (sp) + addi sp, sp, 4 + csrw sscratch, {old_ss} + csrr {sepc} , sepc + csrr {sstatus}, sstatus + ", + ctx = in (reg) ctx_ptr, + old_ss = out (reg) old_sscratch, + sepc = inlateout(reg) sepc, + sstatus = inlateout(reg) sstatus, + execute_naked = sym execute_naked, + ); + let _ = old_sscratch; // suppress unused warning (*ctx_ptr).sepc = sepc; sstatus @@ -187,6 +211,7 @@ fn build_sstatus(supervisor: bool, interrupt: bool) -> usize { /// /// 裸函数。 #[unsafe(naked)] +#[cfg(target_pointer_width = "64")] unsafe extern "C" fn execute_naked() { core::arch::naked_asm!( r" .altmacro @@ -257,3 +282,84 @@ unsafe extern "C" fn execute_naked() { " .option pop", ) } + +/// 线程切换核心部分(RV32 版本)。 +/// +/// 通用寄存器压栈,然后从预存在 `sscratch` 里的上下文指针恢复线程通用寄存器。 +/// +/// # Safety +/// +/// 裸函数。 +#[unsafe(naked)] +#[cfg(target_pointer_width = "32")] +unsafe extern "C" fn execute_naked() { + core::arch::naked_asm!( + r" .altmacro + .macro SAVE n + sw x\n, \n*4(sp) + .endm + .macro SAVE_ALL + sw x1, 1*4(sp) + .set n, 3 + .rept 29 + SAVE %n + .set n, n+1 + .endr + .endm + + .macro LOAD n + lw x\n, \n*4(sp) + .endm + .macro LOAD_ALL + lw x1, 1*4(sp) + .set n, 3 + .rept 29 + LOAD %n + .set n, n+1 + .endr + .endm + ", + // 位置无关加载 + " .option push + .option nopic + ", + // 保存调度上下文 + " addi sp, sp, -32*4 + SAVE_ALL + ", + // 设置陷入入口 + " la t0, 1f + csrw stvec, t0 + ", + // 保存调度上下文地址并切换上下文 + " csrr t0, sscratch + sw sp, (t0) + mv sp, t0 + ", + // 恢复线程上下文 + " LOAD_ALL + lw sp, 2*4(sp) + ", + // 执行线程 + " sret + ", + // 陷入 + " .align 2", + // 切换上下文 + "1: csrrw sp, sscratch, sp", + // 保存线程上下文 + " SAVE_ALL + csrrw t0, sscratch, sp + sw t0, 2*4(sp) + ", + // 切换上下文 + " lw sp, (sp)", + // 恢复调度上下文 + " LOAD_ALL + addi sp, sp, 32*4 + ", + // 返回调度 + " ret", + " .option pop", + ) +} \ No newline at end of file diff --git a/linker/src/app.rs b/linker/src/app.rs index 11fc2e78..b4da59bf 100644 --- a/linker/src/app.rs +++ b/linker/src/app.rs @@ -1,10 +1,10 @@ /// 应用程序元数据。 #[repr(C)] pub struct AppMeta { - base: u64, - step: u64, - count: u64, - first: u64, + base: usize, + step: usize, + count: usize, + first: usize, } impl AppMeta { @@ -27,7 +27,7 @@ impl AppMeta { /// 应用程序迭代器。 pub struct AppIterator { meta: &'static AppMeta, - i: u64, + i: usize, } impl Iterator for AppIterator { @@ -37,16 +37,16 @@ impl Iterator for AppIterator { if self.i >= self.meta.count { None } else { - let i = self.i as usize; + let i = self.i; self.i += 1; unsafe { let slice = core::slice::from_raw_parts( &self.meta.first as *const _ as *const usize, - (self.meta.count + 1) as _, + self.meta.count + 1, ); let pos = slice[i]; let size = slice[i + 1] - pos; - let base = self.meta.base as usize + i * self.meta.step as usize; + let base = self.meta.base + i * self.meta.step; if base != 0 { core::ptr::copy_nonoverlapping::(pos as _, base as _, size); core::slice::from_raw_parts_mut(base as *mut u8, 0x20_0000)[size..].fill(0); diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 55e50824..b059ccaa 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -36,7 +36,7 @@ mod app; pub use app::{AppIterator, AppMeta}; -/// 链接脚本。 +/// 链接脚本(SBI 模式)。 pub const SCRIPT: &[u8] = b"\ OUTPUT_ARCH(riscv) SECTIONS { @@ -68,6 +68,64 @@ SECTIONS { __end = .; }"; +/// 链接脚本(nobios 模式)。 +/// +/// M-Mode 入口在 0x80000000,S-Mode 内核在 0x80200000。 +pub const NOBIOS_SCRIPT: &[u8] = b"\ +OUTPUT_ARCH(riscv) +ENTRY(_m_start) +M_BASE_ADDRESS = 0x80000000; +S_BASE_ADDRESS = 0x80200000; + +SECTIONS { + . = M_BASE_ADDRESS; + + .text.m_entry : { + *(.text.m_entry) + } + + .text.m_trap : { + *(.text.m_trap) + } + + .bss.m_stack : { + *(.bss.m_stack) + } + + .bss.m_data : { + *(.bss.m_data) + } + + . = S_BASE_ADDRESS; + + .text : { + __start = .; + *(.text.entry) + *(.text .text.*) + } + .rodata : ALIGN(4K) { + __rodata = .; + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + .data : ALIGN(4K) { + __data = .; + *(.data .data.*) + *(.sdata .sdata.*) + } + .bss : ALIGN(8) { + __sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + __ebss = .; + } + .boot : ALIGN(4K) { + __boot = .; + KEEP(*(.boot.stack)) + } + __end = .; +}"; + /// 定义内核入口。 /// /// 将设置一个启动栈,并在启动栈上调用高级语言入口。 diff --git a/user/src/heap.rs b/user/src/heap.rs index 591d32af..d16c21e4 100644 --- a/user/src/heap.rs +++ b/user/src/heap.rs @@ -20,6 +20,11 @@ pub fn init() { } type MutAllocator = BuddyAllocator; +// 16 KiB = 2^14, min_order = 2 (4 bytes), needs 14-2=12 layers +// Use pointer width to determine max layers to avoid overflow on RV32 +#[cfg(target_pointer_width = "32")] +static mut HEAP: MutAllocator<12> = MutAllocator::new(); +#[cfg(target_pointer_width = "64")] static mut HEAP: MutAllocator<32> = MutAllocator::new(); struct Global; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f39c78eb..72417002 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -14,12 +14,41 @@ use std::{ path::{Path, PathBuf}, }; -const TARGET_ARCH: &str = "riscv64gc-unknown-none-elf"; - -static PROJECT: Lazy<&'static Path> = +pub static PROJECT: Lazy<&'static Path> = Lazy::new(|| Path::new(std::env!("CARGO_MANIFEST_DIR")).parent().unwrap()); -static TARGET: Lazy = Lazy::new(|| PROJECT.join("target").join(TARGET_ARCH)); +/// Target architecture +#[derive(Clone, Copy, Default, ValueEnum)] +pub enum Arch { + /// RISC-V 32-bit + Riscv32, + /// RISC-V 64-bit + #[default] + Riscv64, +} + +impl Arch { + /// Get Rust target triple + pub fn target(&self) -> &'static str { + match self { + Arch::Riscv32 => "riscv32imac-unknown-none-elf", + Arch::Riscv64 => "riscv64gc-unknown-none-elf", + } + } + + /// Get QEMU system name + pub fn qemu_system(&self) -> &'static str { + match self { + Arch::Riscv32 => "riscv32", + Arch::Riscv64 => "riscv64", + } + } + + /// Get target directory + pub fn target_dir(&self) -> PathBuf { + PROJECT.join("target").join(self.target()) + } +} #[derive(Parser)] #[clap(name = "rCore-Tutorial")] @@ -55,6 +84,9 @@ struct BuildArgs { /// lab or not #[clap(long)] lab: bool, + /// target architecture + #[clap(long, value_enum, default_value_t = Arch::Riscv64)] + arch: Arch, /// features #[clap(short, long)] features: Option, @@ -64,18 +96,34 @@ struct BuildArgs { /// build in release mode #[clap(long)] release: bool, + /// build for no-bios mode (kernel at 0x80000000) + #[clap(long)] + nobios: bool, } impl BuildArgs { + /// Validate arguments + fn validate(&self) { + // RV32 only supports nobios mode + if matches!(self.arch, Arch::Riscv32) && !self.nobios { + eprintln!("Error: RV32 only supports nobios mode. Please add --nobios flag."); + eprintln!("Usage: cargo qemu --ch {} --arch riscv32 --nobios", self.ch); + std::process::exit(1); + } + } + fn make(&self) -> PathBuf { + self.validate(); + + let target_dir = self.arch.target_dir(); let mut env: HashMap<&str, OsString> = HashMap::new(); let package = match self.ch { 1 => if self.lab { "ch1-lab" } else { "ch1" }.to_string(), 2..=8 => { - user::build_for(self.ch, false); + user::build_for(self.ch, false, self.arch); env.insert( "APP_ASM", - TARGET + target_dir .join("debug") .join("app.asm") .as_os_str() @@ -85,25 +133,28 @@ impl BuildArgs { } _ => unreachable!(), }; - // 生成 + // Build let mut build = Cargo::build(); build .package(&package) .optional(&self.features, |cargo, features| { cargo.features(false, features.split_whitespace()); }) + .conditional(self.nobios, |cargo| { + cargo.features(false, ["nobios"]); + }) .optional(&self.log, |cargo, log| { cargo.env("LOG", log); }) .conditional(self.release, |cargo| { cargo.release(); }) - .target(TARGET_ARCH); + .target(self.arch.target()); for (key, value) in env { build.env(key, value); } build.invoke(); - TARGET + target_dir .join(if self.release { "release" } else { "debug" }) .join(package) } @@ -148,15 +199,24 @@ struct QemuArgs { impl QemuArgs { fn run(self) { let elf = self.build.make(); + let target_dir = self.build.arch.target_dir(); + if let Some(p) = &self.qemu_dir { Qemu::search_at(p); } - let mut qemu = Qemu::system("riscv64"); + let mut qemu = Qemu::system(self.build.arch.qemu_system()); qemu.args(&["-machine", "virt"]) - .arg("-nographic") - .arg("-bios") - .arg(PROJECT.join("rustsbi-qemu.bin")) - .arg("-kernel") + .arg("-nographic"); + + if self.build.nobios { + // No BIOS mode: kernel is loaded at 0x80000000 + qemu.args(&["-bios", "none"]); + } else { + // SBI mode: use RustSBI, kernel is loaded at 0x80200000 + qemu.arg("-bios").arg(PROJECT.join("rustsbi-qemu.bin")); + } + + qemu.arg("-kernel") .arg(objcopy(elf, true)) .args(&["-smp", &self.smp.unwrap_or(1).to_string()]) .args(&["-m", "64M"]) @@ -167,7 +227,7 @@ impl QemuArgs { "-drive", format!( "file={},if=none,format=raw,id=x0", - TARGET + target_dir .join(if self.build.release { "release" } else { diff --git a/xtask/src/user.rs b/xtask/src/user.rs index 57a29861..ed4f5d77 100644 --- a/xtask/src/user.rs +++ b/xtask/src/user.rs @@ -1,4 +1,4 @@ -use crate::{fs_pack::easy_fs_pack, objcopy, PROJECT, TARGET, TARGET_ARCH}; +use crate::{fs_pack::easy_fs_pack, objcopy, Arch, PROJECT}; use os_xtask_utils::{Cargo, CommandExt}; use serde_derive::Deserialize; use std::{collections::HashMap, ffi::OsStr, fs::File, io::Write, path::PathBuf}; @@ -17,14 +17,14 @@ pub struct CasesInfo { } impl Cases { - fn build(&mut self, release: bool) -> CasesInfo { + fn build(&mut self, release: bool, arch: Arch) -> CasesInfo { if let Some(names) = &self.cases { let base = self.base.unwrap_or(0); let step = self.step.filter(|_| self.base.is_some()).unwrap_or(0); let cases = names .into_iter() .enumerate() - .map(|(i, name)| build_one(name, release, base + i as u64 * step)) + .map(|(i, name)| build_one(name, release, base + i as u64 * step, arch)) .collect(); CasesInfo { base, @@ -41,7 +41,7 @@ impl Cases { } } -fn build_one(name: impl AsRef, release: bool, base_address: u64) -> PathBuf { +fn build_one(name: impl AsRef, release: bool, base_address: u64, arch: Arch) -> PathBuf { let name = name.as_ref(); let binary = base_address != 0; if binary { @@ -49,7 +49,7 @@ fn build_one(name: impl AsRef, release: bool, base_address: u64) -> PathB } Cargo::build() .package("user_lib") - .target(TARGET_ARCH) + .target(arch.target()) .arg("--bin") .arg(name) .conditional(release, |cargo| { @@ -59,7 +59,8 @@ fn build_one(name: impl AsRef, release: bool, base_address: u64) -> PathB cargo.env("BASE_ADDRESS", base_address.to_string()); }) .invoke(); - let elf = TARGET + let elf = arch + .target_dir() .join(if release { "release" } else { "debug" }) .join(name); if binary { @@ -69,37 +70,50 @@ fn build_one(name: impl AsRef, release: bool, base_address: u64) -> PathB } } -pub fn build_for(ch: u8, release: bool) { +pub fn build_for(ch: u8, release: bool, arch: Arch) { let cfg = std::fs::read_to_string(PROJECT.join("user/cases.toml")).unwrap(); let mut cases = toml::from_str::>(&cfg) .unwrap() .remove(&format!("ch{ch}")) .unwrap_or_default(); - let CasesInfo { base, step, bins } = cases.build(release); + let CasesInfo { base, step, bins } = cases.build(release, arch); if bins.is_empty() { return; } - let asm = TARGET + let target_dir = arch.target_dir(); + let asm = target_dir .join(if release { "release" } else { "debug" }) .join("app.asm"); let mut ld = File::create(asm).unwrap(); + + // Use .word for RV32, .quad for RV64 + let data_directive = match arch { + Arch::Riscv32 => ".word", + Arch::Riscv64 => ".quad", + }; + + let align = match arch { + Arch::Riscv32 => 2, + Arch::Riscv64 => 3, + }; + writeln!( ld, "\ .global apps .section .data - .align 3 + .align {align} apps: - .quad {base:#x} - .quad {step:#x} - .quad {}", + {data_directive} {base:#x} + {data_directive} {step:#x} + {data_directive} {}", bins.len(), ) .unwrap(); - (0..bins.len()).for_each(|i| writeln!(ld, " .quad app_{i}_start").unwrap()); + (0..bins.len()).for_each(|i| writeln!(ld, " {data_directive} app_{i}_start").unwrap()); - writeln!(ld, " .quad app_{}_end", bins.len() - 1).unwrap(); + writeln!(ld, " {data_directive} app_{}_end", bins.len() - 1).unwrap(); bins.iter().enumerate().for_each(|(i, path)| { writeln!( @@ -116,7 +130,7 @@ app_{i}_end:", writeln!( ld, " - .align 3 + .align {align} .section .data .global app_names app_names:" @@ -128,7 +142,7 @@ app_names:" } else if ch >= 6 { easy_fs_pack( &cases.cases.unwrap(), - TARGET + target_dir .join(if release { "release" } else { "debug" }) .into_os_string() .into_string()