#![no_std]
#![deny(warnings, missing_docs)]
use core::{cell::UnsafeCell, mem::size_of, num::NonZeroU32};
const COUNT_SOURCE: usize = 1024;
const COUNT_CONTEXT: usize = 15872;
const U32_BITS: usize = u32::BITS as _;
#[repr(transparent)]
struct Priorities([UnsafeCell<u32>; COUNT_SOURCE]);
#[repr(transparent)]
struct PendingBits([UnsafeCell<u32>; COUNT_SOURCE / U32_BITS]);
#[repr(transparent)]
struct Enables([UnsafeCell<u32>; COUNT_SOURCE * COUNT_CONTEXT / U32_BITS]);
#[repr(C, align(4096))]
struct ContextLocal {
priority_threshold: UnsafeCell<u32>,
claim_or_completion: UnsafeCell<u32>,
_reserved: [u8; 4096 - 2 * size_of::<u32>()],
}
pub trait InterruptSource {
fn id(self) -> NonZeroU32;
}
#[cfg(feature = "primitive-id")]
impl InterruptSource for NonZeroU32 {
#[inline]
fn id(self) -> NonZeroU32 {
self
}
}
#[cfg(feature = "primitive-id")]
impl InterruptSource for u32 {
#[inline]
fn id(self) -> NonZeroU32 {
NonZeroU32::new(self).expect("interrupt source id can not be zero")
}
}
pub trait HartContext {
fn index(self) -> usize;
}
#[cfg(feature = "primitive-id")]
impl HartContext for usize {
#[inline]
fn index(self) -> usize {
self
}
}
#[repr(C, align(4096))]
pub struct Plic {
priorities: Priorities,
pending_bits: PendingBits,
_reserved0: [u8; 4096 - size_of::<PendingBits>()],
enables: Enables,
_reserved1: [u8; 0xe000],
context_local: [ContextLocal; COUNT_CONTEXT],
}
impl Plic {
#[inline]
pub const unsafe fn from_addr(addr: usize) -> &'static Self {
&*(addr as *const Self)
}
#[inline]
pub fn set_priority<S>(&self, source: S, value: u32)
where
S: InterruptSource,
{
let ptr = self.priorities.0[source.id().get() as usize].get();
unsafe { ptr.write_volatile(value) }
}
#[inline]
pub fn get_priority<S>(&self, source: S) -> u32
where
S: InterruptSource,
{
let ptr = self.priorities.0[source.id().get() as usize].get();
unsafe { ptr.read_volatile() }
}
#[inline]
pub fn probe_priority_bits<S>(&self, source: S) -> u32
where
S: InterruptSource,
{
let ptr = self.priorities.0[source.id().get() as usize].get();
unsafe {
ptr.write_volatile(!0);
ptr.read_volatile()
}
}
#[inline]
pub fn is_pending<S>(&self, source: S) -> bool
where
S: InterruptSource,
{
let source = source.id().get() as usize;
let group = source / U32_BITS;
let index = source % U32_BITS;
let ptr = self.pending_bits.0[group].get();
(unsafe { ptr.read_volatile() } & (1 << index)) != 0
}
#[inline]
pub fn enable<S, C>(&self, source: S, context: C)
where
S: InterruptSource,
C: HartContext,
{
let source = source.id().get() as usize;
let context = context.index();
let pos = context * COUNT_SOURCE + source;
let group = pos / U32_BITS;
let index = pos % U32_BITS;
let ptr = self.enables.0[group].get();
unsafe { ptr.write_volatile(ptr.read_volatile() | (1 << index)) }
}
#[inline]
pub fn disable<S, C>(&self, source: S, context: C)
where
S: InterruptSource,
C: HartContext,
{
let source = source.id().get() as usize;
let context = context.index();
let pos = context * COUNT_SOURCE + source;
let group = pos / U32_BITS;
let index = pos % U32_BITS;
let ptr = self.enables.0[group].get();
unsafe { ptr.write_volatile(ptr.read_volatile() & !(1 << index)) }
}
#[inline]
pub fn is_enabled<S, C>(&self, source: S, context: C) -> bool
where
S: InterruptSource,
C: HartContext,
{
let source = source.id().get() as usize;
let context = context.index();
let pos = context * COUNT_SOURCE + source;
let group = pos / U32_BITS;
let index = pos % U32_BITS;
let ptr = self.enables.0[group].get();
(unsafe { ptr.read_volatile() } & (1 << index)) != 0
}
#[inline]
pub fn get_threshold<C>(&self, context: C) -> u32
where
C: HartContext,
{
let ptr = self.context_local[context.index()].priority_threshold.get();
unsafe { ptr.read_volatile() }
}
#[inline]
pub fn set_threshold<C>(&self, context: C, value: u32)
where
C: HartContext,
{
let ptr = self.context_local[context.index()].priority_threshold.get();
unsafe { ptr.write_volatile(value) }
}
#[inline]
pub fn probe_threshold_bits<C>(&self, context: C) -> u32
where
C: HartContext,
{
let ptr = self.context_local[context.index()].priority_threshold.get();
unsafe {
ptr.write_volatile(!0);
ptr.read_volatile()
}
}
#[inline]
pub fn claim<C>(&self, context: C) -> Option<NonZeroU32>
where
C: HartContext,
{
let ptr = self.context_local[context.index()]
.claim_or_completion
.get();
NonZeroU32::new(unsafe { ptr.read_volatile() })
}
#[inline]
pub fn complete<C, S>(&self, context: C, source: S)
where
C: HartContext,
S: InterruptSource,
{
let ptr = self.context_local[context.index()]
.claim_or_completion
.get();
unsafe { ptr.write_volatile(source.id().get()) }
}
}
unsafe impl Sync for Plic {}
#[cfg(test)]
mod tests {
use crate::{ContextLocal, Enables, PendingBits, Plic, Priorities};
use memoffset::offset_of;
#[test]
fn struct_sizes() {
assert_eq!(size_of::<Priorities>(), 0x1000);
assert_eq!(size_of::<PendingBits>(), 0x80);
assert_eq!(size_of::<Enables>(), 0x1f0000);
assert_eq!(size_of::<ContextLocal>(), 0x1000);
assert_eq!(size_of::<Plic>(), 0x4000000);
}
#[test]
fn struct_plic_offsets() {
assert_eq!(offset_of!(Plic, priorities), 0x0);
assert_eq!(offset_of!(Plic, pending_bits), 0x1000);
assert_eq!(offset_of!(Plic, enables), 0x2000);
assert_eq!(offset_of!(Plic, context_local), 0x200000);
}
#[test]
fn struct_context_local_offsets() {
assert_eq!(offset_of!(ContextLocal, priority_threshold), 0x0);
assert_eq!(offset_of!(ContextLocal, claim_or_completion), 0x4);
}
}