Explorar el Código

feat(hal): impl riscv64's interrupt data structure

Heinz hace 8 meses
padre
commit
ef171c4792

+ 62 - 0
crates/eonix_hal/src/arch/riscv64/interrupt/clint.rs

@@ -0,0 +1,62 @@
+use super::super::config::platform::virt::*;
+use core::ptr;
+
+use sbi::{
+    ipi::send_ipi,
+    timer::set_timer,
+    HartMask,
+    SbiError
+};
+use riscv::register::mie;
+
+/// CLINT (Core Local Interruptor) driver
+/// This struct now owns the base address and hart_id.
+pub struct ClintDriver {
+    base_addr: usize,
+    hart_id: usize,
+}
+
+impl ClintDriver {
+    pub fn new(base_addr: usize, hart_id: usize) -> Self {
+        let driver = ClintDriver { base_addr, hart_id };
+
+        driver.clear_soft_interrupt_pending(hart_id);
+
+        // Enable Supervisor-mode Software Interrupts (SSIE)
+        // and Supervisor-mode Timer Interrupts (STIE) in the `mie` CSR.
+        unsafe {
+            mie::set_ssoft(); // Enable S-mode Software Interrupts
+            mie::set_stimer(); // Enable S-mode Timer Interrupts
+        }
+
+        driver
+    }
+
+    /// Reads the current value of the global MTIME (Machine Timer) counter.
+    pub fn get_time(&self) -> u64 {
+        unsafe {
+            // MTIME is a 64-bit counter at CLINT_BASE + CLINT_MTIME_OFFSET
+            ptr::read_volatile((self.base_addr + CLINT_MTIME_OFFSET) as *mut u64)
+        }
+    }
+
+    /// Sets the next timer interrupt trigger point using SBI.
+    pub fn set_timer(&self, time_value: u64) -> Result<(), SbiError> {
+        set_timer(time_value)
+    }
+
+    /// Sends an Inter-Processor Interrupt (IPI) to the specified Hart(s).
+    pub fn send_ipi(&self, hart_id_mask: usize) -> Result<(), SbiError> {
+        // This utilizes the SBI `send_ipi` call.
+        send_ipi(HartMask::from(hart_id_mask))
+    }
+
+    /// Clears the software interrupt pending bit for the specified Hart.
+    pub fn clear_soft_interrupt_pending(&self, hart_id: usize) {
+        unsafe {
+            // MSIP registers are typically located at CLINT_BASE + 4 * hart_id.
+            // Writing 0 to the register clears the pending bit.
+            ptr::write_volatile((self.base_addr + CLINT_MSIP_OFFSET + hart_id * 4) as *mut u32, 0);
+        }
+    }
+}

+ 79 - 0
crates/eonix_hal/src/arch/riscv64/interrupt/mod.rs

@@ -0,0 +1,79 @@
+mod plic;
+mod clint;
+
+use plic::*;
+use clint::*;
+
+use sbi::SbiError;
+
+use super::config::platform::virt::*;
+
+/// Architecture-specific interrupt control block.
+pub struct InterruptControl {
+    hart_id: usize,
+    plic: PlicDriver,
+    clint: ClintDriver,
+}
+
+impl InterruptControl {
+    /// # Safety
+    /// should be called only once.
+    pub(crate) fn new(hart_id: usize) -> Self {
+        // Initialize PLICDriver for this Hart
+        let plic = PlicDriver::new(PLIC_BASE, hart_id);
+
+        // Initialize ClintDriver for this Hart
+        let clint = ClintDriver::new(CLINT_BASE, hart_id);
+
+        Self {
+            hart_id,
+            plic: plic,
+            clint: clint,
+        }
+    }
+
+    /// Configures the CLINT timer for a periodic interrupt.
+    ///
+    /// # Arguments
+    /// * `interval_us`: The desired interval between timer interrupts in microseconds.
+    pub fn setup_timer(&self, interval_us: u64) {
+        let current_time = self.clint.get_time();
+        // Calculate ticks per microsecond based on CPU_FREQ_HZ
+        let ticks_per_us = CPU_FREQ_HZ / 1_000_000;
+        let ticks = interval_us * ticks_per_us;
+
+        let next_timer_at = current_time.checked_add(ticks).unwrap_or(u64::MAX);
+
+        if let Err(e) = self.clint.set_timer(next_timer_at) {
+            panic!("Failed to set CLINT timer: {:?}", e);
+        }
+    }
+
+    /// Sends an Inter-Processor Interrupt (IPI) to target Harts.
+    pub fn send_ipi(&self, target_hart_mask: usize) -> Result<(), SbiError> {
+        self.clint.send_ipi(target_hart_mask)
+    }
+
+    /// Handles the "End Of Interrupt" (EOI) for the most recently claimed
+    /// external interrupt.
+    pub fn end_of_external_interrupt(&self, irq_id: u32) {
+        self.plic.complete_interrupt(irq_id);
+    }
+
+    /// Clears the pending software interrupt for the current Hart.
+    pub fn clear_soft_interrupt_pending(&self) {
+        self.clint.clear_soft_interrupt_pending(self.hart_id);
+    }
+
+    pub fn plic_enable_interrupt(&self, irq_id: u32) {
+        self.plic.enable_interrupt(irq_id);
+    }
+
+    pub fn plic_set_priority(&self, irq_id: u32, priority: u32) {
+        self.plic.set_priority(irq_id, priority);
+    }
+
+    pub fn plic_claim_interrupt(&self) -> u32 {
+        self.plic.claim_interrupt()
+    }
+}

+ 137 - 0
crates/eonix_hal/src/arch/riscv64/interrupt/plic.rs

@@ -0,0 +1,137 @@
+use core::ptr::{read_volatile, write_volatile};
+
+use super::super::config::platform::virt::*;
+
+use riscv::register::mie;
+
+pub struct PlicDriver {
+    base_addr: usize,
+    hart_id: usize,
+}
+
+impl PlicDriver {
+    pub fn new(base_addr: usize, hart_id: usize) -> Self {
+        let driver = PlicDriver { base_addr, hart_id };
+
+        let s_context_id = driver.s_context_id();
+
+        driver.set_priority_threshold(0);
+
+        let enable_reg_base_for_context = driver.enable_addr(s_context_id);
+        // PLIC enable bits are grouped into 32-bit registers.
+        // Assuming 32 registers for up to 1024 IRQs, but 32 is common for a few hundred.
+        for i in 0..(driver.max_irq_num() / 32 + 1) {
+            let reg_addr = (enable_reg_base_for_context as usize + (i as usize) * 4) as *mut u32;
+            unsafe {
+                (reg_addr as *mut u32).write_volatile(0);
+            }
+        }
+
+        unsafe {
+            mie::set_sext();
+        }
+
+        driver.set_priority(10, 1);
+        driver.enable_interrupt(10);
+
+        // TODO: may need more set_priority
+        // driver.set_priority(UART_IRQ_ID, 1);
+        // driver.enable_interrupt(UART_IRQ_ID);
+        // driver.set_priority(VIRTIO_BLOCK_IRQ_ID, 1);
+        // driver.enable_interrupt(VIRTIO_BLOCK_IRQ_ID);
+
+        driver
+    }
+
+    fn s_context_id(&self) -> usize {
+        self.hart_id * PLIC_S_MODE_CONTEXT_STRIDE + 1
+    }
+
+    fn priority_addr(&self, irq_id: u32) -> *mut u32 {
+        (self.base_addr + PLIC_PRIORITY_OFFSET + (irq_id as usize) * 4) as *mut u32
+    }
+
+    fn pending_addr(&self) -> *mut u32 {
+        (self.base_addr + PLIC_PENDING_OFFSET) as *mut u32
+    }
+
+    fn enable_addr(&self, context_id: usize) -> *mut u32 {
+        // PLIC enable bits are typically organized as 32-bit banks.
+        // The offset for each context's enable registers.
+        // A common stride for context enable blocks is 0x80 (128 bytes).
+        (self.base_addr + PLIC_ENABLE_OFFSET + context_id * PLIC_ENABLE_PER_HART_OFFSET) as *mut u32
+    }
+
+    fn threshold_addr(&self, context_id: usize) -> *mut u32 {
+        // A common stride for context threshold/claim/complete registers is 0x1000 (4KB).
+        (self.base_addr + PLIC_THRESHOLD_OFFSET + context_id * PLIC_THRESHOLD_CLAIM_COMPLETE_PER_HART_OFFSET) as *mut u32
+    }
+
+    fn claim_complete_addr(&self, context_id: usize) -> *mut u32 {
+        (self.base_addr + PLIC_CLAIM_COMPLETE_OFFSET + context_id * PLIC_THRESHOLD_CLAIM_COMPLETE_PER_HART_OFFSET) as *mut u32
+    }
+
+    pub fn set_priority(&self, irq_id: u32, priority: u32) {
+        unsafe {
+            write_volatile(self.priority_addr(irq_id), priority);
+        }
+    }
+
+    pub fn get_priority(&self, irq_id: u32) -> u32 {
+        unsafe { read_volatile(self.priority_addr(irq_id)) }
+    }
+
+    pub fn enable_interrupt(&self, irq_id: u32) {
+        let context_id = self.s_context_id();
+        let enable_reg_offset_in_bank = (irq_id / 32) as usize * 4;
+        let bit_index_in_reg = irq_id % 32;
+
+        let enable_reg_addr = (self.enable_addr(context_id) as usize + enable_reg_offset_in_bank) as *mut u32;
+        let bit_mask = 1 << bit_index_in_reg;
+        unsafe {
+            let old = read_volatile(enable_reg_addr);
+            write_volatile(enable_reg_addr, old | bit_mask);
+        }
+    }
+
+    pub fn disable_interrupt(&self, irq_id: u32) {
+        let context_id = self.s_context_id();
+        let enable_reg_offset_in_bank = (irq_id / 32) as usize * 4;
+        let bit_index_in_reg = irq_id % 32;
+
+        let enable_reg_addr = (self.enable_addr(context_id) as usize + enable_reg_offset_in_bank) as *mut u32;
+        let bit_mask = 1 << bit_index_in_reg;
+        unsafe {
+            let old = read_volatile(enable_reg_addr);
+            write_volatile(enable_reg_addr, old & !bit_mask);
+        }
+    }
+
+    pub fn set_priority_threshold(&self, threshold: u32) {
+        let context_id = self.s_context_id();
+        unsafe {
+            write_volatile(self.threshold_addr(context_id), threshold);
+        }
+    }
+
+    pub fn get_priority_threshold(&self) -> u32 {
+        let context_id = self.s_context_id();
+        unsafe { read_volatile(self.threshold_addr(context_id)) }
+    }
+
+    pub fn claim_interrupt(&self) -> u32 {
+        let context_id = self.s_context_id();
+        unsafe { read_volatile(self.claim_complete_addr(context_id)) }
+    }
+
+    pub fn complete_interrupt(&self, irq_id: u32) {
+        let context_id = self.s_context_id();
+        unsafe {
+            write_volatile(self.claim_complete_addr(context_id), irq_id);
+        }
+    }
+
+    pub fn max_irq_num(&self) -> u32 {
+        127
+    }
+}