Explorar o código

feat(interrupt): add external interrupt support for riscv64

greatbridf hai 7 meses
pai
achega
dad0fa00ef

+ 1 - 27
Cargo.lock

@@ -106,8 +106,7 @@ dependencies = [
  "eonix_sync_base",
  "fdt",
  "intrusive_list",
- "riscv 0.13.0",
- "riscv-peripheral",
+ "riscv",
  "sbi",
 ]
 
@@ -339,19 +338,6 @@ dependencies = [
  "riscv-pac",
 ]
 
-[[package]]
-name = "riscv"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f1671c79a01a149fe000af2429ce9ccc8e58cdecda72672355d50e5536b363c"
-dependencies = [
- "critical-section",
- "embedded-hal",
- "paste",
- "riscv-macros",
- "riscv-pac",
-]
-
 [[package]]
 name = "riscv-macros"
 version = "0.2.0"
@@ -369,18 +355,6 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436"
 
-[[package]]
-name = "riscv-peripheral"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50c11ddf3e2a21206642bfe6d5e06f76d5225c1fbece09952dcd40f8c49409a"
-dependencies = [
- "embedded-hal",
- "paste",
- "riscv 0.14.0",
- "riscv-pac",
-]
-
 [[package]]
 name = "safe-mmio"
 version = "0.2.5"

+ 0 - 1
crates/eonix_hal/Cargo.toml

@@ -21,6 +21,5 @@ intrusive_list = { path = "../intrusive_list" }
 buddy_allocator = { path = "../buddy_allocator" }
 sbi = "0.3.0"
 riscv = { version = "0.13.0", features = ["s-mode"] }
-riscv-peripheral = { version = "0.3.0" }
 fdt = "0.1"
 bitflags = "2.6.0"

+ 6 - 3
crates/eonix_hal/src/arch/riscv64/bootstrap.rs

@@ -1,6 +1,8 @@
 use super::{
     config::{self, mm::*},
     console::write_str,
+    cpu::CPUID,
+    time::set_next_timer,
     trap::TRAP_SCRATCH,
 };
 use crate::{
@@ -8,7 +10,6 @@ use crate::{
         cpu::CPU,
         fdt::{init_dtb_and_fdt, FdtExt},
         mm::{ArchPhysAccess, FreeRam, PageAttribute64, GLOBAL_PAGE_TABLE},
-        interrupt::enable_timer_interrupt,
     },
     bootstrap::BootStrapData,
     mm::{ArchMemory, ArchPagingMode, BasicPageAlloc, BasicPageAllocRef, ScopedAllocator},
@@ -209,9 +210,11 @@ fn setup_cpu(alloc: impl PageAlloc, hart_id: usize) {
         }
     });
 
+    CPUID.set(hart_id);
+
     let mut cpu = CPU::local();
     unsafe {
-        cpu.as_mut().init(hart_id);
+        cpu.as_mut().init();
     }
 
     percpu_area.register(cpu.cpuid());
@@ -224,7 +227,7 @@ fn setup_cpu(alloc: impl PageAlloc, hart_id: usize) {
     }
 
     // set current hart's mtimecmp register
-    enable_timer_interrupt();
+    set_next_timer();
 }
 
 /// TODO

+ 10 - 12
crates/eonix_hal/src/arch/riscv64/cpu.rs

@@ -10,11 +10,13 @@ use riscv::register::{
     medeleg::{self, Medeleg},
     mhartid, sscratch, sstatus,
 };
-use riscv_peripheral::plic::PLIC;
 use sbi::PhysicalAddress;
 
 #[eonix_percpu::define_percpu]
-static LOCAL_CPU: LazyLock<CPU> = LazyLock::new(CPU::new);
+pub static CPUID: usize = 0;
+
+#[eonix_percpu::define_percpu]
+static LOCAL_CPU: LazyLock<CPU> = LazyLock::new(|| CPU::new(CPUID.get()));
 
 #[derive(Debug, Clone)]
 pub enum UserTLS {
@@ -23,8 +25,7 @@ pub enum UserTLS {
 
 /// RISC-V Hart
 pub struct CPU {
-    hart_id: usize,
-    interrupt: InterruptControl,
+    pub(crate) interrupt: InterruptControl,
 }
 
 impl UserTLS {
@@ -34,10 +35,9 @@ impl UserTLS {
 }
 
 impl CPU {
-    pub fn new() -> Self {
+    fn new(cpuid: usize) -> Self {
         Self {
-            hart_id: 0,
-            interrupt: InterruptControl::new(),
+            interrupt: InterruptControl::new(cpuid),
         }
     }
 
@@ -46,10 +46,8 @@ impl CPU {
     /// # Safety
     /// This function performs low-level hardware initialization and should
     /// only be called once per Hart during its boot sequence.
-    pub unsafe fn init(mut self: Pin<&mut Self>, hart_id: usize) {
+    pub unsafe fn init(mut self: Pin<&mut Self>) {
         let me = self.as_mut().get_unchecked_mut();
-        me.hart_id = hart_id;
-
         setup_trap();
 
         let interrupt = self.map_unchecked_mut(|me| &mut me.interrupt);
@@ -62,7 +60,7 @@ impl CPU {
     /// Boot all other hart.
     pub unsafe fn bootstrap_cpus(&self) {
         let total_harts = FDT.hart_count();
-        for i in (0..total_harts).filter(|&i| i != self.hart_id) {
+        for i in (0..total_harts).filter(|&i| i != self.cpuid()) {
             sbi::hsm::hart_start(i, todo!("AP entry"), 0)
                 .expect("Failed to start secondary hart via SBI");
         }
@@ -87,7 +85,7 @@ impl CPU {
     }
 
     pub fn cpuid(&self) -> usize {
-        self.hart_id
+        CPUID.get()
     }
 }
 

+ 131 - 27
crates/eonix_hal/src/arch/riscv64/interrupt/mod.rs

@@ -1,48 +1,152 @@
-use core::pin::Pin;
-
+use super::{config::platform::virt::*, fdt::FDT, fence::memory_barrier, mm::ArchPhysAccess};
 use crate::arch::time;
-
-use super::config::platform::virt::*;
+use core::{pin::Pin, ptr::NonNull};
+use eonix_mm::address::{PAddr, PhysAccess};
+use eonix_sync_base::LazyLock;
 use riscv::register::sie;
-use riscv_peripheral::{
-    aclint::{Clint, CLINT},
-    plic::{Plic, PLIC},
-};
 use sbi::SbiError;
 
-#[derive(Clone, Copy)]
-struct ArchPlic;
+const PRIORITY_OFFSET: usize = 0x0;
+const PENDING_OFFSET: usize = 0x1000;
 
-#[derive(Clone, Copy)]
-struct ArchClint;
+const ENABLE_OFFSET: usize = 0x2000;
+const THRESHOLD_OFFSET: usize = 0x200000;
+const CLAIM_COMPLETE_OFFSET: usize = 0x200004;
 
-unsafe impl Plic for ArchPlic {
-    const BASE: usize = PLIC_BASE;
-}
+const ENABLE_STRIDE: usize = 0x80;
+const CONTEXT_STRIDE: usize = 0x1000;
+
+static PLIC_BASE: LazyLock<PAddr> = LazyLock::new(|| {
+    let plic = FDT
+        .find_compatible(&["riscv,plic0", "riscv,plic1"])
+        .expect("Failed to find PLIC in FDT");
+
+    let reg = plic
+        .reg()
+        .expect("PLIC node has no reg property")
+        .next()
+        .expect("PLIC reg property is empty");
+
+    PAddr::from(reg.starting_address as usize)
+});
 
-unsafe impl Clint for ArchClint {
-    const BASE: usize = CLINT_BASE;
-    const MTIME_FREQ: usize = CPU_FREQ_HZ as usize;
+pub struct PLIC {
+    enable: NonNull<u32>,
+    threshold: NonNull<u32>,
+    claim_complete: NonNull<u32>,
 }
 
-/// Architecture-specific interrupt control block.
 pub struct InterruptControl {
-    clint: CLINT<ArchClint>,
+    pub plic: PLIC,
+}
+
+impl PLIC {
+    fn new(cpuid: usize) -> Self {
+        let base = *PLIC_BASE.get();
+
+        let enable = PAddr::from(base + (cpuid * 2 + 1) * ENABLE_STRIDE + ENABLE_OFFSET);
+        let threshold = PAddr::from(base + (cpuid * 2 + 1) * CONTEXT_STRIDE + THRESHOLD_OFFSET);
+        let claim_complete =
+            PAddr::from(base + (cpuid * 2 + 1) * CONTEXT_STRIDE + CLAIM_COMPLETE_OFFSET);
+
+        unsafe {
+            // SAFETY: The PLIC registers are memory-mapped and placed at specific addresses.
+            //         We are pretty sure that the addresses are valid.
+            Self {
+                enable: ArchPhysAccess::as_ptr(enable),
+                threshold: ArchPhysAccess::as_ptr(threshold),
+                claim_complete: ArchPhysAccess::as_ptr(claim_complete),
+            }
+        }
+    }
+
+    pub fn set_threshold(&self, threshold: u32) {
+        unsafe {
+            self.threshold.write_volatile(threshold);
+        }
+    }
+
+    pub fn set_priority(&self, interrupt: usize, priority: u32) {
+        let priority_ptr = unsafe {
+            // SAFETY: The PLIC priority register is memory-mapped and placed at a specific address.
+            //         We are pretty sure that the address is valid.
+            ArchPhysAccess::as_ptr(
+                *PLIC_BASE.get() + PRIORITY_OFFSET + interrupt * size_of::<u32>(),
+            )
+        };
+
+        memory_barrier();
+
+        unsafe {
+            priority_ptr.write_volatile(priority);
+        }
+
+        memory_barrier();
+    }
+
+    pub fn claim_interrupt(&self) -> Option<usize> {
+        match unsafe { self.claim_complete.read_volatile() } {
+            0 => None,
+            interrupt => Some(interrupt as usize),
+        }
+    }
+
+    pub fn complete_interrupt(&self, interrupt: usize) {
+        unsafe {
+            self.claim_complete.write_volatile(interrupt as u32);
+        }
+    }
+
+    pub fn enable_interrupt(&self, interrupt: usize) {
+        debug_assert!(interrupt < 1024, "Interrupt number out of range");
+
+        let enable_ptr = unsafe {
+            // SAFETY: Interrupt number is guaranteed to be less than 1024,
+            //         so we won't overflow the enable register array.
+            self.enable.add(interrupt / 32)
+        };
+
+        let bit = 1 << (interrupt % 32);
+        unsafe {
+            enable_ptr.write_volatile(enable_ptr.read_volatile() | bit);
+        }
+    }
+
+    pub fn disable_interrupt(&self, interrupt: usize) {
+        let enable_ptr = unsafe {
+            // SAFETY: Interrupt number is guaranteed to be less than 1024,
+            //         so we won't overflow the enable register array.
+            self.enable.add(interrupt / 32)
+        };
+
+        let bit = 1 << (interrupt % 32);
+        unsafe {
+            enable_ptr.write_volatile(enable_ptr.read_volatile() & !bit);
+        }
+    }
 }
 
 impl InterruptControl {
     /// # Safety
     /// should be called only once.
-    pub(crate) fn new() -> Self {
+    pub(crate) fn new(cpuid: usize) -> Self {
         Self {
-            clint: CLINT::new(),
+            plic: PLIC::new(cpuid),
         }
     }
 
-    pub fn init(self: Pin<&mut Self>) {}
-}
+    pub fn init(self: Pin<&mut Self>) {
+        self.plic.set_threshold(0);
+
+        // TODO: We should enable interrupts only when we register a handler.
+        for i in 0..32 {
+            self.plic.set_priority(i, 1);
+            self.plic.enable_interrupt(i);
+        }
 
-pub fn enable_timer_interrupt() {
-    unsafe { sie::set_stimer() };
-    time::set_next_timer();
+        unsafe {
+            sie::set_stimer();
+            sie::set_sext();
+        }
+    }
 }

+ 10 - 5
crates/eonix_hal/src/arch/riscv64/trap/trap_context.rs

@@ -1,4 +1,4 @@
-use crate::arch::time::set_next_timer;
+use crate::{arch::time::set_next_timer, processor::CPU};
 use core::arch::asm;
 use eonix_hal_traits::{
     fault::{Fault, PageFaultErrorCode},
@@ -141,15 +141,20 @@ impl RawTrapContext for TrapContext {
                 match Interrupt::from_number(i).unwrap() {
                     Interrupt::SupervisorTimer => TrapType::Timer {
                         callback: |handler| {
-                            handler();
                             set_next_timer();
+                            handler();
                         },
                     },
                     Interrupt::SupervisorExternal => TrapType::Irq {
                         callback: |handler| {
-                            // TODO: Read PLIC to determine the IRQ number.
-                            //       Use 0xa (serial) for now.
-                            handler(0xa);
+                            let mut cpu = CPU::local();
+                            match cpu.as_mut().interrupt.plic.claim_interrupt() {
+                                None => {}
+                                Some(irqno) => {
+                                    cpu.interrupt.plic.complete_interrupt(irqno);
+                                    handler(irqno);
+                                }
+                            }
                         },
                     },
                     // soft interrupt

+ 3 - 2
crates/eonix_hal/src/arch/x86_64/trap/trap_context.rs

@@ -92,8 +92,8 @@ impl RawTrapContext for TrapContext {
             0..0x20 => TrapType::Fault(self.get_fault_type()),
             0x40 => TrapType::Timer {
                 callback: |handler| {
-                    handler();
                     CPU::local().as_mut().end_of_interrupt();
+                    handler();
                 },
             },
             0x80 => TrapType::Syscall {
@@ -110,7 +110,6 @@ impl RawTrapContext for TrapContext {
             no => TrapType::Irq {
                 callback: move |handler| {
                     let irqno = no as usize - 0x20;
-                    handler(irqno);
 
                     use crate::arch::io::Port8;
 
@@ -121,6 +120,8 @@ impl RawTrapContext for TrapContext {
                     if irqno >= 8 {
                         PIC2_COMMAND.write(0x20); // EOI
                     }
+
+                    handler(irqno);
                 },
             },
         }