From 3241169f91418492da7c9d0bb6ef90a13e3b6be0 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 1 Sep 2024 17:26:25 +0100 Subject: [PATCH] Adds a driver for the Xh3irq interrupt controller --- rp235x-hal/src/lib.rs | 1 + rp235x-hal/src/xh3irq.rs | 239 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 rp235x-hal/src/xh3irq.rs diff --git a/rp235x-hal/src/lib.rs b/rp235x-hal/src/lib.rs index 788741ba5..a98908e5a 100644 --- a/rp235x-hal/src/lib.rs +++ b/rp235x-hal/src/lib.rs @@ -70,6 +70,7 @@ pub mod uart; pub mod usb; pub mod vector_table; pub mod watchdog; +pub mod xh3irq; pub mod xosc; // Provide access to common datastructures to avoid repeating ourselves diff --git a/rp235x-hal/src/xh3irq.rs b/rp235x-hal/src/xh3irq.rs new file mode 100644 index 000000000..ce531c533 --- /dev/null +++ b/rp235x-hal/src/xh3irq.rs @@ -0,0 +1,239 @@ +//! Hazard3 Interrupt Controller (Xh3irq) Driver +//! +//! > Xh3irq controls up to 512 external interrupts, with up to 16 levels of +//! > pre-emption. It is architected as a layer on top of the standard mip.meip +//! > external interrupt line, and all standard RISC-V interrupt behaviour still +//! > applies. +//! +//! See [Section 3.8.6.1](https://rptl.io/rp2350-datasheet#extension-xh3irq-section) for more details + +/// The Machine External Interrupt Enable Array +/// +/// The array contains a read-write bit for each external interrupt request: a +/// `1` bit indicates that interrupt is currently enabled. At reset, all +/// external interrupts are disabled. +/// +/// If enabled, an external interrupt can cause assertion of the standard RISC-V +/// machine external interrupt pending flag (`mip.meip`), and therefore cause +/// the processor to enter the external interrupt vector. See `meipa`. +/// +/// There are up to 512 external interrupts. The upper half of this register +/// contains a 16-bit window into the full 512-bit vector. The window is indexed +/// by the 5 LSBs of the write data. +pub const RVCSR_MEIEA_OFFSET: u32 = 0xbe0; + +/// Machine External Interrupt Pending Array +/// +/// Contains a read-only bit for each external interrupt request. Similarly to +/// `meiea`, this register is a window into an array of up to 512 external +/// interrupt flags. The status appears in the upper 16 bits of the value read +/// from `meipa`, and the lower 5 bits of the value _written_ by the same CSR +/// instruction (or 0 if no write takes place) select a 16-bit window of the +/// full interrupt pending array. +/// +/// A `1` bit indicates that interrupt is currently asserted. IRQs are assumed +/// to be level-sensitive, and the relevant `meipa` bit is cleared by servicing +/// the requestor so that it deasserts its interrupt request. +/// +/// When any interrupt of sufficient priority is both set in `meipa` and enabled +/// in `meiea`, the standard RISC-V external interrupt pending bit `mip.meip` is +/// asserted. In other words, `meipa` is filtered by `meiea` to generate the +/// standard `mip.meip` flag. +pub const RVCSR_MEIPA_OFFSET: u32 = 0xbe1; + +/// Machine External Interrupt Force Array +// +/// Contains a read-write bit for every interrupt request. Writing a 1 to a bit +/// in the interrupt force array causes the corresponding bit to become pending +/// in `meipa`. Software can use this feature to manually trigger a particular +/// interrupt. +/// +/// There are no restrictions on using `meifa` inside of an interrupt. The more +/// useful case here is to schedule some lower- priority handler from within a +/// high-priority interrupt, so that it will execute before the core returns to +/// the foreground code. Implementers may wish to reserve some external IRQs +/// with their external inputs tied to 0 for this purpose. +/// +/// Bits can be cleared by software, and are cleared automatically by hardware +/// upon a read of `meinext` which returns the corresponding IRQ number in +/// `meinext.irq` with `mienext.noirq` clear (no matter whether `meinext.update` +/// is written). +/// +/// `meifa` implements the same array window indexing scheme as `meiea` and +/// `meipa`. +pub const RVCSR_MEIFA_OFFSET: u32 = 0xbe2; + +/// Machine External Interrupt Priority Array +/// +/// Each interrupt has an (up to) 4-bit priority value associated with it, and +/// each access to this register reads and/or writes a 16-bit window containing +/// four such priority values. When less than 16 priority levels are available, +/// the LSBs of the priority fields are hardwired to 0. +/// +/// When an interrupt's priority is lower than the current preemption priority +/// `meicontext.preempt`, it is treated as not being pending for the purposes of +/// `mip.meip`. The pending bit in `meipa` will still assert, but the machine +/// external interrupt pending bit `mip.meip` will not, so the processor will +/// ignore this interrupt. See `meicontext`. +pub const RVCSR_MEIPRA_OFFSET: u32 = 0xbe3; + +/// Machine External Get Next Interrupt +/// +/// Contains the index of the highest-priority external interrupt which is both +/// asserted in `meipa` and enabled in `meiea`, left- shifted by 2 so that it +/// can be used to index an array of 32-bit function pointers. If there is no +/// such interrupt, the MSB is set. +/// +/// When multiple interrupts of the same priority are both pending and enabled, +/// the lowest-numbered wins. Interrupts with priority less than +/// `meicontext.ppreempt` -- the _previous_ preemption priority -- are treated +/// as though they are not pending. This is to ensure that a preempting +/// interrupt frame does not service interrupts which may be in progress in the +/// frame that was preempted. +pub const RVCSR_MEINEXT_OFFSET: u32 = 0xbe4; + +/// Check if a given interrupt is pending +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub fn is_pending(irq: rp235x_pac::Interrupt) -> bool { + let (index, bits) = interrupt_to_mask(irq); + let index = index as u32; + let mut csr_rdata: u32; + // Do a CSR Read-Set on RVCSR_MEIPA_OFFSET + unsafe { + core::arch::asm!( + "csrrs {0}, 0xbe1, {1}", + out(reg) csr_rdata, + in(reg) index + ); + } + let bitmask = (bits as u32) << 16; + (csr_rdata & bitmask) != 0 +} + +/// Enable an interrupt +/// +/// # Safety +/// +/// This function is unsafe because it can break mask-based critical +/// sections. Do not call inside a critical section. +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub unsafe fn unmask(irq: rp235x_pac::Interrupt) { + let mask_index = interrupt_to_mask_index(irq); + // Do a RISC-V CSR Set on RVCSR_MEIEA_OFFSET + unsafe { + core::arch::asm!( + "csrs 0xbe0, {0}", + in(reg) mask_index + ); + } +} + +/// Disable an interrupt +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub fn mask(irq: rp235x_pac::Interrupt) { + let mask_index = interrupt_to_mask_index(irq); + // Do a RISC-V CSR Clear on RVCSR_MEIEA_OFFSET + unsafe { + core::arch::asm!( + "csrc 0xbe0, {0}", + in(reg) mask_index + ); + } +} + +/// Check if an interrupt is enabled +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub fn is_enabled(irq: rp235x_pac::Interrupt) -> bool { + let (index, bits) = interrupt_to_mask(irq); + let index = index as u32; + let mut csr_rdata: u32; + // Do a CSR Read-Set on RVCSR_MEIEA_OFFSET + unsafe { + core::arch::asm!( + "csrrs {0}, 0xbe0, {1}", + out(reg) csr_rdata, + in(reg) index + ); + } + let bitmask = (bits as u32) << 16; + (csr_rdata & bitmask) != 0 +} + +/// Set an interrupt as pending, even if it isn't. +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub fn pend(irq: rp235x_pac::Interrupt) { + let mask_index = interrupt_to_mask_index(irq); + // Do a RISC-V CSR Set on RVCSR_MEIFA_OFFSET + unsafe { + core::arch::asm!( + "csrs 0xbe2, {0}", + in(reg) mask_index + ); + } +} + +/// Check which interrupt should be handled next +/// +/// Also updates the state so next time you call this you'll get a different +/// answer. +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub fn get_next_interrupt() -> Option { + const NOIRQ: u32 = 0x8000_0000; + + let mut csr_rdata: u32; + // Do a CSR Read-Set-Immediate on MEINEXT to set the UPDATE bit + unsafe { + core::arch::asm!( + "csrrsi {0}, 0xbe4, 0x01", + out(reg) csr_rdata, + ); + } + + if (csr_rdata & NOIRQ) != 0 { + return None; + } + + let irq_no = (csr_rdata >> 2) as u8; + let irq = rp235x_pac::Interrupt::try_from(irq_no).unwrap(); + Some(irq) +} + +/// Convert an IRQ into a window selection value and separate a bitmask for that +/// window. +pub const fn interrupt_to_mask(irq: rp235x_pac::Interrupt) -> (u16, u16) { + let irq = irq as u16; + // Select a bank of 16 interrupts out of the 512 total + let index = irq / 16; + // Which interrupt out of the 16 we've selected + let bitmask = 1 << (irq % 16); + (index, bitmask) +} + +/// Convert an IRQ into a 32-bit value that selects a window and a bit within +/// that window. +pub const fn interrupt_to_mask_index(irq: rp235x_pac::Interrupt) -> u32 { + let (index, bits) = interrupt_to_mask(irq); + (bits as u32) << 16 | index as u32 +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_interrupt_to_mask() { + let (index, bitmask) = interrupt_to_mask(rp235x_pac::Interrupt::UART1_IRQ); + // 34 -> window 2, bit 3 + assert_eq!(index, 2); + assert_eq!(bitmask, 0b0000_0000_0000_0100); + } + + #[test] + fn test_interrupt_to_mask_index() { + let mask = interrupt_to_mask_index(rp235x_pac::Interrupt::UART1_IRQ); + // 34 -> bit 3 | window 2 + assert_eq!(mask, 0x0004_0002); + } +} + +// End of file