-
Notifications
You must be signed in to change notification settings - Fork 246
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a driver for the Xh3irq interrupt controller
- Loading branch information
Showing
2 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 check_irq_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. | ||
#[cfg(all(target_arch = "riscv32", target_os = "none"))] | ||
pub unsafe fn enable_irq(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 disable_irq(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_irq_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 set_pending(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<rp235x_pac::Interrupt> { | ||
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); | ||
let mask_index = (bits as u32) << 16 | index as u32; | ||
mask_index | ||
} | ||
|
||
#[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 |