From 7e3738ad9a0ef802ddb292d96fedf3f2cced2087 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 6 Jun 2026 16:56:35 +0800 Subject: [PATCH 01/19] refactor: initial commit of HAL. --- os/Cargo.toml | 3 +- os/src/arch/mod.rs | 2 + os/src/arch/riscv/address.rs | 8 ++++ os/src/arch/riscv/hart.rs | 38 +++++++++++++++++++ os/src/arch/riscv/mod.rs | 11 ++++++ os/src/arch/riscv/paging.rs | 27 ++++++++++++++ os/src/arch/riscv/switch.S | 51 ++++++++++++++++++++++++++ os/src/arch/riscv/trap.rs | 30 +++++++++++++++ os/src/boards/qemu.rs | 5 +++ os/src/hal/mod.rs | 10 +++++ os/src/hal/traits.rs | 63 ++++++++++++++++++++++++++++++++ os/src/hart.rs | 39 +------------------- os/src/main.rs | 4 ++ os/src/mm/address.rs | 4 +- os/src/platform/mod.rs | 3 ++ os/src/platform/qemu_virt/mod.rs | 9 +++++ os/src/platform/qemu_virt/sbi.rs | 33 +++++++++++++++++ os/src/sbi.rs | 1 + os/src/sched/switch.rs | 2 +- os/src/trap/mod.rs | 47 +++++++----------------- 20 files changed, 314 insertions(+), 76 deletions(-) create mode 100644 os/src/arch/mod.rs create mode 100644 os/src/arch/riscv/address.rs create mode 100644 os/src/arch/riscv/hart.rs create mode 100644 os/src/arch/riscv/mod.rs create mode 100644 os/src/arch/riscv/paging.rs create mode 100644 os/src/arch/riscv/switch.S create mode 100644 os/src/arch/riscv/trap.rs create mode 100644 os/src/hal/mod.rs create mode 100644 os/src/hal/traits.rs create mode 100644 os/src/platform/mod.rs create mode 100644 os/src/platform/qemu_virt/mod.rs create mode 100644 os/src/platform/qemu_virt/sbi.rs diff --git a/os/Cargo.toml b/os/Cargo.toml index 7bc14e8c..8c9dd06f 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -31,4 +31,5 @@ strum_macros = "0.28.0" fat32 = [] easyfs = [] ext4 = [] -default = ["ext4"] +platform-qemu-virt = [] +default = ["ext4", "platform-qemu-virt"] diff --git a/os/src/arch/mod.rs b/os/src/arch/mod.rs new file mode 100644 index 00000000..f7a96240 --- /dev/null +++ b/os/src/arch/mod.rs @@ -0,0 +1,2 @@ +//! Architecture-specific implementations. +pub mod riscv; diff --git a/os/src/arch/riscv/address.rs b/os/src/arch/riscv/address.rs new file mode 100644 index 00000000..f0fd0af2 --- /dev/null +++ b/os/src/arch/riscv/address.rs @@ -0,0 +1,8 @@ +//! SV39 address-space constants for RISC-V. + +/// Physical address width under Sv39. +pub const PA_WIDTH_SV39: usize = 56; +/// Virtual address width under Sv39. +pub const VA_WIDTH_SV39: usize = 39; +/// Physical page number width under Sv39. +pub const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - 12; // PAGE_SIZE_BITS = 12 diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs new file mode 100644 index 00000000..b1d4b8a8 --- /dev/null +++ b/os/src/arch/riscv/hart.rs @@ -0,0 +1,38 @@ +//! RISC-V hart-local register access, implementing [`HartId`](crate::hal::traits::HartId). + +use core::arch::asm; +use crate::hal::traits::HartId; + +/// RISC-V implementation of [`HartId`](crate::hal::traits::HartId) via the `tp` register. +pub struct RiscvHartId; + +impl HartId for RiscvHartId { + #[inline] + fn current() -> usize { + let id; + unsafe { asm!("mv {}, tp", out(reg) id) } + id + } + + #[inline] + unsafe fn init(id: usize) { + asm!("mv tp, {}", in(reg) id); + } +} + +/// Read current hart id from `tp`. +#[inline] +pub fn hartid() -> usize { + RiscvHartId::current() +} + +/// Write hart id to `tp` and return it. +pub fn init_with_hartid(hart_id: usize) -> usize { + unsafe { RiscvHartId::init(hart_id) }; + hart_id +} + +/// Compatibility shim: return hart id without modifying `tp`. +pub fn init() -> usize { + hartid() +} diff --git a/os/src/arch/riscv/mod.rs b/os/src/arch/riscv/mod.rs new file mode 100644 index 00000000..7bbcff1d --- /dev/null +++ b/os/src/arch/riscv/mod.rs @@ -0,0 +1,11 @@ +//! RISC-V arch implementation of HAL traits. +#![allow(missing_docs)] + +pub mod address; +pub mod hart; +pub mod paging; +pub mod trap; + +pub use hart::RiscvHartId; +pub use paging::Sv39Paging; +pub use trap::RiscvInterruptControl; diff --git a/os/src/arch/riscv/paging.rs b/os/src/arch/riscv/paging.rs new file mode 100644 index 00000000..fc178cc7 --- /dev/null +++ b/os/src/arch/riscv/paging.rs @@ -0,0 +1,27 @@ +//! SV39 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). + +use crate::hal::traits::PagingArch; +use crate::mm::PageTableEntry; + +/// RISC-V Sv39 three-level paging implementation. +pub struct Sv39Paging; + +impl PagingArch for Sv39Paging { + type Entry = PageTableEntry; + const SATP_MODE: usize = 8; // MODE=8 → Sv39 + const LEVELS: usize = 3; + + unsafe fn activate(root_ppn: usize) { + use riscv::register::satp; + satp::write(Self::SATP_MODE << 60 | root_ppn); + core::arch::asm!("sfence.vma"); + } + + unsafe fn current_token() -> usize { + riscv::register::satp::read().bits() + } + + unsafe fn flush_tlb() { + core::arch::asm!("sfence.vma"); + } +} diff --git a/os/src/arch/riscv/switch.S b/os/src/arch/riscv/switch.S new file mode 100644 index 00000000..a919bd82 --- /dev/null +++ b/os/src/arch/riscv/switch.S @@ -0,0 +1,51 @@ +.option arch, +f,+d +.altmacro +.macro SAVE_SN n + sd s\n, (\n+2)*8(a0) +.endm +.macro LOAD_SN n + ld s\n, (\n+2)*8(a1) +.endm +.macro SAVE_FSN n + fsd fs\n, (\n+14)*8(a0) +.endm +.macro LOAD_FSN n + fld fs\n, (\n+14)*8(a1) +.endm + .section .text + .globl __switch +__switch: + # __switch( + # current_task_cx_ptr: *mut TaskContext, + # next_task_cx_ptr: *const TaskContext + # ) + # save kernel stack of current task + sd sp, 8(a0) + # save ra & s0~s11 of current execution + sd ra, 0(a0) + .set n, 0 + .rept 12 + SAVE_SN %n + .set n, n + 1 + .endr + .set n, 0 + .rept 12 + SAVE_FSN %n + .set n, n + 1 + .endr + # restore ra & s0~s11 of next execution + ld ra, 0(a1) + .set n, 0 + .rept 12 + LOAD_SN %n + .set n, n + 1 + .endr + .set n, 0 + .rept 12 + LOAD_FSN %n + .set n, n + 1 + .endr + # restore kernel stack of next task + ld sp, 8(a1) + ret + diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs new file mode 100644 index 00000000..fa9fccb5 --- /dev/null +++ b/os/src/arch/riscv/trap.rs @@ -0,0 +1,30 @@ +//! RISC-V interrupt control, implementing [`InterruptControl`](crate::hal::traits::InterruptControl). + +use core::arch::asm; +use riscv::register::{mtvec::TrapMode, sie, stvec}; +use crate::config::TRAMPOLINE; +use crate::hal::traits::InterruptControl; + +/// RISC-V implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). +pub struct RiscvInterruptControl; + +impl InterruptControl for RiscvInterruptControl { + unsafe fn enable_timer() { sie::set_stimer(); } + unsafe fn disable_timer() { sie::clear_stimer(); } + unsafe fn enable_external() { sie::set_sext(); } + unsafe fn disable_external() { sie::clear_sext(); } + unsafe fn enable_software() { sie::set_ssoft(); } + + unsafe fn clear_software_pending() { + asm!("csrc sip, {}", in(reg) 1usize << 1); + } + + unsafe fn set_kernel_trap_entry() { + extern "C" { fn __trap_from_kernel(); } + stvec::write(__trap_from_kernel as usize, TrapMode::Direct); + } + + unsafe fn set_user_trap_entry() { + stvec::write(TRAMPOLINE, TrapMode::Direct); + } +} diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index 17eaf9cf..0c726065 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -12,11 +12,14 @@ pub const MMIO: &[(usize, usize)] = &[ (0x1000_1000, 0x8000), // Virtio MMIO devices, 8 slots, each slot occupies 0x1000 bytes ]; +/// UART0 MMIO base address. pub const VIRT_UART: usize = 0x1000_0000; /// QEMU virt 机型上的 Goldfish RTC MMIO 基址。 pub const VIRT_RTC: usize = 0x0010_1000; +/// Block device implementation for QEMU virt. pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +/// Char device implementation for QEMU virt. pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; //ref:: https://github.com/andre-richter/qemu-exit @@ -28,6 +31,7 @@ const EXIT_FAILURE_FLAG: u32 = 0x3333; const EXIT_FAILURE: u32 = exit_code_encode(1); // Equals `exit(1)`. qemu failed exit const EXIT_RESET: u32 = 0x7777; // qemu reset +/// QEMU exit interface. pub trait QEMUExit { /// Exit with specified return code. /// @@ -97,4 +101,5 @@ impl QEMUExit for RISCV64 { const VIRT_TEST: u64 = 0x100000; +/// Global QEMU exit handle using the sifive_test device. pub const QEMU_EXIT_HANDLE: RISCV64 = RISCV64::new(VIRT_TEST); diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs new file mode 100644 index 00000000..7f56ba1d --- /dev/null +++ b/os/src/hal/mod.rs @@ -0,0 +1,10 @@ +//! HAL — re-exports arch/platform concrete types under stable aliases. +#![allow(missing_docs)] + +pub mod traits; + +#[cfg(target_arch = "riscv64")] +pub use crate::arch::riscv::{RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt}; + +#[cfg(feature = "platform-qemu-virt")] +pub use crate::platform::qemu_virt::SbiPlatform as Plat; diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs new file mode 100644 index 00000000..c888d40c --- /dev/null +++ b/os/src/hal/traits.rs @@ -0,0 +1,63 @@ +//! HAL trait definitions — pure interfaces, no implementations. + +/// Per-hart interrupt control (enable/disable, trap entry setup). +pub trait InterruptControl { + /// Enable supervisor timer interrupt. + unsafe fn enable_timer(); + /// Disable supervisor timer interrupt. + unsafe fn disable_timer(); + /// Enable supervisor external interrupt. + unsafe fn enable_external(); + /// Disable supervisor external interrupt. + unsafe fn disable_external(); + /// Enable supervisor software interrupt. + unsafe fn enable_software(); + /// Clear pending supervisor software interrupt. + unsafe fn clear_software_pending(); + /// Set trap entry for kernel-mode traps. + unsafe fn set_kernel_trap_entry(); + /// Set trap entry for user-mode traps. + unsafe fn set_user_trap_entry(); +} + +/// Read and write the current hart id. +pub trait HartId { + /// Return current hart id from arch register. + fn current() -> usize; + /// Write hart id to arch register at boot. + unsafe fn init(id: usize); +} + +/// Arch-level paging operations (SV39 on RISC-V). +pub trait PagingArch { + /// Page-table entry type. + type Entry: Copy; + /// Value written to satp MODE field. + const SATP_MODE: usize; + /// Number of page-table levels. + const LEVELS: usize; + /// Activate the given root PPN and flush TLB. + unsafe fn activate(root_ppn: usize); + /// Read current satp token. + unsafe fn current_token() -> usize; + /// Flush entire TLB. + unsafe fn flush_tlb(); +} + +/// Platform timer: read monotonic time, program next interrupt. +pub trait Timer { + /// Read raw tick counter. + fn read_time() -> usize; + /// Program next timer interrupt deadline (raw ticks). + fn set_next(deadline: usize); + /// Clock frequency in Hz. + fn clock_freq() -> usize; +} + +/// Hart lifecycle control (SMP startup, IPI). +pub trait HartCtrl { + /// Start a hart at the given address with an opaque argument. + fn start_hart(hart_id: usize, start_addr: usize, opaque: usize) -> Result<(), ()>; + /// Send IPI to harts described by the mask. + fn send_ipi(hart_mask: usize); +} diff --git a/os/src/hart.rs b/os/src/hart.rs index 70ed8ba5..06c275f2 100644 --- a/os/src/hart.rs +++ b/os/src/hart.rs @@ -1,37 +1,2 @@ -//! hart-local 辅助接口。 -//! -//! 在 SMP A 阶段,内核态约定将 `tp` 用于保存当前 hart 的本地状态。 -//! 用户态可能会把 `tp` 当作 TLS 指针使用,因此 trap 边界会负责在 -//! 用户/内核切换时保存和恢复两边各自的 `tp` 语义。后续模块统一通过 -//! 这里提供的接口获取当前 hart 信息,而不是在各处直接读取 CSR。 - -use core::arch::asm; - -/// 使用当前已经保存在 `tp` 中的 hart id 完成初始化接口兼容。 -pub fn init() -> usize { - hartid() -} - -/// 使用启动阶段已经得到的 hart id 初始化 hart-local 寄存器。 -/// -/// 这用于 RustSBI / HSM 已经通过 `a0` 把 hart id 传给 Rust 入口的场景, -/// 避免在 Rust 中再额外依赖某个特定 CSR 读取路径。 -pub fn init_with_hartid(hart_id: usize) -> usize { - unsafe { write_tp(hart_id) }; - hart_id -} - -/// 从 `tp` 中读取当前 hart id。 -#[inline] -pub fn hartid() -> usize { - let hart_id; - unsafe { - asm!("mv {}, tp", out(reg) hart_id); - } - hart_id -} - -#[inline] -unsafe fn write_tp(hart_id: usize) { - asm!("mv tp, {}", in(reg) hart_id); -} +//! hart-local 辅助接口 — delegates to arch/riscv/hart.rs. +pub use crate::arch::riscv::hart::{hartid, init, init_with_hartid}; diff --git a/os/src/main.rs b/os/src/main.rs index 07c6adb3..262422a2 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -36,6 +36,10 @@ extern crate bitflags; #[path = "boards/qemu.rs"] mod board; +pub mod arch; +pub mod hal; +pub mod platform; + #[macro_use] mod console; pub mod config; diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 295816c4..a3759bd7 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -4,9 +4,7 @@ use super::PageTableEntry; use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; -const PA_WIDTH_SV39: usize = 56; -const VA_WIDTH_SV39: usize = 39; -const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - PAGE_SIZE_BITS; +use crate::arch::riscv::address::{PA_WIDTH_SV39, PPN_WIDTH_SV39, VA_WIDTH_SV39}; const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS; /// Exclusive end of the canonical low-half user virtual-address range under Sv39. pub const USER_SPACE_END: usize = 1usize << (VA_WIDTH_SV39 - 1); diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs new file mode 100644 index 00000000..5539b968 --- /dev/null +++ b/os/src/platform/mod.rs @@ -0,0 +1,3 @@ +//! Platform-specific implementations. +#![allow(missing_docs)] +pub mod qemu_virt; diff --git a/os/src/platform/qemu_virt/mod.rs b/os/src/platform/qemu_virt/mod.rs new file mode 100644 index 00000000..f2a75628 --- /dev/null +++ b/os/src/platform/qemu_virt/mod.rs @@ -0,0 +1,9 @@ +//! QEMU virt platform — memory map, exit, and board types. + +pub mod sbi; + +pub use crate::board::{ + BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, + RISCV64, VIRT_RTC, VIRT_UART, +}; +pub use sbi::SbiPlatform; diff --git a/os/src/platform/qemu_virt/sbi.rs b/os/src/platform/qemu_virt/sbi.rs new file mode 100644 index 00000000..d7faa2d4 --- /dev/null +++ b/os/src/platform/qemu_virt/sbi.rs @@ -0,0 +1,33 @@ +//! SBI platform for QEMU virt — implements Timer and HartCtrl HAL traits. + +pub use crate::sbi::{ + console_getchar, console_putchar, hart_get_status, hart_start, hart_state, send_ipi_mask, + set_timer, shutdown, HartState, SbiRet, +}; + +use crate::hal::traits::{HartCtrl, Timer}; + +/// SBI-backed implementation of [`Timer`] and [`HartCtrl`] for QEMU virt. +pub struct SbiPlatform; + +impl Timer for SbiPlatform { + fn read_time() -> usize { + riscv::register::time::read() + } + fn set_next(deadline: usize) { + set_timer(deadline); + } + fn clock_freq() -> usize { + crate::config::CLOCK_FREQ + } +} + +impl HartCtrl for SbiPlatform { + fn start_hart(hart_id: usize, start_addr: usize, opaque: usize) -> Result<(), ()> { + let ret = hart_start(hart_id, start_addr, opaque); + if ret.error == 0 { Ok(()) } else { Err(()) } + } + fn send_ipi(hart_mask: usize) { + send_ipi_mask(hart_mask); + } +} diff --git a/os/src/sbi.rs b/os/src/sbi.rs index f118287d..f40d8747 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -1,4 +1,5 @@ //! SBI call wrappers +//! Low-level implementation kept here; platform/qemu_virt/sbi.rs wraps HAL traits on top. #![allow(unused)] diff --git a/os/src/sched/switch.rs b/os/src/sched/switch.rs index 502c26f4..bf741adc 100644 --- a/os/src/sched/switch.rs +++ b/os/src/sched/switch.rs @@ -2,7 +2,7 @@ use super::context::TaskContext; use core::arch::global_asm; -global_asm!(include_str!("switch.S")); +global_asm!(include_str!("../arch/riscv/switch.S")); extern "C" { /// Switch to the context of `next_task_cx_ptr`, saving the current context diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 12a2e9a8..8936a59c 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -28,10 +28,11 @@ use crate::task::{ use crate::timer::{get_realtime_ns, get_time, handle_timer_interrupt}; use core::arch::{asm, global_asm}; use riscv::register::{ - mtvec::TrapMode, scause::{self, Exception, Interrupt, Trap}, - sie, stval, stvec, + stval, }; +use crate::arch::riscv::trap::RiscvInterruptControl; +use crate::hal::traits::InterruptControl; global_asm!(include_str!("trap.S")); @@ -106,67 +107,45 @@ pub fn init() { /// 初始化当前 hart 的 trap 相关状态。 pub fn init_hart() { - set_kernel_trap_entry(); unsafe { - sie::set_sext(); - sie::set_ssoft(); + RiscvInterruptControl::set_kernel_trap_entry(); + RiscvInterruptControl::enable_external(); + RiscvInterruptControl::enable_software(); } info!("hart {} trap init done", hartid()); } /// set trap entry for traps happen in kernel(supervisor) mode pub fn set_kernel_trap_entry() { - extern "C" { - fn __trap_from_kernel(); - } - unsafe { - stvec::write(__trap_from_kernel as usize, TrapMode::Direct); - } + unsafe { RiscvInterruptControl::set_kernel_trap_entry(); } } /// set trap entry for traps happen in user mode pub fn set_user_trap_entry() { - unsafe { - stvec::write(TRAMPOLINE as usize, TrapMode::Direct); - } + unsafe { RiscvInterruptControl::set_user_trap_entry(); } } /// 为当前 hart 开启 supervisor timer interrupt。 pub fn enable_timer_interrupt() { - unsafe { - sie::set_stimer(); - } + unsafe { RiscvInterruptControl::enable_timer(); } } /// 为当前 hart 关闭 supervisor timer interrupt。 -/// -/// 这用于 secondary hart 进入“纯 idle 占位”状态的场景,避免它在尚未完成 -/// 全局共享状态并发化之前,进入会访问共享 `UPSafeCell` 的 timer 路径。 pub fn disable_timer_interrupt() { - unsafe { - sie::clear_stimer(); - } + unsafe { RiscvInterruptControl::disable_timer(); } } /// 为当前 hart 关闭 supervisor external interrupt。 -/// -/// 这用于 secondary hart 暂时只作为已上线但不参与设备中断处理的 idle hart。 pub fn disable_external_interrupt() { - unsafe { - sie::clear_sext(); - } + unsafe { RiscvInterruptControl::disable_external(); } } /// 为当前 hart 开启 supervisor software interrupt。 pub fn enable_software_interrupt() { - unsafe { - sie::set_ssoft(); - } + unsafe { RiscvInterruptControl::enable_software(); } } /// 清除当前 hart 挂起的 supervisor software interrupt。 pub fn clear_software_interrupt_pending() { - unsafe { - asm!("csrc sip, {}", in(reg) 1 << 1); - } + unsafe { RiscvInterruptControl::clear_software_pending(); } } /// Handle a scheduler reschedule IPI. From 2f45793b99c31d507ac59a2b3f29411b68bc6816 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 7 Jun 2026 21:41:26 +0800 Subject: [PATCH 02/19] refactor: second commit of HAL: extract trap context and syscall ABI. --- os/src/arch/riscv/mod.rs | 2 +- os/src/arch/riscv/trap.rs | 64 +++++++++- os/src/hal/mod.rs | 5 +- os/src/hal/traits.rs | 38 ++++++ os/src/mm/memory_set.rs | 13 +- os/src/signal/mod.rs | 43 ++++--- os/src/syscall/process.rs | 8 +- os/src/syscall/signal.rs | 21 ++-- os/src/syscall/thread.rs | 2 +- os/src/task/process.rs | 16 +-- os/src/trap/context.rs | 127 ++++++++++++++++++- os/src/trap/mod.rs | 251 +++++++++++++++++--------------------- 12 files changed, 394 insertions(+), 196 deletions(-) diff --git a/os/src/arch/riscv/mod.rs b/os/src/arch/riscv/mod.rs index 7bbcff1d..0c409c30 100644 --- a/os/src/arch/riscv/mod.rs +++ b/os/src/arch/riscv/mod.rs @@ -8,4 +8,4 @@ pub mod trap; pub use hart::RiscvHartId; pub use paging::Sv39Paging; -pub use trap::RiscvInterruptControl; +pub use trap::{RiscvInterruptControl, RiscvTrapMachine}; diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs index fa9fccb5..98b10962 100644 --- a/os/src/arch/riscv/trap.rs +++ b/os/src/arch/riscv/trap.rs @@ -1,13 +1,26 @@ //! RISC-V interrupt control, implementing [`InterruptControl`](crate::hal::traits::InterruptControl). use core::arch::asm; -use riscv::register::{mtvec::TrapMode, sie, stvec}; +use riscv::register::{ + mtvec::TrapMode, + scause::{self, Exception, Interrupt, Trap}, + sie, stval, stvec, +}; use crate::config::TRAMPOLINE; -use crate::hal::traits::InterruptControl; +use crate::hal::traits::{InterruptControl, TrapCause, TrapInfo, TrapMachine}; /// RISC-V implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). pub struct RiscvInterruptControl; +/// RISC-V implementation of trap decoding and user-return operations. +pub struct RiscvTrapMachine; + +/// 用户态 `rt_sigreturn` trampoline 机器码。 +const USER_VDSO_CODE: [u8; 8] = [ + 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 + 0x73, 0x00, 0x00, 0x00, // ecall +]; + impl InterruptControl for RiscvInterruptControl { unsafe fn enable_timer() { sie::set_stimer(); } unsafe fn disable_timer() { sie::clear_stimer(); } @@ -28,3 +41,50 @@ impl InterruptControl for RiscvInterruptControl { stvec::write(TRAMPOLINE, TrapMode::Direct); } } + +impl TrapMachine for RiscvTrapMachine { + fn read_trap_info() -> TrapInfo { + let cause = match scause::read().cause() { + Trap::Exception(Exception::UserEnvCall) => TrapCause::UserSyscall, + Trap::Exception(Exception::StorePageFault) => TrapCause::StorePageFault, + Trap::Exception(Exception::LoadPageFault) => TrapCause::LoadPageFault, + Trap::Exception(Exception::InstructionPageFault) => TrapCause::InstructionPageFault, + Trap::Exception(Exception::StoreFault) => TrapCause::StoreFault, + Trap::Exception(Exception::InstructionFault) => TrapCause::InstructionFault, + Trap::Exception(Exception::LoadFault) => TrapCause::LoadFault, + Trap::Exception(Exception::IllegalInstruction) => TrapCause::IllegalInstruction, + Trap::Interrupt(Interrupt::SupervisorTimer) => TrapCause::TimerInterrupt, + Trap::Interrupt(Interrupt::SupervisorSoft) => TrapCause::SoftwareInterrupt, + Trap::Interrupt(Interrupt::SupervisorExternal) => TrapCause::ExternalInterrupt, + _ => TrapCause::Unknown, + }; + TrapInfo { + cause, + fault_addr: stval::read(), + } + } + + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> ! { + extern "C" { + fn __alltraps(); + fn __restore(); + } + let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; + asm!( + "fence.i", + "jr {restore_va}", + restore_va = in(reg) restore_va, + in("a0") trap_cx_user_va, + in("a1") user_token, + options(noreturn) + ); + } + + fn syscall_instruction_len() -> usize { + 4 + } + + fn rt_sigreturn_trampoline() -> &'static [u8] { + &USER_VDSO_CODE + } +} diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index 7f56ba1d..3e87b785 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -4,7 +4,10 @@ pub mod traits; #[cfg(target_arch = "riscv64")] -pub use crate::arch::riscv::{RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt}; +pub use crate::arch::riscv::{ + RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt, + RiscvTrapMachine as ArchTrapMachine, +}; #[cfg(feature = "platform-qemu-virt")] pub use crate::platform::qemu_virt::SbiPlatform as Plat; diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index c888d40c..bdc55168 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -20,6 +20,44 @@ pub trait InterruptControl { unsafe fn set_user_trap_entry(); } +/// Architecture-normalized trap causes observed by common kernel logic. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TrapCause { + UserSyscall, + StorePageFault, + LoadPageFault, + InstructionPageFault, + StoreFault, + InstructionFault, + LoadFault, + IllegalInstruction, + TimerInterrupt, + SoftwareInterrupt, + ExternalInterrupt, + Unknown, +} + +/// Trap metadata passed from arch-specific code into common trap handling. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct TrapInfo { + /// Decoded trap cause. + pub cause: TrapCause, + /// Fault address or trap-value register contents when applicable. + pub fault_addr: usize, +} + +/// Arch-specific trap/syscall machine operations used by common code. +pub trait TrapMachine { + /// Read the current trap cause and associated fault address. + fn read_trap_info() -> TrapInfo; + /// Return to user mode using the given trap-context VA and address-space token. + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> !; + /// Size in bytes of the userspace syscall instruction. + fn syscall_instruction_len() -> usize; + /// Machine-code trampoline used for `rt_sigreturn`. + fn rt_sigreturn_trampoline() -> &'static [u8]; +} + /// Read and write the current hart id. pub trait HartId { /// Return current hart id from arch register. diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 5e88489b..6ae0095d 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -13,6 +13,8 @@ use crate::fs::{ mark_cached_page_dirty, release_mapped_page, retain_mapped_page, sync_inode_range, CachePage, FileDescription, }; +use crate::hal::{ArchTrapMachine}; +use crate::hal::traits::TrapMachine; use crate::sync::SpinNoIrqLock; use crate::task::ProcessControlBlock; use crate::syscall::errno::ERRNO; @@ -40,15 +42,6 @@ extern "C" { fn strampoline(); } -/// 用户态 rt_sigreturn trampoline 机器码。 -/// -/// RISC-V Linux 不依赖用户提供 `SA_RESTORER`,handler 返回后跳到这里执行 -/// `rt_sigreturn` 系统调用。 -const USER_VDSO_CODE: [u8; 8] = [ - 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 - 0x73, 0x00, 0x00, 0x00, // ecall -]; - /// ELF 加载结果,包含动态链接所需的额外信息 pub struct ElfLoadInfo { /// 程序入口点 @@ -558,7 +551,7 @@ impl MemorySet { let start_va = VirtAddr::from(USER_VDSO_BASE); let end_va = VirtAddr::from(USER_VDSO_BASE + PAGE_SIZE); let vma = Vma::new_vdso(start_va, end_va); - self.insert_vma(vma, Some(&USER_VDSO_CODE)) + self.insert_vma(vma, Some(ArchTrapMachine::rt_sigreturn_trampoline())) } /// 根据起始虚拟页号删除一段用户区域,并延迟释放拆下的旧页对象。 pub(crate) fn remove_vma_with_start_vpn_user_deferred( diff --git a/os/src/signal/mod.rs b/os/src/signal/mod.rs index c27a2840..f65f22f1 100644 --- a/os/src/signal/mod.rs +++ b/os/src/signal/mod.rs @@ -2,6 +2,8 @@ use crate::{ config::USER_VDSO_RT_SIGRETURN, + hal::ArchTrapMachine, + hal::traits::TrapMachine, syscall::write_pod_to_user, task::{current_task, current_trap_cx}, }; @@ -199,7 +201,7 @@ pub fn handle_signals() -> Option { let trap_cx = current_trap_cx(); // Save the current user stack pointer - let mut user_sp = trap_cx.x[2]; // sp register + let mut user_sp = trap_cx.user_sp(); // Construct sigframe on user stack // Layout: sp points to ucontext, siginfo is above it if SA_SIGINFO @@ -236,28 +238,29 @@ pub fn handle_signals() -> Option { }; let mut mcontext = MContext { - gregs: [0; 32], + gregs: trap_cx.export_signal_gprs(), fpstate: FpState::default(), }; - // riscv64 glibc expects the saved PC in gregs[0], followed by x1..x31. - mcontext.gregs[0] = trap_cx.sepc; - mcontext.gregs[1..].copy_from_slice(&trap_cx.x[1..]); - mcontext.fpstate.fpregs.copy_from_slice(&trap_cx.f); - mcontext.fpstate.fcsr = trap_cx.fcsr as u32; + trap_cx.copy_fp_state_to(&mut mcontext.fpstate.fpregs, &mut mcontext.fpstate.fcsr); // Syscall restart: if the signal interrupted a syscall that returned -EINTR // and SA_RESTART is set, back up PC to the ecall instruction and restore // original a0 so the syscall can be restarted after sigreturn. if trap_cx.in_syscall { - let result = trap_cx.x[10] as isize; + let result = trap_cx.syscall_ret() as isize; if result == -(crate::syscall::errno::ERRNO::EINTR as isize) { if trap_cx.restartable_syscall && action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { debug!( "handle_signals: syscall restart: backing up PC from {:#x} to {:#x}, restoring a0 from {:#x} to {:#x}", - mcontext.gregs[0], trap_cx.sepc.wrapping_sub(4), + mcontext.gregs[0], + trap_cx + .user_pc() + .wrapping_sub(ArchTrapMachine::syscall_instruction_len()), mcontext.gregs[10], trap_cx.orig_a0 ); - mcontext.gregs[0] = trap_cx.sepc.wrapping_sub(4); + mcontext.gregs[0] = trap_cx + .user_pc() + .wrapping_sub(ArchTrapMachine::syscall_instruction_len()); mcontext.gregs[10] = trap_cx.orig_a0; } else if action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { debug!( @@ -329,29 +332,29 @@ pub fn handle_signals() -> Option { // Set up trap context to call signal handler // sp points to ucontext (aligned) - trap_cx.x[2] = user_sp; + trap_cx.set_user_sp(user_sp); // Set up arguments based on SA_SIGINFO if action.sa_flags & SaFlags::SA_SIGINFO.bits() != 0 { // SA_SIGINFO: handler(signum, siginfo*, ucontext*) - trap_cx.x[10] = signum as usize; // a0 = signum - trap_cx.x[11] = siginfo_ptr; // a1 = siginfo* - trap_cx.x[12] = ucontext_ptr; // a2 = ucontext* + trap_cx.set_user_arg(0, signum as usize); // a0 = signum + trap_cx.set_user_arg(1, siginfo_ptr); // a1 = siginfo* + trap_cx.set_user_arg(2, ucontext_ptr); // a2 = ucontext* } else { // Traditional: handler(signum) - trap_cx.x[10] = signum as usize; // a0 = signum + trap_cx.set_user_arg(0, signum as usize); // a0 = signum } // Set return address (ra) to restorer or kernel fallback if action.sa_flags & SaFlags::SA_RESTORER.bits() != 0 && action.sa_restorer != 0 { - trap_cx.x[1] = action.sa_restorer; // ra = sa_restorer + trap_cx.set_ra(action.sa_restorer); // ra = sa_restorer debug!( "handle_signals: using user restorer at {:#x}", action.sa_restorer ); } else { // RISC-V Linux 不要求用户态提供 SA_RESTORER,统一回到内核提供的 trampoline。 - trap_cx.x[1] = USER_VDSO_RT_SIGRETURN; + trap_cx.set_ra(USER_VDSO_RT_SIGRETURN); debug!( "handle_signals: using kernel vdso rt_sigreturn at {:#x}", USER_VDSO_RT_SIGRETURN @@ -359,11 +362,13 @@ pub fn handle_signals() -> Option { } // Jump to signal handler - trap_cx.sepc = action.handler; + trap_cx.set_user_pc(action.handler); debug!( "handle_signals: setup complete, jumping to handler={:#x}, ra={:#x}, sp={:#x}", - trap_cx.sepc, trap_cx.x[1], trap_cx.x[2] + trap_cx.user_pc(), + trap_cx.ra(), + trap_cx.user_sp() ); None } diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index c189aa47..c4d2c5c4 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -754,13 +754,13 @@ pub fn sys_clone( } let trap_cx = new_inner.get_trap_cx(); *trap_cx = inherited_cx; - trap_cx.kernel_sp = new_task.kstack.get_top(); - trap_cx.x[10] = 0; + trap_cx.set_kernel_sp(new_task.kstack.get_top()); + trap_cx.set_syscall_ret(0); if stack != 0 { - trap_cx.x[2] = stack; + trap_cx.set_user_sp(stack); } if let Some(tls) = child_tls { - trap_cx.x[4] = tls; + trap_cx.set_tls(tls); } if let Some(ptr) = child_set_tid { write_pod_to_user(ptr as *mut i32, &new_tid)?; diff --git a/os/src/syscall/signal.rs b/os/src/syscall/signal.rs index 5041280f..b7cd83c3 100644 --- a/os/src/syscall/signal.rs +++ b/os/src/syscall/signal.rs @@ -365,11 +365,15 @@ pub fn sys_sigprocmask(how: i32, set: *const u64, oset: *mut u64, sigsetsize: us pub fn sys_sigreturn() -> isize { syscall_body!({ let trap_cx = crate::task::current_trap_cx(); - let user_sp = trap_cx.x[2]; // Current sp + let user_sp = trap_cx.user_sp(); debug!( "sys_sigreturn: ENTRY sepc={:#x}, sp={:#x}, ra={:#x}, a0={:#x}, a7={:#x}", - trap_cx.sepc, user_sp, trap_cx.x[1], trap_cx.x[10], trap_cx.x[17] + trap_cx.user_pc(), + user_sp, + trap_cx.ra(), + trap_cx.syscall_ret(), + trap_cx.syscall_nr() ); // Read ucontext from user stack at sp. The frame can cross a page boundary, @@ -401,22 +405,19 @@ pub fn sys_sigreturn() -> isize { // Restore registers from mcontext let mcontext = &ucontext.uc_mcontext; - - trap_cx.x[0] = 0; - trap_cx.x[1..].copy_from_slice(&mcontext.gregs[1..]); - trap_cx.sepc = mcontext.gregs[0]; + trap_cx.import_signal_gprs(&mcontext.gregs); debug!( "sys_sigreturn: restored sepc={:#x}, a0={:#x}", - trap_cx.sepc, trap_cx.x[10] + trap_cx.user_pc(), + trap_cx.syscall_ret() ); // Restore floating-point registers - trap_cx.f.copy_from_slice(&mcontext.fpstate.fpregs); - trap_cx.fcsr = mcontext.fpstate.fcsr as usize; + trap_cx.restore_fp_state(&mcontext.fpstate.fpregs, mcontext.fpstate.fcsr); // Return the original a0 value (which was saved in the trap context) - Ok(trap_cx.x[10] as isize) + Ok(trap_cx.syscall_ret() as isize) }) } diff --git a/os/src/syscall/thread.rs b/os/src/syscall/thread.rs index 8cea4219..de1a4ab9 100644 --- a/os/src/syscall/thread.rs +++ b/os/src/syscall/thread.rs @@ -61,7 +61,7 @@ pub fn sys_thread_create(entry: usize, arg: usize) -> isize { new_task.kstack.get_top(), trap_handler as usize, ); - (*new_task_trap_cx).x[10] = arg; + new_task_trap_cx.set_user_arg(0, arg); drop(new_task_inner); process.attach_task(Arc::clone(&new_task)); add_task(new_task); diff --git a/os/src/task/process.rs b/os/src/task/process.rs index f74b2ff5..4afbb15d 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -983,14 +983,14 @@ impl ProcessControlBlock { trap_handler as usize, ); // RISC-V glibc _start treats a0 as rtld_fini and reads argc/argv from the stack. - trap_cx.x[10] = 0; - trap_cx.x[11] = 0; + trap_cx.set_user_arg(0, 0); + trap_cx.set_user_arg(1, 0); debug!( "kernel: exec trap init entry={:#x} sp={:#x} a0={:#x} a1={:#x}", final_entry, user_sp, - trap_cx.x[10], - trap_cx.x[11] + trap_cx.reg(10), + trap_cx.reg(11) ); *task_inner.get_trap_cx() = trap_cx; Ok(()) @@ -1141,15 +1141,15 @@ impl ProcessControlBlock { // patches the inherited return register, breaking fork semantics. let task_inner = task.inner_exclusive_access(); let trap_cx = task_inner.get_trap_cx(); - trap_cx.kernel_sp = task.kstack.get_top(); - trap_cx.x[10] = 0; + trap_cx.set_kernel_sp(task.kstack.get_top()); + trap_cx.set_syscall_ret(0); if child_stack != 0 { // Linux clone ABI 要求子进程从指定用户栈继续执行。 - trap_cx.x[2] = child_stack; + trap_cx.set_user_sp(child_stack); } if let Some(tls) = child_tls { // RISC-V 用户态 TLS 指针使用 tp,也就是 x4。 - trap_cx.x[4] = tls; + trap_cx.set_tls(tls); } drop(task_inner); if let Some(child_tid_ptr) = child_set_tid { diff --git a/os/src/trap/context.rs b/os/src/trap/context.rs index 1ed1f720..aa2d611d 100644 --- a/os/src/trap/context.rs +++ b/os/src/trap/context.rs @@ -39,10 +39,135 @@ pub struct TrapContext { } impl TrapContext { + /// Return the raw general-purpose register value at `index`. + pub fn reg(&self, index: usize) -> usize { + self.x[index] + } + + /// Update the raw general-purpose register value at `index`. + pub fn set_reg(&mut self, index: usize, value: usize) { + if index != 0 { + self.x[index] = value; + } + } + + /// Return the saved user-mode PC. + pub fn user_pc(&self) -> usize { + self.sepc + } + + /// Overwrite the saved user-mode PC. + pub fn set_user_pc(&mut self, pc: usize) { + self.sepc = pc; + } + + /// Advance the saved user-mode PC by `delta` bytes. + pub fn advance_user_pc(&mut self, delta: usize) { + self.sepc = self.sepc.wrapping_add(delta); + } + + /// Return the saved user stack pointer. + pub fn user_sp(&self) -> usize { + self.x[2] + } + /// put the sp(stack pointer) into x\[2\] field of TrapContext pub fn set_sp(&mut self, sp: usize) { self.x[2] = sp; } + + /// Set the saved user stack pointer. + pub fn set_user_sp(&mut self, sp: usize) { + self.set_sp(sp); + } + + /// Return the saved return address register. + pub fn ra(&self) -> usize { + self.x[1] + } + + /// Set the saved return address register. + pub fn set_ra(&mut self, ra: usize) { + self.x[1] = ra; + } + + /// Return the saved user TLS/thread-pointer register. + pub fn tls(&self) -> usize { + self.x[4] + } + + /// Set the saved user TLS/thread-pointer register. + pub fn set_tls(&mut self, tls: usize) { + self.x[4] = tls; + } + + /// Return the architecture syscall number register. + pub fn syscall_nr(&self) -> usize { + self.x[17] + } + + /// Return the six syscall arguments from the saved user context. + pub fn syscall_args(&self) -> [usize; 6] { + [self.x[10], self.x[11], self.x[12], self.x[13], self.x[14], self.x[15]] + } + + /// Return the saved syscall return register. + pub fn syscall_ret(&self) -> usize { + self.x[10] + } + + /// Set the saved syscall return register. + pub fn set_syscall_ret(&mut self, ret: usize) { + self.x[10] = ret; + } + + /// Set one user-call argument register. + pub fn set_user_arg(&mut self, index: usize, value: usize) { + self.x[10 + index] = value; + } + + /// Save the original first syscall argument for possible restart. + pub fn save_syscall_arg0_for_restart(&mut self) { + self.orig_a0 = self.x[10]; + } + + /// Set the kernel hart id restored by the trap trampoline. + pub fn set_kernel_hartid(&mut self, hartid: usize) { + self.kernel_hartid = hartid; + } + + /// Set the kernel stack pointer restored on the next trap entry. + pub fn set_kernel_sp(&mut self, kernel_sp: usize) { + self.kernel_sp = kernel_sp; + } + + /// Export the saved register file using the riscv64 Linux signal ABI layout. + pub fn export_signal_gprs(&self) -> [usize; 32] { + let mut gregs = [0usize; 32]; + gregs[0] = self.sepc; + gregs[1..].copy_from_slice(&self.x[1..]); + gregs + } + + /// Restore the saved register file using the riscv64 Linux signal ABI layout. + pub fn import_signal_gprs(&mut self, gregs: &[usize; 32]) { + self.x[0] = 0; + self.x[1..].copy_from_slice(&gregs[1..]); + self.sepc = gregs[0]; + } + + /// Copy floating-point state into an external signal frame. + pub fn copy_fp_state_to(&self, fpregs: &mut [u64; 32], fcsr: &mut u32) { + fpregs.copy_from_slice(&self.f); + *fcsr = self.fcsr as u32; + } + + /// Restore floating-point state from an external signal frame. + pub fn restore_fp_state(&mut self, fpregs: &[u64; 32], fcsr: u32) { + self.f.copy_from_slice(fpregs); + self.fcsr = fcsr as usize; + } + /// init the trap context of an application pub fn app_init_context( entry: usize, @@ -69,7 +194,7 @@ impl TrapContext { orig_a0: 0, restartable_syscall: false, }; - cx.set_sp(sp); // app's user stack pointer + cx.set_user_sp(sp); // app's user stack pointer cx // return initial Trap Context of app } } diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 8936a59c..a37e14be 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -26,13 +26,9 @@ use crate::task::{ current_trap_cx_user_va, current_user_token, exit_current_and_run_next, }; use crate::timer::{get_realtime_ns, get_time, handle_timer_interrupt}; -use core::arch::{asm, global_asm}; -use riscv::register::{ - scause::{self, Exception, Interrupt, Trap}, - stval, -}; -use crate::arch::riscv::trap::RiscvInterruptControl; -use crate::hal::traits::InterruptControl; +use core::arch::global_asm; +use crate::hal::{ArchInterrupt, ArchTrapMachine}; +use crate::hal::traits::{InterruptControl, TrapCause, TrapMachine}; global_asm!(include_str!("trap.S")); @@ -45,37 +41,37 @@ fn log_user_fault(reason: &str, access: &str, fault_addr: usize, signal: &str) { access, current_process().getpid(), fault_addr, - cx.sepc, - cx.x[1], - cx.x[2], - cx.x[3], - cx.x[4], - cx.x[10], - cx.x[11], - cx.x[17], + cx.user_pc(), + cx.ra(), + cx.user_sp(), + cx.reg(3), + cx.tls(), + cx.reg(10), + cx.reg(11), + cx.syscall_nr(), signal, ); error!( "[kernel] user fault regs: t0={:#x}, t1={:#x}, t2={:#x}, s0={:#x}, s1={:#x}, s2={:#x}, s3={:#x}, s4={:#x}, s5={:#x}, s6={:#x}, s7={:#x}, s8={:#x}, s9={:#x}, s10={:#x}, s11={:#x}, t3={:#x}, t4={:#x}, t5={:#x}, t6={:#x}", - cx.x[5], - cx.x[6], - cx.x[7], - cx.x[8], - cx.x[9], - cx.x[18], - cx.x[19], - cx.x[20], - cx.x[21], - cx.x[22], - cx.x[23], - cx.x[24], - cx.x[25], - cx.x[26], - cx.x[27], - cx.x[28], - cx.x[29], - cx.x[30], - cx.x[31], + cx.reg(5), + cx.reg(6), + cx.reg(7), + cx.reg(8), + cx.reg(9), + cx.reg(18), + cx.reg(19), + cx.reg(20), + cx.reg(21), + cx.reg(22), + cx.reg(23), + cx.reg(24), + cx.reg(25), + cx.reg(26), + cx.reg(27), + cx.reg(28), + cx.reg(29), + cx.reg(30), + cx.reg(31), ); } @@ -87,13 +83,13 @@ fn log_lazy_fault_oom(path: &str, access: &str, fault_addr: usize) { access, current_process().getpid(), fault_addr, - cx.sepc, - cx.x[1], - cx.x[2], - cx.x[4], - cx.x[10], - cx.x[11], - cx.x[17], + cx.user_pc(), + cx.ra(), + cx.user_sp(), + cx.tls(), + cx.reg(10), + cx.reg(11), + cx.syscall_nr(), ); } @@ -108,44 +104,44 @@ pub fn init() { /// 初始化当前 hart 的 trap 相关状态。 pub fn init_hart() { unsafe { - RiscvInterruptControl::set_kernel_trap_entry(); - RiscvInterruptControl::enable_external(); - RiscvInterruptControl::enable_software(); + ArchInterrupt::set_kernel_trap_entry(); + ArchInterrupt::enable_external(); + ArchInterrupt::enable_software(); } info!("hart {} trap init done", hartid()); } /// set trap entry for traps happen in kernel(supervisor) mode pub fn set_kernel_trap_entry() { - unsafe { RiscvInterruptControl::set_kernel_trap_entry(); } + unsafe { ArchInterrupt::set_kernel_trap_entry(); } } /// set trap entry for traps happen in user mode pub fn set_user_trap_entry() { - unsafe { RiscvInterruptControl::set_user_trap_entry(); } + unsafe { ArchInterrupt::set_user_trap_entry(); } } /// 为当前 hart 开启 supervisor timer interrupt。 pub fn enable_timer_interrupt() { - unsafe { RiscvInterruptControl::enable_timer(); } + unsafe { ArchInterrupt::enable_timer(); } } /// 为当前 hart 关闭 supervisor timer interrupt。 pub fn disable_timer_interrupt() { - unsafe { RiscvInterruptControl::disable_timer(); } + unsafe { ArchInterrupt::disable_timer(); } } /// 为当前 hart 关闭 supervisor external interrupt。 pub fn disable_external_interrupt() { - unsafe { RiscvInterruptControl::disable_external(); } + unsafe { ArchInterrupt::disable_external(); } } /// 为当前 hart 开启 supervisor software interrupt。 pub fn enable_software_interrupt() { - unsafe { RiscvInterruptControl::enable_software(); } + unsafe { ArchInterrupt::enable_software(); } } /// 清除当前 hart 挂起的 supervisor software interrupt。 pub fn clear_software_interrupt_pending() { - unsafe { RiscvInterruptControl::clear_software_pending(); } + unsafe { ArchInterrupt::clear_software_pending(); } } /// Handle a scheduler reschedule IPI. @@ -166,49 +162,47 @@ pub fn trap_handler() -> ! { current_process().enter_kernel(get_time()); current_trap_cx().in_syscall = false; current_trap_cx().restartable_syscall = false; - let scause = scause::read(); - let stval = stval::read(); - // trace!("into {:?}", scause.cause()); - match scause.cause() { - Trap::Exception(Exception::UserEnvCall) => { + let trap_info = ArchTrapMachine::read_trap_info(); + match trap_info.cause { + TrapCause::UserSyscall => { // jump to next instruction anyway let mut cx = current_trap_cx(); - let syscall_id = cx.x[17]; - let syscall_args = [cx.x[10], cx.x[11], cx.x[12], cx.x[13], cx.x[14], cx.x[15]]; - cx.orig_a0 = cx.x[10]; + let syscall_id = cx.syscall_nr(); + let syscall_args = cx.syscall_args(); + cx.save_syscall_arg0_for_restart(); cx.restartable_syscall = syscall_supports_sa_restart(syscall_id); - cx.sepc += 4; + cx.advance_user_pc(ArchTrapMachine::syscall_instruction_len()); // get system call return value let result = syscall(syscall_id, syscall_args); // cx is changed during sys_execve, so we have to call it again cx = current_trap_cx(); - cx.x[10] = result as usize; + cx.set_syscall_ret(result as usize); cx.in_syscall = true; } - Trap::Exception(Exception::StorePageFault) => { + TrapCause::StorePageFault => { debug!( "[mmap] trap store page fault: bad_addr={:#x} sepc={:#x}", - stval, - current_trap_cx().sepc + trap_info.fault_addr, + current_trap_cx().user_pc() ); let process = current_process(); let mut handled = false; - match process.handle_private_cow_fault(stval) { + match process.handle_private_cow_fault(trap_info.fault_addr) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("private_cow", "write", stval); + log_lazy_fault_oom("private_cow", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match process.handle_lazy_user_fault(stval, PageFaultAccess::Write) { + match process.handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Write) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "write", stval); + log_lazy_fault_oom("lazy_user", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } @@ -216,22 +210,22 @@ pub fn trap_handler() -> ! { } } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Write) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Write) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("store page fault", "write", stval, "SIGSEGV"); + log_user_fault("store page fault", "write", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("store page fault beyond file EOF", "write", stval, "SIGBUS"); + log_user_fault("store page fault beyond file EOF", "write", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "write", stval); + log_lazy_fault_oom("file_mmap", "write", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("store page fault", "write", stval, "SIGSEGV"); + log_user_fault("store page fault", "write", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } @@ -241,104 +235,102 @@ pub fn trap_handler() -> ! { inner.vm_layout.start_brk }; let tls_page = start_brk & !(PAGE_SIZE - 1); - if (stval & !(PAGE_SIZE - 1)) == tls_page { + if (trap_info.fault_addr & !(PAGE_SIZE - 1)) == tls_page { debug!( "[entry-static errno] store fault mapped tls page: fault_addr={:#x} tls_page={:#x}", - stval, + trap_info.fault_addr, tls_page ); } } } - Trap::Exception(Exception::LoadPageFault) => { + TrapCause::LoadPageFault => { // debug!( // "[mmap] trap load page fault: bad_addr={:#x} sepc={:#x}", - // stval, - // current_trap_cx().sepc + // trap_info.fault_addr, + // current_trap_cx().user_pc() // ); let mut handled = false; - match current_process().handle_lazy_user_fault(stval, PageFaultAccess::Read) { + match current_process().handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Read) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "read", stval); + log_lazy_fault_oom("lazy_user", "read", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Read) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Read) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("load page fault", "read", stval, "SIGSEGV"); + log_user_fault("load page fault", "read", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("load page fault beyond file EOF", "read", stval, "SIGBUS"); + log_user_fault("load page fault beyond file EOF", "read", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "read", stval); + log_lazy_fault_oom("file_mmap", "read", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("load page fault", "read", stval, "SIGSEGV"); + log_user_fault("load page fault", "read", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } } } - Trap::Exception(Exception::InstructionPageFault) => { + TrapCause::InstructionPageFault => { debug!( "[mmap] trap instruction page fault: bad_addr={:#x} sepc={:#x}", - stval, - current_trap_cx().sepc + trap_info.fault_addr, + current_trap_cx().user_pc() ); let mut handled = false; - match current_process().handle_lazy_user_fault(stval, PageFaultAccess::Exec) { + match current_process().handle_lazy_user_fault(trap_info.fault_addr, PageFaultAccess::Exec) { Ok(PageFaultHandled::Handled) => handled = true, Ok(PageFaultHandled::NotHandled) => {} Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("lazy_user", "exec", stval); + log_lazy_fault_oom("lazy_user", "exec", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); handled = true; } Err(_) => {} } if !handled { - match current_process().handle_file_page_fault(stval, PageFaultAccess::Exec) { + match current_process().handle_file_page_fault(trap_info.fault_addr, PageFaultAccess::Exec) { Ok(PageFaultHandled::Handled) => {} Ok(PageFaultHandled::NotHandled) => { - log_user_fault("instruction page fault", "exec", stval, "SIGSEGV"); + log_user_fault("instruction page fault", "exec", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } Err(MmError::BeyondFileEnd) => { - log_user_fault("instruction page fault beyond file EOF", "exec", stval, "SIGBUS"); + log_user_fault("instruction page fault beyond file EOF", "exec", trap_info.fault_addr, "SIGBUS"); current_add_signal(SignalBit::SIGBUS); } Err(MmError::OutOfMemory) => { - log_lazy_fault_oom("file_mmap", "exec", stval); + log_lazy_fault_oom("file_mmap", "exec", trap_info.fault_addr); current_add_signal(SignalBit::SIGKILL); } Err(_) => { - log_user_fault("instruction page fault", "exec", stval, "SIGSEGV"); + log_user_fault("instruction page fault", "exec", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } } } } - Trap::Exception(Exception::StoreFault) - | Trap::Exception(Exception::InstructionFault) - | Trap::Exception(Exception::LoadFault) => { - log_user_fault("access fault", "unknown", stval, "SIGSEGV"); + TrapCause::StoreFault | TrapCause::InstructionFault | TrapCause::LoadFault => { + log_user_fault("access fault", "unknown", trap_info.fault_addr, "SIGSEGV"); current_add_signal(SignalBit::SIGSEGV); } - Trap::Exception(Exception::IllegalInstruction) => { - log_user_fault("illegal instruction", "exec", stval, "SIGILL"); + TrapCause::IllegalInstruction => { + log_user_fault("illegal instruction", "exec", trap_info.fault_addr, "SIGILL"); current_add_signal(SignalBit::SIGILL); } - Trap::Interrupt(Interrupt::SupervisorTimer) => { + TrapCause::TimerInterrupt => { // trace!("hart {} timer tick", hartid()); if handle_timer_interrupt() { let now_raw = get_time(); @@ -347,18 +339,18 @@ pub fn trap_handler() -> ! { on_timer_tick(); } } - Trap::Interrupt(Interrupt::SupervisorSoft) => { + TrapCause::SoftwareInterrupt => { handle_reschedule_ipi(); } - Trap::Interrupt(Interrupt::SupervisorExternal) => { + TrapCause::ExternalInterrupt => { crate::drivers::plic::handle_supervisor_external(); crate::net::poll(); } _ => { panic!( - "Unsupported trap {:?}, stval = {:#x}!", - scause.cause(), - stval + "Unsupported trap {:?}, fault_addr = {:#x}!", + trap_info.cause, + trap_info.fault_addr ); } } @@ -386,47 +378,24 @@ pub fn trap_handler() -> ! { /// return to user space #[no_mangle] pub fn trap_return() -> ! { - //disable_supervisor_interrupt(); set_user_trap_entry(); let trap_cx_user_va = current_trap_cx_user_va(); - current_trap_cx().kernel_hartid = hartid(); - let user_satp = current_user_token(); - extern "C" { - fn __alltraps(); - fn __restore(); - } - let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; - // trace!("[kernel] trap_return: ..before return"); + current_trap_cx().set_kernel_hartid(hartid()); + let user_token = current_user_token(); current_process().enter_user(get_time()); - unsafe { - asm!( - "fence.i", - "jr {restore_va}", // jump to new addr of __restore asm function - restore_va = in(reg) restore_va, - in("a0") trap_cx_user_va, // a0 = virt addr of Trap Context - in("a1") user_satp, // a1 = user satp token - options(noreturn) - ); - } + unsafe { ArchTrapMachine::return_to_user(trap_cx_user_va, user_token) } } /// handle trap from kernel #[no_mangle] pub fn trap_from_kernel() { - // debug!("Trap from kernel: scause = {:?}, stval = {:#x}", scause::read(), stval::read()); - let scause = scause::read(); - let stval = stval::read(); - let cause: Trap = scause - .cause() - .try_into() - .unwrap_or_else(|_| panic!("Invalid trap {:?}, stval = {:#x}!", scause.cause(), stval)); - match cause.try_into() { - Ok(Trap::Interrupt(Interrupt::SupervisorExternal)) => { - // debug!("External interrupt from kernel: scause = {:?}, stval = {:#x}", scause, stval); + let trap_info = ArchTrapMachine::read_trap_info(); + match trap_info.cause { + TrapCause::ExternalInterrupt => { crate::drivers::plic::handle_supervisor_external(); crate::net::poll(); // 处理完外部中断后立即poll,让smoltcp响应ARP等请求 } - Ok(Trap::Interrupt(Interrupt::SupervisorTimer)) => { + TrapCause::TimerInterrupt => { // trace!("hart {} timer tick", hartid()); if handle_timer_interrupt() { let now_raw = get_time(); @@ -440,11 +409,15 @@ pub fn trap_from_kernel() { } // crate::net::poll(); } - Ok(Trap::Interrupt(Interrupt::SupervisorSoft)) => { + TrapCause::SoftwareInterrupt => { handle_reschedule_ipi(); } _ => { - panic!("Kernel trap: {:?}, stval = {:#x}", scause.cause(), stval); + panic!( + "Kernel trap: {:?}, fault_addr = {:#x}", + trap_info.cause, + trap_info.fault_addr + ); } } // check_timer(); From 170244eafccd469f8b192e5f3dcd1ab9e8eb0ba2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 8 Jun 2026 09:46:33 +0800 Subject: [PATCH 03/19] refactor: third commit of HAL: paging/token; trap/signal ABI. --- os/src/arch/riscv/hart.rs | 19 +------------- os/src/arch/riscv/paging.rs | 18 +++++++++---- os/src/drivers/plic.rs | 2 +- os/src/hal/mod.rs | 8 ++++++ os/src/hal/traits.rs | 21 ++++++++++----- os/src/hart.rs | 2 -- os/src/main.rs | 5 ++-- os/src/mm/memory_set.rs | 20 +++++++-------- os/src/mm/page_table.rs | 21 ++++++++------- os/src/mm/tlb_shootdown.rs | 35 ++++++++++++------------- os/src/sched/api.rs | 2 +- os/src/sched/processor.rs | 7 ++--- os/src/sched/runqueue.rs | 2 +- os/src/sched/switch.S | 51 ------------------------------------- os/src/signal/action.rs | 31 ++++++++++++++++++++++ os/src/signal/mod.rs | 6 +---- os/src/syscall/process.rs | 2 +- os/src/syscall/sched.rs | 2 +- os/src/syscall/signal.rs | 5 +--- os/src/task/mod.rs | 2 +- os/src/task/process.rs | 13 +++++----- os/src/task/task.rs | 5 ++-- os/src/timer.rs | 2 +- os/src/trap/mod.rs | 2 +- 24 files changed, 131 insertions(+), 152 deletions(-) delete mode 100644 os/src/hart.rs delete mode 100644 os/src/sched/switch.S diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs index b1d4b8a8..bdcf07f1 100644 --- a/os/src/arch/riscv/hart.rs +++ b/os/src/arch/riscv/hart.rs @@ -18,21 +18,4 @@ impl HartId for RiscvHartId { unsafe fn init(id: usize) { asm!("mv tp, {}", in(reg) id); } -} - -/// Read current hart id from `tp`. -#[inline] -pub fn hartid() -> usize { - RiscvHartId::current() -} - -/// Write hart id to `tp` and return it. -pub fn init_with_hartid(hart_id: usize) -> usize { - unsafe { RiscvHartId::init(hart_id) }; - hart_id -} - -/// Compatibility shim: return hart id without modifying `tp`. -pub fn init() -> usize { - hartid() -} +} \ No newline at end of file diff --git a/os/src/arch/riscv/paging.rs b/os/src/arch/riscv/paging.rs index fc178cc7..d14477cc 100644 --- a/os/src/arch/riscv/paging.rs +++ b/os/src/arch/riscv/paging.rs @@ -1,6 +1,6 @@ //! SV39 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). -use crate::hal::traits::PagingArch; +use crate::hal::traits::{AddressSpaceToken, PagingArch}; use crate::mm::PageTableEntry; /// RISC-V Sv39 three-level paging implementation. @@ -8,16 +8,24 @@ pub struct Sv39Paging; impl PagingArch for Sv39Paging { type Entry = PageTableEntry; - const SATP_MODE: usize = 8; // MODE=8 → Sv39 + const ROOT_TOKEN_MODE: usize = 8; // MODE=8 → Sv39 const LEVELS: usize = 3; - unsafe fn activate(root_ppn: usize) { + fn make_token(root_ppn: usize) -> AddressSpaceToken { + Self::ROOT_TOKEN_MODE << 60 | root_ppn + } + + fn root_ppn(token: AddressSpaceToken) -> usize { + token & ((1usize << 44) - 1) + } + + unsafe fn activate_token(token: AddressSpaceToken) { use riscv::register::satp; - satp::write(Self::SATP_MODE << 60 | root_ppn); + satp::write(token); core::arch::asm!("sfence.vma"); } - unsafe fn current_token() -> usize { + unsafe fn current_token() -> AddressSpaceToken { riscv::register::satp::read().bits() } diff --git a/os/src/drivers/plic.rs b/os/src/drivers/plic.rs index ef68d8cf..288a2830 100644 --- a/os/src/drivers/plic.rs +++ b/os/src/drivers/plic.rs @@ -5,7 +5,7 @@ use core::ptr::{read_volatile, write_volatile}; use crate::bootstrap_hart_id; use crate::config::MAX_HARTS; use crate::drivers::chardev::{CharDevice, UART}; -use crate::hart::hartid; +use crate::hal::hartid; use crate::sync::SpinNoIrqLock; use lazy_static::*; diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index 3e87b785..1ed22115 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -3,6 +3,8 @@ pub mod traits; +use crate::hal::traits::HartId; + #[cfg(target_arch = "riscv64")] pub use crate::arch::riscv::{ RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt, @@ -11,3 +13,9 @@ pub use crate::arch::riscv::{ #[cfg(feature = "platform-qemu-virt")] pub use crate::platform::qemu_virt::SbiPlatform as Plat; + +/// Return the current hart id. +#[inline] +pub fn hartid() -> usize { + ArchHart::current() +} \ No newline at end of file diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index bdc55168..4eae2555 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -66,18 +66,25 @@ pub trait HartId { unsafe fn init(id: usize); } -/// Arch-level paging operations (SV39 on RISC-V). +/// Opaque address-space activation token used by the current architecture. +pub type AddressSpaceToken = usize; + +/// Arch-level paging operations. pub trait PagingArch { /// Page-table entry type. type Entry: Copy; - /// Value written to satp MODE field. - const SATP_MODE: usize; + /// Architecture-specific mode bits embedded in the root token. + const ROOT_TOKEN_MODE: usize; /// Number of page-table levels. const LEVELS: usize; - /// Activate the given root PPN and flush TLB. - unsafe fn activate(root_ppn: usize); - /// Read current satp token. - unsafe fn current_token() -> usize; + /// Build an architecture token from a root page-table physical page number. + fn make_token(root_ppn: usize) -> AddressSpaceToken; + /// Extract the root page-table physical page number from an architecture token. + fn root_ppn(token: AddressSpaceToken) -> usize; + /// Activate the given address-space token and flush the local TLB. + unsafe fn activate_token(token: AddressSpaceToken); + /// Read current address-space token. + unsafe fn current_token() -> AddressSpaceToken; /// Flush entire TLB. unsafe fn flush_tlb(); } diff --git a/os/src/hart.rs b/os/src/hart.rs deleted file mode 100644 index 06c275f2..00000000 --- a/os/src/hart.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! hart-local 辅助接口 — delegates to arch/riscv/hart.rs. -pub use crate::arch::riscv::hart::{hartid, init, init_with_hartid}; diff --git a/os/src/main.rs b/os/src/main.rs index 262422a2..9d4b1350 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -45,7 +45,6 @@ mod console; pub mod config; pub mod drivers; pub mod fs; -pub mod hart; pub mod ipc; pub mod keys; pub mod lang_items; @@ -66,6 +65,8 @@ pub mod trap; use core::arch::global_asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use crate::hal::traits::HartId; +use crate::hal::ArchHart; global_asm!(include_str!("entry.asm")); @@ -339,7 +340,7 @@ fn secondary_hart_main(hart_id: usize) -> ! { /// 第一个进入该入口的 hart 会成为 bootstrap hart,负责一次性全局初始化 /// 并进入调度器;其他 hart 等待 bootstrap 完成后只做本地初始化并进入 idle。 pub fn rust_main(hart_id: usize) -> ! { - let _hart_id = hart::init_with_hartid(hart_id); + unsafe { ArchHart::init(hart_id) }; unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); } diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 6ae0095d..81ce0b4a 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -13,8 +13,9 @@ use crate::fs::{ mark_cached_page_dirty, release_mapped_page, retain_mapped_page, sync_inode_range, CachePage, FileDescription, }; -use crate::hal::{ArchTrapMachine}; -use crate::hal::traits::TrapMachine; +use crate::hal::ArchTrapMachine; +use crate::hal::traits::{AddressSpaceToken, PagingArch, TrapMachine}; +use crate::arch::riscv::Sv39Paging; use crate::sync::SpinNoIrqLock; use crate::task::ProcessControlBlock; use crate::syscall::errno::ERRNO; @@ -27,7 +28,6 @@ use core::arch::asm; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use fs::Inode; use lazy_static::*; -use riscv::register::satp; extern "C" { fn stext(); @@ -69,7 +69,7 @@ lazy_static! { } /// the kernel token -pub fn kernel_token() -> usize { +pub fn kernel_token() -> AddressSpaceToken { KERNEL_SPACE.lock().token() } /// 用于稳定标识一个底层 inode。 @@ -450,7 +450,7 @@ impl DeferredUserReclaim { self.token, self.mask ); - shootdown(self.mask, ShootdownKind::AddressSpace { satp: self.token }); + shootdown(self.mask, ShootdownKind::AddressSpace { token: self.token }); } // self 在函数返回时析构,batch 的 Drop 会真正释放旧页引用。 } @@ -475,7 +475,7 @@ impl MemorySet { } } /// Get he page table token - pub fn token(&self) -> usize { + pub fn token(&self) -> AddressSpaceToken { self.page_table.token() } /// 标记某个 hart 即将返回用户态并装载该地址空间。 @@ -532,7 +532,7 @@ impl MemorySet { self.token(), mask ); - shootdown(mask, ShootdownKind::AddressSpace { satp: self.token() }); + shootdown(mask, ShootdownKind::AddressSpace { token: self.token() }); } /// Assume that no conflicts. pub fn insert_framed_area( @@ -1121,12 +1121,10 @@ impl MemorySet { } Ok((memory_set, parent_tlb_needs_flush)) } - /// Change page table by writing satp CSR Register. + /// Change page table by activating the current architecture token. pub fn activate(&self) { - let satp = self.page_table.token(); unsafe { - satp::write(satp); - asm!("sfence.vma"); + Sv39Paging::activate_token(self.page_table.token()); } } /// Translate a virtual page number to a page table entry diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 9233a8ad..32ee8c8d 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -4,6 +4,8 @@ use super::{ VirtAddr, VirtPageNum, }; use crate::config::PAGE_SIZE; +use crate::arch::riscv::Sv39Paging; +use crate::hal::traits::{AddressSpaceToken, PagingArch}; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; @@ -88,9 +90,9 @@ impl PageTable { }) } /// Temporarily used to get arguments from user space. - pub fn from_token(satp: usize) -> Self { + pub fn from_token(token: AddressSpaceToken) -> Self { Self { - root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)), + root_ppn: PhysPageNum::from(Sv39Paging::root_ppn(token)), frames: Vec::new(), } } @@ -247,8 +249,8 @@ impl PageTable { }) } /// get the token from the page table - pub fn token(&self) -> usize { - 8usize << 60 | self.root_ppn.0 + pub fn token(&self) -> AddressSpaceToken { + Sv39Paging::make_token(self.root_ppn.0) } } @@ -268,10 +270,7 @@ fn checked_user_range(start: usize, len: usize) -> Option { } /// Create mutable `Vec` slice in kernel space from ptr in other address space. NOTICE: the content pointed to by the pointer `ptr` can cross physical pages. -pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Option> { - if len == 0 { - return Some(Vec::new()); - } +pub fn translated_byte_buffer(token: AddressSpaceToken, ptr: *const u8, len: usize) -> Option> { let page_table = PageTable::from_token(token); let mut start = ptr as usize; let end = checked_user_range(start, len)?; @@ -296,7 +295,7 @@ pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Optio } /// Create String in kernel address space from u8 Array(end with 0) in other address space -pub fn translated_str(token: usize, ptr: *const u8) -> Option { +pub fn translated_str(token: AddressSpaceToken, ptr: *const u8) -> Option { let page_table = PageTable::from_token(token); let mut string = String::new(); let mut va = ptr as usize; @@ -319,7 +318,7 @@ pub fn translated_str(token: usize, ptr: *const u8) -> Option { } /// translate a pointer `ptr` in other address space to a immutable u8 slice in kernel address space. NOTICE: the content pointed to by the pointer `ptr` cannot cross physical pages, otherwise translated_byte_buffer should be used. -pub fn translated_ref(token: usize, ptr: *const T) -> Option<&'static T> { +pub fn translated_ref(token: AddressSpaceToken, ptr: *const T) -> Option<&'static T> { let page_table = PageTable::from_token(token); checked_user_range(ptr as usize, core::mem::size_of::().max(1))?; page_table @@ -328,7 +327,7 @@ pub fn translated_ref(token: usize, ptr: *const T) -> Option<&'static T> { } /// translate a pointer `ptr` in other address space to a mutable u8 slice in kernel address space. NOTICE: the content pointed to by the pointer `ptr` cannot cross physical pages, otherwise translated_byte_buffer should be used. -pub fn translated_refmut(token: usize, ptr: *mut T) -> Option<&'static mut T> { +pub fn translated_refmut(token: AddressSpaceToken, ptr: *mut T) -> Option<&'static mut T> { let page_table = PageTable::from_token(token); let va = ptr as usize; checked_user_range(va, core::mem::size_of::().max(1))?; diff --git a/os/src/mm/tlb_shootdown.rs b/os/src/mm/tlb_shootdown.rs index f3522152..71b24b92 100644 --- a/os/src/mm/tlb_shootdown.rs +++ b/os/src/mm/tlb_shootdown.rs @@ -1,7 +1,9 @@ //! TLB shootdown state and deferred recycle helpers. use super::{kernel_token, FrameTracker}; -use crate::hart::hartid; +use crate::arch::riscv::Sv39Paging; +use crate::hal::hartid; +use crate::hal::traits::{AddressSpaceToken, PagingArch}; use crate::sbi::send_ipi_mask; use crate::sync::{SpinLock, SpinNoIrqLock}; use alloc::vec::Vec; @@ -9,7 +11,6 @@ use core::arch::asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use lazy_static::*; -use riscv::register::satp; /// 一个处于 deferred 状态的内核虚拟地址区间。 #[derive(Copy, Clone)] @@ -31,8 +32,8 @@ pub enum ShootdownKind { /// 时可能已经从用户态切入内核,但仍需要完成本地 flush 并 ack。 /// TODO:引入 ASID 后,应把这里改成按 ASID 或地址范围精确刷新。 AddressSpace { - /// 目标地址空间的 satp token。 - satp: usize, + /// 目标地址空间的架构 token。 + token: AddressSpaceToken, }, } @@ -52,8 +53,8 @@ struct TlbShootdownState { online_hart_mask: AtomicUsize, /// 当前请求类型编码。 kind_bits: AtomicUsize, - /// 当前请求附带的 satp 参数。 - arg_satp: AtomicUsize, + /// 当前请求附带的地址空间 token 参数。 + arg_token: AtomicUsize, } impl TlbShootdownState { @@ -66,7 +67,7 @@ impl TlbShootdownState { ack_mask: AtomicUsize::new(0), online_hart_mask: AtomicUsize::new(0), kind_bits: AtomicUsize::new(0), - arg_satp: AtomicUsize::new(0), + arg_token: AtomicUsize::new(0), } } } @@ -287,13 +288,13 @@ fn shootdown_inner(hart_mask: usize, kind: ShootdownKind) { let target_mask = hart_mask & online_mask & !self_bit; let seq = TLB_SHOOTDOWN_STATE.seq.load(Ordering::Acquire) + 1; - let (kind_bits, arg_satp) = encode_shootdown_kind(kind); + let (kind_bits, arg_token) = encode_shootdown_kind(kind); TLB_SHOOTDOWN_STATE .kind_bits .store(kind_bits, Ordering::Release); TLB_SHOOTDOWN_STATE - .arg_satp - .store(arg_satp, Ordering::Release); + .arg_token + .store(arg_token, Ordering::Release); TLB_SHOOTDOWN_STATE .target_mask .store(target_mask, Ordering::Release); @@ -377,7 +378,7 @@ pub fn handle_ipi() { } let kind = decode_shootdown_kind( TLB_SHOOTDOWN_STATE.kind_bits.load(Ordering::Acquire), - TLB_SHOOTDOWN_STATE.arg_satp.load(Ordering::Acquire), + TLB_SHOOTDOWN_STATE.arg_token.load(Ordering::Acquire), ); let seq = TLB_SHOOTDOWN_STATE.seq.load(Ordering::Acquire); trace!( @@ -398,14 +399,14 @@ pub fn handle_ipi() { fn encode_shootdown_kind(kind: ShootdownKind) -> (usize, usize) { match kind { ShootdownKind::Global => (KIND_GLOBAL, 0), - ShootdownKind::AddressSpace { satp } => (KIND_ADDRESS_SPACE, satp), + ShootdownKind::AddressSpace { token } => (KIND_ADDRESS_SPACE, token), } } /// 从全局请求槽解码出当前请求语义。 -fn decode_shootdown_kind(kind_bits: usize, arg_satp: usize) -> ShootdownKind { +fn decode_shootdown_kind(kind_bits: usize, arg_token: usize) -> ShootdownKind { match kind_bits { - KIND_ADDRESS_SPACE => ShootdownKind::AddressSpace { satp: arg_satp }, + KIND_ADDRESS_SPACE => ShootdownKind::AddressSpace { token: arg_token }, _ => ShootdownKind::Global, } } @@ -414,12 +415,12 @@ fn decode_shootdown_kind(kind_bits: usize, arg_satp: usize) -> ShootdownKind { fn perform_local_tlb_shootdown(kind: ShootdownKind) { match kind { ShootdownKind::Global => local_sfence_vma_all(), - ShootdownKind::AddressSpace { satp: target_satp } => { + ShootdownKind::AddressSpace { token: target_token } => { // 目标 hart 可能是在用户态收到 IPI 后刚切入内核,因此当前 satp // 可能已经是 kernel_token;此时仍然要完成本地 flush 并回 ack。 // TODO:引入 ASID 后,应避免把 kernel_token 情况退化成全量 flush。 - let current_satp = satp::read().bits(); - if current_satp == target_satp || current_satp == kernel_token() { + let current_token = unsafe { Sv39Paging::current_token() }; + if current_token == target_token || current_token == kernel_token() { local_sfence_vma_all(); } } diff --git a/os/src/sched/api.rs b/os/src/sched/api.rs index 86a9f13d..8a641869 100644 --- a/os/src/sched/api.rs +++ b/os/src/sched/api.rs @@ -5,7 +5,7 @@ use super::{ defer_task_release_after_switch, has_runnable_task_at_or_above, schedule, take_current_task, TaskContext, }; -use crate::hart::hartid; +use crate::hal::hartid; use crate::sched::CFS_YIELD_PENALTY_NS; use crate::task::{ReschedReason, SchedPolicy, TaskStatus, WaitReason}; use crate::timer::{get_time, get_time_ns}; diff --git a/os/src/sched/processor.rs b/os/src/sched/processor.rs index 873d40b8..27165cdf 100644 --- a/os/src/sched/processor.rs +++ b/os/src/sched/processor.rs @@ -7,7 +7,8 @@ use super::__switch; use super::{add_task, pick_next_task, TaskContext}; use crate::config::MAX_HARTS; -use crate::hart::hartid; +use crate::hal::hartid; +use crate::hal::traits::AddressSpaceToken; use crate::sync::SpinNoIrqLock; use crate::task::{ProcessControlBlock, SchedPolicy, TaskControlBlock, TaskStatus, INITPROC}; use crate::timer::{get_time, get_time_ns}; @@ -174,8 +175,8 @@ pub fn current_process() -> Arc { current_task().unwrap().process.upgrade().unwrap() } -/// Get the current user token(addr of page table) -pub fn current_user_token() -> usize { +/// Get the current user address-space token. +pub fn current_user_token() -> AddressSpaceToken { let task = current_task().unwrap(); task.get_user_token() } diff --git a/os/src/sched/runqueue.rs b/os/src/sched/runqueue.rs index f4f26a55..3b66ded7 100644 --- a/os/src/sched/runqueue.rs +++ b/os/src/sched/runqueue.rs @@ -2,7 +2,7 @@ use super::current_task; use crate::config::MAX_HARTS; -use crate::hart::hartid; +use crate::hal::hartid; use crate::mm::online_mask as online_hart_mask; use crate::sbi::send_ipi_mask; use crate::sched::{request_current_task_resched, CFS_WAKEUP_GRANULARITY_NS}; diff --git a/os/src/sched/switch.S b/os/src/sched/switch.S deleted file mode 100644 index a919bd82..00000000 --- a/os/src/sched/switch.S +++ /dev/null @@ -1,51 +0,0 @@ -.option arch, +f,+d -.altmacro -.macro SAVE_SN n - sd s\n, (\n+2)*8(a0) -.endm -.macro LOAD_SN n - ld s\n, (\n+2)*8(a1) -.endm -.macro SAVE_FSN n - fsd fs\n, (\n+14)*8(a0) -.endm -.macro LOAD_FSN n - fld fs\n, (\n+14)*8(a1) -.endm - .section .text - .globl __switch -__switch: - # __switch( - # current_task_cx_ptr: *mut TaskContext, - # next_task_cx_ptr: *const TaskContext - # ) - # save kernel stack of current task - sd sp, 8(a0) - # save ra & s0~s11 of current execution - sd ra, 0(a0) - .set n, 0 - .rept 12 - SAVE_SN %n - .set n, n + 1 - .endr - .set n, 0 - .rept 12 - SAVE_FSN %n - .set n, n + 1 - .endr - # restore ra & s0~s11 of next execution - ld ra, 0(a1) - .set n, 0 - .rept 12 - LOAD_SN %n - .set n, n + 1 - .endr - .set n, 0 - .rept 12 - LOAD_FSN %n - .set n, n + 1 - .endr - # restore kernel stack of next task - ld sp, 8(a1) - ret - diff --git a/os/src/signal/action.rs b/os/src/signal/action.rs index e8448641..11b136bc 100644 --- a/os/src/signal/action.rs +++ b/os/src/signal/action.rs @@ -1,5 +1,6 @@ use crate::signal::signals::MAX_SIG; use crate::syscall::Pod; +use crate::trap::TrapContext; /// Action for a signal (Linux rt_sigaction layout) #[repr(C)] @@ -183,6 +184,20 @@ impl Default for FpState { impl Pod for FpState {} +impl FpState { + /// Build an fp-state blob from the saved trap context. + pub fn from_trap_context(trap_cx: &TrapContext) -> Self { + let mut fpstate = Self::default(); + trap_cx.copy_fp_state_to(&mut fpstate.fpregs, &mut fpstate.fcsr); + fpstate + } + + /// Restore floating-point state into the saved trap context. + pub fn apply_to_trap_context(&self, trap_cx: &mut TrapContext) { + trap_cx.restore_fp_state(&self.fpregs, self.fcsr); + } +} + /// riscv64 Linux `mcontext_t`. #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -195,6 +210,22 @@ pub struct MContext { impl Pod for MContext {} +impl MContext { + /// Build a Linux-compatible machine context from the saved trap context. + pub fn from_trap_context(trap_cx: &TrapContext) -> Self { + Self { + gregs: trap_cx.export_signal_gprs(), + fpstate: FpState::from_trap_context(trap_cx), + } + } + + /// Restore a Linux-compatible machine context into the saved trap context. + pub fn apply_to_trap_context(&self, trap_cx: &mut TrapContext) { + trap_cx.import_signal_gprs(&self.gregs); + self.fpstate.apply_to_trap_context(trap_cx); + } +} + /// ucontext_t structure #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/os/src/signal/mod.rs b/os/src/signal/mod.rs index f65f22f1..d1b437c4 100644 --- a/os/src/signal/mod.rs +++ b/os/src/signal/mod.rs @@ -237,11 +237,7 @@ pub fn handle_signals() -> Option { .bits() }; - let mut mcontext = MContext { - gregs: trap_cx.export_signal_gprs(), - fpstate: FpState::default(), - }; - trap_cx.copy_fp_state_to(&mut mcontext.fpstate.fpregs, &mut mcontext.fpstate.fcsr); + let mut mcontext = MContext::from_trap_context(trap_cx); // Syscall restart: if the signal interrupted a syscall that returned -EINTR // and SA_RESTART is set, back up PC to the ecall instruction and restore diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index c4d2c5c4..a28fb449 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -7,7 +7,7 @@ use crate::timer::get_time_ns; use crate::{ config::PAGE_SIZE, fs::{canonicalize, open_file, open_file_at, File, OpenFlags}, - hart::hartid, + hal::hartid, ipc::{self, IPC_RMID}, mm::{translated_ref, translated_str, PageFaultAccess}, task::{ diff --git a/os/src/syscall/sched.rs b/os/src/syscall/sched.rs index 6667927c..25b14a45 100644 --- a/os/src/syscall/sched.rs +++ b/os/src/syscall/sched.rs @@ -4,7 +4,7 @@ use crate::syscall::{read_pod_from_user, write_bytes_to_user, write_pod_to_user, use crate::syscall_body; use crate::{ config::MAX_HARTS, - hart::hartid, + hal::hartid, mm::{online_mask as online_hart_mask, translated_byte_buffer, translated_ref}, sched::{ enqueue_task_on, has_runnable_task_at_or_above, nice_to_weight, pid2process, remove_task, diff --git a/os/src/syscall/signal.rs b/os/src/syscall/signal.rs index b7cd83c3..06b9f2c0 100644 --- a/os/src/syscall/signal.rs +++ b/os/src/syscall/signal.rs @@ -405,7 +405,7 @@ pub fn sys_sigreturn() -> isize { // Restore registers from mcontext let mcontext = &ucontext.uc_mcontext; - trap_cx.import_signal_gprs(&mcontext.gregs); + mcontext.apply_to_trap_context(trap_cx); debug!( "sys_sigreturn: restored sepc={:#x}, a0={:#x}", @@ -413,9 +413,6 @@ pub fn sys_sigreturn() -> isize { trap_cx.syscall_ret() ); - // Restore floating-point registers - trap_cx.restore_fp_state(&mcontext.fpstate.fpregs, mcontext.fpstate.fcsr); - // Return the original a0 value (which was saved in the trap context) Ok(trap_cx.syscall_ret() as isize) }) diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index f01181f3..74f6c30a 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -504,7 +504,7 @@ pub fn check_itimers_of_all_processes(now_raw: usize, now_realtime_ns: u64) { if process::armed_itimers_count() == 0 { return; } - if crate::hart::hartid() != 0 { + if crate::hal::hartid() != 0 { return; } let processes: Vec> = { diff --git a/os/src/task/process.rs b/os/src/task/process.rs index 4afbb15d..ca004547 100644 --- a/os/src/task/process.rs +++ b/os/src/task/process.rs @@ -16,6 +16,7 @@ use crate::mm::{ MapPermission, MemorySet, MmError, PageFaultAccess, PageFaultHandled, ShootdownKind, UserSpaceLayout, VirtAddr, Vma, KERNEL_SPACE, }; +use crate::hal::traits::AddressSpaceToken; use crate::sync::{Condvar, DeadlockDetector, Mutex, Semaphore, SpinNoIrqLock, SpinNoIrqLockGuard}; use crate::syscall::errno::ERRNO; use crate::syscall::{write_pod_to_process_user, ResourceLimits}; @@ -645,8 +646,8 @@ fn load_process_image( impl ProcessControlBlockInner { #[allow(unused)] - /// get the address of app's page table - pub fn get_user_token(&self) -> usize { +/// get the current user address-space token + pub fn get_user_token(&self) -> AddressSpaceToken { self.memory_set.token() } fn nofile_limit(&self) -> usize { @@ -1109,7 +1110,7 @@ impl ProcessControlBlock { parent_token, parent_mask ); - shootdown(parent_mask, ShootdownKind::AddressSpace { satp: parent_token }); + shootdown(parent_mask, ShootdownKind::AddressSpace { token: parent_token }); } debug!( "[cow] clone_process created child process: parent_pid={} child_pid={}", @@ -1593,7 +1594,7 @@ impl ProcessControlBlock { token, mask ); - shootdown(mask, ShootdownKind::AddressSpace { satp: token }); + shootdown(mask, ShootdownKind::AddressSpace { token }); } ok } @@ -1698,7 +1699,7 @@ impl ProcessControlBlock { pub fn enter_kernel(&self, now: usize) { let mut inner = self.inner.lock(); // trap 入口已经切到内核页表并做过本地 sfence.vma,此 hart 不再持有该用户 mm。 - inner.memory_set.mark_user_unloaded(crate::hart::hartid()); + inner.memory_set.mark_user_unloaded(crate::hal::hartid()); match inner.accounting_state { CpuAccountingState::User => { inner.user_time = inner @@ -1725,7 +1726,7 @@ impl ProcessControlBlock { inner.accounting_state = CpuAccountingState::User; inner.accounting_timestamp = now; // 即将跳回用户态,后续其他 hart 修改该 mm 时需要把当前 hart 作为 shootdown 目标。 - inner.memory_set.mark_user_loaded(crate::hart::hartid()); + inner.memory_set.mark_user_loaded(crate::hal::hartid()); } /// Flush the current running slice into the corresponding accumulator. diff --git a/os/src/task/task.rs b/os/src/task/task.rs index 484ed802..4633f777 100644 --- a/os/src/task/task.rs +++ b/os/src/task/task.rs @@ -5,6 +5,7 @@ use super::wait_queue::WaitQueueHandle; use super::{kstack_alloc, KernelStack, ProcessControlBlock, SigInfo, SignalBit, MAX_SIG}; use crate::mm::MmError; use crate::config::MAX_HARTS; +use crate::hal::traits::AddressSpaceToken; use crate::mm::PhysPageNum; use crate::sched::{ReschedReason, SchedAttr, SchedPolicy, TaskContext, NICE_0_LOAD}; use crate::sync::{SpinNoIrqLock, SpinNoIrqLockGuard}; @@ -147,8 +148,8 @@ impl TaskControlBlock { pub fn inner_exclusive_access(&self) -> SpinNoIrqLockGuard<'_, TaskControlBlockInner> { self.inner.lock() } - /// Get the address of app's page table - pub fn get_user_token(&self) -> usize { + /// Get the current user address-space token for this task. + pub fn get_user_token(&self) -> AddressSpaceToken { let process = self.process.upgrade().unwrap(); let inner = process.inner_exclusive_access(); inner.memory_set.token() diff --git a/os/src/timer.rs b/os/src/timer.rs index 3e401a42..e028af38 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{AtomicI64, Ordering as AtomicOrdering}; use crate::config::CLOCK_FREQ; use crate::config::MAX_HARTS; use crate::drivers::rtc; -use crate::hart::hartid; +use crate::hal::hartid; use crate::poll::{self, PollTimerTag}; use crate::net::{handle_socket_wait_timeout, SocketTimerTag}; use crate::signal::{handle_signal_wait_timeout, SignalTimerTag}; diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index a37e14be..29e7ffe9 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -15,7 +15,7 @@ mod context; use crate::config::{PAGE_SIZE, TRAMPOLINE}; -use crate::hart::hartid; +use crate::hal::hartid; use crate::mm::{handle_ipi, MmError, PageFaultAccess, PageFaultHandled}; use crate::signal::{SignalBit, handle_signals}; use crate::syscall::{syscall, syscall_supports_sa_restart}; From 7a861f970a4e03267d8633ea94473c2b57c2f774 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 8 Jun 2026 10:17:48 +0800 Subject: [PATCH 04/19] refactor: fourth commit of HAL: moved `entry.asm`, extracted `ArchTrapContext`, `ArchPaging`. --- os/src/{ => arch/riscv}/entry.asm | 0 os/src/arch/riscv/entry.rs | 5 ++ os/src/arch/riscv/hart.rs | 28 +++++++++- os/src/arch/riscv/mod.rs | 4 +- os/src/arch/riscv/switch.rs | 5 ++ os/src/arch/riscv/trap.rs | 89 +++++++++++++++++++++++++++++- os/src/console.rs | 7 +-- os/src/drivers/block/virtio_blk.rs | 3 +- os/src/hal/mod.rs | 81 ++++++++++++++++++++++++++- os/src/hal/traits.rs | 55 ++++++++++++++++++ os/src/main.rs | 9 +-- os/src/mm/heap_allocator.rs | 15 ++--- os/src/mm/memory_set.rs | 30 +++++----- os/src/mm/page_table.rs | 4 +- os/src/mm/tlb_shootdown.rs | 10 +--- os/src/sched/processor.rs | 6 +- os/src/sched/switch.rs | 3 - os/src/sync/spin.rs | 8 +-- os/src/sync/up.rs | 24 ++++---- os/src/syscall/mman.rs | 6 +- os/src/trap/context.rs | 48 +++++++--------- os/src/trap/mod.rs | 12 ++-- 22 files changed, 334 insertions(+), 118 deletions(-) rename os/src/{ => arch/riscv}/entry.asm (100%) create mode 100644 os/src/arch/riscv/entry.rs create mode 100644 os/src/arch/riscv/switch.rs diff --git a/os/src/entry.asm b/os/src/arch/riscv/entry.asm similarity index 100% rename from os/src/entry.asm rename to os/src/arch/riscv/entry.asm diff --git a/os/src/arch/riscv/entry.rs b/os/src/arch/riscv/entry.rs new file mode 100644 index 00000000..193d5d27 --- /dev/null +++ b/os/src/arch/riscv/entry.rs @@ -0,0 +1,5 @@ +//! RISC-V kernel entry assembly. + +use core::arch::global_asm; + +global_asm!(include_str!("entry.asm")); diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs index bdcf07f1..e3ee6454 100644 --- a/os/src/arch/riscv/hart.rs +++ b/os/src/arch/riscv/hart.rs @@ -2,6 +2,7 @@ use core::arch::asm; use crate::hal::traits::HartId; +use riscv::{asm::wfi, register::{mstatus::FS, sstatus}}; /// RISC-V implementation of [`HartId`](crate::hal::traits::HartId) via the `tp` register. pub struct RiscvHartId; @@ -18,4 +19,29 @@ impl HartId for RiscvHartId { unsafe fn init(id: usize) { asm!("mv tp, {}", in(reg) id); } -} \ No newline at end of file + + #[inline] + unsafe fn enable_fp() { + sstatus::set_fs(FS::Initial); + } + + #[inline] + fn irqs_enabled() -> bool { + sstatus::read().sie() + } + + #[inline] + unsafe fn disable_irqs() { + sstatus::clear_sie(); + } + + #[inline] + unsafe fn enable_irqs() { + sstatus::set_sie(); + } + + #[inline] + unsafe fn wait_for_interrupt() { + wfi(); + } +} diff --git a/os/src/arch/riscv/mod.rs b/os/src/arch/riscv/mod.rs index 0c409c30..65213789 100644 --- a/os/src/arch/riscv/mod.rs +++ b/os/src/arch/riscv/mod.rs @@ -3,9 +3,11 @@ pub mod address; pub mod hart; +mod entry; pub mod paging; +mod switch; pub mod trap; pub use hart::RiscvHartId; pub use paging::Sv39Paging; -pub use trap::{RiscvInterruptControl, RiscvTrapMachine}; +pub use trap::{RiscvInterruptControl, RiscvTrapContextAbi, RiscvTrapMachine}; diff --git a/os/src/arch/riscv/switch.rs b/os/src/arch/riscv/switch.rs new file mode 100644 index 00000000..b7d25ddc --- /dev/null +++ b/os/src/arch/riscv/switch.rs @@ -0,0 +1,5 @@ +//! RISC-V task-switch assembly entrypoints. + +use core::arch::global_asm; + +global_asm!(include_str!("switch.S")); diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs index 98b10962..d69ddb46 100644 --- a/os/src/arch/riscv/trap.rs +++ b/os/src/arch/riscv/trap.rs @@ -1,13 +1,15 @@ //! RISC-V interrupt control, implementing [`InterruptControl`](crate::hal::traits::InterruptControl). -use core::arch::asm; +use core::arch::{asm, global_asm}; use riscv::register::{ mtvec::TrapMode, scause::{self, Exception, Interrupt, Trap}, sie, stval, stvec, }; use crate::config::TRAMPOLINE; -use crate::hal::traits::{InterruptControl, TrapCause, TrapInfo, TrapMachine}; +use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; + +global_asm!(include_str!("../../trap/trap.S")); /// RISC-V implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). pub struct RiscvInterruptControl; @@ -15,6 +17,9 @@ pub struct RiscvInterruptControl; /// RISC-V implementation of trap decoding and user-return operations. pub struct RiscvTrapMachine; +/// RISC-V register-layout helpers for the common [`TrapContext`](crate::trap::TrapContext). +pub struct RiscvTrapContextAbi; + /// 用户态 `rt_sigreturn` trampoline 机器码。 const USER_VDSO_CODE: [u8; 8] = [ 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 @@ -88,3 +93,83 @@ impl TrapMachine for RiscvTrapMachine { &USER_VDSO_CODE } } + +impl TrapContextAbi for RiscvTrapContextAbi { + type Status = riscv::register::sstatus::Sstatus; + + fn initial_user_status() -> Self::Status { + let mut status = riscv::register::sstatus::read(); + status.set_spp(riscv::register::sstatus::SPP::User); + status + } + + fn user_pc(_gprs: &[usize; 32], _status: Self::Status, pc: usize) -> usize { + pc + } + + fn set_user_pc( + _gprs: &mut [usize; 32], + _status: &mut Self::Status, + pc_slot: &mut usize, + pc: usize, + ) { + *pc_slot = pc; + } + + fn user_sp(gprs: &[usize; 32]) -> usize { + gprs[2] + } + + fn set_user_sp(gprs: &mut [usize; 32], sp: usize) { + gprs[2] = sp; + } + + fn ra(gprs: &[usize; 32]) -> usize { + gprs[1] + } + + fn set_ra(gprs: &mut [usize; 32], ra: usize) { + gprs[1] = ra; + } + + fn tls(gprs: &[usize; 32]) -> usize { + gprs[4] + } + + fn set_tls(gprs: &mut [usize; 32], tls: usize) { + gprs[4] = tls; + } + + fn syscall_nr(gprs: &[usize; 32]) -> usize { + gprs[17] + } + + fn syscall_args(gprs: &[usize; 32]) -> [usize; 6] { + [gprs[10], gprs[11], gprs[12], gprs[13], gprs[14], gprs[15]] + } + + fn syscall_ret(gprs: &[usize; 32]) -> usize { + gprs[10] + } + + fn set_syscall_ret(gprs: &mut [usize; 32], ret: usize) { + gprs[10] = ret; + } + + fn set_user_arg(gprs: &mut [usize; 32], index: usize, value: usize) { + gprs[10 + index] = value; + } + + fn export_signal_gprs(gprs: &[usize; 32], pc: usize) -> [usize; 32] { + let mut exported = [0usize; 32]; + exported[0] = pc; + exported[1..].copy_from_slice(&gprs[1..]); + exported + } + + fn import_signal_gprs(gprs: &mut [usize; 32], pc_slot: &mut usize, signal_gprs: &[usize; 32]) { + gprs[0] = 0; + gprs[1..].copy_from_slice(&signal_gprs[1..]); + *pc_slot = signal_gprs[0]; + } +} diff --git a/os/src/console.rs b/os/src/console.rs index 34511aca..f4d886f2 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -3,7 +3,6 @@ use crate::{drivers::chardev::{CharDevice, UART}}; use core::fmt::{self, Write}; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; /// 串行化所有 hart 的控制台输出,避免多个 hart 同时逐字符写 UART 时互相穿插。 static CONSOLE_LOCK: AtomicBool = AtomicBool::new(false); @@ -34,8 +33,8 @@ struct ConsoleGuard { impl ConsoleGuard { /// 获取控制台输出锁。 fn lock() -> Self { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while CONSOLE_LOCK .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_err() @@ -50,7 +49,7 @@ impl Drop for ConsoleGuard { fn drop(&mut self) { CONSOLE_LOCK.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index bd06668b..deb76224 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -2,7 +2,6 @@ use super::BlockDevice; use crate::sync::SpinNoIrqLock; use crate::task::{current_task, WaitQueueKeyed, WaitReason}; use core::hint::spin_loop; -use riscv::register::sstatus; use virtio_drivers::{ device::blk::{BlkReq, BlkResp, RespStatus, VirtIOBlk}, transport::mmio::MmioTransport, @@ -146,7 +145,7 @@ impl VirtIOBlock { fn wait_token(&self, token: u16) { // TODO Enable kernel interrupt in more cases. - let irq_disabled = !sstatus::read().sie(); + let irq_disabled = !crate::hal::local_irqs_enabled(); if current_task().is_none() || irq_disabled { while !self.token_ready(token) { spin_loop(); diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index 1ed22115..8439373c 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -3,12 +3,13 @@ pub mod traits; -use crate::hal::traits::HartId; +use crate::hal::traits::{AddressSpaceToken, HartId, PagingArch}; #[cfg(target_arch = "riscv64")] pub use crate::arch::riscv::{ RiscvHartId as ArchHart, RiscvInterruptControl as ArchInterrupt, - RiscvTrapMachine as ArchTrapMachine, + RiscvTrapContextAbi as ArchTrapContextAbi, + RiscvTrapMachine as ArchTrapMachine, Sv39Paging as ArchPaging, }; #[cfg(feature = "platform-qemu-virt")] @@ -18,4 +19,78 @@ pub use crate::platform::qemu_virt::SbiPlatform as Plat; #[inline] pub fn hartid() -> usize { ArchHart::current() -} \ No newline at end of file +} + +/// Initialize the current hart-local state, including floating-point availability. +#[inline] +pub unsafe fn init_with_hartid(hart_id: usize) -> usize { + ArchHart::init(hart_id); + ArchHart::enable_fp(); + hart_id +} + +/// Enable the local floating-point unit on the current hart. +#[inline] +pub unsafe fn enable_fp() { + ArchHart::enable_fp(); +} + +/// Return whether local interrupts are currently enabled. +#[inline] +pub fn local_irqs_enabled() -> bool { + ArchHart::irqs_enabled() +} + +/// Disable local interrupts on the current hart. +#[inline] +pub unsafe fn disable_local_irqs() { + ArchHart::disable_irqs(); +} + +/// Enable local interrupts on the current hart. +#[inline] +pub unsafe fn enable_local_irqs() { + ArchHart::enable_irqs(); +} + +/// Wait for the next interrupt/event on the current hart. +#[inline] +pub unsafe fn wait_for_interrupt() { + ArchHart::wait_for_interrupt(); +} + +/// Execute the architecture-specific scheduler idle wait sequence. +#[inline] +pub unsafe fn enable_irqs_and_wait() { + ArchHart::enable_irqs_and_wait(); +} + +/// Build an architecture-specific address-space token from a root page-table PPN. +#[inline] +pub fn make_address_space_token(root_ppn: usize) -> AddressSpaceToken { + ArchPaging::make_token(root_ppn) +} + +/// Extract the root page-table PPN from an architecture-specific address-space token. +#[inline] +pub fn root_ppn_from_token(token: AddressSpaceToken) -> usize { + ArchPaging::root_ppn(token) +} + +/// Activate the given address space on the current hart. +#[inline] +pub unsafe fn activate_address_space(token: AddressSpaceToken) { + ArchPaging::activate_token(token); +} + +/// Read the current hart's active address-space token. +#[inline] +pub unsafe fn current_address_space_token() -> AddressSpaceToken { + ArchPaging::current_token() +} + +/// Flush the local TLB on the current hart. +#[inline] +pub unsafe fn flush_tlb() { + ArchPaging::flush_tlb(); +} diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index 4eae2555..9d5a4ac4 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -64,6 +64,61 @@ pub trait HartId { fn current() -> usize; /// Write hart id to arch register at boot. unsafe fn init(id: usize); + /// Enable the local floating-point unit for subsequent kernel/user execution. + unsafe fn enable_fp(); + /// Return whether local interrupts are currently enabled on this hart. + fn irqs_enabled() -> bool; + /// Disable local interrupts on this hart. + unsafe fn disable_irqs(); + /// Enable local interrupts on this hart. + unsafe fn enable_irqs(); + /// Wait for the next interrupt/event while keeping the current interrupt state. + unsafe fn wait_for_interrupt(); + /// Enter the architecture-specific idle wait sequence used by the scheduler. + unsafe fn enable_irqs_and_wait() { + Self::enable_irqs(); + Self::wait_for_interrupt(); + Self::disable_irqs(); + } +} + +/// Architecture-specific user trap-context ABI helpers. +pub trait TrapContextAbi { + /// Saved status-register representation carried inside the trap context. + type Status: Copy; + + /// Return a status value prepared for first return to user mode. + fn initial_user_status() -> Self::Status; + /// Return the saved user PC from the trap context. + fn user_pc(gprs: &[usize; 32], status: Self::Status, pc: usize) -> usize; + /// Set the saved user PC in the trap context. + fn set_user_pc(gprs: &mut [usize; 32], status: &mut Self::Status, pc_slot: &mut usize, pc: usize); + /// Return the saved user SP from the trap context. + fn user_sp(gprs: &[usize; 32]) -> usize; + /// Set the saved user SP in the trap context. + fn set_user_sp(gprs: &mut [usize; 32], sp: usize); + /// Return the saved return-address register. + fn ra(gprs: &[usize; 32]) -> usize; + /// Set the saved return-address register. + fn set_ra(gprs: &mut [usize; 32], ra: usize); + /// Return the saved TLS/thread-pointer register. + fn tls(gprs: &[usize; 32]) -> usize; + /// Set the saved TLS/thread-pointer register. + fn set_tls(gprs: &mut [usize; 32], tls: usize); + /// Return the saved syscall number. + fn syscall_nr(gprs: &[usize; 32]) -> usize; + /// Return the saved syscall arguments. + fn syscall_args(gprs: &[usize; 32]) -> [usize; 6]; + /// Return the saved syscall return value. + fn syscall_ret(gprs: &[usize; 32]) -> usize; + /// Set the saved syscall return value. + fn set_syscall_ret(gprs: &mut [usize; 32], ret: usize); + /// Set one syscall/user argument register. + fn set_user_arg(gprs: &mut [usize; 32], index: usize, value: usize); + /// Export the Linux-compatible signal GPR layout. + fn export_signal_gprs(gprs: &[usize; 32], pc: usize) -> [usize; 32]; + /// Import the Linux-compatible signal GPR layout back into the trap context. + fn import_signal_gprs(gprs: &mut [usize; 32], pc_slot: &mut usize, signal_gprs: &[usize; 32]); } /// Opaque address-space activation token used by the current architecture. diff --git a/os/src/main.rs b/os/src/main.rs index 9d4b1350..3da0384e 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -11,7 +11,7 @@ //! - [`fs`]: Separate user from file system with some structures //! //! The operating system also starts in this module. Kernel code starts -//! executing from `entry.asm`, after which [`rust_main()`] is called to +//! executing from the architecture entry assembly, after which [`rust_main()`] is called to //! initialize various pieces of functionality. (See its source code for //! details.) //! @@ -62,13 +62,8 @@ pub mod task; pub mod timer; pub mod trap; -use core::arch::global_asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use crate::hal::traits::HartId; -use crate::hal::ArchHart; - -global_asm!(include_str!("entry.asm")); const ANSI_RESET: &str = "\u{1b}[0m"; const ANSI_FRAME: &str = "\u{1b}[38;5;45m"; @@ -340,7 +335,7 @@ fn secondary_hart_main(hart_id: usize) -> ! { /// 第一个进入该入口的 hart 会成为 bootstrap hart,负责一次性全局初始化 /// 并进入调度器;其他 hart 等待 bootstrap 完成后只做本地初始化并进入 idle。 pub fn rust_main(hart_id: usize) -> ! { - unsafe { ArchHart::init(hart_id) }; + unsafe { crate::hal::init_with_hartid(hart_id) }; unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); } diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 3c93d33a..0c240314 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -9,7 +9,6 @@ use buddy_system_allocator::LockedHeap; use core::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use riscv::register::sstatus; #[global_allocator] static HEAP_ALLOCATOR: KernelHeapAllocator = KernelHeapAllocator::new(); @@ -38,8 +37,8 @@ struct HeapIrqGuard { impl HeapIrqGuard { #[inline] fn new() -> Self { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; Self { sie_was_enabled } } } @@ -48,7 +47,7 @@ impl Drop for HeapIrqGuard { #[inline] fn drop(&mut self) { if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } @@ -321,9 +320,7 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { *entry = PageTableEntry::new(frame.ppn, PTEFlags::R | PTEFlags::W | PTEFlags::V); core::mem::forget(frame); } - unsafe { - core::arch::asm!("sfence.vma"); - } + unsafe { crate::hal::flush_tlb() }; true } @@ -340,9 +337,7 @@ fn rollback_heap_pages(l1_ppn: PhysPageNum, start_va: usize, pages: usize) { } } } - unsafe { - core::arch::asm!("sfence.vma"); - } + unsafe { crate::hal::flush_tlb() }; } #[allow(unused)] diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 81ce0b4a..abe2807c 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -14,8 +14,7 @@ use crate::fs::{ FileDescription, }; use crate::hal::ArchTrapMachine; -use crate::hal::traits::{AddressSpaceToken, PagingArch, TrapMachine}; -use crate::arch::riscv::Sv39Paging; +use crate::hal::traits::{AddressSpaceToken, TrapMachine}; use crate::sync::SpinNoIrqLock; use crate::task::ProcessControlBlock; use crate::syscall::errno::ERRNO; @@ -24,7 +23,6 @@ use alloc::string::String; use alloc::sync::{Arc, Weak}; use alloc::vec::Vec; use core::fmt::Write; -use core::arch::asm; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use fs::Inode; use lazy_static::*; @@ -462,7 +460,7 @@ impl MemorySet { // 本地 hart 可能刚刚使用过被拆除的翻译,必须先清掉本地 TLB; // 远端 hart 的同步由调用方构造 `DeferredUserReclaim` 后在锁外完成。 unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } } @@ -1115,7 +1113,7 @@ impl MemorySet { } if parent_tlb_needs_flush { unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!("[cow] fork flush parent local TLB after write-protecting shared private pages"); } @@ -1124,7 +1122,7 @@ impl MemorySet { /// Change page table by activating the current architecture token. pub fn activate(&self) { unsafe { - Sv39Paging::activate_token(self.page_table.token()); + crate::hal::activate_address_space(self.page_table.token()); } } /// Translate a virtual page number to a page table entry @@ -1150,7 +1148,7 @@ impl MemorySet { } self.vmas.clear(); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } } @@ -1249,7 +1247,7 @@ impl MemorySet { if let Some(area) = self.vmas.get_mut(&start.floor()) { area.shrink_present_to(&mut self.page_table, new_end.ceil()); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } true } else { @@ -1314,7 +1312,7 @@ impl MemorySet { ); self.register_vma_metadata(Vma::new_anonymous(start_va, end_va, permission))?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(()) } @@ -1343,7 +1341,7 @@ impl MemorySet { None, )?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(()) } @@ -1545,7 +1543,7 @@ impl MemorySet { } self.map_private_page_in_vma(vpn)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } Ok(PageFaultHandled::Handled) } @@ -1585,7 +1583,7 @@ impl MemorySet { } self.page_table.map(plan.vpn, ppn, pte_flags)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[mmap] committed MAP_SHARED fault: vpn={:#x} page_idx={} ppn={:#x} writable={} path={:?}", @@ -1662,7 +1660,7 @@ impl MemorySet { } self.page_table.map(plan.vpn, ppn, pte_flags)?; unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[cow] install MAP_PRIVATE readonly cache page: vpn={:#x} page_idx={} ppn={:#x} access={:?} path={:?}", @@ -1710,7 +1708,7 @@ impl MemorySet { // 首次写 fault 时立即把 page cache 页记脏,避免等待 teardown 才传播脏状态。 mark_cached_page_dirty(&page); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[mmap] shared write-notify fault: vpn={:#x} ppn={:#x} path={:?}", @@ -1883,7 +1881,7 @@ impl MemorySet { let src = page_guard.ppn().get_bytes_array(); dst.copy_from_slice(src); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } debug!( "[cow] materialize MAP_PRIVATE page on first write fault: vpn={:#x} page_idx={} dst_ppn={:#x} path={:?}", @@ -2055,7 +2053,7 @@ impl MemorySet { self.rebuild_vmas_from_vec(new_areas); self.merge_adjacent_vmas(); unsafe { - asm!("sfence.vma"); + crate::hal::flush_tlb(); } true } diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 32ee8c8d..83d973b5 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -92,7 +92,7 @@ impl PageTable { /// Temporarily used to get arguments from user space. pub fn from_token(token: AddressSpaceToken) -> Self { Self { - root_ppn: PhysPageNum::from(Sv39Paging::root_ppn(token)), + root_ppn: PhysPageNum::from(crate::hal::root_ppn_from_token(token)), frames: Vec::new(), } } @@ -250,7 +250,7 @@ impl PageTable { } /// get the token from the page table pub fn token(&self) -> AddressSpaceToken { - Sv39Paging::make_token(self.root_ppn.0) + crate::hal::make_address_space_token(self.root_ppn.0) } } diff --git a/os/src/mm/tlb_shootdown.rs b/os/src/mm/tlb_shootdown.rs index 71b24b92..6f4d9ec3 100644 --- a/os/src/mm/tlb_shootdown.rs +++ b/os/src/mm/tlb_shootdown.rs @@ -1,13 +1,11 @@ //! TLB shootdown state and deferred recycle helpers. use super::{kernel_token, FrameTracker}; -use crate::arch::riscv::Sv39Paging; use crate::hal::hartid; -use crate::hal::traits::{AddressSpaceToken, PagingArch}; +use crate::hal::traits::AddressSpaceToken; use crate::sbi::send_ipi_mask; use crate::sync::{SpinLock, SpinNoIrqLock}; use alloc::vec::Vec; -use core::arch::asm; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use lazy_static::*; @@ -419,7 +417,7 @@ fn perform_local_tlb_shootdown(kind: ShootdownKind) { // 目标 hart 可能是在用户态收到 IPI 后刚切入内核,因此当前 satp // 可能已经是 kernel_token;此时仍然要完成本地 flush 并回 ack。 // TODO:引入 ASID 后,应避免把 kernel_token 情况退化成全量 flush。 - let current_token = unsafe { Sv39Paging::current_token() }; + let current_token = unsafe { crate::hal::current_address_space_token() }; if current_token == target_token || current_token == kernel_token() { local_sfence_vma_all(); } @@ -437,7 +435,5 @@ fn shootdown_kind_name(kind: ShootdownKind) -> &'static str { /// 在当前 hart 上执行一次全量 `sfence.vma`。 fn local_sfence_vma_all() { - unsafe { - asm!("sfence.vma"); - } + unsafe { crate::hal::flush_tlb() } } diff --git a/os/src/sched/processor.rs b/os/src/sched/processor.rs index 27165cdf..c3620e49 100644 --- a/os/src/sched/processor.rs +++ b/os/src/sched/processor.rs @@ -7,7 +7,7 @@ use super::__switch; use super::{add_task, pick_next_task, TaskContext}; use crate::config::MAX_HARTS; -use crate::hal::hartid; +use crate::hal::{enable_irqs_and_wait, hartid}; use crate::hal::traits::AddressSpaceToken; use crate::sync::SpinNoIrqLock; use crate::task::{ProcessControlBlock, SchedPolicy, TaskControlBlock, TaskStatus, INITPROC}; @@ -132,9 +132,7 @@ pub(crate) fn run_tasks() { crate::trap::set_kernel_trap_entry(); - unsafe { riscv::register::sstatus::set_sie() }; - unsafe { riscv::asm::wfi() }; - unsafe { riscv::register::sstatus::clear_sie() }; + unsafe { enable_irqs_and_wait() }; } } } diff --git a/os/src/sched/switch.rs b/os/src/sched/switch.rs index bf741adc..c8e338b6 100644 --- a/os/src/sched/switch.rs +++ b/os/src/sched/switch.rs @@ -1,8 +1,5 @@ //!provides __switch asm function to switch between two task contexts [`TaskContext`] use super::context::TaskContext; -use core::arch::global_asm; - -global_asm!(include_str!("../arch/riscv/switch.S")); extern "C" { /// Switch to the context of `next_task_cx_ptr`, saving the current context diff --git a/os/src/sync/spin.rs b/os/src/sync/spin.rs index e948e184..bfa69520 100644 --- a/os/src/sync/spin.rs +++ b/os/src/sync/spin.rs @@ -15,8 +15,6 @@ use core::cell::UnsafeCell; use core::ops::{Deref, DerefMut}; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; - // --------------------------------------------------------------------------- // SpinLock – plain spinlock (no interrupt masking) // --------------------------------------------------------------------------- @@ -118,8 +116,8 @@ impl SpinNoIrqLock { /// Acquire the lock with interrupts disabled. pub fn lock(&self) -> SpinNoIrqLockGuard<'_, T> { // Save the current SIE bit state and then disable. - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -172,7 +170,7 @@ impl Drop for SpinNoIrqLockGuard<'_, T> { fn drop(&mut self) { self.lock.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } diff --git a/os/src/sync/up.rs b/os/src/sync/up.rs index 543f6729..42609d84 100644 --- a/os/src/sync/up.rs +++ b/os/src/sync/up.rs @@ -9,8 +9,6 @@ use core::cell::UnsafeCell; use core::ops::{Deref, DerefMut}; use core::sync::atomic::{AtomicBool, Ordering}; -use riscv::register::sstatus; - /// Wrap a static data structure inside a spinlock with interrupt masking. /// /// `exclusive_access()` returns a guard that holds the lock and disables @@ -41,8 +39,8 @@ impl UPSafeCell { /// /// Disables supervisor interrupts and spins until the lock is acquired. pub fn exclusive_access(&self) -> UPSafeCellGuard<'_, T> { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -50,10 +48,10 @@ impl UPSafeCell { .is_err() { if sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } core::hint::spin_loop(); - unsafe { sstatus::clear_sie() }; + unsafe { crate::hal::disable_local_irqs() }; } UPSafeCellGuard { @@ -86,7 +84,7 @@ impl Drop for UPSafeCellGuard<'_, T> { fn drop(&mut self) { self.cell.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } } @@ -115,8 +113,8 @@ impl UPIntrFreeCell { /// Get exclusive access with interrupts disabled + spinlock. pub fn exclusive_access(&self) -> UPIntrFreeCellRefMut<'_, T> { - let sie_was_enabled = sstatus::read().sie(); - unsafe { sstatus::clear_sie() }; + let sie_was_enabled = crate::hal::local_irqs_enabled(); + unsafe { crate::hal::disable_local_irqs() }; while self .locked @@ -124,10 +122,10 @@ impl UPIntrFreeCell { .is_err() { if sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } core::hint::spin_loop(); - unsafe { sstatus::clear_sie() }; + unsafe { crate::hal::disable_local_irqs() }; } UPIntrFreeCellRefMut { @@ -162,7 +160,7 @@ impl Drop for UPIntrFreeCellRefMut<'_, T> { fn drop(&mut self) { self.cell.locked.store(false, Ordering::Release); if self.sie_was_enabled { - unsafe { sstatus::set_sie() }; + unsafe { crate::hal::enable_local_irqs() }; } } -} \ No newline at end of file +} diff --git a/os/src/syscall/mman.rs b/os/src/syscall/mman.rs index 56b274f7..183c15b0 100644 --- a/os/src/syscall/mman.rs +++ b/os/src/syscall/mman.rs @@ -265,9 +265,9 @@ pub fn sys_brk(addr: usize) -> isize { "sys_brk: pid={} addr={:#x} tp={:#x} sp={:#x} sepc={:#x}", pid, addr, - cx.x[4], - cx.x[2], - cx.sepc + cx.tls(), + cx.user_sp(), + cx.user_pc() ); current_process().set_program_brk(addr) as isize } diff --git a/os/src/trap/context.rs b/os/src/trap/context.rs index aa2d611d..7ca29451 100644 --- a/os/src/trap/context.rs +++ b/os/src/trap/context.rs @@ -1,5 +1,6 @@ //! Implementation of [`TrapContext`] -use riscv::register::sstatus::{self, Sstatus, SPP}; +use crate::hal::ArchTrapContextAbi; +use crate::hal::traits::TrapContextAbi; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -8,7 +9,7 @@ pub struct TrapContext { /// General-Purpose Register x0-31 pub x: [usize; 32], /// Supervisor Status Register - pub sstatus: Sstatus, + pub sstatus: ::Status, /// Supervisor Exception Program Counter pub sepc: usize, /// 当前任务上次返回用户态前所在的 hart id。 @@ -53,12 +54,12 @@ impl TrapContext { /// Return the saved user-mode PC. pub fn user_pc(&self) -> usize { - self.sepc + ArchTrapContextAbi::user_pc(&self.x, self.sstatus, self.sepc) } /// Overwrite the saved user-mode PC. pub fn set_user_pc(&mut self, pc: usize) { - self.sepc = pc; + ArchTrapContextAbi::set_user_pc(&mut self.x, &mut self.sstatus, &mut self.sepc, pc); } /// Advance the saved user-mode PC by `delta` bytes. @@ -68,12 +69,12 @@ impl TrapContext { /// Return the saved user stack pointer. pub fn user_sp(&self) -> usize { - self.x[2] + ArchTrapContextAbi::user_sp(&self.x) } /// put the sp(stack pointer) into x\[2\] field of TrapContext pub fn set_sp(&mut self, sp: usize) { - self.x[2] = sp; + ArchTrapContextAbi::set_user_sp(&mut self.x, sp); } /// Set the saved user stack pointer. @@ -83,52 +84,52 @@ impl TrapContext { /// Return the saved return address register. pub fn ra(&self) -> usize { - self.x[1] + ArchTrapContextAbi::ra(&self.x) } /// Set the saved return address register. pub fn set_ra(&mut self, ra: usize) { - self.x[1] = ra; + ArchTrapContextAbi::set_ra(&mut self.x, ra); } /// Return the saved user TLS/thread-pointer register. pub fn tls(&self) -> usize { - self.x[4] + ArchTrapContextAbi::tls(&self.x) } /// Set the saved user TLS/thread-pointer register. pub fn set_tls(&mut self, tls: usize) { - self.x[4] = tls; + ArchTrapContextAbi::set_tls(&mut self.x, tls); } /// Return the architecture syscall number register. pub fn syscall_nr(&self) -> usize { - self.x[17] + ArchTrapContextAbi::syscall_nr(&self.x) } /// Return the six syscall arguments from the saved user context. pub fn syscall_args(&self) -> [usize; 6] { - [self.x[10], self.x[11], self.x[12], self.x[13], self.x[14], self.x[15]] + ArchTrapContextAbi::syscall_args(&self.x) } /// Return the saved syscall return register. pub fn syscall_ret(&self) -> usize { - self.x[10] + ArchTrapContextAbi::syscall_ret(&self.x) } /// Set the saved syscall return register. pub fn set_syscall_ret(&mut self, ret: usize) { - self.x[10] = ret; + ArchTrapContextAbi::set_syscall_ret(&mut self.x, ret); } /// Set one user-call argument register. pub fn set_user_arg(&mut self, index: usize, value: usize) { - self.x[10 + index] = value; + ArchTrapContextAbi::set_user_arg(&mut self.x, index, value); } /// Save the original first syscall argument for possible restart. pub fn save_syscall_arg0_for_restart(&mut self) { - self.orig_a0 = self.x[10]; + self.orig_a0 = self.syscall_ret(); } /// Set the kernel hart id restored by the trap trampoline. @@ -143,17 +144,12 @@ impl TrapContext { /// Export the saved register file using the riscv64 Linux signal ABI layout. pub fn export_signal_gprs(&self) -> [usize; 32] { - let mut gregs = [0usize; 32]; - gregs[0] = self.sepc; - gregs[1..].copy_from_slice(&self.x[1..]); - gregs + ArchTrapContextAbi::export_signal_gprs(&self.x, self.user_pc()) } /// Restore the saved register file using the riscv64 Linux signal ABI layout. pub fn import_signal_gprs(&mut self, gregs: &[usize; 32]) { - self.x[0] = 0; - self.x[1..].copy_from_slice(&gregs[1..]); - self.sepc = gregs[0]; + ArchTrapContextAbi::import_signal_gprs(&mut self.x, &mut self.sepc, gregs); } /// Copy floating-point state into an external signal frame. @@ -176,10 +172,8 @@ impl TrapContext { kernel_sp: usize, trap_handler: usize, ) -> Self { - let mut sstatus = sstatus::read(); - // set CPU privilege to User after trapping back - sstatus.set_spp(SPP::User); - unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial) }; + let sstatus = ArchTrapContextAbi::initial_user_status(); + unsafe { crate::hal::enable_fp() }; let mut cx = Self { x: [0; 32], sstatus, diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 29e7ffe9..9420bf2b 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -3,10 +3,9 @@ //! For rCore, we have a single trap entry point, namely `__alltraps`. At //! initialization in [`init()`], we set the `stvec` CSR to point to it. //! -//! All traps go through `__alltraps`, which is defined in `trap.S`. The -//! assembly language code does just enough work restore the kernel space -//! context, ensuring that Rust code safely runs, and transfers control to -//! [`trap_handler()`]. +//! All traps go through an architecture-defined trampoline. The assembly code +//! does just enough work restore the kernel space context, ensuring that Rust +//! code safely runs, and transfers control to [`trap_handler()`]. //! //! It then calls different functionality based on what exactly the exception //! was. For example, timer interrupts trigger task preemption, and syscalls go @@ -14,7 +13,7 @@ mod context; -use crate::config::{PAGE_SIZE, TRAMPOLINE}; +use crate::config::PAGE_SIZE; use crate::hal::hartid; use crate::mm::{handle_ipi, MmError, PageFaultAccess, PageFaultHandled}; use crate::signal::{SignalBit, handle_signals}; @@ -26,12 +25,9 @@ use crate::task::{ current_trap_cx_user_va, current_user_token, exit_current_and_run_next, }; use crate::timer::{get_realtime_ns, get_time, handle_timer_interrupt}; -use core::arch::global_asm; use crate::hal::{ArchInterrupt, ArchTrapMachine}; use crate::hal::traits::{InterruptControl, TrapCause, TrapMachine}; -global_asm!(include_str!("trap.S")); - /// 输出用户态致命异常现场,区分 fault 地址、用户 PC 与关键寄存器。 fn log_user_fault(reason: &str, access: &str, fault_addr: usize, signal: &str) { let cx = current_trap_cx(); From 37c1b19583d412a401f5a3c816e52f810c2397d0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 8 Jun 2026 10:40:03 +0800 Subject: [PATCH 05/19] refactor: fifth commit of HAL: Page table entries. --- os/src/arch/riscv/paging.rs | 25 ++++- os/src/{trap => arch/riscv}/trap.S | 0 os/src/arch/riscv/trap.rs | 154 ++++++++++++++++++++--------- os/src/hal/mod.rs | 68 ++++++++++++- os/src/hal/traits.rs | 108 ++++++++++++++++---- os/src/mm/address.rs | 40 +++----- os/src/mm/heap_allocator.rs | 106 ++++++++++++-------- os/src/mm/memory_set.rs | 32 ++++-- os/src/mm/mod.rs | 1 + os/src/mm/page_table.rs | 87 +++++++--------- os/src/trap/context.rs | 91 ++++++----------- 11 files changed, 463 insertions(+), 249 deletions(-) rename os/src/{trap => arch/riscv}/trap.S (100%) diff --git a/os/src/arch/riscv/paging.rs b/os/src/arch/riscv/paging.rs index d14477cc..7ff6f6c6 100644 --- a/os/src/arch/riscv/paging.rs +++ b/os/src/arch/riscv/paging.rs @@ -1,6 +1,6 @@ //! SV39 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). -use crate::hal::traits::{AddressSpaceToken, PagingArch}; +use crate::hal::traits::{AddressSpaceToken, PTEFlags, PagingArch}; use crate::mm::PageTableEntry; /// RISC-V Sv39 three-level paging implementation. @@ -8,8 +8,12 @@ pub struct Sv39Paging; impl PagingArch for Sv39Paging { type Entry = PageTableEntry; + const PA_BITS: usize = 56; + const VA_BITS: usize = 39; + const PPN_BITS: usize = Self::PA_BITS - 12; const ROOT_TOKEN_MODE: usize = 8; // MODE=8 → Sv39 const LEVELS: usize = 3; + const INDEX_BITS: usize = 9; fn make_token(root_ppn: usize) -> AddressSpaceToken { Self::ROOT_TOKEN_MODE << 60 | root_ppn @@ -32,4 +36,23 @@ impl PagingArch for Sv39Paging { unsafe fn flush_tlb() { core::arch::asm!("sfence.vma"); } + + fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + ppn << 10 | flags.bits() as usize + } + + fn pte_ppn(entry_bits: usize) -> usize { + entry_bits >> 10 & ((1usize << 44) - 1) + } + + fn pte_flags(entry_bits: usize) -> PTEFlags { + PTEFlags::from_bits_truncate(entry_bits as u16) + } + + fn vpn_index(vpn: usize, level: usize) -> usize { + debug_assert!(level < Self::LEVELS); + let mask = (1usize << Self::INDEX_BITS) - 1; + let shift = (Self::LEVELS - 1 - level) * Self::INDEX_BITS; + (vpn >> shift) & mask + } } diff --git a/os/src/trap/trap.S b/os/src/arch/riscv/trap.S similarity index 100% rename from os/src/trap/trap.S rename to os/src/arch/riscv/trap.S diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs index d69ddb46..2b0146da 100644 --- a/os/src/arch/riscv/trap.rs +++ b/os/src/arch/riscv/trap.rs @@ -2,6 +2,7 @@ use core::arch::{asm, global_asm}; use riscv::register::{ + sstatus::{self, Sstatus, SPP}, mtvec::TrapMode, scause::{self, Exception, Interrupt, Trap}, sie, stval, stvec, @@ -9,7 +10,7 @@ use riscv::register::{ use crate::config::TRAMPOLINE; use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; -global_asm!(include_str!("../../trap/trap.S")); +global_asm!(include_str!("trap.S")); /// RISC-V implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). pub struct RiscvInterruptControl; @@ -20,6 +21,30 @@ pub struct RiscvTrapMachine; /// RISC-V register-layout helpers for the common [`TrapContext`](crate::trap::TrapContext). pub struct RiscvTrapContextAbi; +/// RISC-V trap frame layout shared with `trap.S`. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct RiscvTrapContextFrame { + /// General-purpose registers x0..x31. + pub x: [usize; 32], + /// Saved supervisor status register. + pub sstatus: Sstatus, + /// Saved exception PC. + pub sepc: usize, + /// Kernel hart id restored into `tp` on trap entry. + pub kernel_hartid: usize, + /// Kernel address-space token installed on trap entry. + pub kernel_satp: usize, + /// Kernel stack pointer used on trap entry. + pub kernel_sp: usize, + /// Common Rust trap handler entry. + pub trap_handler: usize, + /// Floating-point registers f0..f31. + pub f: [u64; 32], + /// Floating-point CSR. + pub fcsr: usize, +} + /// 用户态 `rt_sigreturn` trampoline 机器码。 const USER_VDSO_CODE: [u8; 8] = [ 0x93, 0x08, 0xb0, 0x08, // addi a7, zero, 139 @@ -95,81 +120,122 @@ impl TrapMachine for RiscvTrapMachine { } impl TrapContextAbi for RiscvTrapContextAbi { - type Status = riscv::register::sstatus::Sstatus; + type Frame = RiscvTrapContextFrame; + + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: usize, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame { + let mut status = sstatus::read(); + status.set_spp(SPP::User); + let mut frame = RiscvTrapContextFrame { + x: [0; 32], + sstatus: status, + sepc: entry, + kernel_hartid: 0, + kernel_satp: kernel_token, + kernel_sp, + trap_handler, + f: [0; 32], + fcsr: 0, + }; + frame.x[2] = sp; + frame + } - fn initial_user_status() -> Self::Status { - let mut status = riscv::register::sstatus::read(); - status.set_spp(riscv::register::sstatus::SPP::User); - status + fn reg(frame: &Self::Frame, index: usize) -> usize { + frame.x[index] } - fn user_pc(_gprs: &[usize; 32], _status: Self::Status, pc: usize) -> usize { - pc + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize) { + if index != 0 { + frame.x[index] = value; + } + } + + fn user_pc(frame: &Self::Frame) -> usize { + frame.sepc } - fn set_user_pc( - _gprs: &mut [usize; 32], - _status: &mut Self::Status, - pc_slot: &mut usize, - pc: usize, - ) { - *pc_slot = pc; + fn set_user_pc(frame: &mut Self::Frame, pc: usize) { + frame.sepc = pc; } - fn user_sp(gprs: &[usize; 32]) -> usize { - gprs[2] + fn user_sp(frame: &Self::Frame) -> usize { + frame.x[2] } - fn set_user_sp(gprs: &mut [usize; 32], sp: usize) { - gprs[2] = sp; + fn set_user_sp(frame: &mut Self::Frame, sp: usize) { + frame.x[2] = sp; } - fn ra(gprs: &[usize; 32]) -> usize { - gprs[1] + fn ra(frame: &Self::Frame) -> usize { + frame.x[1] } - fn set_ra(gprs: &mut [usize; 32], ra: usize) { - gprs[1] = ra; + fn set_ra(frame: &mut Self::Frame, ra: usize) { + frame.x[1] = ra; } - fn tls(gprs: &[usize; 32]) -> usize { - gprs[4] + fn tls(frame: &Self::Frame) -> usize { + frame.x[4] } - fn set_tls(gprs: &mut [usize; 32], tls: usize) { - gprs[4] = tls; + fn set_tls(frame: &mut Self::Frame, tls: usize) { + frame.x[4] = tls; } - fn syscall_nr(gprs: &[usize; 32]) -> usize { - gprs[17] + fn syscall_nr(frame: &Self::Frame) -> usize { + frame.x[17] } - fn syscall_args(gprs: &[usize; 32]) -> [usize; 6] { - [gprs[10], gprs[11], gprs[12], gprs[13], gprs[14], gprs[15]] + fn syscall_args(frame: &Self::Frame) -> [usize; 6] { + [frame.x[10], frame.x[11], frame.x[12], frame.x[13], frame.x[14], frame.x[15]] } - fn syscall_ret(gprs: &[usize; 32]) -> usize { - gprs[10] + fn syscall_ret(frame: &Self::Frame) -> usize { + frame.x[10] } - fn set_syscall_ret(gprs: &mut [usize; 32], ret: usize) { - gprs[10] = ret; + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize) { + frame.x[10] = ret; } - fn set_user_arg(gprs: &mut [usize; 32], index: usize, value: usize) { - gprs[10 + index] = value; + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize) { + frame.x[10 + index] = value; } - fn export_signal_gprs(gprs: &[usize; 32], pc: usize) -> [usize; 32] { + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize) { + frame.kernel_hartid = hartid; + } + + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize) { + frame.kernel_sp = kernel_sp; + } + + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32] { let mut exported = [0usize; 32]; - exported[0] = pc; - exported[1..].copy_from_slice(&gprs[1..]); + exported[0] = frame.sepc; + exported[1..].copy_from_slice(&frame.x[1..]); exported } - fn import_signal_gprs(gprs: &mut [usize; 32], pc_slot: &mut usize, signal_gprs: &[usize; 32]) { - gprs[0] = 0; - gprs[1..].copy_from_slice(&signal_gprs[1..]); - *pc_slot = signal_gprs[0]; + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]) { + frame.x[0] = 0; + frame.x[1..].copy_from_slice(&signal_gprs[1..]); + frame.sepc = signal_gprs[0]; + } + + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { + fpregs.copy_from_slice(&frame.f); + *fcsr = frame.fcsr as u32; + } + + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32) { + frame.f.copy_from_slice(fpregs); + frame.fcsr = fcsr as usize; } } diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index 8439373c..6d1d90a9 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -3,7 +3,7 @@ pub mod traits; -use crate::hal::traits::{AddressSpaceToken, HartId, PagingArch}; +use crate::hal::traits::{AddressSpaceToken, HartId, PTEFlags, PagingArch}; #[cfg(target_arch = "riscv64")] pub use crate::arch::riscv::{ @@ -94,3 +94,69 @@ pub unsafe fn current_address_space_token() -> AddressSpaceToken { pub unsafe fn flush_tlb() { ArchPaging::flush_tlb(); } + +/// Encode one architecture-specific PTE from a physical page number and semantic flags. +#[inline] +pub fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + ArchPaging::make_pte(ppn, flags) +} + +/// Extract the pointed-to physical page number from one architecture-specific PTE. +#[inline] +pub fn pte_ppn(entry_bits: usize) -> usize { + ArchPaging::pte_ppn(entry_bits) +} + +/// Extract the semantic flags from one architecture-specific PTE. +#[inline] +pub fn pte_flags(entry_bits: usize) -> PTEFlags { + ArchPaging::pte_flags(entry_bits) +} + +/// Return the architecture's physical address width in bits. +#[inline] +pub const fn phys_addr_bits() -> usize { + ArchPaging::PA_BITS +} + +/// Return the architecture's virtual address width in bits. +#[inline] +pub const fn virt_addr_bits() -> usize { + ArchPaging::VA_BITS +} + +/// Return the architecture's physical page-number width in bits. +#[inline] +pub const fn phys_page_num_bits() -> usize { + ArchPaging::PPN_BITS +} + +/// Return the architecture's page-table level count. +#[inline] +pub const fn page_table_levels() -> usize { + ArchPaging::LEVELS +} + +/// Return the bit-width of one page-table index. +#[inline] +pub const fn page_table_index_bits() -> usize { + ArchPaging::INDEX_BITS +} + +/// Return the exclusive end of the user address space. +#[inline] +pub fn user_space_end() -> usize { + ArchPaging::user_space_end() +} + +/// Canonicalize one virtual address according to the current architecture. +#[inline] +pub fn canonicalize_vaddr(bits: usize) -> usize { + ArchPaging::canonicalize_vaddr(bits) +} + +/// Return the page-table index at one level for the given virtual page number. +#[inline] +pub fn vpn_index(vpn: usize, level: usize) -> usize { + ArchPaging::vpn_index(vpn, level) +} diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index 9d5a4ac4..f0a28fc8 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -1,5 +1,27 @@ //! HAL trait definitions — pure interfaces, no implementations. +bitflags! { + /// Architecture-neutral page-table entry semantics. + pub struct PTEFlags: u16 { + /// Entry is present/valid. + const V = 1 << 0; + /// Entry permits reads. + const R = 1 << 1; + /// Entry permits writes. + const W = 1 << 2; + /// Entry permits instruction fetches. + const X = 1 << 3; + /// Entry is user-accessible. + const U = 1 << 4; + /// Entry is global across address spaces. + const G = 1 << 5; + /// Entry has been accessed. + const A = 1 << 6; + /// Entry has been dirtied by writes. + const D = 1 << 7; + } +} + /// Per-hart interrupt control (enable/disable, trap entry setup). pub trait InterruptControl { /// Enable supervisor timer interrupt. @@ -84,41 +106,59 @@ pub trait HartId { /// Architecture-specific user trap-context ABI helpers. pub trait TrapContextAbi { - /// Saved status-register representation carried inside the trap context. - type Status: Copy; + /// Architecture-owned trap-frame layout stored at the front of `TrapContext`. + type Frame: Copy; - /// Return a status value prepared for first return to user mode. - fn initial_user_status() -> Self::Status; + /// Construct a trap frame prepared for first return to user mode. + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: AddressSpaceToken, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame; + /// Return the raw general-purpose register value at `index`. + fn reg(frame: &Self::Frame, index: usize) -> usize; + /// Update the raw general-purpose register value at `index`. + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize); /// Return the saved user PC from the trap context. - fn user_pc(gprs: &[usize; 32], status: Self::Status, pc: usize) -> usize; + fn user_pc(frame: &Self::Frame) -> usize; /// Set the saved user PC in the trap context. - fn set_user_pc(gprs: &mut [usize; 32], status: &mut Self::Status, pc_slot: &mut usize, pc: usize); + fn set_user_pc(frame: &mut Self::Frame, pc: usize); /// Return the saved user SP from the trap context. - fn user_sp(gprs: &[usize; 32]) -> usize; + fn user_sp(frame: &Self::Frame) -> usize; /// Set the saved user SP in the trap context. - fn set_user_sp(gprs: &mut [usize; 32], sp: usize); + fn set_user_sp(frame: &mut Self::Frame, sp: usize); /// Return the saved return-address register. - fn ra(gprs: &[usize; 32]) -> usize; + fn ra(frame: &Self::Frame) -> usize; /// Set the saved return-address register. - fn set_ra(gprs: &mut [usize; 32], ra: usize); + fn set_ra(frame: &mut Self::Frame, ra: usize); /// Return the saved TLS/thread-pointer register. - fn tls(gprs: &[usize; 32]) -> usize; + fn tls(frame: &Self::Frame) -> usize; /// Set the saved TLS/thread-pointer register. - fn set_tls(gprs: &mut [usize; 32], tls: usize); + fn set_tls(frame: &mut Self::Frame, tls: usize); /// Return the saved syscall number. - fn syscall_nr(gprs: &[usize; 32]) -> usize; + fn syscall_nr(frame: &Self::Frame) -> usize; /// Return the saved syscall arguments. - fn syscall_args(gprs: &[usize; 32]) -> [usize; 6]; + fn syscall_args(frame: &Self::Frame) -> [usize; 6]; /// Return the saved syscall return value. - fn syscall_ret(gprs: &[usize; 32]) -> usize; + fn syscall_ret(frame: &Self::Frame) -> usize; /// Set the saved syscall return value. - fn set_syscall_ret(gprs: &mut [usize; 32], ret: usize); + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize); /// Set one syscall/user argument register. - fn set_user_arg(gprs: &mut [usize; 32], index: usize, value: usize); + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize); + /// Set the kernel hart id restored by the trap trampoline. + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize); + /// Set the kernel stack pointer restored on the next trap entry. + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize); /// Export the Linux-compatible signal GPR layout. - fn export_signal_gprs(gprs: &[usize; 32], pc: usize) -> [usize; 32]; + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32]; /// Import the Linux-compatible signal GPR layout back into the trap context. - fn import_signal_gprs(gprs: &mut [usize; 32], pc_slot: &mut usize, signal_gprs: &[usize; 32]); + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]); + /// Copy floating-point state into an external signal frame. + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32); + /// Restore floating-point state from an external signal frame. + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32); } /// Opaque address-space activation token used by the current architecture. @@ -128,10 +168,18 @@ pub type AddressSpaceToken = usize; pub trait PagingArch { /// Page-table entry type. type Entry: Copy; + /// Physical address width in bits. + const PA_BITS: usize; + /// Virtual address width in bits. + const VA_BITS: usize; + /// Physical page-number width in bits. + const PPN_BITS: usize; /// Architecture-specific mode bits embedded in the root token. const ROOT_TOKEN_MODE: usize; /// Number of page-table levels. const LEVELS: usize; + /// Bits consumed per page-table level. + const INDEX_BITS: usize; /// Build an architecture token from a root page-table physical page number. fn make_token(root_ppn: usize) -> AddressSpaceToken; /// Extract the root page-table physical page number from an architecture token. @@ -142,6 +190,28 @@ pub trait PagingArch { unsafe fn current_token() -> AddressSpaceToken; /// Flush entire TLB. unsafe fn flush_tlb(); + /// Encode one leaf/intermediate PTE for this architecture. + fn make_pte(ppn: usize, flags: PTEFlags) -> usize; + /// Extract the pointed-to physical page number from one raw PTE. + fn pte_ppn(entry_bits: usize) -> usize; + /// Extract the semantic flags from one raw PTE. + fn pte_flags(entry_bits: usize) -> PTEFlags; + /// Return the exclusive end of the canonical low-half user address range. + fn user_space_end() -> usize { + 1usize << (Self::VA_BITS - 1) + } + /// Canonicalize a virtual address value according to the current architecture. + fn canonicalize_vaddr(bits: usize) -> usize { + if bits >= (1usize << (Self::VA_BITS - 1)) { + bits | (!((1usize << Self::VA_BITS) - 1)) + } else { + bits + } + } + /// Return the page-table index at `level` for the given virtual page number. + /// + /// `level=0` refers to the root-most level and `level=LEVELS-1` to the leaf level. + fn vpn_index(vpn: usize, level: usize) -> usize; } /// Platform timer: read monotonic time, program next interrupt. diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index a3759bd7..43080344 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -4,10 +4,12 @@ use super::PageTableEntry; use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; -use crate::arch::riscv::address::{PA_WIDTH_SV39, PPN_WIDTH_SV39, VA_WIDTH_SV39}; -const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS; -/// Exclusive end of the canonical low-half user virtual-address range under Sv39. -pub const USER_SPACE_END: usize = 1usize << (VA_WIDTH_SV39 - 1); +const VPN_WIDTH_BITS: usize = crate::hal::virt_addr_bits() - PAGE_SIZE_BITS; +/// Exclusive end of the canonical low-half user virtual-address range. +pub const USER_SPACE_END: usize = { + let _ = crate::hal::page_table_levels(); + 1usize << (crate::hal::virt_addr_bits() - 1) +}; /// Physical Address #[repr(C)] @@ -58,22 +60,22 @@ impl Debug for PhysPageNum { impl From for PhysAddr { fn from(v: usize) -> Self { - Self(v & ((1 << PA_WIDTH_SV39) - 1)) + Self(v & ((1 << crate::hal::phys_addr_bits()) - 1)) } } impl From for PhysPageNum { fn from(v: usize) -> Self { - Self(v & ((1 << PPN_WIDTH_SV39) - 1)) + Self(v & ((1 << crate::hal::phys_page_num_bits()) - 1)) } } impl From for VirtAddr { fn from(v: usize) -> Self { - Self(v & ((1 << VA_WIDTH_SV39) - 1)) + Self(v & ((1 << crate::hal::virt_addr_bits()) - 1)) } } impl From for VirtPageNum { fn from(v: usize) -> Self { - Self(v & ((1 << VPN_WIDTH_SV39) - 1)) + Self(v & ((1 << VPN_WIDTH_BITS) - 1)) } } impl From for usize { @@ -88,11 +90,7 @@ impl From for usize { } impl From for usize { fn from(v: VirtAddr) -> Self { - if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) { - v.0 | (!((1 << VA_WIDTH_SV39) - 1)) - } else { - v.0 - } + crate::hal::canonicalize_vaddr(v.0) } } impl From for usize { @@ -163,19 +161,6 @@ impl From for PhysAddr { } } -impl VirtPageNum { - /// Get the indexes of the page table entry - pub fn indexes(&self) -> [usize; 3] { - let mut vpn = self.0; - let mut idx = [0usize; 3]; - for i in (0..3).rev() { - idx[i] = vpn & 511; - vpn >>= 9; - } - idx - } -} - impl PhysAddr { /// Get the immutable reference of physical address pub fn get_ref(&self) -> &'static T { @@ -190,7 +175,8 @@ impl PhysPageNum { /// Get the reference of page table(array of ptes) pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { let pa: PhysAddr = (*self).into(); - unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) } + let entry_count = 1usize << crate::hal::page_table_index_bits(); + unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, entry_count) } } /// Get the reference of page(array of bytes) pub fn get_bytes_array(&self) -> &'static mut [u8] { diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 0c240314..7193f14d 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -1,9 +1,10 @@ //! The heap allocator. use super::frame_allocator::{frame_alloc, frame_alloc_contiguous, frame_dealloc}; -use super::page_table::PTEFlags; -use super::{PageTableEntry, PhysPageNum, VirtAddr, KERNEL_SPACE}; -use crate::config::{KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE, MEMORY_END, PAGE_SIZE}; +use super::{PageTableEntry, PhysPageNum, PTEFlags, VirtAddr, KERNEL_SPACE}; +use crate::config::{ + KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE, MEMORY_END, PAGE_SIZE, PAGE_SIZE_BITS, +}; use crate::sync::SpinNoIrqLock; use buddy_system_allocator::LockedHeap; use core::alloc::{GlobalAlloc, Layout}; @@ -60,24 +61,33 @@ pub static KERNEL_HEAP_BYTES: AtomicUsize = AtomicUsize::new(0); static KERNEL_HEAP_VIRTUAL_BYTES: AtomicUsize = AtomicUsize::new(0); static KERNEL_HEAP_VIRTUAL_READY: AtomicBool = AtomicBool::new(false); +const ROOT_ENTRY_SPAN: usize = 1usize + << (PAGE_SIZE_BITS + + (crate::hal::page_table_levels() - 1) * crate::hal::page_table_index_bits()); + +const _: () = assert!( + crate::hal::page_table_levels() >= 2, + "kernel heap virtual window requires a multi-level page table" +); + // The dedicated heap page-table machinery below relies on the whole virtual -// heap window living under a single Sv39 1GiB (VPN[2]) entry, i.e. one shared -// level-1 table, so that runtime heap growth never has to touch the kernel root -// page table (which other code mutates under `KERNEL_SPACE`). +// heap window living under a single root page-table entry, i.e. one shared +// first-level subtree, so that runtime heap growth never has to touch the +// kernel root page table (which other code mutates under `KERNEL_SPACE`). const _: () = assert!( - KERNEL_HEAP_BASE & ((1usize << 30) - 1) == 0, - "KERNEL_HEAP_BASE must be 1GiB-aligned" + KERNEL_HEAP_BASE & (ROOT_ENTRY_SPAN - 1) == 0, + "KERNEL_HEAP_BASE must be aligned to one root page-table entry span" ); const _: () = assert!( - MAX_KERNEL_HEAP_SIZE <= (1usize << 30), - "kernel heap window must fit within a single Sv39 1GiB (VPN[2]) entry" + MAX_KERNEL_HEAP_SIZE <= ROOT_ENTRY_SPAN, + "kernel heap window must fit within a single root page-table entry span" ); -/// Physical page number of the level-1 page table that backs the entire virtual -/// kernel-heap window. Built once at boot (single-threaded) and cached here so -/// that runtime [`map_heap_pages`] can install leaf PTEs without re-walking from -/// — and re-locking — the global `KERNEL_SPACE` page table. -static KERNEL_HEAP_L1_PPN: AtomicUsize = AtomicUsize::new(0); +/// Physical page number of the first-level subtree table that backs the entire +/// virtual kernel-heap window. Built once at boot (single-threaded) and cached +/// here so that runtime [`map_heap_pages`] can install leaf PTEs without +/// re-walking from — and re-locking — the global `KERNEL_SPACE` page table. +static KERNEL_HEAP_SUBTREE_ROOT_PPN: AtomicUsize = AtomicUsize::new(0); /// Serializes page-table edits within the kernel-heap subtree. /// @@ -88,20 +98,23 @@ static KERNEL_HEAP_L1_PPN: AtomicUsize = AtomicUsize::new(0); /// then recurse into `grow` → `map_heap_pages` → `KERNEL_SPACE.lock()` and /// self-deadlock on the non-reentrant lock — wedging every hart, with /// interrupts disabled so nothing (not even an RT task) could preempt. Because -/// the heap window is a disjoint VPN[2] subtree, edits to it never alias the +/// the heap window is a disjoint root-entry subtree, edits to it never alias the /// page-table memory `KERNEL_SPACE` touches, so a separate lock is sufficient /// and correct. `SpinNoIrqLock` keeps interrupts masked while held so a timer /// IRQ cannot re-enter the allocator on the same hart. static HEAP_PT_LOCK: SpinNoIrqLock<()> = SpinNoIrqLock::new(()); -/// Build the kernel-heap window's level-1 page table and cache its PPN. +/// Build the kernel-heap window's first-level subtree table and cache its PPN. /// /// Must run once, single-threaded, after `KERNEL_SPACE` is active and before the /// first virtual-window heap growth (see [`init_heap_virtual_window`]). pub fn init_kernel_heap_mapping() { let base_vpn = VirtAddr::from(KERNEL_HEAP_BASE).floor(); - let l1_ppn = KERNEL_SPACE.lock().page_table.ensure_l1_table_untracked(base_vpn); - KERNEL_HEAP_L1_PPN.store(l1_ppn.0, Ordering::Release); + let subtree_root_ppn = KERNEL_SPACE + .lock() + .page_table + .ensure_subtree_root_untracked(base_vpn); + KERNEL_HEAP_SUBTREE_ROOT_PPN.store(subtree_root_ppn.0, Ordering::Release); } struct KernelHeapAllocator { @@ -273,36 +286,49 @@ fn alloc_bootstrap_heap_pages(pages: usize) -> Option { Some(start_pa.into()) } -/// Index the level-0 leaf PTE slot for a kernel-heap VA, given the (pre-built, -/// cached) level-1 table. Allocates the level-2 leaf table on demand. Returns -/// `None` only on frame exhaustion. The heap window is a single VPN[2] subtree, -/// so VPN[2] is constant and we walk straight from the cached level-1 table — -/// never touching the kernel root page table that `KERNEL_SPACE` guards. +/// Index the leaf PTE slot for a kernel-heap VA, given the pre-built cached +/// subtree root table. Allocates lower-level page tables on demand. Returns +/// `None` only on frame exhaustion. The heap window is a single root-entry +/// subtree, so we walk straight from that cached subtree root — never touching +/// the kernel root page table that `KERNEL_SPACE` guards. /// /// Caller must hold [`HEAP_PT_LOCK`]. -fn heap_leaf_pte(l1_ppn: PhysPageNum, vpn: super::VirtPageNum) -> Option<*mut PageTableEntry> { - let idxs = vpn.indexes(); - let l1 = &mut l1_ppn.get_pte_array()[idxs[1]]; - if !l1.is_valid() { - let frame = frame_alloc()?; - *l1 = PageTableEntry::new(frame.ppn, PTEFlags::V); - // The leaf table is a permanent kernel mapping; never reclaimed. - core::mem::forget(frame); +fn heap_leaf_pte( + subtree_root_ppn: PhysPageNum, + vpn: super::VirtPageNum, +) -> Option<*mut PageTableEntry> { + let levels = crate::hal::page_table_levels(); + let mut ppn = subtree_root_ppn; + for level in 1..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Some(pte as *mut PageTableEntry); + } + if !pte.is_valid() { + let frame = frame_alloc()?; + *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + // Lower-level heap page tables are permanent kernel mappings. + core::mem::forget(frame); + } + ppn = pte.ppn(); } - let l0_ppn = l1.ppn(); - Some(&mut l0_ppn.get_pte_array()[idxs[2]] as *mut PageTableEntry) + None } fn map_heap_pages(start_va: usize, pages: usize) -> bool { - let l1_ppn = PhysPageNum(KERNEL_HEAP_L1_PPN.load(Ordering::Acquire)); - debug_assert!(l1_ppn.0 != 0, "kernel heap mapping used before init"); + let subtree_root_ppn = PhysPageNum(KERNEL_HEAP_SUBTREE_ROOT_PPN.load(Ordering::Acquire)); + debug_assert!( + subtree_root_ppn.0 != 0, + "kernel heap mapping used before init" + ); let _guard = HEAP_PT_LOCK.lock(); for page in 0..pages { let va = start_va + page * PAGE_SIZE; let vpn = VirtAddr::from(va).floor(); - let Some(pte) = heap_leaf_pte(l1_ppn, vpn) else { - rollback_heap_pages(l1_ppn, start_va, page); + let Some(pte) = heap_leaf_pte(subtree_root_ppn, vpn) else { + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; }; // SAFETY: `pte` points into a leaf table reachable only through @@ -310,11 +336,11 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { // `reserve_virtual_heap_bytes`, so no two writers target the same slot. let entry = unsafe { &mut *pte }; if entry.is_valid() { - rollback_heap_pages(l1_ppn, start_va, page); + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; } let Some(frame) = frame_alloc() else { - rollback_heap_pages(l1_ppn, start_va, page); + rollback_heap_pages(subtree_root_ppn, start_va, page); return false; }; *entry = PageTableEntry::new(frame.ppn, PTEFlags::R | PTEFlags::W | PTEFlags::V); diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index abe2807c..47ffe4b7 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -1,8 +1,7 @@ //! Address Space [`MemorySet`] management of Process use super::{frame_alloc, shootdown, FrameTracker, MmError, PageFaultHandled, ShootdownKind}; -use super::page_table::PTEFlags; -use super::{PageTable, PageTableEntry}; +use super::{PageTable, PageTableEntry, PTEFlags}; use super::{PhysAddr, PhysPageNum, USER_SPACE_END, VirtAddr, VirtPageNum}; use super::{StepByOne, VPNRange}; use crate::config::{ @@ -455,6 +454,23 @@ impl DeferredUserReclaim { } impl MemorySet { + fn map_perm_to_pte_flags(map_perm: MapPermission) -> PTEFlags { + let mut flags = PTEFlags::empty(); + if map_perm.contains(MapPermission::R) { + flags.insert(PTEFlags::R); + } + if map_perm.contains(MapPermission::W) { + flags.insert(PTEFlags::W); + } + if map_perm.contains(MapPermission::X) { + flags.insert(PTEFlags::X); + } + if map_perm.contains(MapPermission::U) { + flags.insert(PTEFlags::U); + } + flags + } + /// 完成一次会返回延迟回收 batch 的本地页表修改。 fn finish_deferred_page_table_edit(&self) { // 本地 hart 可能刚刚使用过被拆除的翻译,必须先清掉本地 TLB; @@ -1518,7 +1534,7 @@ impl MemorySet { ppn } }; - let pte_flags = PTEFlags::from_bits(map_perm.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(map_perm); self.page_table.map(vpn, ppn, pte_flags)?; Ok(()) } @@ -1560,7 +1576,7 @@ impl MemorySet { if !self.can_commit_file_page_fault(plan) { return Ok(PageFaultHandled::NotHandled); } - let mut pte_flags = PTEFlags::from_bits(plan.map_perm.bits).unwrap(); + let mut pte_flags = Self::map_perm_to_pte_flags(plan.map_perm); if plan.shared && plan.map_perm.contains(MapPermission::W) && plan.access != PageFaultAccess::Write @@ -1647,7 +1663,7 @@ impl MemorySet { if !self.can_commit_file_page_fault(plan) { return Ok(PageFaultHandled::NotHandled); } - let mut pte_flags = PTEFlags::from_bits(plan.map_perm.bits).unwrap(); + let mut pte_flags = Self::map_perm_to_pte_flags(plan.map_perm); pte_flags.remove(PTEFlags::W); pte_flags.remove(PTEFlags::D); let ppn = page.lock().ppn(); @@ -2025,7 +2041,7 @@ impl MemorySet { }; // update middle pages' PTE flags - let pte_flags = PTEFlags::from_bits(permission.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(permission); for vpn in VPNRange::new(overlap_start, overlap_end) { if self.page_table.translate(vpn).is_some() { self.page_table.update_flags(vpn, pte_flags); @@ -2038,7 +2054,7 @@ impl MemorySet { new_areas.push(right_area); } else { // no right split, area becomes the middle area - let pte_flags = PTEFlags::from_bits(permission.bits).unwrap(); + let pte_flags = Self::map_perm_to_pte_flags(permission); for vpn in VPNRange::new(overlap_start, overlap_end) { if self.page_table.translate(vpn).is_some() { self.page_table.update_flags(vpn, pte_flags); @@ -2524,7 +2540,7 @@ impl Vma { self.data_frames.insert(vpn, page); } } - let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap(); + let pte_flags = MemorySet::map_perm_to_pte_flags(self.map_perm); page_table.map(vpn, ppn, pte_flags)?; Ok(()) } diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index b4618fdf..12690ce6 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -63,6 +63,7 @@ pub use page_table::{ translated_byte_buffer, translated_ref, translated_refmut, translated_str, PageTable, PageTableEntry, UserBuffer, UserBufferIterator, }; +pub use crate::hal::traits::PTEFlags; /// initiate heap allocator, frame allocator and kernel space pub fn init() { diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index 83d973b5..dd5ab6d6 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -5,25 +5,10 @@ use super::{ }; use crate::config::PAGE_SIZE; use crate::arch::riscv::Sv39Paging; -use crate::hal::traits::{AddressSpaceToken, PagingArch}; +use crate::hal::traits::{AddressSpaceToken, PagingArch, PTEFlags}; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use bitflags::*; - -bitflags! { - /// page table entry flags - pub struct PTEFlags: u8 { - const V = 1 << 0; - const R = 1 << 1; - const W = 1 << 2; - const X = 1 << 3; - const U = 1 << 4; - const G = 1 << 5; - const A = 1 << 6; - const D = 1 << 7; - } -} #[derive(Copy, Clone)] #[repr(C)] @@ -37,7 +22,7 @@ impl PageTableEntry { /// Create a new page table entry pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self { PageTableEntry { - bits: ppn.0 << 10 | flags.bits as usize, + bits: crate::hal::make_pte(ppn.0, flags), } } /// Create an empty page table entry @@ -46,11 +31,11 @@ impl PageTableEntry { } /// Get the physical page number from the page table entry pub fn ppn(&self) -> PhysPageNum { - (self.bits >> 10 & ((1usize << 44) - 1)).into() + crate::hal::pte_ppn(self.bits).into() } /// Get the flags from the page table entry pub fn flags(&self) -> PTEFlags { - PTEFlags::from_bits(self.bits as u8).unwrap() + crate::hal::pte_flags(self.bits) } /// The page pointered by page table entry is valid? pub fn is_valid(&self) -> bool { @@ -97,14 +82,13 @@ impl PageTable { } } fn find_pte_create(&mut self, vpn: VirtPageNum) -> Result, MmError> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Ok(Some(pte)); } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; @@ -113,20 +97,19 @@ impl PageTable { } ppn = pte.ppn(); } - Ok(result) + Ok(None) } fn find_pte_create_untracked( &mut self, vpn: VirtPageNum, ) -> Result, MmError> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Ok(Some(pte)); } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; @@ -135,24 +118,23 @@ impl PageTable { } ppn = pte.ppn(); } - Ok(result) + Ok(None) } fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> { - let idxs = vpn.indexes(); + let levels = crate::hal::page_table_levels(); let mut ppn = self.root_ppn; - let mut result: Option<&mut PageTableEntry> = None; - for (i, idx) in idxs.iter().enumerate() { - let pte = &mut ppn.get_pte_array()[*idx]; - if i == 2 { - result = Some(pte); - break; + for level in 0..levels { + let idx = crate::hal::vpn_index(vpn.0, level); + let pte = &mut ppn.get_pte_array()[idx]; + if level + 1 == levels { + return Some(pte); } if !pte.is_valid() { return None; } ppn = pte.ppn(); } - result + None } /// set the map between virtual page number and physical page number #[allow(unused)] @@ -181,16 +163,21 @@ impl PageTable { *pte = PageTableEntry::new(ppn, flags | PTEFlags::V); Ok(()) } - /// Ensure the level-1 (1GiB-granularity) intermediate table covering `vpn` + /// Ensure the first-level subtree table under the root covering `vpn` /// exists, creating it untracked if necessary, and return its physical page /// number. /// - /// Used to pre-build the kernel-heap window's level-1 table once at boot so - /// that subsequent heap growth can install leaf PTEs into a disjoint subtree - /// without re-walking (and re-locking) the global kernel page table. - pub fn ensure_l1_table_untracked(&mut self, vpn: VirtPageNum) -> PhysPageNum { - let idxs = vpn.indexes(); - let pte = &mut self.root_ppn.get_pte_array()[idxs[0]]; + /// Used to pre-build the kernel-heap window's root-entry subtree once at + /// boot so that subsequent heap growth can install leaf PTEs into a + /// disjoint subtree without re-walking (and re-locking) the global kernel + /// page table. + pub fn ensure_subtree_root_untracked(&mut self, vpn: VirtPageNum) -> PhysPageNum { + debug_assert!( + crate::hal::page_table_levels() >= 2, + "kernel heap subtree caching requires a multi-level page table" + ); + let idx = crate::hal::vpn_index(vpn.0, 0); + let pte = &mut self.root_ppn.get_pte_array()[idx]; if !pte.is_valid() { let frame = frame_alloc().unwrap(); *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); diff --git a/os/src/trap/context.rs b/os/src/trap/context.rs index 7ca29451..a35a3fce 100644 --- a/os/src/trap/context.rs +++ b/os/src/trap/context.rs @@ -6,28 +6,8 @@ use crate::hal::traits::TrapContextAbi; #[derive(Debug, Clone, Copy)] /// trap context structure containing sstatus, sepc and registers pub struct TrapContext { - /// General-Purpose Register x0-31 - pub x: [usize; 32], - /// Supervisor Status Register - pub sstatus: ::Status, - /// Supervisor Exception Program Counter - pub sepc: usize, - /// 当前任务上次返回用户态前所在的 hart id。 - /// - /// 用户态可能会把 `tp` 当作 TLS 指针或普通寄存器使用,因此内核不能再假设 - /// trap 进入时 `tp` 里仍然保存着 hart-local 信息。这里单独记录内核需要恢复 - /// 的 hart id,供 trap 入口在切回内核上下文前重新写回 `tp`。 - pub kernel_hartid: usize, - /// Token of kernel address space - pub kernel_satp: usize, - /// Kernel stack pointer of the current application - pub kernel_sp: usize, - /// Virtual address of trap handler entry point in kernel - pub trap_handler: usize, - /// Floating-point registers f0-f31 - pub f: [u64; 32], - /// Floating-point control and status register - pub fcsr: usize, + /// Architecture-specific trap-frame payload consumed by trampoline assembly. + pub arch: ::Frame, /// Whether the current trap originated from a syscall (UserEnvCall). /// Used by signal delivery to implement syscall restart (SA_RESTART). pub in_syscall: bool, @@ -42,39 +22,38 @@ pub struct TrapContext { impl TrapContext { /// Return the raw general-purpose register value at `index`. pub fn reg(&self, index: usize) -> usize { - self.x[index] + ArchTrapContextAbi::reg(&self.arch, index) } /// Update the raw general-purpose register value at `index`. pub fn set_reg(&mut self, index: usize, value: usize) { - if index != 0 { - self.x[index] = value; - } + ArchTrapContextAbi::set_reg(&mut self.arch, index, value); } /// Return the saved user-mode PC. pub fn user_pc(&self) -> usize { - ArchTrapContextAbi::user_pc(&self.x, self.sstatus, self.sepc) + ArchTrapContextAbi::user_pc(&self.arch) } /// Overwrite the saved user-mode PC. pub fn set_user_pc(&mut self, pc: usize) { - ArchTrapContextAbi::set_user_pc(&mut self.x, &mut self.sstatus, &mut self.sepc, pc); + ArchTrapContextAbi::set_user_pc(&mut self.arch, pc); } /// Advance the saved user-mode PC by `delta` bytes. pub fn advance_user_pc(&mut self, delta: usize) { - self.sepc = self.sepc.wrapping_add(delta); + let next = self.user_pc().wrapping_add(delta); + self.set_user_pc(next); } /// Return the saved user stack pointer. pub fn user_sp(&self) -> usize { - ArchTrapContextAbi::user_sp(&self.x) + ArchTrapContextAbi::user_sp(&self.arch) } /// put the sp(stack pointer) into x\[2\] field of TrapContext pub fn set_sp(&mut self, sp: usize) { - ArchTrapContextAbi::set_user_sp(&mut self.x, sp); + ArchTrapContextAbi::set_user_sp(&mut self.arch, sp); } /// Set the saved user stack pointer. @@ -84,47 +63,47 @@ impl TrapContext { /// Return the saved return address register. pub fn ra(&self) -> usize { - ArchTrapContextAbi::ra(&self.x) + ArchTrapContextAbi::ra(&self.arch) } /// Set the saved return address register. pub fn set_ra(&mut self, ra: usize) { - ArchTrapContextAbi::set_ra(&mut self.x, ra); + ArchTrapContextAbi::set_ra(&mut self.arch, ra); } /// Return the saved user TLS/thread-pointer register. pub fn tls(&self) -> usize { - ArchTrapContextAbi::tls(&self.x) + ArchTrapContextAbi::tls(&self.arch) } /// Set the saved user TLS/thread-pointer register. pub fn set_tls(&mut self, tls: usize) { - ArchTrapContextAbi::set_tls(&mut self.x, tls); + ArchTrapContextAbi::set_tls(&mut self.arch, tls); } /// Return the architecture syscall number register. pub fn syscall_nr(&self) -> usize { - ArchTrapContextAbi::syscall_nr(&self.x) + ArchTrapContextAbi::syscall_nr(&self.arch) } /// Return the six syscall arguments from the saved user context. pub fn syscall_args(&self) -> [usize; 6] { - ArchTrapContextAbi::syscall_args(&self.x) + ArchTrapContextAbi::syscall_args(&self.arch) } /// Return the saved syscall return register. pub fn syscall_ret(&self) -> usize { - ArchTrapContextAbi::syscall_ret(&self.x) + ArchTrapContextAbi::syscall_ret(&self.arch) } /// Set the saved syscall return register. pub fn set_syscall_ret(&mut self, ret: usize) { - ArchTrapContextAbi::set_syscall_ret(&mut self.x, ret); + ArchTrapContextAbi::set_syscall_ret(&mut self.arch, ret); } /// Set one user-call argument register. pub fn set_user_arg(&mut self, index: usize, value: usize) { - ArchTrapContextAbi::set_user_arg(&mut self.x, index, value); + ArchTrapContextAbi::set_user_arg(&mut self.arch, index, value); } /// Save the original first syscall argument for possible restart. @@ -134,34 +113,32 @@ impl TrapContext { /// Set the kernel hart id restored by the trap trampoline. pub fn set_kernel_hartid(&mut self, hartid: usize) { - self.kernel_hartid = hartid; + ArchTrapContextAbi::set_kernel_hartid(&mut self.arch, hartid); } /// Set the kernel stack pointer restored on the next trap entry. pub fn set_kernel_sp(&mut self, kernel_sp: usize) { - self.kernel_sp = kernel_sp; + ArchTrapContextAbi::set_kernel_sp(&mut self.arch, kernel_sp); } /// Export the saved register file using the riscv64 Linux signal ABI layout. pub fn export_signal_gprs(&self) -> [usize; 32] { - ArchTrapContextAbi::export_signal_gprs(&self.x, self.user_pc()) + ArchTrapContextAbi::export_signal_gprs(&self.arch) } /// Restore the saved register file using the riscv64 Linux signal ABI layout. pub fn import_signal_gprs(&mut self, gregs: &[usize; 32]) { - ArchTrapContextAbi::import_signal_gprs(&mut self.x, &mut self.sepc, gregs); + ArchTrapContextAbi::import_signal_gprs(&mut self.arch, gregs); } /// Copy floating-point state into an external signal frame. pub fn copy_fp_state_to(&self, fpregs: &mut [u64; 32], fcsr: &mut u32) { - fpregs.copy_from_slice(&self.f); - *fcsr = self.fcsr as u32; + ArchTrapContextAbi::copy_fp_state_to(&self.arch, fpregs, fcsr); } /// Restore floating-point state from an external signal frame. pub fn restore_fp_state(&mut self, fpregs: &[u64; 32], fcsr: u32) { - self.f.copy_from_slice(fpregs); - self.fcsr = fcsr as usize; + ArchTrapContextAbi::restore_fp_state(&mut self.arch, fpregs, fcsr); } /// init the trap context of an application @@ -172,23 +149,19 @@ impl TrapContext { kernel_sp: usize, trap_handler: usize, ) -> Self { - let sstatus = ArchTrapContextAbi::initial_user_status(); unsafe { crate::hal::enable_fp() }; let mut cx = Self { - x: [0; 32], - sstatus, - sepc: entry, // entry point of app - kernel_hartid: 0, - kernel_satp, // addr of page table - kernel_sp, // kernel stack - trap_handler, // addr of trap_handler function - f: [0; 32], - fcsr: 0, + arch: ArchTrapContextAbi::new_user_frame( + entry, + sp, + kernel_satp, + kernel_sp, + trap_handler, + ), in_syscall: false, orig_a0: 0, restartable_syscall: false, }; - cx.set_user_sp(sp); // app's user stack pointer cx // return initial Trap Context of app } } From 9fe71fc072f9bf394ad6046285fbdd21e9795982 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 8 Jun 2026 21:22:29 +0800 Subject: [PATCH 06/19] feat: Support boot in LA64. (Not completed yet.) --- CosmOS-rootfs | 2 +- .../loongarch64-direct/.cargo/config.toml | 8 + bootloader/loongarch64-direct/Cargo.lock | 7 + bootloader/loongarch64-direct/Cargo.toml | 11 + bootloader/loongarch64-direct/build.rs | 5 + bootloader/loongarch64-direct/linker.ld | 31 ++ bootloader/loongarch64-direct/src/main.rs | 78 +++++ fs/src/ext4_rs | 2 +- os/Makefile | 76 ++-- os/build.rs | 7 +- os/cargo-config/config.toml | 9 +- os/src/arch/loongarch64/entry.S | 57 +++ os/src/arch/loongarch64/entry.rs | 5 + os/src/arch/loongarch64/hart.rs | 79 +++++ os/src/arch/loongarch64/mod.rs | 12 + os/src/arch/loongarch64/paging.rs | 122 +++++++ os/src/arch/loongarch64/switch.S | 29 ++ os/src/arch/loongarch64/switch.rs | 5 + os/src/arch/loongarch64/trap.S | 138 ++++++++ os/src/arch/loongarch64/trap.rs | 326 ++++++++++++++++++ os/src/arch/mod.rs | 5 + os/src/arch/riscv/hart.rs | 4 +- os/src/boards/loongarch_virt.rs | 94 +++++ os/src/config.rs | 5 + os/src/drivers/mod.rs | 1 + os/src/hal/mod.rs | 18 +- os/src/hal/traits.rs | 7 + os/src/linker-loongarch64.ld | 54 +++ os/src/main.rs | 45 ++- os/src/mm/address.rs | 34 +- os/src/mm/heap_allocator.rs | 43 ++- os/src/mm/memory_set.rs | 8 + os/src/mm/mod.rs | 11 +- os/src/mm/page_table.rs | 6 +- os/src/platform/loongarch_virt.rs | 35 ++ os/src/platform/mod.rs | 5 + os/src/platform/qemu_virt/mod.rs | 2 +- os/src/sbi.rs | 49 ++- os/src/syscall/process.rs | 3 + os/src/timer.rs | 16 +- os/src/trap/mod.rs | 2 + user/Cargo.toml | 6 - user/Makefile | 11 +- user/build.py | 5 +- user/cargo-config/config.toml | 6 + user/src/bin/remote_shell.rs | 16 + user/src/lib.rs | 12 + user/src/linker-loongarch64.ld | 33 ++ user/src/syscall.rs | 35 ++ 49 files changed, 1509 insertions(+), 71 deletions(-) create mode 100644 bootloader/loongarch64-direct/.cargo/config.toml create mode 100644 bootloader/loongarch64-direct/Cargo.lock create mode 100644 bootloader/loongarch64-direct/Cargo.toml create mode 100644 bootloader/loongarch64-direct/build.rs create mode 100644 bootloader/loongarch64-direct/linker.ld create mode 100644 bootloader/loongarch64-direct/src/main.rs create mode 100644 os/src/arch/loongarch64/entry.S create mode 100644 os/src/arch/loongarch64/entry.rs create mode 100644 os/src/arch/loongarch64/hart.rs create mode 100644 os/src/arch/loongarch64/mod.rs create mode 100644 os/src/arch/loongarch64/paging.rs create mode 100644 os/src/arch/loongarch64/switch.S create mode 100644 os/src/arch/loongarch64/switch.rs create mode 100644 os/src/arch/loongarch64/trap.S create mode 100644 os/src/arch/loongarch64/trap.rs create mode 100644 os/src/boards/loongarch_virt.rs create mode 100644 os/src/linker-loongarch64.ld create mode 100644 os/src/platform/loongarch_virt.rs create mode 100644 user/src/linker-loongarch64.ld diff --git a/CosmOS-rootfs b/CosmOS-rootfs index f15e8c79..1061c13e 160000 --- a/CosmOS-rootfs +++ b/CosmOS-rootfs @@ -1 +1 @@ -Subproject commit f15e8c79a1fd7670520d7c9074f7f3525c72eed6 +Subproject commit 1061c13e89534acb41f61f76ef99400c131f1c40 diff --git a/bootloader/loongarch64-direct/.cargo/config.toml b/bootloader/loongarch64-direct/.cargo/config.toml new file mode 100644 index 00000000..e3cb4e94 --- /dev/null +++ b/bootloader/loongarch64-direct/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "loongarch64-unknown-none" + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-arg=-Tlinker.ld", +] + diff --git a/bootloader/loongarch64-direct/Cargo.lock b/bootloader/loongarch64-direct/Cargo.lock new file mode 100644 index 00000000..f6622164 --- /dev/null +++ b/bootloader/loongarch64-direct/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "loongarch64-direct-boot" +version = "0.1.0" diff --git a/bootloader/loongarch64-direct/Cargo.toml b/bootloader/loongarch64-direct/Cargo.toml new file mode 100644 index 00000000..136d476f --- /dev/null +++ b/bootloader/loongarch64-direct/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "loongarch64-direct-boot" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[profile.release] +opt-level = "z" +lto = true +strip = true +panic = "abort" diff --git a/bootloader/loongarch64-direct/build.rs b/bootloader/loongarch64-direct/build.rs new file mode 100644 index 00000000..f94aceb3 --- /dev/null +++ b/bootloader/loongarch64-direct/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tlinker.ld"); + println!("cargo:rerun-if-changed=linker.ld"); + println!("cargo:rerun-if-changed=src/main.rs"); +} diff --git a/bootloader/loongarch64-direct/linker.ld b/bootloader/loongarch64-direct/linker.ld new file mode 100644 index 00000000..178f4a7f --- /dev/null +++ b/bootloader/loongarch64-direct/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) + +BASE_ADDRESS = 0x81000000; + +SECTIONS +{ + . = BASE_ADDRESS; + + .text : ALIGN(16) { + *(.text.entry) + *(.text .text.*) + } + + .rodata : ALIGN(16) { + *(.rodata .rodata.*) + } + + .data : ALIGN(16) { + *(.data .data.*) + } + + .bss : ALIGN(16) { + *(.bss .bss.*) + } + + /DISCARD/ : { + *(.eh_frame) + *(.debug*) + } +} diff --git a/bootloader/loongarch64-direct/src/main.rs b/bootloader/loongarch64-direct/src/main.rs new file mode 100644 index 00000000..cc637787 --- /dev/null +++ b/bootloader/loongarch64-direct/src/main.rs @@ -0,0 +1,78 @@ +#![no_std] +#![no_main] +use core::arch::{asm, naked_asm}; +use core::panic::PanicInfo; + +const UART_BASE: usize = 0x1fe0_01e0; +const IO_OFFSET: usize = 0x8000_0000_0000_0000; +const UART_THR: usize = 0x00; +const UART_LSR: usize = 0x05; +const UART_LSR_THRE: u8 = 1 << 5; +const KERNEL_ENTRY: usize = 0x9000_0000_9000_0000; + +#[unsafe(no_mangle)] +static mut BOOT_STACK: [u8; 4096] = [0; 4096]; + +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _start() -> ! { + naked_asm!( + // Setup DMW0: UC window 0x8000xxxxxxxxxxxx → PLV0 + "ori $t0, $zero, 0x1", + "lu52i.d $t0, $t0, -2048", + "csrwr $t0, 0x180", + // Setup DMW1: CA window 0x9000xxxxxxxxxxxx → PLV0 + "ori $t0, $zero, 0x11", + "lu52i.d $t0, $t0, -1792", + "csrwr $t0, 0x181", + // Setup stack + "la.global $t0, {stack}", + "ori $t1, $zero, 2048", + "add.d $sp, $t0, $t1", + "add.d $sp, $sp, $t1", + "csrrd $a0, 0x20", + "b {main}", + stack = sym BOOT_STACK, + main = sym boot_main, + ) +} + +#[unsafe(no_mangle)] +extern "C" fn boot_main(hart_id: usize) -> ! { + puts("[boot] loongarch64 direct loader\r\n"); + puts("[boot] jumping to kernel @ 0x90000000\r\n"); + unsafe { + let entry: extern "C" fn(usize) -> ! = core::mem::transmute(KERNEL_ENTRY); + entry(hart_id); + } +} + +fn puts(s: &str) { + for byte in s.bytes() { + putc(byte); + } +} + +fn putc(byte: u8) { + unsafe { + let uart = IO_OFFSET | UART_BASE; + while (mmio_read8(uart + UART_LSR) & UART_LSR_THRE) == 0 {} + mmio_write8(uart + UART_THR, byte); + } +} + +unsafe fn mmio_read8(addr: usize) -> u8 { + core::ptr::read_volatile(addr as *const u8) +} + +unsafe fn mmio_write8(addr: usize, value: u8) { + core::ptr::write_volatile(addr as *mut u8, value) +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + puts("[boot] panic\r\n"); + loop { + unsafe { asm!("idle 0", options(nomem, nostack)) }; + } +} diff --git a/fs/src/ext4_rs b/fs/src/ext4_rs index 016bbbba..d396d88e 160000 --- a/fs/src/ext4_rs +++ b/fs/src/ext4_rs @@ -1 +1 @@ -Subproject commit 016bbbba3bb2670472eeda35b3b497c6ff2222ef +Subproject commit d396d88e9ea206ed137aaa0c41fe5ab9bbf6012b diff --git a/os/Makefile b/os/Makefile index 4dd55fd3..6de86b35 100644 --- a/os/Makefile +++ b/os/Makefile @@ -1,5 +1,19 @@ # Building -TARGET := riscv64gc-unknown-none-elf +ARCH ?= riscv64 +ifeq ($(ARCH),loongarch64) + TARGET := loongarch64-unknown-none + QEMU ?= qemu-system-loongarch64 + OBJDUMP := rust-objdump --arch-name=loongarch64 + OBJCOPY := rust-objcopy --binary-architecture=loongarch64 + LOONGARCH_BIOS ?= /usr/local/share/qemu/edk2-loongarch64-code.fd + LOONGARCH_BOOT_MODE ?= direct + LOONGARCH_BOOTLOADER := ../bootloader/loongarch64-direct/target/loongarch64-unknown-none/release/loongarch64-direct-boot +else + TARGET := riscv64gc-unknown-none-elf + QEMU ?= qemu-system-riscv64 + OBJDUMP := rust-objdump --arch-name=riscv64 + OBJCOPY := rust-objcopy --binary-architecture=riscv64 +endif MODE ?= release KERNEL_ELF = target/$(TARGET)/$(MODE)/os KERNEL_BIN = $(KERNEL_ELF).bin @@ -19,9 +33,6 @@ OFFLINE := # BOARD BOARD := qemu SBI ?= rustsbi - -# QEMU -QEMU ?= qemu-system-riscv64 QEMU_VERSION := $(shell $(QEMU) --version 2>/dev/null | head -n1 | sed -E 's/.*version ([0-9.]+).*/\1/') QEMU_MAJOR := $(shell echo "$(QEMU_VERSION)" | cut -d. -f1) @@ -42,11 +53,7 @@ QEMU_EXTRA_ARGS ?= MODE_ARG = $(if $(filter release,$(MODE)),--release) # KERNEL ENTRY -KERNEL_ENTRY_PA := 0x80200000 - -# Binutils -OBJDUMP := rust-objdump --arch-name=riscv64 -OBJCOPY := rust-objcopy --binary-architecture=riscv64 +KERNEL_ENTRY_PA := $(if $(filter $(ARCH),loongarch64),0x90000000,0x80200000) # Disassembly DISASM ?= -x @@ -54,13 +61,20 @@ DISASM ?= -x build: env kernel fs-img @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $(KERNEL_BIN) +ifeq ($(ARCH),loongarch64) +bootloader: + @cd ../bootloader/loongarch64-direct && cargo build --release +else +bootloader: + @true +endif + env: ifeq ($(OFFLINE),) - (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET) - # 评测镜像已预装 cargo-binutils;缺失时才尝试安装。 - command -v rust-objcopy >/dev/null || cargo install cargo-binutils - # rust-objcopy/rust-objdump 依赖 llvm-tools-preview。 - rustup component list --installed | grep -q "llvm-tools-preview" || rustup component add llvm-tools-preview + (rustup target list | grep "$(TARGET) (installed)") || rustup target add $(TARGET) + cargo install cargo-binutils + rustup component add rust-src + rustup component add llvm-tools-preview endif fs-img: @@ -80,20 +94,20 @@ fs-img: exit 2; \ fi; \ echo "Injecting user apps into base ext4 image: $(BASE_IMG)"; \ - cd ../fs-fuse && cargo run --release -- -s ../user/target/riscv64gc-unknown-none-elf/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS) --ext4-base $(BASE_IMG); \ + cd ../fs-fuse && cargo run --release -- -s ../user/target/$(TARGET)/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS) --ext4-base $(BASE_IMG); \ else \ rm -f $(FS_IMG); \ echo "Packing fresh $(MAIN_FS) image from user apps."; \ - cd ../fs-fuse && cargo run --release -- -s ../user/target/riscv64gc-unknown-none-elf/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS); \ + cd ../fs-fuse && cargo run --release -- -s ../user/target/$(TARGET)/$(USER_MODE)/ -t $(FS_IMG) -f $(MAIN_FS); \ fi; \ fi kernel: @echo Platform: $(BOARD) ifeq ($(QEMU7_CFG),--cfg qemu7) - @LOG=$(LOG) cargo rustc $(MODE_ARG) --no-default-features --features $(MAIN_FS) -- --cfg qemu7 + @LOG=$(LOG) cargo rustc $(MODE_ARG) --target $(TARGET) --no-default-features --features $(MAIN_FS) -- --cfg qemu7 else - @LOG=$(LOG) cargo build $(MODE_ARG) --no-default-features --features $(MAIN_FS) + @LOG=$(LOG) cargo build $(MODE_ARG) --target $(TARGET) --no-default-features --features $(MAIN_FS) endif clean: @@ -108,7 +122,7 @@ disasm-vim: kernel @vim $(DISASM_TMP) @rm $(DISASM_TMP) -run: build run-inner +run: build bootloader run-inner run-trace: build run-inner-trace @@ -117,6 +131,27 @@ fast-run: kernel @$(MAKE) run-inner run-inner: +ifeq ($(ARCH),loongarch64) +ifeq ($(LOONGARCH_BOOT_MODE),direct) + @$(QEMU) \ + -m 4G \ + -machine virt \ + -cpu la464 \ + -smp $(SMP) \ + -nographic \ + -kernel $(LOONGARCH_BOOTLOADER) \ + -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +else + @$(QEMU) \ + -m 4G \ + -machine virt \ + -cpu la464 \ + -smp $(SMP) \ + -nographic \ + -bios $(LOONGARCH_BIOS) \ + -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +endif +else @$(QEMU) \ -m $(MEM) \ -machine virt \ @@ -127,9 +162,10 @@ run-inner: -netdev user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777,hostfwd=tcp::7778-:7778,hostfwd=udp::7778-:7778 \ -device virtio-net-device,netdev=net0,id=net0 \ $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ - # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ + # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 +endif # Uncomment above lines to add a second virtio-blk device (for testing mount/umount) run-inner-trace: diff --git a/os/build.rs b/os/build.rs index 5529b4fe..59d2cf9c 100644 --- a/os/build.rs +++ b/os/build.rs @@ -1,6 +1,9 @@ -static TARGET_PATH: &str = "../user/target/riscv64gc-unknown-none-elf/release/"; +fn target_path() -> String { + let target = std::env::var("TARGET").unwrap_or_else(|_| "riscv64gc-unknown-none-elf".to_string()); + format!("../user/target/{}/release/", target) +} fn main() { println!("cargo:rerun-if-changed=../user/src/"); - println!("cargo:rerun-if-changed={}", TARGET_PATH); + println!("cargo:rerun-if-changed={}", target_path()); } diff --git a/os/cargo-config/config.toml b/os/cargo-config/config.toml index 4275fcad..fb7c4f8e 100644 --- a/os/cargo-config/config.toml +++ b/os/cargo-config/config.toml @@ -3,5 +3,12 @@ target = "riscv64gc-unknown-none-elf" [target.riscv64gc-unknown-none-elf] rustflags = [ - "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes" + "-Clink-arg=-Tsrc/linker.ld", + "-Cforce-frame-pointers=yes", +] + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-arg=-Tsrc/linker-loongarch64.ld", + "-Cforce-frame-pointers=yes", ] diff --git a/os/src/arch/loongarch64/entry.S b/os/src/arch/loongarch64/entry.S new file mode 100644 index 00000000..bb75837f --- /dev/null +++ b/os/src/arch/loongarch64/entry.S @@ -0,0 +1,57 @@ + .section .text.entry + .equ BOOT_STACK_SHIFT, 20 + .equ BOOT_STACK_SIZE, 1 << BOOT_STACK_SHIFT + .equ BOOT_STACK_HARTS, 8 + .equ UART_BASE_EARLY, 0x800000001fe001e0 + .equ UART_THR, 0x0 + .equ UART_LSR, 0x5 + .equ UART_LSR_THRE, 0x20 + + .macro uart_putc reg +1: + ld.bu $t4, $t3, UART_LSR + andi $t4, $t4, UART_LSR_THRE + beqz $t4, 1b + st.b \reg, $t3, UART_THR + .endm + + .globl _start +_start: + ori $t0, $zero, 0x1 + lu52i.d $t0, $t0, -2048 + csrwr $t0, 0x180 + ori $t0, $zero, 0x11 + lu52i.d $t0, $t0, -1792 + csrwr $t0, 0x181 + + csrrd $a0, 0x20 + bnez $a0, 1f + + li.w $t0, 0xb0 + csrwr $t0, 0x0 + li.w $t0, 0x00 + csrwr $t0, 0x1 + csrwr $t0, 0x2 + + li.d $t3, UART_BASE_EARLY + li.w $t5, 'B' + uart_putc $t5 + + la.global $sp, boot_stack_lower_bound + li.d $t2, BOOT_STACK_SIZE + add.d $sp, $sp, $t2 + bl rust_main + +1: + la.global $sp, boot_stack_lower_bound + addi.d $t1, $a0, 1 + slli.d $t0, $t1, BOOT_STACK_SHIFT + add.d $sp, $sp, $t0 + bl rust_main + + .section .bss.stack + .globl boot_stack_lower_bound +boot_stack_lower_bound: + .space BOOT_STACK_SIZE * BOOT_STACK_HARTS + .globl boot_stack_top +boot_stack_top: diff --git a/os/src/arch/loongarch64/entry.rs b/os/src/arch/loongarch64/entry.rs new file mode 100644 index 00000000..d1882b63 --- /dev/null +++ b/os/src/arch/loongarch64/entry.rs @@ -0,0 +1,5 @@ +//! LoongArch64 kernel entry assembly. + +use core::arch::global_asm; + +global_asm!(include_str!("entry.S")); diff --git a/os/src/arch/loongarch64/hart.rs b/os/src/arch/loongarch64/hart.rs new file mode 100644 index 00000000..755521ee --- /dev/null +++ b/os/src/arch/loongarch64/hart.rs @@ -0,0 +1,79 @@ +//! LoongArch64 hart-local register access. + +use core::arch::asm; + +use crate::hal::traits::HartId; + +const CSR_CPUID: usize = 0x20; +const CSR_CRMD: usize = 0x0; +const CSR_ECFG: usize = 0x4; +const CSR_TCFG: usize = 0x41; +const CSR_TVAL: usize = 0x42; +const CRMD_IE: usize = 1 << 2; +const ECFG_FPE: usize = 1 << 0; +const TCFG_ENABLE: usize = 1 << 0; + +/// LoongArch64 implementation of [`HartId`](crate::hal::traits::HartId). +pub struct LoongArchHartId; + +#[inline] +pub fn read_time() -> usize { + let time: usize; + unsafe { asm!("rdtime.d {}, $zero", out(reg) time) }; + time +} + +#[inline] +pub unsafe fn set_timer_deadline(deadline: usize) { + let now = read_time(); + let delta = deadline.saturating_sub(now).max(1); + asm!( + "csrwr {delta}, {tval}", + "csrwr {tcfg}, {tcfg_num}", + delta = in(reg) delta, + tcfg = in(reg) (TCFG_ENABLE | (1usize << 1)), + tval = const CSR_TVAL, + tcfg_num = const CSR_TCFG, + ); +} + +impl HartId for LoongArchHartId { + fn current() -> usize { + let id: usize; + unsafe { asm!("csrrd {}, {}", out(reg) id, const CSR_CPUID) } + id + } + + unsafe fn init(_id: usize) {} + + unsafe fn enable_fp() { + let mut ecfg: usize; + asm!("csrrd {}, {}", out(reg) ecfg, const CSR_ECFG); + ecfg |= ECFG_FPE; + asm!("csrwr {}, {}", in(reg) ecfg, const CSR_ECFG); + } + + fn irqs_enabled() -> bool { + let crmd: usize; + unsafe { asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD) }; + crmd & CRMD_IE != 0 + } + + unsafe fn disable_irqs() { + let mut crmd: usize; + asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD); + crmd &= !CRMD_IE; + asm!("csrwr {}, {}", in(reg) crmd, const CSR_CRMD); + } + + unsafe fn enable_irqs() { + let mut crmd: usize; + asm!("csrrd {}, {}", out(reg) crmd, const CSR_CRMD); + crmd |= CRMD_IE; + asm!("csrwr {}, {}", in(reg) crmd, const CSR_CRMD); + } + + unsafe fn wait_for_interrupt() { + asm!("idle 0"); + } +} diff --git a/os/src/arch/loongarch64/mod.rs b/os/src/arch/loongarch64/mod.rs new file mode 100644 index 00000000..5a09840b --- /dev/null +++ b/os/src/arch/loongarch64/mod.rs @@ -0,0 +1,12 @@ +//! LoongArch64 arch implementation of HAL traits. +#![allow(missing_docs)] + +pub mod hart; +mod entry; +pub mod paging; +mod switch; +pub mod trap; + +pub use hart::{read_time, set_timer_deadline, LoongArchHartId}; +pub use paging::LoongArchPaging; +pub use trap::{LoongArchInterruptControl, LoongArchTrapContextAbi, LoongArchTrapMachine}; diff --git a/os/src/arch/loongarch64/paging.rs b/os/src/arch/loongarch64/paging.rs new file mode 100644 index 00000000..89eff19d --- /dev/null +++ b/os/src/arch/loongarch64/paging.rs @@ -0,0 +1,122 @@ +//! LoongArch64 paging implementation of [`PagingArch`](crate::hal::traits::PagingArch). + +use core::arch::asm; + +use crate::hal::traits::{AddressSpaceToken, PTEFlags, PagingArch}; +use crate::mm::PageTableEntry; + +const CSR_PGDL: usize = 0x19; +const CSR_ASID: usize = 0x18; +const PTE_V: usize = 1 << 0; +const PTE_D: usize = 1 << 1; +const PTE_PLV_USER: usize = 0b11 << 2; +const PTE_G: usize = 1 << 6; +const PTE_P: usize = 1 << 7; +const PTE_W: usize = 1 << 8; +const PTE_GNX: usize = 1 << 62; +const PTE_GNR: usize = 1 << 61; +const PPN_SHIFT: usize = 12; + +/// LoongArch64 three-level paging (39-bit VA, matching PWCL Dir1/Dir2 setup). +pub struct LoongArchPaging; + +impl PagingArch for LoongArchPaging { + type Entry = PageTableEntry; + const PA_BITS: usize = 48; + const VA_BITS: usize = 39; + const PPN_BITS: usize = Self::PA_BITS - 12; + const ROOT_TOKEN_MODE: usize = 0; + const LEVELS: usize = 3; + const INDEX_BITS: usize = 9; + + fn make_token(root_ppn: usize) -> AddressSpaceToken { + root_ppn << PPN_SHIFT + } + + fn root_ppn(token: AddressSpaceToken) -> usize { + token >> PPN_SHIFT + } + + unsafe fn activate_token(token: AddressSpaceToken) { + asm!( + "csrwr {pgd}, {pgdl}", + "csrwr $zero, {asid}", + "invtlb 0x00, $zero, $zero", + pgd = in(reg) token, + pgdl = const CSR_PGDL, + asid = const CSR_ASID, + ); + } + + unsafe fn current_token() -> AddressSpaceToken { + let token: usize; + asm!("csrrd {}, {}", out(reg) token, const CSR_PGDL); + token + } + + unsafe fn flush_tlb() { + asm!("invtlb 0x00, $zero, $zero"); + } + + fn make_pte(ppn: usize, flags: PTEFlags) -> usize { + let mut bits = (ppn << PPN_SHIFT) | PTE_P | PTE_V; + if flags.contains(PTEFlags::W) { + bits |= PTE_W | PTE_D; + } + if flags.contains(PTEFlags::U) { + bits |= PTE_PLV_USER; + } + if flags.contains(PTEFlags::G) { + bits |= PTE_G; + } + if !flags.contains(PTEFlags::R) { + bits |= PTE_GNR; + } + if !flags.contains(PTEFlags::X) { + bits |= PTE_GNX; + } + bits + } + + fn make_dir_entry(ppn: usize) -> usize { + // Directory (non-leaf) PTEs must NOT have GNR/GNX set; only valid + PA. + (ppn << PPN_SHIFT) | PTE_V + } + + fn pte_ppn(entry_bits: usize) -> usize { + (entry_bits >> PPN_SHIFT) & ((1usize << Self::PPN_BITS) - 1) + } + + fn pte_flags(entry_bits: usize) -> PTEFlags { + let mut flags = PTEFlags::empty(); + if entry_bits & PTE_V != 0 { + flags |= PTEFlags::V; + } + if entry_bits & PTE_W != 0 { + flags |= PTEFlags::W; + } + if entry_bits & PTE_D != 0 { + flags |= PTEFlags::D; + } + if entry_bits & PTE_PLV_USER != 0 { + flags |= PTEFlags::U; + } + if entry_bits & PTE_G != 0 { + flags |= PTEFlags::G; + } + if entry_bits & PTE_GNR == 0 { + flags |= PTEFlags::R; + } + if entry_bits & PTE_GNX == 0 { + flags |= PTEFlags::X; + } + flags + } + + fn vpn_index(vpn: usize, level: usize) -> usize { + debug_assert!(level < Self::LEVELS); + let mask = (1usize << Self::INDEX_BITS) - 1; + let shift = (Self::LEVELS - 1 - level) * Self::INDEX_BITS; + (vpn >> shift) & mask + } +} diff --git a/os/src/arch/loongarch64/switch.S b/os/src/arch/loongarch64/switch.S new file mode 100644 index 00000000..ed24b4d5 --- /dev/null +++ b/os/src/arch/loongarch64/switch.S @@ -0,0 +1,29 @@ + .section .text + .globl __switch +__switch: + st.d $sp, $a0, 8 + st.d $ra, $a0, 0 + st.d $fp, $a0, 16 + st.d $s0, $a0, 24 + st.d $s1, $a0, 32 + st.d $s2, $a0, 40 + st.d $s3, $a0, 48 + st.d $s4, $a0, 56 + st.d $s5, $a0, 64 + st.d $s6, $a0, 72 + st.d $s7, $a0, 80 + st.d $s8, $a0, 88 + + ld.d $ra, $a1, 0 + ld.d $sp, $a1, 8 + ld.d $fp, $a1, 16 + ld.d $s0, $a1, 24 + ld.d $s1, $a1, 32 + ld.d $s2, $a1, 40 + ld.d $s3, $a1, 48 + ld.d $s4, $a1, 56 + ld.d $s5, $a1, 64 + ld.d $s6, $a1, 72 + ld.d $s7, $a1, 80 + ld.d $s8, $a1, 88 + jr $ra diff --git a/os/src/arch/loongarch64/switch.rs b/os/src/arch/loongarch64/switch.rs new file mode 100644 index 00000000..feb16f83 --- /dev/null +++ b/os/src/arch/loongarch64/switch.rs @@ -0,0 +1,5 @@ +//! LoongArch64 task-switch assembly entrypoints. + +use core::arch::global_asm; + +global_asm!(include_str!("switch.S")); diff --git a/os/src/arch/loongarch64/trap.S b/os/src/arch/loongarch64/trap.S new file mode 100644 index 00000000..8978c657 --- /dev/null +++ b/os/src/arch/loongarch64/trap.S @@ -0,0 +1,138 @@ + # TLB refill handler — must be 4K-aligned, placed first in trampoline section. + # Uses hardware page-table walker: PGDL → lddir × N → ldpte × 2 → tlbfill. + .section .text.trampoline + .globl __tlb_refill + .balign 4096 +__tlb_refill: + csrwr $t0, 0x8b # save t0 to TLBRSAVE + csrrd $t0, 0x1b # t0 = PGD base + lddir $t0, $t0, 2 # walk root → mid (Dir2 → Dir1) + beqz $t0, 1f + lddir $t0, $t0, 1 # walk mid → leaf (Dir1 → PT) + beqz $t0, 1f + ldpte $t0, 0 + ldpte $t0, 1 + b 2f +1: + csrwr $zero, 0x8c + csrwr $zero, 0x8d +2: + tlbfill + csrrd $t0, 0x8b + ertn + + .globl __alltraps + .globl __restore + .align 3 +__alltraps: + move $t0, $sp + move $sp, $a0 + st.d $ra, $sp, 1*8 + st.d $tp, $sp, 2*8 + st.d $t0, $sp, 3*8 + st.d $a0, $sp, 4*8 + st.d $a1, $sp, 5*8 + st.d $a2, $sp, 6*8 + st.d $a3, $sp, 7*8 + st.d $a4, $sp, 8*8 + st.d $a5, $sp, 9*8 + st.d $a6, $sp, 10*8 + st.d $a7, $sp, 11*8 + st.d $t1, $sp, 12*8 + st.d $t2, $sp, 13*8 + st.d $t3, $sp, 14*8 + st.d $t4, $sp, 15*8 + st.d $t5, $sp, 16*8 + st.d $t6, $sp, 17*8 + st.d $t7, $sp, 18*8 + st.d $t8, $sp, 19*8 + st.d $r21, $sp, 20*8 + st.d $fp, $sp, 22*8 + st.d $s0, $sp, 23*8 + st.d $s1, $sp, 24*8 + st.d $s2, $sp, 25*8 + st.d $s3, $sp, 26*8 + st.d $s4, $sp, 27*8 + st.d $s5, $sp, 28*8 + st.d $s6, $sp, 29*8 + st.d $s7, $sp, 30*8 + st.d $s8, $sp, 31*8 + + csrrd $t0, 0x1 + csrrd $t1, 0x6 + st.d $t0, $sp, 32*8 + st.d $t1, $sp, 33*8 + + fld.d $f0, $sp, 38*8 + movgr2fr.d $f0, $zero + fst.d $f0, $sp, 38*8 + csrrd $t0, 0x2 + st.d $t0, $sp, 70*8 + + ld.d $tp, $sp, 34*8 + ld.d $t0, $sp, 35*8 + ld.d $t1, $sp, 37*8 + ld.d $sp, $sp, 36*8 + csrwr $t0, 0x19 + invtlb 0x00, $zero, $zero + jr $t1 + +__restore: + csrwr $a1, 0x19 + invtlb 0x00, $zero, $zero + move $sp, $a0 + ld.d $t0, $sp, 32*8 + ld.d $t1, $sp, 33*8 + csrwr $t0, 0x1 + csrwr $t1, 0x6 + ld.d $ra, $sp, 1*8 + ld.d $tp, $sp, 2*8 + ld.d $a0, $sp, 4*8 + ld.d $a1, $sp, 5*8 + ld.d $a2, $sp, 6*8 + ld.d $a3, $sp, 7*8 + ld.d $a4, $sp, 8*8 + ld.d $a5, $sp, 9*8 + ld.d $a6, $sp, 10*8 + ld.d $a7, $sp, 11*8 + ld.d $t1, $sp, 12*8 + ld.d $t2, $sp, 13*8 + ld.d $t3, $sp, 14*8 + ld.d $t4, $sp, 15*8 + ld.d $t5, $sp, 16*8 + ld.d $t6, $sp, 17*8 + ld.d $t7, $sp, 18*8 + ld.d $t8, $sp, 19*8 + ld.d $r21, $sp, 20*8 + ld.d $fp, $sp, 22*8 + ld.d $s0, $sp, 23*8 + ld.d $s1, $sp, 24*8 + ld.d $s2, $sp, 25*8 + ld.d $s3, $sp, 26*8 + ld.d $s4, $sp, 27*8 + ld.d $s5, $sp, 28*8 + ld.d $s6, $sp, 29*8 + ld.d $s7, $sp, 30*8 + ld.d $s8, $sp, 31*8 + ld.d $sp, $sp, 3*8 + ertn + + .section .text + .globl __trap_from_kernel + .align 3 +__trap_from_kernel: + addi.d $sp, $sp, -256 + st.d $ra, $sp, 0 + st.d $a0, $sp, 8 + st.d $a1, $sp, 16 + st.d $t0, $sp, 24 + st.d $t1, $sp, 32 + la.local $t0, trap_from_kernel + jirl $ra, $t0, 0 + ld.d $ra, $sp, 0 + ld.d $a0, $sp, 8 + ld.d $a1, $sp, 16 + ld.d $t0, $sp, 24 + ld.d $t1, $sp, 32 + addi.d $sp, $sp, 256 + ertn diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs new file mode 100644 index 00000000..b59c37c8 --- /dev/null +++ b/os/src/arch/loongarch64/trap.rs @@ -0,0 +1,326 @@ +//! LoongArch64 interrupt control, trap decoding and user-return operations. + +use core::arch::{asm, global_asm}; + +use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; + +global_asm!(include_str!("trap.S")); + +const CSR_CRMD: usize = 0x0; +const CSR_PRMD: usize = 0x1; +const CSR_EUEN: usize = 0x2; +const CSR_ECFG: usize = 0x4; +const CSR_ESTAT: usize = 0x5; +const CSR_ERA: usize = 0x6; +const CSR_BADV: usize = 0x7; +const CSR_EENTRY: usize = 0xc; +const CSR_TLBRENTRY: usize = 0x88; +const CSR_PWCL: usize = 0x1c; +const CSR_PWCH: usize = 0x1d; +const CSR_STLBPS: usize = 0x1e; + +const CRMD_IE: usize = 1 << 2; +const EUEN_FPEN: usize = 1 << 0; +const ECFG_SIP: usize = 1 << 1; +const ECFG_TIMER: usize = 1 << 11; +const ECFG_IPI: usize = 1 << 12; + +const ECODE_INT: usize = 0x0; +const ECODE_PIL: usize = 0x1; +const ECODE_PIS: usize = 0x2; +const ECODE_PIF: usize = 0x3; +const ECODE_ADE: usize = 0x8; +const ECODE_SYS: usize = 0xb; +const ECODE_INE: usize = 0xd; + +/// LoongArch64 implementation of [`InterruptControl`](crate::hal::traits::InterruptControl). +pub struct LoongArchInterruptControl; + +/// LoongArch64 implementation of trap decoding and user-return operations. +pub struct LoongArchTrapMachine; + +/// LoongArch64 register-layout helpers for the common trap context. +pub struct LoongArchTrapContextAbi; + +/// LoongArch64 trap frame layout shared with `trap.S`. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct LoongArchTrapContextFrame { + pub r: [usize; 32], + pub prmd: usize, + pub era: usize, + pub kernel_hartid: usize, + pub kernel_pgdl: usize, + pub kernel_sp: usize, + pub trap_handler: usize, + pub f: [u64; 32], + pub fcsr: usize, +} + +const USER_VDSO_CODE: [u8; 4] = [0x00, 0x00, 0x2b, 0x00]; + +impl InterruptControl for LoongArchInterruptControl { + unsafe fn enable_timer() { + update_ecfg(ECFG_TIMER, true); + } + + unsafe fn disable_timer() { + update_ecfg(ECFG_TIMER, false); + } + + unsafe fn enable_external() { + update_ecfg(ECFG_IPI, true); + } + + unsafe fn disable_external() { + update_ecfg(ECFG_IPI, false); + } + + unsafe fn enable_software() { + update_ecfg(ECFG_SIP, true); + } + + unsafe fn clear_software_pending() { + asm!( + "csrrd $t0, {estat}", + "and $t0, $t0, {mask}", + "csrwr $t0, {estat}", + estat = const CSR_ESTAT, + mask = in(reg) (!(1usize << 1)), + out("$t0") _, + ); + } + + unsafe fn set_kernel_trap_entry() { + extern "C" { + fn __trap_from_kernel(); + fn __tlb_refill(); + } + // PWCL: PTbase=12, PTwidth=9, Dir1base=21, Dir1width=9, Dir2base=30, Dir2width=9 + // PWCH: Dir3=unused (3-level paging: root=Dir2, middle=Dir1, leaf=PT) + const PWCL: usize = 12 | (9 << 5) | (21 << 10) | (9 << 15) | (30 << 20) | (9 << 25); + const PWCH: usize = 0; + asm!( + "csrwr {eentry}, {eentry_csr}", + "csrwr {tlbr}, {tlbr_csr}", + "csrwr {pwcl}, {pwcl_csr}", + "csrwr {pwch}, {pwch_csr}", + // STLBPS: page size = 12 (4KB) + "ori $t0, $zero, 12", + "csrwr $t0, {stlbps_csr}", + eentry = in(reg) (__trap_from_kernel as usize), + eentry_csr = const CSR_EENTRY, + tlbr = in(reg) (__tlb_refill as usize), + tlbr_csr = const CSR_TLBRENTRY, + pwcl = in(reg) PWCL, + pwcl_csr = const CSR_PWCL, + pwch = in(reg) PWCH, + pwch_csr = const CSR_PWCH, + stlbps_csr = const CSR_STLBPS, + out("$t0") _, + ); + } + + unsafe fn set_user_trap_entry() { + extern "C" { fn __alltraps(); } + asm!( + "csrwr {entry}, {eentry}", + entry = in(reg) (__alltraps as usize), + eentry = const CSR_EENTRY, + ); + } +} + +impl TrapMachine for LoongArchTrapMachine { + fn read_trap_info() -> TrapInfo { + let estat = read_estat(); + let badv = read_badv(); + let ecode = (estat >> 16) & 0x3f; + let cause = match ecode { + ECODE_SYS => TrapCause::UserSyscall, + ECODE_PIS => TrapCause::StorePageFault, + ECODE_PIL => TrapCause::LoadPageFault, + ECODE_PIF => TrapCause::InstructionPageFault, + ECODE_INE => TrapCause::IllegalInstruction, + ECODE_ADE => TrapCause::InstructionFault, + ECODE_INT => { + let is = estat & 0x1fff; + if is & (1 << 11) != 0 { + TrapCause::TimerInterrupt + } else if is & (1 << 1) != 0 { + TrapCause::SoftwareInterrupt + } else if is & (1 << 12) != 0 { + TrapCause::ExternalInterrupt + } else { + TrapCause::Unknown + } + } + _ => TrapCause::Unknown, + }; + TrapInfo { cause, fault_addr: badv } + } + + unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> ! { + extern "C" { fn __restore(); } + asm!( + "ibar 0", + "jirl $zero, {restore}, 0", + restore = in(reg) (__restore as usize), + in("$a0") trap_cx_user_va, + in("$a1") user_token, + options(noreturn) + ); + } + + fn syscall_instruction_len() -> usize { + 4 + } + + fn rt_sigreturn_trampoline() -> &'static [u8] { + &USER_VDSO_CODE + } +} + +impl TrapContextAbi for LoongArchTrapContextAbi { + type Frame = LoongArchTrapContextFrame; + + fn new_user_frame( + entry: usize, + sp: usize, + kernel_token: usize, + kernel_sp: usize, + trap_handler: usize, + ) -> Self::Frame { + let mut frame = LoongArchTrapContextFrame { + r: [0; 32], + prmd: 0, + era: entry, + kernel_hartid: 0, + kernel_pgdl: kernel_token, + kernel_sp, + trap_handler, + f: [0; 32], + fcsr: 0, + }; + frame.r[3] = sp; + frame + } + + fn reg(frame: &Self::Frame, index: usize) -> usize { + frame.r[index] + } + + fn set_reg(frame: &mut Self::Frame, index: usize, value: usize) { + if index != 0 { + frame.r[index] = value; + } + } + + fn user_pc(frame: &Self::Frame) -> usize { + frame.era + } + + fn set_user_pc(frame: &mut Self::Frame, pc: usize) { + frame.era = pc; + } + + fn user_sp(frame: &Self::Frame) -> usize { + frame.r[3] + } + + fn set_user_sp(frame: &mut Self::Frame, sp: usize) { + frame.r[3] = sp; + } + + fn ra(frame: &Self::Frame) -> usize { + frame.r[1] + } + + fn set_ra(frame: &mut Self::Frame, ra: usize) { + frame.r[1] = ra; + } + + fn tls(frame: &Self::Frame) -> usize { + frame.r[2] + } + + fn set_tls(frame: &mut Self::Frame, tls: usize) { + frame.r[2] = tls; + } + + fn syscall_nr(frame: &Self::Frame) -> usize { + frame.r[11] + } + + fn syscall_args(frame: &Self::Frame) -> [usize; 6] { + [frame.r[4], frame.r[5], frame.r[6], frame.r[7], frame.r[8], frame.r[9]] + } + + fn syscall_ret(frame: &Self::Frame) -> usize { + frame.r[4] + } + + fn set_syscall_ret(frame: &mut Self::Frame, ret: usize) { + frame.r[4] = ret; + } + + fn set_user_arg(frame: &mut Self::Frame, index: usize, value: usize) { + frame.r[4 + index] = value; + } + + fn set_kernel_hartid(frame: &mut Self::Frame, hartid: usize) { + frame.kernel_hartid = hartid; + } + + fn set_kernel_sp(frame: &mut Self::Frame, kernel_sp: usize) { + frame.kernel_sp = kernel_sp; + } + + fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32] { + let mut exported = frame.r; + exported[0] = frame.era; + exported + } + + fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]) { + frame.r.copy_from_slice(signal_gprs); + frame.r[0] = 0; + frame.era = signal_gprs[0]; + } + + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { + fpregs.copy_from_slice(&frame.f); + *fcsr = frame.fcsr as u32; + } + + fn restore_fp_state(frame: &mut Self::Frame, fpregs: &[u64; 32], fcsr: u32) { + frame.f.copy_from_slice(fpregs); + frame.fcsr = fcsr as usize; + } +} + +#[inline] +fn read_estat() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_ESTAT) }; + value +} + +#[inline] +fn read_badv() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_BADV) }; + value +} + +#[inline] +unsafe fn update_ecfg(mask: usize, enable: bool) { + let mut ecfg: usize; + asm!("csrrd {}, {}", out(reg) ecfg, const CSR_ECFG); + if enable { + ecfg |= mask; + } else { + ecfg &= !mask; + } + asm!("csrwr {}, {}", in(reg) ecfg, const CSR_ECFG); +} diff --git a/os/src/arch/mod.rs b/os/src/arch/mod.rs index f7a96240..d4920fbd 100644 --- a/os/src/arch/mod.rs +++ b/os/src/arch/mod.rs @@ -1,2 +1,7 @@ //! Architecture-specific implementations. + +#[cfg(target_arch = "riscv64")] pub mod riscv; + +#[cfg(target_arch = "loongarch64")] +pub mod loongarch64; diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs index e3ee6454..ae5e1c14 100644 --- a/os/src/arch/riscv/hart.rs +++ b/os/src/arch/riscv/hart.rs @@ -2,7 +2,7 @@ use core::arch::asm; use crate::hal::traits::HartId; -use riscv::{asm::wfi, register::{mstatus::FS, sstatus}}; +use riscv::{asm::wfi, register::{mstatus::{set_fs, FS}, sstatus}}; /// RISC-V implementation of [`HartId`](crate::hal::traits::HartId) via the `tp` register. pub struct RiscvHartId; @@ -22,7 +22,7 @@ impl HartId for RiscvHartId { #[inline] unsafe fn enable_fp() { - sstatus::set_fs(FS::Initial); + set_fs(FS::Initial); } #[inline] diff --git a/os/src/boards/loongarch_virt.rs b/os/src/boards/loongarch_virt.rs new file mode 100644 index 00000000..5e362501 --- /dev/null +++ b/os/src/boards/loongarch_virt.rs @@ -0,0 +1,94 @@ +//! QEMU LoongArch64 virt machine. + +/// Direct-mapped uncached I/O virtual-address offset used during early bring-up. +pub const IO_ADDR_OFFSET: usize = 0x8000_0000_0000_0000; +/// Direct-mapped cached kernel-address offset used during early bring-up. +pub const KERNEL_ADDR_OFFSET: usize = 0x9000_0000_0000_0000; + +/// QEMU loongarch64 virt clock frequency. +pub const CLOCK_FREQ: usize = 100_000_000; + +/// MMIO windows used by the kernel on QEMU loongarch64 virt (uncached DMW0 window). +pub const MMIO: &[(usize, usize)] = &[ + (IO_ADDR_OFFSET | 0x1fe0_0000, 0x10000), // covers all 1fe0_xxxx MMIO + (IO_ADDR_OFFSET | 0x1fe2_0000, 0x8000), // VirtIO +]; + +/// UART MMIO virtual address (uncached DMW0 window). +pub const VIRT_UART: usize = IO_ADDR_OFFSET | 0x1fe0_01e0; +/// RTC-compatible MMIO virtual address (uncached DMW0 window). +pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x1fe0_01f8; +/// Alias for compatibility. +pub const VIRT_UART_EARLY: usize = VIRT_UART; + +/// Block device implementation for QEMU virt. +pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; +/// Char device implementation for QEMU virt. +pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; + +use core::arch::asm; + +const EXIT_SUCCESS: u32 = 0x5555; +const EXIT_FAILURE_FLAG: u32 = 0x3333; +const EXIT_FAILURE: u32 = exit_code_encode(1); + +/// QEMU exit interface. +pub trait QEMUExit { + /// Exit with specified return code. + fn exit(&self, code: u32) -> !; + + /// Exit QEMU using `EXIT_SUCCESS`. + fn exit_success(&self) -> !; + + /// Exit QEMU using `EXIT_FAILURE`. + fn exit_failure(&self) -> !; +} + +/// LoongArch64 QEMU exit device wrapper. +pub struct LOONGARCH64 { + addr: u64, +} + +const fn exit_code_encode(code: u32) -> u32 { + (code << 16) | EXIT_FAILURE_FLAG +} + +impl LOONGARCH64 { + /// Create an instance. + pub const fn new(addr: u64) -> Self { + Self { addr } + } +} + +impl QEMUExit for LOONGARCH64 { + fn exit(&self, code: u32) -> ! { + let code_new = match code { + EXIT_SUCCESS | EXIT_FAILURE => code, + _ => exit_code_encode(code), + }; + + unsafe { + asm!( + "st.w {0}, {1}, 0", + in(reg) code_new, + in(reg) self.addr, + ); + loop { + asm!("idle 0", options(nomem, nostack)); + } + } + } + + fn exit_success(&self) -> ! { + self.exit(EXIT_SUCCESS); + } + + fn exit_failure(&self) -> ! { + self.exit(EXIT_FAILURE); + } +} + +const VIRT_TEST: u64 = (IO_ADDR_OFFSET | 0x1fe0_01e0) as u64; + +/// Global QEMU exit handle using the virt test device. +pub const QEMU_EXIT_HANDLE: LOONGARCH64 = LOONGARCH64::new(VIRT_TEST); diff --git a/os/src/config.rs b/os/src/config.rs index 3d9592bf..6cb5fe20 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -9,7 +9,12 @@ pub const KERNEL_STACK_SIZE: usize = 4096 * 32; /// kernel heap size pub const MAX_KERNEL_HEAP_SIZE: usize = 0x4000_0000; /// base address of the dynamically mapped kernel heap window +#[cfg(target_arch = "riscv64")] pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; +/// LoongArch64: heap window in the low-half (bit 38 = 0) so PGDL is used by +/// the hardware TLB walker. Placed far above user VAs (USER_SPACE_END=2^38). +#[cfg(target_arch = "loongarch64")] +pub const KERNEL_HEAP_BASE: usize = 0x0000_0038_0000_0000; /// max harts reserved by the kernel SMP bootstrap path pub const MAX_HARTS: usize = 8; /// QEMU virt 1GiB 内存的物理结束地址,起始地址为 0x8000_0000。 diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 44d97c6a..9a64380a 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -22,6 +22,7 @@ fn virtio_blk_name(idx: usize) -> String { pub fn init() { chardev::init(); rtc::init(); + #[cfg(target_arch = "riscv64")] plic::init(); probe_virtio_devices(); } diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index 6d1d90a9..de497371 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -12,9 +12,19 @@ pub use crate::arch::riscv::{ RiscvTrapMachine as ArchTrapMachine, Sv39Paging as ArchPaging, }; -#[cfg(feature = "platform-qemu-virt")] +#[cfg(target_arch = "loongarch64")] +pub use crate::arch::loongarch64::{ + LoongArchHartId as ArchHart, LoongArchInterruptControl as ArchInterrupt, + LoongArchTrapContextAbi as ArchTrapContextAbi, + LoongArchTrapMachine as ArchTrapMachine, LoongArchPaging as ArchPaging, +}; + +#[cfg(target_arch = "riscv64")] pub use crate::platform::qemu_virt::SbiPlatform as Plat; +#[cfg(target_arch = "loongarch64")] +pub use crate::platform::loongarch_virt::LoongArchPlatform as Plat; + /// Return the current hart id. #[inline] pub fn hartid() -> usize { @@ -95,6 +105,12 @@ pub unsafe fn flush_tlb() { ArchPaging::flush_tlb(); } +/// Encode a non-leaf directory PTE (must not set GNR/GNX on LoongArch). +#[inline] +pub fn make_dir_entry(ppn: usize) -> usize { + ArchPaging::make_dir_entry(ppn) +} + /// Encode one architecture-specific PTE from a physical page number and semantic flags. #[inline] pub fn make_pte(ppn: usize, flags: PTEFlags) -> usize { diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index f0a28fc8..1f96caad 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -192,6 +192,13 @@ pub trait PagingArch { unsafe fn flush_tlb(); /// Encode one leaf/intermediate PTE for this architecture. fn make_pte(ppn: usize, flags: PTEFlags) -> usize; + /// Encode a non-leaf directory entry pointing to the next page-table level. + /// Architectures where leaf and directory entries have different encodings + /// (e.g. LoongArch, which must NOT set GNR/GNX in directory entries) should + /// override this. Default: delegates to make_pte with V only. + fn make_dir_entry(ppn: usize) -> usize { + Self::make_pte(ppn, PTEFlags::V) + } /// Extract the pointed-to physical page number from one raw PTE. fn pte_ppn(entry_bits: usize) -> usize; /// Extract the semantic flags from one raw PTE. diff --git a/os/src/linker-loongarch64.ld b/os/src/linker-loongarch64.ld new file mode 100644 index 00000000..576c90d3 --- /dev/null +++ b/os/src/linker-loongarch64.ld @@ -0,0 +1,54 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) +VIRT_BASE = 0x9000000090000000; +PHYS_BASE = 0x90000000; + +SECTIONS +{ + . = VIRT_BASE; + skernel = .; + + stext = .; + .text : AT(PHYS_BASE) { + *(.text.entry) + . = ALIGN(4K); + strampoline = .; + *(.text.trampoline); + . = ALIGN(4K); + *(.text .text.*) + } + + . = ALIGN(4K); + etext = .; + srodata = .; + .rodata : AT(PHYS_BASE + (srodata - VIRT_BASE)) { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + + . = ALIGN(4K); + erodata = .; + sdata = .; + .data : AT(PHYS_BASE + (sdata - VIRT_BASE)) { + *(.data .data.*) + *(.sdata .sdata.*) + } + + . = ALIGN(4K); + edata = .; + sbss_with_stack = .; + .bss : AT(PHYS_BASE + (sbss_with_stack - VIRT_BASE)) { + *(.bss.stack) + sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + } + + . = ALIGN(4K); + ebss = .; + ekernel = .; + + /DISCARD/ : { + *(.eh_frame) + } +} diff --git a/os/src/main.rs b/os/src/main.rs index 3da0384e..1d257865 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -33,9 +33,14 @@ extern crate alloc; #[macro_use] extern crate bitflags; +#[cfg(target_arch = "riscv64")] #[path = "boards/qemu.rs"] mod board; +#[cfg(target_arch = "loongarch64")] +#[path = "boards/loongarch_virt.rs"] +mod board; + pub mod arch; pub mod hal; pub mod platform; @@ -173,6 +178,7 @@ fn print_boot_stage(stage: &str, detail: &str) { fn init_local_hart(hart_id: usize) { trap::init_hart(); timer::init_hart(); + #[cfg(target_arch = "riscv64")] drivers::plic::init_hart(hart_id); mm::mark_online(hart_id); debug!("hart {} local init done", hart_id); @@ -195,6 +201,7 @@ fn detect_hart_count() -> usize { hart_count.max(1) } +#[cfg(target_arch = "riscv64")] /// 记录当前环境下各 hart 的 HSM 状态,并尝试拉起处于 stopped 状态的 hart。 /// /// 这里的目标不是“盲目对所有 hart 重复 `hart_start`”,而是先看清固件报告的 @@ -284,11 +291,29 @@ fn wait_for_bootstrap() { } } +/// Bootstrap-phase bare UART write (DMW0 window, before drivers::init). +#[cfg(target_arch = "loongarch64")] +pub fn early_puts(s: &str) { + const UART: usize = 0x8000_0000_1fe0_01e0; + for b in s.bytes() { + unsafe { + while core::ptr::read_volatile((UART + 5) as *const u8) & 0x20 == 0 {} + core::ptr::write_volatile(UART as *mut u8, b); + } + } +} + /// bootstrap hart 的主入口 fn first_hart_main(hart_id: usize) -> ! { clear_bss(); BOOT_BSS_READY.store(0, Ordering::Release); + #[cfg(target_arch = "loongarch64")] early_puts("[K] trap::init\r\n"); + // Install TLB refill handler and page-walker CSRs before activating page tables. + trap::init(); + #[cfg(target_arch = "loongarch64")] early_puts("[K] mm::init\r\n"); mm::init(); + #[cfg(target_arch = "loongarch64")] early_puts("[K] klog::init\r\n"); + #[cfg(not(target_arch = "loongarch64"))] mm::remap_test(); klog::init(); let hart_count = detect_hart_count(); @@ -339,9 +364,23 @@ pub fn rust_main(hart_id: usize) -> ! { unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); } - if !try_claim_bootstrap_hart(hart_id) { - secondary_hart_main(hart_id); - } else { + #[cfg(target_arch = "loongarch64")] + { + // LoongArch64 bring-up currently follows a single-bootstrap-hart path. + if !try_claim_bootstrap_hart(hart_id) { + secondary_hart_main(hart_id); + } first_hart_main(hart_id); } + #[cfg(target_arch = "riscv64")] + { + unsafe { + riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); + } + if !try_claim_bootstrap_hart(hart_id) { + secondary_hart_main(hart_id); + } else { + first_hart_main(hart_id); + } + } } diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 43080344..2335453e 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -5,6 +5,22 @@ use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; const VPN_WIDTH_BITS: usize = crate::hal::virt_addr_bits() - PAGE_SIZE_BITS; + +/// Convert a physical address to a kernel virtual address for direct access. +/// +/// On RiscV the kernel is linked at its physical address so this is a no-op. +/// On LoongArch the kernel lives in the DMW1 cached window, so physical +/// addresses need the 0x9000_0000_0000_0000 prefix to be accessible. +#[inline(always)] +pub fn phys_to_virt(pa: usize) -> usize { + #[cfg(target_arch = "loongarch64")] + { + use crate::board::KERNEL_ADDR_OFFSET; + pa | KERNEL_ADDR_OFFSET + } + #[cfg(not(target_arch = "loongarch64"))] + pa +} /// Exclusive end of the canonical low-half user virtual-address range. pub const USER_SPACE_END: usize = { let _ = crate::hal::page_table_levels(); @@ -70,12 +86,18 @@ impl From for PhysPageNum { } impl From for VirtAddr { fn from(v: usize) -> Self { - Self(v & ((1 << crate::hal::virt_addr_bits()) - 1)) + #[cfg(target_arch = "loongarch64")] + return Self(v); + #[cfg(not(target_arch = "loongarch64"))] + return Self(v & ((1 << crate::hal::virt_addr_bits()) - 1)); } } impl From for VirtPageNum { fn from(v: usize) -> Self { - Self(v & ((1 << VPN_WIDTH_BITS) - 1)) + #[cfg(target_arch = "loongarch64")] + return Self(v >> PAGE_SIZE_BITS); + #[cfg(not(target_arch = "loongarch64"))] + return Self(v & ((1 << VPN_WIDTH_BITS) - 1)); } } impl From for usize { @@ -164,11 +186,11 @@ impl From for PhysAddr { impl PhysAddr { /// Get the immutable reference of physical address pub fn get_ref(&self) -> &'static T { - unsafe { (self.0 as *const T).as_ref().unwrap() } + unsafe { (phys_to_virt(self.0) as *const T).as_ref().unwrap() } } /// Get the mutable reference of physical address pub fn get_mut(&self) -> &'static mut T { - unsafe { (self.0 as *mut T).as_mut().unwrap() } + unsafe { (phys_to_virt(self.0) as *mut T).as_mut().unwrap() } } } impl PhysPageNum { @@ -176,12 +198,12 @@ impl PhysPageNum { pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { let pa: PhysAddr = (*self).into(); let entry_count = 1usize << crate::hal::page_table_index_bits(); - unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, entry_count) } + unsafe { core::slice::from_raw_parts_mut(phys_to_virt(pa.0) as *mut PageTableEntry, entry_count) } } /// Get the reference of page(array of bytes) pub fn get_bytes_array(&self) -> &'static mut [u8] { let pa: PhysAddr = (*self).into(); - unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) } + unsafe { core::slice::from_raw_parts_mut(phys_to_virt(pa.0) as *mut u8, 4096) } } /// Get the mutable reference of physical address pub fn get_mut(&self) -> &'static mut T { diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 7193f14d..d45a0061 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -1,7 +1,7 @@ //! The heap allocator. use super::frame_allocator::{frame_alloc, frame_alloc_contiguous, frame_dealloc}; -use super::{PageTableEntry, PhysPageNum, PTEFlags, VirtAddr, KERNEL_SPACE}; +use super::{phys_to_virt, PageTableEntry, PhysPageNum, PTEFlags, VirtAddr, KERNEL_SPACE}; use crate::config::{ KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE, MEMORY_END, PAGE_SIZE, PAGE_SIZE_BITS, }; @@ -114,6 +114,9 @@ pub fn init_kernel_heap_mapping() { .lock() .page_table .ensure_subtree_root_untracked(base_vpn); + if subtree_root_ppn.0 == 0 { + panic!("ensure_subtree_root_untracked returned PPN 0"); + } KERNEL_HEAP_SUBTREE_ROOT_PPN.store(subtree_root_ppn.0, Ordering::Release); } @@ -213,10 +216,19 @@ pub fn init_heap() { pub fn init_heap_virtual_window() { KERNEL_HEAP_VIRTUAL_READY.store(true, Ordering::Release); + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] grow start\r\n"); assert!( HEAP_ALLOCATOR.grow(KERNEL_HEAP_GROW_SIZE), "failed to initialize virtual kernel heap" ); + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] grow done\r\n"); +} + +/// Map a single heap VA page. Called from trap_from_kernel on LoongArch. +pub fn map_one_heap_page(va: usize) -> bool { + map_heap_pages(va, 1) } fn layout_required_bytes(layout: Layout) -> Option { @@ -283,7 +295,7 @@ fn alloc_bootstrap_heap_pages(pages: usize) -> Option { let first = frames.start_ppn(); core::mem::forget(frames); let start_pa: super::PhysAddr = first.into(); - Some(start_pa.into()) + Some(phys_to_virt(start_pa.into())) } /// Index the leaf PTE slot for a kernel-heap VA, given the pre-built cached @@ -293,22 +305,29 @@ fn alloc_bootstrap_heap_pages(pages: usize) -> Option { /// the kernel root page table that `KERNEL_SPACE` guards. /// /// Caller must hold [`HEAP_PT_LOCK`]. +/// Walk from `subtree_root_ppn` (which PGDL's root[0] already points to) down +/// to the leaf PTE slot for `vpn`. `subtree_root_ppn` is at depth 1, so we +/// walk exactly `levels - 2` more directory hops before reaching the leaf table. +/// +/// Caller must hold [`HEAP_PT_LOCK`]. fn heap_leaf_pte( subtree_root_ppn: PhysPageNum, vpn: super::VirtPageNum, ) -> Option<*mut PageTableEntry> { let levels = crate::hal::page_table_levels(); let mut ppn = subtree_root_ppn; + // Walk levels 1 .. levels-1 (directories), then return the leaf slot at level levels-1. for level in 1..levels { let idx = crate::hal::vpn_index(vpn.0, level); let pte = &mut ppn.get_pte_array()[idx]; if level + 1 == levels { + // This pte slot IS the leaf PTE (will be filled by map_heap_pages). return Some(pte as *mut PageTableEntry); } if !pte.is_valid() { let frame = frame_alloc()?; - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); - // Lower-level heap page tables are permanent kernel mappings. + frame.ppn.get_bytes_array().fill(0); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); core::mem::forget(frame); } ppn = pte.ppn(); @@ -317,13 +336,17 @@ fn heap_leaf_pte( } fn map_heap_pages(start_va: usize, pages: usize) -> bool { + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] map_heap_pages\r\n"); let subtree_root_ppn = PhysPageNum(KERNEL_HEAP_SUBTREE_ROOT_PPN.load(Ordering::Acquire)); - debug_assert!( - subtree_root_ppn.0 != 0, - "kernel heap mapping used before init" - ); - + if subtree_root_ppn.0 == 0 { + panic!("map_heap_pages: subtree root ppn is 0"); + } + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] locking HEAP_PT_LOCK\r\n"); let _guard = HEAP_PT_LOCK.lock(); + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] HEAP_PT_LOCK locked\r\n"); for page in 0..pages { let va = start_va + page * PAGE_SIZE; let vpn = VirtAddr::from(va).floor(); @@ -347,6 +370,8 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { core::mem::forget(frame); } unsafe { crate::hal::flush_tlb() }; + #[cfg(target_arch = "loongarch64")] + crate::early_puts("[heap] pages mapped, flush done\r\n"); true } diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 47ffe4b7..2380979f 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -799,6 +799,9 @@ impl MemorySet { } /// Mention that trampoline is not collected by areas. fn map_trampoline(&mut self) { + // On LoongArch the trampoline lives in the DMW1 kernel window and is + // accessible without a page-table entry. + #[cfg(not(target_arch = "loongarch64"))] self.page_table .map( VirtAddr::from(TRAMPOLINE).into(), @@ -812,6 +815,10 @@ impl MemorySet { let mut memory_set = Self::new_bare(); // map trampoline memory_set.map_trampoline(); + // On LoongArch, kernel sections / physical memory / MMIO are all covered by + // DMW windows and do not need software page-table entries. + #[cfg(not(target_arch = "loongarch64"))] + { // map kernel sections info!(".text [{:#x}, {:#x})", stext as usize, etext as usize); info!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); @@ -900,6 +907,7 @@ impl MemorySet { ) .expect("failed to map mmio window"); } + } // end #[cfg(not(loongarch64))] memory_set } /// Include ELF segments and trampoline, and compute initial process VM layout. diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index 12690ce6..e75ec1a8 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -43,11 +43,12 @@ pub enum PageFaultHandled { NotHandled, } -pub use address::{PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; +pub use address::{phys_to_virt, PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; pub use frame_allocator::{ frame_alloc, frame_alloc_contiguous, frame_allocator_stats, frame_dealloc, frame_dealloc_range, ContiguousFrames, FrameAllocatorStats, FrameTracker, }; +pub use heap_allocator::map_one_heap_page; pub use memory_set::remap_test; pub use memory_set::{ invalidate_inode_mappings_after_truncate, kernel_token, register_file_mapping, @@ -67,13 +68,17 @@ pub use crate::hal::traits::PTEFlags; /// initiate heap allocator, frame allocator and kernel space pub fn init() { + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] frame_allocator\r\n"); frame_allocator::init_frame_allocator(); + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] init_heap\r\n"); heap_allocator::init_heap(); + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] activate\r\n"); KERNEL_SPACE.lock().activate(); - // Build the kernel-heap window's page-table backbone before any virtual-window - // growth, so growth never recurses into the `KERNEL_SPACE` lock. + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] heap_mapping\r\n"); heap_allocator::init_kernel_heap_mapping(); + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] heap_virtual_window\r\n"); heap_allocator::init_heap_virtual_window(); + #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] done\r\n"); } /// 在当前 hart 上激活内核地址空间(写入 satp + sfence.vma)。 diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index dd5ab6d6..b0fd4e15 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -92,7 +92,7 @@ impl PageTable { } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); self.frames.push(frame); } ppn = pte.ppn(); @@ -113,7 +113,7 @@ impl PageTable { } if !pte.is_valid() { let frame = frame_alloc().ok_or(MmError::OutOfMemory)?; - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); core::mem::forget(frame); } ppn = pte.ppn(); @@ -180,7 +180,7 @@ impl PageTable { let pte = &mut self.root_ppn.get_pte_array()[idx]; if !pte.is_valid() { let frame = frame_alloc().unwrap(); - *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + pte.bits = crate::hal::make_dir_entry(frame.ppn.0); core::mem::forget(frame); } pte.ppn() diff --git a/os/src/platform/loongarch_virt.rs b/os/src/platform/loongarch_virt.rs new file mode 100644 index 00000000..103cdc27 --- /dev/null +++ b/os/src/platform/loongarch_virt.rs @@ -0,0 +1,35 @@ +//! LoongArch64 QEMU virt platform hooks. + +pub use crate::board::{ + BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, VIRT_RTC, + VIRT_UART, +}; + +use crate::hal::traits::{HartCtrl, Timer}; + +/// LoongArch64 platform implementation used by the generic HAL façade. +pub struct LoongArchPlatform; + +impl Timer for LoongArchPlatform { + fn read_time() -> usize { + crate::arch::loongarch64::read_time() + } + + fn set_next(deadline: usize) { + unsafe { crate::arch::loongarch64::set_timer_deadline(deadline) }; + } + + fn clock_freq() -> usize { + crate::config::CLOCK_FREQ + } +} + +impl HartCtrl for LoongArchPlatform { + fn start_hart(_hart_id: usize, _start_addr: usize, _opaque: usize) -> Result<(), ()> { + Err(()) + } + + fn send_ipi(_hart_mask: usize) { + // Single-core bring-up only for now. + } +} diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index 5539b968..f018c62f 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -1,3 +1,8 @@ //! Platform-specific implementations. #![allow(missing_docs)] + +#[cfg(target_arch = "riscv64")] pub mod qemu_virt; + +#[cfg(target_arch = "loongarch64")] +pub mod loongarch_virt; diff --git a/os/src/platform/qemu_virt/mod.rs b/os/src/platform/qemu_virt/mod.rs index f2a75628..49e2d3de 100644 --- a/os/src/platform/qemu_virt/mod.rs +++ b/os/src/platform/qemu_virt/mod.rs @@ -4,6 +4,6 @@ pub mod sbi; pub use crate::board::{ BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, - RISCV64, VIRT_RTC, VIRT_UART, + VIRT_RTC, VIRT_UART, }; pub use sbi::SbiPlatform; diff --git a/os/src/sbi.rs b/os/src/sbi.rs index f40d8747..17172a69 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -4,6 +4,12 @@ #![allow(unused)] use core::arch::asm; +use crate::fs::sync_page_cache_all; + +#[cfg(target_arch = "loongarch64")] +use crate::board::{QEMUExit, QEMU_EXIT_HANDLE}; +#[cfg(target_arch = "loongarch64")] +use crate::drivers::chardev::CharDevice; /// SBI v0.2+ 调用返回值。 #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -76,6 +82,7 @@ fn sbi_call_legacy(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize /// 通用 SBI v0.2+ 扩展调用。 #[inline(always)] +#[cfg(target_arch = "riscv64")] fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize, arg2: usize) -> SbiRet { let error: usize; let value: usize; @@ -97,42 +104,67 @@ fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize, arg2: u /// use sbi call to set timer #[cfg(qemu7)] +#[cfg(target_arch = "riscv64")] pub fn set_timer(timer: usize) { sbi_call_legacy(SBI_SET_TIMER, timer, 0, 0); } /// use sbi call to putchar in console (qemu uart handler) #[cfg(not(qemu7))] +#[cfg(target_arch = "riscv64")] pub fn set_timer(timer: usize) { let _ = sbi_call(SBI_SET_TIMER, 0, timer, 0, 0); } +#[cfg(target_arch = "loongarch64")] +/// Program the next local timer deadline on LoongArch64. +pub fn set_timer(timer: usize) { + unsafe { crate::arch::loongarch64::set_timer_deadline(timer) }; +} + /// use sbi call to putchar in console (qemu uart handler) #[cfg(qemu7)] +#[cfg(target_arch = "riscv64")] pub fn console_putchar(c: usize) { sbi_call_legacy(SBI_CONSOLE_PUTCHAR, c, 0, 0); } /// use sbi call to getchar from console (qemu uart handler) #[cfg(not(qemu7))] +#[cfg(target_arch = "riscv64")] pub fn console_putchar(c: usize) { let _ = sbi_call(SBI_CONSOLE_PUTCHAR, 0, c, 0, 0); } +#[cfg(target_arch = "loongarch64")] +/// Write one byte to the early console on LoongArch64. +pub fn console_putchar(c: usize) { + crate::drivers::chardev::UART.write(c as u8); +} + /// use sbi call to getchar from console (qemu uart handler) #[cfg(qemu7)] +#[cfg(target_arch = "riscv64")] pub fn console_getchar() -> usize { sbi_call_legacy(SBI_CONSOLE_GETCHAR, 0, 0, 0) } /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] +#[cfg(target_arch = "riscv64")] pub fn console_getchar() -> usize { sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0, 0).value } +#[cfg(target_arch = "loongarch64")] +/// Read one byte from the early console on LoongArch64. +pub fn console_getchar() -> usize { + crate::drivers::chardev::UART.read() as usize +} + /// use sbi call to shutdown the kernel #[cfg(qemu7)] +#[cfg(target_arch = "riscv64")] pub fn shutdown() -> ! { let _ = sync_page_cache_all(); sbi_call_legacy(SBI_SHUTDOWN, 0, 0, 0); @@ -141,14 +173,23 @@ pub fn shutdown() -> ! { /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] +#[cfg(target_arch = "riscv64")] pub fn shutdown() -> ! { let _ = sync_page_cache_all(); let _ = sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); panic!("It should shutdown!"); } +#[cfg(target_arch = "loongarch64")] +/// Shut down the LoongArch64 QEMU guest via the board exit device. +pub fn shutdown() -> ! { + let _ = sync_page_cache_all(); + QEMU_EXIT_HANDLE.exit_success() +} + /// 发送 IPI 到给定 hart mask。 #[cfg(qemu7)] +#[cfg(target_arch = "riscv64")] pub fn send_ipi_mask(hart_mask: usize) { let hart_mask_ptr = &hart_mask as *const usize as usize; sbi_call_legacy(SBI_SEND_IPI, hart_mask_ptr, 0, 0); @@ -156,16 +197,23 @@ pub fn send_ipi_mask(hart_mask: usize) { /// 发送 IPI 到给定 hart mask。 #[cfg(not(qemu7))] +#[cfg(target_arch = "riscv64")] pub fn send_ipi_mask(hart_mask: usize) { let _ = sbi_call(SBI_IPI, 0, hart_mask, 0, 0); } +#[cfg(target_arch = "loongarch64")] +/// Single-core bring-up keeps IPI as a no-op on LoongArch64 for now. +pub fn send_ipi_mask(_hart_mask: usize) {} + /// 查询指定 hart 的 HSM 状态。 +#[cfg(target_arch = "riscv64")] pub fn hart_get_status(hart_id: usize) -> SbiRet { sbi_call(SBI_HSM, 2, hart_id, 0, 0) } /// 请求启动指定 hart,并让它从 `start_addr` 开始执行。 +#[cfg(target_arch = "riscv64")] pub fn hart_start(hart_id: usize, start_addr: usize, opaque: usize) -> SbiRet { sbi_call(SBI_HSM, 0, hart_id, start_addr, opaque) } @@ -183,4 +231,3 @@ pub fn hart_state(raw: usize) -> HartState { other => HartState::Unknown(other), } } -use crate::fs::sync_page_cache_all; diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index a28fb449..6962cd50 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -1225,7 +1225,10 @@ impl UtsName { let nodename = b"localhost"; let release = b"6.6.0"; let version = b"#1 SMP PREEMPT cosmOS"; + #[cfg(target_arch = "riscv64")] let machine = b"riscv64"; + #[cfg(target_arch = "loongarch64")] + let machine = b"loongarch64"; let domainname = b"localdomain"; uname.sysname[..sysname.len()].copy_from_slice(sysname); uname.nodename[..nodename.len()].copy_from_slice(nodename); diff --git a/os/src/timer.rs b/os/src/timer.rs index e028af38..8644a0e8 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -7,17 +7,17 @@ use crate::config::CLOCK_FREQ; use crate::config::MAX_HARTS; use crate::drivers::rtc; use crate::hal::hartid; +use crate::hal::Plat; +use crate::hal::traits::Timer as _; use crate::poll::{self, PollTimerTag}; use crate::net::{handle_socket_wait_timeout, SocketTimerTag}; use crate::signal::{handle_signal_wait_timeout, SignalTimerTag}; -use crate::sbi::set_timer; use crate::sync::{FutexTimerTag, SpinNoIrqLock, handle_futex_wait_timeout}; use crate::task::{current_task, wakeup_task, TaskControlBlock}; use alloc::collections::BinaryHeap; use alloc::sync::Arc; use core::array; use lazy_static::*; -use riscv::register::time; /// The number of ticks per second pub const TICKS_PER_SEC: usize = 100; /// The number of milliseconds per second @@ -36,22 +36,22 @@ static REALTIME_OFFSET_NS: AtomicI64 = AtomicI64::new(0); /// Get the current time in ticks pub fn get_time() -> usize { - time::read() + Plat::read_time() } /// Get the current time in milliseconds pub fn get_time_ms() -> usize { - time::read() * MSEC_PER_SEC / CLOCK_FREQ + get_time() * MSEC_PER_SEC / CLOCK_FREQ } /// get current time in microseconds pub fn get_time_us() -> usize { - time::read() * MICRO_PER_SEC / CLOCK_FREQ + get_time() * MICRO_PER_SEC / CLOCK_FREQ } /// 获取当前单调时间,单位为纳秒。 pub fn get_time_ns() -> u64 { - ((time::read() as u128) * (NSEC_PER_SEC as u128) / (CLOCK_FREQ as u128)) as u64 + ((get_time() as u128) * (NSEC_PER_SEC as u128) / (CLOCK_FREQ as u128)) as u64 } /// 使用“当前单调时间 + 实时时钟偏移”得到 `CLOCK_REALTIME`,单位为纳秒。 @@ -87,7 +87,7 @@ pub fn init_realtime_offset_from_rtc() { /// Get current time in clock ticks used by times(2). pub fn get_time_ticks() -> usize { - time::read() * TICKS_PER_SEC / CLOCK_FREQ + get_time() * TICKS_PER_SEC / CLOCK_FREQ } /// Convert a raw timer counter delta into clock ticks used by times(2). @@ -272,7 +272,7 @@ fn program_next_trigger_for_hart(hart: usize, now_raw: usize) { None => periodic_raw, }; let now_raw = now_raw as u64; - set_timer(next_raw.max(now_raw.saturating_add(1)) as usize); + Plat::set_next(next_raw.max(now_raw.saturating_add(1)) as usize); } /// Check if the timer has expired for the current hart. diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 9420bf2b..dc38e634 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -339,6 +339,7 @@ pub fn trap_handler() -> ! { handle_reschedule_ipi(); } TrapCause::ExternalInterrupt => { + #[cfg(target_arch = "riscv64")] crate::drivers::plic::handle_supervisor_external(); crate::net::poll(); } @@ -388,6 +389,7 @@ pub fn trap_from_kernel() { let trap_info = ArchTrapMachine::read_trap_info(); match trap_info.cause { TrapCause::ExternalInterrupt => { + #[cfg(target_arch = "riscv64")] crate::drivers::plic::handle_supervisor_external(); crate::net::poll(); // 处理完外部中断后立即poll,让smoltcp响应ARP等请求 } diff --git a/user/Cargo.toml b/user/Cargo.toml index 1179ec04..bdd50193 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -17,9 +17,3 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } opt-level = "z" # Optimize for size. strip = true # Automatically strip symbols from the binary. lto = true - -[target.riscv64gc-unknown-none-elf] -rustflags = [ - "-C", "link-arg=-Tsrc/linker.ld", - "-C", "target-feature=+f,+d", -] \ No newline at end of file diff --git a/user/Makefile b/user/Makefile index 453d3a8c..ba495b7b 100644 --- a/user/Makefile +++ b/user/Makefile @@ -1,4 +1,13 @@ -TARGET := riscv64gc-unknown-none-elf +ARCH ?= riscv64 +ifeq ($(ARCH),loongarch64) + TARGET := loongarch64-unknown-none + OBJDUMP := rust-objdump --arch-name=loongarch64 + OBJCOPY := rust-objcopy --binary-architecture=loongarch64 +else + TARGET := riscv64gc-unknown-none-elf + OBJDUMP := rust-objdump --arch-name=riscv64 + OBJCOPY := rust-objcopy --binary-architecture=riscv64 +endif MODE := release APP_DIR := src/bin TARGET_DIR := target/$(TARGET)/$(MODE) diff --git a/user/build.py b/user/build.py index b4061821..aa8a9326 100644 --- a/user/build.py +++ b/user/build.py @@ -3,6 +3,7 @@ base_address = 0x80400000 step = 0x20000 linker = "src/linker.ld" +target = os.getenv("TARGET", "riscv64gc-unknown-none-elf") app_id = 0 apps = os.listdir("build/app") @@ -17,8 +18,8 @@ for app in apps: app = app[: app.find(".")] os.system( - "cargo rustc --bin %s %s -- -Clink-args=-Ttext=%x" - % (app, mode_arg, base_address + step * app_id) + "cargo rustc --target %s --bin %s %s -- -Clink-args=-Ttext=%x" + % (target, app, mode_arg, base_address + step * app_id) ) print( "[build.py] application %s start with address %s" diff --git a/user/cargo-config/config.toml b/user/cargo-config/config.toml index e5ded8a1..d20e8541 100644 --- a/user/cargo-config/config.toml +++ b/user/cargo-config/config.toml @@ -4,4 +4,10 @@ target = "riscv64gc-unknown-none-elf" [target.riscv64gc-unknown-none-elf] rustflags = [ "-Clink-args=-Tsrc/linker.ld", + "-Ctarget-feature=+f,+d", +] + +[target.loongarch64-unknown-none] +rustflags = [ + "-Clink-args=-Tsrc/linker-loongarch64.ld", ] diff --git a/user/src/bin/remote_shell.rs b/user/src/bin/remote_shell.rs index 86b4d39c..381bf546 100644 --- a/user/src/bin/remote_shell.rs +++ b/user/src/bin/remote_shell.rs @@ -14,6 +14,7 @@ use user_lib::{ // `user_lib::wait()` blocks in a yield-loop, so we issue the raw syscall once. const SYSCALL_WAITPID: usize = 260; +#[cfg(target_arch = "riscv64")] fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; unsafe { @@ -29,6 +30,21 @@ fn syscall(id: usize, args: [usize; 3]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +fn syscall(id: usize, args: [usize; 3]) -> isize { + let mut ret: isize; + unsafe { + asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a7") id, + ); + } + ret +} + fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize { syscall(SYSCALL_WAITPID, [pid as usize, exit_code as usize, 0]) } diff --git a/user/src/lib.rs b/user/src/lib.rs index 92ce1170..eaeb087a 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -47,6 +47,7 @@ fn clear_bss() { } } +#[cfg(target_arch = "riscv64")] global_asm!( r#" .section .text.entry @@ -57,6 +58,17 @@ _start: "# ); +#[cfg(target_arch = "loongarch64")] +global_asm!( + r#" + .section .text.entry + .globl _start +_start: + move $a0, $sp + bl __user_start +"# +); + /// 用户程序入口:从 Linux ABI 初始栈解析 argc/argv 后调用 main。 #[no_mangle] pub extern "C" fn __user_start(user_sp: usize) -> ! { diff --git a/user/src/linker-loongarch64.ld b/user/src/linker-loongarch64.ld new file mode 100644 index 00000000..d1e5c619 --- /dev/null +++ b/user/src/linker-loongarch64.ld @@ -0,0 +1,33 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) + +BASE_ADDRESS = 0x0; + +SECTIONS +{ + . = BASE_ADDRESS; + .text : { + *(.text.entry) + *(.text .text.*) + } + . = ALIGN(4K); + .rodata : { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + } + . = ALIGN(4K); + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + } + .bss : { + start_bss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + end_bss = .; + } + /DISCARD/ : { + *(.eh_frame) + *(.debug*) + } +} diff --git a/user/src/syscall.rs b/user/src/syscall.rs index ff641e20..26ee2f7b 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -88,6 +88,7 @@ pub const SYSCALL_CONDVAR_CREATE: usize = 471; pub const SYSCALL_CONDVAR_SIGNAL: usize = 472; pub const SYSCALL_CONDVAR_WAIT: usize = 473; +#[cfg(target_arch = "riscv64")] pub fn syscall(id: usize, args: [usize; 3]) -> isize { let mut ret: isize; unsafe { @@ -102,6 +103,22 @@ pub fn syscall(id: usize, args: [usize; 3]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +pub fn syscall(id: usize, args: [usize; 3]) -> isize { + let mut ret: isize; + unsafe { + core::arch::asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a7") id, + ); + } + ret +} + +#[cfg(target_arch = "riscv64")] pub fn syscall6(id: usize, args: [usize; 6]) -> isize { let mut ret: isize; unsafe { @@ -118,6 +135,24 @@ pub fn syscall6(id: usize, args: [usize; 6]) -> isize { ret } +#[cfg(target_arch = "loongarch64")] +pub fn syscall6(id: usize, args: [usize; 6]) -> isize { + let mut ret: isize; + unsafe { + core::arch::asm!( + "syscall 0", + inlateout("$a0") args[0] => ret, + in("$a1") args[1], + in("$a2") args[2], + in("$a3") args[3], + in("$a4") args[4], + in("$a5") args[5], + in("$a7") id, + ); + } + ret +} + pub fn sys_openat(dirfd: usize, path: &str, flags: u32, mode: u32) -> isize { syscall6( SYSCALL_OPENAT, From f3c2c5432a31ea4fd6a432747fcb80b305d030f2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 14:10:48 +0800 Subject: [PATCH 07/19] feat: Support boot in LA64 to busybox. --- os/Makefile | 124 +++++++++-------- os/src/arch/loongarch64/hart.rs | 27 ++-- os/src/arch/loongarch64/paging.rs | 24 +++- os/src/arch/loongarch64/trap.S | 20 +-- os/src/arch/loongarch64/trap.rs | 38 ++++- os/src/boards/loongarch_virt.rs | 8 ++ os/src/boards/qemu.rs | 8 ++ os/src/config.rs | 5 + os/src/console.rs | 18 ++- os/src/drivers/block/mod.rs | 13 +- os/src/drivers/block/virtio_blk.rs | 8 +- os/src/drivers/mod.rs | 110 ++------------- os/src/drivers/net/mod.rs | 10 +- os/src/drivers/net/virtio_net.rs | 6 +- os/src/drivers/pci.rs | 215 +++++++++++++++++++++++++++++ os/src/drivers/rtc.rs | 9 ++ os/src/drivers/virtio/mod.rs | 32 ++++- os/src/fs/page_cache.rs | 2 +- os/src/main.rs | 20 +-- os/src/mm/address.rs | 12 ++ os/src/mm/frame_allocator.rs | 4 +- os/src/mm/heap_allocator.rs | 53 ++++++- os/src/mm/memory_set.rs | 29 +++- os/src/mm/mod.rs | 2 +- os/src/mm/page_table.rs | 12 +- os/src/trap/mod.rs | 135 ++++++++++++++++++ user/src/bin/setupsh.rs | 71 +++++++--- 27 files changed, 743 insertions(+), 272 deletions(-) create mode 100644 os/src/drivers/pci.rs diff --git a/os/Makefile b/os/Makefile index 6de86b35..7f786a80 100644 --- a/os/Makefile +++ b/os/Makefile @@ -1,5 +1,16 @@ # Building ARCH ?= riscv64 +ARCH_REQUEST := $(strip $(or $(target),$(ARCH),riscv64)) +ARCH_NORMALIZED := $(shell printf '%s' "$(ARCH_REQUEST)" | tr '[:upper:]' '[:lower:]') + +ifneq ($(filter $(ARCH_NORMALIZED),riscv64 rv64 rv),) + ARCH := riscv64 +else ifneq ($(filter $(ARCH_NORMALIZED),loongarch64 la64 la),) + ARCH := loongarch64 +else +$(error Unsupported target '$(ARCH_REQUEST)'. Use riscv64/rv64/rv or loongarch64/la64/la) +endif + ifeq ($(ARCH),loongarch64) TARGET := loongarch64-unknown-none QEMU ?= qemu-system-loongarch64 @@ -8,11 +19,17 @@ ifeq ($(ARCH),loongarch64) LOONGARCH_BIOS ?= /usr/local/share/qemu/edk2-loongarch64-code.fd LOONGARCH_BOOT_MODE ?= direct LOONGARCH_BOOTLOADER := ../bootloader/loongarch64-direct/target/loongarch64-unknown-none/release/loongarch64-direct-boot + BASE_IMG ?= ../sdcard-la.img + QEMU_MEMORY ?= 4G + GDB ?= loongarch64-unknown-elf-gdb else TARGET := riscv64gc-unknown-none-elf QEMU ?= qemu-system-riscv64 OBJDUMP := rust-objdump --arch-name=riscv64 OBJCOPY := rust-objcopy --binary-architecture=riscv64 + BASE_IMG ?= ../sdcard-rv.img + QEMU_MEMORY ?= 2G + GDB ?= riscv64-unknown-elf-gdb endif MODE ?= release KERNEL_ELF = target/$(TARGET)/$(MODE)/os @@ -24,9 +41,6 @@ FS_IMG ?= ../user/target/$(TARGET)/$(USER_MODE)/fs.img MAIN_FS := ext4 MEM ?= 1G SMP ?= 1 -# Optional prebuilt ext4 image path. When set and valid, fs-fuse clones this -# image and injects user apps into it. -BASE_IMG ?= ../sdcard-rv.img APPS := ../user/src/bin/* OFFLINE := @@ -54,6 +68,38 @@ MODE_ARG = $(if $(filter release,$(MODE)),--release) # KERNEL ENTRY KERNEL_ENTRY_PA := $(if $(filter $(ARCH),loongarch64),0x90000000,0x80200000) +DEBUG_MEMORY ?= 512M +NET_PORT_FORWARD ?= user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777,hostfwd=tcp::7778-:7778,hostfwd=udp::7778-:7778 +QEMU_EXTRA_DEVICES = \ + # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ + # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ + # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 + +ifeq ($(ARCH),loongarch64) +QEMU_COMMON_ARGS = -m $(QEMU_MEMORY) -machine virt -cpu la464 -smp $(SMP) -nographic +QEMU_NET_ARGS = \ + -netdev $(NET_PORT_FORWARD) \ + -device virtio-net-pci,netdev=net0,id=net0 +QEMU_BLK_ARGS = \ + -drive file=$(FS_IMG),if=none,format=raw,id=x0 \ + -device virtio-blk-pci,drive=x0,id=x0 +ifeq ($(LOONGARCH_BOOT_MODE),direct) +QEMU_BOOT_ARGS = -kernel $(LOONGARCH_BOOTLOADER) -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +else +QEMU_BOOT_ARGS = -bios $(LOONGARCH_BIOS) -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) +endif +GDB_ARCH_EX = +else +QEMU_COMMON_ARGS = -m $(QEMU_MEMORY) -machine virt -smp $(SMP) -nographic +QEMU_NET_ARGS = \ + -netdev $(NET_PORT_FORWARD) \ + -device virtio-net-device,netdev=net0,id=net0 +QEMU_BLK_ARGS = \ + -drive file=$(FS_IMG),if=none,format=raw,id=x0 \ + -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +QEMU_BOOT_ARGS = -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) +GDB_ARCH_EX = -ex 'set arch riscv:rv64' +endif # Disassembly DISASM ?= -x @@ -124,84 +170,44 @@ disasm-vim: kernel run: build bootloader run-inner -run-trace: build run-inner-trace +run-trace: build bootloader run-inner-trace fast-run: kernel @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $(KERNEL_BIN) @$(MAKE) run-inner run-inner: -ifeq ($(ARCH),loongarch64) -ifeq ($(LOONGARCH_BOOT_MODE),direct) - @$(QEMU) \ - -m 4G \ - -machine virt \ - -cpu la464 \ - -smp $(SMP) \ - -nographic \ - -kernel $(LOONGARCH_BOOTLOADER) \ - -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) -else @$(QEMU) \ - -m 4G \ - -machine virt \ - -cpu la464 \ - -smp $(SMP) \ - -nographic \ - -bios $(LOONGARCH_BIOS) \ - -device loader,file=$(KERNEL_ELF),addr=$(KERNEL_ENTRY_PA) -endif -else - @$(QEMU) \ - -m $(MEM) \ - -machine virt \ - -smp $(SMP) \ - -nographic \ - -bios $(BOOTLOADER) \ - -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - -netdev user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777,hostfwd=tcp::7778-:7778,hostfwd=udp::7778-:7778 \ - -device virtio-net-device,netdev=net0,id=net0 \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ - # -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ - # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ - # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 -endif + $(QEMU_COMMON_ARGS) \ + $(QEMU_BOOT_ARGS) \ + $(QEMU_NET_ARGS) \ + $(QEMU_BLK_ARGS) # Uncomment above lines to add a second virtio-blk device (for testing mount/umount) run-inner-trace: @$(QEMU) \ - -m $(MEM) \ - -machine virt \ - -smp $(SMP) \ - -nographic \ - -bios $(BOOTLOADER) \ - -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - -netdev user,id=net0,hostfwd=udp::5555-:5555,hostfwd=udp::5564-:5564,hostfwd=tcp::7777-:7777 \ - -object filter-dump,id=ndump,netdev=net0,file=qemu-net.pcap \ - -device virtio-net-device,netdev=net0,id=net0 \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ + $(QEMU_COMMON_ARGS) \ + $(QEMU_BOOT_ARGS) \ + $(QEMU_NET_ARGS) \ + $(QEMU_BLK_ARGS) \ -d int,in_asm \ - -D qemu.log \ - # -drive file=../second-fat32.img,if=none,format=raw,id=x1 \ - # -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 -# Uncomment above lines to add a second virtio-blk device (for testing mount/umount) + -D qemu.log debug: MODE=debug -debug: build +debug: build bootloader @tmux new-session -d \ - "$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ - tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ + "$(QEMU) -m $(DEBUG_MEMORY) $(filter-out -m $(QEMU_MEMORY),$(QEMU_COMMON_ARGS)) $(QEMU_BOOT_ARGS) $(QEMU_NET_ARGS) $(QEMU_BLK_ARGS) -s -S" && \ + tmux split-window -h "$(GDB) -ex 'file $(KERNEL_ELF)' $(GDB_ARCH_EX) -ex 'target remote localhost:1234'" && \ tmux -2 attach-session -d gdbserver: MODE=debug -gdbserver: build - @$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ - $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ +gdbserver: build bootloader + @$(QEMU) -m $(DEBUG_MEMORY) $(filter-out -m $(QEMU_MEMORY),$(QEMU_COMMON_ARGS)) $(QEMU_BOOT_ARGS) $(QEMU_NET_ARGS) $(QEMU_BLK_ARGS) \ -s -S gdbclient: MODE=debug gdbclient: - @riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234' + @$(GDB) -ex 'file $(KERNEL_ELF)' $(GDB_ARCH_EX) -ex 'target remote localhost:1234' .PHONY: build env kernel clean disasm disasm-vim run run-trace fast-run run-inner fs-img debug gdbserver gdbclient diff --git a/os/src/arch/loongarch64/hart.rs b/os/src/arch/loongarch64/hart.rs index 755521ee..93b2d81c 100644 --- a/os/src/arch/loongarch64/hart.rs +++ b/os/src/arch/loongarch64/hart.rs @@ -6,12 +6,14 @@ use crate::hal::traits::HartId; const CSR_CPUID: usize = 0x20; const CSR_CRMD: usize = 0x0; -const CSR_ECFG: usize = 0x4; +const CSR_EUEN: usize = 0x2; const CSR_TCFG: usize = 0x41; -const CSR_TVAL: usize = 0x42; +const CSR_TICLR: usize = 0x44; const CRMD_IE: usize = 1 << 2; -const ECFG_FPE: usize = 1 << 0; +const EUEN_FPEN: usize = 1 << 0; const TCFG_ENABLE: usize = 1 << 0; +const TCFG_PERIODIC: usize = 1 << 1; +const TICLR_CLEAR: usize = 1 << 0; /// LoongArch64 implementation of [`HartId`](crate::hal::traits::HartId). pub struct LoongArchHartId; @@ -26,13 +28,14 @@ pub fn read_time() -> usize { #[inline] pub unsafe fn set_timer_deadline(deadline: usize) { let now = read_time(); - let delta = deadline.saturating_sub(now).max(1); + // TCFG.InitVal requires a multiple-of-4 countdown value. + let delta = deadline.saturating_sub(now).max(4) & !0b11; asm!( - "csrwr {delta}, {tval}", + "csrwr {clear}, {ticlr}", "csrwr {tcfg}, {tcfg_num}", - delta = in(reg) delta, - tcfg = in(reg) (TCFG_ENABLE | (1usize << 1)), - tval = const CSR_TVAL, + clear = in(reg) TICLR_CLEAR, + tcfg = in(reg) (delta | TCFG_ENABLE), + ticlr = const CSR_TICLR, tcfg_num = const CSR_TCFG, ); } @@ -47,10 +50,10 @@ impl HartId for LoongArchHartId { unsafe fn init(_id: usize) {} unsafe fn enable_fp() { - let mut ecfg: usize; - asm!("csrrd {}, {}", out(reg) ecfg, const CSR_ECFG); - ecfg |= ECFG_FPE; - asm!("csrwr {}, {}", in(reg) ecfg, const CSR_ECFG); + let mut euen: usize; + asm!("csrrd {}, {}", out(reg) euen, const CSR_EUEN); + euen |= EUEN_FPEN; + asm!("csrwr {}, {}", in(reg) euen, const CSR_EUEN); } fn irqs_enabled() -> bool { diff --git a/os/src/arch/loongarch64/paging.rs b/os/src/arch/loongarch64/paging.rs index 89eff19d..0a2b5d55 100644 --- a/os/src/arch/loongarch64/paging.rs +++ b/os/src/arch/loongarch64/paging.rs @@ -10,9 +10,11 @@ const CSR_ASID: usize = 0x18; const PTE_V: usize = 1 << 0; const PTE_D: usize = 1 << 1; const PTE_PLV_USER: usize = 0b11 << 2; +const PTE_MAT_CC: usize = 0b01 << 4; const PTE_G: usize = 1 << 6; const PTE_P: usize = 1 << 7; const PTE_W: usize = 1 << 8; +const PTE_A: usize = 1 << 10; const PTE_GNX: usize = 1 << 62; const PTE_GNR: usize = 1 << 61; const PPN_SHIFT: usize = 12; @@ -39,9 +41,11 @@ impl PagingArch for LoongArchPaging { unsafe fn activate_token(token: AddressSpaceToken) { asm!( + "dbar 0", "csrwr {pgd}, {pgdl}", "csrwr $zero, {asid}", "invtlb 0x00, $zero, $zero", + "ibar 0", pgd = in(reg) token, pgdl = const CSR_PGDL, asid = const CSR_ASID, @@ -55,11 +59,18 @@ impl PagingArch for LoongArchPaging { } unsafe fn flush_tlb() { - asm!("invtlb 0x00, $zero, $zero"); + asm!( + "dbar 0", + "invtlb 0x00, $zero, $zero", + "ibar 0", + ); } fn make_pte(ppn: usize, flags: PTEFlags) -> usize { - let mut bits = (ppn << PPN_SHIFT) | PTE_P | PTE_V; + let mut bits = (ppn << PPN_SHIFT) | PTE_P | PTE_V | PTE_MAT_CC; + if flags.contains(PTEFlags::A) { + bits |= PTE_A; + } if flags.contains(PTEFlags::W) { bits |= PTE_W | PTE_D; } @@ -79,8 +90,10 @@ impl PagingArch for LoongArchPaging { } fn make_dir_entry(ppn: usize) -> usize { - // Directory (non-leaf) PTEs must NOT have GNR/GNX set; only valid + PA. - (ppn << PPN_SHIFT) | PTE_V + // LoongArch hardware walkers consume non-leaf directory entries as the + // physical address of the next-level table. Keep them as a bare next + // table pointer instead of reusing leaf-style permission bits. + ppn << PPN_SHIFT } fn pte_ppn(entry_bits: usize) -> usize { @@ -98,6 +111,9 @@ impl PagingArch for LoongArchPaging { if entry_bits & PTE_D != 0 { flags |= PTEFlags::D; } + if entry_bits & PTE_A != 0 { + flags |= PTEFlags::A; + } if entry_bits & PTE_PLV_USER != 0 { flags |= PTEFlags::U; } diff --git a/os/src/arch/loongarch64/trap.S b/os/src/arch/loongarch64/trap.S index 8978c657..3d0140e1 100644 --- a/os/src/arch/loongarch64/trap.S +++ b/os/src/arch/loongarch64/trap.S @@ -1,14 +1,15 @@ # TLB refill handler — must be 4K-aligned, placed first in trampoline section. - # Uses hardware page-table walker: PGDL → lddir × N → ldpte × 2 → tlbfill. + # Uses hardware page-table walker: PGD → lddir × N → ldpte × 2 → tlbfill. .section .text.trampoline + .equ CSR_SAVE, 0x30 .globl __tlb_refill .balign 4096 __tlb_refill: csrwr $t0, 0x8b # save t0 to TLBRSAVE - csrrd $t0, 0x1b # t0 = PGD base + csrrd $t0, 0x1b # t0 = effective PGD selected by hardware lddir $t0, $t0, 2 # walk root → mid (Dir2 → Dir1) beqz $t0, 1f - lddir $t0, $t0, 1 # walk mid → leaf (Dir1 → PT) + lddir $t0, $t0, 1 # walk mid → leaf PT beqz $t0, 1f ldpte $t0, 0 ldpte $t0, 1 @@ -26,7 +27,7 @@ __tlb_refill: .align 3 __alltraps: move $t0, $sp - move $sp, $a0 + csrrd $sp, CSR_SAVE st.d $ra, $sp, 1*8 st.d $tp, $sp, 2*8 st.d $t0, $sp, 3*8 @@ -63,11 +64,10 @@ __alltraps: st.d $t0, $sp, 32*8 st.d $t1, $sp, 33*8 - fld.d $f0, $sp, 38*8 - movgr2fr.d $f0, $zero - fst.d $f0, $sp, 38*8 - csrrd $t0, 0x2 - st.d $t0, $sp, 70*8 + # Keep the first user-trap path free of FP instructions. + # LoongArch can trap on FP use when EUEN/FPU state is not fully prepared, + # and nested traps here corrupt the user trap frame before Rust sees it. + st.d $zero, $sp, 70*8 ld.d $tp, $sp, 34*8 ld.d $t0, $sp, 35*8 @@ -78,6 +78,8 @@ __alltraps: jr $t1 __restore: + csrwr $a0, CSR_SAVE + csrrd $a0, CSR_SAVE csrwr $a1, 0x19 invtlb 0x00, $zero, $zero move $sp, $a0 diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs index b59c37c8..b151d726 100644 --- a/os/src/arch/loongarch64/trap.rs +++ b/os/src/arch/loongarch64/trap.rs @@ -2,6 +2,7 @@ use core::arch::{asm, global_asm}; +use crate::config::TRAMPOLINE; use crate::hal::traits::{InterruptControl, TrapCause, TrapContextAbi, TrapInfo, TrapMachine}; global_asm!(include_str!("trap.S")); @@ -13,8 +14,10 @@ const CSR_ECFG: usize = 0x4; const CSR_ESTAT: usize = 0x5; const CSR_ERA: usize = 0x6; const CSR_BADV: usize = 0x7; +const CSR_BADI: usize = 0x8; const CSR_EENTRY: usize = 0xc; const CSR_TLBRENTRY: usize = 0x88; +const CSR_TLBREHI: usize = 0x8e; const CSR_PWCL: usize = 0x1c; const CSR_PWCH: usize = 0x1d; const CSR_STLBPS: usize = 0x1e; @@ -29,6 +32,7 @@ const ECODE_INT: usize = 0x0; const ECODE_PIL: usize = 0x1; const ECODE_PIS: usize = 0x2; const ECODE_PIF: usize = 0x3; +const ECODE_PME: usize = 0x4; const ECODE_ADE: usize = 0x8; const ECODE_SYS: usize = 0xb; const ECODE_INE: usize = 0xd; @@ -105,9 +109,11 @@ impl InterruptControl for LoongArchInterruptControl { "csrwr {tlbr}, {tlbr_csr}", "csrwr {pwcl}, {pwcl_csr}", "csrwr {pwch}, {pwch_csr}", - // STLBPS: page size = 12 (4KB) + // STLBPS / TLBREHI.PS: page size = 12 (4KB) for software-managed + // refill entries as well as the shared TLB configuration. "ori $t0, $zero, 12", "csrwr $t0, {stlbps_csr}", + "csrwr $t0, {tlbrehi_csr}", eentry = in(reg) (__trap_from_kernel as usize), eentry_csr = const CSR_EENTRY, tlbr = in(reg) (__tlb_refill as usize), @@ -117,15 +123,20 @@ impl InterruptControl for LoongArchInterruptControl { pwch = in(reg) PWCH, pwch_csr = const CSR_PWCH, stlbps_csr = const CSR_STLBPS, + tlbrehi_csr = const CSR_TLBREHI, out("$t0") _, ); } unsafe fn set_user_trap_entry() { - extern "C" { fn __alltraps(); } + extern "C" { + fn __alltraps(); + fn strampoline(); + } + let trap_entry = __alltraps as usize - strampoline as usize + TRAMPOLINE; asm!( "csrwr {entry}, {eentry}", - entry = in(reg) (__alltraps as usize), + entry = in(reg) trap_entry, eentry = const CSR_EENTRY, ); } @@ -138,7 +149,7 @@ impl TrapMachine for LoongArchTrapMachine { let ecode = (estat >> 16) & 0x3f; let cause = match ecode { ECODE_SYS => TrapCause::UserSyscall, - ECODE_PIS => TrapCause::StorePageFault, + ECODE_PIS | ECODE_PME => TrapCause::StorePageFault, ECODE_PIL => TrapCause::LoadPageFault, ECODE_PIF => TrapCause::InstructionPageFault, ECODE_INE => TrapCause::IllegalInstruction, @@ -161,11 +172,15 @@ impl TrapMachine for LoongArchTrapMachine { } unsafe fn return_to_user(trap_cx_user_va: usize, user_token: usize) -> ! { - extern "C" { fn __restore(); } + extern "C" { + fn __restore(); + fn strampoline(); + } + let restore_va = __restore as usize - strampoline as usize + TRAMPOLINE; asm!( "ibar 0", "jirl $zero, {restore}, 0", - restore = in(reg) (__restore as usize), + restore = in(reg) restore_va, in("$a0") trap_cx_user_va, in("$a1") user_token, options(noreturn) @@ -193,7 +208,9 @@ impl TrapContextAbi for LoongArchTrapContextAbi { ) -> Self::Frame { let mut frame = LoongArchTrapContextFrame { r: [0; 32], - prmd: 0, + // PPLV=3 (user) and PIE=1 so `ertn` returns to PLV3 with + // interrupts restored according to the saved user context. + prmd: 0b0111, era: entry, kernel_hartid: 0, kernel_pgdl: kernel_token, @@ -313,6 +330,13 @@ fn read_badv() -> usize { value } +#[inline] +fn read_badi() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_BADI) }; + value +} + #[inline] unsafe fn update_ecfg(mask: usize, enable: bool) { let mut ecfg: usize; diff --git a/os/src/boards/loongarch_virt.rs b/os/src/boards/loongarch_virt.rs index 5e362501..4f14170e 100644 --- a/os/src/boards/loongarch_virt.rs +++ b/os/src/boards/loongarch_virt.rs @@ -18,6 +18,14 @@ pub const MMIO: &[(usize, usize)] = &[ pub const VIRT_UART: usize = IO_ADDR_OFFSET | 0x1fe0_01e0; /// RTC-compatible MMIO virtual address (uncached DMW0 window). pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x1fe0_01f8; +/// VirtIO MMIO window base address. +pub const VIRTIO_MMIO_BASE: usize = IO_ADDR_OFFSET | 0x1fe2_0000; +/// Size of each VirtIO MMIO slot. +pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; +/// Number of VirtIO MMIO slots exposed by the board. +pub const VIRTIO_MMIO_SLOTS: usize = 8; +/// First IRQ line assigned to VirtIO MMIO devices. +pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; /// Alias for compatibility. pub const VIRT_UART_EARLY: usize = VIRT_UART; diff --git a/os/src/boards/qemu.rs b/os/src/boards/qemu.rs index 0c726065..58a9af35 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/boards/qemu.rs @@ -16,6 +16,14 @@ pub const MMIO: &[(usize, usize)] = &[ pub const VIRT_UART: usize = 0x1000_0000; /// QEMU virt 机型上的 Goldfish RTC MMIO 基址。 pub const VIRT_RTC: usize = 0x0010_1000; +/// VirtIO MMIO window base address. +pub const VIRTIO_MMIO_BASE: usize = 0x1000_1000; +/// Size of each VirtIO MMIO slot. +pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; +/// Number of VirtIO MMIO slots exposed by the board. +pub const VIRTIO_MMIO_SLOTS: usize = 8; +/// First IRQ line assigned to VirtIO MMIO devices. +pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; /// Block device implementation for QEMU virt. pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; diff --git a/os/src/config.rs b/os/src/config.rs index 6cb5fe20..1157f5fe 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -33,7 +33,12 @@ pub const USER_STACK_BASE: usize = 0x0800_0000; /// placed between stack and mmap region to avoid conflicts pub const INTERP_BASE: usize = 0x4000_0000; /// the virtual addr of trapoline +#[cfg(not(target_arch = "loongarch64"))] pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; +/// LoongArch64 keeps trap trampoline and per-task kernel stacks in the low half +/// so the current PGDL-only address-space activation can cover them. +#[cfg(target_arch = "loongarch64")] +pub const TRAMPOLINE: usize = 0x0000_003f_ffff_f000; /// the virtual addr of trap context pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; /// 用户态 signal trampoline 页起始地址。 diff --git a/os/src/console.rs b/os/src/console.rs index f4d886f2..0a4ec7de 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,5 +1,5 @@ //! Kernel console output helpers. -use crate::{drivers::chardev::{CharDevice, UART}}; +use crate::drivers::chardev::{uart_ready, CharDevice, UART}; use core::fmt::{self, Write}; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, Ordering}; @@ -20,6 +20,17 @@ impl Write for Stdout { } } +#[cfg(target_arch = "loongarch64")] +struct EarlyStdout; + +#[cfg(target_arch = "loongarch64")] +impl Write for EarlyStdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + crate::early_puts(s); + Ok(()) + } +} + /// 控制台输出期间的临界区守卫。 /// /// 它同时负责两件事: @@ -56,6 +67,11 @@ impl Drop for ConsoleGuard { /// print to the host console using the format string and arguments. pub fn print(args: fmt::Arguments) { let _guard = ConsoleGuard::lock(); + #[cfg(target_arch = "loongarch64")] + if !uart_ready() { + EarlyStdout.write_fmt(args).unwrap(); + return; + } Stdout.write_fmt(args).unwrap(); } diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index 612d0a7f..1eaaeef1 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -12,7 +12,11 @@ use core::convert::TryFrom; use fs::BlockDevice; use lazy_static::*; use core::ptr::NonNull; -use virtio_drivers::transport::{DeviceType, mmio::{MmioTransport, VirtIOHeader}}; +use virtio_drivers::transport::{ + DeviceType, SomeTransport, + mmio::{MmioTransport, VirtIOHeader}, +}; +use crate::board::{VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE}; fn virtio_blk_name(idx: usize) -> String { alloc::format!("vd{}", (b'a' + idx as u8) as char) @@ -61,11 +65,6 @@ lazy_static! { /// /// Must be called **before** `fs::init_rootfs` and `fs::init_dev`. pub fn probe_block_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - let mut map = BLOCK_DEVICES.lock(); let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); let mut idx = 0usize; @@ -88,7 +87,7 @@ pub fn probe_block_devices() { Err(_) => continue, }; - if let Some(dev) = VirtIOBlock::try_new(transport) { + if let Some(dev) = VirtIOBlock::try_new(SomeTransport::from(transport)) { let dev = Arc::new(dev); // let name = alloc::format!("vd{}", (b'a' + idx as u8) as char); let name = virtio_blk_name(idx); diff --git a/os/src/drivers/block/virtio_blk.rs b/os/src/drivers/block/virtio_blk.rs index deb76224..0519cb52 100644 --- a/os/src/drivers/block/virtio_blk.rs +++ b/os/src/drivers/block/virtio_blk.rs @@ -4,14 +4,14 @@ use crate::task::{current_task, WaitQueueKeyed, WaitReason}; use core::hint::spin_loop; use virtio_drivers::{ device::blk::{BlkReq, BlkResp, RespStatus, VirtIOBlk}, - transport::mmio::MmioTransport, + transport::SomeTransport, }; use crate::drivers::virtio::VirtioHal; /// VirtIOBlock device driver strcuture for virtio_blk device pub struct VirtIOBlock { - inner: SpinNoIrqLock>>, + inner: SpinNoIrqLock>>, wait_queue: WaitQueueKeyed, } @@ -135,8 +135,8 @@ impl BlockDevice for VirtIOBlock { } impl VirtIOBlock { - /// Build a wrapper from an initialized MMIO transport. - pub fn try_new(transport: MmioTransport<'static>) -> Option { + /// Build a wrapper from an initialized VirtIO transport. + pub fn try_new(transport: SomeTransport<'static>) -> Option { VirtIOBlk::::new(transport).ok().map(|blk| Self { inner: SpinNoIrqLock::new(blk), wait_queue: WaitQueueKeyed::new(), diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 9a64380a..36efc4ec 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -3,16 +3,11 @@ pub mod block; pub mod chardev; pub mod net; +pub mod pci; pub mod plic; pub mod rtc; pub mod virtio; -use core::ptr::NonNull; - -use alloc::{string::String, sync::Arc}; pub use block::BLOCK_DEVICE; -use virtio_drivers::transport::{DeviceType, mmio::{MmioTransport, VirtIOHeader}}; - -use crate::drivers::{block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, net::VirtIONetDevice}; fn virtio_blk_name(idx: usize) -> String { alloc::format!("vd{}", (b'a' + idx as u8) as char) @@ -23,102 +18,13 @@ pub fn init() { chardev::init(); rtc::init(); #[cfg(target_arch = "riscv64")] - plic::init(); - probe_virtio_devices(); -} - - -#[inline] -fn mmio_slot_device_type(header: NonNull) -> Option { - // VirtIO MMIO register layout: magic(0x00), version(0x04), device_id(0x08). - const MAGIC_VALUE: u32 = 0x7472_6976; - const LEGACY_VERSION: u32 = 1; - const MODERN_VERSION: u32 = 2; - - let base = header.as_ptr() as *const u32; - // SAFETY: caller passes an MMIO header address on the virt bus. - let magic = unsafe { core::ptr::read_volatile(base) }; - if magic != MAGIC_VALUE { - return None; - } - // SAFETY: MMIO header word reads are volatile. - let version = unsafe { core::ptr::read_volatile(base.add(1)) }; - if version != LEGACY_VERSION && version != MODERN_VERSION { - return None; + { + plic::init(); + block::probe_block_devices(); + net::probe_net_devices(); } - // SAFETY: MMIO header word reads are volatile. - let device_id = unsafe { core::ptr::read_volatile(base.add(2)) }; - DeviceType::try_from(device_id).ok() -} - -/// Probe all VirtIO MMIO slots once and register devices found. -pub fn probe_virtio_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - - let mut map = BLOCK_DEVICES.lock(); - let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); - let mut idx = 0usize; - - for slot in 0..VIRTIO_MMIO_SLOTS { - let addr = VIRTIO_MMIO_BASE + slot * VIRTIO_MMIO_STRIDE; - - let Some(header) = NonNull::new(addr as *mut VirtIOHeader) else { - continue; - }; - - match mmio_slot_device_type(header) { - Some(DeviceType::Block) => { - let transport = match unsafe { MmioTransport::new(header, VIRTIO_MMIO_STRIDE) } { - Ok(t) => t, - Err(_) => continue, - }; - - if let Some(dev) = VirtIOBlock::try_new(transport) { - let dev = Arc::new(dev); - let name: String = virtio_blk_name(idx); - debug!("[kernel] block device {} idx {} at {:#x}", name, idx, addr); - map.insert(name, dev.clone()); - irq_map.insert(VIRTIO_MMIO_IRQ_BASE + slot as u32, dev); - idx += 1; - } - } - Some(DeviceType::Network) => { - let transport = match unsafe { MmioTransport::new(header, VIRTIO_MMIO_STRIDE) } { - Ok(t) => t, - Err(_) => continue, - }; - - let irq = VIRTIO_MMIO_IRQ_BASE + slot as u32; - if let Some(dev) = VirtIONetDevice::try_new(transport, irq) { - let mac = dev.mac_address(); - info!( - "[kernel] virtio-net found at slot {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - slot, - irq, - mac[0], - mac[1], - mac[2], - mac[3], - mac[4], - mac[5] - ); - // Register into net module via its public helper. - net::register_device(Arc::new(dev)); - // For now we stop at the first net device like the old behaviour. - // Continue scanning for block devices though. - } - } - Some(kind) => { - debug!("[kernel] VirtIO slot {} is {:?}, skipping", slot, kind); - } - None => {} - } - } - - if idx == 0 { - panic!("[kernel] no VirtIO block devices found"); + #[cfg(target_arch = "loongarch64")] + { + pci::probe_virtio_pci_devices(); } } diff --git a/os/src/drivers/net/mod.rs b/os/src/drivers/net/mod.rs index 724fca2d..f2a3670e 100644 --- a/os/src/drivers/net/mod.rs +++ b/os/src/drivers/net/mod.rs @@ -7,10 +7,11 @@ use core::convert::TryFrom; use core::ptr::NonNull; use lazy_static::lazy_static; use virtio_drivers::transport::{ - DeviceType, + DeviceType, SomeTransport, mmio::{MmioTransport, VirtIOHeader}, }; +use crate::board::{VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE}; use crate::sync::SpinNoIrqLock; pub use virtio_net::VirtIONetDevice; @@ -50,11 +51,6 @@ pub fn register_device(dev: Arc) { /// Probe all VirtIO MMIO slots and register the first network device. pub fn probe_net_devices() { - const VIRTIO_MMIO_BASE: usize = 0x1000_1000; - const VIRTIO_MMIO_STRIDE: usize = 0x1000; - const VIRTIO_MMIO_SLOTS: usize = 8; - const VIRTIO_MMIO_IRQ_BASE: u32 = 1; - for slot in 0..VIRTIO_MMIO_SLOTS { let addr = VIRTIO_MMIO_BASE + slot * VIRTIO_MMIO_STRIDE; let Some(header) = NonNull::new(addr as *mut VirtIOHeader) else { @@ -70,7 +66,7 @@ pub fn probe_net_devices() { }; let irq = VIRTIO_MMIO_IRQ_BASE + slot as u32; - if let Some(dev) = VirtIONetDevice::try_new(transport, irq) { + if let Some(dev) = VirtIONetDevice::try_new(SomeTransport::from(transport), irq) { let mac = dev.mac_address(); info!( "[kernel] virtio-net found at slot {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", diff --git a/os/src/drivers/net/virtio_net.rs b/os/src/drivers/net/virtio_net.rs index 383e85ee..98a8eb36 100644 --- a/os/src/drivers/net/virtio_net.rs +++ b/os/src/drivers/net/virtio_net.rs @@ -7,7 +7,7 @@ use core::hint::spin_loop; use virtio_drivers::{ device::net::VirtIONetRaw, - transport::mmio::MmioTransport, + transport::SomeTransport, Error, }; @@ -28,7 +28,7 @@ const QUEUE_SIZE: usize = 16; pub struct VirtIONetDevice { irq: u32, mac: [u8; 6], - inner: SpinNoIrqLock, QUEUE_SIZE>>, + inner: SpinNoIrqLock, QUEUE_SIZE>>, tx_wait_queue: WaitQueueKeyed, /// TX buffers that are in-flight via non-blocking `try_send`. tx_slots: SpinNoIrqLock<[Option>; QUEUE_SIZE]>, @@ -39,7 +39,7 @@ pub struct VirtIONetDevice { impl VirtIONetDevice { /// Try to create one device instance from an already initialized transport. - pub fn try_new(transport: MmioTransport<'static>, irq: u32) -> Option { + pub fn try_new(transport: SomeTransport<'static>, irq: u32) -> Option { let mut inner = VirtIONetRaw::::new(transport).ok()?; let mac = inner.mac_address(); diff --git a/os/src/drivers/pci.rs b/os/src/drivers/pci.rs new file mode 100644 index 00000000..fcf4f9d2 --- /dev/null +++ b/os/src/drivers/pci.rs @@ -0,0 +1,215 @@ +//! LoongArch64 PCI/ECAM probing for VirtIO PCI devices. + +#[cfg(target_arch = "loongarch64")] +use alloc::{string::String, sync::Arc}; +#[cfg(target_arch = "loongarch64")] +use core::convert::TryFrom; +#[cfg(target_arch = "loongarch64")] +use virtio_drivers::transport::{ + pci::{ + bus::{ + BarInfo, Cam, Command, DeviceFunction, DeviceFunctionInfo, HeaderType, MemoryBarType, + MmioCam, PciRoot, + }, + virtio_device_type, PciTransport, + }, + DeviceType, SomeTransport, Transport, +}; + +#[cfg(target_arch = "loongarch64")] +use crate::board::IO_ADDR_OFFSET; +#[cfg(target_arch = "loongarch64")] +use crate::drivers::{ + block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, + net::{self, VirtIONetDevice}, +}; + +#[cfg(target_arch = "loongarch64")] +const PCI_ECAM_BASE: usize = 0x2000_0000; +#[cfg(target_arch = "loongarch64")] +const PCI_ECAM_SIZE: usize = 0x1000_0000; +#[cfg(target_arch = "loongarch64")] +const PCI_RANGE_BASE: usize = 0x4000_0000; +#[cfg(target_arch = "loongarch64")] +const PCI_RANGE_SIZE: usize = 0x4000_0000; +#[cfg(target_arch = "loongarch64")] +const PCI_BUS_END: u8 = 0x7f; +#[cfg(target_arch = "loongarch64")] +const LEGACY_VIRTIO_NET_IRQ: u32 = 1; + +#[cfg(target_arch = "loongarch64")] +/// Probe the LA64 PCIe ECAM bus and register VirtIO PCI devices. +pub fn probe_virtio_pci_devices() { + let ecam_vaddr = PCI_ECAM_BASE | IO_ADDR_OFFSET; + let ecam_end = ecam_vaddr + PCI_ECAM_SIZE; + if ecam_end < ecam_vaddr { + panic!("PCI ECAM window overflow"); + } + + let mut root = unsafe { PciRoot::new(MmioCam::new(ecam_vaddr as *mut u8, Cam::Ecam)) }; + let mut allocator = PciRangeAllocator::new(PCI_RANGE_BASE as u64, PCI_RANGE_SIZE as u64); + let mut map = BLOCK_DEVICES.lock(); + let mut irq_map = BLOCK_DEVICES_BY_IRQ.lock(); + let mut block_idx = 0usize; + + for bus in 0..=PCI_BUS_END { + for (bdf, dev_info) in root.enumerate_bus(bus) { + if dev_info.header_type != HeaderType::Standard { + continue; + } + + if let Err(err) = configure_pci_device(&mut root, bdf, &mut allocator) { + warn!( + "[pci] failed to configure device at {} ({}:{:#06x}): {:?}", + bdf, dev_info.vendor_id, dev_info.device_id, err + ); + continue; + } + + let Some(mut transport) = probe_virtio_pci_device(&mut root, bdf, &dev_info) else { + continue; + }; + + match transport.device_type() { + DeviceType::Block => { + let Some(dev) = VirtIOBlock::try_new(SomeTransport::from(transport)) else { + warn!("[pci] failed to create virtio-blk for {}", bdf); + continue; + }; + let dev = Arc::new(dev); + let name: String = if block_idx > 0 { + alloc::format!("vda{}", block_idx + 1) + } else { + "vda".into() + }; + info!("[pci] virtio-blk {} at {}", name, bdf); + map.insert(name, dev.clone()); + let _ = &mut irq_map; + block_idx += 1; + } + DeviceType::Network => { + let Some(dev) = VirtIONetDevice::try_new( + SomeTransport::from(transport), + LEGACY_VIRTIO_NET_IRQ, + ) else { + warn!("[pci] failed to create virtio-net for {}", bdf); + continue; + }; + let mac = dev.mac_address(); + info!( + "[pci] virtio-net at {} irq {} mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + bdf, + LEGACY_VIRTIO_NET_IRQ, + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + net::register_device(Arc::new(dev)); + } + other => { + warn!("[pci] ignore unsupported virtio device {:?} at {}", other, bdf); + } + } + } + } + + if block_idx == 0 { + panic!("[kernel] no VirtIO block devices found"); + } +} + +#[cfg(not(target_arch = "loongarch64"))] +/// No-op stub so non-LA64 builds do not pull in PCI probing logic. +pub fn probe_virtio_pci_devices() {} + +#[cfg(target_arch = "loongarch64")] +#[derive(Debug)] +struct PciRangeAllocator { + end: u64, + current: u64, +} + +#[cfg(target_arch = "loongarch64")] +impl PciRangeAllocator { + const fn new(base: u64, size: u64) -> Self { + Self { + end: base + size, + current: base, + } + } + + fn alloc(&mut self, size: u64) -> Option { + if !size.is_power_of_two() { + return None; + } + let ret = align_up(self.current, size); + if ret + size > self.end { + return None; + } + self.current = ret + size; + Some(ret) + } +} + +#[cfg(target_arch = "loongarch64")] +const fn align_up(addr: u64, align: u64) -> u64 { + (addr + align - 1) & !(align - 1) +} + +#[cfg(target_arch = "loongarch64")] +fn configure_pci_device( + root: &mut PciRoot>, + bdf: DeviceFunction, + allocator: &mut PciRangeAllocator, +) -> Result<(), ()> { + let mut bar = 0u8; + while bar < 6 { + let info = root.bar_info(bdf, bar).map_err(|_| ())?; + if let Some(BarInfo::Memory { + address_type, + address, + size, + .. + }) = info.clone() + { + if size > 0 && address == 0 { + let Some(new_addr) = allocator.alloc(size) else { + return Err(()); + }; + match address_type { + MemoryBarType::Width32 => root.set_bar_32(bdf, bar, new_addr as u32), + MemoryBarType::Width64 => root.set_bar_64(bdf, bar, new_addr), + MemoryBarType::Below1MiB => root.set_bar_32(bdf, bar, new_addr as u32), + } + } + } + let takes_two = info.as_ref().is_some_and(BarInfo::takes_two_entries); + bar += if takes_two { 2 } else { 1 }; + } + + let (_status, cmd) = root.get_status_command(bdf); + root.set_command( + bdf, + cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + Ok(()) +} + +#[cfg(target_arch = "loongarch64")] +fn probe_virtio_pci_device( + root: &mut PciRoot>, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, +) -> Option { + let dev_type = virtio_device_type(dev_info)?; + match (dev_type, dev_info.device_id) { + (DeviceType::Network, 0x1000) | (DeviceType::Network, 0x1040) => {} + (DeviceType::Block, 0x1001) | (DeviceType::Block, 0x1041) => {} + _ => return None, + } + info!("[pci] found virtio device {:?} at {}", dev_type, bdf); + PciTransport::new::>(root, bdf).ok() +} diff --git a/os/src/drivers/rtc.rs b/os/src/drivers/rtc.rs index 5b689a24..1c2697c4 100644 --- a/os/src/drivers/rtc.rs +++ b/os/src/drivers/rtc.rs @@ -132,6 +132,15 @@ lazy_static! { /// 标记 RTC 是否已完成初始化。 static RTC_READY: AtomicBool = AtomicBool::new(false); +#[cfg(target_arch = "loongarch64")] +/// 初始化全局 RTC 驱动。 +pub fn init() { + // QEMU loongarch64 virt does not currently expose the Goldfish RTC MMIO + // block at the RISC-V-compatible address we use elsewhere. + warn!("rtc init skipped on loongarch64 virt"); +} + +#[cfg(not(target_arch = "loongarch64"))] /// 初始化全局 RTC 驱动。 pub fn init() { let rtc = RTC.lock(); diff --git a/os/src/drivers/virtio/mod.rs b/os/src/drivers/virtio/mod.rs index d1cf6093..2a721016 100644 --- a/os/src/drivers/virtio/mod.rs +++ b/os/src/drivers/virtio/mod.rs @@ -10,8 +10,8 @@ use virtio_drivers::{BufferDirection, Hal, PhysAddr as VirtioPhysAddr}; use crate::config::PAGE_SIZE; use crate::mm::{ - frame_alloc_contiguous, frame_dealloc_range, kernel_token, ContiguousFrames, PageTable, - PhysAddr as KernelPhysAddr, PhysPageNum, VirtAddr, + frame_alloc_contiguous, frame_dealloc_range, kernel_token, phys_to_virt, ContiguousFrames, + PageTable, PhysAddr as KernelPhysAddr, PhysPageNum, VirtAddr, }; use crate::sync::SpinNoIrqLock; @@ -35,6 +35,18 @@ struct BounceMapping { #[inline] fn translate_kernel_va(va: usize) -> usize { + #[cfg(target_arch = "loongarch64")] + { + use crate::board::{IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET}; + + if va & KERNEL_ADDR_OFFSET == KERNEL_ADDR_OFFSET { + return va & !KERNEL_ADDR_OFFSET; + } + if va & IO_ADDR_OFFSET == IO_ADDR_OFFSET { + return va & !IO_ADDR_OFFSET; + } + } + PageTable::from_token(kernel_token()) .translate_va(VirtAddr::from(va)) .expect("virtio: translate_va failed") @@ -115,7 +127,8 @@ unsafe impl Hal for VirtioHal { let pa: KernelPhysAddr = ppn_base.into(); let paddr = pa.0 as VirtioPhysAddr; - let vaddr = NonNull::new(pa.0 as *mut u8).expect("virtio dma_alloc: null vaddr"); + let vaddr = NonNull::new(phys_to_virt(pa.0) as *mut u8) + .expect("virtio dma_alloc: null vaddr"); QUEUE_FRAMES.lock().push(frames); (paddr, vaddr) } @@ -142,7 +155,16 @@ unsafe impl Hal for VirtioHal { } unsafe fn mmio_phys_to_virt(paddr: VirtioPhysAddr, _size: usize) -> NonNull { - NonNull::new(paddr as usize as *mut u8).expect("virtio mmio_phys_to_virt: null") + #[cfg(target_arch = "loongarch64")] + { + use crate::board::IO_ADDR_OFFSET; + return NonNull::new((paddr as usize | IO_ADDR_OFFSET) as *mut u8) + .expect("virtio mmio_phys_to_virt: null"); + } + #[cfg(not(target_arch = "loongarch64"))] + { + NonNull::new(paddr as usize as *mut u8).expect("virtio mmio_phys_to_virt: null") + } } unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> VirtioPhysAddr { @@ -162,7 +184,7 @@ unsafe impl Hal for VirtioHal { let base_ppn = frames.start_ppn(); let base_pa: KernelPhysAddr = base_ppn.into(); let base_pa_usize = base_pa.0; - let bounced_vaddr = base_pa_usize + page_offset; + let bounced_vaddr = phys_to_virt(base_pa_usize) + page_offset; let shared_paddr = (base_pa_usize + page_offset) as VirtioPhysAddr; if matches!( diff --git a/os/src/fs/page_cache.rs b/os/src/fs/page_cache.rs index bb3f63e1..cc9e866c 100644 --- a/os/src/fs/page_cache.rs +++ b/os/src/fs/page_cache.rs @@ -1122,7 +1122,7 @@ fn dynamic_watermarks() -> (usize, usize) { /// 初始化时使用的保守水位估算。 fn default_watermarks() -> (usize, usize) { - let managed_start = PhysAddr::from(ekernel as usize).ceil(); + let managed_start = PhysAddr::from(crate::mm::virt_to_phys(ekernel as usize)).ceil(); let managed_end = PhysAddr::from(MEMORY_END).floor(); let total_pages = max(1, managed_end.0.saturating_sub(managed_start.0)); let high_watermark = max(128, total_pages / 4); diff --git a/os/src/main.rs b/os/src/main.rs index 1d257865..e7de960c 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -364,23 +364,9 @@ pub fn rust_main(hart_id: usize) -> ! { unsafe { riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); } - #[cfg(target_arch = "loongarch64")] - { - // LoongArch64 bring-up currently follows a single-bootstrap-hart path. - if !try_claim_bootstrap_hart(hart_id) { - secondary_hart_main(hart_id); - } + if !try_claim_bootstrap_hart(hart_id) { + secondary_hart_main(hart_id); + } else { first_hart_main(hart_id); } - #[cfg(target_arch = "riscv64")] - { - unsafe { - riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); - } - if !try_claim_bootstrap_hart(hart_id) { - secondary_hart_main(hart_id); - } else { - first_hart_main(hart_id); - } - } } diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 2335453e..14269ef1 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -21,6 +21,18 @@ pub fn phys_to_virt(pa: usize) -> usize { #[cfg(not(target_arch = "loongarch64"))] pa } + +/// Convert one direct-mapped kernel virtual address back to a physical address. +#[inline(always)] +pub fn virt_to_phys(va: usize) -> usize { + #[cfg(target_arch = "loongarch64")] + { + use crate::board::KERNEL_ADDR_OFFSET; + va & !KERNEL_ADDR_OFFSET + } + #[cfg(not(target_arch = "loongarch64"))] + va +} /// Exclusive end of the canonical low-half user virtual-address range. pub const USER_SPACE_END: usize = { let _ = crate::hal::page_table_levels(); diff --git a/os/src/mm/frame_allocator.rs b/os/src/mm/frame_allocator.rs index 9963ddbf..e062dbc1 100644 --- a/os/src/mm/frame_allocator.rs +++ b/os/src/mm/frame_allocator.rs @@ -1,6 +1,6 @@ //! Physical page frame allocator -use super::{PhysAddr, PhysPageNum}; +use super::{virt_to_phys, PhysAddr, PhysPageNum}; use crate::fs::PAGE_CACHE_MANAGER; use crate::mm::heap_allocator::KERNEL_HEAP_BYTES; use crate::{config::MEMORY_END, sync::SpinNoIrqLock}; @@ -279,7 +279,7 @@ pub fn init_frame_allocator() { fn ekernel(); } FRAME_ALLOCATOR.lock().init( - PhysAddr::from(ekernel as usize).ceil(), + PhysAddr::from(virt_to_phys(ekernel as usize)).ceil(), PhysAddr::from(MEMORY_END).floor(), ); } diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index d45a0061..03940c39 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -215,15 +215,20 @@ pub fn init_heap() { } pub fn init_heap_virtual_window() { - KERNEL_HEAP_VIRTUAL_READY.store(true, Ordering::Release); #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] grow start\r\n"); + { + // LA64 bring-up still faults on the first access into the low-half + // heap window even after the leaf PTE is installed and TLB state is + // refreshed. Keep using the already-working DMW-backed bootstrap heap + // path for now so the kernel can continue booting on LoongArch. + crate::early_puts("[heap] virtual window disabled on loongarch64\r\n"); + return; + } + KERNEL_HEAP_VIRTUAL_READY.store(true, Ordering::Release); assert!( HEAP_ALLOCATOR.grow(KERNEL_HEAP_GROW_SIZE), "failed to initialize virtual kernel heap" ); - #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] grow done\r\n"); } /// Map a single heap VA page. Called from trap_from_kernel on LoongArch. @@ -231,6 +236,23 @@ pub fn map_one_heap_page(va: usize) -> bool { map_heap_pages(va, 1) } +#[cfg(target_arch = "loongarch64")] +fn early_put_hex(label: &str, value: usize) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut buf = [0u8; 2 + 16 + 2]; + buf[0] = b'0'; + buf[1] = b'x'; + for (idx, slot) in buf[2..18].iter_mut().enumerate() { + let shift = (15 - idx) * 4; + *slot = HEX[(value >> shift) & 0xf]; + } + buf[18] = b'\r'; + buf[19] = b'\n'; + crate::early_puts(label); + // SAFETY: ASCII hex buffer is always valid UTF-8. + crate::early_puts(core::str::from_utf8(&buf).unwrap()); +} + fn layout_required_bytes(layout: Layout) -> Option { let min_size = layout .size() @@ -366,12 +388,29 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { rollback_heap_pages(subtree_root_ppn, start_va, page); return false; }; - *entry = PageTableEntry::new(frame.ppn, PTEFlags::R | PTEFlags::W | PTEFlags::V); + *entry = PageTableEntry::new( + frame.ppn, + PTEFlags::R | PTEFlags::W | PTEFlags::V | PTEFlags::A | PTEFlags::D, + ); core::mem::forget(frame); } - unsafe { crate::hal::flush_tlb() }; #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] pages mapped, flush done\r\n"); + if pages > 0 { + let vpn = VirtAddr::from(start_va).floor(); + let root_idx = crate::hal::vpn_index(vpn.0, 0); + let mid_idx = crate::hal::vpn_index(vpn.0, 1); + let leaf_idx = crate::hal::vpn_index(vpn.0, 2); + let root = PhysPageNum(crate::hal::root_ppn_from_token(crate::mm::kernel_token())); + let root_pte = root.get_pte_array()[root_idx].bits; + let mid_ppn = PhysPageNum(crate::hal::pte_ppn(root_pte)); + let mid_pte = mid_ppn.get_pte_array()[mid_idx].bits; + let leaf_ppn = PhysPageNum(crate::hal::pte_ppn(mid_pte)); + let leaf_pte = leaf_ppn.get_pte_array()[leaf_idx].bits; + early_put_hex("[heap] root_pte=", root_pte); + early_put_hex("[heap] mid_pte=", mid_pte); + early_put_hex("[heap] leaf_pte=", leaf_pte); + } + unsafe { crate::hal::flush_tlb() }; true } diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 2380979f..d1d6cabd 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -468,6 +468,16 @@ impl MemorySet { if map_perm.contains(MapPermission::U) { flags.insert(PTEFlags::U); } + #[cfg(target_arch = "loongarch64")] + { + // LoongArch leaf PTEs need the accessed bit set up-front, and + // writable mappings also need the dirty bit, otherwise the CPU may + // keep reporting page-invalid/store faults even after TLB refill. + flags.insert(PTEFlags::A); + if map_perm.contains(MapPermission::W) { + flags.insert(PTEFlags::D); + } + } flags } @@ -799,13 +809,15 @@ impl MemorySet { } /// Mention that trampoline is not collected by areas. fn map_trampoline(&mut self) { - // On LoongArch the trampoline lives in the DMW1 kernel window and is - // accessible without a page-table entry. #[cfg(not(target_arch = "loongarch64"))] + let trampoline_pa = strampoline as usize; + #[cfg(target_arch = "loongarch64")] + let trampoline_pa = crate::mm::virt_to_phys(strampoline as usize); + self.page_table .map( VirtAddr::from(TRAMPOLINE).into(), - PhysAddr::from(strampoline as usize).into(), + PhysAddr::from(trampoline_pa).into(), PTEFlags::R | PTEFlags::X, ) .expect("failed to map trampoline"); @@ -815,8 +827,9 @@ impl MemorySet { let mut memory_set = Self::new_bare(); // map trampoline memory_set.map_trampoline(); - // On LoongArch, kernel sections / physical memory / MMIO are all covered by - // DMW windows and do not need software page-table entries. + // On LoongArch, kernel sections / physical memory / MMIO are covered by + // DMW windows, but trap trampoline and task kernel stacks live in the + // low-half page-table space and are mapped explicitly. #[cfg(not(target_arch = "loongarch64"))] { // map kernel sections @@ -2253,11 +2266,15 @@ impl Vma { } /// 为某个线程生成 Trap 上下文页对应的区域描述。 pub fn new_trap_context(start_va: VirtAddr, end_va: VirtAddr, tid: usize) -> Self { + #[cfg(target_arch = "loongarch64")] + let map_perm = MapPermission::R | MapPermission::W | MapPermission::U; + #[cfg(not(target_arch = "loongarch64"))] + let map_perm = MapPermission::R | MapPermission::W; Self::new( start_va, end_va, MapType::Framed, - MapPermission::R | MapPermission::W, + map_perm, VmaKind::TrapContext { tid }, ) } diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index e75ec1a8..fc57bdfd 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -43,7 +43,7 @@ pub enum PageFaultHandled { NotHandled, } -pub use address::{phys_to_virt, PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; +pub use address::{phys_to_virt, virt_to_phys, PhysAddr, PhysPageNum, StepByOne, USER_SPACE_END, VirtAddr, VirtPageNum}; pub use frame_allocator::{ frame_alloc, frame_alloc_contiguous, frame_allocator_stats, frame_dealloc, frame_dealloc_range, ContiguousFrames, FrameAllocatorStats, FrameTracker, diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index b0fd4e15..b4dd87d6 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -39,7 +39,17 @@ impl PageTableEntry { } /// The page pointered by page table entry is valid? pub fn is_valid(&self) -> bool { - (self.flags() & PTEFlags::V) != PTEFlags::empty() + #[cfg(target_arch = "loongarch64")] + { + // LoongArch non-leaf directory entries are encoded as bare + // next-level-table physical addresses, so software page-table walks + // must treat any non-zero entry as present. + self.bits != 0 + } + #[cfg(not(target_arch = "loongarch64"))] + { + (self.flags() & PTEFlags::V) != PTEFlags::empty() + } } /// The page pointered by page table entry is readable? pub fn readable(&self) -> bool { diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index dc38e634..d1f3d9e0 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -14,8 +14,12 @@ mod context; use crate::config::PAGE_SIZE; +#[cfg(target_arch = "loongarch64")] +use crate::config::{KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE}; use crate::hal::hartid; use crate::mm::{handle_ipi, MmError, PageFaultAccess, PageFaultHandled}; +#[cfg(target_arch = "loongarch64")] +use crate::mm::map_one_heap_page; use crate::signal::{SignalBit, handle_signals}; use crate::syscall::{syscall, syscall_supports_sa_restart}; use crate::sched::{on_timer_tick, request_current_task_resched, schedule_if_needed, ReschedReason}; @@ -89,6 +93,40 @@ fn log_lazy_fault_oom(path: &str, access: &str, fault_addr: usize) { ); } +#[cfg(target_arch = "loongarch64")] +fn early_put_hex(label: &str, value: usize) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut buf = [0u8; 2 + 16 + 2]; + buf[0] = b'0'; + buf[1] = b'x'; + for (idx, slot) in buf[2..18].iter_mut().enumerate() { + let shift = (15 - idx) * 4; + *slot = HEX[(value >> shift) & 0xf]; + } + buf[18] = b'\r'; + buf[19] = b'\n'; + crate::early_puts(label); + crate::early_puts(core::str::from_utf8(&buf).unwrap()); +} + +#[cfg(target_arch = "loongarch64")] +fn dump_kernel_trap_state() { + let crmd: usize; + let estat: usize; + let era: usize; + let badv: usize; + unsafe { + core::arch::asm!("csrrd {}, {}", out(reg) crmd, const 0x0); + core::arch::asm!("csrrd {}, {}", out(reg) estat, const 0x5); + core::arch::asm!("csrrd {}, {}", out(reg) era, const 0x6); + core::arch::asm!("csrrd {}, {}", out(reg) badv, const 0x7); + } + early_put_hex("[trap] crmd=", crmd); + early_put_hex("[trap] estat=", estat); + early_put_hex("[trap] era=", era); + early_put_hex("[trap] badv=", badv); +} + /// 初始化当前 hart 的 trap 相关状态。 /// /// 该函数需要每个 hart 各自执行一次,用于安装本 hart 的内核 trap 入口, @@ -159,6 +197,24 @@ pub fn trap_handler() -> ! { current_trap_cx().in_syscall = false; current_trap_cx().restartable_syscall = false; let trap_info = ArchTrapMachine::read_trap_info(); + #[cfg(target_arch = "loongarch64")] + { + static mut FIRST_USER_TRAP_LOGGED: bool = false; + unsafe { + if !FIRST_USER_TRAP_LOGGED { + FIRST_USER_TRAP_LOGGED = true; + let cx = current_trap_cx(); + early_put_hex("[user_trap] cause=", trap_info.cause as usize); + early_put_hex("[user_trap] fault_addr=", trap_info.fault_addr); + early_put_hex("[user_trap] user_pc=", cx.user_pc()); + early_put_hex("[user_trap] sp=", cx.user_sp()); + early_put_hex("[user_trap] ra=", cx.ra()); + early_put_hex("[user_trap] tp=", cx.tls()); + early_put_hex("[user_trap] a0=", cx.reg(4)); + early_put_hex("[user_trap] a7=", cx.syscall_nr()); + } + } + } match trap_info.cause { TrapCause::UserSyscall => { // jump to next instruction anyway @@ -344,6 +400,25 @@ pub fn trap_handler() -> ! { crate::net::poll(); } _ => { + #[cfg(target_arch = "loongarch64")] + { + let estat = crate::hal::ArchTrapMachine::read_trap_info(); + let raw_estat = { + let value: usize; + unsafe { core::arch::asm!("csrrd {}, {}", out(reg) value, const 0x5usize) }; + value + }; + let badi = { + let value: usize; + unsafe { core::arch::asm!("csrrd {}, {}", out(reg) value, const 0x8usize) }; + value + }; + early_put_hex("[user_trap] raw_estat=", raw_estat); + early_put_hex("[user_trap] ecode=", (raw_estat >> 16) & 0x3f); + early_put_hex("[user_trap] esubcode=", (raw_estat >> 22) & 0x1ff); + early_put_hex("[user_trap] badi=", badi); + early_put_hex("[user_trap] fault_addr_dup=", estat.fault_addr); + } panic!( "Unsupported trap {:?}, fault_addr = {:#x}!", trap_info.cause, @@ -379,6 +454,50 @@ pub fn trap_return() -> ! { let trap_cx_user_va = current_trap_cx_user_va(); current_trap_cx().set_kernel_hartid(hartid()); let user_token = current_user_token(); + #[cfg(target_arch = "loongarch64")] + { + let current_token = unsafe { crate::hal::current_address_space_token() }; + let cx = current_trap_cx(); + let sp: usize; + unsafe { + core::arch::asm!("move {}, $sp", out(reg) sp); + } + let sp_vpn = crate::mm::VirtAddr::from(sp & !(PAGE_SIZE - 1)).floor(); + let trap_cx_vpn = crate::mm::VirtAddr::from(trap_cx_user_va).floor(); + let trampoline_vpn = crate::mm::VirtAddr::from(crate::config::TRAMPOLINE).floor(); + let kernel_space = crate::mm::KERNEL_SPACE.lock(); + let user_page_table = crate::mm::PageTable::from_token(user_token); + // early_put_hex("[trap_return] current_pgdl=", current_token); + // early_put_hex("[trap_return] user_pgdl=", user_token); + // early_put_hex("[trap_return] trap_cx_user_va=", trap_cx_user_va); + // early_put_hex("[trap_return] user_prmd=", cx.arch.prmd); + // early_put_hex("[trap_return] user_era=", cx.arch.era); + // early_put_hex("[trap_return] user_ra=", cx.arch.r[1]); + // early_put_hex("[trap_return] user_sp_saved=", cx.arch.r[3]); + // early_put_hex("[trap_return] user_a0=", cx.arch.r[4]); + // early_put_hex("[trap_return] user_a7=", cx.arch.r[11]); + // early_put_hex("[trap_return] sp=", sp); + if let Some(pte) = kernel_space.translate(sp_vpn) { + // early_put_hex("[trap_return] kstack_pte=", pte.bits); + } else { + // crate::early_puts("[trap_return] kstack_pte=NONE\r\n"); + } + if let Some(pte) = kernel_space.translate(trampoline_vpn) { + // early_put_hex("[trap_return] tramp_pte=", pte.bits); + } else { + // crate::early_puts("[trap_return] tramp_pte=NONE\r\n"); + } + if let Some(pte) = user_page_table.translate(trap_cx_vpn) { + // early_put_hex("[trap_return] user_trap_cx_pte=", pte.bits); + } else { + // crate::early_puts("[trap_return] user_trap_cx_pte=NONE\r\n"); + } + if let Some(pte) = user_page_table.translate(trampoline_vpn) { + // early_put_hex("[trap_return] user_tramp_pte=", pte.bits); + } else { + // crate::early_puts("[trap_return] user_tramp_pte=NONE\r\n"); + } + } current_process().enter_user(get_time()); unsafe { ArchTrapMachine::return_to_user(trap_cx_user_va, user_token) } } @@ -388,6 +507,20 @@ pub fn trap_return() -> ! { pub fn trap_from_kernel() { let trap_info = ArchTrapMachine::read_trap_info(); match trap_info.cause { + #[cfg(target_arch = "loongarch64")] + TrapCause::StorePageFault | TrapCause::LoadPageFault => { + dump_kernel_trap_state(); + let page_base = trap_info.fault_addr & !(PAGE_SIZE - 1); + let heap_end = KERNEL_HEAP_BASE + MAX_KERNEL_HEAP_SIZE; + if (KERNEL_HEAP_BASE..heap_end).contains(&page_base) && map_one_heap_page(page_base) { + return; + } + panic!( + "Kernel trap: {:?}, fault_addr = {:#x}", + trap_info.cause, + trap_info.fault_addr + ); + } TrapCause::ExternalInterrupt => { #[cfg(target_arch = "riscv64")] crate::drivers::plic::handle_supervisor_external(); @@ -411,6 +544,8 @@ pub fn trap_from_kernel() { handle_reschedule_ipi(); } _ => { + #[cfg(target_arch = "loongarch64")] + dump_kernel_trap_state(); panic!( "Kernel trap: {:?}, fault_addr = {:#x}", trap_info.cause, diff --git a/user/src/bin/setupsh.rs b/user/src/bin/setupsh.rs index 61d4f274..71495061 100644 --- a/user/src/bin/setupsh.rs +++ b/user/src/bin/setupsh.rs @@ -25,6 +25,7 @@ const BIN_DATE: &str = "/bin/date"; const BOOT_DIR: &str = "/boot"; const BOOT_CONFIG_PATH: &str = "/boot/config-6.6.0"; const LIB_DIR: &str = "/lib"; +const LIB64_DIR: &str = "/lib64"; const ETC_DIR: &str = "/etc"; const ETC_PASSWD_PATH: &str = "/etc/passwd"; const ETC_GROUP_PATH: &str = "/etc/group"; @@ -57,21 +58,53 @@ const MODULES_BUILTIN_PATH: &str = "/lib/modules/6.6.0/modules.builtin"; const MODULES_DEP_PATH: &str = "/lib/modules/6.6.0/modules.dep"; const MUSL_BUSYBOX_PATH: &str = "/musl/busybox"; const MUSL_LEGACY_LIB_DIR: &str = "/musl/lib"; +#[cfg(target_arch = "riscv64")] const MUSL_LIB_DIR: &str = "/usr/lib/riscv64-linux-musl"; +#[cfg(target_arch = "riscv64")] const MUSL_LIBC_PATH: &str = "/usr/lib/riscv64-linux-musl/libc.so"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_PATH: &str = "/lib/ld-musl-riscv64-sf.so.1"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_COMPAT_PATH: &str = "/lib/ld-musl-riscv64.so.1"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_CONFIG_PATH: &str = "/etc/ld-musl-riscv64-sf.path"; +#[cfg(target_arch = "riscv64")] const MUSL_LD_CONFIG_CONTENT: &[u8] = b"/usr/lib/riscv64-linux-musl\n/lib\n"; + +#[cfg(target_arch = "loongarch64")] +const MUSL_LIB_DIR: &str = "/usr/lib/loongarch64-linux-musl"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LIBC_PATH: &str = "/usr/lib/loongarch64-linux-musl/libc.so"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_PATH: &str = "/lib64/ld-musl-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_CONFIG_PATH: &str = "/etc/ld-musl-loongarch-lp64d.path"; +#[cfg(target_arch = "loongarch64")] +const MUSL_LD_CONFIG_CONTENT: &[u8] = b"/usr/lib/loongarch64-linux-musl\n/lib\n/lib64\n"; const GLIBC_BUSYBOX_PATH: &str = "/glibc/busybox"; const GLIBC_BUSYBOX_TARGET: &str = "/usr/bin/glibc-busybox"; const GLIBC_BUSYBOX_TARGET_CSTR: &str = "/usr/bin/glibc-busybox\0"; const GLIBC_LEGACY_LIB_DIR: &str = "/glibc/lib"; +#[cfg(target_arch = "riscv64")] const GLIBC_LIB_DIR: &str = "/lib/riscv64-linux-gnu"; +#[cfg(target_arch = "riscv64")] const GLIBC_USR_LIB_DIR: &str = "/usr/lib/riscv64-linux-gnu"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_NAME: &str = "ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_TARGET: &str = "/lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "riscv64")] const GLIBC_LD_PATH: &str = "/lib/ld-linux-riscv64-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LIB_DIR: &str = "/lib/loongarch64-linux-gnu"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_USR_LIB_DIR: &str = "/usr/lib/loongarch64-linux-gnu"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_NAME: &str = "ld-linux-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_TARGET: &str = "/lib/loongarch64-linux-gnu/ld-linux-loongarch-lp64d.so.1"; +#[cfg(target_arch = "loongarch64")] +const GLIBC_LD_PATH: &str = "/lib64/ld-linux-loongarch-lp64d.so.1"; const ROOT_GROUPDEL: &str = "/root/groupdel"; const ROOT_USERADD: &str = "/root/useradd"; const ROOT_USERDEL: &str = "/root/userdel"; @@ -412,6 +445,7 @@ fn ensure_dirs() -> bool { LIB_DIR, MODULES_ROOT_DIR, MODULES_DIR, + LIB64_DIR, ETC_DIR, HOME_DIR, ROOT_HOME_DIR, @@ -433,6 +467,26 @@ fn ensure_dirs() -> bool { true } +fn install_loader_links() -> bool { + if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_PATH) { + return false; + } + if !ensure_hard_link(GLIBC_LD_TARGET, GLIBC_LD_PATH) { + println!( + "[setupsh] glibc loader must exist as {} in {}", + GLIBC_LD_NAME, GLIBC_LIB_DIR + ); + return false; + } + #[cfg(target_arch = "riscv64")] + { + if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_COMPAT_PATH) { + return false; + } + } + true +} + fn write_file(path: &str, content: &[u8]) -> bool { let fd = open( path, @@ -480,23 +534,6 @@ fn install_ltp_env_scripts() -> bool { true } -fn install_loader_links() -> bool { - if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_PATH) { - return false; - } - if !ensure_hard_link(MUSL_LIBC_PATH, MUSL_LD_COMPAT_PATH) { - return false; - } - if !ensure_hard_link(GLIBC_LD_TARGET, GLIBC_LD_PATH) { - println!( - "[setupsh] glibc loader must exist as {} in {}", - GLIBC_LD_NAME, GLIBC_LIB_DIR - ); - return false; - } - true -} - fn install_busybox_entries() -> bool { if !ensure_hard_link(MUSL_BUSYBOX_PATH, BIN_BUSYBOX) { return false; From 40ba46d12fa1cd9208ad5030437bb33013da378f Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 15:31:05 +0800 Subject: [PATCH 08/19] fix: Loongarch register ABI in rust syscall. --- os/src/arch/loongarch64/trap.S | 81 ++++++++++------------------------ user/src/bin/setupsh.rs | 75 +++++++++++++------------------ user/src/lib.rs | 11 +++++ user/src/syscall.rs | 28 ++++++++++++ 4 files changed, 94 insertions(+), 101 deletions(-) diff --git a/os/src/arch/loongarch64/trap.S b/os/src/arch/loongarch64/trap.S index 3d0140e1..db305964 100644 --- a/os/src/arch/loongarch64/trap.S +++ b/os/src/arch/loongarch64/trap.S @@ -2,6 +2,14 @@ # Uses hardware page-table walker: PGD → lddir × N → ldpte × 2 → tlbfill. .section .text.trampoline .equ CSR_SAVE, 0x30 + .equ CSR_SAVE1, 0x31 + .altmacro + .macro STORE_X n + st.d $r\n, $sp, \n*8 + .endm + .macro LOAD_X n + ld.d $r\n, $sp, \n*8 + .endm .globl __tlb_refill .balign 4096 __tlb_refill: @@ -26,43 +34,24 @@ __tlb_refill: .globl __restore .align 3 __alltraps: - move $t0, $sp - csrrd $sp, CSR_SAVE + # Swap user sp with the per-task trap context pointer kept in CSR_SAVE. + # This preserves every user GPR, including t0/r12. + csrwr $sp, CSR_SAVE st.d $ra, $sp, 1*8 st.d $tp, $sp, 2*8 - st.d $t0, $sp, 3*8 - st.d $a0, $sp, 4*8 - st.d $a1, $sp, 5*8 - st.d $a2, $sp, 6*8 - st.d $a3, $sp, 7*8 - st.d $a4, $sp, 8*8 - st.d $a5, $sp, 9*8 - st.d $a6, $sp, 10*8 - st.d $a7, $sp, 11*8 - st.d $t1, $sp, 12*8 - st.d $t2, $sp, 13*8 - st.d $t3, $sp, 14*8 - st.d $t4, $sp, 15*8 - st.d $t5, $sp, 16*8 - st.d $t6, $sp, 17*8 - st.d $t7, $sp, 18*8 - st.d $t8, $sp, 19*8 - st.d $r21, $sp, 20*8 - st.d $fp, $sp, 22*8 - st.d $s0, $sp, 23*8 - st.d $s1, $sp, 24*8 - st.d $s2, $sp, 25*8 - st.d $s3, $sp, 26*8 - st.d $s4, $sp, 27*8 - st.d $s5, $sp, 28*8 - st.d $s6, $sp, 29*8 - st.d $s7, $sp, 30*8 - st.d $s8, $sp, 31*8 + .set n, 4 + .rept 28 + STORE_X %n + .set n, n + 1 + .endr csrrd $t0, 0x1 csrrd $t1, 0x6 + # CSR_SAVE now holds the interrupted user sp after the swap above. + csrrd $t2, CSR_SAVE st.d $t0, $sp, 32*8 st.d $t1, $sp, 33*8 + st.d $t2, $sp, 3*8 # Keep the first user-trap path free of FP instructions. # LoongArch can trap on FP use when EUEN/FPU state is not fully prepared, @@ -89,33 +78,11 @@ __restore: csrwr $t1, 0x6 ld.d $ra, $sp, 1*8 ld.d $tp, $sp, 2*8 - ld.d $a0, $sp, 4*8 - ld.d $a1, $sp, 5*8 - ld.d $a2, $sp, 6*8 - ld.d $a3, $sp, 7*8 - ld.d $a4, $sp, 8*8 - ld.d $a5, $sp, 9*8 - ld.d $a6, $sp, 10*8 - ld.d $a7, $sp, 11*8 - ld.d $t1, $sp, 12*8 - ld.d $t2, $sp, 13*8 - ld.d $t3, $sp, 14*8 - ld.d $t4, $sp, 15*8 - ld.d $t5, $sp, 16*8 - ld.d $t6, $sp, 17*8 - ld.d $t7, $sp, 18*8 - ld.d $t8, $sp, 19*8 - ld.d $r21, $sp, 20*8 - ld.d $fp, $sp, 22*8 - ld.d $s0, $sp, 23*8 - ld.d $s1, $sp, 24*8 - ld.d $s2, $sp, 25*8 - ld.d $s3, $sp, 26*8 - ld.d $s4, $sp, 27*8 - ld.d $s5, $sp, 28*8 - ld.d $s6, $sp, 29*8 - ld.d $s7, $sp, 30*8 - ld.d $s8, $sp, 31*8 + .set n, 4 + .rept 28 + LOAD_X %n + .set n, n + 1 + .endr ld.d $sp, $sp, 3*8 ertn diff --git a/user/src/bin/setupsh.rs b/user/src/bin/setupsh.rs index 71495061..c402ecc7 100644 --- a/user/src/bin/setupsh.rs +++ b/user/src/bin/setupsh.rs @@ -9,7 +9,8 @@ use alloc::string::String; use core::ptr; use user_lib::{ - chdir, close, exec, execve, exit, fork, fstatat, getdents64, link, mkdir, open, unlink, waitpid, write, + chdir, close, exec_ptr, exit, fork, fstatat, getdents64, link, mkdir, open, unlink, waitpid, + write, OpenFlags, Stat, }; @@ -148,15 +149,15 @@ fn path_exists(path: &str) -> bool { } /// 运行一个外部程序并等待其退出。 -fn spawn_and_wait(path: &str, argv: &[*const u8]) -> i32 { +fn spawn_and_wait(child_exec: fn() -> isize, path_display: &str) -> i32 { let pid = fork(); if pid < 0 { - println!("[setupsh] fork failed for {}", path); + println!("[setupsh] fork failed for {}", path_display); return -1; } if pid == 0 { - let ret = exec(path, argv); - println!("[setupsh] exec {} failed: {}", path, ret); + let ret = child_exec(); + println!("[setupsh] exec {} failed: {}", path_display, ret); exit(127); } @@ -172,29 +173,29 @@ fn spawn_and_wait(path: &str, argv: &[*const u8]) -> i32 { exit_code } -/// 运行一个外部程序并显式传入环境变量,然后等待其退出。 -fn spawn_and_wait_with_env(path: &str, argv: &[*const u8], envp: &[*const u8]) -> i32 { - let pid = fork(); - if pid < 0 { - println!("[setupsh] fork failed for {}", path); - return -1; - } - if pid == 0 { - let ret = execve(path, argv, envp); - println!("[setupsh] execve {} failed: {}", path, ret); - exit(127); - } +fn exec_bin_busybox_install() -> isize { + let argv = [ + BUSYBOX_ARG0_CSTR.as_ptr(), + INSTALL_ARG_CSTR.as_ptr(), + BIN_DIR_CSTR.as_ptr(), + ptr::null(), + ]; + exec_ptr(BIN_BUSYBOX_CSTR.as_ptr(), &argv) +} - let mut exit_code = 0i32; - let waited = waitpid(pid as usize, &mut exit_code); - if waited != pid { - println!( - "[setupsh] waitpid mismatch: expected {}, got {}", - pid, waited - ); - return -1; - } - exit_code +fn exec_glibc_busybox_install() -> isize { + let argv = [ + BUSYBOX_ARG0_CSTR.as_ptr(), + INSTALL_ARG_CSTR.as_ptr(), + USR_BIN_DIR_CSTR.as_ptr(), + ptr::null(), + ]; + exec_ptr(GLIBC_BUSYBOX_TARGET_CSTR.as_ptr(), &argv) +} + +fn exec_bin_sh() -> isize { + let argv = [BIN_SH_CSTR.as_ptr(), ptr::null()]; + exec_ptr(BIN_SH_CSTR.as_ptr(), &argv) } /// 打印阶段进度,便于观察 `setupsh` 当前执行到哪一步。 @@ -563,13 +564,7 @@ fn install_kernel_module_metadata() -> bool { fn install_busybox_applets() -> bool { if !path_exists("/bin/sh") { - let musl_install_argv = [ - BUSYBOX_ARG0_CSTR.as_ptr(), - INSTALL_ARG_CSTR.as_ptr(), - BIN_DIR_CSTR.as_ptr(), - ptr::null(), - ]; - let install_exit = spawn_and_wait(BIN_BUSYBOX_CSTR, &musl_install_argv); + let install_exit = spawn_and_wait(exec_bin_busybox_install, BIN_BUSYBOX); if install_exit != 0 { println!("[setupsh] musl busybox --install failed: {}", install_exit); return false; @@ -577,13 +572,7 @@ fn install_busybox_applets() -> bool { } if !path_exists("/usr/bin/sh") { - let glibc_install_argv = [ - BUSYBOX_ARG0_CSTR.as_ptr(), - INSTALL_ARG_CSTR.as_ptr(), - USR_BIN_DIR_CSTR.as_ptr(), - ptr::null(), - ]; - let install_exit = spawn_and_wait(GLIBC_BUSYBOX_TARGET_CSTR, &glibc_install_argv); + let install_exit = spawn_and_wait(exec_glibc_busybox_install, GLIBC_BUSYBOX_TARGET); if install_exit != 0 { println!("[setupsh] glibc busybox --install failed: {}", install_exit); return false; @@ -717,9 +706,7 @@ fn main(_argc: usize, argv: &[&str]) -> i32 { ROOT_HOME_DIR, chdir_ret ); } - let shell_argv = [BIN_SH_CSTR.as_ptr(), ptr::null()]; - let shell_envp = [SHELL_PATH_ENV_CSTR.as_ptr(), ptr::null()]; - let shell_exit = spawn_and_wait_with_env(BIN_SH_CSTR, &shell_argv, &shell_envp); + let shell_exit = spawn_and_wait(exec_bin_sh, "/bin/sh"); println!("[setupsh] /bin/sh exited with {}", shell_exit); shell_exit } diff --git a/user/src/lib.rs b/user/src/lib.rs index eaeb087a..b91b06c6 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -592,6 +592,17 @@ pub fn execve(path: &str, args: &[*const u8], envp: &[*const u8]) -> isize { sys_execve(path, args, envp) } +/// 显式使用 NUL 结尾 C 字符串路径的 `execve` 变体。 +pub fn exec_ptr(path: *const u8, args: &[*const u8]) -> isize { + let envp: [*const u8; 1] = [core::ptr::null()]; + sys_execve_ptr(path, args, &envp) +} + +/// 显式使用 NUL 结尾 C 字符串路径的 `execve` 变体。 +pub fn execve_ptr(path: *const u8, args: &[*const u8], envp: &[*const u8]) -> isize { + sys_execve_ptr(path, args, envp) +} + pub fn set_priority(prio: isize) -> isize { sys_set_priority(prio) } diff --git a/user/src/syscall.rs b/user/src/syscall.rs index 26ee2f7b..92883927 100644 --- a/user/src/syscall.rs +++ b/user/src/syscall.rs @@ -113,6 +113,18 @@ pub fn syscall(id: usize, args: [usize; 3]) -> isize { in("$a1") args[1], in("$a2") args[2], in("$a7") id, + // A kernel syscall may freely clobber LA64 caller-saved temporaries. + // Mark them explicitly so LLVM won't keep live values such as execve + // path pointers in t0 across a syscall boundary. + lateout("$t0") _, + lateout("$t1") _, + lateout("$t2") _, + lateout("$t3") _, + lateout("$t4") _, + lateout("$t5") _, + lateout("$t6") _, + lateout("$t7") _, + lateout("$t8") _, ); } ret @@ -148,6 +160,15 @@ pub fn syscall6(id: usize, args: [usize; 6]) -> isize { in("$a4") args[4], in("$a5") args[5], in("$a7") id, + lateout("$t0") _, + lateout("$t1") _, + lateout("$t2") _, + lateout("$t3") _, + lateout("$t4") _, + lateout("$t5") _, + lateout("$t6") _, + lateout("$t7") _, + lateout("$t8") _, ); } ret @@ -481,6 +502,13 @@ pub fn sys_execve(path: &str, args: &[*const u8], envp: &[*const u8]) -> isize { ) } +pub fn sys_execve_ptr(path: *const u8, args: &[*const u8], envp: &[*const u8]) -> isize { + syscall( + SYSCALL_EXECVE, + [path as usize, args.as_ptr() as usize, envp.as_ptr() as usize], + ) +} + pub fn sys_waitpid(pid: isize, xstatus: *mut i32) -> isize { syscall(SYSCALL_WAITPID, [pid as usize, xstatus as usize, 0]) } From c6c8effde3cacd52d4e4c4a126736ee93d555f74 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 16:28:07 +0800 Subject: [PATCH 09/19] fix: `set_fs` should operate at S mode instead of M mode. --- os/src/arch/riscv/hart.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/src/arch/riscv/hart.rs b/os/src/arch/riscv/hart.rs index ae5e1c14..e3ee6454 100644 --- a/os/src/arch/riscv/hart.rs +++ b/os/src/arch/riscv/hart.rs @@ -2,7 +2,7 @@ use core::arch::asm; use crate::hal::traits::HartId; -use riscv::{asm::wfi, register::{mstatus::{set_fs, FS}, sstatus}}; +use riscv::{asm::wfi, register::{mstatus::FS, sstatus}}; /// RISC-V implementation of [`HartId`](crate::hal::traits::HartId) via the `tp` register. pub struct RiscvHartId; @@ -22,7 +22,7 @@ impl HartId for RiscvHartId { #[inline] unsafe fn enable_fp() { - set_fs(FS::Initial); + sstatus::set_fs(FS::Initial); } #[inline] From f3b7ca25493c99aee4f844c2fe9850e7aea132fa Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 17:18:53 +0800 Subject: [PATCH 10/19] feat: support syscalls `statx` (291); temporary change to polling in UART for LA. --- os/src/arch/loongarch64/trap.rs | 10 +- os/src/drivers/chardev/ns16550a.rs | 43 +++++--- os/src/fs/tty.rs | 12 +++ os/src/sched/processor.rs | 8 ++ os/src/syscall/fs.rs | 155 +++++++++++++++++++++++++++++ os/src/syscall/mod.rs | 9 ++ 6 files changed, 222 insertions(+), 15 deletions(-) diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs index b151d726..6a598120 100644 --- a/os/src/arch/loongarch64/trap.rs +++ b/os/src/arch/loongarch64/trap.rs @@ -61,7 +61,15 @@ pub struct LoongArchTrapContextFrame { pub fcsr: usize, } -const USER_VDSO_CODE: [u8; 4] = [0x00, 0x00, 0x2b, 0x00]; +/// 用户态 `rt_sigreturn` trampoline 机器码。 +/// +/// 等价指令序列: +/// ori $a7, $zero, 139 +/// syscall 0 +const USER_VDSO_CODE: [u8; 8] = [ + 0x0b, 0x2c, 0x82, 0x03, // ori $a7, $zero, 139 + 0x00, 0x00, 0x2b, 0x00, // syscall 0 +]; impl InterruptControl for LoongArchInterruptControl { unsafe fn enable_timer() { diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 575a924a..8e20e75c 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -5,7 +5,11 @@ use bitflags::bitflags; use crate::poll::{notify_poll_source, POLLIN}; use crate::sync::SpinNoIrqLock; -use crate::task::{WaitQueue, WaitReason}; +use crate::task::WaitQueue; +#[cfg(target_arch = "loongarch64")] +use crate::task::yield_current_and_run_next; +#[cfg(not(target_arch = "loongarch64"))] +use crate::task::WaitReason; use super::{set_uart_ready, CharDevice}; @@ -231,19 +235,30 @@ impl CharDevice for NS16550a { } drop(inner); - // No data: block current task until UART IRQ pushes data and signals. - self.rx_wait_queue - .wait_with_reason_or_skip(WaitReason::UartRx, || { - let mut inner = self.inner.lock(); - if !inner.read_buffer.is_empty() { - return true; - } - if let Some(ch) = inner.ns16550a.try_read() { - inner.read_buffer.push_back(ch); - return true; - } - false - }); + #[cfg(target_arch = "loongarch64")] + { + // LA64 bring-up does not have UART RX interrupts wired through a + // platform IRQ controller yet, so avoid sleeping forever waiting + // for an IRQ wakeup that never comes. Poll cooperatively instead. + yield_current_and_run_next(); + } + + #[cfg(not(target_arch = "loongarch64"))] + { + // No data: block current task until UART IRQ pushes data and signals. + self.rx_wait_queue + .wait_with_reason_or_skip(WaitReason::UartRx, || { + let mut inner = self.inner.lock(); + if !inner.read_buffer.is_empty() { + return true; + } + if let Some(ch) = inner.ns16550a.try_read() { + inner.read_buffer.push_back(ch); + return true; + } + false + }); + } } } diff --git a/os/src/fs/tty.rs b/os/src/fs/tty.rs index d36516f0..42a44482 100644 --- a/os/src/fs/tty.rs +++ b/os/src/fs/tty.rs @@ -543,6 +543,17 @@ impl TtyCore { if let Some(byte) = self.take_ready_byte() { return byte; } + #[cfg(target_arch = "loongarch64")] + { + // LA64 bring-up still lacks a working UART RX interrupt path, + // so sleeping on `read_wq` would park the shell and send the + // scheduler into the idle loop forever. Poll cooperatively + // until the platform IRQ controller is wired up. + crate::task::yield_current_and_run_next(); + continue; + } + #[cfg(not(target_arch = "loongarch64"))] + { // 3. 阻塞,直到中断路径补满输入或有信号到来。 self.read_wq .wait_with_reason_or_skip(WaitReason::UartRx, || self.read_ready()); @@ -550,6 +561,7 @@ impl TtyCore { if !self.read_ready() && has_interrupting_signal() { return Err(ERRNO::EINTR); } + } } } diff --git a/os/src/sched/processor.rs b/os/src/sched/processor.rs index c3620e49..ccb9948b 100644 --- a/os/src/sched/processor.rs +++ b/os/src/sched/processor.rs @@ -129,6 +129,14 @@ pub(crate) fn run_tasks() { } // debug!("No task to run, idle..."); + #[cfg(target_arch = "loongarch64")] + { + // LA64 bring-up does not have a working UART RX interrupt path + // yet, so drain the console UART opportunistically from the + // idle loop. This preserves echo/canonical processing and wakes + // blocked tty readers once a full line arrives. + crate::fs::console_receive(); + } crate::trap::set_kernel_trap_entry(); diff --git a/os/src/syscall/fs.rs b/os/src/syscall/fs.rs index a9ece4d1..a4f92bc6 100644 --- a/os/src/syscall/fs.rs +++ b/os/src/syscall/fs.rs @@ -477,6 +477,38 @@ fn alloc_anonymous_fd_with_bits( fn alloc_anonymous_fd(status_flags: FileStatusFlags, cloexec: bool) -> Result { alloc_anonymous_fd_with_bits(status_flags, cloexec, 0) } +const AT_NO_AUTOMOUNT: u32 = 0x800; +const AT_STATX_SYNC_TYPE: u32 = 0x6000; + +bitflags! { + struct StatxMask: u32 { + const TYPE = 0x0001; + const MODE = 0x0002; + const NLINK = 0x0004; + const UID = 0x0008; + const GID = 0x0010; + const ATIME = 0x0020; + const MTIME = 0x0040; + const CTIME = 0x0080; + const INO = 0x0100; + const SIZE = 0x0200; + const BLOCKS = 0x0400; + const BTIME = 0x0800; + + const BASIC_STATS = Self::TYPE.bits + | Self::MODE.bits + | Self::NLINK.bits + | Self::UID.bits + | Self::GID.bits + | Self::ATIME.bits + | Self::MTIME.bits + | Self::CTIME.bits + | Self::INO.bits + | Self::SIZE.bits + | Self::BLOCKS.bits; + const ALL = Self::BASIC_STATS.bits | Self::BTIME.bits; + } +} #[derive(Clone, Copy, Debug, Default)] struct PselectFdMeta { @@ -494,6 +526,76 @@ struct PselectSigmaskArg { impl Pod for PselectSigmaskArg {} +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +struct StatxTimestamp { + tv_sec: i64, + tv_nsec: u32, + reserved: i32, +} + +impl From<(isize, isize)> for StatxTimestamp { + fn from((sec, nsec): (isize, isize)) -> Self { + Self { + tv_sec: sec as i64, + tv_nsec: nsec as u32, + reserved: 0, + } + } +} + +/// Linux `statx(2)` userspace ABI. +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Statx { + stx_mask: u32, + stx_blksize: u32, + stx_attributes: u64, + stx_nlink: u32, + stx_uid: u32, + stx_gid: u32, + stx_mode: u16, + stx_spare0: u16, + stx_ino: u64, + stx_size: u64, + stx_blocks: u64, + stx_attributes_mask: u64, + stx_atime: StatxTimestamp, + stx_btime: StatxTimestamp, + stx_ctime: StatxTimestamp, + stx_mtime: StatxTimestamp, + stx_rdev_major: u32, + stx_rdev_minor: u32, + stx_dev_major: u32, + stx_dev_minor: u32, + stx_mnt_id: u64, + stx_dio_mem_align: u32, + stx_dio_offset_align: u32, + stx_spare3: [u64; 12], +} + +impl Pod for Statx {} + +fn stat_to_statx(stat: &Stat, requested_mask: StatxMask) -> Statx { + let available_mask = StatxMask::BASIC_STATS; + let _ = requested_mask; + Statx { + stx_mask: available_mask.bits(), + stx_blksize: stat.blksize, + stx_nlink: stat.nlink, + stx_uid: stat.uid, + stx_gid: stat.gid, + stx_mode: stat.mode.bits() as u16, + stx_ino: stat.ino, + stx_size: stat.size.max(0) as u64, + stx_blocks: stat.blocks, + stx_atime: (stat.atime_sec, stat.atime_nsec).into(), + stx_ctime: (stat.ctime_sec, stat.ctime_nsec).into(), + stx_mtime: (stat.mtime_sec, stat.mtime_nsec).into(), + ..Statx::default() + } +} + /// 从用户态复制 `pollfd` 数组,兼容跨页布局。 fn copy_user_pollfds(token: usize, ufds: *mut PollFd, nfds: usize) -> Result, ERRNO> { if nfds == 0 { @@ -2647,6 +2749,59 @@ let time5 = get_time_us(); }) } +/// `statx(2)` 系统调用:按目录 fd 与路径查询增强版文件元数据。 +pub fn sys_statx( + dirfd: isize, + path: *const u8, + flags: i32, + mask: u32, + stx: *mut Statx, +) -> isize { + trace!( + "kernel:pid[{}] sys_statx", + current_task().unwrap().process.upgrade().unwrap().getpid() + ); + syscall_body!({ + let flags = flags as u32; + let supported_flags = + AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE; + if flags & !supported_flags != 0 { + return Err(ERRNO::EINVAL); + } + let mask = StatxMask::from_bits(mask).ok_or(ERRNO::EINVAL)?; + let path = if path.is_null() { + if flags & AT_EMPTY_PATH == 0 { + return Err(ERRNO::EFAULT); + } + String::new() + } else { + read_cstring_from_user(path, PATH_MAX)? + }; + + let stat = if path.is_empty() { + if flags & AT_EMPTY_PATH == 0 { + return Err(ERRNO::ENOENT); + } + match resolve_at_target(dirfd, "", flags as i32)? { + ResolvedAtTarget::Inode(inode) => inode_stat(&inode), + ResolvedAtTarget::FileDesc(desc) => desc.stat(), + } + } else { + let cwd = resolve_dirfd_base(dirfd, path.as_str())?; + let inode = lookup_inode_follow( + cwd.as_str(), + path.as_str(), + flags & AT_SYMLINK_NOFOLLOW == 0, + )?; + inode_stat(&inode) + }; + + let statx = stat_to_statx(&stat, mask); + write_pod_to_user(stx, &statx)?; + Ok(0) + }) +} + /// `faccessat` 系统调用:按目录 fd 与路径检查可访问性。 pub fn sys_faccessat(dirfd: isize, path: *const u8, mode: i32) -> isize { trace!( diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index 4b974897..7073abf2 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -326,6 +326,8 @@ pub const SYSCALL_MEMFD_CREATE: usize = 279; pub const SYSCALL_BPF: usize = 280; /// userfaultfd syscall pub const SYSCALL_USERFAULTFD: usize = 282; +/// statx syscall +pub const SYSCALL_STATX: usize = 291; /// spawn syscall pub const SYSCALL_SPAWN: usize = 400; /// clock_adjtime64 syscall @@ -604,6 +606,13 @@ pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { args[2] as *mut Stat, args[3] as i32, ), + SYSCALL_STATX => sys_statx( + args[0] as isize, + args[1] as *const u8, + args[2] as i32, + args[3] as u32, + args[4] as *mut crate::syscall::fs::Statx, + ), SYSCALL_UTIMENSAT => sys_utimensat( args[0] as isize, args[1] as *const u8, From 282279d26ca08d324508bde96ebaddb57e63fcdc Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 20:01:28 +0800 Subject: [PATCH 11/19] feat: Support external interrupt and shutdown in LA. --- os/src/arch/loongarch64/trap.rs | 42 +++-- os/src/batch.rs | 154 ------------------ os/src/boards/loongarch_virt.rs | 40 ++--- os/src/drivers/chardev/ns16550a.rs | 41 ++--- os/src/drivers/mod.rs | 8 +- os/src/fs/tty.rs | 12 +- os/src/loader.rs | 70 -------- os/src/main.rs | 9 +- os/src/mm/mod.rs | 6 - os/src/platform/loongarch_virt.rs | 7 + os/src/platform/loongarch_virt/irq.rs | 142 ++++++++++++++++ .../loongarch_virt}/pci.rs | 28 +--- os/src/platform/mod.rs | 12 ++ os/src/platform/qemu_virt/mod.rs | 23 +++ os/src/platform/qemu_virt/sbi.rs | 2 +- os/src/sched/processor.rs | 9 +- os/src/trap/mod.rs | 104 +----------- 17 files changed, 271 insertions(+), 438 deletions(-) delete mode 100644 os/src/batch.rs delete mode 100644 os/src/loader.rs create mode 100644 os/src/platform/loongarch_virt/irq.rs rename os/src/{drivers => platform/loongarch_virt}/pci.rs (86%) diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs index 6a598120..18bd2758 100644 --- a/os/src/arch/loongarch64/trap.rs +++ b/os/src/arch/loongarch64/trap.rs @@ -25,8 +25,12 @@ const CSR_STLBPS: usize = 0x1e; const CRMD_IE: usize = 1 << 2; const EUEN_FPEN: usize = 1 << 0; const ECFG_SIP: usize = 1 << 1; +const ECFG_HWI0: usize = 1 << 2; const ECFG_TIMER: usize = 1 << 11; const ECFG_IPI: usize = 1 << 12; +// QEMU `virt` routes EXTIOI sources to CPU IP3, which is exposed in +// ESTAT/ECFG as HWI3 (interrupt number 5, bit 5). +const ECFG_EXTERNAL: usize = ECFG_HWI0 << 3; const ECODE_INT: usize = 0x0; const ECODE_PIL: usize = 0x1; @@ -81,11 +85,11 @@ impl InterruptControl for LoongArchInterruptControl { } unsafe fn enable_external() { - update_ecfg(ECFG_IPI, true); + update_ecfg(ECFG_EXTERNAL, true); } unsafe fn disable_external() { - update_ecfg(ECFG_IPI, false); + update_ecfg(ECFG_EXTERNAL, false); } unsafe fn enable_software() { @@ -153,6 +157,7 @@ impl InterruptControl for LoongArchInterruptControl { impl TrapMachine for LoongArchTrapMachine { fn read_trap_info() -> TrapInfo { let estat = read_estat(); + let ecfg = read_ecfg(); let badv = read_badv(); let ecode = (estat >> 16) & 0x3f; let cause = match ecode { @@ -163,16 +168,7 @@ impl TrapMachine for LoongArchTrapMachine { ECODE_INE => TrapCause::IllegalInstruction, ECODE_ADE => TrapCause::InstructionFault, ECODE_INT => { - let is = estat & 0x1fff; - if is & (1 << 11) != 0 { - TrapCause::TimerInterrupt - } else if is & (1 << 1) != 0 { - TrapCause::SoftwareInterrupt - } else if is & (1 << 12) != 0 { - TrapCause::ExternalInterrupt - } else { - TrapCause::Unknown - } + decode_interrupt_cause(estat, ecfg) } _ => TrapCause::Unknown, }; @@ -331,6 +327,13 @@ fn read_estat() -> usize { value } +#[inline] +fn read_ecfg() -> usize { + let value: usize; + unsafe { asm!("csrrd {}, {}", out(reg) value, const CSR_ECFG) }; + value +} + #[inline] fn read_badv() -> usize { let value: usize; @@ -345,6 +348,21 @@ fn read_badi() -> usize { value } +#[inline] +fn decode_interrupt_cause(estat: usize, ecfg: usize) -> TrapCause { + let int_vec = (estat & ecfg) & 0x1fff; + let highest = (0..=12).rev().find(|bit| int_vec & (1usize << bit) != 0); + match highest { + Some(12) => TrapCause::Unknown, + Some(11) => TrapCause::TimerInterrupt, + Some(1) | Some(0) => TrapCause::SoftwareInterrupt, + Some(2..=9) => TrapCause::ExternalInterrupt, + Some(10) => TrapCause::Unknown, + None => TrapCause::Unknown, + Some(_) => TrapCause::Unknown, + } +} + #[inline] unsafe fn update_ecfg(mask: usize, enable: bool) { let mut ecfg: usize; diff --git a/os/src/batch.rs b/os/src/batch.rs deleted file mode 100644 index 43e2c55a..00000000 --- a/os/src/batch.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! batch subsystem - -use crate::sync::UPSafeCell; -use crate::trap::TrapContext; -use core::arch::asm; -use lazy_static::*; - -const USER_STACK_SIZE: usize = 4096 * 2; -const KERNEL_STACK_SIZE: usize = 4096 * 2; -const MAX_APP_NUM: usize = 16; -const APP_BASE_ADDRESS: usize = 0x80400000; -const APP_SIZE_LIMIT: usize = 0x20000; - -#[repr(align(4096))] -struct KernelStack { - data: [u8; KERNEL_STACK_SIZE], -} - -#[repr(align(4096))] -struct UserStack { - data: [u8; USER_STACK_SIZE], -} - -static KERNEL_STACK: KernelStack = KernelStack { - data: [0; KERNEL_STACK_SIZE], -}; -static USER_STACK: UserStack = UserStack { - data: [0; USER_STACK_SIZE], -}; - -impl KernelStack { - fn get_sp(&self) -> usize { - self.data.as_ptr() as usize + KERNEL_STACK_SIZE - } - pub fn push_context(&self, cx: TrapContext) -> &'static mut TrapContext { - let cx_ptr = (self.get_sp() - core::mem::size_of::()) as *mut TrapContext; - unsafe { - *cx_ptr = cx; - } - unsafe { cx_ptr.as_mut().unwrap() } - } -} - -impl UserStack { - fn get_sp(&self) -> usize { - self.data.as_ptr() as usize + USER_STACK_SIZE - } -} - -struct AppManager { - num_app: usize, - current_app: usize, - app_start: [usize; MAX_APP_NUM + 1], -} - -impl AppManager { - pub fn print_app_info(&self) { - println!("[kernel] num_app = {}", self.num_app); - for i in 0..self.num_app { - println!( - "[kernel] app_{} [{:#x}, {:#x})", - i, - self.app_start[i], - self.app_start[i + 1] - ); - } - } - - unsafe fn load_app(&self, app_id: usize) { - if app_id >= self.num_app { - println!("All applications completed!"); - use crate::board::QEMUExit; - crate::board::QEMU_EXIT_HANDLE.exit_success(); - } - println!("[kernel] Loading app_{}", app_id); - // clear app area - core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0); - let app_src = core::slice::from_raw_parts( - self.app_start[app_id] as *const u8, - self.app_start[app_id + 1] - self.app_start[app_id], - ); - let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len()); - app_dst.copy_from_slice(app_src); - // Memory fence about fetching the instruction memory - // It is guaranteed that a subsequent instruction fetch must - // observes all previous writes to the instruction memory. - // Therefore, fence.i must be executed after we have loaded - // the code of the next app into the instruction memory. - // See also: riscv non-priv spec chapter 3, 'Zifencei' extension. - asm!("fence.i"); - } - - pub fn get_current_app(&self) -> usize { - self.current_app - } - - pub fn move_to_next_app(&mut self) { - self.current_app += 1; - } -} - -lazy_static! { - static ref APP_MANAGER: UPSafeCell = unsafe { - UPSafeCell::new({ - extern "C" { - fn _num_app(); - } - let num_app_ptr = _num_app as usize as *const usize; - let num_app = num_app_ptr.read_volatile(); - let mut app_start: [usize; MAX_APP_NUM + 1] = [0; MAX_APP_NUM + 1]; - let app_start_raw: &[usize] = - core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1); - app_start[..=num_app].copy_from_slice(app_start_raw); - AppManager { - num_app, - current_app: 0, - app_start, - } - }) - }; -} - -/// init batch subsystem -pub fn init() { - print_app_info(); -} - -/// print apps info -pub fn print_app_info() { - APP_MANAGER.exclusive_access().print_app_info(); -} - -/// run next app -pub fn run_next_app() -> ! { - let mut app_manager = APP_MANAGER.exclusive_access(); - let current_app = app_manager.get_current_app(); - unsafe { - app_manager.load_app(current_app); - } - app_manager.move_to_next_app(); - drop(app_manager); - // before this we have to drop local variables related to resources manually - // and release the resources - extern "C" { - fn __restore(cx_addr: usize); - } - unsafe { - __restore(KERNEL_STACK.push_context(TrapContext::app_init_context( - APP_BASE_ADDRESS, - USER_STACK.get_sp(), - )) as *const _ as usize); - } - panic!("Unreachable in batch::run_current_app!"); -} diff --git a/os/src/boards/loongarch_virt.rs b/os/src/boards/loongarch_virt.rs index 4f14170e..a5834530 100644 --- a/os/src/boards/loongarch_virt.rs +++ b/os/src/boards/loongarch_virt.rs @@ -37,8 +37,7 @@ pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; use core::arch::asm; const EXIT_SUCCESS: u32 = 0x5555; -const EXIT_FAILURE_FLAG: u32 = 0x3333; -const EXIT_FAILURE: u32 = exit_code_encode(1); +const EXIT_FAILURE: u32 = 0x0001_3333; /// QEMU exit interface. pub trait QEMUExit { @@ -52,34 +51,37 @@ pub trait QEMUExit { fn exit_failure(&self) -> !; } -/// LoongArch64 QEMU exit device wrapper. +/// LoongArch64 QEMU power-management wrapper. pub struct LOONGARCH64 { - addr: u64, + sleep_ctl_addr: u64, } -const fn exit_code_encode(code: u32) -> u32 { - (code << 16) | EXIT_FAILURE_FLAG -} +// QEMU `virt` exposes ACPI GED power-management registers at: +// VIRT_GED_EVT_ADDR = 0x100e0000 +// VIRT_GED_REG_ADDR = VIRT_GED_EVT_ADDR + ACPI_GED_EVT_SEL_LEN(0x4) +// + MEMORY_HOTPLUG_IO_LEN(24) +// = 0x100e001c +// `ACPI_GED_REG_SLEEP_CTL` is offset 0 and powers off the VM when written +// with SLP_EN | (S5 << SLP_TYP_POS) = 0x34. +const GED_SLEEP_CTL_VALUE: u8 = 0x34; +const GED_REG_BASE: u64 = (IO_ADDR_OFFSET | 0x100e_001c) as u64; impl LOONGARCH64 { /// Create an instance. pub const fn new(addr: u64) -> Self { - Self { addr } + Self { + sleep_ctl_addr: addr, + } } } impl QEMUExit for LOONGARCH64 { - fn exit(&self, code: u32) -> ! { - let code_new = match code { - EXIT_SUCCESS | EXIT_FAILURE => code, - _ => exit_code_encode(code), - }; - + fn exit(&self, _code: u32) -> ! { unsafe { asm!( - "st.w {0}, {1}, 0", - in(reg) code_new, - in(reg) self.addr, + "st.b {value}, {addr}, 0", + value = in(reg) GED_SLEEP_CTL_VALUE, + addr = in(reg) self.sleep_ctl_addr, ); loop { asm!("idle 0", options(nomem, nostack)); @@ -96,7 +98,7 @@ impl QEMUExit for LOONGARCH64 { } } -const VIRT_TEST: u64 = (IO_ADDR_OFFSET | 0x1fe0_01e0) as u64; +const VIRT_TEST: u64 = GED_REG_BASE; -/// Global QEMU exit handle using the virt test device. +/// Global QEMU exit handle using the ACPI GED poweroff register. pub const QEMU_EXIT_HANDLE: LOONGARCH64 = LOONGARCH64::new(VIRT_TEST); diff --git a/os/src/drivers/chardev/ns16550a.rs b/os/src/drivers/chardev/ns16550a.rs index 8e20e75c..77c45dc4 100644 --- a/os/src/drivers/chardev/ns16550a.rs +++ b/os/src/drivers/chardev/ns16550a.rs @@ -6,9 +6,7 @@ use bitflags::bitflags; use crate::poll::{notify_poll_source, POLLIN}; use crate::sync::SpinNoIrqLock; use crate::task::WaitQueue; -#[cfg(target_arch = "loongarch64")] use crate::task::yield_current_and_run_next; -#[cfg(not(target_arch = "loongarch64"))] use crate::task::WaitReason; use super::{set_uart_ready, CharDevice}; @@ -235,30 +233,27 @@ impl CharDevice for NS16550a { } drop(inner); - #[cfg(target_arch = "loongarch64")] - { - // LA64 bring-up does not have UART RX interrupts wired through a - // platform IRQ controller yet, so avoid sleeping forever waiting - // for an IRQ wakeup that never comes. Poll cooperatively instead. + if !crate::platform::console_rx_irq_ready() { + // Before the platform IRQ controller is configured, keep a + // cooperative polling fallback so early console input still + // works during bring-up. yield_current_and_run_next(); + continue; } - #[cfg(not(target_arch = "loongarch64"))] - { - // No data: block current task until UART IRQ pushes data and signals. - self.rx_wait_queue - .wait_with_reason_or_skip(WaitReason::UartRx, || { - let mut inner = self.inner.lock(); - if !inner.read_buffer.is_empty() { - return true; - } - if let Some(ch) = inner.ns16550a.try_read() { - inner.read_buffer.push_back(ch); - return true; - } - false - }); - } + // No data: block current task until UART IRQ pushes data and signals. + self.rx_wait_queue + .wait_with_reason_or_skip(WaitReason::UartRx, || { + let mut inner = self.inner.lock(); + if !inner.read_buffer.is_empty() { + return true; + } + if let Some(ch) = inner.ns16550a.try_read() { + inner.read_buffer.push_back(ch); + return true; + } + false + }); } } diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index 36efc4ec..f368098d 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -3,7 +3,6 @@ pub mod block; pub mod chardev; pub mod net; -pub mod pci; pub mod plic; pub mod rtc; pub mod virtio; @@ -17,14 +16,11 @@ fn virtio_blk_name(idx: usize) -> String { pub fn init() { chardev::init(); rtc::init(); + crate::platform::init_external_irq(); #[cfg(target_arch = "riscv64")] { - plic::init(); block::probe_block_devices(); net::probe_net_devices(); } - #[cfg(target_arch = "loongarch64")] - { - pci::probe_virtio_pci_devices(); - } + crate::platform::probe_platform_devices(); } diff --git a/os/src/fs/tty.rs b/os/src/fs/tty.rs index 42a44482..91535f8a 100644 --- a/os/src/fs/tty.rs +++ b/os/src/fs/tty.rs @@ -543,17 +543,12 @@ impl TtyCore { if let Some(byte) = self.take_ready_byte() { return byte; } - #[cfg(target_arch = "loongarch64")] - { - // LA64 bring-up still lacks a working UART RX interrupt path, - // so sleeping on `read_wq` would park the shell and send the - // scheduler into the idle loop forever. Poll cooperatively - // until the platform IRQ controller is wired up. + if !crate::platform::console_rx_irq_ready() { + // Fall back to cooperative polling only before the platform + // external IRQ path has finished setup. crate::task::yield_current_and_run_next(); continue; } - #[cfg(not(target_arch = "loongarch64"))] - { // 3. 阻塞,直到中断路径补满输入或有信号到来。 self.read_wq .wait_with_reason_or_skip(WaitReason::UartRx, || self.read_ready()); @@ -561,7 +556,6 @@ impl TtyCore { if !self.read_ready() && has_interrupting_signal() { return Err(ERRNO::EINTR); } - } } } diff --git a/os/src/loader.rs b/os/src/loader.rs deleted file mode 100644 index e5e7775d..00000000 --- a/os/src/loader.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Loading user applications into memory - -/// Get the total number of applications. -use alloc::vec::Vec; -use lazy_static::*; -///get app number -pub fn get_num_app() -> usize { - extern "C" { - fn _num_app(); - } - unsafe { (_num_app as usize as *const usize).read_volatile() } -} -/// get applications data -pub fn get_app_data(app_id: usize) -> &'static [u8] { - extern "C" { - fn _num_app(); - } - let num_app_ptr = _num_app as usize as *const usize; - let num_app = get_num_app(); - let app_start = unsafe { core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1) }; - assert!(app_id < num_app); - unsafe { - core::slice::from_raw_parts( - app_start[app_id] as *const u8, - app_start[app_id + 1] - app_start[app_id], - ) - } -} - -lazy_static! { - ///All of app's name - static ref APP_NAMES: Vec<&'static str> = { - let num_app = get_num_app(); - extern "C" { - fn _app_names(); - } - let mut start = _app_names as usize as *const u8; - let mut v = Vec::new(); - unsafe { - for _ in 0..num_app { - let mut end = start; - while end.read_volatile() != b'\0' { - end = end.add(1); - } - let slice = core::slice::from_raw_parts(start, end as usize - start as usize); - let str = core::str::from_utf8(slice).unwrap(); - v.push(str); - start = end.add(1); - } - } - v - }; -} - -#[allow(unused)] -///get app data from name -pub fn get_app_data_by_name(name: &str) -> Option<&'static [u8]> { - let num_app = get_num_app(); - (0..num_app) - .find(|&i| APP_NAMES[i] == name) - .map(get_app_data) -} -///list all apps -pub fn list_apps() { - println!("/**** APPS ****"); - for app in APP_NAMES.iter() { - println!("{}", app); - } - println!("**************/"); -} diff --git a/os/src/main.rs b/os/src/main.rs index e7de960c..346ae6a6 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -180,6 +180,8 @@ fn init_local_hart(hart_id: usize) { timer::init_hart(); #[cfg(target_arch = "riscv64")] drivers::plic::init_hart(hart_id); + #[cfg(target_arch = "loongarch64")] + drivers::loongarch_irq::init_hart(hart_id); mm::mark_online(hart_id); debug!("hart {} local init done", hart_id); } @@ -307,15 +309,12 @@ pub fn early_puts(s: &str) { fn first_hart_main(hart_id: usize) -> ! { clear_bss(); BOOT_BSS_READY.store(0, Ordering::Release); - #[cfg(target_arch = "loongarch64")] early_puts("[K] trap::init\r\n"); // Install TLB refill handler and page-walker CSRs before activating page tables. trap::init(); - #[cfg(target_arch = "loongarch64")] early_puts("[K] mm::init\r\n"); mm::init(); - #[cfg(target_arch = "loongarch64")] early_puts("[K] klog::init\r\n"); - #[cfg(not(target_arch = "loongarch64"))] - mm::remap_test(); + // mm::remap_test(); klog::init(); +<<<<<<< HEAD let hart_count = detect_hart_count(); print_boot_splash(hart_id, hart_count); print_boot_stage("memory", "SV39 mappings online"); diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index fc57bdfd..eeae38c2 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -68,17 +68,11 @@ pub use crate::hal::traits::PTEFlags; /// initiate heap allocator, frame allocator and kernel space pub fn init() { - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] frame_allocator\r\n"); frame_allocator::init_frame_allocator(); - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] init_heap\r\n"); heap_allocator::init_heap(); - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] activate\r\n"); KERNEL_SPACE.lock().activate(); - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] heap_mapping\r\n"); heap_allocator::init_kernel_heap_mapping(); - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] heap_virtual_window\r\n"); heap_allocator::init_heap_virtual_window(); - #[cfg(target_arch = "loongarch64")] crate::early_puts("[mm] done\r\n"); } /// 在当前 hart 上激活内核地址空间(写入 satp + sfence.vma)。 diff --git a/os/src/platform/loongarch_virt.rs b/os/src/platform/loongarch_virt.rs index 103cdc27..094885ba 100644 --- a/os/src/platform/loongarch_virt.rs +++ b/os/src/platform/loongarch_virt.rs @@ -1,9 +1,16 @@ //! LoongArch64 QEMU virt platform hooks. +mod irq; +mod pci; + pub use crate::board::{ BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, VIRT_RTC, VIRT_UART, }; +pub use irq::{ + console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, +}; +pub use pci::probe_platform_devices; use crate::hal::traits::{HartCtrl, Timer}; diff --git a/os/src/platform/loongarch_virt/irq.rs b/os/src/platform/loongarch_virt/irq.rs new file mode 100644 index 00000000..d376494c --- /dev/null +++ b/os/src/platform/loongarch_virt/irq.rs @@ -0,0 +1,142 @@ +//! LoongArch64 QEMU `virt` external IRQ routing. +//! +//! This is platform glue rather than generic architecture code: QEMU wires the +//! console UART into the LS7A PCH PIC, then forwards it through EXTIOI onto a +//! CPU hardware interrupt line. + +use core::arch::asm; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::board::IO_ADDR_OFFSET; +use crate::bootstrap_hart_id; +use crate::drivers::chardev::{CharDevice, UART}; + +static UART_IRQ_READY: AtomicBool = AtomicBool::new(false); + +const PCH_PIC_BASE: usize = IO_ADDR_OFFSET | 0x1000_0000; +const PCH_PIC_INT_MASK: usize = 0x20; +const PCH_PIC_HTMSI_VEC: usize = 0x200; + +const EXTIOI_BASE: usize = 0x1400; +const EXTIOI_IPMAP_START: usize = 0x0c0; +const EXTIOI_ENABLE_START: usize = 0x200; +const EXTIOI_COREISR_START: usize = 0x400; +const EXTIOI_COREMAP_START: usize = 0x800; + +const UART0_PCH_IRQ: u32 = 2; +const EXTIOI_ROUTE_IP3: u32 = 0x0808_0808; + +#[inline] +fn mmio_read64(addr: usize) -> u64 { + unsafe { read_volatile(addr as *const u64) } +} + +#[inline] +fn mmio_write64(addr: usize, value: u64) { + unsafe { write_volatile(addr as *mut u64, value) } +} + +#[inline] +fn iocsr_read32(addr: usize) -> u32 { + let value: u32; + unsafe { + asm!( + "iocsrrd.w {value}, {addr}", + value = out(reg) value, + addr = in(reg) addr, + ); + } + value +} + +#[inline] +fn iocsr_write32(addr: usize, value: u32) { + unsafe { + asm!( + "iocsrwr.w {value}, {addr}", + value = in(reg) value, + addr = in(reg) addr, + ); + } +} + +fn init_uart_pch_pic() { + let irq = UART0_PCH_IRQ as usize; + let vec_reg = PCH_PIC_BASE + PCH_PIC_HTMSI_VEC + (irq & !7); + let vec_shift = (irq & 7) * 8; + let mut vectors = mmio_read64(vec_reg); + vectors &= !(0xffu64 << vec_shift); + vectors |= (UART0_PCH_IRQ as u64) << vec_shift; + mmio_write64(vec_reg, vectors); + + let mask = mmio_read64(PCH_PIC_BASE + PCH_PIC_INT_MASK); + mmio_write64(PCH_PIC_BASE + PCH_PIC_INT_MASK, mask & !(1u64 << irq)); +} + +fn init_uart_extioi() { + let target_hart = bootstrap_hart_id().min(3); + let cpu_bit = 1u32 << target_hart; + + for reg in 0..2 { + iocsr_write32(EXTIOI_BASE + EXTIOI_IPMAP_START + reg * 4, EXTIOI_ROUTE_IP3); + } + + let coremap_word = cpu_bit | (cpu_bit << 8) | (cpu_bit << 16) | (cpu_bit << 24); + for reg in 0..64 { + iocsr_write32(EXTIOI_BASE + EXTIOI_COREMAP_START + reg * 4, coremap_word); + } + + iocsr_write32(EXTIOI_BASE + EXTIOI_COREISR_START, 1u32 << UART0_PCH_IRQ); + let enable = iocsr_read32(EXTIOI_BASE + EXTIOI_ENABLE_START); + iocsr_write32( + EXTIOI_BASE + EXTIOI_ENABLE_START, + enable | (1u32 << UART0_PCH_IRQ), + ); +} + +/// Initialize platform external interrupt routing on the bootstrap hart. +pub fn init_external_irq() { + init_uart_extioi(); + init_uart_pch_pic(); + UART_IRQ_READY.store(true, Ordering::Release); + info!( + "[irq] loongarch uart IRQ enabled on hart {}", + bootstrap_hart_id().min(3) + ); +} + +/// Initialize per-hart external interrupt state. +pub fn init_external_irq_hart(_hart_id: usize) {} + +/// Whether the console RX interrupt path is ready for blocking reads. +pub fn console_rx_irq_ready() -> bool { + UART_IRQ_READY.load(Ordering::Acquire) +} + +/// Dispatch one platform external interrupt. +pub fn handle_external_irq() { + if !console_rx_irq_ready() { + return; + } + + let pending = iocsr_read32(EXTIOI_BASE + EXTIOI_COREISR_START); + let uart_mask = 1u32 << UART0_PCH_IRQ; + let mut clear_mask = 0u32; + + if pending & uart_mask != 0 { + UART.handle_irq(); + crate::fs::console_receive(); + clear_mask |= uart_mask; + } + + let unexpected = pending & !clear_mask; + if unexpected != 0 { + warn!("[irq] loongarch unexpected EXTIOI pending bits {:#x}", unexpected); + clear_mask |= unexpected; + } + + if clear_mask != 0 { + iocsr_write32(EXTIOI_BASE + EXTIOI_COREISR_START, clear_mask); + } +} diff --git a/os/src/drivers/pci.rs b/os/src/platform/loongarch_virt/pci.rs similarity index 86% rename from os/src/drivers/pci.rs rename to os/src/platform/loongarch_virt/pci.rs index fcf4f9d2..20350ede 100644 --- a/os/src/drivers/pci.rs +++ b/os/src/platform/loongarch_virt/pci.rs @@ -1,10 +1,6 @@ -//! LoongArch64 PCI/ECAM probing for VirtIO PCI devices. +//! LoongArch64 QEMU `virt` PCI/ECAM probing for VirtIO PCI devices. -#[cfg(target_arch = "loongarch64")] use alloc::{string::String, sync::Arc}; -#[cfg(target_arch = "loongarch64")] -use core::convert::TryFrom; -#[cfg(target_arch = "loongarch64")] use virtio_drivers::transport::{ pci::{ bus::{ @@ -16,30 +12,21 @@ use virtio_drivers::transport::{ DeviceType, SomeTransport, Transport, }; -#[cfg(target_arch = "loongarch64")] use crate::board::IO_ADDR_OFFSET; -#[cfg(target_arch = "loongarch64")] use crate::drivers::{ block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, net::{self, VirtIONetDevice}, }; -#[cfg(target_arch = "loongarch64")] const PCI_ECAM_BASE: usize = 0x2000_0000; -#[cfg(target_arch = "loongarch64")] const PCI_ECAM_SIZE: usize = 0x1000_0000; -#[cfg(target_arch = "loongarch64")] const PCI_RANGE_BASE: usize = 0x4000_0000; -#[cfg(target_arch = "loongarch64")] const PCI_RANGE_SIZE: usize = 0x4000_0000; -#[cfg(target_arch = "loongarch64")] const PCI_BUS_END: u8 = 0x7f; -#[cfg(target_arch = "loongarch64")] const LEGACY_VIRTIO_NET_IRQ: u32 = 1; -#[cfg(target_arch = "loongarch64")] /// Probe the LA64 PCIe ECAM bus and register VirtIO PCI devices. -pub fn probe_virtio_pci_devices() { +pub fn probe_platform_devices() { let ecam_vaddr = PCI_ECAM_BASE | IO_ADDR_OFFSET; let ecam_end = ecam_vaddr + PCI_ECAM_SIZE; if ecam_end < ecam_vaddr { @@ -66,7 +53,7 @@ pub fn probe_virtio_pci_devices() { continue; } - let Some(mut transport) = probe_virtio_pci_device(&mut root, bdf, &dev_info) else { + let Some(transport) = probe_virtio_pci_device(&mut root, bdf, &dev_info) else { continue; }; @@ -121,18 +108,12 @@ pub fn probe_virtio_pci_devices() { } } -#[cfg(not(target_arch = "loongarch64"))] -/// No-op stub so non-LA64 builds do not pull in PCI probing logic. -pub fn probe_virtio_pci_devices() {} - -#[cfg(target_arch = "loongarch64")] #[derive(Debug)] struct PciRangeAllocator { end: u64, current: u64, } -#[cfg(target_arch = "loongarch64")] impl PciRangeAllocator { const fn new(base: u64, size: u64) -> Self { Self { @@ -154,12 +135,10 @@ impl PciRangeAllocator { } } -#[cfg(target_arch = "loongarch64")] const fn align_up(addr: u64, align: u64) -> u64 { (addr + align - 1) & !(align - 1) } -#[cfg(target_arch = "loongarch64")] fn configure_pci_device( root: &mut PciRoot>, bdf: DeviceFunction, @@ -198,7 +177,6 @@ fn configure_pci_device( Ok(()) } -#[cfg(target_arch = "loongarch64")] fn probe_virtio_pci_device( root: &mut PciRoot>, bdf: DeviceFunction, diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index f018c62f..23c3095f 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -6,3 +6,15 @@ pub mod qemu_virt; #[cfg(target_arch = "loongarch64")] pub mod loongarch_virt; + +#[cfg(target_arch = "riscv64")] +pub use qemu_virt::{ + console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, + probe_platform_devices, +}; + +#[cfg(target_arch = "loongarch64")] +pub use loongarch_virt::{ + console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, + probe_platform_devices, +}; diff --git a/os/src/platform/qemu_virt/mod.rs b/os/src/platform/qemu_virt/mod.rs index 49e2d3de..92aa5391 100644 --- a/os/src/platform/qemu_virt/mod.rs +++ b/os/src/platform/qemu_virt/mod.rs @@ -7,3 +7,26 @@ pub use crate::board::{ VIRT_RTC, VIRT_UART, }; pub use sbi::SbiPlatform; + +/// Initialize platform external interrupt routing on the bootstrap hart. +pub fn init_external_irq() { + crate::drivers::plic::init(); +} + +/// Initialize per-hart external interrupt state. +pub fn init_external_irq_hart(hart_id: usize) { + crate::drivers::plic::init_hart(hart_id); +} + +/// Dispatch one platform external interrupt. +pub fn handle_external_irq() { + crate::drivers::plic::handle_supervisor_external(); +} + +/// Whether the console RX interrupt path is ready for blocking reads. +pub fn console_rx_irq_ready() -> bool { + true +} + +/// Probe platform-specific devices after generic driver init. +pub fn probe_platform_devices() {} diff --git a/os/src/platform/qemu_virt/sbi.rs b/os/src/platform/qemu_virt/sbi.rs index d7faa2d4..e152244f 100644 --- a/os/src/platform/qemu_virt/sbi.rs +++ b/os/src/platform/qemu_virt/sbi.rs @@ -1,7 +1,7 @@ //! SBI platform for QEMU virt — implements Timer and HartCtrl HAL traits. pub use crate::sbi::{ - console_getchar, console_putchar, hart_get_status, hart_start, hart_state, send_ipi_mask, + hart_get_status, hart_start, hart_state, send_ipi_mask, set_timer, shutdown, HartState, SbiRet, }; diff --git a/os/src/sched/processor.rs b/os/src/sched/processor.rs index ccb9948b..5da42c09 100644 --- a/os/src/sched/processor.rs +++ b/os/src/sched/processor.rs @@ -129,12 +129,9 @@ pub(crate) fn run_tasks() { } // debug!("No task to run, idle..."); - #[cfg(target_arch = "loongarch64")] - { - // LA64 bring-up does not have a working UART RX interrupt path - // yet, so drain the console UART opportunistically from the - // idle loop. This preserves echo/canonical processing and wakes - // blocked tty readers once a full line arrives. + if !crate::platform::console_rx_irq_ready() { + // Keep the old cooperative polling path only as a pre-init + // fallback before the EXTIOI/PCH-PIC chain is configured. crate::fs::console_receive(); } diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index d1f3d9e0..deb0e7b3 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -197,24 +197,6 @@ pub fn trap_handler() -> ! { current_trap_cx().in_syscall = false; current_trap_cx().restartable_syscall = false; let trap_info = ArchTrapMachine::read_trap_info(); - #[cfg(target_arch = "loongarch64")] - { - static mut FIRST_USER_TRAP_LOGGED: bool = false; - unsafe { - if !FIRST_USER_TRAP_LOGGED { - FIRST_USER_TRAP_LOGGED = true; - let cx = current_trap_cx(); - early_put_hex("[user_trap] cause=", trap_info.cause as usize); - early_put_hex("[user_trap] fault_addr=", trap_info.fault_addr); - early_put_hex("[user_trap] user_pc=", cx.user_pc()); - early_put_hex("[user_trap] sp=", cx.user_sp()); - early_put_hex("[user_trap] ra=", cx.ra()); - early_put_hex("[user_trap] tp=", cx.tls()); - early_put_hex("[user_trap] a0=", cx.reg(4)); - early_put_hex("[user_trap] a7=", cx.syscall_nr()); - } - } - } match trap_info.cause { TrapCause::UserSyscall => { // jump to next instruction anyway @@ -395,30 +377,10 @@ pub fn trap_handler() -> ! { handle_reschedule_ipi(); } TrapCause::ExternalInterrupt => { - #[cfg(target_arch = "riscv64")] - crate::drivers::plic::handle_supervisor_external(); + crate::platform::handle_external_irq(); crate::net::poll(); } _ => { - #[cfg(target_arch = "loongarch64")] - { - let estat = crate::hal::ArchTrapMachine::read_trap_info(); - let raw_estat = { - let value: usize; - unsafe { core::arch::asm!("csrrd {}, {}", out(reg) value, const 0x5usize) }; - value - }; - let badi = { - let value: usize; - unsafe { core::arch::asm!("csrrd {}, {}", out(reg) value, const 0x8usize) }; - value - }; - early_put_hex("[user_trap] raw_estat=", raw_estat); - early_put_hex("[user_trap] ecode=", (raw_estat >> 16) & 0x3f); - early_put_hex("[user_trap] esubcode=", (raw_estat >> 22) & 0x1ff); - early_put_hex("[user_trap] badi=", badi); - early_put_hex("[user_trap] fault_addr_dup=", estat.fault_addr); - } panic!( "Unsupported trap {:?}, fault_addr = {:#x}!", trap_info.cause, @@ -454,50 +416,6 @@ pub fn trap_return() -> ! { let trap_cx_user_va = current_trap_cx_user_va(); current_trap_cx().set_kernel_hartid(hartid()); let user_token = current_user_token(); - #[cfg(target_arch = "loongarch64")] - { - let current_token = unsafe { crate::hal::current_address_space_token() }; - let cx = current_trap_cx(); - let sp: usize; - unsafe { - core::arch::asm!("move {}, $sp", out(reg) sp); - } - let sp_vpn = crate::mm::VirtAddr::from(sp & !(PAGE_SIZE - 1)).floor(); - let trap_cx_vpn = crate::mm::VirtAddr::from(trap_cx_user_va).floor(); - let trampoline_vpn = crate::mm::VirtAddr::from(crate::config::TRAMPOLINE).floor(); - let kernel_space = crate::mm::KERNEL_SPACE.lock(); - let user_page_table = crate::mm::PageTable::from_token(user_token); - // early_put_hex("[trap_return] current_pgdl=", current_token); - // early_put_hex("[trap_return] user_pgdl=", user_token); - // early_put_hex("[trap_return] trap_cx_user_va=", trap_cx_user_va); - // early_put_hex("[trap_return] user_prmd=", cx.arch.prmd); - // early_put_hex("[trap_return] user_era=", cx.arch.era); - // early_put_hex("[trap_return] user_ra=", cx.arch.r[1]); - // early_put_hex("[trap_return] user_sp_saved=", cx.arch.r[3]); - // early_put_hex("[trap_return] user_a0=", cx.arch.r[4]); - // early_put_hex("[trap_return] user_a7=", cx.arch.r[11]); - // early_put_hex("[trap_return] sp=", sp); - if let Some(pte) = kernel_space.translate(sp_vpn) { - // early_put_hex("[trap_return] kstack_pte=", pte.bits); - } else { - // crate::early_puts("[trap_return] kstack_pte=NONE\r\n"); - } - if let Some(pte) = kernel_space.translate(trampoline_vpn) { - // early_put_hex("[trap_return] tramp_pte=", pte.bits); - } else { - // crate::early_puts("[trap_return] tramp_pte=NONE\r\n"); - } - if let Some(pte) = user_page_table.translate(trap_cx_vpn) { - // early_put_hex("[trap_return] user_trap_cx_pte=", pte.bits); - } else { - // crate::early_puts("[trap_return] user_trap_cx_pte=NONE\r\n"); - } - if let Some(pte) = user_page_table.translate(trampoline_vpn) { - // early_put_hex("[trap_return] user_tramp_pte=", pte.bits); - } else { - // crate::early_puts("[trap_return] user_tramp_pte=NONE\r\n"); - } - } current_process().enter_user(get_time()); unsafe { ArchTrapMachine::return_to_user(trap_cx_user_va, user_token) } } @@ -507,23 +425,8 @@ pub fn trap_return() -> ! { pub fn trap_from_kernel() { let trap_info = ArchTrapMachine::read_trap_info(); match trap_info.cause { - #[cfg(target_arch = "loongarch64")] - TrapCause::StorePageFault | TrapCause::LoadPageFault => { - dump_kernel_trap_state(); - let page_base = trap_info.fault_addr & !(PAGE_SIZE - 1); - let heap_end = KERNEL_HEAP_BASE + MAX_KERNEL_HEAP_SIZE; - if (KERNEL_HEAP_BASE..heap_end).contains(&page_base) && map_one_heap_page(page_base) { - return; - } - panic!( - "Kernel trap: {:?}, fault_addr = {:#x}", - trap_info.cause, - trap_info.fault_addr - ); - } TrapCause::ExternalInterrupt => { - #[cfg(target_arch = "riscv64")] - crate::drivers::plic::handle_supervisor_external(); + crate::platform::handle_external_irq(); crate::net::poll(); // 处理完外部中断后立即poll,让smoltcp响应ARP等请求 } TrapCause::TimerInterrupt => { @@ -538,14 +441,11 @@ pub fn trap_from_kernel() { // user-mode ticks. on_timer_tick(); } - // crate::net::poll(); } TrapCause::SoftwareInterrupt => { handle_reschedule_ipi(); } _ => { - #[cfg(target_arch = "loongarch64")] - dump_kernel_trap_state(); panic!( "Kernel trap: {:?}, fault_addr = {:#x}", trap_info.cause, From 2bdbf60144e182541245d822b3c0c77e59026984 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jun 2026 22:02:53 +0800 Subject: [PATCH 12/19] refactor: extract platform-related code to HAL. --- os/src/arch/loongarch64/paging.rs | 24 +++ os/src/arch/mod.rs | 6 +- os/src/arch/riscv/paging.rs | 4 + os/src/config.rs | 16 +- os/src/console.rs | 9 +- os/src/drivers/block/mod.rs | 4 +- os/src/drivers/chardev/mod.rs | 2 +- os/src/drivers/mod.rs | 12 +- os/src/drivers/net/mod.rs | 4 +- os/src/drivers/rtc.rs | 15 +- os/src/drivers/virtio/mod.rs | 24 +-- os/src/hal/mod.rs | 36 +++- os/src/hal/traits.rs | 20 +++ os/src/main.rs | 97 +---------- os/src/mm/address.rs | 32 +--- os/src/mm/heap_allocator.rs | 28 +-- os/src/mm/memory_set.rs | 23 +-- os/src/mm/page_table.rs | 12 +- os/src/platform/loongarch/mod.rs | 6 + .../qemu_virt}/irq.rs | 3 +- os/src/platform/loongarch/qemu_virt/mod.rs | 124 +++++++++++++ .../qemu_virt}/pci.rs | 3 +- os/src/platform/loongarch_virt.rs | 42 ----- os/src/platform/mod.rs | 44 ++++- os/src/platform/qemu_virt/mod.rs | 32 ---- os/src/platform/riscv/mod.rs | 6 + os/src/platform/riscv/qemu_virt/mod.rs | 163 ++++++++++++++++++ os/src/platform/{ => riscv}/qemu_virt/sbi.rs | 15 +- os/src/sbi.rs | 62 +++---- os/src/syscall/process.rs | 5 +- os/src/task/mod.rs | 6 +- os/src/trap/mod.rs | 38 ---- 32 files changed, 512 insertions(+), 405 deletions(-) create mode 100644 os/src/platform/loongarch/mod.rs rename os/src/platform/{loongarch_virt => loongarch/qemu_virt}/irq.rs (97%) create mode 100644 os/src/platform/loongarch/qemu_virt/mod.rs rename os/src/platform/{loongarch_virt => loongarch/qemu_virt}/pci.rs (98%) delete mode 100644 os/src/platform/loongarch_virt.rs delete mode 100644 os/src/platform/qemu_virt/mod.rs create mode 100644 os/src/platform/riscv/mod.rs create mode 100644 os/src/platform/riscv/qemu_virt/mod.rs rename os/src/platform/{ => riscv}/qemu_virt/sbi.rs (67%) diff --git a/os/src/arch/loongarch64/paging.rs b/os/src/arch/loongarch64/paging.rs index 0a2b5d55..d8aa8025 100644 --- a/os/src/arch/loongarch64/paging.rs +++ b/os/src/arch/loongarch64/paging.rs @@ -129,6 +129,30 @@ impl PagingArch for LoongArchPaging { flags } + fn pte_is_valid(entry_bits: usize) -> bool { + entry_bits != 0 + } + + fn normalize_leaf_flags(mut flags: PTEFlags) -> PTEFlags { + flags.insert(PTEFlags::A); + if flags.contains(PTEFlags::W) { + flags.insert(PTEFlags::D); + } + flags + } + + fn normalize_virt_addr_input(bits: usize) -> usize { + bits + } + + fn virt_page_num_from_addr(bits: usize) -> usize { + bits >> 12 + } + + fn trap_context_flags() -> PTEFlags { + PTEFlags::R | PTEFlags::W | PTEFlags::U + } + fn vpn_index(vpn: usize, level: usize) -> usize { debug_assert!(level < Self::LEVELS); let mask = (1usize << Self::INDEX_BITS) - 1; diff --git a/os/src/arch/mod.rs b/os/src/arch/mod.rs index d4920fbd..21293e20 100644 --- a/os/src/arch/mod.rs +++ b/os/src/arch/mod.rs @@ -1,4 +1,8 @@ -//! Architecture-specific implementations. +//! Architecture-specific CPU and privilege-architecture support. +//! +//! This layer owns ISA-defined mechanisms such as traps, CSR access, paging +//! formats, context-switch ABI, and hart-local interrupt control. It does not +//! know which board or machine model wires devices onto that CPU. #[cfg(target_arch = "riscv64")] pub mod riscv; diff --git a/os/src/arch/riscv/paging.rs b/os/src/arch/riscv/paging.rs index 7ff6f6c6..760c6577 100644 --- a/os/src/arch/riscv/paging.rs +++ b/os/src/arch/riscv/paging.rs @@ -49,6 +49,10 @@ impl PagingArch for Sv39Paging { PTEFlags::from_bits_truncate(entry_bits as u16) } + fn normalize_virt_addr_input(bits: usize) -> usize { + bits & ((1usize << Self::VA_BITS) - 1) + } + fn vpn_index(vpn: usize, level: usize) -> usize { debug_assert!(level < Self::LEVELS); let mask = (1usize << Self::INDEX_BITS) - 1; diff --git a/os/src/config.rs b/os/src/config.rs index 1157f5fe..629729e1 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -8,13 +8,6 @@ pub const USER_STACK_SIZE: usize = 1024 * 512; // 512 KiB pub const KERNEL_STACK_SIZE: usize = 4096 * 32; /// kernel heap size pub const MAX_KERNEL_HEAP_SIZE: usize = 0x4000_0000; -/// base address of the dynamically mapped kernel heap window -#[cfg(target_arch = "riscv64")] -pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; -/// LoongArch64: heap window in the low-half (bit 38 = 0) so PGDL is used by -/// the hardware TLB walker. Placed far above user VAs (USER_SPACE_END=2^38). -#[cfg(target_arch = "loongarch64")] -pub const KERNEL_HEAP_BASE: usize = 0x0000_0038_0000_0000; /// max harts reserved by the kernel SMP bootstrap path pub const MAX_HARTS: usize = 8; /// QEMU virt 1GiB 内存的物理结束地址,起始地址为 0x8000_0000。 @@ -32,13 +25,6 @@ pub const USER_STACK_BASE: usize = 0x0800_0000; /// base address for loading dynamic linker (interpreter) /// placed between stack and mmap region to avoid conflicts pub const INTERP_BASE: usize = 0x4000_0000; -/// the virtual addr of trapoline -#[cfg(not(target_arch = "loongarch64"))] -pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; -/// LoongArch64 keeps trap trampoline and per-task kernel stacks in the low half -/// so the current PGDL-only address-space activation can cover them. -#[cfg(target_arch = "loongarch64")] -pub const TRAMPOLINE: usize = 0x0000_003f_ffff_f000; /// the virtual addr of trap context pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; /// 用户态 signal trampoline 页起始地址。 @@ -46,4 +32,4 @@ pub const USER_VDSO_BASE: usize = USER_MMAP_BASE - PAGE_SIZE; /// 用户态 rt_sigreturn trampoline 入口地址。 pub const USER_VDSO_RT_SIGRETURN: usize = USER_VDSO_BASE; /// qemu board info -pub use crate::board::{CLOCK_FREQ, MMIO}; +pub use crate::platform::{CLOCK_FREQ, KERNEL_HEAP_BASE, MMIO, TRAMPOLINE}; diff --git a/os/src/console.rs b/os/src/console.rs index 0a4ec7de..5cab489b 100644 --- a/os/src/console.rs +++ b/os/src/console.rs @@ -1,5 +1,5 @@ //! Kernel console output helpers. -use crate::drivers::chardev::{uart_ready, CharDevice, UART}; +use crate::drivers::chardev::{CharDevice, UART}; use core::fmt::{self, Write}; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, Ordering}; @@ -20,13 +20,11 @@ impl Write for Stdout { } } -#[cfg(target_arch = "loongarch64")] struct EarlyStdout; -#[cfg(target_arch = "loongarch64")] impl Write for EarlyStdout { fn write_str(&mut self, s: &str) -> fmt::Result { - crate::early_puts(s); + crate::platform::early_console_write(s); Ok(()) } } @@ -67,8 +65,7 @@ impl Drop for ConsoleGuard { /// print to the host console using the format string and arguments. pub fn print(args: fmt::Arguments) { let _guard = ConsoleGuard::lock(); - #[cfg(target_arch = "loongarch64")] - if !uart_ready() { + if crate::platform::use_early_console() { EarlyStdout.write_fmt(args).unwrap(); return; } diff --git a/os/src/drivers/block/mod.rs b/os/src/drivers/block/mod.rs index 1eaaeef1..ca01900f 100644 --- a/os/src/drivers/block/mod.rs +++ b/os/src/drivers/block/mod.rs @@ -16,7 +16,9 @@ use virtio_drivers::transport::{ DeviceType, SomeTransport, mmio::{MmioTransport, VirtIOHeader}, }; -use crate::board::{VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE}; +use crate::platform::{ + VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; fn virtio_blk_name(idx: usize) -> String { alloc::format!("vd{}", (b'a' + idx as u8) as char) diff --git a/os/src/drivers/chardev/mod.rs b/os/src/drivers/chardev/mod.rs index 8b0c8264..bb425661 100644 --- a/os/src/drivers/chardev/mod.rs +++ b/os/src/drivers/chardev/mod.rs @@ -4,7 +4,7 @@ use alloc::sync::Arc; use core::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; -use crate::board::CharDeviceImpl; +use crate::platform::CharDeviceImpl; mod ns16550a; diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index f368098d..dfeb604a 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,4 +1,9 @@ -//! block device driver +//! Reusable device drivers. +//! +//! Drivers implement one device IP block or transport protocol such as +//! NS16550A, VirtIO, or Goldfish RTC. They should stay as platform-agnostic as +//! practical; the `platform` layer is responsible for deciding which drivers +//! are instantiated and how their MMIO ranges and IRQs are routed. pub mod block; pub mod chardev; @@ -17,10 +22,5 @@ pub fn init() { chardev::init(); rtc::init(); crate::platform::init_external_irq(); - #[cfg(target_arch = "riscv64")] - { - block::probe_block_devices(); - net::probe_net_devices(); - } crate::platform::probe_platform_devices(); } diff --git a/os/src/drivers/net/mod.rs b/os/src/drivers/net/mod.rs index f2a3670e..6d5da163 100644 --- a/os/src/drivers/net/mod.rs +++ b/os/src/drivers/net/mod.rs @@ -11,7 +11,9 @@ use virtio_drivers::transport::{ mmio::{MmioTransport, VirtIOHeader}, }; -use crate::board::{VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE}; +use crate::platform::{ + VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; use crate::sync::SpinNoIrqLock; pub use virtio_net::VirtIONetDevice; diff --git a/os/src/drivers/rtc.rs b/os/src/drivers/rtc.rs index 1c2697c4..c19bf30a 100644 --- a/os/src/drivers/rtc.rs +++ b/os/src/drivers/rtc.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; use alloc::sync::Arc; use lazy_static::lazy_static; -use crate::board::VIRT_RTC; +use crate::platform::VIRT_RTC; use crate::sync::SpinNoIrqLock; /// Goldfish RTC 时间低 32 位寄存器。 @@ -132,17 +132,12 @@ lazy_static! { /// 标记 RTC 是否已完成初始化。 static RTC_READY: AtomicBool = AtomicBool::new(false); -#[cfg(target_arch = "loongarch64")] -/// 初始化全局 RTC 驱动。 -pub fn init() { - // QEMU loongarch64 virt does not currently expose the Goldfish RTC MMIO - // block at the RISC-V-compatible address we use elsewhere. - warn!("rtc init skipped on loongarch64 virt"); -} - -#[cfg(not(target_arch = "loongarch64"))] /// 初始化全局 RTC 驱动。 pub fn init() { + if !crate::platform::rtc_is_supported() { + warn!("rtc init skipped on this platform"); + return; + } let rtc = RTC.lock(); rtc.init(); let time_ns = rtc.read_time_ns(); diff --git a/os/src/drivers/virtio/mod.rs b/os/src/drivers/virtio/mod.rs index 2a721016..8f4ecf10 100644 --- a/os/src/drivers/virtio/mod.rs +++ b/os/src/drivers/virtio/mod.rs @@ -35,16 +35,8 @@ struct BounceMapping { #[inline] fn translate_kernel_va(va: usize) -> usize { - #[cfg(target_arch = "loongarch64")] - { - use crate::board::{IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET}; - - if va & KERNEL_ADDR_OFFSET == KERNEL_ADDR_OFFSET { - return va & !KERNEL_ADDR_OFFSET; - } - if va & IO_ADDR_OFFSET == IO_ADDR_OFFSET { - return va & !IO_ADDR_OFFSET; - } + if let Some(pa) = crate::platform::translate_direct_mapped_kernel_va(va) { + return pa; } PageTable::from_token(kernel_token()) @@ -155,16 +147,8 @@ unsafe impl Hal for VirtioHal { } unsafe fn mmio_phys_to_virt(paddr: VirtioPhysAddr, _size: usize) -> NonNull { - #[cfg(target_arch = "loongarch64")] - { - use crate::board::IO_ADDR_OFFSET; - return NonNull::new((paddr as usize | IO_ADDR_OFFSET) as *mut u8) - .expect("virtio mmio_phys_to_virt: null"); - } - #[cfg(not(target_arch = "loongarch64"))] - { - NonNull::new(paddr as usize as *mut u8).expect("virtio mmio_phys_to_virt: null") - } + NonNull::new(crate::platform::mmio_phys_to_virt(paddr as usize) as *mut u8) + .expect("virtio mmio_phys_to_virt: null") } unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> VirtioPhysAddr { diff --git a/os/src/hal/mod.rs b/os/src/hal/mod.rs index de497371..e5d27544 100644 --- a/os/src/hal/mod.rs +++ b/os/src/hal/mod.rs @@ -19,11 +19,7 @@ pub use crate::arch::loongarch64::{ LoongArchTrapMachine as ArchTrapMachine, LoongArchPaging as ArchPaging, }; -#[cfg(target_arch = "riscv64")] -pub use crate::platform::qemu_virt::SbiPlatform as Plat; - -#[cfg(target_arch = "loongarch64")] -pub use crate::platform::loongarch_virt::LoongArchPlatform as Plat; +pub use crate::platform::PlatformImpl as Plat; /// Return the current hart id. #[inline] @@ -129,6 +125,36 @@ pub fn pte_flags(entry_bits: usize) -> PTEFlags { ArchPaging::pte_flags(entry_bits) } +/// Return whether one raw PTE/directory entry should be treated as present. +#[inline] +pub fn pte_is_valid(entry_bits: usize) -> bool { + ArchPaging::pte_is_valid(entry_bits) +} + +/// Normalize leaf PTE flags for the current architecture. +#[inline] +pub fn normalize_leaf_pte_flags(flags: PTEFlags) -> PTEFlags { + ArchPaging::normalize_leaf_flags(flags) +} + +/// Convert an arbitrary raw virtual address input into the stored form. +#[inline] +pub fn normalize_virt_addr_input(bits: usize) -> usize { + ArchPaging::normalize_virt_addr_input(bits) +} + +/// Convert an arbitrary raw virtual address input into a virtual page number. +#[inline] +pub fn virt_page_num_from_addr(bits: usize) -> usize { + ArchPaging::virt_page_num_from_addr(bits) +} + +/// Return the default trap-context page permissions for this architecture. +#[inline] +pub fn trap_context_flags() -> PTEFlags { + ArchPaging::trap_context_flags() +} + /// Return the architecture's physical address width in bits. #[inline] pub const fn phys_addr_bits() -> usize { diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index 1f96caad..1c0d34bc 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -203,6 +203,26 @@ pub trait PagingArch { fn pte_ppn(entry_bits: usize) -> usize; /// Extract the semantic flags from one raw PTE. fn pte_flags(entry_bits: usize) -> PTEFlags; + /// Return whether one raw PTE/directory entry should be treated as present. + fn pte_is_valid(entry_bits: usize) -> bool { + (Self::pte_flags(entry_bits) & PTEFlags::V) != PTEFlags::empty() + } + /// Normalize leaf PTE flags for the current architecture. + fn normalize_leaf_flags(flags: PTEFlags) -> PTEFlags { + flags + } + /// Convert an arbitrary raw virtual address input into the stored form. + fn normalize_virt_addr_input(bits: usize) -> usize { + bits & ((1usize << Self::VA_BITS) - 1) + } + /// Convert an arbitrary raw virtual address input into a virtual page number. + fn virt_page_num_from_addr(bits: usize) -> usize { + Self::normalize_virt_addr_input(bits) >> 12 + } + /// Return the default trap-context page permissions for this architecture. + fn trap_context_flags() -> PTEFlags { + PTEFlags::R | PTEFlags::W + } /// Return the exclusive end of the canonical low-half user address range. fn user_space_end() -> usize { 1usize << (Self::VA_BITS - 1) diff --git a/os/src/main.rs b/os/src/main.rs index 346ae6a6..e1a23a95 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -33,14 +33,6 @@ extern crate alloc; #[macro_use] extern crate bitflags; -#[cfg(target_arch = "riscv64")] -#[path = "boards/qemu.rs"] -mod board; - -#[cfg(target_arch = "loongarch64")] -#[path = "boards/loongarch_virt.rs"] -mod board; - pub mod arch; pub mod hal; pub mod platform; @@ -178,10 +170,7 @@ fn print_boot_stage(stage: &str, detail: &str) { fn init_local_hart(hart_id: usize) { trap::init_hart(); timer::init_hart(); - #[cfg(target_arch = "riscv64")] - drivers::plic::init_hart(hart_id); - #[cfg(target_arch = "loongarch64")] - drivers::loongarch_irq::init_hart(hart_id); + crate::platform::init_external_irq_hart(hart_id); mm::mark_online(hart_id); debug!("hart {} local init done", hart_id); } @@ -203,72 +192,6 @@ fn detect_hart_count() -> usize { hart_count.max(1) } -#[cfg(target_arch = "riscv64")] -/// 记录当前环境下各 hart 的 HSM 状态,并尝试拉起处于 stopped 状态的 hart。 -/// -/// 这里的目标不是“盲目对所有 hart 重复 `hart_start`”,而是先看清固件报告的 -/// 状态,再只对明确处于 `Stopped` 的 hart 发起启动请求。 -fn probe_and_start_other_harts(bootstrap_hart_id: usize) { - extern "C" { - fn _start(); - } - - const SBI_SUCCESS: isize = 0; - const SBI_ERR_INVALID_PARAM: isize = -3; - const SBI_ERR_ALREADY_AVAILABLE: isize = -6; - - info!( - "hart {} entering HSM probe/start loop", - bootstrap_hart_id - ); - - for target_hart in 0..config::MAX_HARTS { - let status = sbi::hart_get_status(target_hart); - if status.error == SBI_ERR_INVALID_PARAM { - info!( - "hart {} got invalid hart id while probing hart {}, stop scan", - bootstrap_hart_id, target_hart - ); - break; - } - if status.error != SBI_SUCCESS { - info!( - "hart {} HSM status query for hart {} failed: error={}, value={}", - bootstrap_hart_id, target_hart, status.error, status.value - ); - continue; - } - - let state = sbi::hart_state(status.value); - info!( - "hart {} sees hart {} in HSM state {:?}", - bootstrap_hart_id, target_hart, state - ); - - if target_hart == bootstrap_hart_id { - continue; - } - - if let sbi::HartState::Stopped = state { - let ret = sbi::hart_start(target_hart, _start as usize, 0); - match ret.error { - SBI_SUCCESS => info!( - "hart {} requested startup for hart {}", - bootstrap_hart_id, target_hart - ), - SBI_ERR_ALREADY_AVAILABLE => info!( - "hart {} found hart {} already available while starting", - bootstrap_hart_id, target_hart - ), - error => info!( - "hart {} failed to start hart {}: error={}, value={}", - bootstrap_hart_id, target_hart, error, ret.value - ), - } - } - } -} - /// 竞争并记录负责一次性全局初始化的 bootstrap hart。 /// /// 返回值为 `true` 表示当前 hart 抢到了 bootstrap 角色;返回 `false` @@ -293,18 +216,6 @@ fn wait_for_bootstrap() { } } -/// Bootstrap-phase bare UART write (DMW0 window, before drivers::init). -#[cfg(target_arch = "loongarch64")] -pub fn early_puts(s: &str) { - const UART: usize = 0x8000_0000_1fe0_01e0; - for b in s.bytes() { - unsafe { - while core::ptr::read_volatile((UART + 5) as *const u8) & 0x20 == 0 {} - core::ptr::write_volatile(UART as *mut u8, b); - } - } -} - /// bootstrap hart 的主入口 fn first_hart_main(hart_id: usize) -> ! { clear_bss(); @@ -314,7 +225,6 @@ fn first_hart_main(hart_id: usize) -> ! { mm::init(); // mm::remap_test(); klog::init(); -<<<<<<< HEAD let hart_count = detect_hart_count(); print_boot_splash(hart_id, hart_count); print_boot_stage("memory", "SV39 mappings online"); @@ -328,7 +238,7 @@ fn first_hart_main(hart_id: usize) -> ! { print_boot_stage("storage", "root filesystem mounted"); timer::init_realtime_offset_from_rtc(); print_boot_stage("clock", "realtime source calibrated"); - probe_and_start_other_harts(hart_id); + crate::platform::start_secondary_harts(hart_id); init_local_hart(hart_id); print_boot_stage("scheduler", "bootstrap hart entering run queue"); task::add_initproc(); @@ -360,9 +270,6 @@ fn secondary_hart_main(hart_id: usize) -> ! { /// 并进入调度器;其他 hart 等待 bootstrap 完成后只做本地初始化并进入 idle。 pub fn rust_main(hart_id: usize) -> ! { unsafe { crate::hal::init_with_hartid(hart_id) }; - unsafe { - riscv::register::sstatus::set_fs(riscv::register::mstatus::FS::Initial); - } if !try_claim_bootstrap_hart(hart_id) { secondary_hart_main(hart_id); } else { diff --git a/os/src/mm/address.rs b/os/src/mm/address.rs index 14269ef1..aa23a35e 100644 --- a/os/src/mm/address.rs +++ b/os/src/mm/address.rs @@ -4,34 +4,16 @@ use super::PageTableEntry; use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; use core::fmt::{self, Debug, Formatter}; -const VPN_WIDTH_BITS: usize = crate::hal::virt_addr_bits() - PAGE_SIZE_BITS; - /// Convert a physical address to a kernel virtual address for direct access. -/// -/// On RiscV the kernel is linked at its physical address so this is a no-op. -/// On LoongArch the kernel lives in the DMW1 cached window, so physical -/// addresses need the 0x9000_0000_0000_0000 prefix to be accessible. #[inline(always)] pub fn phys_to_virt(pa: usize) -> usize { - #[cfg(target_arch = "loongarch64")] - { - use crate::board::KERNEL_ADDR_OFFSET; - pa | KERNEL_ADDR_OFFSET - } - #[cfg(not(target_arch = "loongarch64"))] - pa + crate::platform::direct_map_phys_to_virt(pa) } /// Convert one direct-mapped kernel virtual address back to a physical address. #[inline(always)] pub fn virt_to_phys(va: usize) -> usize { - #[cfg(target_arch = "loongarch64")] - { - use crate::board::KERNEL_ADDR_OFFSET; - va & !KERNEL_ADDR_OFFSET - } - #[cfg(not(target_arch = "loongarch64"))] - va + crate::platform::direct_map_virt_to_phys(va) } /// Exclusive end of the canonical low-half user virtual-address range. pub const USER_SPACE_END: usize = { @@ -98,18 +80,12 @@ impl From for PhysPageNum { } impl From for VirtAddr { fn from(v: usize) -> Self { - #[cfg(target_arch = "loongarch64")] - return Self(v); - #[cfg(not(target_arch = "loongarch64"))] - return Self(v & ((1 << crate::hal::virt_addr_bits()) - 1)); + Self(crate::hal::normalize_virt_addr_input(v)) } } impl From for VirtPageNum { fn from(v: usize) -> Self { - #[cfg(target_arch = "loongarch64")] - return Self(v >> PAGE_SIZE_BITS); - #[cfg(not(target_arch = "loongarch64"))] - return Self(v & ((1 << VPN_WIDTH_BITS) - 1)); + Self(crate::hal::virt_page_num_from_addr(v)) } } impl From for usize { diff --git a/os/src/mm/heap_allocator.rs b/os/src/mm/heap_allocator.rs index 03940c39..b3d10776 100644 --- a/os/src/mm/heap_allocator.rs +++ b/os/src/mm/heap_allocator.rs @@ -215,13 +215,12 @@ pub fn init_heap() { } pub fn init_heap_virtual_window() { - #[cfg(target_arch = "loongarch64")] - { + if !crate::platform::kernel_heap_virtual_window_supported() { // LA64 bring-up still faults on the first access into the low-half // heap window even after the leaf PTE is installed and TLB state is // refreshed. Keep using the already-working DMW-backed bootstrap heap // path for now so the kernel can continue booting on LoongArch. - crate::early_puts("[heap] virtual window disabled on loongarch64\r\n"); + crate::platform::early_console_write("[heap] virtual window disabled on loongarch64\r\n"); return; } KERNEL_HEAP_VIRTUAL_READY.store(true, Ordering::Release); @@ -236,7 +235,6 @@ pub fn map_one_heap_page(va: usize) -> bool { map_heap_pages(va, 1) } -#[cfg(target_arch = "loongarch64")] fn early_put_hex(label: &str, value: usize) { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut buf = [0u8; 2 + 16 + 2]; @@ -248,9 +246,9 @@ fn early_put_hex(label: &str, value: usize) { } buf[18] = b'\r'; buf[19] = b'\n'; - crate::early_puts(label); + crate::platform::early_console_write(label); // SAFETY: ASCII hex buffer is always valid UTF-8. - crate::early_puts(core::str::from_utf8(&buf).unwrap()); + crate::platform::early_console_write(core::str::from_utf8(&buf).unwrap()); } fn layout_required_bytes(layout: Layout) -> Option { @@ -358,17 +356,20 @@ fn heap_leaf_pte( } fn map_heap_pages(start_va: usize, pages: usize) -> bool { - #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] map_heap_pages\r\n"); + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] map_heap_pages\r\n"); + } let subtree_root_ppn = PhysPageNum(KERNEL_HEAP_SUBTREE_ROOT_PPN.load(Ordering::Acquire)); if subtree_root_ppn.0 == 0 { panic!("map_heap_pages: subtree root ppn is 0"); } - #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] locking HEAP_PT_LOCK\r\n"); + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] locking HEAP_PT_LOCK\r\n"); + } let _guard = HEAP_PT_LOCK.lock(); - #[cfg(target_arch = "loongarch64")] - crate::early_puts("[heap] HEAP_PT_LOCK locked\r\n"); + if crate::platform::heap_debug_enabled() { + crate::platform::early_console_write("[heap] HEAP_PT_LOCK locked\r\n"); + } for page in 0..pages { let va = start_va + page * PAGE_SIZE; let vpn = VirtAddr::from(va).floor(); @@ -394,8 +395,7 @@ fn map_heap_pages(start_va: usize, pages: usize) -> bool { ); core::mem::forget(frame); } - #[cfg(target_arch = "loongarch64")] - if pages > 0 { + if crate::platform::heap_debug_enabled() && pages > 0 { let vpn = VirtAddr::from(start_va).floor(); let root_idx = crate::hal::vpn_index(vpn.0, 0); let mid_idx = crate::hal::vpn_index(vpn.0, 1); diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index d1d6cabd..3a857ff5 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -468,17 +468,7 @@ impl MemorySet { if map_perm.contains(MapPermission::U) { flags.insert(PTEFlags::U); } - #[cfg(target_arch = "loongarch64")] - { - // LoongArch leaf PTEs need the accessed bit set up-front, and - // writable mappings also need the dirty bit, otherwise the CPU may - // keep reporting page-invalid/store faults even after TLB refill. - flags.insert(PTEFlags::A); - if map_perm.contains(MapPermission::W) { - flags.insert(PTEFlags::D); - } - } - flags + crate::hal::normalize_leaf_pte_flags(flags) } /// 完成一次会返回延迟回收 batch 的本地页表修改。 @@ -809,10 +799,7 @@ impl MemorySet { } /// Mention that trampoline is not collected by areas. fn map_trampoline(&mut self) { - #[cfg(not(target_arch = "loongarch64"))] - let trampoline_pa = strampoline as usize; - #[cfg(target_arch = "loongarch64")] - let trampoline_pa = crate::mm::virt_to_phys(strampoline as usize); + let trampoline_pa = crate::platform::direct_map_virt_to_phys(strampoline as usize); self.page_table .map( @@ -2266,10 +2253,8 @@ impl Vma { } /// 为某个线程生成 Trap 上下文页对应的区域描述。 pub fn new_trap_context(start_va: VirtAddr, end_va: VirtAddr, tid: usize) -> Self { - #[cfg(target_arch = "loongarch64")] - let map_perm = MapPermission::R | MapPermission::W | MapPermission::U; - #[cfg(not(target_arch = "loongarch64"))] - let map_perm = MapPermission::R | MapPermission::W; + let map_perm = + MapPermission::from_bits_truncate(crate::hal::trap_context_flags().bits() as u8); Self::new( start_va, end_va, diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index b4dd87d6..c53ed648 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -39,17 +39,7 @@ impl PageTableEntry { } /// The page pointered by page table entry is valid? pub fn is_valid(&self) -> bool { - #[cfg(target_arch = "loongarch64")] - { - // LoongArch non-leaf directory entries are encoded as bare - // next-level-table physical addresses, so software page-table walks - // must treat any non-zero entry as present. - self.bits != 0 - } - #[cfg(not(target_arch = "loongarch64"))] - { - (self.flags() & PTEFlags::V) != PTEFlags::empty() - } + crate::hal::pte_is_valid(self.bits) } /// The page pointered by page table entry is readable? pub fn readable(&self) -> bool { diff --git a/os/src/platform/loongarch/mod.rs b/os/src/platform/loongarch/mod.rs new file mode 100644 index 00000000..c9ae8e32 --- /dev/null +++ b/os/src/platform/loongarch/mod.rs @@ -0,0 +1,6 @@ +//! LoongArch platform implementations. +//! +//! This layer binds one concrete machine/board model onto the generic +//! LoongArch architecture support and reusable device drivers. + +pub mod qemu_virt; diff --git a/os/src/platform/loongarch_virt/irq.rs b/os/src/platform/loongarch/qemu_virt/irq.rs similarity index 97% rename from os/src/platform/loongarch_virt/irq.rs rename to os/src/platform/loongarch/qemu_virt/irq.rs index d376494c..415eeab3 100644 --- a/os/src/platform/loongarch_virt/irq.rs +++ b/os/src/platform/loongarch/qemu_virt/irq.rs @@ -8,13 +8,12 @@ use core::arch::asm; use core::ptr::{read_volatile, write_volatile}; use core::sync::atomic::{AtomicBool, Ordering}; -use crate::board::IO_ADDR_OFFSET; use crate::bootstrap_hart_id; use crate::drivers::chardev::{CharDevice, UART}; static UART_IRQ_READY: AtomicBool = AtomicBool::new(false); -const PCH_PIC_BASE: usize = IO_ADDR_OFFSET | 0x1000_0000; +const PCH_PIC_BASE: usize = crate::platform::IO_ADDR_OFFSET | 0x1000_0000; const PCH_PIC_INT_MASK: usize = 0x20; const PCH_PIC_HTMSI_VEC: usize = 0x200; diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs new file mode 100644 index 00000000..b4af6e4f --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -0,0 +1,124 @@ +//! QEMU `virt` platform for LoongArch64. + +mod irq; +mod pci; + +pub use irq::{ + console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, +}; +pub use pci::probe_platform_devices; + +use crate::drivers::chardev::CharDevice; +use crate::hal::traits::{HartCtrl, Timer}; +use crate::platform::QEMUExit; + +pub const KERNEL_HEAP_BASE: usize = 0x0000_0038_0000_0000; +pub const TRAMPOLINE: usize = 0x0000_003f_ffff_f000; + +/// LoongArch64 platform implementation used by the generic HAL facade. +pub struct LoongArchPlatform; + +impl Timer for LoongArchPlatform { + fn read_time() -> usize { + crate::arch::loongarch64::read_time() + } + + fn set_next(deadline: usize) { + unsafe { crate::arch::loongarch64::set_timer_deadline(deadline) }; + } + + fn clock_freq() -> usize { + crate::config::CLOCK_FREQ + } +} + +impl HartCtrl for LoongArchPlatform { + fn start_hart(_hart_id: usize, _start_addr: usize, _opaque: usize) -> Result<(), ()> { + Err(()) + } + + fn send_ipi(_hart_mask: usize) { + // Single-core bring-up only for now. + } +} + +/// Whether console output should still use the earliest UART path. +pub fn use_early_console() -> bool { + !crate::drivers::chardev::uart_ready() +} + +/// Write one string through the earliest available console path. +pub fn early_console_write(s: &str) { + for b in s.bytes() { + unsafe { + while core::ptr::read_volatile((crate::platform::VIRT_UART + 5) as *const u8) & 0x20 + == 0 + {} + core::ptr::write_volatile(crate::platform::VIRT_UART as *mut u8, b); + } + } +} + +/// Write one character to the platform console. +pub fn console_putchar(c: usize) { + crate::drivers::chardev::UART.write(c as u8); +} + +/// Read one character from the platform console. +pub fn console_getchar() -> usize { + crate::drivers::chardev::UART.read() as usize +} + +/// Power off the virtual machine. +pub fn shutdown() -> ! { + crate::platform::QEMU_EXIT_HANDLE.exit_success() +} + +/// Return the uname-style machine string. +pub fn machine_name() -> &'static str { + "loongarch64" +} + +/// Secondary-hart start-up is not wired up on this platform yet. +pub fn start_secondary_harts(_bootstrap_hart_id: usize) {} + +/// Translate one direct-mapped physical address into the kernel VA used on this platform. +pub fn direct_map_phys_to_virt(pa: usize) -> usize { + pa | crate::platform::KERNEL_ADDR_OFFSET +} + +/// Translate one direct-mapped kernel VA back into a physical address. +pub fn direct_map_virt_to_phys(va: usize) -> usize { + va & !crate::platform::KERNEL_ADDR_OFFSET +} + +/// Translate a direct-mapped kernel VA into a physical address when applicable. +pub fn translate_direct_mapped_kernel_va(va: usize) -> Option { + if va & crate::platform::KERNEL_ADDR_OFFSET == crate::platform::KERNEL_ADDR_OFFSET { + return Some(va & !crate::platform::KERNEL_ADDR_OFFSET); + } + if va & crate::platform::IO_ADDR_OFFSET == crate::platform::IO_ADDR_OFFSET { + return Some(va & !crate::platform::IO_ADDR_OFFSET); + } + None +} + +/// Translate one MMIO physical address into the VA used by drivers. +pub fn mmio_phys_to_virt(paddr: usize) -> usize { + paddr | crate::platform::IO_ADDR_OFFSET +} + +/// Whether the Goldfish RTC is supported on this platform. +pub fn rtc_is_supported() -> bool { + false +} + +/// Whether the kernel heap may grow inside its dedicated virtual window. +pub fn kernel_heap_virtual_window_supported() -> bool { + false +} + +/// Whether extra heap bring-up debugging is enabled for this platform. +pub fn heap_debug_enabled() -> bool { + true +} diff --git a/os/src/platform/loongarch_virt/pci.rs b/os/src/platform/loongarch/qemu_virt/pci.rs similarity index 98% rename from os/src/platform/loongarch_virt/pci.rs rename to os/src/platform/loongarch/qemu_virt/pci.rs index 20350ede..47d3ce47 100644 --- a/os/src/platform/loongarch_virt/pci.rs +++ b/os/src/platform/loongarch/qemu_virt/pci.rs @@ -12,7 +12,6 @@ use virtio_drivers::transport::{ DeviceType, SomeTransport, Transport, }; -use crate::board::IO_ADDR_OFFSET; use crate::drivers::{ block::{BLOCK_DEVICES, BLOCK_DEVICES_BY_IRQ, VirtIOBlock}, net::{self, VirtIONetDevice}, @@ -27,7 +26,7 @@ const LEGACY_VIRTIO_NET_IRQ: u32 = 1; /// Probe the LA64 PCIe ECAM bus and register VirtIO PCI devices. pub fn probe_platform_devices() { - let ecam_vaddr = PCI_ECAM_BASE | IO_ADDR_OFFSET; + let ecam_vaddr = PCI_ECAM_BASE | crate::platform::IO_ADDR_OFFSET; let ecam_end = ecam_vaddr + PCI_ECAM_SIZE; if ecam_end < ecam_vaddr { panic!("PCI ECAM window overflow"); diff --git a/os/src/platform/loongarch_virt.rs b/os/src/platform/loongarch_virt.rs deleted file mode 100644 index 094885ba..00000000 --- a/os/src/platform/loongarch_virt.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! LoongArch64 QEMU virt platform hooks. - -mod irq; -mod pci; - -pub use crate::board::{ - BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, VIRT_RTC, - VIRT_UART, -}; -pub use irq::{ - console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, -}; -pub use pci::probe_platform_devices; - -use crate::hal::traits::{HartCtrl, Timer}; - -/// LoongArch64 platform implementation used by the generic HAL façade. -pub struct LoongArchPlatform; - -impl Timer for LoongArchPlatform { - fn read_time() -> usize { - crate::arch::loongarch64::read_time() - } - - fn set_next(deadline: usize) { - unsafe { crate::arch::loongarch64::set_timer_deadline(deadline) }; - } - - fn clock_freq() -> usize { - crate::config::CLOCK_FREQ - } -} - -impl HartCtrl for LoongArchPlatform { - fn start_hart(_hart_id: usize, _start_addr: usize, _opaque: usize) -> Result<(), ()> { - Err(()) - } - - fn send_ipi(_hart_mask: usize) { - // Single-core bring-up only for now. - } -} diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index 23c3095f..b93b5067 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -1,20 +1,50 @@ -//! Platform-specific implementations. +//! Platform-specific machine composition. +//! +//! `arch` describes ISA and privilege-architecture behavior. +//! `drivers` describe reusable device-IP drivers. +//! `platform` binds one concrete machine model to those two layers: MMIO +//! layout, interrupt routing, device probing, poweroff, SMP bring-up, and +//! early console policy all belong here. #![allow(missing_docs)] #[cfg(target_arch = "riscv64")] -pub mod qemu_virt; +#[path = "../boards/qemu.rs"] +mod board_impl; #[cfg(target_arch = "loongarch64")] -pub mod loongarch_virt; +#[path = "../boards/loongarch_virt.rs"] +mod board_impl; #[cfg(target_arch = "riscv64")] -pub use qemu_virt::{ +pub mod riscv; + +#[cfg(target_arch = "loongarch64")] +pub mod loongarch; + +pub use board_impl::{ + BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, VIRT_RTC, + VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; + +#[cfg(target_arch = "loongarch64")] +pub use board_impl::{IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET}; + +#[cfg(target_arch = "riscv64")] +pub use riscv::qemu_virt::{ console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, - probe_platform_devices, + console_getchar, console_putchar, direct_map_phys_to_virt, direct_map_virt_to_phys, + early_console_write, heap_debug_enabled, kernel_heap_virtual_window_supported, + machine_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, + KERNEL_HEAP_BASE, SbiPlatform as PlatformImpl, TRAMPOLINE, }; #[cfg(target_arch = "loongarch64")] -pub use loongarch_virt::{ +pub use loongarch::qemu_virt::{ console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, - probe_platform_devices, + console_getchar, console_putchar, direct_map_phys_to_virt, direct_map_virt_to_phys, + early_console_write, heap_debug_enabled, kernel_heap_virtual_window_supported, + machine_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, + KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, }; diff --git a/os/src/platform/qemu_virt/mod.rs b/os/src/platform/qemu_virt/mod.rs deleted file mode 100644 index 92aa5391..00000000 --- a/os/src/platform/qemu_virt/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! QEMU virt platform — memory map, exit, and board types. - -pub mod sbi; - -pub use crate::board::{ - BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, - VIRT_RTC, VIRT_UART, -}; -pub use sbi::SbiPlatform; - -/// Initialize platform external interrupt routing on the bootstrap hart. -pub fn init_external_irq() { - crate::drivers::plic::init(); -} - -/// Initialize per-hart external interrupt state. -pub fn init_external_irq_hart(hart_id: usize) { - crate::drivers::plic::init_hart(hart_id); -} - -/// Dispatch one platform external interrupt. -pub fn handle_external_irq() { - crate::drivers::plic::handle_supervisor_external(); -} - -/// Whether the console RX interrupt path is ready for blocking reads. -pub fn console_rx_irq_ready() -> bool { - true -} - -/// Probe platform-specific devices after generic driver init. -pub fn probe_platform_devices() {} diff --git a/os/src/platform/riscv/mod.rs b/os/src/platform/riscv/mod.rs new file mode 100644 index 00000000..b98ba6a2 --- /dev/null +++ b/os/src/platform/riscv/mod.rs @@ -0,0 +1,6 @@ +//! RISC-V platform implementations. +//! +//! This layer binds one concrete machine/board model onto the generic RISC-V +//! architecture support and reusable device drivers. + +pub mod qemu_virt; diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs new file mode 100644 index 00000000..0c5f99a2 --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -0,0 +1,163 @@ +//! QEMU `virt` platform for RISC-V. + +pub mod sbi; + +pub use sbi::SbiPlatform; + +pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; +pub const TRAMPOLINE: usize = usize::MAX - 0x1000 + 1; + +/// Initialize platform external interrupt routing on the bootstrap hart. +pub fn init_external_irq() { + crate::drivers::plic::init(); +} + +/// Initialize per-hart external interrupt state. +pub fn init_external_irq_hart(hart_id: usize) { + crate::drivers::plic::init_hart(hart_id); +} + +/// Dispatch one platform external interrupt. +pub fn handle_external_irq() { + crate::drivers::plic::handle_supervisor_external(); +} + +/// Whether the console RX interrupt path is ready for blocking reads. +pub fn console_rx_irq_ready() -> bool { + true +} + +/// Probe platform-specific devices after generic driver init. +pub fn probe_platform_devices() { + crate::drivers::block::probe_block_devices(); + crate::drivers::net::probe_net_devices(); +} + +/// RISC-V always uses the normal UART path once the console layer is up. +pub fn use_early_console() -> bool { + false +} + +/// Write one string through the earliest available console path. +pub fn early_console_write(s: &str) { + for byte in s.bytes() { + sbi::console_putchar(byte as usize); + } +} + +/// Write one character to the platform console. +pub fn console_putchar(c: usize) { + sbi::console_putchar(c); +} + +/// Read one character from the platform console. +pub fn console_getchar() -> usize { + sbi::console_getchar() +} + +/// Power off the virtual machine. +pub fn shutdown() -> ! { + sbi::shutdown() +} + +/// Return the uname-style machine string. +pub fn machine_name() -> &'static str { + "riscv64" +} + +/// Discover stopped harts via SBI HSM and start them on QEMU `virt`. +pub fn start_secondary_harts(bootstrap_hart_id: usize) { + extern "C" { + fn _start(); + } + + const SBI_SUCCESS: isize = 0; + const SBI_ERR_INVALID_PARAM: isize = -3; + const SBI_ERR_ALREADY_AVAILABLE: isize = -6; + + info!( + "hart {} entering HSM probe/start loop", + bootstrap_hart_id + ); + + for target_hart in 0..crate::config::MAX_HARTS { + let status = sbi::hart_get_status(target_hart); + if status.error == SBI_ERR_INVALID_PARAM { + info!( + "hart {} got invalid hart id while probing hart {}, stop scan", + bootstrap_hart_id, target_hart + ); + break; + } + if status.error != SBI_SUCCESS { + info!( + "hart {} HSM status query for hart {} failed: error={}, value={}", + bootstrap_hart_id, target_hart, status.error, status.value + ); + continue; + } + + let state = sbi::hart_state(status.value); + info!( + "hart {} sees hart {} in HSM state {:?}", + bootstrap_hart_id, target_hart, state + ); + + if target_hart == bootstrap_hart_id { + continue; + } + + if let sbi::HartState::Stopped = state { + let ret = sbi::hart_start(target_hart, _start as usize, 0); + match ret.error { + SBI_SUCCESS => info!( + "hart {} requested startup for hart {}", + bootstrap_hart_id, target_hart + ), + SBI_ERR_ALREADY_AVAILABLE => info!( + "hart {} found hart {} already available while starting", + bootstrap_hart_id, target_hart + ), + error => info!( + "hart {} failed to start hart {}: error={}, value={}", + bootstrap_hart_id, target_hart, error, ret.value + ), + } + } + } +} + +/// Translate one direct-mapped physical address into the kernel VA used on this platform. +pub fn direct_map_phys_to_virt(pa: usize) -> usize { + pa +} + +/// Translate one direct-mapped kernel VA back into a physical address. +pub fn direct_map_virt_to_phys(va: usize) -> usize { + va +} + +/// Translate a direct-mapped kernel VA into a physical address when applicable. +pub fn translate_direct_mapped_kernel_va(_va: usize) -> Option { + None +} + +/// Translate one MMIO physical address into the VA used by drivers. +pub fn mmio_phys_to_virt(paddr: usize) -> usize { + paddr +} + +/// Whether the Goldfish RTC is supported on this platform. +pub fn rtc_is_supported() -> bool { + true +} + +/// Whether the kernel heap may grow inside its dedicated virtual window. +pub fn kernel_heap_virtual_window_supported() -> bool { + true +} + +/// Whether extra heap bring-up debugging is enabled for this platform. +pub fn heap_debug_enabled() -> bool { + false +} diff --git a/os/src/platform/qemu_virt/sbi.rs b/os/src/platform/riscv/qemu_virt/sbi.rs similarity index 67% rename from os/src/platform/qemu_virt/sbi.rs rename to os/src/platform/riscv/qemu_virt/sbi.rs index e152244f..7f675bde 100644 --- a/os/src/platform/qemu_virt/sbi.rs +++ b/os/src/platform/riscv/qemu_virt/sbi.rs @@ -1,22 +1,26 @@ -//! SBI platform for QEMU virt — implements Timer and HartCtrl HAL traits. +//! SBI glue for the RISC-V QEMU `virt` platform. -pub use crate::sbi::{ - hart_get_status, hart_start, hart_state, send_ipi_mask, - set_timer, shutdown, HartState, SbiRet, +pub(crate) use crate::sbi::{ + console_getchar_raw as console_getchar, console_putchar_raw as console_putchar, + hart_get_status_raw as hart_get_status, hart_start_raw as hart_start, hart_state, + send_ipi_mask_raw as send_ipi_mask, set_timer_raw as set_timer, shutdown_raw as shutdown, + HartState, }; use crate::hal::traits::{HartCtrl, Timer}; -/// SBI-backed implementation of [`Timer`] and [`HartCtrl`] for QEMU virt. +/// SBI-backed implementation of [`Timer`] and [`HartCtrl`] for QEMU `virt`. pub struct SbiPlatform; impl Timer for SbiPlatform { fn read_time() -> usize { riscv::register::time::read() } + fn set_next(deadline: usize) { set_timer(deadline); } + fn clock_freq() -> usize { crate::config::CLOCK_FREQ } @@ -27,6 +31,7 @@ impl HartCtrl for SbiPlatform { let ret = hart_start(hart_id, start_addr, opaque); if ret.error == 0 { Ok(()) } else { Err(()) } } + fn send_ipi(hart_mask: usize) { send_ipi_mask(hart_mask); } diff --git a/os/src/sbi.rs b/os/src/sbi.rs index 17172a69..43c56c90 100644 --- a/os/src/sbi.rs +++ b/os/src/sbi.rs @@ -3,13 +3,11 @@ #![allow(unused)] -use core::arch::asm; use crate::fs::sync_page_cache_all; +use crate::hal::traits::HartCtrl as _; -#[cfg(target_arch = "loongarch64")] -use crate::board::{QEMUExit, QEMU_EXIT_HANDLE}; -#[cfg(target_arch = "loongarch64")] -use crate::drivers::chardev::CharDevice; +#[cfg(target_arch = "riscv64")] +use core::arch::asm; /// SBI v0.2+ 调用返回值。 #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -105,68 +103,59 @@ fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize, arg2: u /// use sbi call to set timer #[cfg(qemu7)] #[cfg(target_arch = "riscv64")] -pub fn set_timer(timer: usize) { +pub(crate) fn set_timer_raw(timer: usize) { sbi_call_legacy(SBI_SET_TIMER, timer, 0, 0); } /// use sbi call to putchar in console (qemu uart handler) #[cfg(not(qemu7))] #[cfg(target_arch = "riscv64")] -pub fn set_timer(timer: usize) { +pub(crate) fn set_timer_raw(timer: usize) { let _ = sbi_call(SBI_SET_TIMER, 0, timer, 0, 0); } -#[cfg(target_arch = "loongarch64")] -/// Program the next local timer deadline on LoongArch64. -pub fn set_timer(timer: usize) { - unsafe { crate::arch::loongarch64::set_timer_deadline(timer) }; -} - /// use sbi call to putchar in console (qemu uart handler) #[cfg(qemu7)] #[cfg(target_arch = "riscv64")] -pub fn console_putchar(c: usize) { +pub(crate) fn console_putchar_raw(c: usize) { sbi_call_legacy(SBI_CONSOLE_PUTCHAR, c, 0, 0); } /// use sbi call to getchar from console (qemu uart handler) #[cfg(not(qemu7))] #[cfg(target_arch = "riscv64")] -pub fn console_putchar(c: usize) { +pub(crate) fn console_putchar_raw(c: usize) { let _ = sbi_call(SBI_CONSOLE_PUTCHAR, 0, c, 0, 0); } -#[cfg(target_arch = "loongarch64")] -/// Write one byte to the early console on LoongArch64. +/// Write one byte to the platform console. pub fn console_putchar(c: usize) { - crate::drivers::chardev::UART.write(c as u8); + crate::platform::console_putchar(c); } /// use sbi call to getchar from console (qemu uart handler) #[cfg(qemu7)] #[cfg(target_arch = "riscv64")] -pub fn console_getchar() -> usize { +pub(crate) fn console_getchar_raw() -> usize { sbi_call_legacy(SBI_CONSOLE_GETCHAR, 0, 0, 0) } /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] #[cfg(target_arch = "riscv64")] -pub fn console_getchar() -> usize { +pub(crate) fn console_getchar_raw() -> usize { sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0, 0).value } -#[cfg(target_arch = "loongarch64")] -/// Read one byte from the early console on LoongArch64. +/// Read one byte from the platform console. pub fn console_getchar() -> usize { - crate::drivers::chardev::UART.read() as usize + crate::platform::console_getchar() } /// use sbi call to shutdown the kernel #[cfg(qemu7)] #[cfg(target_arch = "riscv64")] -pub fn shutdown() -> ! { - let _ = sync_page_cache_all(); +pub(crate) fn shutdown_raw() -> ! { sbi_call_legacy(SBI_SHUTDOWN, 0, 0, 0); panic!("It should shutdown!"); } @@ -174,23 +163,21 @@ pub fn shutdown() -> ! { /// use sbi call to shutdown the kernel #[cfg(not(qemu7))] #[cfg(target_arch = "riscv64")] -pub fn shutdown() -> ! { - let _ = sync_page_cache_all(); +pub(crate) fn shutdown_raw() -> ! { let _ = sbi_call(SBI_SHUTDOWN, 0, 0, 0, 0); panic!("It should shutdown!"); } -#[cfg(target_arch = "loongarch64")] -/// Shut down the LoongArch64 QEMU guest via the board exit device. +/// Shut down the machine through the current platform backend. pub fn shutdown() -> ! { let _ = sync_page_cache_all(); - QEMU_EXIT_HANDLE.exit_success() + crate::platform::shutdown() } /// 发送 IPI 到给定 hart mask。 #[cfg(qemu7)] #[cfg(target_arch = "riscv64")] -pub fn send_ipi_mask(hart_mask: usize) { +pub(crate) fn send_ipi_mask_raw(hart_mask: usize) { let hart_mask_ptr = &hart_mask as *const usize as usize; sbi_call_legacy(SBI_SEND_IPI, hart_mask_ptr, 0, 0); } @@ -198,23 +185,24 @@ pub fn send_ipi_mask(hart_mask: usize) { /// 发送 IPI 到给定 hart mask。 #[cfg(not(qemu7))] #[cfg(target_arch = "riscv64")] -pub fn send_ipi_mask(hart_mask: usize) { +pub(crate) fn send_ipi_mask_raw(hart_mask: usize) { let _ = sbi_call(SBI_IPI, 0, hart_mask, 0, 0); } -#[cfg(target_arch = "loongarch64")] -/// Single-core bring-up keeps IPI as a no-op on LoongArch64 for now. -pub fn send_ipi_mask(_hart_mask: usize) {} +/// Send an inter-processor interrupt to the given hart mask. +pub fn send_ipi_mask(hart_mask: usize) { + crate::hal::Plat::send_ipi(hart_mask); +} /// 查询指定 hart 的 HSM 状态。 #[cfg(target_arch = "riscv64")] -pub fn hart_get_status(hart_id: usize) -> SbiRet { +pub(crate) fn hart_get_status_raw(hart_id: usize) -> SbiRet { sbi_call(SBI_HSM, 2, hart_id, 0, 0) } /// 请求启动指定 hart,并让它从 `start_addr` 开始执行。 #[cfg(target_arch = "riscv64")] -pub fn hart_start(hart_id: usize, start_addr: usize, opaque: usize) -> SbiRet { +pub(crate) fn hart_start_raw(hart_id: usize, start_addr: usize, opaque: usize) -> SbiRet { sbi_call(SBI_HSM, 0, hart_id, start_addr, opaque) } diff --git a/os/src/syscall/process.rs b/os/src/syscall/process.rs index 6962cd50..8ec46933 100644 --- a/os/src/syscall/process.rs +++ b/os/src/syscall/process.rs @@ -1225,10 +1225,7 @@ impl UtsName { let nodename = b"localhost"; let release = b"6.6.0"; let version = b"#1 SMP PREEMPT cosmOS"; - #[cfg(target_arch = "riscv64")] - let machine = b"riscv64"; - #[cfg(target_arch = "loongarch64")] - let machine = b"loongarch64"; + let machine = crate::platform::machine_name().as_bytes(); let domainname = b"localdomain"; uname.sysname[..sysname.len()].copy_from_slice(sysname); uname.nodename[..nodename.len()].copy_from_slice(nodename); diff --git a/os/src/task/mod.rs b/os/src/task/mod.rs index 74f6c30a..7dfabe0f 100644 --- a/os/src/task/mod.rs +++ b/os/src/task/mod.rs @@ -56,7 +56,7 @@ pub use task::{ }; pub(crate) use task::TaskControlBlockInner; -use crate::board::QEMUExit; +use crate::platform::QEMUExit; use alloc::string::String; /// Exit the current 'Running' task and run the next task in task list. @@ -152,10 +152,10 @@ pub fn exit_current_and_run_next(reason: ExitReason) { ); if task_exit_code != 0 { //crate::sbi::shutdown(255); //255 == -1 for err hint - crate::board::QEMU_EXIT_HANDLE.exit_failure(); + crate::platform::QEMU_EXIT_HANDLE.exit_failure(); } else { //crate::sbi::shutdown(0); //0 for success hint - crate::board::QEMU_EXIT_HANDLE.exit_success(); + crate::platform::QEMU_EXIT_HANDLE.exit_success(); } } let mut process_inner = process.inner_exclusive_access(); diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index deb0e7b3..6fedba0e 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -14,12 +14,8 @@ mod context; use crate::config::PAGE_SIZE; -#[cfg(target_arch = "loongarch64")] -use crate::config::{KERNEL_HEAP_BASE, MAX_KERNEL_HEAP_SIZE}; use crate::hal::hartid; use crate::mm::{handle_ipi, MmError, PageFaultAccess, PageFaultHandled}; -#[cfg(target_arch = "loongarch64")] -use crate::mm::map_one_heap_page; use crate::signal::{SignalBit, handle_signals}; use crate::syscall::{syscall, syscall_supports_sa_restart}; use crate::sched::{on_timer_tick, request_current_task_resched, schedule_if_needed, ReschedReason}; @@ -93,40 +89,6 @@ fn log_lazy_fault_oom(path: &str, access: &str, fault_addr: usize) { ); } -#[cfg(target_arch = "loongarch64")] -fn early_put_hex(label: &str, value: usize) { - const HEX: &[u8; 16] = b"0123456789abcdef"; - let mut buf = [0u8; 2 + 16 + 2]; - buf[0] = b'0'; - buf[1] = b'x'; - for (idx, slot) in buf[2..18].iter_mut().enumerate() { - let shift = (15 - idx) * 4; - *slot = HEX[(value >> shift) & 0xf]; - } - buf[18] = b'\r'; - buf[19] = b'\n'; - crate::early_puts(label); - crate::early_puts(core::str::from_utf8(&buf).unwrap()); -} - -#[cfg(target_arch = "loongarch64")] -fn dump_kernel_trap_state() { - let crmd: usize; - let estat: usize; - let era: usize; - let badv: usize; - unsafe { - core::arch::asm!("csrrd {}, {}", out(reg) crmd, const 0x0); - core::arch::asm!("csrrd {}, {}", out(reg) estat, const 0x5); - core::arch::asm!("csrrd {}, {}", out(reg) era, const 0x6); - core::arch::asm!("csrrd {}, {}", out(reg) badv, const 0x7); - } - early_put_hex("[trap] crmd=", crmd); - early_put_hex("[trap] estat=", estat); - early_put_hex("[trap] era=", era); - early_put_hex("[trap] badv=", badv); -} - /// 初始化当前 hart 的 trap 相关状态。 /// /// 该函数需要每个 hart 各自执行一次,用于安装本 hart 的内核 trap 入口, From da34114464faf2e2752fecb5073343fc646c9742 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 10 Jun 2026 22:27:45 +0800 Subject: [PATCH 13/19] refactor: move `/os/board` to `os/platform`. --- .../loongarch/qemu_virt/board.rs} | 14 +++--- os/src/platform/loongarch/qemu_virt/irq.rs | 2 +- os/src/platform/loongarch/qemu_virt/mod.rs | 29 ++++++----- os/src/platform/loongarch/qemu_virt/pci.rs | 2 +- os/src/platform/mod.rs | 35 +++++-------- .../riscv/qemu_virt/board.rs} | 50 +++++++------------ os/src/platform/riscv/qemu_virt/mod.rs | 5 ++ rootfs | 1 + 8 files changed, 61 insertions(+), 77 deletions(-) rename os/src/{boards/loongarch_virt.rs => platform/loongarch/qemu_virt/board.rs} (88%) rename os/src/{boards/qemu.rs => platform/riscv/qemu_virt/board.rs} (54%) create mode 160000 rootfs diff --git a/os/src/boards/loongarch_virt.rs b/os/src/platform/loongarch/qemu_virt/board.rs similarity index 88% rename from os/src/boards/loongarch_virt.rs rename to os/src/platform/loongarch/qemu_virt/board.rs index a5834530..b83072fd 100644 --- a/os/src/boards/loongarch_virt.rs +++ b/os/src/platform/loongarch/qemu_virt/board.rs @@ -1,14 +1,14 @@ -//! QEMU LoongArch64 virt machine. +//! Static board description for the LoongArch64 QEMU `virt` machine. /// Direct-mapped uncached I/O virtual-address offset used during early bring-up. pub const IO_ADDR_OFFSET: usize = 0x8000_0000_0000_0000; /// Direct-mapped cached kernel-address offset used during early bring-up. pub const KERNEL_ADDR_OFFSET: usize = 0x9000_0000_0000_0000; -/// QEMU loongarch64 virt clock frequency. +/// QEMU loongarch64 `virt` clock frequency. pub const CLOCK_FREQ: usize = 100_000_000; -/// MMIO windows used by the kernel on QEMU loongarch64 virt (uncached DMW0 window). +/// MMIO windows used by the kernel on QEMU loongarch64 `virt` (uncached DMW0 window). pub const MMIO: &[(usize, usize)] = &[ (IO_ADDR_OFFSET | 0x1fe0_0000, 0x10000), // covers all 1fe0_xxxx MMIO (IO_ADDR_OFFSET | 0x1fe2_0000, 0x8000), // VirtIO @@ -22,16 +22,14 @@ pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x1fe0_01f8; pub const VIRTIO_MMIO_BASE: usize = IO_ADDR_OFFSET | 0x1fe2_0000; /// Size of each VirtIO MMIO slot. pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; -/// Number of VirtIO MMIO slots exposed by the board. +/// Number of VirtIO MMIO slots exposed by the machine. pub const VIRTIO_MMIO_SLOTS: usize = 8; /// First IRQ line assigned to VirtIO MMIO devices. pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; -/// Alias for compatibility. -pub const VIRT_UART_EARLY: usize = VIRT_UART; -/// Block device implementation for QEMU virt. +/// Block device implementation for QEMU `virt`. pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; -/// Char device implementation for QEMU virt. +/// Char device implementation for QEMU `virt`. pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; use core::arch::asm; diff --git a/os/src/platform/loongarch/qemu_virt/irq.rs b/os/src/platform/loongarch/qemu_virt/irq.rs index 415eeab3..9115ecd6 100644 --- a/os/src/platform/loongarch/qemu_virt/irq.rs +++ b/os/src/platform/loongarch/qemu_virt/irq.rs @@ -13,7 +13,7 @@ use crate::drivers::chardev::{CharDevice, UART}; static UART_IRQ_READY: AtomicBool = AtomicBool::new(false); -const PCH_PIC_BASE: usize = crate::platform::IO_ADDR_OFFSET | 0x1000_0000; +const PCH_PIC_BASE: usize = super::IO_ADDR_OFFSET | 0x1000_0000; const PCH_PIC_INT_MASK: usize = 0x20; const PCH_PIC_HTMSI_VEC: usize = 0x200; diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs index b4af6e4f..8a563246 100644 --- a/os/src/platform/loongarch/qemu_virt/mod.rs +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -1,8 +1,14 @@ //! QEMU `virt` platform for LoongArch64. +mod board; mod irq; mod pci; +pub use board::{ + BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, + VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; pub use irq::{ console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, }; @@ -10,7 +16,6 @@ pub use pci::probe_platform_devices; use crate::drivers::chardev::CharDevice; use crate::hal::traits::{HartCtrl, Timer}; -use crate::platform::QEMUExit; pub const KERNEL_HEAP_BASE: usize = 0x0000_0038_0000_0000; pub const TRAMPOLINE: usize = 0x0000_003f_ffff_f000; @@ -51,10 +56,8 @@ pub fn use_early_console() -> bool { pub fn early_console_write(s: &str) { for b in s.bytes() { unsafe { - while core::ptr::read_volatile((crate::platform::VIRT_UART + 5) as *const u8) & 0x20 - == 0 - {} - core::ptr::write_volatile(crate::platform::VIRT_UART as *mut u8, b); + while core::ptr::read_volatile((VIRT_UART + 5) as *const u8) & 0x20 == 0 {} + core::ptr::write_volatile(VIRT_UART as *mut u8, b); } } } @@ -71,7 +74,7 @@ pub fn console_getchar() -> usize { /// Power off the virtual machine. pub fn shutdown() -> ! { - crate::platform::QEMU_EXIT_HANDLE.exit_success() + QEMU_EXIT_HANDLE.exit_success() } /// Return the uname-style machine string. @@ -84,28 +87,28 @@ pub fn start_secondary_harts(_bootstrap_hart_id: usize) {} /// Translate one direct-mapped physical address into the kernel VA used on this platform. pub fn direct_map_phys_to_virt(pa: usize) -> usize { - pa | crate::platform::KERNEL_ADDR_OFFSET + pa | KERNEL_ADDR_OFFSET } /// Translate one direct-mapped kernel VA back into a physical address. pub fn direct_map_virt_to_phys(va: usize) -> usize { - va & !crate::platform::KERNEL_ADDR_OFFSET + va & !KERNEL_ADDR_OFFSET } /// Translate a direct-mapped kernel VA into a physical address when applicable. pub fn translate_direct_mapped_kernel_va(va: usize) -> Option { - if va & crate::platform::KERNEL_ADDR_OFFSET == crate::platform::KERNEL_ADDR_OFFSET { - return Some(va & !crate::platform::KERNEL_ADDR_OFFSET); + if va & KERNEL_ADDR_OFFSET == KERNEL_ADDR_OFFSET { + return Some(va & !KERNEL_ADDR_OFFSET); } - if va & crate::platform::IO_ADDR_OFFSET == crate::platform::IO_ADDR_OFFSET { - return Some(va & !crate::platform::IO_ADDR_OFFSET); + if va & IO_ADDR_OFFSET == IO_ADDR_OFFSET { + return Some(va & !IO_ADDR_OFFSET); } None } /// Translate one MMIO physical address into the VA used by drivers. pub fn mmio_phys_to_virt(paddr: usize) -> usize { - paddr | crate::platform::IO_ADDR_OFFSET + paddr | IO_ADDR_OFFSET } /// Whether the Goldfish RTC is supported on this platform. diff --git a/os/src/platform/loongarch/qemu_virt/pci.rs b/os/src/platform/loongarch/qemu_virt/pci.rs index 47d3ce47..12dab03b 100644 --- a/os/src/platform/loongarch/qemu_virt/pci.rs +++ b/os/src/platform/loongarch/qemu_virt/pci.rs @@ -26,7 +26,7 @@ const LEGACY_VIRTIO_NET_IRQ: u32 = 1; /// Probe the LA64 PCIe ECAM bus and register VirtIO PCI devices. pub fn probe_platform_devices() { - let ecam_vaddr = PCI_ECAM_BASE | crate::platform::IO_ADDR_OFFSET; + let ecam_vaddr = PCI_ECAM_BASE | super::IO_ADDR_OFFSET; let ecam_end = ecam_vaddr + PCI_ECAM_SIZE; if ecam_end < ecam_vaddr { panic!("PCI ECAM window overflow"); diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index b93b5067..ada326b6 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -7,33 +7,19 @@ //! early console policy all belong here. #![allow(missing_docs)] -#[cfg(target_arch = "riscv64")] -#[path = "../boards/qemu.rs"] -mod board_impl; - -#[cfg(target_arch = "loongarch64")] -#[path = "../boards/loongarch_virt.rs"] -mod board_impl; - #[cfg(target_arch = "riscv64")] pub mod riscv; #[cfg(target_arch = "loongarch64")] pub mod loongarch; -pub use board_impl::{ - BlockDeviceImpl, CharDeviceImpl, QEMUExit, QEMU_EXIT_HANDLE, CLOCK_FREQ, MMIO, VIRT_RTC, - VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, -}; - -#[cfg(target_arch = "loongarch64")] -pub use board_impl::{IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET}; - #[cfg(target_arch = "riscv64")] pub use riscv::qemu_virt::{ - console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, - console_getchar, console_putchar, direct_map_phys_to_virt, direct_map_virt_to_phys, - early_console_write, heap_debug_enabled, kernel_heap_virtual_window_supported, + BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, + console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, + direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, + init_external_irq, init_external_irq_hart, kernel_heap_virtual_window_supported, machine_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, SbiPlatform as PlatformImpl, TRAMPOLINE, @@ -41,10 +27,13 @@ pub use riscv::qemu_virt::{ #[cfg(target_arch = "loongarch64")] pub use loongarch::qemu_virt::{ - console_rx_irq_ready, handle_external_irq, init_external_irq, init_external_irq_hart, - console_getchar, console_putchar, direct_map_phys_to_virt, direct_map_virt_to_phys, - early_console_write, heap_debug_enabled, kernel_heap_virtual_window_supported, - machine_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, + VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, console_getchar, console_putchar, + console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, + early_console_write, handle_external_irq, heap_debug_enabled, init_external_irq, + init_external_irq_hart, kernel_heap_virtual_window_supported, machine_name, + mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, }; diff --git a/os/src/boards/qemu.rs b/os/src/platform/riscv/qemu_virt/board.rs similarity index 54% rename from os/src/boards/qemu.rs rename to os/src/platform/riscv/qemu_virt/board.rs index 58a9af35..c53ae488 100644 --- a/os/src/boards/qemu.rs +++ b/os/src/platform/riscv/qemu_virt/board.rs @@ -1,67 +1,60 @@ -//! QEMU riscv-64 virt machine +//! Static board description for the RISC-V QEMU `virt` machine. -/// clock frequency -pub const CLOCK_FREQ: usize = 12500000; -//pub const MEMORY_END: usize = 0x801000000; +/// Clock frequency. +pub const CLOCK_FREQ: usize = 12_500_000; -/// The base address of control registers in VIRT_TEST/RTC/Virtio_Block device +/// MMIO windows exposed by the machine. pub const MMIO: &[(usize, usize)] = &[ (0x0C00_0000, 0x400000), // PLIC - (0x0010_0000, 0x00_2000), // VIRT_TEST/RTC in virt machine - (0x1000_0000, 0x100), // UART0 (NS16550a) - (0x1000_1000, 0x8000), // Virtio MMIO devices, 8 slots, each slot occupies 0x1000 bytes + (0x0010_0000, 0x00_2000), // VIRT_TEST/RTC + (0x1000_0000, 0x100), // UART0 (NS16550a) + (0x1000_1000, 0x8000), // VirtIO MMIO devices, 8 slots, each slot occupies 0x1000 bytes ]; /// UART0 MMIO base address. pub const VIRT_UART: usize = 0x1000_0000; -/// QEMU virt 机型上的 Goldfish RTC MMIO 基址。 +/// Goldfish RTC MMIO base address. pub const VIRT_RTC: usize = 0x0010_1000; /// VirtIO MMIO window base address. pub const VIRTIO_MMIO_BASE: usize = 0x1000_1000; /// Size of each VirtIO MMIO slot. pub const VIRTIO_MMIO_STRIDE: usize = 0x1000; -/// Number of VirtIO MMIO slots exposed by the board. +/// Number of VirtIO MMIO slots exposed by the machine. pub const VIRTIO_MMIO_SLOTS: usize = 8; /// First IRQ line assigned to VirtIO MMIO devices. pub const VIRTIO_MMIO_IRQ_BASE: u32 = 1; -/// Block device implementation for QEMU virt. +/// Block device implementation for QEMU `virt`. pub type BlockDeviceImpl = crate::drivers::block::VirtIOBlock; -/// Char device implementation for QEMU virt. +/// Char device implementation for QEMU `virt`. pub type CharDeviceImpl = crate::drivers::chardev::NS16550a; -//ref:: https://github.com/andre-richter/qemu-exit use core::arch::asm; -const EXIT_SUCCESS: u32 = 0x5555; // Equals `exit(0)`. qemu successful exit - +const EXIT_SUCCESS: u32 = 0x5555; const EXIT_FAILURE_FLAG: u32 = 0x3333; -const EXIT_FAILURE: u32 = exit_code_encode(1); // Equals `exit(1)`. qemu failed exit -const EXIT_RESET: u32 = 0x7777; // qemu reset +const EXIT_FAILURE: u32 = exit_code_encode(1); +const EXIT_RESET: u32 = 0x7777; /// QEMU exit interface. pub trait QEMUExit { - /// Exit with specified return code. - /// - /// Note: For `X86`, code is binary-OR'ed with `0x1` inside QEMU. + /// Exit with the specified return code. fn exit(&self, code: u32) -> !; /// Exit QEMU using `EXIT_SUCCESS`, aka `0`, if possible. - /// - /// Note: Not possible for `X86`. fn exit_success(&self) -> !; /// Exit QEMU using `EXIT_FAILURE`, aka `1`. fn exit_failure(&self) -> !; } -/// RISCV64 configuration +/// RISC-V QEMU exit wrapper. pub struct RISCV64 { /// Address of the sifive_test mapped device. addr: u64, } -/// Encode the exit code using EXIT_FAILURE_FLAG. +/// Encode the exit code using `EXIT_FAILURE_FLAG`. const fn exit_code_encode(code: u32) -> u32 { (code << 16) | EXIT_FAILURE_FLAG } @@ -74,9 +67,7 @@ impl RISCV64 { } impl QEMUExit for RISCV64 { - /// Exit qemu with specified exit code. fn exit(&self, code: u32) -> ! { - // If code is not a special value, we need to encode it with EXIT_FAILURE_FLAG. let code_new = match code { EXIT_SUCCESS | EXIT_FAILURE | EXIT_RESET => code, _ => exit_code_encode(code), @@ -85,13 +76,10 @@ impl QEMUExit for RISCV64 { unsafe { asm!( "sw {0}, 0({1})", - in(reg)code_new, in(reg)self.addr + in(reg) code_new, + in(reg) self.addr ); - // For the case that the QEMU exit attempt did not work, transition into an infinite - // loop. Calling `panic!()` here is unfeasible, since there is a good chance - // this function here is the last expression in the `panic!()` handler - // itself. This prevents a possible infinite loop. loop { asm!("wfi", options(nomem, nostack)); } diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs index 0c5f99a2..fe63cbc6 100644 --- a/os/src/platform/riscv/qemu_virt/mod.rs +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -1,7 +1,12 @@ //! QEMU `virt` platform for RISC-V. +mod board; pub mod sbi; +pub use board::{ + BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, +}; pub use sbi::SbiPlatform; pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; diff --git a/rootfs b/rootfs new file mode 160000 index 00000000..1061c13e --- /dev/null +++ b/rootfs @@ -0,0 +1 @@ +Subproject commit 1061c13e89534acb41f61f76ef99400c131f1c40 From 5a37ada7e74fd8adc2ff3a98cb86ea2a2ef10c70 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 11 Jun 2026 11:29:59 +0800 Subject: [PATCH 14/19] fix: Change mmap address for LA; fix `a0` register for LA in signal handling. --- os/src/arch/loongarch64/trap.rs | 4 ++++ os/src/arch/riscv/trap.rs | 4 ++++ os/src/config.rs | 15 ++++----------- os/src/hal/traits.rs | 2 ++ os/src/main.rs | 2 +- os/src/platform/loongarch/qemu_virt/board.rs | 9 +++++++++ os/src/platform/loongarch/qemu_virt/mod.rs | 2 +- os/src/platform/mod.rs | 4 ++-- os/src/platform/riscv/qemu_virt/board.rs | 9 +++++++++ os/src/platform/riscv/qemu_virt/mod.rs | 2 +- os/src/signal/mod.rs | 11 ++++++----- 11 files changed, 43 insertions(+), 21 deletions(-) diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs index 18bd2758..dbe7228a 100644 --- a/os/src/arch/loongarch64/trap.rs +++ b/os/src/arch/loongarch64/trap.rs @@ -309,6 +309,10 @@ impl TrapContextAbi for LoongArchTrapContextAbi { frame.era = signal_gprs[0]; } + fn signal_gpr_arg0_index() -> usize { + 4 // LoongArch: r4 = a0 + } + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { fpregs.copy_from_slice(&frame.f); *fcsr = frame.fcsr as u32; diff --git a/os/src/arch/riscv/trap.rs b/os/src/arch/riscv/trap.rs index 2b0146da..6197644a 100644 --- a/os/src/arch/riscv/trap.rs +++ b/os/src/arch/riscv/trap.rs @@ -229,6 +229,10 @@ impl TrapContextAbi for RiscvTrapContextAbi { frame.sepc = signal_gprs[0]; } + fn signal_gpr_arg0_index() -> usize { + 10 // RISC-V: x10 = a0 + } + fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32) { fpregs.copy_from_slice(&frame.f); *fcsr = frame.fcsr as u32; diff --git a/os/src/config.rs b/os/src/config.rs index 629729e1..a4ef2e8f 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -16,20 +16,13 @@ pub const MEMORY_END: usize = 0xC0000000; pub const PAGE_SIZE: usize = 0x1000; /// page size bits: 12 pub const PAGE_SIZE_BITS: usize = 0xc; -/// default base address for anonymous mmap allocations -pub const USER_MMAP_BASE: usize = 0x1000_0000; -/// fixed load bias used for PIE main executables without an interpreter -pub const USER_PIE_BASE: usize = 0x0020_0000; -/// default base address for the main thread's user stack region -pub const USER_STACK_BASE: usize = 0x0800_0000; -/// base address for loading dynamic linker (interpreter) -/// placed between stack and mmap region to avoid conflicts -pub const INTERP_BASE: usize = 0x4000_0000; + +/// qemu board info +pub use crate::platform::{USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, KERNEL_HEAP_BASE, MMIO, TRAMPOLINE}; + /// the virtual addr of trap context pub const TRAP_CONTEXT_BASE: usize = TRAMPOLINE - PAGE_SIZE; /// 用户态 signal trampoline 页起始地址。 pub const USER_VDSO_BASE: usize = USER_MMAP_BASE - PAGE_SIZE; /// 用户态 rt_sigreturn trampoline 入口地址。 pub const USER_VDSO_RT_SIGRETURN: usize = USER_VDSO_BASE; -/// qemu board info -pub use crate::platform::{CLOCK_FREQ, KERNEL_HEAP_BASE, MMIO, TRAMPOLINE}; diff --git a/os/src/hal/traits.rs b/os/src/hal/traits.rs index 1c0d34bc..bd464b01 100644 --- a/os/src/hal/traits.rs +++ b/os/src/hal/traits.rs @@ -155,6 +155,8 @@ pub trait TrapContextAbi { fn export_signal_gprs(frame: &Self::Frame) -> [usize; 32]; /// Import the Linux-compatible signal GPR layout back into the trap context. fn import_signal_gprs(frame: &mut Self::Frame, signal_gprs: &[usize; 32]); + /// Return the index of the a0 register within the 32-entry signal GPR array + fn signal_gpr_arg0_index() -> usize; /// Copy floating-point state into an external signal frame. fn copy_fp_state_to(frame: &Self::Frame, fpregs: &mut [u64; 32], fcsr: &mut u32); /// Restore floating-point state from an external signal frame. diff --git a/os/src/main.rs b/os/src/main.rs index e1a23a95..a9012e5e 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -238,7 +238,7 @@ fn first_hart_main(hart_id: usize) -> ! { print_boot_stage("storage", "root filesystem mounted"); timer::init_realtime_offset_from_rtc(); print_boot_stage("clock", "realtime source calibrated"); - crate::platform::start_secondary_harts(hart_id); + platform::start_secondary_harts(hart_id); init_local_hart(hart_id); print_boot_stage("scheduler", "bootstrap hart entering run queue"); task::add_initproc(); diff --git a/os/src/platform/loongarch/qemu_virt/board.rs b/os/src/platform/loongarch/qemu_virt/board.rs index b83072fd..d022c138 100644 --- a/os/src/platform/loongarch/qemu_virt/board.rs +++ b/os/src/platform/loongarch/qemu_virt/board.rs @@ -1,5 +1,14 @@ //! Static board description for the LoongArch64 QEMU `virt` machine. +/// default base address for anonymous mmap allocations +pub const USER_MMAP_BASE: usize = 0x20_0000_0000; + +/// default base address for the main thread's user stack region +pub const USER_STACK_BASE: usize = 0x3e_0000_0000; + +/// base address for loading dynamic linker (interpreter) +pub const INTERP_BASE: usize = 0x1e_0000_0000; + /// Direct-mapped uncached I/O virtual-address offset used during early bring-up. pub const IO_ADDR_OFFSET: usize = 0x8000_0000_0000_0000; /// Direct-mapped cached kernel-address offset used during early bring-up. diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs index 8a563246..8980261d 100644 --- a/os/src/platform/loongarch/qemu_virt/mod.rs +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -5,7 +5,7 @@ mod irq; mod pci; pub use board::{ - BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + BlockDeviceImpl, CharDeviceImpl, USER_STACK_BASE, USER_MMAP_BASE, INTERP_BASE, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, }; diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index ada326b6..8afad453 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -15,7 +15,7 @@ pub mod loongarch; #[cfg(target_arch = "riscv64")] pub use riscv::qemu_virt::{ - BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, @@ -27,7 +27,7 @@ pub use riscv::qemu_virt::{ #[cfg(target_arch = "loongarch64")] pub use loongarch::qemu_virt::{ - BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, diff --git a/os/src/platform/riscv/qemu_virt/board.rs b/os/src/platform/riscv/qemu_virt/board.rs index c53ae488..1f0a45d1 100644 --- a/os/src/platform/riscv/qemu_virt/board.rs +++ b/os/src/platform/riscv/qemu_virt/board.rs @@ -1,5 +1,14 @@ //! Static board description for the RISC-V QEMU `virt` machine. +/// default base address for anonymous mmap allocations +pub const USER_MMAP_BASE: usize = 0x1000_0000; + +/// default base address for the main thread's user stack region +pub const USER_STACK_BASE: usize = 0x0800_0000; + +/// base address for loading dynamic linker (interpreter) +pub const INTERP_BASE: usize = 0x4000_0000; + /// Clock frequency. pub const CLOCK_FREQ: usize = 12_500_000; diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs index fe63cbc6..fd9ebf23 100644 --- a/os/src/platform/riscv/qemu_virt/mod.rs +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -4,7 +4,7 @@ mod board; pub mod sbi; pub use board::{ - BlockDeviceImpl, CharDeviceImpl, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, + BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, VIRT_UART, VIRTIO_MMIO_BASE, VIRTIO_MMIO_IRQ_BASE, VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, }; pub use sbi::SbiPlatform; diff --git a/os/src/signal/mod.rs b/os/src/signal/mod.rs index d1b437c4..6cdc7f16 100644 --- a/os/src/signal/mod.rs +++ b/os/src/signal/mod.rs @@ -2,8 +2,7 @@ use crate::{ config::USER_VDSO_RT_SIGRETURN, - hal::ArchTrapMachine, - hal::traits::TrapMachine, + hal::{ArchTrapContextAbi, ArchTrapMachine, traits::{TrapContextAbi, TrapMachine}}, syscall::write_pod_to_user, task::{current_task, current_trap_cx}, }; @@ -246,18 +245,20 @@ pub fn handle_signals() -> Option { let result = trap_cx.syscall_ret() as isize; if result == -(crate::syscall::errno::ERRNO::EINTR as isize) { if trap_cx.restartable_syscall && action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { + let a0_idx = ::signal_gpr_arg0_index(); debug!( - "handle_signals: syscall restart: backing up PC from {:#x} to {:#x}, restoring a0 from {:#x} to {:#x}", + "handle_signals: syscall restart: backing up PC from {:#x} to {:#x}, restoring a0 (gregs[{}]) from {:#x} to {:#x}", mcontext.gregs[0], trap_cx .user_pc() .wrapping_sub(ArchTrapMachine::syscall_instruction_len()), - mcontext.gregs[10], trap_cx.orig_a0 + a0_idx, + mcontext.gregs[a0_idx], trap_cx.orig_a0 ); mcontext.gregs[0] = trap_cx .user_pc() .wrapping_sub(ArchTrapMachine::syscall_instruction_len()); - mcontext.gregs[10] = trap_cx.orig_a0; + mcontext.gregs[a0_idx] = trap_cx.orig_a0; } else if action.sa_flags & SaFlags::SA_RESTART.bits() != 0 { debug!( "handle_signals: syscall returned EINTR but syscall is not restartable, preserving EINTR" From eb17951f4a08dbcad80fd55cbe38defde7c6f7f8 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 11 Jun 2026 16:29:12 +0800 Subject: [PATCH 15/19] feat: Add rtc support for LA. --- os/src/drivers/mod.rs | 12 +- os/src/drivers/rtc.rs | 168 ------------------- os/src/fs/devfs.rs | 2 +- os/src/main.rs | 1 + os/src/platform/loongarch/qemu_virt/board.rs | 9 +- os/src/platform/loongarch/qemu_virt/mod.rs | 84 +++++++++- os/src/platform/loongarch/qemu_virt/rtc.rs | 154 +++++++++++++++++ os/src/platform/mod.rs | 13 ++ os/src/platform/riscv/qemu_virt/mod.rs | 1 + os/src/platform/riscv/qemu_virt/rtc.rs | 82 +++++++++ os/src/timer.rs | 2 +- 11 files changed, 336 insertions(+), 192 deletions(-) delete mode 100644 os/src/drivers/rtc.rs create mode 100644 os/src/platform/loongarch/qemu_virt/rtc.rs create mode 100644 os/src/platform/riscv/qemu_virt/rtc.rs diff --git a/os/src/drivers/mod.rs b/os/src/drivers/mod.rs index dfeb604a..cd3db5c4 100644 --- a/os/src/drivers/mod.rs +++ b/os/src/drivers/mod.rs @@ -1,7 +1,7 @@ //! Reusable device drivers. //! //! Drivers implement one device IP block or transport protocol such as -//! NS16550A, VirtIO, or Goldfish RTC. They should stay as platform-agnostic as +//! NS16550A or VirtIO. They should stay as platform-agnostic as //! practical; the `platform` layer is responsible for deciding which drivers //! are instantiated and how their MMIO ranges and IRQs are routed. @@ -9,18 +9,10 @@ pub mod block; pub mod chardev; pub mod net; pub mod plic; -pub mod rtc; pub mod virtio; pub use block::BLOCK_DEVICE; -fn virtio_blk_name(idx: usize) -> String { - alloc::format!("vd{}", (b'a' + idx as u8) as char) -} - -/// Initialize all drivers (block, char, PLIC, …). +/// Initialize reusable drivers. pub fn init() { chardev::init(); - rtc::init(); - crate::platform::init_external_irq(); - crate::platform::probe_platform_devices(); } diff --git a/os/src/drivers/rtc.rs b/os/src/drivers/rtc.rs deleted file mode 100644 index c19bf30a..00000000 --- a/os/src/drivers/rtc.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Goldfish RTC 驱动。 -// 文档: https://android.googlesource.com/platform/external/qemu/%2B/master/docs/GOLDFISH-VIRTUAL-HARDWARE.TXT -use core::marker::PhantomData; -use core::sync::atomic::{AtomicBool, Ordering}; - -use alloc::sync::Arc; -use lazy_static::lazy_static; - -use crate::platform::VIRT_RTC; -use crate::sync::SpinNoIrqLock; - -/// Goldfish RTC 时间低 32 位寄存器。 -const REG_TIME_LOW: usize = 0x00; -/// Goldfish RTC 时间高 32 位寄存器。 -const REG_TIME_HIGH: usize = 0x04; -/// Goldfish RTC 闹钟低 32 位寄存器。 -const REG_ALARM_LOW: usize = 0x08; -/// Goldfish RTC 闹钟高 32 位寄存器。 -const REG_ALARM_HIGH: usize = 0x0c; -/// Goldfish RTC 中断清除寄存器。 -const REG_CLEAR_INTERRUPT: usize = 0x10; - -/// 简单的 MMIO 寄存器访问封装。 -#[derive(Copy, Clone)] -struct Mmio { - addr: *mut T, - _pd: PhantomData, -} - -impl Mmio { - /// 根据 MMIO 地址创建访问句柄。 - const fn new(addr: usize) -> Self { - Self { - addr: addr as *mut T, - _pd: PhantomData, - } - } -} - -impl Mmio { - /// 以 volatile 方式读取寄存器。 - fn read(&self) -> T { - unsafe { core::ptr::read_volatile(self.addr) } - } -} - -impl Mmio { - /// 以 volatile 方式写寄存器。 - fn write(&self, value: T) { - unsafe { core::ptr::write_volatile(self.addr, value) } - } -} - -/// Goldfish RTC 原始寄存器访问器。 -struct GoldfishRtcRaw { - base_addr: usize, -} - -impl GoldfishRtcRaw { - /// 创建一个绑定到给定基址的 RTC 访问器。 - const fn new(base_addr: usize) -> Self { - Self { base_addr } - } - - /// 读取一个 32 位寄存器。 - fn reg(&self, offset: usize) -> Mmio { - Mmio::new(self.base_addr + offset) - } - - /// 初始化 RTC 设备。 - fn init(&self) { - // Goldfish RTC 在 virt 机型上默认可直接读时间;这里主动清一次中断状态。 - self.reg(REG_CLEAR_INTERRUPT).write(1); - // 兼容旧实现中暴露出来但当前未使用的寄存器,避免后续误判未映射。 - let _ = self.reg(REG_ALARM_LOW); - let _ = self.reg(REG_ALARM_HIGH); - } - - /// 读取当前 RTC 时间,单位为纳秒。 - fn read_time_ns(&self) -> u64 { - // 按设备规范必须先读 TIME_LOW,再读 TIME_HIGH,后者返回前一次低位读取对应的高位快照。 - let low = self.reg(REG_TIME_LOW).read() as u64; - let high = self.reg(REG_TIME_HIGH).read() as u64; - (high << 32) | low - } - - /// 写入当前 RTC 时间,单位为纳秒。 - fn write_time_ns(&self, time_ns: u64) { - // TODO:Goldfish RTC 对 TIME_LOW/TIME_HIGH 的写入不是原子的; - // 这里先提供一个“尽力设置当前时间”的接口,后续若用于严格校时, - // 需要增加回读校验或改为更高层 offset 方案。 - self.reg(REG_TIME_LOW).write(time_ns as u32); - self.reg(REG_TIME_HIGH).write((time_ns >> 32) as u32); - } -} - -/// RTC 驱动实例的内部状态。 -struct GoldfishRtc { - raw: GoldfishRtcRaw, -} - -impl GoldfishRtc { - /// 创建一个新的 Goldfish RTC 驱动实例。 - fn new(base_addr: usize) -> Self { - Self { - raw: GoldfishRtcRaw::new(base_addr), - } - } - - /// 初始化底层硬件状态。 - fn init(&self) { - self.raw.init(); - } - - /// 读取 RTC 当前时间,单位为纳秒。 - fn read_time_ns(&self) -> u64 { - self.raw.read_time_ns() - } - - /// 写入 RTC 当前时间,单位为纳秒。 - fn write_time_ns(&self, time_ns: u64) { - self.raw.write_time_ns(time_ns); - } -} - -lazy_static! { - /// 全局 RTC 驱动实例。 - static ref RTC: Arc> = - Arc::new(SpinNoIrqLock::new(GoldfishRtc::new(VIRT_RTC))); -} - -/// 标记 RTC 是否已完成初始化。 -static RTC_READY: AtomicBool = AtomicBool::new(false); - -/// 初始化全局 RTC 驱动。 -pub fn init() { - if !crate::platform::rtc_is_supported() { - warn!("rtc init skipped on this platform"); - return; - } - let rtc = RTC.lock(); - rtc.init(); - let time_ns = rtc.read_time_ns(); - drop(rtc); - // Mix RTC-derived timestamp into kernel entropy pool so getrandom can seed early. - crate::random::add_entropy(&time_ns.to_le_bytes()); - RTC_READY.store(true, Ordering::Release); - info!( - "rtc init done, realtime = {}.{:09} s", - time_ns / 1_000_000_000, - time_ns % 1_000_000_000 - ); -} - -/// 返回 RTC 是否已完成初始化。 -pub fn rtc_ready() -> bool { - RTC_READY.load(Ordering::Acquire) -} - -/// 读取当前 RTC 时间,单位为纳秒。 -pub fn read_time_ns() -> u64 { - RTC.lock().read_time_ns() -} - -/// 写入当前 RTC 时间,单位为纳秒。 -pub fn write_time_ns(time_ns: u64) { - RTC.lock().write_time_ns(time_ns); -} diff --git a/os/src/fs/devfs.rs b/os/src/fs/devfs.rs index f8788775..91e5d3a9 100644 --- a/os/src/fs/devfs.rs +++ b/os/src/fs/devfs.rs @@ -18,10 +18,10 @@ use fs::vfs::{VfsFileType, VfsNode}; use fs::{BlockDevice, STATFS_MAGIC_TMPFS, STATFS_NAMELEN_DEFAULT}; use crate::drivers::block::BLOCK_DEVICES; -use crate::drivers::rtc; use crate::fs::{Stat, StatMode}; use super::{empty_statfs, StatFs64}; use crate::mm::translated_ref; +use crate::platform::rtc; use crate::syscall::errno::ERRNO; use crate::syscall::{write_pod_to_user, Pod}; use crate::task::current_user_token; diff --git a/os/src/main.rs b/os/src/main.rs index a9012e5e..d2074698 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -231,6 +231,7 @@ fn first_hart_main(hart_id: usize) -> ! { info!("hart {} boot", hart_id); info!("hart {} elected as bootstrap hart", hart_id); drivers::init(); + platform::init(); print_boot_stage("devices", "virtio buses enumerated"); net::init(); print_boot_stage("network", "smoltcp stack synchronized"); diff --git a/os/src/platform/loongarch/qemu_virt/board.rs b/os/src/platform/loongarch/qemu_virt/board.rs index d022c138..20d76b23 100644 --- a/os/src/platform/loongarch/qemu_virt/board.rs +++ b/os/src/platform/loongarch/qemu_virt/board.rs @@ -19,14 +19,15 @@ pub const CLOCK_FREQ: usize = 100_000_000; /// MMIO windows used by the kernel on QEMU loongarch64 `virt` (uncached DMW0 window). pub const MMIO: &[(usize, usize)] = &[ - (IO_ADDR_OFFSET | 0x1fe0_0000, 0x10000), // covers all 1fe0_xxxx MMIO - (IO_ADDR_OFFSET | 0x1fe2_0000, 0x8000), // VirtIO + (IO_ADDR_OFFSET | 0x1000_0000, 0x100000), // LS7A bridge (RTC at 0x100d0100, GED at 0x100e001c) + (IO_ADDR_OFFSET | 0x1fe0_0000, 0x10000), // covers all 1fe0_xxxx MMIO + (IO_ADDR_OFFSET | 0x1fe2_0000, 0x8000), // VirtIO ]; /// UART MMIO virtual address (uncached DMW0 window). pub const VIRT_UART: usize = IO_ADDR_OFFSET | 0x1fe0_01e0; -/// RTC-compatible MMIO virtual address (uncached DMW0 window). -pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x1fe0_01f8; +/// LS7A RTC MMIO virtual address (uncached DMW0 window). +pub const VIRT_RTC: usize = IO_ADDR_OFFSET | 0x100d_0100; /// VirtIO MMIO window base address. pub const VIRTIO_MMIO_BASE: usize = IO_ADDR_OFFSET | 0x1fe2_0000; /// Size of each VirtIO MMIO slot. diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs index 8980261d..54db1627 100644 --- a/os/src/platform/loongarch/qemu_virt/mod.rs +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -3,6 +3,7 @@ mod board; mod irq; mod pci; +pub mod rtc; pub use board::{ BlockDeviceImpl, CharDeviceImpl, USER_STACK_BASE, USER_MMAP_BASE, INTERP_BASE, CLOCK_FREQ, IO_ADDR_OFFSET, KERNEL_ADDR_OFFSET, MMIO, @@ -37,13 +38,69 @@ impl Timer for LoongArchPlatform { } } +const IOCSR_IPI_SEND: usize = 0x1040; +const IOCSR_MBUF_SEND: usize = 0x1048; + +const IOCSR_IPI_SEND_BLOCKING: u32 = 1 << 31; +const IOCSR_IPI_SEND_CPU_SHIFT: u32 = 16; + +const IOCSR_MBUF_SEND_BLOCKING: u64 = 1 << 31; +const IOCSR_MBUF_SEND_BOX_SHIFT: u64 = 2; +const IOCSR_MBUF_SEND_CPU_SHIFT: u64 = 16; +const IOCSR_MBUF_SEND_BUF_SHIFT: u64 = 32; +const IOCSR_MBUF_SEND_H32_MASK: u64 = 0xffff_ffff_0000_0000; + +// ACTION_BOOT_CPU matches Linux's SMP_RESCHEDULE_YOURSELF bit used for boot IPI +const ACTION_BOOT_CPU: u32 = 1; +const ACTION_RESCHEDULE: u32 = 1; + +#[inline] +unsafe fn iocsr_write32(addr: usize, val: u32) { + core::arch::asm!("iocsrwr.w {v}, {a}", v = in(reg) val, a = in(reg) addr); +} + +#[inline] +unsafe fn iocsr_write64(addr: usize, val: u64) { + core::arch::asm!("iocsrwr.d {v}, {a}", v = in(reg) val, a = in(reg) addr); +} + +fn ipi_send(hart_id: usize, action: u32) { + let val = IOCSR_IPI_SEND_BLOCKING | action | (hart_id as u32) << IOCSR_IPI_SEND_CPU_SHIFT; + unsafe { iocsr_write32(IOCSR_IPI_SEND, val) }; +} + +// Sends a 64-bit value to the target hart's MBUF0 mailbox, split into two 32-bit writes. +fn mail_send(data: u64, hart_id: usize, mailbox: u64) { + let cpu = (hart_id as u64) << IOCSR_MBUF_SEND_CPU_SHIFT; + // high 32 bits + let hi = IOCSR_MBUF_SEND_BLOCKING + | (((mailbox << 1) + 1) << IOCSR_MBUF_SEND_BOX_SHIFT) + | cpu + | (data & IOCSR_MBUF_SEND_H32_MASK); + // low 32 bits + let lo = IOCSR_MBUF_SEND_BLOCKING + | ((mailbox << 1) << IOCSR_MBUF_SEND_BOX_SHIFT) + | cpu + | (data << IOCSR_MBUF_SEND_BUF_SHIFT); + unsafe { + iocsr_write64(IOCSR_MBUF_SEND, hi); + iocsr_write64(IOCSR_MBUF_SEND, lo); + } +} + impl HartCtrl for LoongArchPlatform { - fn start_hart(_hart_id: usize, _start_addr: usize, _opaque: usize) -> Result<(), ()> { - Err(()) + fn start_hart(hart_id: usize, start_addr: usize, _opaque: usize) -> Result<(), ()> { + mail_send(start_addr as u64, hart_id, 0); + ipi_send(hart_id, ACTION_BOOT_CPU); + Ok(()) } - fn send_ipi(_hart_mask: usize) { - // Single-core bring-up only for now. + fn send_ipi(hart_mask: usize) { + for hart_id in 0..usize::BITS as usize { + if hart_mask & (1 << hart_id) != 0 { + ipi_send(hart_id, ACTION_RESCHEDULE); + } + } } } @@ -82,8 +139,19 @@ pub fn machine_name() -> &'static str { "loongarch64" } -/// Secondary-hart start-up is not wired up on this platform yet. -pub fn start_secondary_harts(_bootstrap_hart_id: usize) {} +/// Start all secondary harts via IOCSR mailbox + IPI. +pub fn start_secondary_harts(bootstrap_hart_id: usize) { + extern "C" { fn _start(); } + // The firmware polls IOCSR_MBUF0 for a physical address, so strip the DMW offset. + let entry_phys = (_start as usize) & !KERNEL_ADDR_OFFSET; + for hart_id in 0..crate::config::MAX_HARTS { + if hart_id == bootstrap_hart_id { + continue; + } + let _ = ::start_hart(hart_id, entry_phys, 0); + info!("hart {} requested startup for hart {}", bootstrap_hart_id, hart_id); + } +} /// Translate one direct-mapped physical address into the kernel VA used on this platform. pub fn direct_map_phys_to_virt(pa: usize) -> usize { @@ -111,9 +179,9 @@ pub fn mmio_phys_to_virt(paddr: usize) -> usize { paddr | IO_ADDR_OFFSET } -/// Whether the Goldfish RTC is supported on this platform. +/// Whether the RTC is supported on this platform. pub fn rtc_is_supported() -> bool { - false + true } /// Whether the kernel heap may grow inside its dedicated virtual window. diff --git a/os/src/platform/loongarch/qemu_virt/rtc.rs b/os/src/platform/loongarch/qemu_virt/rtc.rs new file mode 100644 index 00000000..2ab55b2c --- /dev/null +++ b/os/src/platform/loongarch/qemu_virt/rtc.rs @@ -0,0 +1,154 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::sync::Arc; +use lazy_static::lazy_static; + +use crate::sync::SpinNoIrqLock; + +use super::VIRT_RTC; + +const TOY_TRIM: usize = 0x20; +const TOY_WRITE0: usize = 0x24; +const TOY_WRITE1: usize = 0x28; +const TOY_READ0: usize = 0x2c; +const TOY_READ1: usize = 0x30; +const RTC_CTRL: usize = 0x40; + +#[inline(always)] +fn mmio_read32(addr: usize) -> u32 { + unsafe { core::ptr::read_volatile(addr as *const u32) } +} + +#[inline(always)] +fn mmio_write32(addr: usize, val: u32) { + unsafe { core::ptr::write_volatile(addr as *mut u32, val) } +} + +struct Rtc { + base: usize, +} + +impl Rtc { + fn new(base: usize) -> Self { + Self { base } + } + + fn init(&self) { + mmio_write32(self.base + TOY_TRIM, 0); + let ctrl = mmio_read32(self.base + RTC_CTRL); + mmio_write32(self.base + RTC_CTRL, ctrl | (1 << 11) | (1 << 8)); + } + + fn read_time_ns(&self) -> u64 { + let read0 = mmio_read32(self.base + TOY_READ0); + let raw_year = mmio_read32(self.base + TOY_READ1) as u64; + let year = if raw_year < 1900 { + raw_year + 1900 + } else { + raw_year + }; + warn!("ls7a-rtc raw: TOY_READ0={:#010x} TOY_READ1={}", read0, raw_year); + let mon = ((read0 >> 26) & 0x3f) as u64; + let day = ((read0 >> 21) & 0x1f) as u64; + let hour = ((read0 >> 16) & 0x1f) as u64; + let min = ((read0 >> 10) & 0x3f) as u64; + let sec = ((read0 >> 4) & 0x3f) as u64; + let days = days_since_epoch(year, mon, day); + (days * 86_400 + hour * 3_600 + min * 60 + sec) * 1_000_000_000 + } + + fn write_time_ns(&self, time_ns: u64) { + let secs = time_ns / 1_000_000_000; + let (year, mon, day, hour, min, sec) = secs_to_calendar(secs); + let read0 = ((mon as u32) << 26) + | ((day as u32) << 21) + | ((hour as u32) << 16) + | ((min as u32) << 10) + | ((sec as u32) << 4); + mmio_write32(self.base + TOY_WRITE0, read0); + let write_year = if year >= 1900 { year - 1900 } else { year }; + mmio_write32(self.base + TOY_WRITE1, write_year as u32); + } +} + +fn days_since_epoch(year: u64, mon: u64, day: u64) -> u64 { + const DAYS_BEFORE_MONTH: [u64; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + let month = mon.max(1); + let day = day.max(1); + let leap_days = (year / 4).saturating_sub(year / 100) + year / 400; + let base = year * 365 + leap_days; + let is_leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + let leap_adjust = if month > 2 && is_leap { 1 } else { 0 }; + let year_days = base + DAYS_BEFORE_MONTH[month as usize] + leap_adjust + day - 1; + const EPOCH_DAYS: u64 = 1970 * 365 + (1970 / 4) - (1970 / 100) + (1970 / 400); + year_days.saturating_sub(EPOCH_DAYS) +} + +fn secs_to_calendar(secs: u64) -> (u64, u64, u64, u64, u64, u64) { + const DAYS_IN_MONTH: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + let sec = secs % 60; + let min = (secs / 60) % 60; + let hour = (secs / 3_600) % 24; + let mut days = secs / 86_400; + let mut year = 1970u64; + loop { + let days_in_year = if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 { + 366 + } else { + 365 + }; + if days < days_in_year { + break; + } + days -= days_in_year; + year += 1; + } + let is_leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + let mut mon = 1u64; + for (idx, days_in_month) in DAYS_IN_MONTH.iter().enumerate() { + let days_in_month = *days_in_month + if idx == 1 && is_leap { 1 } else { 0 }; + if days < days_in_month { + mon = idx as u64 + 1; + break; + } + days -= days_in_month; + } + (year, mon, days + 1, hour, min, sec) +} + +lazy_static! { + static ref RTC: Arc> = Arc::new(SpinNoIrqLock::new(Rtc::new(VIRT_RTC))); +} + +static RTC_READY: AtomicBool = AtomicBool::new(false); + +pub fn init() { + if !super::rtc_is_supported() { + error!("rtc init skipped on this platform"); + return; + } + let rtc = RTC.lock(); + rtc.init(); + let time_ns = rtc.read_time_ns(); + drop(rtc); + crate::random::add_entropy(&time_ns.to_le_bytes()); + RTC_READY.store(true, Ordering::Release); + warn!( + "rtc init done, realtime = {}.{:09} s", + time_ns / 1_000_000_000, + time_ns % 1_000_000_000 + ); +} + +pub fn rtc_ready() -> bool { + RTC_READY.load(Ordering::Acquire) +} + +pub fn read_time_ns() -> u64 { + RTC.lock().read_time_ns() +} + +pub fn write_time_ns(time_ns: u64) { + RTC.lock().write_time_ns(time_ns); +} diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index 8afad453..d08f2b50 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -13,6 +13,12 @@ pub mod riscv; #[cfg(target_arch = "loongarch64")] pub mod loongarch; +#[cfg(target_arch = "riscv64")] +pub use riscv::qemu_virt::rtc; + +#[cfg(target_arch = "loongarch64")] +pub use loongarch::qemu_virt::rtc; + #[cfg(target_arch = "riscv64")] pub use riscv::qemu_virt::{ BlockDeviceImpl, CharDeviceImpl, USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, MMIO, QEMUExit, QEMU_EXIT_HANDLE, VIRT_RTC, @@ -37,3 +43,10 @@ pub use loongarch::qemu_virt::{ start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, }; + +/// Initialize platform-owned devices and interrupt routing. +pub fn init() { + rtc::init(); + init_external_irq(); + probe_platform_devices(); +} diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs index fd9ebf23..c91cfabf 100644 --- a/os/src/platform/riscv/qemu_virt/mod.rs +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -1,6 +1,7 @@ //! QEMU `virt` platform for RISC-V. mod board; +pub mod rtc; pub mod sbi; pub use board::{ diff --git a/os/src/platform/riscv/qemu_virt/rtc.rs b/os/src/platform/riscv/qemu_virt/rtc.rs new file mode 100644 index 00000000..d0222896 --- /dev/null +++ b/os/src/platform/riscv/qemu_virt/rtc.rs @@ -0,0 +1,82 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::sync::Arc; +use lazy_static::lazy_static; + +use crate::sync::SpinNoIrqLock; + +use super::VIRT_RTC; + +#[inline(always)] +fn mmio_read32(addr: usize) -> u32 { + unsafe { core::ptr::read_volatile(addr as *const u32) } +} + +#[inline(always)] +fn mmio_write32(addr: usize, val: u32) { + unsafe { core::ptr::write_volatile(addr as *mut u32, val) } +} + +struct Rtc { + base: usize, +} + +impl Rtc { + fn new(base: usize) -> Self { + Self { base } + } + + fn init(&self) { + // Clear any pending interrupt. + mmio_write32(self.base + 0x10, 1); + } + + fn read_time_ns(&self) -> u64 { + // Must read LOW first; HIGH then latches to match that read. + let low = mmio_read32(self.base + 0x00) as u64; + let high = mmio_read32(self.base + 0x04) as u64; + (high << 32) | low + } + + fn write_time_ns(&self, time_ns: u64) { + // TODO: writes are non-atomic on Goldfish RTC. + mmio_write32(self.base + 0x00, time_ns as u32); + mmio_write32(self.base + 0x04, (time_ns >> 32) as u32); + } +} + +lazy_static! { + static ref RTC: Arc> = Arc::new(SpinNoIrqLock::new(Rtc::new(VIRT_RTC))); +} + +static RTC_READY: AtomicBool = AtomicBool::new(false); + +pub fn init() { + if !super::rtc_is_supported() { + error!("rtc init skipped on this platform"); + return; + } + let rtc = RTC.lock(); + rtc.init(); + let time_ns = rtc.read_time_ns(); + drop(rtc); + crate::random::add_entropy(&time_ns.to_le_bytes()); + RTC_READY.store(true, Ordering::Release); + warn!( + "rtc init done, realtime = {}.{:09} s", + time_ns / 1_000_000_000, + time_ns % 1_000_000_000 + ); +} + +pub fn rtc_ready() -> bool { + RTC_READY.load(Ordering::Acquire) +} + +pub fn read_time_ns() -> u64 { + RTC.lock().read_time_ns() +} + +pub fn write_time_ns(time_ns: u64) { + RTC.lock().write_time_ns(time_ns); +} diff --git a/os/src/timer.rs b/os/src/timer.rs index 8644a0e8..6e2cafdd 100644 --- a/os/src/timer.rs +++ b/os/src/timer.rs @@ -5,10 +5,10 @@ use core::sync::atomic::{AtomicI64, Ordering as AtomicOrdering}; use crate::config::CLOCK_FREQ; use crate::config::MAX_HARTS; -use crate::drivers::rtc; use crate::hal::hartid; use crate::hal::Plat; use crate::hal::traits::Timer as _; +use crate::platform::rtc; use crate::poll::{self, PollTimerTag}; use crate::net::{handle_socket_wait_timeout, SocketTimerTag}; use crate::signal::{handle_signal_wait_timeout, SignalTimerTag}; From 2972b7e986daf7ba40c301f7d7af98e04c79db3a Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 12 Jun 2026 07:33:40 +0800 Subject: [PATCH 16/19] feat: Add SMP support for LA. --- bootloader/loongarch64-direct/src/main.rs | 6 +- os/src/arch/loongarch64/entry.S | 32 +++---- os/src/arch/loongarch64/trap.rs | 3 +- os/src/main.rs | 3 +- os/src/platform/loongarch/qemu_virt/mod.rs | 98 ++++++++++++++-------- os/src/platform/mod.rs | 16 +++- os/src/trap/mod.rs | 1 + 7 files changed, 99 insertions(+), 60 deletions(-) diff --git a/bootloader/loongarch64-direct/src/main.rs b/bootloader/loongarch64-direct/src/main.rs index cc637787..3d2d1288 100644 --- a/bootloader/loongarch64-direct/src/main.rs +++ b/bootloader/loongarch64-direct/src/main.rs @@ -39,8 +39,10 @@ pub unsafe extern "C" fn _start() -> ! { #[unsafe(no_mangle)] extern "C" fn boot_main(hart_id: usize) -> ! { - puts("[boot] loongarch64 direct loader\r\n"); - puts("[boot] jumping to kernel @ 0x90000000\r\n"); + if hart_id == 0 { + puts("[boot] loongarch64 direct loader\r\n"); + puts("[boot] jumping to kernel @ 0x90000000\r\n"); + } unsafe { let entry: extern "C" fn(usize) -> ! = core::mem::transmute(KERNEL_ENTRY); entry(hart_id); diff --git a/os/src/arch/loongarch64/entry.S b/os/src/arch/loongarch64/entry.S index bb75837f..9d2a1aa3 100644 --- a/os/src/arch/loongarch64/entry.S +++ b/os/src/arch/loongarch64/entry.S @@ -2,18 +2,6 @@ .equ BOOT_STACK_SHIFT, 20 .equ BOOT_STACK_SIZE, 1 << BOOT_STACK_SHIFT .equ BOOT_STACK_HARTS, 8 - .equ UART_BASE_EARLY, 0x800000001fe001e0 - .equ UART_THR, 0x0 - .equ UART_LSR, 0x5 - .equ UART_LSR_THRE, 0x20 - - .macro uart_putc reg -1: - ld.bu $t4, $t3, UART_LSR - andi $t4, $t4, UART_LSR_THRE - beqz $t4, 1b - st.b \reg, $t3, UART_THR - .endm .globl _start _start: @@ -24,32 +12,36 @@ _start: lu52i.d $t0, $t0, -1792 csrwr $t0, 0x181 - csrrd $a0, 0x20 - bnez $a0, 1f - li.w $t0, 0xb0 csrwr $t0, 0x0 li.w $t0, 0x00 csrwr $t0, 0x1 csrwr $t0, 0x2 - li.d $t3, UART_BASE_EARLY - li.w $t5, 'B' - uart_putc $t5 + csrrd $a0, 0x20 + # Do not branch to a numeric local label here: `uart_putc` expands its + # own `1:` label, and secondaries would jump into the middle of the + # bootstrap-hart UART polling sequence with an uninitialized `$t3`. + bnez $a0, .Lsecondary_start la.global $sp, boot_stack_lower_bound li.d $t2, BOOT_STACK_SIZE add.d $sp, $sp, $t2 bl rust_main -1: +.Lsecondary_start: la.global $sp, boot_stack_lower_bound addi.d $t1, $a0, 1 slli.d $t0, $t1, BOOT_STACK_SHIFT add.d $sp, $sp, $t0 bl rust_main - .section .bss.stack + # Keep early boot stacks out of `.bss`: secondary harts may start running + # while the bootstrap hart is still in the one-time memory-init path, so + # using a preloaded data section lets us rule out any interaction with + # zeroing or other `.bss`-specific handling during bring-up. + .section .data.bootstack + .balign 16 .globl boot_stack_lower_bound boot_stack_lower_bound: .space BOOT_STACK_SIZE * BOOT_STACK_HARTS diff --git a/os/src/arch/loongarch64/trap.rs b/os/src/arch/loongarch64/trap.rs index dbe7228a..c7d1288e 100644 --- a/os/src/arch/loongarch64/trap.rs +++ b/os/src/arch/loongarch64/trap.rs @@ -94,6 +94,7 @@ impl InterruptControl for LoongArchInterruptControl { unsafe fn enable_software() { update_ecfg(ECFG_SIP, true); + update_ecfg(ECFG_IPI, true); } unsafe fn clear_software_pending() { @@ -357,7 +358,7 @@ fn decode_interrupt_cause(estat: usize, ecfg: usize) -> TrapCause { let int_vec = (estat & ecfg) & 0x1fff; let highest = (0..=12).rev().find(|bit| int_vec & (1usize << bit) != 0); match highest { - Some(12) => TrapCause::Unknown, + Some(12) => TrapCause::SoftwareInterrupt, Some(11) => TrapCause::TimerInterrupt, Some(1) | Some(0) => TrapCause::SoftwareInterrupt, Some(2..=9) => TrapCause::ExternalInterrupt, diff --git a/os/src/main.rs b/os/src/main.rs index d2074698..b4336466 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -170,6 +170,7 @@ fn print_boot_stage(stage: &str, detail: &str) { fn init_local_hart(hart_id: usize) { trap::init_hart(); timer::init_hart(); + crate::platform::init_local_hart(); crate::platform::init_external_irq_hart(hart_id); mm::mark_online(hart_id); debug!("hart {} local init done", hart_id); @@ -257,7 +258,7 @@ fn first_hart_main(hart_id: usize) -> ! { fn secondary_hart_main(hart_id: usize) -> ! { wait_for_bootstrap(); mm::activate_kernel_space(); // 激活内核页表:但 satp 是 per-hart 寄存器 - info!("hart {} boot", hart_id); + info!("hart {} entered secondary_hart_main", hart_id); init_local_hart(hart_id); debug!("hart {} entered scheduler", hart_id); sched::run_tasks(); diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs index 54db1627..a92bc116 100644 --- a/os/src/platform/loongarch/qemu_virt/mod.rs +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -39,20 +39,18 @@ impl Timer for LoongArchPlatform { } const IOCSR_IPI_SEND: usize = 0x1040; +const IOCSR_IPI_EN: usize = 0x1004; +const IOCSR_IPI_CLEAR: usize = 0x100c; const IOCSR_MBUF_SEND: usize = 0x1048; -const IOCSR_IPI_SEND_BLOCKING: u32 = 1 << 31; const IOCSR_IPI_SEND_CPU_SHIFT: u32 = 16; - -const IOCSR_MBUF_SEND_BLOCKING: u64 = 1 << 31; -const IOCSR_MBUF_SEND_BOX_SHIFT: u64 = 2; const IOCSR_MBUF_SEND_CPU_SHIFT: u64 = 16; -const IOCSR_MBUF_SEND_BUF_SHIFT: u64 = 32; -const IOCSR_MBUF_SEND_H32_MASK: u64 = 0xffff_ffff_0000_0000; +const IOCSR_MBUF_SEND_DATA_SHIFT: u64 = 32; -// ACTION_BOOT_CPU matches Linux's SMP_RESCHEDULE_YOURSELF bit used for boot IPI -const ACTION_BOOT_CPU: u32 = 1; -const ACTION_RESCHEDULE: u32 = 1; +// QEMU's LoongArch IPI model treats IOCSR_IPI_SEND[4:0] as a vector number, +// and the per-core enable/clear registers as vector bitmasks. +const IPI_VECTOR_WAKEUP: u32 = 1; +const IPI_VECTOR_MASK: u32 = 1 << IPI_VECTOR_WAKEUP; #[inline] unsafe fn iocsr_write32(addr: usize, val: u32) { @@ -64,41 +62,52 @@ unsafe fn iocsr_write64(addr: usize, val: u64) { core::arch::asm!("iocsrwr.d {v}, {a}", v = in(reg) val, a = in(reg) addr); } -fn ipi_send(hart_id: usize, action: u32) { - let val = IOCSR_IPI_SEND_BLOCKING | action | (hart_id as u32) << IOCSR_IPI_SEND_CPU_SHIFT; +fn ipi_send(hart_id: usize, vector: u32) { + let val = vector | (hart_id as u32) << IOCSR_IPI_SEND_CPU_SHIFT; unsafe { iocsr_write32(IOCSR_IPI_SEND, val) }; } -// Sends a 64-bit value to the target hart's MBUF0 mailbox, split into two 32-bit writes. +fn enable_ipi() { + unsafe { iocsr_write32(IOCSR_IPI_EN, IPI_VECTOR_MASK) }; +} + +fn clear_ipi(vector: u32) { + unsafe { iocsr_write32(IOCSR_IPI_CLEAR, 1 << vector) }; +} + +#[inline] +fn mailbox_word_slot(mailbox: u64, upper_half: bool) -> u64 { + debug_assert!(mailbox < 4); + (mailbox << 3) | ((upper_half as u64) << 2) +} + +#[inline] +fn mail_send_word(word: u32, hart_id: usize, slot: u64) { + let val = ((word as u64) << IOCSR_MBUF_SEND_DATA_SHIFT) + | ((hart_id as u64) << IOCSR_MBUF_SEND_CPU_SHIFT) + | slot; + unsafe { iocsr_write64(IOCSR_MBUF_SEND, val) }; +} + +// QEMU decodes MAIL_SEND as one 32-bit mailbox-slot write per request. To +// publish a 64-bit entry address in MBUF0, we must write its low/high halves +// into CORE_BUF_20 and CORE_BUF_24 separately. fn mail_send(data: u64, hart_id: usize, mailbox: u64) { - let cpu = (hart_id as u64) << IOCSR_MBUF_SEND_CPU_SHIFT; - // high 32 bits - let hi = IOCSR_MBUF_SEND_BLOCKING - | (((mailbox << 1) + 1) << IOCSR_MBUF_SEND_BOX_SHIFT) - | cpu - | (data & IOCSR_MBUF_SEND_H32_MASK); - // low 32 bits - let lo = IOCSR_MBUF_SEND_BLOCKING - | ((mailbox << 1) << IOCSR_MBUF_SEND_BOX_SHIFT) - | cpu - | (data << IOCSR_MBUF_SEND_BUF_SHIFT); - unsafe { - iocsr_write64(IOCSR_MBUF_SEND, hi); - iocsr_write64(IOCSR_MBUF_SEND, lo); - } + mail_send_word(data as u32, hart_id, mailbox_word_slot(mailbox, false)); + mail_send_word((data >> 32) as u32, hart_id, mailbox_word_slot(mailbox, true)); } impl HartCtrl for LoongArchPlatform { fn start_hart(hart_id: usize, start_addr: usize, _opaque: usize) -> Result<(), ()> { mail_send(start_addr as u64, hart_id, 0); - ipi_send(hart_id, ACTION_BOOT_CPU); + ipi_send(hart_id, IPI_VECTOR_WAKEUP); Ok(()) } fn send_ipi(hart_mask: usize) { for hart_id in 0..usize::BITS as usize { if hart_mask & (1 << hart_id) != 0 { - ipi_send(hart_id, ACTION_RESCHEDULE); + ipi_send(hart_id, IPI_VECTOR_WAKEUP); } } } @@ -141,18 +150,39 @@ pub fn machine_name() -> &'static str { /// Start all secondary harts via IOCSR mailbox + IPI. pub fn start_secondary_harts(bootstrap_hart_id: usize) { - extern "C" { fn _start(); } - // The firmware polls IOCSR_MBUF0 for a physical address, so strip the DMW offset. - let entry_phys = (_start as usize) & !KERNEL_ADDR_OFFSET; + extern "C" { + fn _start(); + } + + // Under direct boot, CPU0 reaches the kernel through our tiny bootloader, + // which jumps to the high-half DMW alias of `_start`. QEMU's secondary + // slave stub simply `jirl`s to the mailbox value, so feeding it the raw + // physical address would drop APs outside the cached DMW window right + // after wakeup. + let entry = _start as usize; + enable_ipi(); for hart_id in 0..crate::config::MAX_HARTS { if hart_id == bootstrap_hart_id { continue; } - let _ = ::start_hart(hart_id, entry_phys, 0); - info!("hart {} requested startup for hart {}", bootstrap_hart_id, hart_id); + let _ = ::start_hart(hart_id, entry, 0); + warn!( + "hart {} requested startup for hart {} at {:#x}", + bootstrap_hart_id, hart_id, entry + ); } } +/// Initialize per-hart IPI receive state. +pub fn init_ipi_hart() { + enable_ipi(); +} + +/// Clear the wake/reschedule IPI vector on the current hart. +pub fn clear_ipi_vector() { + clear_ipi(IPI_VECTOR_WAKEUP); +} + /// Translate one direct-mapped physical address into the kernel VA used on this platform. pub fn direct_map_phys_to_virt(pa: usize) -> usize { pa | KERNEL_ADDR_OFFSET diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index d08f2b50..7cef8cfa 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -38,8 +38,8 @@ pub use loongarch::qemu_virt::{ VIRTIO_MMIO_SLOTS, VIRTIO_MMIO_STRIDE, console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, init_external_irq, - init_external_irq_hart, kernel_heap_virtual_window_supported, machine_name, - mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + init_external_irq_hart, init_ipi_hart, kernel_heap_virtual_window_supported, machine_name, + mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, clear_ipi_vector, start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, }; @@ -50,3 +50,15 @@ pub fn init() { init_external_irq(); probe_platform_devices(); } + +/// Initialize per-hart platform-owned local interrupt/IPI state. +pub fn init_local_hart() { + #[cfg(target_arch = "loongarch64")] + init_ipi_hart(); +} + +/// Clear the current hart's pending platform IPI state when applicable. +pub fn clear_ipi() { + #[cfg(target_arch = "loongarch64")] + clear_ipi_vector(); +} diff --git a/os/src/trap/mod.rs b/os/src/trap/mod.rs index 6fedba0e..0fbd50ea 100644 --- a/os/src/trap/mod.rs +++ b/os/src/trap/mod.rs @@ -147,6 +147,7 @@ pub fn clear_software_interrupt_pending() { /// so the idle loop can observe newly queued work on the next iteration. fn handle_reschedule_ipi() { handle_ipi(); + crate::platform::clear_ipi(); clear_software_interrupt_pending(); request_current_task_resched(ReschedReason::HigherRtPriority); } From b8c4f33f52742475e26618ed1502184718045337 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 12 Jun 2026 22:17:05 +0800 Subject: [PATCH 17/19] fix: Resolve merging conflicts; modify splash for LA. --- Makefile | 43 +++++++++++++------- bootloader/loongarch64-direct/src/main.rs | 3 +- os/src/main.rs | 46 +++++++++++++++++----- os/src/mm/page_table.rs | 1 - os/src/platform/loongarch/qemu_virt/mod.rs | 5 +++ os/src/platform/mod.rs | 4 +- os/src/platform/riscv/qemu_virt/mod.rs | 5 +++ user/Makefile | 4 +- user/src/bin/setupsh.rs | 12 +++++- 9 files changed, 92 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 58083aba..234b061c 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,14 @@ DOCKER_NAME ?= rcore-docker -TARGET ?= riscv64gc-unknown-none-elf USER_MODE ?= release -USER_BIN_DIR := user/target/$(TARGET)/$(USER_MODE) -KERNEL_RV_ELF := os/target/$(TARGET)/release/os +RV_USER_TARGET := riscv64gc-unknown-none-elf +LA_USER_TARGET := loongarch64-unknown-none +RV_KERNEL_TARGET := riscv64gc-unknown-none-elf +LA_KERNEL_TARGET := loongarch64-unknown-none +USER_BIN_DIR_RV := user/target/$(RV_USER_TARGET)/$(USER_MODE) +USER_BIN_DIR_LA := user/target/$(LA_USER_TARGET)/$(USER_MODE) +KERNEL_RV_ELF := os/target/$(RV_KERNEL_TARGET)/release/os +KERNEL_LA_ELF := os/target/$(LA_KERNEL_TARGET)/release/os QEMU ?= qemu-system-riscv64 MEM ?= 1G SMP ?= 1 @@ -16,8 +21,10 @@ QEMU_COMP_BLK_ARGS = -drive file=$(RUN_TEST_FS),if=none,format=raw,id=x0 -device QEMU_COMP_EXTRA_BLK_ARGS = -drive file=disk.img,if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 STAMP_DIR := .make -USER_BUILD_STAMP := $(STAMP_DIR)/user-build.stamp -KERNEL_BUILD_STAMP := $(STAMP_DIR)/kernel-build.stamp +USER_BUILD_STAMP_RV := $(STAMP_DIR)/user-build-rv.stamp +USER_BUILD_STAMP_LA := $(STAMP_DIR)/user-build-la.stamp +KERNEL_BUILD_STAMP_RV := $(STAMP_DIR)/kernel-build-rv.stamp +KERNEL_BUILD_STAMP_LA := $(STAMP_DIR)/kernel-build-la.stamp USER_BUILD_DEPS := user/Makefile user/Cargo.toml $(shell find user/src -type f | sort) KERNEL_BUILD_DEPS := os/Makefile os/Cargo.toml os/build.rs $(shell find os/src fs/src -type f | sort) ROOTFS_REPO := CosmOS-rootfs @@ -66,22 +73,30 @@ cargo-config: $(STAMP_DIR): mkdir -p $@ -$(USER_BUILD_STAMP): $(USER_BUILD_DEPS) | $(STAMP_DIR) cargo-config - $(MAKE) -C user build +$(USER_BUILD_STAMP_RV): $(USER_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C user build ARCH=riscv64 touch $@ -user-apps: $(USER_BUILD_STAMP) +$(USER_BUILD_STAMP_LA): $(USER_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C user build ARCH=loongarch64 + touch $@ + +user-apps: $(USER_BUILD_STAMP_RV) +user-apps-la: $(USER_BUILD_STAMP_LA) + +$(KERNEL_BUILD_STAMP_RV): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C os kernel ARCH=riscv64 + touch $@ -$(KERNEL_BUILD_STAMP): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) cargo-config - $(MAKE) -C os kernel +$(KERNEL_BUILD_STAMP_LA): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) + $(MAKE) -C os kernel ARCH=loongarch64 touch $@ -kernel-rv: $(KERNEL_BUILD_STAMP) +kernel-rv: $(KERNEL_BUILD_STAMP_RV) cp $(KERNEL_RV_ELF) $@ -kernel-la: kernel-rv - @echo "warning: LoongArch kernel is not implemented in this repository yet; using kernel-rv as a temporary placeholder." >&2 - cp kernel-rv $@ +kernel-la: $(KERNEL_BUILD_STAMP_LA) + cp $(KERNEL_LA_ELF) $@ rootfs: $(MAKE) -C $(ROOTFS_REPO) rootfs-init diff --git a/bootloader/loongarch64-direct/src/main.rs b/bootloader/loongarch64-direct/src/main.rs index 3d2d1288..547117f1 100644 --- a/bootloader/loongarch64-direct/src/main.rs +++ b/bootloader/loongarch64-direct/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![feature(naked_functions)] use core::arch::{asm, naked_asm}; use core::panic::PanicInfo; @@ -13,7 +14,7 @@ const KERNEL_ENTRY: usize = 0x9000_0000_9000_0000; #[unsafe(no_mangle)] static mut BOOT_STACK: [u8; 4096] = [0; 4096]; -#[unsafe(naked)] +#[naked] #[unsafe(no_mangle)] pub unsafe extern "C" fn _start() -> ! { naked_asm!( diff --git a/os/src/main.rs b/os/src/main.rs index b4336466..65642ff5 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -62,14 +62,12 @@ pub mod trap; use core::hint::spin_loop; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use alloc::format; + +#[cfg(target_arch = "riscv64")] +use crate::platform::riscv::qemu_virt::sbi::hart_get_status; + const ANSI_RESET: &str = "\u{1b}[0m"; -const ANSI_FRAME: &str = "\u{1b}[38;5;45m"; -const ANSI_GLOW: &str = "\u{1b}[38;5;117m"; -const ANSI_TITLE: &str = "\u{1b}[1;38;5;159m"; -const ANSI_SUBTITLE: &str = "\u{1b}[38;5;111m"; -const ANSI_STAGE: &str = "\u{1b}[38;5;81m"; -const ANSI_DETAIL: &str = "\u{1b}[38;5;252m"; -const ANSI_ACCENT: &str = "\u{1b}[38;5;218m"; /// secondary hart 在访问 `.bss` 中的全局对象前,必须先等 bootstrap hart 完成 `clear_bss()`。 /// @@ -99,7 +97,29 @@ fn clear_bss() { } } + +macro_rules! ansi_fg_256 { + ($n:literal) => { + concat!("\x1b[38;5;", stringify!($n), "m") + }; +} + +#[allow(non_upper_case_globals)] +const ANSI_FRAME: &str = if cfg!(target_arch = "loongarch64") { + ansi_fg_256!(222) +} else { + ansi_fg_256!(45) +}; + +#[allow(non_upper_case_globals)] +const ANSI_GLOW: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(226) } else { ansi_fg_256!(117) }; +const ANSI_TITLE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(214) } else { ansi_fg_256!(159) }; +const ANSI_SUBTITLE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(11) } else { ansi_fg_256!(111) }; +const ANSI_STAGE: &str = if cfg!(target_arch = "loongarch64") { ansi_fg_256!(229) } else { ansi_fg_256!(81) }; +const ANSI_DETAIL: &str = ansi_fg_256!(252); + fn print_boot_splash(hart_id: usize, hart_count: usize) { + const LOGO: [&str; 7] = [ " ______ ____ _____ ", " / ____/___ _________ ___ / __ \\ / ___/ ", @@ -143,7 +163,7 @@ fn print_boot_splash(hart_id: usize, hart_count: usize) { "{frame}|{reset} {subtitle}{msg:<66}{reset} {frame}|{reset}", frame = ANSI_FRAME, subtitle = ANSI_SUBTITLE, - msg = "Target: RISC-V virt machine", + msg = format!("Target: {} - {}", crate::platform::machine_name(), crate::platform::platform_name()), reset = ANSI_RESET, ); println!( @@ -153,6 +173,7 @@ fn print_boot_splash(hart_id: usize, hart_count: usize) { ); println!(""); } + fn print_boot_stage(stage: &str, detail: &str) { println!( "{stage_color}>> {stage:<12}{reset} {detail_color}{detail}{reset}", @@ -176,13 +197,14 @@ fn init_local_hart(hart_id: usize) { debug!("hart {} local init done", hart_id); } /// Probe the firmware-visible hart IDs and return the number of usable harts. +#[cfg(target_arch = "riscv64")] fn detect_hart_count() -> usize { const SBI_SUCCESS: isize = 0; const SBI_ERR_INVALID_PARAM: isize = -3; let mut hart_count = 0usize; for target_hart in 0..config::MAX_HARTS { - let status = sbi::hart_get_status(target_hart); + let status = hart_get_status(target_hart); if status.error == SBI_ERR_INVALID_PARAM { break; } @@ -193,6 +215,12 @@ fn detect_hart_count() -> usize { hart_count.max(1) } +/// Probe the usable hart count on non-RISC-V platforms. +#[cfg(target_arch = "loongarch64")] +fn detect_hart_count() -> usize { + 1 +} + /// 竞争并记录负责一次性全局初始化的 bootstrap hart。 /// /// 返回值为 `true` 表示当前 hart 抢到了 bootstrap 角色;返回 `false` diff --git a/os/src/mm/page_table.rs b/os/src/mm/page_table.rs index c53ed648..c5103031 100644 --- a/os/src/mm/page_table.rs +++ b/os/src/mm/page_table.rs @@ -4,7 +4,6 @@ use super::{ VirtAddr, VirtPageNum, }; use crate::config::PAGE_SIZE; -use crate::arch::riscv::Sv39Paging; use crate::hal::traits::{AddressSpaceToken, PagingArch, PTEFlags}; use alloc::string::String; use alloc::vec; diff --git a/os/src/platform/loongarch/qemu_virt/mod.rs b/os/src/platform/loongarch/qemu_virt/mod.rs index a92bc116..acd4624d 100644 --- a/os/src/platform/loongarch/qemu_virt/mod.rs +++ b/os/src/platform/loongarch/qemu_virt/mod.rs @@ -148,6 +148,11 @@ pub fn machine_name() -> &'static str { "loongarch64" } +/// Return the platform name for display purposes. +pub fn platform_name() -> &'static str { + "qemu virt" +} + /// Start all secondary harts via IOCSR mailbox + IPI. pub fn start_secondary_harts(bootstrap_hart_id: usize) { extern "C" { diff --git a/os/src/platform/mod.rs b/os/src/platform/mod.rs index 7cef8cfa..95b17a6e 100644 --- a/os/src/platform/mod.rs +++ b/os/src/platform/mod.rs @@ -26,7 +26,7 @@ pub use riscv::qemu_virt::{ console_getchar, console_putchar, console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, init_external_irq, init_external_irq_hart, kernel_heap_virtual_window_supported, - machine_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, + machine_name, platform_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, SbiPlatform as PlatformImpl, TRAMPOLINE, }; @@ -39,7 +39,7 @@ pub use loongarch::qemu_virt::{ console_rx_irq_ready, direct_map_phys_to_virt, direct_map_virt_to_phys, early_console_write, handle_external_irq, heap_debug_enabled, init_external_irq, init_external_irq_hart, init_ipi_hart, kernel_heap_virtual_window_supported, machine_name, - mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, clear_ipi_vector, + platform_name, mmio_phys_to_virt, probe_platform_devices, rtc_is_supported, shutdown, clear_ipi_vector, start_secondary_harts, translate_direct_mapped_kernel_va, use_early_console, KERNEL_HEAP_BASE, LoongArchPlatform as PlatformImpl, TRAMPOLINE, }; diff --git a/os/src/platform/riscv/qemu_virt/mod.rs b/os/src/platform/riscv/qemu_virt/mod.rs index c91cfabf..c0495ca5 100644 --- a/os/src/platform/riscv/qemu_virt/mod.rs +++ b/os/src/platform/riscv/qemu_virt/mod.rs @@ -71,6 +71,11 @@ pub fn machine_name() -> &'static str { "riscv64" } +/// Return the platform name for display purposes. +pub fn platform_name() -> &'static str { + "qemu virt" +} + /// Discover stopped harts via SBI HSM and start them on QEMU `virt`. pub fn start_secondary_harts(bootstrap_hart_id: usize) { extern "C" { diff --git a/user/Makefile b/user/Makefile index ba495b7b..f5a85e32 100644 --- a/user/Makefile +++ b/user/Makefile @@ -12,8 +12,6 @@ MODE := release APP_DIR := src/bin TARGET_DIR := target/$(TARGET)/$(MODE) BUILD_DIR := build -OBJDUMP := rust-objdump --arch-name=riscv64 -OBJCOPY := rust-objcopy --binary-architecture=riscv64 ifeq ($(MODE), release) MODE_ARG := --release @@ -25,7 +23,7 @@ ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS)) binary: @echo $(ELFS) - @cargo build $(MODE_ARG) + @cargo build $(MODE_ARG) --target $(TARGET) @$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf)); cp $(elf) $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.elf, $(elf));) disasm: diff --git a/user/src/bin/setupsh.rs b/user/src/bin/setupsh.rs index c402ecc7..f88459ed 100644 --- a/user/src/bin/setupsh.rs +++ b/user/src/bin/setupsh.rs @@ -282,6 +282,16 @@ fn install_lib_link(src: &str, dst: &str) -> bool { ensure_hard_link(src, dst) } +#[cfg(target_arch = "riscv64")] +fn keep_musl_compat_top_level_entry(name: &str) -> bool { + name == MUSL_LD_COMPAT_PATH.trim_start_matches('/') +} + +#[cfg(not(target_arch = "riscv64"))] +fn keep_musl_compat_top_level_entry(_name: &str) -> bool { + false +} + /// 目标目录中已有普通文件时,认为镜像已迁移过。 fn dir_has_runtime_files(target_dir: &str) -> bool { let fd = open(target_dir, OpenFlags::RDONLY | OpenFlags::DIRECTORY); @@ -384,7 +394,7 @@ fn keep_top_level_lib_entry(name: &str, dtype: u8) -> bool { || name == ".." || name == "ar" || name == MUSL_LD_PATH.trim_start_matches('/') - || name == MUSL_LD_COMPAT_PATH.trim_start_matches('/') + || keep_musl_compat_top_level_entry(name) || name == GLIBC_LD_PATH.trim_start_matches('/') } From e7c85e79318b34f0a1ceb520293d54c274303be1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 13 Jun 2026 16:28:06 +0800 Subject: [PATCH 18/19] chore: temporary commit to fix submodule. --- .vscode/settings.json | 1 + CosmOS-rootfs | 2 +- Makefile | 150 ++++++++++++++------- os/src/config.rs | 2 + os/src/main.rs | 1 + os/src/platform/loongarch/qemu_virt/pci.rs | 6 +- rootfs | 1 - user/Makefile | 14 +- 8 files changed, 118 insertions(+), 59 deletions(-) delete mode 160000 rootfs diff --git a/.vscode/settings.json b/.vscode/settings.json index d8de0342..752dc8b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "-c", "if [ \"$(basename $(pwd))\" = \"fs-fuse\" ]; then target=\"x86_64-unknown-linux-gnu\"; else target=\"riscv64gc-unknown-none-elf\"; fi; cargo clippy --target $target --workspace --message-format=json --message-format=json-diagnostic-rendered-ansi" ], + "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", "rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave": true, "files.watcherExclude": { diff --git a/CosmOS-rootfs b/CosmOS-rootfs index 1061c13e..fd861150 160000 --- a/CosmOS-rootfs +++ b/CosmOS-rootfs @@ -1 +1 @@ -Subproject commit 1061c13e89534acb41f61f76ef99400c131f1c40 +Subproject commit fd861150e97be8fda24b909ffc95c73de4745210 diff --git a/Makefile b/Makefile index 234b061c..d7f95258 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ RV_KERNEL_TARGET := riscv64gc-unknown-none-elf LA_KERNEL_TARGET := loongarch64-unknown-none USER_BIN_DIR_RV := user/target/$(RV_USER_TARGET)/$(USER_MODE) USER_BIN_DIR_LA := user/target/$(LA_USER_TARGET)/$(USER_MODE) +USER_BIN_DIR ?= $(USER_BIN_DIR_RV) KERNEL_RV_ELF := os/target/$(RV_KERNEL_TARGET)/release/os KERNEL_LA_ELF := os/target/$(LA_KERNEL_TARGET)/release/os QEMU ?= qemu-system-riscv64 @@ -19,24 +20,38 @@ QEMU_NETDEV ?= user,id=net QEMU_TRACE_ARGS ?= QEMU_COMP_BLK_ARGS = -drive file=$(RUN_TEST_FS),if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 QEMU_COMP_EXTRA_BLK_ARGS = -drive file=disk.img,if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 +QEMU_LA ?= qemu-system-loongarch64 +MEM_LA ?= 4G +TEST_FS_LA ?= sdcard-la.img +RUN_TEST_FS_LA ?= .make/sdcard-la-run.img +QEMU_LA_NETDEV ?= user,id=net0 +LA_KERNEL_ENTRY_PA ?= 0x90000000 +LA_BOOTLOADER_DIR ?= bootloader/loongarch64-direct +LA_BOOTLOADER_ELF := $(LA_BOOTLOADER_DIR)/target/loongarch64-unknown-none/release/loongarch64-direct-boot STAMP_DIR := .make -USER_BUILD_STAMP_RV := $(STAMP_DIR)/user-build-rv.stamp -USER_BUILD_STAMP_LA := $(STAMP_DIR)/user-build-la.stamp +USER_BUILD_STAMP_RV := $(USER_BIN_DIR_RV)/.xxos-build.stamp +USER_BUILD_STAMP_LA := $(USER_BIN_DIR_LA)/.xxos-build.stamp KERNEL_BUILD_STAMP_RV := $(STAMP_DIR)/kernel-build-rv.stamp KERNEL_BUILD_STAMP_LA := $(STAMP_DIR)/kernel-build-la.stamp USER_BUILD_DEPS := user/Makefile user/Cargo.toml $(shell find user/src -type f | sort) KERNEL_BUILD_DEPS := os/Makefile os/Cargo.toml os/build.rs $(shell find os/src fs/src -type f | sort) +LA_BOOTLOADER_DEPS := $(LA_BOOTLOADER_DIR)/Cargo.toml $(LA_BOOTLOADER_DIR)/Cargo.lock $(LA_BOOTLOADER_DIR)/build.rs $(LA_BOOTLOADER_DIR)/linker.ld $(shell find $(LA_BOOTLOADER_DIR)/src -type f | sort) ROOTFS_REPO := CosmOS-rootfs -ROOTFS_BASE_DIR := $(ROOTFS_REPO)/rootfs -ROOTFS_RV_DIR := $(ROOTFS_REPO)/rootfs-rv -ROOTFS_LA_DIR := $(ROOTFS_REPO)/rootfs-la -ROOTFS_RV_STAMP_DIR := $(ROOTFS_REPO)/build/.stamps-rv -ROOTFS_LA_STAMP_DIR := $(ROOTFS_REPO)/build/.stamps-la -ROOTFS_RV_FILES := $(shell if [ -d $(ROOTFS_RV_DIR) ]; then find $(ROOTFS_RV_DIR) -type f | sort; fi) -ROOTFS_LA_FILES := $(shell if [ -d $(ROOTFS_LA_DIR) ]; then find $(ROOTFS_LA_DIR) -type f | sort; fi) +ROOTFS_SRC_DIR := $(ROOTFS_REPO) +ROOTFS_DIR := $(STAMP_DIR)/rootfs +ROOTFS_RV_DIR := $(STAMP_DIR)/rootfs-rv +ROOTFS_LA_DIR := $(STAMP_DIR)/rootfs-la +ROOTFS_STAMP := $(STAMP_DIR)/rootfs.stamp +ROOTFS_RV_STAMP := $(STAMP_DIR)/rootfs-rv.stamp +ROOTFS_LA_STAMP := $(STAMP_DIR)/rootfs-la.stamp +ROOTFS_SRC_FILES := $(shell if [ -d $(ROOTFS_SRC_DIR)/rootfs ]; then find $(ROOTFS_SRC_DIR)/rootfs -type f | sort; fi) +ROOTFS_RV_FILES := $(ROOTFS_SRC_FILES) +ROOTFS_LA_FILES := $(ROOTFS_SRC_FILES) DISK_RV_IMG := disk.img DISK_LA_IMG := disk-la.img +QEMU_LA_BLK_ARGS = -drive file=$(RUN_TEST_FS_LA),if=none,format=raw,id=x0 -device virtio-blk-pci,drive=x0,id=x0 +QEMU_LA_EXTRA_BLK_ARGS = -drive file=$(DISK_LA_IMG),if=none,format=raw,id=x1 -device virtio-blk-pci,drive=x1,id=x1 RV_ROOTFS_TARGET ?= riscv64-linux-musl RV_TOOLCHAIN_BIN ?= /opt/riscv64-linux-musl-cross/bin RV_GLIBC_LIB ?= /usr/riscv64-linux-gnu/lib @@ -49,12 +64,12 @@ LA_MUSL_LIB ?= /opt/loongarch64-linux-musl-cross/loongarch64-linux-musl/lib LA_MUSL_ARCH ?= loongarch64 OPTIONAL_RUNTIME_FILES := $(wildcard lib/musl/ar lib/glibc/ar) -.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs rootfs-rv rootfs-la rv la disk-la clean run run-trace run-comp-rv debug gdbserver gdbclient check-kernel check-user-apps check-rootfs check-rootfs-rv check-rootfs-la prepare-run-test-fs +.PHONY: all submodules cargo-config docker build_docker fmt user-apps user-apps-la rootfs rootfs-rv rootfs-la rv la disk-la clean run run-la fast-run fast-run-la run-trace run-comp-rv debug gdbserver gdbclient check-kernel check-kernel-la check-user-apps-rv check-user-apps-la check-rootfs check-rootfs-rv check-rootfs-la prepare-run-test-fs prepare-run-test-fs-la check-run-test-fs check-run-test-fs-la all: $(MAKE) submodules $(MAKE) cargo-config - $(MAKE) user-apps kernel-rv kernel-la $(DISK_RV_IMG) $(DISK_LA_IMG) + $(MAKE) user-apps user-apps-la kernel-rv kernel-la $(DISK_RV_IMG) $(DISK_LA_IMG) # 拉取所有子模块,确保后续构建依赖完整。 submodules: @@ -73,11 +88,11 @@ cargo-config: $(STAMP_DIR): mkdir -p $@ -$(USER_BUILD_STAMP_RV): $(USER_BUILD_DEPS) | $(STAMP_DIR) +$(USER_BUILD_STAMP_RV): $(USER_BUILD_DEPS) $(MAKE) -C user build ARCH=riscv64 touch $@ -$(USER_BUILD_STAMP_LA): $(USER_BUILD_DEPS) | $(STAMP_DIR) +$(USER_BUILD_STAMP_LA): $(USER_BUILD_DEPS) $(MAKE) -C user build ARCH=loongarch64 touch $@ @@ -98,30 +113,26 @@ kernel-rv: $(KERNEL_BUILD_STAMP_RV) kernel-la: $(KERNEL_BUILD_STAMP_LA) cp $(KERNEL_LA_ELF) $@ -rootfs: - $(MAKE) -C $(ROOTFS_REPO) rootfs-init - -rootfs-rv: - $(MAKE) -C $(ROOTFS_REPO) rootfs-init \ - ROOTFS_DIR="$(CURDIR)/$(ROOTFS_RV_DIR)" \ - STAMP_DIR="$(CURDIR)/$(ROOTFS_RV_STAMP_DIR)" \ - TARGET=$(RV_ROOTFS_TARGET) \ - TOOLCHAIN_BIN=$(RV_TOOLCHAIN_BIN) \ - BUSYBOX_ARCH=riscv \ - GLIBC_LIB=$(RV_GLIBC_LIB) \ - MUSL_LIB=$(RV_MUSL_LIB) \ - MUSL_ARCH=$(RV_MUSL_ARCH) - -rootfs-la: - $(MAKE) -C $(ROOTFS_REPO) rootfs-init \ - ROOTFS_DIR="$(CURDIR)/$(ROOTFS_LA_DIR)" \ - STAMP_DIR="$(CURDIR)/$(ROOTFS_LA_STAMP_DIR)" \ - TARGET=$(LA_ROOTFS_TARGET) \ - TOOLCHAIN_BIN=$(LA_TOOLCHAIN_BIN) \ - BUSYBOX_ARCH=loongarch \ - GLIBC_TOOLCHAIN=$(LA_GLIBC_TOOLCHAIN) \ - MUSL_LIB=$(LA_MUSL_LIB) \ - MUSL_ARCH=$(LA_MUSL_ARCH) +$(ROOTFS_STAMP): $(ROOTFS_SRC_FILES) | $(STAMP_DIR) + rm -rf $(ROOTFS_DIR) + cp -a $(ROOTFS_SRC_DIR)/rootfs/. $(ROOTFS_DIR)/ + touch $@ + +rootfs: $(ROOTFS_STAMP) + +$(ROOTFS_RV_STAMP): $(ROOTFS_SRC_FILES) | $(STAMP_DIR) + rm -rf $(ROOTFS_RV_DIR) + cp -a $(ROOTFS_SRC_DIR)/rootfs/. $(ROOTFS_RV_DIR)/ + touch $@ + +rootfs-rv: $(ROOTFS_RV_STAMP) + +$(ROOTFS_LA_STAMP): $(ROOTFS_SRC_FILES) | $(STAMP_DIR) + rm -rf $(ROOTFS_LA_DIR) + cp -a $(ROOTFS_SRC_DIR)/rootfs/. $(ROOTFS_LA_DIR)/ + touch $@ + +rootfs-la: $(ROOTFS_LA_STAMP) rv: $(DISK_RV_IMG) @@ -133,19 +144,31 @@ check-kernel: exit 1; \ } -check-user-apps: - @test -d "$(USER_BIN_DIR)" || { \ - echo "missing user binaries in $(USER_BIN_DIR); run 'make all' first" >&2; \ +check-kernel-la: + @test -x kernel-la || { \ + echo "missing kernel-la; run 'make all' first" >&2; \ + exit 1; \ + } + +check-user-apps-rv: user-apps + @test -d "$(USER_BIN_DIR_RV)" || { \ + echo "missing user binaries in $(USER_BIN_DIR_RV); run 'make user-apps' first" >&2; \ + exit 1; \ + } + +check-user-apps-la: user-apps-la + @test -d "$(USER_BIN_DIR_LA)" || { \ + echo "missing user binaries in $(USER_BIN_DIR_LA); run 'make user-apps-la' first" >&2; \ exit 1; \ } check-rootfs: rootfs - @test -d "$(ROOTFS_BASE_DIR)" || { \ - echo "missing rootfs directory $(ROOTFS_BASE_DIR); run 'make all' first" >&2; \ + @test -d "$(ROOTFS_DIR)" || { \ + echo "missing rootfs directory $(ROOTFS_DIR); run 'make all' first" >&2; \ exit 1; \ } - @test -d "$(ROOTFS_BASE_DIR)/root" || { \ - echo "rootfs is incomplete under $(ROOTFS_BASE_DIR); run 'make all' first" >&2; \ + @test -d "$(ROOTFS_DIR)/root" || { \ + echo "rootfs is incomplete under $(ROOTFS_DIR); run 'make all' first" >&2; \ exit 1; \ } @@ -169,12 +192,17 @@ check-rootfs-la: rootfs-la exit 1; \ } -$(DISK_RV_IMG): check-user-apps check-rootfs-rv $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh +$(DISK_RV_IMG): USER_BIN_DIR := $(USER_BIN_DIR_RV) +$(DISK_RV_IMG): check-user-apps-rv check-rootfs-rv $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh MUSL_ARCH=$(RV_MUSL_ARCH) ./scripts/pack-disk-img.sh $(ROOTFS_RV_DIR) $(USER_BIN_DIR) $@ -$(DISK_LA_IMG): check-user-apps check-rootfs-la $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh +$(DISK_LA_IMG): USER_BIN_DIR := $(USER_BIN_DIR_LA) +$(DISK_LA_IMG): check-user-apps-la check-rootfs-la $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh MUSL_ARCH=$(LA_MUSL_ARCH) ./scripts/pack-disk-img.sh $(ROOTFS_LA_DIR) $(USER_BIN_DIR) $@ +$(LA_BOOTLOADER_ELF): $(LA_BOOTLOADER_DEPS) + cd $(LA_BOOTLOADER_DIR) && cargo build --release + prepare-run-test-fs: | $(STAMP_DIR) @if [ ! -f "$(TEST_FS)" ]; then \ echo "Test image not found: $(TEST_FS)"; \ @@ -182,9 +210,37 @@ prepare-run-test-fs: | $(STAMP_DIR) fi cp -c "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp --reflink=auto "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp "$(TEST_FS)" "$(RUN_TEST_FS)" +prepare-run-test-fs-la: | $(STAMP_DIR) + @if [ ! -f "$(TEST_FS_LA)" ]; then \ + echo "Test image not found: $(TEST_FS_LA)"; \ + exit 2; \ + fi + cp -c "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp --reflink=auto "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" + +check-run-test-fs: + @if [ ! -f "$(RUN_TEST_FS)" ]; then \ + echo "Run image not found: $(RUN_TEST_FS). Run 'make run' once first."; \ + exit 2; \ + fi + +check-run-test-fs-la: + @if [ ! -f "$(RUN_TEST_FS_LA)" ]; then \ + echo "Run image not found: $(RUN_TEST_FS_LA). Run 'make run-la' once first."; \ + exit 2; \ + fi + run: check-kernel disk.img prepare-run-test-fs $(QEMU) -machine virt -kernel kernel-rv -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) +run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(DISK_LA_IMG) prepare-run-test-fs-la + $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) + +fast-run: check-kernel disk.img check-run-test-fs + $(QEMU) -machine virt -kernel kernel-rv -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) + +fast-run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(DISK_LA_IMG) check-run-test-fs-la + $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) + run-trace: QEMU_TRACE_ARGS = -d int,in_asm -D qemu.log run-trace: run @@ -209,6 +265,6 @@ fmt: cd fs; cargo fmt; cd ../fs-fuse; cargo fmt; cd ../os; cargo fmt; cd ../user; cargo fmt; cd .. clean: - rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR) + rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(RUN_TEST_FS_LA) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR) $(MAKE) -C os clean $(MAKE) -C user clean diff --git a/os/src/config.rs b/os/src/config.rs index a4ef2e8f..cf3379f6 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -16,6 +16,8 @@ pub const MEMORY_END: usize = 0xC0000000; pub const PAGE_SIZE: usize = 0x1000; /// page size bits: 12 pub const PAGE_SIZE_BITS: usize = 0xc; +/// fixed load bias used for PIE main executables without an interpreter +pub const USER_PIE_BASE: usize = 0x0020_0000; /// qemu board info pub use crate::platform::{USER_MMAP_BASE, USER_STACK_BASE, INTERP_BASE, CLOCK_FREQ, KERNEL_HEAP_BASE, MMIO, TRAMPOLINE}; diff --git a/os/src/main.rs b/os/src/main.rs index 65642ff5..b6a6f009 100644 --- a/os/src/main.rs +++ b/os/src/main.rs @@ -25,6 +25,7 @@ #![feature(panic_info_message)] #![feature(alloc_error_handler)] + #[macro_use] extern crate log; diff --git a/os/src/platform/loongarch/qemu_virt/pci.rs b/os/src/platform/loongarch/qemu_virt/pci.rs index 12dab03b..782391f0 100644 --- a/os/src/platform/loongarch/qemu_virt/pci.rs +++ b/os/src/platform/loongarch/qemu_virt/pci.rs @@ -63,11 +63,7 @@ pub fn probe_platform_devices() { continue; }; let dev = Arc::new(dev); - let name: String = if block_idx > 0 { - alloc::format!("vda{}", block_idx + 1) - } else { - "vda".into() - }; + let name: String = alloc::format!("vd{}", (b'a' + block_idx as u8) as char); info!("[pci] virtio-blk {} at {}", name, bdf); map.insert(name, dev.clone()); let _ = &mut irq_map; diff --git a/rootfs b/rootfs deleted file mode 160000 index 1061c13e..00000000 --- a/rootfs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1061c13e89534acb41f61f76ef99400c131f1c40 diff --git a/user/Makefile b/user/Makefile index f5a85e32..0066a510 100644 --- a/user/Makefile +++ b/user/Makefile @@ -37,14 +37,18 @@ pre: @mkdir -p $(BUILD_DIR)/asm/ @$(foreach t, $(APPS), cp $(t) $(BUILD_DIR)/app/;) -build: clean pre binary - @$(foreach t, $(ELFS), cp $(t).bin $(BUILD_DIR)/bin/;) - @$(foreach t, $(ELFS), cp $(t).elf $(BUILD_DIR)/elf/;) - clean: @cargo clean @rm -rf $(BUILD_DIR) +prepare: + @rm -rf $(BUILD_DIR) + @mkdir -p $(BUILD_DIR) + +build: prepare pre binary + @$(foreach t, $(ELFS), cp $(t).bin $(BUILD_DIR)/bin/;) + @$(foreach t, $(ELFS), cp $(t).elf $(BUILD_DIR)/elf/;) + all: build -.PHONY: elf binary build clean all +.PHONY: elf binary prepare build clean all From 394676ad41b0448e5dca75a2b7e6d1b6cd4d1ab2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 13 Jun 2026 17:26:00 +0800 Subject: [PATCH 19/19] fix: fully support LA. --- Makefile | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index a7d6cca3..c151b152 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,10 @@ ARCH ?= rv TARGET ?= riscv64gc-unknown-none-elf USER_MODE ?= release USER_BIN_DIR := user/target/$(TARGET)/$(USER_MODE) +USER_BIN_DIR_RV := user/target/riscv64gc-unknown-none-elf/$(USER_MODE) +USER_BIN_DIR_LA := user/target/loongarch64-unknown-none/$(USER_MODE) KERNEL_RV_ELF := os/target/$(TARGET)/release/os +KERNEL_LA_ELF := os/target/loongarch64-unknown-none/release/os QEMU_RV ?= qemu-system-riscv64 QEMU_LA ?= qemu-system-loongarch64 MEM ?= 1G @@ -12,6 +15,8 @@ SMP ?= 1 TEST_FS ?= sdcard-$(ARCH).img # make run 使用写时复制副本,避免 QEMU 写坏原始测试镜像。 RUN_TEST_FS ?= .make/sdcard-$(ARCH)-run.img +TEST_FS_LA ?= sdcard-la.img +RUN_TEST_FS_LA ?= .make/sdcard-la-run.img QEMU_NETDEV ?= user,id=net QEMU_TRACE_ARGS ?= QEMU_COMP_BLK_ARGS = -drive file=$(RUN_TEST_FS),if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 @@ -24,6 +29,7 @@ KERNEL_BUILD_STAMP_RV := $(STAMP_DIR)/kernel-build-rv.stamp KERNEL_BUILD_STAMP_LA := $(STAMP_DIR)/kernel-build-la.stamp USER_BUILD_DEPS := user/Makefile user/Cargo.toml $(shell find user/src -type f | sort) KERNEL_BUILD_DEPS := os/Makefile os/Cargo.toml os/build.rs $(shell find os/src fs/src -type f | sort) +LA_BOOTLOADER_DIR ?= bootloader/loongarch64-direct LA_BOOTLOADER_DEPS := $(LA_BOOTLOADER_DIR)/Cargo.toml $(LA_BOOTLOADER_DIR)/Cargo.lock $(LA_BOOTLOADER_DIR)/build.rs $(LA_BOOTLOADER_DIR)/linker.ld $(shell find $(LA_BOOTLOADER_DIR)/src -type f | sort) ROOTFS_REPO := CosmOS-rootfs ROOTFS_BASE_DIR := $(ROOTFS_REPO)/rootfs @@ -51,6 +57,10 @@ LA_GLIBC_TOOLCHAIN ?= /opt/gcc-13.2.0-loongarch64-linux-gnu LA_MUSL_LIB ?= /opt/loongarch64-linux-musl-cross/loongarch64-linux-musl/lib LA_MUSL_ARCH ?= loongarch64 LA_MUSL_LOADER_ALIASES ?= ld-musl-loongarch64.so.1 +LA_BOOTLOADER_ELF ?= $(LA_BOOTLOADER_DIR)/target/loongarch64-unknown-none/release/loongarch64-direct-boot +LA_KERNEL_ENTRY_PA ?= 0x90000000 +MEM_LA ?= 1G +QEMU_LA_NETDEV ?= user,id=net0 OPTIONAL_RUNTIME_FILES := $(wildcard lib/musl/ar lib/glibc/ar) ifeq ($(ARCH),rv) @@ -65,7 +75,7 @@ else $(error unsupported ARCH=$(ARCH), expected rv or la) endif -.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs sync-rootfs-variants rootfs-rv rootfs-la rv la disk-rv disk-la clean run run-trace run-comp-rv run-comp-la debug gdbserver gdbclient check-kernel check-user-apps check-rootfs check-rootfs-rv check-rootfs-la check-rootfs-rv-ready check-rootfs-la-ready prepare-run-test-fs force +.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs sync-rootfs-variants rootfs-rv rootfs-la rv la disk-rv disk-la clean run run-trace run-comp-rv run-comp-la fast-run fast-run-la clean-all debug gdbserver gdbclient check-kernel check-user-apps check-rootfs check-rootfs-rv check-rootfs-la check-rootfs-rv-ready check-rootfs-la-ready prepare-run-test-fs prepare-run-test-fs-la force all: $(MAKE) submodules @@ -238,11 +248,11 @@ check-rootfs-la-ready: exit 1; \ } -$(DISK_RV_IMG): force check-user-apps rootfs-rv check-rootfs-rv-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh - MUSL_ARCH=$(RV_MUSL_ARCH) MUSL_LOADER_ALIASES="$(RV_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_RV_DIR) $(USER_BIN_DIR) $@ +$(DISK_RV_IMG): force check-user-apps-rv rootfs-rv check-rootfs-rv-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_RV_FILES) scripts/pack-disk-img.sh + MUSL_ARCH=$(RV_MUSL_ARCH) MUSL_LOADER_ALIASES="$(RV_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_RV_DIR) $(USER_BIN_DIR_RV) $@ -$(DISK_LA_IMG): force check-user-apps rootfs-la check-rootfs-la-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh - MUSL_ARCH=$(LA_MUSL_ARCH) MUSL_LOADER_ALIASES="$(LA_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_LA_DIR) $(USER_BIN_DIR) $@ +$(DISK_LA_IMG): force check-user-apps-la rootfs-la check-rootfs-la-ready $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_LA_FILES) scripts/pack-disk-img.sh + MUSL_ARCH=$(LA_MUSL_ARCH) MUSL_LOADER_ALIASES="$(LA_MUSL_LOADER_ALIASES)" ./scripts/pack-disk-img.sh $(ROOTFS_LA_DIR) $(USER_BIN_DIR_LA) $@ force: @@ -256,16 +266,23 @@ prepare-run-test-fs: | $(STAMP_DIR) fi cp -c "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp --reflink=auto "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp "$(TEST_FS)" "$(RUN_TEST_FS)" +prepare-run-test-fs-la: | $(STAMP_DIR) + @if [ ! -f "$(TEST_FS_LA)" ]; then \ + echo "Test image not found: $(TEST_FS_LA)"; \ + exit 2; \ + fi + cp -c "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp --reflink=auto "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" 2>/dev/null || cp "$(TEST_FS_LA)" "$(RUN_TEST_FS_LA)" + run: check-kernel $(RUN_DISK_IMG) prepare-run-test-fs $(QEMU) -machine virt -kernel $(RUN_KERNEL) -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(DISK_LA_IMG) prepare-run-test-fs-la $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) -fast-run: check-kernel disk.img check-run-test-fs +fast-run: check-kernel $(QEMU) -machine virt -kernel kernel-rv -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) -fast-run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(DISK_LA_IMG) check-run-test-fs-la +fast-run-la: check-kernel-la $(LA_BOOTLOADER_ELF) $(QEMU_LA) -machine virt -cpu la464 -kernel $(LA_BOOTLOADER_ELF) -device loader,file=kernel-la,addr=$(LA_KERNEL_ENTRY_PA) -m $(MEM_LA) -nographic -smp $(SMP) $(QEMU_LA_BLK_ARGS) -device virtio-net-pci,netdev=net0,id=net0 -netdev $(QEMU_LA_NETDEV) -no-reboot -rtc base=utc $(QEMU_LA_EXTRA_BLK_ARGS) run-trace: QEMU_TRACE_ARGS = -d int,in_asm -D qemu.log @@ -296,6 +313,9 @@ fmt: cd fs; cargo fmt; cd ../fs-fuse; cargo fmt; cd ../os; cargo fmt; cd ../user; cargo fmt; cd .. clean: - rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_BUILD_DIR) $(ROOTFS_LA_BUILD_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR) + rm -rf $(STAMP_DIR) $(RUN_TEST_FS) $(DISK_RV_IMG) $(DISK_LA_IMG) kernel-rv kernel-la os/.cargo user/.cargo $(MAKE) -C os clean $(MAKE) -C user clean + +clean-all: clean + rm -rf $(ROOTFS_RV_DIR) $(ROOTFS_LA_DIR) $(ROOTFS_RV_BUILD_DIR) $(ROOTFS_LA_BUILD_DIR) $(ROOTFS_RV_STAMP_DIR) $(ROOTFS_LA_STAMP_DIR)