From 252a08ef9dcf72c4b8a2aa8d8ebb2a7f8b77261b Mon Sep 17 00:00:00 2001 From: Dayuxiaoshui <792179245@qq.com> Date: Thu, 16 Oct 2025 21:23:10 +0800 Subject: [PATCH] riscv64: complete PLIC + DTB + IRQ integration; fix warnings; add basic tests - PLIC: init, validation, claim/complete, batch enable/disable, error types - DTB: custom parser (memory/cpu/timer/uart/plic) with fallbacks - IRQ: wire S_EXT to PLIC; split CPU vs device enables - MEM: DTB-backed ranges with safe static storage - Init: early DTB init; Tests: no_std-style utilities - Clippy: zero warnings; cleanup unused items --- Cargo.lock | 7 + platforms/axplat-riscv64-qemu-virt/Cargo.toml | 1 + platforms/axplat-riscv64-qemu-virt/src/dtb.rs | 698 ++++++++++++++++++ .../axplat-riscv64-qemu-virt/src/init.rs | 10 +- platforms/axplat-riscv64-qemu-virt/src/irq.rs | 78 +- platforms/axplat-riscv64-qemu-virt/src/lib.rs | 6 + platforms/axplat-riscv64-qemu-virt/src/mem.rs | 34 +- .../axplat-riscv64-qemu-virt/src/plic.rs | 342 +++++++++ .../axplat-riscv64-qemu-virt/src/tests.rs | 281 +++++++ 9 files changed, 1444 insertions(+), 13 deletions(-) create mode 100644 platforms/axplat-riscv64-qemu-virt/src/dtb.rs create mode 100644 platforms/axplat-riscv64-qemu-virt/src/plic.rs create mode 100644 platforms/axplat-riscv64-qemu-virt/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0a02dfd2..9d4568ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,7 @@ dependencies = [ "axconfig-macros", "axcpu", "axplat", + "fdt", "log", "riscv", "riscv_goldfish", @@ -526,6 +527,12 @@ dependencies = [ "typeid", ] +[[package]] +name = "fdt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" + [[package]] name = "form_urlencoded" version = "1.2.1" diff --git a/platforms/axplat-riscv64-qemu-virt/Cargo.toml b/platforms/axplat-riscv64-qemu-virt/Cargo.toml index 35a64148..74788ea6 100644 --- a/platforms/axplat-riscv64-qemu-virt/Cargo.toml +++ b/platforms/axplat-riscv64-qemu-virt/Cargo.toml @@ -22,6 +22,7 @@ log = "0.4" riscv = "0.14" sbi-rt = { version = "0.0.3", features = ["legacy"] } riscv_goldfish = { version = "0.1", optional = true } +fdt = "0.1.5" axconfig-macros = "0.2" axcpu = { workspace = true } diff --git a/platforms/axplat-riscv64-qemu-virt/src/dtb.rs b/platforms/axplat-riscv64-qemu-virt/src/dtb.rs new file mode 100644 index 00000000..4d57157f --- /dev/null +++ b/platforms/axplat-riscv64-qemu-virt/src/dtb.rs @@ -0,0 +1,698 @@ +//! Device Tree Blob (DTB) parsing utilities for RISC-V +//! +//! This module provides functionality to parse the device tree blob +//! passed by the bootloader to extract hardware information. + +extern crate alloc; + +use core::ptr; +use alloc::vec::Vec; + +/// Device Tree Blob header structure +#[repr(C)] +struct DtbHeader { + magic: u32, + totalsize: u32, + off_dt_struct: u32, + off_dt_strings: u32, + off_mem_rsvmap: u32, + version: u32, + last_comp_version: u32, + boot_cpuid_phys: u32, + size_dt_strings: u32, + size_dt_struct: u32, +} + +/// Device Tree token types +const FDT_BEGIN_NODE: u32 = 0x00000001; +const FDT_END_NODE: u32 = 0x00000002; +const FDT_PROP: u32 = 0x00000003; +const FDT_NOP: u32 = 0x00000004; +const FDT_END: u32 = 0x00000009; + +/// Device Tree property structure +#[repr(C)] +struct DtbProperty { + len: u32, + nameoff: u32, +} + +/// Memory range information from device tree +#[derive(Debug, Clone, Copy)] +pub struct MemoryRange { + pub base: u64, + pub size: u64, +} + +/// Device tree parser +pub struct DtbParser { + dtb_ptr: *const u8, + struct_ptr: *const u32, + strings_ptr: *const u8, + valid: bool, +} + +impl DtbParser { + /// Create a new DTB parser from a raw DTB pointer + pub unsafe fn new(dtb_ptr: *const u8) -> Result { + if dtb_ptr.is_null() { + return Err("DTB pointer is null"); + } + + let header = dtb_ptr as *const DtbHeader; + + // Validate DTB magic number + let magic = unsafe { ptr::read_volatile(&(*header).magic) }; + if magic != 0xd00dfeed { + return Err("Invalid DTB magic number"); + } + + // Get header information + let _totalsize = unsafe { ptr::read_volatile(&(*header).totalsize) }; + let off_dt_struct = unsafe { ptr::read_volatile(&(*header).off_dt_struct) }; + let off_dt_strings = unsafe { ptr::read_volatile(&(*header).off_dt_strings) }; + + // Calculate pointers + let struct_ptr = unsafe { dtb_ptr.add(off_dt_struct as usize) as *const u32 }; + let strings_ptr = unsafe { dtb_ptr.add(off_dt_strings as usize) }; + + Ok(Self { + dtb_ptr, + struct_ptr, + strings_ptr, + valid: true, + }) + } + + /// Get memory ranges from the device tree + pub fn get_memory_ranges(&self) -> Vec { + if !self.valid { + return Vec::new(); + } + + let mut ranges = Vec::new(); + self.parse_memory_nodes(&mut ranges); + + if ranges.is_empty() { + // Fallback to default range for QEMU virt machine + ranges.push(MemoryRange { + base: 0x80000000, + size: 0x8000000, // 128MB + }); + } + + ranges + } + + /// Parse memory nodes from device tree + fn parse_memory_nodes(&self, ranges: &mut Vec) { + unsafe { + let mut current = self.struct_ptr; + let mut depth = 0; + let mut in_memory_node = false; + + loop { + let token = ptr::read_volatile(current); + current = current.add(1); + + match token { + FDT_BEGIN_NODE => { + depth += 1; + if depth == 1 { + // Check if this is a memory node + let node_name = current as *const u8; + let name_len = self.strlen(node_name); + if name_len >= 6 && self.strncmp(node_name, c"memory".as_ptr(), 6) == 0 { + in_memory_node = true; + } + } + // Skip node name (null-terminated string) + let name_len = self.strlen(current as *const u8); + current = current.add((name_len + 4) / 4); // Align to 4-byte boundary + } + FDT_END_NODE => { + depth -= 1; + if depth == 0 { + in_memory_node = false; + } + if depth < 0 { + break; + } + } + FDT_PROP => { + if in_memory_node { + self.parse_memory_property(current, ranges); + } + // Skip property + let prop = current as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + current = current.add(2); // Skip property header + current = current.add((prop_len as usize).div_ceil(4)); // Align to 4-byte boundary + } + FDT_NOP => { + // Do nothing + } + FDT_END => { + break; + } + _ => { + // Unknown token, skip + break; + } + } + } + } + } + + /// Parse memory property (reg property) + fn parse_memory_property(&self, prop_ptr: *const u32, ranges: &mut Vec) { + unsafe { + let prop = prop_ptr as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + let nameoff = ptr::read_volatile(&(*prop).nameoff); + + // Get property name + let name_ptr = self.strings_ptr.add(nameoff as usize); + if self.strncmp(name_ptr, c"reg".as_ptr(), 3) != 0 { + return; // Not a reg property + } + + // Parse reg property (address, size pairs) + let data_ptr = prop_ptr.add(2) as *const u8; + let mut offset = 0; + + while offset < prop_len as usize { + if offset + 16 > prop_len as usize { + break; // Not enough data for address+size pair + } + + // Read 64-bit address and size (big-endian) + let addr_high = u32::from_be_bytes([ + *data_ptr.add(offset), + *data_ptr.add(offset + 1), + *data_ptr.add(offset + 2), + *data_ptr.add(offset + 3), + ]); + let addr_low = u32::from_be_bytes([ + *data_ptr.add(offset + 4), + *data_ptr.add(offset + 5), + *data_ptr.add(offset + 6), + *data_ptr.add(offset + 7), + ]); + let size_high = u32::from_be_bytes([ + *data_ptr.add(offset + 8), + *data_ptr.add(offset + 9), + *data_ptr.add(offset + 10), + *data_ptr.add(offset + 11), + ]); + let size_low = u32::from_be_bytes([ + *data_ptr.add(offset + 12), + *data_ptr.add(offset + 13), + *data_ptr.add(offset + 14), + *data_ptr.add(offset + 15), + ]); + + let base = ((addr_high as u64) << 32) | (addr_low as u64); + let size = ((size_high as u64) << 32) | (size_low as u64); + + if size > 0 { + ranges.push(MemoryRange { base, size }); + } + + offset += 16; + } + } + } + + /// Helper function to get string length + unsafe fn strlen(&self, s: *const u8) -> usize { + let mut len = 0; + while unsafe { *s.add(len) } != 0 { + len += 1; + } + len + } + + /// Helper function to compare strings + unsafe fn strncmp(&self, s1: *const u8, s2: *const u8, n: usize) -> i32 { + for i in 0..n { + let c1 = unsafe { *s1.add(i) }; + let c2 = unsafe { *s2.add(i) }; + if c1 != c2 { + return c1 as i32 - c2 as i32; + } + if c1 == 0 { + break; + } + } + 0 + } + + /// Get CPU count from device tree + pub fn get_cpu_count(&self) -> usize { + if !self.valid { + return 1; + } + + let mut cpu_count = 0; + self.parse_cpu_nodes(&mut cpu_count); + + if cpu_count == 0 { + 1 // Default to 1 CPU if parsing fails + } else { + cpu_count + } + } + + /// Parse CPU nodes from device tree + fn parse_cpu_nodes(&self, cpu_count: &mut usize) { + unsafe { + let mut current = self.struct_ptr; + let mut depth = 0; + + loop { + let token = ptr::read_volatile(current); + current = current.add(1); + + match token { + FDT_BEGIN_NODE => { + depth += 1; + if depth == 1 { + // Check if this is a CPU node + let node_name = current as *const u8; + let name_len = self.strlen(node_name); + if name_len >= 3 && self.strncmp(node_name, c"cpu".as_ptr(), 3) == 0 { + *cpu_count += 1; + } + } + // Skip node name (null-terminated string) + let name_len = self.strlen(current as *const u8); + current = current.add((name_len + 4) / 4); // Align to 4-byte boundary + } + FDT_END_NODE => { + depth -= 1; + if depth < 0 { + break; + } + } + FDT_PROP => { + // Skip property + let prop = current as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + current = current.add(2); // Skip property header + current = current.add((prop_len as usize).div_ceil(4)); // Align to 4-byte boundary + } + FDT_NOP => { + // Do nothing + } + FDT_END => { + break; + } + _ => { + // Unknown token, skip + break; + } + } + } + } + } + + /// Get timer frequency from device tree + pub fn get_timer_frequency(&self) -> Option { + if !self.valid { + return None; + } + + // Try to parse timer frequency from device tree + let mut frequency = None; + self.parse_timer_nodes(&mut frequency); + + // Fallback to default timer frequency for QEMU virt machine + frequency.or(Some(10_000_000)) // 10 MHz + } + + /// Parse timer nodes from device tree + fn parse_timer_nodes(&self, frequency: &mut Option) { + unsafe { + let mut current = self.struct_ptr; + let mut depth = 0; + let mut in_timer_node = false; + + loop { + let token = ptr::read_volatile(current); + current = current.add(1); + + match token { + FDT_BEGIN_NODE => { + depth += 1; + if depth == 1 { + // Check if this is a timer node + let node_name = current as *const u8; + let name_len = self.strlen(node_name); + if name_len >= 5 && self.strncmp(node_name, c"timer".as_ptr(), 5) == 0 { + in_timer_node = true; + } + } + // Skip node name (null-terminated string) + let name_len = self.strlen(current as *const u8); + current = current.add((name_len + 4).div_ceil(4)); // Align to 4-byte boundary + } + FDT_END_NODE => { + depth -= 1; + if depth == 0 { + in_timer_node = false; + } + if depth < 0 { + break; + } + } + FDT_PROP => { + if in_timer_node { + self.parse_timer_property(current, frequency); + } + // Skip property + let prop = current as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + current = current.add(2); // Skip property header + current = current.add((prop_len as usize).div_ceil(4)); // Align to 4-byte boundary + } + FDT_NOP => { + // Do nothing + } + FDT_END => { + break; + } + _ => { + // Unknown token, skip + break; + } + } + } + } + } + + /// Parse timer property (clock-frequency) + fn parse_timer_property(&self, prop_ptr: *const u32, frequency: &mut Option) { + unsafe { + let prop = prop_ptr as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + let nameoff = ptr::read_volatile(&(*prop).nameoff); + + // Get property name + let name_ptr = self.strings_ptr.add(nameoff as usize); + if self.strncmp(name_ptr, c"clock-frequency".as_ptr(), 14) != 0 { + return; // Not a clock-frequency property + } + + // Parse clock-frequency property (32-bit value) + if prop_len >= 4 { + let data_ptr = prop_ptr.add(2) as *const u8; + let freq = u32::from_be_bytes([ + *data_ptr, + *data_ptr.add(1), + *data_ptr.add(2), + *data_ptr.add(3), + ]); + *frequency = Some(freq as u64); + } + } + } + + /// Get UART base address from device tree + pub fn get_uart_base(&self) -> Option { + if !self.valid { + return None; + } + + // Try to parse UART base from device tree + let mut uart_base = None; + self.parse_uart_nodes(&mut uart_base); + + // Fallback to default UART base for QEMU virt machine + uart_base.or(Some(0x10000000)) + } + + /// Parse UART nodes from device tree + fn parse_uart_nodes(&self, uart_base: &mut Option) { + unsafe { + let mut current = self.struct_ptr; + let mut depth = 0; + let mut in_uart_node = false; + + loop { + let token = ptr::read_volatile(current); + current = current.add(1); + + match token { + FDT_BEGIN_NODE => { + depth += 1; + if depth == 1 { + // Check if this is a UART node + let node_name = current as *const u8; + let name_len = self.strlen(node_name); + if name_len >= 4 && self.strncmp(node_name, c"uart".as_ptr(), 4) == 0 { + in_uart_node = true; + } + } + // Skip node name (null-terminated string) + let name_len = self.strlen(current as *const u8); + current = current.add((name_len + 4).div_ceil(4)); // Align to 4-byte boundary + } + FDT_END_NODE => { + depth -= 1; + if depth == 0 { + in_uart_node = false; + } + if depth < 0 { + break; + } + } + FDT_PROP => { + if in_uart_node { + self.parse_uart_property(current, uart_base); + } + // Skip property + let prop = current as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + current = current.add(2); // Skip property header + current = current.add((prop_len as usize).div_ceil(4)); // Align to 4-byte boundary + } + FDT_NOP => { + // Do nothing + } + FDT_END => { + break; + } + _ => { + // Unknown token, skip + break; + } + } + } + } + } + + /// Parse UART property (reg) + fn parse_uart_property(&self, prop_ptr: *const u32, uart_base: &mut Option) { + unsafe { + let prop = prop_ptr as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + let nameoff = ptr::read_volatile(&(*prop).nameoff); + + // Get property name + let name_ptr = self.strings_ptr.add(nameoff as usize); + if self.strncmp(name_ptr, c"reg".as_ptr(), 3) != 0 { + return; // Not a reg property + } + + // Parse reg property (address, size pairs) + if prop_len >= 8 { + let data_ptr = prop_ptr.add(2) as *const u8; + // Read 64-bit address (big-endian) + let addr_high = u32::from_be_bytes([ + *data_ptr, + *data_ptr.add(1), + *data_ptr.add(2), + *data_ptr.add(3), + ]); + let addr_low = u32::from_be_bytes([ + *data_ptr.add(4), + *data_ptr.add(5), + *data_ptr.add(6), + *data_ptr.add(7), + ]); + + let base = ((addr_high as u64) << 32) | (addr_low as u64); + *uart_base = Some(base); + } + } + } + + /// Get PLIC base address from device tree + pub fn get_plic_base(&self) -> Option { + if !self.valid { + return None; + } + + // Try to parse PLIC base from device tree + let mut plic_base = None; + self.parse_plic_nodes(&mut plic_base); + + // Fallback to default PLIC base for QEMU virt machine + plic_base.or(Some(0x0c000000)) + } + + /// Parse PLIC nodes from device tree + fn parse_plic_nodes(&self, plic_base: &mut Option) { + unsafe { + let mut current = self.struct_ptr; + let mut depth = 0; + let mut in_plic_node = false; + + loop { + let token = ptr::read_volatile(current); + current = current.add(1); + + match token { + FDT_BEGIN_NODE => { + depth += 1; + if depth == 1 { + // Check if this is a PLIC node + let node_name = current as *const u8; + let name_len = self.strlen(node_name); + if name_len >= 4 && self.strncmp(node_name, c"plic".as_ptr(), 4) == 0 { + in_plic_node = true; + } + } + // Skip node name (null-terminated string) + let name_len = self.strlen(current as *const u8); + current = current.add((name_len + 4).div_ceil(4)); // Align to 4-byte boundary + } + FDT_END_NODE => { + depth -= 1; + if depth == 0 { + in_plic_node = false; + } + if depth < 0 { + break; + } + } + FDT_PROP => { + if in_plic_node { + self.parse_plic_property(current, plic_base); + } + // Skip property + let prop = current as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + current = current.add(2); // Skip property header + current = current.add((prop_len as usize).div_ceil(4)); // Align to 4-byte boundary + } + FDT_NOP => { + // Do nothing + } + FDT_END => { + break; + } + _ => { + // Unknown token, skip + break; + } + } + } + } + } + + /// Parse PLIC property (reg) + fn parse_plic_property(&self, prop_ptr: *const u32, plic_base: &mut Option) { + unsafe { + let prop = prop_ptr as *const DtbProperty; + let prop_len = ptr::read_volatile(&(*prop).len); + let nameoff = ptr::read_volatile(&(*prop).nameoff); + + // Get property name + let name_ptr = self.strings_ptr.add(nameoff as usize); + if self.strncmp(name_ptr, c"reg".as_ptr(), 3) != 0 { + return; // Not a reg property + } + + // Parse reg property (address, size pairs) + if prop_len >= 8 { + let data_ptr = prop_ptr.add(2) as *const u8; + // Read 64-bit address (big-endian) + let addr_high = u32::from_be_bytes([ + *data_ptr, + *data_ptr.add(1), + *data_ptr.add(2), + *data_ptr.add(3), + ]); + let addr_low = u32::from_be_bytes([ + *data_ptr.add(4), + *data_ptr.add(5), + *data_ptr.add(6), + *data_ptr.add(7), + ]); + + let base = ((addr_high as u64) << 32) | (addr_low as u64); + *plic_base = Some(base); + } + } + } + + + /// Print device tree information for debugging + pub fn print_info(&self) { + if self.valid { + info!("Device Tree Information:"); + info!(" DTB pointer: 0x{:x}", self.dtb_ptr as usize); + info!(" Status: Valid"); + + let memory_ranges = self.get_memory_ranges(); + info!(" Memory ranges: {} found", memory_ranges.len()); + for (i, range) in memory_ranges.iter().enumerate() { + info!(" Range {}: 0x{:x} - 0x{:x} ({} bytes)", + i, range.base, range.base + range.size, range.size); + } + + info!(" CPU count: {}", self.get_cpu_count()); + + if let Some(freq) = self.get_timer_frequency() { + info!(" Timer frequency: {} Hz", freq); + } + + if let Some(uart_base) = self.get_uart_base() { + info!(" UART base: 0x{:x}", uart_base); + } + + if let Some(plic_base) = self.get_plic_base() { + info!(" PLIC base: 0x{:x}", plic_base); + } + } else { + warn!("No device tree available"); + } + } +} + +/// Global DTB parser instance +static mut DTB_PARSER: Option = None; + +/// Initialize the global DTB parser +pub unsafe fn init(dtb_ptr: *const u8) -> Result<(), &'static str> { + let parser = unsafe { DtbParser::new(dtb_ptr)? }; + parser.print_info(); + unsafe { DTB_PARSER = Some(parser); } + Ok(()) +} + +/// Get a reference to the global DTB parser +#[allow(dead_code)] +pub fn get() -> Option<&'static DtbParser> { + unsafe { + // SAFETY: We ensure that DTB_PARSER is only modified during init + // and this function is called after init, so it's safe to create a reference + if let Some(ref parser) = DTB_PARSER { + Some(parser) + } else { + None + } + } +} diff --git a/platforms/axplat-riscv64-qemu-virt/src/init.rs b/platforms/axplat-riscv64-qemu-virt/src/init.rs index 7090f415..55ab3670 100644 --- a/platforms/axplat-riscv64-qemu-virt/src/init.rs +++ b/platforms/axplat-riscv64-qemu-virt/src/init.rs @@ -7,8 +7,16 @@ impl InitIf for InitIfImpl { /// This function should be called immediately after the kernel has booted, /// and performed earliest platform configuration and initialization (e.g., /// early console, clocking). - fn init_early(_cpu_id: usize, _mbi: usize) { + fn init_early(_cpu_id: usize, dtb: usize) { axcpu::init::init_trap(); + + // Initialize device tree parser + unsafe { + if let Err(e) = crate::dtb::init(dtb as *const u8) { + warn!("Failed to initialize DTB parser: {}", e); + } + } + crate::time::init_early(); } diff --git a/platforms/axplat-riscv64-qemu-virt/src/irq.rs b/platforms/axplat-riscv64-qemu-virt/src/irq.rs index 250c4c85..bc69d628 100644 --- a/platforms/axplat-riscv64-qemu-virt/src/irq.rs +++ b/platforms/axplat-riscv64-qemu-virt/src/irq.rs @@ -1,4 +1,4 @@ -//! TODO: PLIC +//! Interrupt handling for RISC-V with PLIC support use axplat::irq::{HandlerTable, IpiTarget, IrqHandler, IrqIf}; use core::sync::atomic::{AtomicPtr, Ordering}; @@ -47,6 +47,11 @@ macro_rules! with_cause { } pub(super) fn init_percpu() { + // Initialize PLIC + if let Err(e) = crate::plic::init() { + warn!("Failed to initialize PLIC: {:?}", e); + } + // enable soft interrupts, timer interrupts, and external interrupts unsafe { sie::set_ssoft(); @@ -60,9 +65,46 @@ struct IrqIfImpl; #[impl_plat_interface] impl IrqIf for IrqIfImpl { /// Enables or disables the given IRQ. - fn set_enable(irq: usize, _enabled: bool) { - // TODO: set enable in PLIC - warn!("set_enable is not implemented for IRQ {}", irq); + fn set_enable(irq: usize, enabled: bool) { + if irq & INTC_IRQ_BASE == 0 { + // Device-side interrupt - use PLIC + let plic = crate::plic::get(); + if enabled { + if let Err(e) = plic.enable_interrupt(0, irq) { // Context 0 for supervisor mode + warn!("Failed to enable interrupt {}: {:?}", irq, e); + } + } else if let Err(e) = plic.disable_interrupt(0, irq) { + warn!("Failed to disable interrupt {}: {:?}", irq, e); + } + } else { + // CPU-side interrupt - handled by CPU directly + match irq { + S_TIMER => { + if enabled { + unsafe { sie::set_stimer(); } + } else { + unsafe { sie::clear_stimer(); } + } + } + S_SOFT => { + if enabled { + unsafe { sie::set_ssoft(); } + } else { + unsafe { sie::clear_ssoft(); } + } + } + S_EXT => { + if enabled { + unsafe { sie::set_sext(); } + } else { + unsafe { sie::clear_sext(); } + } + } + _ => { + warn!("Unknown CPU-side IRQ: {}", irq); + } + } + } } /// Registers an IRQ handler for the given IRQ. @@ -155,13 +197,33 @@ impl IrqIf for IrqIfImpl { } }, @S_EXT => { - // TODO: get IRQ number from PLIC - if !IRQ_HANDLER_TABLE.handle(0) { - warn!("Unhandled IRQ {}", 0); + // Get IRQ number from PLIC + let plic = crate::plic::get(); + match plic.claim(0) { + Ok(Some(irq_num)) => { + if !IRQ_HANDLER_TABLE.handle(irq_num) { + warn!("Unhandled external IRQ {}", irq_num); + } + if let Err(e) = plic.complete(0, irq_num) { + warn!("Failed to complete interrupt {}: {:?}", irq_num, e); + } + } + Ok(None) => { + // No interrupt to claim + } + Err(e) => { + warn!("Failed to claim interrupt: {:?}", e); + } } }, @EX_IRQ => { - unreachable!("Device-side IRQs should be handled by triggering the External Interrupt."); + // Device-side IRQs are handled directly through the handler table + // This should not be reached in normal operation, as device-side + // interrupts should trigger the External Interrupt (S_EXT) which + // then claims the interrupt from PLIC and calls the handler. + if !IRQ_HANDLER_TABLE.handle(irq) { + warn!("Unhandled device-side IRQ {}", irq); + } } ) } diff --git a/platforms/axplat-riscv64-qemu-virt/src/lib.rs b/platforms/axplat-riscv64-qemu-virt/src/lib.rs index e5939e04..725ce144 100644 --- a/platforms/axplat-riscv64-qemu-virt/src/lib.rs +++ b/platforms/axplat-riscv64-qemu-virt/src/lib.rs @@ -7,13 +7,19 @@ extern crate axplat; mod boot; mod console; +mod dtb; mod init; #[cfg(feature = "irq")] mod irq; mod mem; +#[cfg(feature = "irq")] +mod plic; mod power; mod time; +#[cfg(test)] +mod tests; + pub mod config { //! Platform configuration module. //! diff --git a/platforms/axplat-riscv64-qemu-virt/src/mem.rs b/platforms/axplat-riscv64-qemu-virt/src/mem.rs index 8870c883..37f74f70 100644 --- a/platforms/axplat-riscv64-qemu-virt/src/mem.rs +++ b/platforms/axplat-riscv64-qemu-virt/src/mem.rs @@ -14,12 +14,38 @@ impl MemIf for MemIfImpl { /// All memory ranges except reserved ranges (including the kernel loaded /// range) are free for allocation. fn phys_ram_ranges() -> &'static [RawRange] { - // TODO: paser dtb to get the available memory ranges - // We can't directly use `PHYS_MEMORY_BASE` here, because it may has been used by sbi. - &[( + // Try to get memory ranges from device tree first + if let Some(dtb_parser) = crate::dtb::get() { + let dtb_ranges = dtb_parser.get_memory_ranges(); + if !dtb_ranges.is_empty() { + // Convert DTB ranges to static format + // We use a simple approach with a fixed-size array for now + static mut DTB_RANGES: [RawRange; 4] = [(0, 0); 4]; + static mut RANGE_COUNT: usize = 0; + static mut INITIALIZED: bool = false; + + unsafe { + if !INITIALIZED { + RANGE_COUNT = dtb_ranges.len().min(4); + for (i, range) in dtb_ranges.iter().take(4).enumerate() { + DTB_RANGES[i] = (range.base as usize, range.size as usize); + } + INITIALIZED = true; + } + + if RANGE_COUNT > 0 { + return &DTB_RANGES[..RANGE_COUNT]; + } + } + } + } + + // Fallback to configuration-based ranges + static DEFAULT_RANGES: [RawRange; 1] = [( KERNEL_BASE_PADDR, PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE - KERNEL_BASE_PADDR, - )] + )]; + &DEFAULT_RANGES } /// Returns all reserved physical memory ranges on the platform. diff --git a/platforms/axplat-riscv64-qemu-virt/src/plic.rs b/platforms/axplat-riscv64-qemu-virt/src/plic.rs new file mode 100644 index 00000000..357f4d9f --- /dev/null +++ b/platforms/axplat-riscv64-qemu-virt/src/plic.rs @@ -0,0 +1,342 @@ +//! PLIC (Platform-Level Interrupt Controller) driver for RISC-V +//! +//! This module implements the PLIC interface for handling external interrupts +//! on RISC-V platforms, specifically for QEMU virt machine. + +use core::ptr::{read_volatile, write_volatile}; + +/// PLIC error types +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PlicError { + InvalidInterruptId, + InvalidContextId, + InvalidPriority, + InvalidThreshold, + NotInitialized, +} + +/// PLIC result type +pub type PlicResult = Result; + +/// PLIC base address from MMIO ranges +const PLIC_BASE: usize = 0x0c00_0000; + +/// PLIC register offsets +const PLIC_PRIORITY_BASE: usize = 0x0000; +#[allow(dead_code)] +const PLIC_PENDING_BASE: usize = 0x1000; +const PLIC_ENABLE_BASE: usize = 0x2000; +const PLIC_THRESHOLD_BASE: usize = 0x200000; +const PLIC_CLAIM_BASE: usize = 0x200004; + +/// Number of interrupt sources supported by PLIC +const PLIC_MAX_INTERRUPTS: usize = 1024; + +/// Number of contexts (hart contexts) supported +const PLIC_MAX_CONTEXTS: usize = 8; + +/// PLIC driver structure +pub struct Plic { + base: usize, + initialized: bool, + max_interrupts: usize, + max_contexts: usize, +} + +impl Plic { + /// Create a new PLIC driver instance + pub const fn new() -> Self { + Self { + base: PLIC_BASE, + initialized: false, + max_interrupts: PLIC_MAX_INTERRUPTS, + max_contexts: PLIC_MAX_CONTEXTS, + } + } + + /// Initialize the PLIC + pub fn init(&mut self) -> PlicResult<()> { + info!("Initializing PLIC at base address 0x{:x}", self.base); + + // Set priority for all interrupts to 1 (minimum non-zero priority) + for i in 1..self.max_interrupts { + if let Err(e) = self.set_priority(i, 1) { + warn!("Failed to set priority for interrupt {}: {:?}", i, e); + } + } + + // Set threshold for context 0 (supervisor mode) to 0 + if let Err(e) = self.set_threshold(0, 0) { + warn!("Failed to set threshold for context 0: {:?}", e); + return Err(e); + } + + self.initialized = true; + info!("PLIC initialized successfully"); + Ok(()) + } + + /// Check if PLIC is initialized + #[allow(dead_code)] + pub fn is_initialized(&self) -> bool { + self.initialized + } + + /// Validate interrupt ID + fn validate_interrupt_id(&self, interrupt_id: usize) -> PlicResult<()> { + if interrupt_id >= self.max_interrupts { + Err(PlicError::InvalidInterruptId) + } else { + Ok(()) + } + } + + /// Validate context ID + fn validate_context_id(&self, context_id: usize) -> PlicResult<()> { + if context_id >= self.max_contexts { + Err(PlicError::InvalidContextId) + } else { + Ok(()) + } + } + + /// Validate priority + fn validate_priority(&self, priority: u32) -> PlicResult<()> { + if priority > 7 { // PLIC supports priority 0-7 + Err(PlicError::InvalidPriority) + } else { + Ok(()) + } + } + + /// Validate threshold + fn validate_threshold(&self, threshold: u32) -> PlicResult<()> { + if threshold > 7 { // PLIC supports threshold 0-7 + Err(PlicError::InvalidThreshold) + } else { + Ok(()) + } + } + + /// Set priority for an interrupt source + pub fn set_priority(&self, interrupt_id: usize, priority: u32) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_interrupt_id(interrupt_id)?; + self.validate_priority(priority)?; + + let reg_addr = self.base + PLIC_PRIORITY_BASE + (interrupt_id * 4); + unsafe { + write_volatile(reg_addr as *mut u32, priority); + } + + Ok(()) + } + + /// Get priority for an interrupt source + #[allow(dead_code)] + pub fn get_priority(&self, interrupt_id: usize) -> PlicResult { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_interrupt_id(interrupt_id)?; + + let reg_addr = self.base + PLIC_PRIORITY_BASE + (interrupt_id * 4); + let priority = unsafe { + read_volatile(reg_addr as *const u32) + }; + + Ok(priority) + } + + /// Enable an interrupt for a specific context + pub fn enable_interrupt(&self, context_id: usize, interrupt_id: usize) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + self.validate_interrupt_id(interrupt_id)?; + + let reg_addr = self.base + PLIC_ENABLE_BASE + (context_id * 0x80) + ((interrupt_id / 32) * 4); + let bit = interrupt_id % 32; + + unsafe { + let mut value = read_volatile(reg_addr as *const u32); + value |= 1 << bit; + write_volatile(reg_addr as *mut u32, value); + } + + Ok(()) + } + + /// Disable an interrupt for a specific context + pub fn disable_interrupt(&self, context_id: usize, interrupt_id: usize) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + self.validate_interrupt_id(interrupt_id)?; + + let reg_addr = self.base + PLIC_ENABLE_BASE + (context_id * 0x80) + ((interrupt_id / 32) * 4); + let bit = interrupt_id % 32; + + unsafe { + let mut value = read_volatile(reg_addr as *const u32); + value &= !(1 << bit); + write_volatile(reg_addr as *mut u32, value); + } + + Ok(()) + } + + /// Set threshold for a context + pub fn set_threshold(&self, context_id: usize, threshold: u32) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + self.validate_threshold(threshold)?; + + let reg_addr = self.base + PLIC_THRESHOLD_BASE + (context_id * 0x1000); + unsafe { + write_volatile(reg_addr as *mut u32, threshold); + } + + Ok(()) + } + + /// Get threshold for a context + #[allow(dead_code)] + pub fn get_threshold(&self, context_id: usize) -> PlicResult { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + + let reg_addr = self.base + PLIC_THRESHOLD_BASE + (context_id * 0x1000); + let threshold = unsafe { + read_volatile(reg_addr as *const u32) + }; + + Ok(threshold) + } + + /// Claim an interrupt (read the highest priority pending interrupt) + pub fn claim(&self, context_id: usize) -> PlicResult> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + + let reg_addr = self.base + PLIC_CLAIM_BASE + (context_id * 0x1000); + let interrupt_id = unsafe { + read_volatile(reg_addr as *const u32) + }; + + if interrupt_id == 0 { + Ok(None) + } else { + Ok(Some(interrupt_id as usize)) + } + } + + /// Complete an interrupt (acknowledge that the interrupt has been handled) + pub fn complete(&self, context_id: usize, interrupt_id: usize) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + self.validate_interrupt_id(interrupt_id)?; + + let reg_addr = self.base + PLIC_CLAIM_BASE + (context_id * 0x1000); + unsafe { + write_volatile(reg_addr as *mut u32, interrupt_id as u32); + } + + Ok(()) + } + + /// Check if an interrupt is pending + #[allow(dead_code)] + pub fn is_pending(&self, interrupt_id: usize) -> PlicResult { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_interrupt_id(interrupt_id)?; + + let reg_addr = self.base + PLIC_PENDING_BASE + ((interrupt_id / 32) * 4); + let bit = interrupt_id % 32; + + let value = unsafe { + read_volatile(reg_addr as *const u32) + }; + + Ok((value & (1 << bit)) != 0) + } + + /// Batch enable multiple interrupts for a context + #[allow(dead_code)] + pub fn enable_interrupts_batch(&self, context_id: usize, interrupt_ids: &[usize]) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + + for &interrupt_id in interrupt_ids { + self.enable_interrupt(context_id, interrupt_id)?; + } + + Ok(()) + } + + /// Batch disable multiple interrupts for a context + #[allow(dead_code)] + pub fn disable_interrupts_batch(&self, context_id: usize, interrupt_ids: &[usize]) -> PlicResult<()> { + if !self.initialized { + return Err(PlicError::NotInitialized); + } + + self.validate_context_id(context_id)?; + + for &interrupt_id in interrupt_ids { + self.disable_interrupt(context_id, interrupt_id)?; + } + + Ok(()) + } +} + +/// Global PLIC instance +static mut PLIC: Plic = Plic::new(); + +/// Initialize the global PLIC instance +pub fn init() -> PlicResult<()> { + unsafe { + // SAFETY: We ensure that PLIC is only initialized once + // and this function is called during system initialization + let plic_ptr = &raw mut PLIC; + (*plic_ptr).init() + } +} + +/// Get a reference to the global PLIC instance +pub fn get() -> &'static Plic { + unsafe { + // SAFETY: We ensure that PLIC is only modified during init + // and this function is called after init, so it's safe to create a reference + // We use a raw pointer to avoid the static_mut_refs warning + #[allow(clippy::deref_addrof)] + &*&raw const PLIC + } +} diff --git a/platforms/axplat-riscv64-qemu-virt/src/tests.rs b/platforms/axplat-riscv64-qemu-virt/src/tests.rs new file mode 100644 index 00000000..00e9881d --- /dev/null +++ b/platforms/axplat-riscv64-qemu-virt/src/tests.rs @@ -0,0 +1,281 @@ +//! Test module for RISC-V platform implementation +//! +//! This module contains functionality tests for the RISC-V platform +//! hardware abstraction layer implementation. + +/// Test utilities for RISC-V platform +pub mod test_utils { + use crate::dtb::MemoryRange; + + /// Test memory range structure + pub fn test_memory_range() -> bool { + let range = MemoryRange { + base: 0x80000000, + size: 0x8000000, + }; + + // Test basic properties + if range.base != 0x80000000 || range.size != 0x8000000 { + return false; + } + + // Test clone and copy + let range2 = range; + if range.base != range2.base || range.size != range2.size { + return false; + } + + let range3 = range.clone(); + if range.base != range3.base || range.size != range3.size { + return false; + } + + true + } + + /// Test basic constants and configurations + pub fn test_basic_constants() -> bool { + // Test that basic constants are defined correctly + crate::config::plat::PHYS_MEMORY_BASE > 0 + && crate::config::plat::PHYS_MEMORY_SIZE > 0 + && crate::config::plat::KERNEL_BASE_PADDR > 0 + && crate::config::plat::PHYS_VIRT_OFFSET > 0 + } + + /// Test device tree parser basic functionality + pub fn test_dtb_parser_basic() -> bool { + use crate::dtb::DtbParser; + + // Test with null pointer + unsafe { + if let Ok(_) = DtbParser::new(core::ptr::null()) { + return false; // Should fail + } + } + + // Test with invalid magic number + let mut invalid_dtb = [0u8; 64]; + unsafe { + if let Ok(_) = DtbParser::new(invalid_dtb.as_ptr()) { + return false; // Should fail + } + } + + true + } + + /// Test device tree parser information methods + pub fn test_dtb_parser_info() -> bool { + use crate::dtb::DtbParser; + + // Create a mock parser for testing + let parser = DtbParser { + dtb_ptr: core::ptr::null(), + struct_ptr: core::ptr::null(), + strings_ptr: core::ptr::null(), + valid: false, + }; + + // Test with invalid parser + if parser.get_memory_ranges().len() != 0 { + return false; + } + if parser.get_cpu_count() != 1 { + return false; // Default fallback + } + if parser.get_timer_frequency().is_some() { + return false; + } + if parser.get_uart_base().is_some() { + return false; + } + if parser.get_plic_base().is_some() { + return false; + } + + // Test with valid parser (should return defaults) + let parser_valid = DtbParser { + dtb_ptr: core::ptr::null(), + struct_ptr: core::ptr::null(), + strings_ptr: core::ptr::null(), + valid: true, + }; + + let memory_ranges = parser_valid.get_memory_ranges(); + if memory_ranges.len() != 1 { + return false; + } + if memory_ranges[0].base != 0x80000000 || memory_ranges[0].size != 0x8000000 { + return false; + } + + if parser_valid.get_cpu_count() != 1 { + return false; + } + if parser_valid.get_timer_frequency() != Some(10_000_000) { + return false; + } + if parser_valid.get_uart_base() != Some(0x10000000) { + return false; + } + if parser_valid.get_plic_base() != Some(0x0c000000) { + return false; + } + + true + } +} + +/// PLIC driver tests (only when irq feature is enabled) +#[cfg(feature = "irq")] +pub mod plic_test_utils { + use crate::plic::{Plic, PlicError}; + + /// Test PLIC driver basic functionality + pub fn test_plic_basic_functionality() -> bool { + // Test PLIC creation + let plic = Plic::new(); + if plic.is_initialized() { + return false; + } + + // Test validation through public APIs + if plic.set_priority(0, 1) != Err(PlicError::InvalidInterruptId) { + return false; + } + if plic.set_priority(1, 1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.set_priority(1024, 1) != Err(PlicError::InvalidInterruptId) { + return false; + } + + if plic.set_priority(1, 8) != Err(PlicError::InvalidPriority) { + return false; + } + if plic.set_threshold(8, 0) != Err(PlicError::InvalidContextId) { + return false; + } + if plic.set_threshold(0, 8) != Err(PlicError::InvalidThreshold) { + return false; + } + + true + } + + /// Test PLIC error handling + pub fn test_plic_error_handling() -> bool { + let plic = Plic::new(); + + // Test operations on uninitialized PLIC + if plic.set_priority(1, 1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.get_priority(1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.enable_interrupt(0, 1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.disable_interrupt(0, 1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.set_threshold(0, 0) != Err(PlicError::NotInitialized) { + return false; + } + if plic.get_threshold(0) != Err(PlicError::NotInitialized) { + return false; + } + if plic.claim(0) != Err(PlicError::NotInitialized) { + return false; + } + if plic.complete(0, 1) != Err(PlicError::NotInitialized) { + return false; + } + if plic.is_pending(1) != Err(PlicError::NotInitialized) { + return false; + } + + true + } + + /// Test PLIC batch operations + pub fn test_plic_batch_operations() -> bool { + let plic = Plic::new(); + + // Test batch operations on uninitialized PLIC + if plic.enable_interrupts_batch(0, &[1, 2, 3]) != Err(PlicError::NotInitialized) { + return false; + } + if plic.disable_interrupts_batch(0, &[1, 2, 3]) != Err(PlicError::NotInitialized) { + return false; + } + + // Test with invalid context + if plic.enable_interrupts_batch(8, &[1, 2, 3]) != Err(PlicError::InvalidContextId) { + return false; + } + if plic.disable_interrupts_batch(8, &[1, 2, 3]) != Err(PlicError::InvalidContextId) { + return false; + } + + true + } +} + +/// Run all tests +pub fn run_all_tests() -> bool { + use crate::test_utils::*; + + let mut all_passed = true; + + // Run basic tests + if !test_memory_range() { + crate::log::error!("Memory range test failed"); + all_passed = false; + } + + if !test_basic_constants() { + crate::log::error!("Basic constants test failed"); + all_passed = false; + } + + if !test_dtb_parser_basic() { + crate::log::error!("DTB parser basic test failed"); + all_passed = false; + } + + if !test_dtb_parser_info() { + crate::log::error!("DTB parser info test failed"); + all_passed = false; + } + + // Run PLIC tests if irq feature is enabled + #[cfg(feature = "irq")] + { + use crate::plic_test_utils::*; + + if !test_plic_basic_functionality() { + crate::log::error!("PLIC basic functionality test failed"); + all_passed = false; + } + + if !test_plic_error_handling() { + crate::log::error!("PLIC error handling test failed"); + all_passed = false; + } + + if !test_plic_batch_operations() { + crate::log::error!("PLIC batch operations test failed"); + all_passed = false; + } + } + + if all_passed { + crate::log::info!("All tests passed!"); + } else { + crate::log::error!("Some tests failed!"); + } + + all_passed +}