#![cfg_attr(not(target_arch = "riscv32"), allow(unused_unsafe, unused_variables, unused_mut))]
pub use crate::soc::chip::Interrupt;
const SYS_VECTOR_CNT: u16 = 26;
const LOCAL_IRQ_VECTOR_CNT: u16 = 32;
const LOCIEN_IRQ_NUM: u16 = 32;
const LOCIPRI_IRQ_NUM: u16 = 8;
const LOCIPRI_IRQ_BITS: u16 = 4;
const LOCIPRI_FIELD_MASK: u32 = 0xF;
const PRIORITY_IRQ_END: u16 = SYS_VECTOR_CNT + 16 * LOCIPRI_IRQ_NUM;
const LOCIPRI_DEFAULT_VAL: u32 = 0x1111_1111;
#[cfg(target_arch = "riscv32")]
macro_rules! csr_set {
($csr:literal, $v:expr) => {
core::arch::asm!(concat!("csrs ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
};
}
#[cfg(target_arch = "riscv32")]
macro_rules! csr_clear {
($csr:literal, $v:expr) => {
core::arch::asm!(concat!("csrc ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
};
}
#[cfg(target_arch = "riscv32")]
macro_rules! csr_write {
($csr:literal, $v:expr) => {
core::arch::asm!(concat!("csrw ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
};
}
#[cfg(target_arch = "riscv32")]
macro_rules! csr_read {
($csr:literal) => {{
let v: u32;
core::arch::asm!(concat!("csrr {0}, ", $csr), out(reg) v, options(nomem, nostack));
v
}};
}
#[cfg(not(target_arch = "riscv32"))]
macro_rules! csr_set {
($csr:literal, $v:expr) => {{
let _ = $v;
}};
}
#[cfg(not(target_arch = "riscv32"))]
macro_rules! csr_clear {
($csr:literal, $v:expr) => {{
let _ = $v;
}};
}
#[cfg(not(target_arch = "riscv32"))]
macro_rules! csr_write {
($csr:literal, $v:expr) => {{
let _ = $v;
}};
}
#[cfg(not(target_arch = "riscv32"))]
macro_rules! csr_read {
($csr:literal) => {{ 0u32 }};
}
#[inline]
unsafe fn locien_write(idx: u16, mask: u32, set: bool) {
unsafe {
match (idx, set) {
(0, true) => csr_set!("0xbe0", mask),
(0, false) => csr_clear!("0xbe0", mask),
(1, true) => csr_set!("0xbe1", mask),
(1, false) => csr_clear!("0xbe1", mask),
(2, true) => csr_set!("0xbe2", mask),
(2, false) => csr_clear!("0xbe2", mask),
_ => {}
}
}
}
#[inline]
fn locien_read(idx: u16) -> u32 {
unsafe {
match idx {
0 => csr_read!("0xbe0"),
1 => csr_read!("0xbe1"),
2 => csr_read!("0xbe2"),
_ => 0,
}
}
}
#[inline]
fn locipd_read(idx: u16) -> u32 {
unsafe {
match idx {
0 => csr_read!("0xbe8"),
1 => csr_read!("0xbe9"),
2 => csr_read!("0xbea"),
_ => 0,
}
}
}
#[inline]
fn locipri_set_field(idx: u16, shift: u16, value: u32) {
let clear = LOCIPRI_FIELD_MASK << shift;
let set = (value & LOCIPRI_FIELD_MASK) << shift;
unsafe {
macro_rules! rmw {
($csr:literal) => {{
csr_clear!($csr, clear);
csr_set!($csr, set);
}};
}
match idx {
0 => rmw!("0xbc0"),
1 => rmw!("0xbc1"),
2 => rmw!("0xbc2"),
3 => rmw!("0xbc3"),
4 => rmw!("0xbc4"),
5 => rmw!("0xbc5"),
6 => rmw!("0xbc6"),
7 => rmw!("0xbc7"),
8 => rmw!("0xbc8"),
9 => rmw!("0xbc9"),
10 => rmw!("0xbca"),
11 => rmw!("0xbcb"),
12 => rmw!("0xbcc"),
13 => rmw!("0xbcd"),
14 => rmw!("0xbce"),
15 => rmw!("0xbcf"),
_ => {}
}
}
}
#[inline]
fn locipri_read(idx: u16) -> u32 {
unsafe {
match idx {
0 => csr_read!("0xbc0"),
1 => csr_read!("0xbc1"),
2 => csr_read!("0xbc2"),
3 => csr_read!("0xbc3"),
4 => csr_read!("0xbc4"),
5 => csr_read!("0xbc5"),
6 => csr_read!("0xbc6"),
7 => csr_read!("0xbc7"),
8 => csr_read!("0xbc8"),
9 => csr_read!("0xbc9"),
10 => csr_read!("0xbca"),
11 => csr_read!("0xbcb"),
12 => csr_read!("0xbcc"),
13 => csr_read!("0xbcd"),
14 => csr_read!("0xbce"),
15 => csr_read!("0xbcf"),
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Priority(u8);
impl Priority {
pub const P1: Self = Priority(1);
pub const P2: Self = Priority(2);
pub const P3: Self = Priority(3);
pub const P4: Self = Priority(4);
pub const P5: Self = Priority(5);
pub const P6: Self = Priority(6);
pub const P7: Self = Priority(7);
pub const LOWEST: Self = Priority(1);
pub const HIGHEST: Self = Priority(7);
pub const fn from_level(level: u8) -> Option<Self> {
if level >= 1 && level <= 7 { Some(Priority(level)) } else { None }
}
const fn from_register(level: u8) -> Self {
if level < 1 {
Self::LOWEST
} else if level > 7 {
Self::HIGHEST
} else {
Priority(level)
}
}
pub const fn level(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Threshold(u8);
impl Threshold {
pub const T0: Self = Threshold(0);
pub const T1: Self = Threshold(1);
pub const T2: Self = Threshold(2);
pub const T3: Self = Threshold(3);
pub const T4: Self = Threshold(4);
pub const T5: Self = Threshold(5);
pub const T6: Self = Threshold(6);
pub const T7: Self = Threshold(7);
pub const fn from_level(level: u8) -> Option<Self> {
if level <= 7 { Some(Threshold(level)) } else { None }
}
pub const fn level(self) -> u8 {
self.0
}
}
#[inline]
fn irq_num(irq: Interrupt) -> u16 {
irq as u16
}
pub unsafe fn enable(irq: Interrupt) {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
unsafe { csr_set!("mie", 1u32 << n) };
} else if n >= LOCAL_IRQ_VECTOR_CNT {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
unsafe { locien_write(reg, 1u32 << bit, true) };
}
if (SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n) {
let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
let shift = ((n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM) * LOCIPRI_IRQ_BITS;
locipri_set_field(reg, shift, 1);
}
}
pub unsafe fn disable(irq: Interrupt) {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
unsafe { csr_clear!("mie", 1u32 << n) };
} else if n >= LOCAL_IRQ_VECTOR_CNT {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
unsafe { locien_write(reg, 1u32 << bit, false) };
}
clear_pending(irq);
}
pub fn is_enabled(irq: Interrupt) -> bool {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
let mie: u32 = unsafe { csr_read!("mie") };
(mie & (1u32 << n)) != 0
} else if n >= LOCAL_IRQ_VECTOR_CNT {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
(locien_read(reg) & (1u32 << bit)) != 0
} else {
false
}
}
#[instability::unstable]
pub fn set_priority(irq: Interrupt, priority: Priority) {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n) {
let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
locipri_set_field(reg, order * LOCIPRI_IRQ_BITS, priority.0 as u32);
}
}
#[instability::unstable]
pub fn priority(irq: Interrupt) -> Priority {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n) {
let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
let shift = order * LOCIPRI_IRQ_BITS;
let field = (locipri_read(reg) >> shift) & LOCIPRI_FIELD_MASK;
Priority::from_register(field as u8)
} else {
Priority::LOWEST
}
}
#[instability::unstable]
pub fn set_threshold(threshold: Threshold) {
unsafe { csr_write!("0xbfe", threshold.level() as u32) };
}
#[instability::unstable]
pub fn threshold() -> Threshold {
Threshold((unsafe { csr_read!("0xbfe") } & 0x7) as u8)
}
pub fn clear_pending(irq: Interrupt) {
let n = irq_num(irq) as u32;
unsafe {
#[cfg(target_arch = "riscv32")]
core::arch::asm!("fence", options(nostack));
csr_write!("0xbf0", n);
#[cfg(target_arch = "riscv32")]
core::arch::asm!("fence", options(nostack));
}
}
pub fn is_pending(irq: Interrupt) -> bool {
let n = irq_num(irq);
if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
let mip: u32 = unsafe { csr_read!("mip") };
(mip & (1u32 << n)) != 0
} else if n >= LOCAL_IRQ_VECTOR_CNT {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
(locipd_read(reg) & (1u32 << bit)) != 0
} else {
false
}
}
pub fn init() {
unsafe {
csr_write!("0xbc0", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc1", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc2", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc3", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc4", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc5", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc6", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc7", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc8", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbc9", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbca", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbcb", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbcc", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbcd", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbce", LOCIPRI_DEFAULT_VAL);
csr_write!("0xbcf", LOCIPRI_DEFAULT_VAL);
}
}
pub unsafe fn enable_global() {
#[cfg(target_arch = "riscv32")]
unsafe {
core::arch::asm!("csrsi mstatus, 0x8", options(nomem, nostack))
};
}
pub unsafe fn disable_global() {
#[cfg(target_arch = "riscv32")]
unsafe {
core::arch::asm!("csrci mstatus, 0x8", options(nomem, nostack))
};
}
#[instability::unstable]
pub unsafe fn free<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
#[cfg(target_arch = "riscv32")]
let prev: usize;
#[cfg(target_arch = "riscv32")]
unsafe {
core::arch::asm!("csrrci {0}, mstatus, 0x8", out(reg) prev, options(nomem, nostack))
};
let r = f();
#[cfg(target_arch = "riscv32")]
if prev & 0x8 != 0 {
unsafe { core::arch::asm!("csrsi mstatus, 0x8", options(nomem, nostack)) };
}
r
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::*;
#[test]
fn priority_from_level_rejects_low() {
assert_eq!(Priority::from_level(0), None);
}
#[test]
fn priority_from_level_rejects_high() {
assert_eq!(Priority::from_level(8), None);
assert_eq!(Priority::from_level(u8::MAX), None);
}
#[test]
fn priority_from_level_passes_through_in_range() {
for l in 1u8..=7 {
assert_eq!(Priority::from_level(l).unwrap().level(), l);
}
}
#[test]
fn priority_constants_match_levels() {
assert_eq!(Priority::P1.level(), 1);
assert_eq!(Priority::P2.level(), 2);
assert_eq!(Priority::P3.level(), 3);
assert_eq!(Priority::P4.level(), 4);
assert_eq!(Priority::P5.level(), 5);
assert_eq!(Priority::P6.level(), 6);
assert_eq!(Priority::P7.level(), 7);
assert_eq!(Priority::LOWEST, Priority::P1);
assert_eq!(Priority::HIGHEST, Priority::P7);
}
#[test]
fn priority_ordering_is_monotonic() {
assert!(Priority::P1 < Priority::P2);
assert!(Priority::LOWEST < Priority::HIGHEST);
assert!(Priority::P7 > Priority::P3);
}
#[test]
fn irq_num_matches_enum_discriminant() {
assert_eq!(irq_num(Interrupt::TIMER_INT0), 26);
assert_eq!(irq_num(Interrupt::GPIO_INT0), 33);
assert_eq!(irq_num(Interrupt::GPIO_INT1), 34);
assert_eq!(irq_num(Interrupt::GPIO_INT2), 35);
}
fn uses_mie(n: u16) -> bool {
(SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n)
}
fn uses_locien(n: u16) -> bool {
n >= LOCAL_IRQ_VECTOR_CNT
}
#[test]
fn timer_int0_is_mie_tier() {
let n = irq_num(Interrupt::TIMER_INT0);
assert!(uses_mie(n));
assert!(!uses_locien(n));
}
#[test]
fn gpio_ints_are_locien_tier() {
for irq in [Interrupt::GPIO_INT0, Interrupt::GPIO_INT1, Interrupt::GPIO_INT2] {
let n = irq_num(irq);
assert!(uses_locien(n));
assert!(!uses_mie(n));
}
}
#[test]
fn vector_tier_boundary_is_exclusive_at_32() {
assert!(uses_mie(LOCAL_IRQ_VECTOR_CNT - 1));
assert!(!uses_locien(LOCAL_IRQ_VECTOR_CNT - 1));
assert!(!uses_mie(LOCAL_IRQ_VECTOR_CNT));
assert!(uses_locien(LOCAL_IRQ_VECTOR_CNT));
assert!(!uses_mie(SYS_VECTOR_CNT - 1));
assert!(!uses_locien(SYS_VECTOR_CNT - 1));
}
fn locien_reg_bit(n: u16) -> (u16, u16) {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
(reg, bit)
}
#[test]
fn locien_split_for_gpio_ints() {
assert_eq!(locien_reg_bit(irq_num(Interrupt::GPIO_INT0)), (0, 1));
assert_eq!(locien_reg_bit(irq_num(Interrupt::GPIO_INT1)), (0, 2));
assert_eq!(locien_reg_bit(irq_num(Interrupt::GPIO_INT2)), (0, 3));
}
#[test]
fn locien_split_register_boundaries() {
assert_eq!(locien_reg_bit(LOCAL_IRQ_VECTOR_CNT), (0, 0));
assert_eq!(locien_reg_bit(LOCAL_IRQ_VECTOR_CNT + LOCIEN_IRQ_NUM - 1), (0, 31));
assert_eq!(locien_reg_bit(LOCAL_IRQ_VECTOR_CNT + LOCIEN_IRQ_NUM), (1, 0));
}
fn locipri_reg_shift(n: u16) -> (u16, u16) {
let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
(reg, order * LOCIPRI_IRQ_BITS)
}
fn in_priority_range(n: u16) -> bool {
(SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n)
}
#[test]
fn locipri_split_for_known_irqs() {
assert_eq!(locipri_reg_shift(SYS_VECTOR_CNT), (0, 0));
assert_eq!(locipri_reg_shift(irq_num(Interrupt::TIMER_INT0)), (0, 0));
assert_eq!(locipri_reg_shift(irq_num(Interrupt::GPIO_INT0)), (0, 28));
assert_eq!(locipri_reg_shift(irq_num(Interrupt::GPIO_INT1)), (1, 0));
}
#[test]
fn locipri_shift_stays_within_a_word() {
let max_shift = (LOCIPRI_IRQ_NUM - 1) * LOCIPRI_IRQ_BITS;
assert_eq!(max_shift, 28);
assert!(u32::from(max_shift) + u32::from(LOCIPRI_IRQ_BITS) <= 32);
}
#[test]
fn priority_range_end_matches_register_count() {
assert_eq!(PRIORITY_IRQ_END, SYS_VECTOR_CNT + 16 * LOCIPRI_IRQ_NUM);
assert!(!in_priority_range(SYS_VECTOR_CNT - 1));
assert!(in_priority_range(SYS_VECTOR_CNT));
assert!(in_priority_range(PRIORITY_IRQ_END - 1));
assert!(!in_priority_range(PRIORITY_IRQ_END));
}
#[test]
fn locipri_default_val_is_all_priority_one() {
for order in 0..LOCIPRI_IRQ_NUM {
let shift = order * LOCIPRI_IRQ_BITS;
let field = (LOCIPRI_DEFAULT_VAL >> shift) & LOCIPRI_FIELD_MASK;
assert_eq!(field, 1, "field at order {order} should be priority 1");
}
assert_eq!(LOCIPRI_DEFAULT_VAL, 0x1111_1111);
}
#[test]
fn threshold_from_level_accepts_only_three_bit_values() {
for level in 0u8..=7 {
assert_eq!(Threshold::from_level(level).unwrap().level(), level);
}
assert_eq!(Threshold::from_level(8), None);
assert_eq!(Threshold::from_level(u8::MAX), None);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn priority_from_level_acceptance_matches_range(level in any::<u8>()) {
let p = Priority::from_level(level);
prop_assert_eq!(p.is_some(), (1..=7).contains(&level));
}
#[test]
fn priority_from_level_roundtrips_when_valid(level in 1u8..=7) {
prop_assert_eq!(Priority::from_level(level).unwrap().level(), level);
}
#[test]
fn threshold_from_level_acceptance_matches_range(level in any::<u8>()) {
let threshold = Threshold::from_level(level);
prop_assert_eq!(threshold.is_some(), level <= 7);
if let Some(threshold) = threshold {
prop_assert_eq!(threshold.level(), level);
}
}
#[test]
fn locien_split_reconstructs(n in LOCAL_IRQ_VECTOR_CNT..=127u16) {
let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
prop_assert!(bit < LOCIEN_IRQ_NUM);
prop_assert_eq!(reg * LOCIEN_IRQ_NUM + bit + LOCAL_IRQ_VECTOR_CNT, n);
}
#[test]
fn locipri_field_fits_in_word(n in SYS_VECTOR_CNT..PRIORITY_IRQ_END) {
let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
let shift = order * LOCIPRI_IRQ_BITS;
prop_assert!(u32::from(shift) + u32::from(LOCIPRI_IRQ_BITS) <= 32);
}
}
}