Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

riscv-peripheral: Support Core-Local Interrupt Controller (CLIC) RISC-V Privileged Architecture Extensions #195

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions riscv-peripheral/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

- Add support for CLIC <https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc>

## [v0.1.0] - 2024-02-15

### Added
Expand Down
5 changes: 4 additions & 1 deletion riscv-peripheral/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors = ["The RISC-V Team <[email protected]>"]
categories = ["embedded", "hardware-support", "no-std"]
description = "Interfaces for standard RISC-V peripherals"
documentation = "https://docs.rs/riscv-peripheral"
keywords = ["riscv", "peripheral", "clint", "plic"]
keywords = ["riscv", "peripheral", "clint", "plic", "clic"]
license = "ISC"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -24,6 +24,9 @@ heapless = "0.8.0"

[features]
aclint-hal-async = ["embedded-hal-async"]
clic-smclic = []
# CLIC selective hardware vectoring extension
clic-smclicshv = []

[package.metadata.docs.rs]
all-features = true
Expand Down
95 changes: 95 additions & 0 deletions riscv-peripheral/src/clic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! Core-Local Interrupt Controller (CLIC) peripheral.
//!
//! Specification: <https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc>

pub mod intattr;
pub mod intctl;
pub mod intie;
pub mod intip;
pub mod inttrig;
#[cfg(feature = "clic-smclic")]
pub mod smclicconfig;

pub use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; // re-export useful riscv-pac traits

/// Trait for a CLIC peripheral.
///
/// # Safety
///
/// * This trait must only be implemented on a PAC of a target with a CLIC peripheral.
/// * The CLIC peripheral base address `BASE` must be valid for the target device.
pub unsafe trait Clic: Copy {
/// Base address of the CLIC peripheral.
const BASE: usize;
}

/// Core-Local Interrupt Controller (CLIC) peripheral.
///
/// The RISC-V standard does not specify a fixed location for the CLIC.
/// Thus, each platform must specify the base address of the CLIC on the platform.
/// The base address, as well as all the associated types, are defined in the [`Clic`] trait.
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CLIC<P: Clic> {
_marker: core::marker::PhantomData<P>,
}

impl<P: Clic> CLIC<P> {
#[cfg(feature = "clic-smclic")]
const SMCLICCONFIG_OFFSET: usize = 0x0;

const INTTRIG_OFFSET: usize = 0x40;
const INTTRIG_SEPARATION: usize = 0x4;

const INT_OFFSET: usize = 0x1000;
const INT_SEPARATION: usize = 0x4;

/// Returns the smclicconfig register of the CLIC.
#[inline]
#[cfg(feature = "clic-smclic")]
pub fn smclicconfig() -> smclicconfig::SMCLICCONFIG {
// SAFETY: valid address
unsafe { smclicconfig::SMCLICCONFIG::new(P::BASE + Self::SMCLICCONFIG_OFFSET) }
}

/// Returns the clicinttrig register for a given interrupt number.
#[inline]
pub fn inttrig<I: InterruptNumber>(int_nr: I) -> inttrig::INTTRIG {
let addr =
P::BASE + Self::INTTRIG_OFFSET + int_nr.number() as usize * Self::INTTRIG_SEPARATION;
// SAFETY: valid address
unsafe { inttrig::INTTRIG::new(addr) }
}

/// Returns the interrupts pending register of a given interrupt number.
#[inline]
pub fn ip<I: InterruptNumber>(int_nr: I) -> intip::INTIP {
let addr = P::BASE + Self::INT_OFFSET + int_nr.number() as usize * Self::INT_SEPARATION;
// SAFETY: valid address
unsafe { intip::INTIP::new(addr) }
}

/// Returns the interrupts enable register of a given interrupt number.
#[inline]
pub fn ie<I: InterruptNumber>(int_nr: I) -> intie::INTIE {
let addr = P::BASE + Self::INT_OFFSET + int_nr.number() as usize * Self::INT_SEPARATION;
// SAFETY: valid interrupt_number
unsafe { intie::INTIE::new(addr) }
}

/// Returns the attribute register of a given interrupt number.
#[inline]
pub fn attr<I: InterruptNumber>(int_nr: I) -> intattr::INTATTR {
let addr = P::BASE + Self::INT_OFFSET + int_nr.number() as usize * Self::INT_SEPARATION;
// SAFETY: valid address
unsafe { intattr::INTATTR::new(addr) }
}

/// Returns the control register of this interrupt.
#[inline]
pub fn ctl<I: InterruptNumber>(int_nr: I) -> intctl::INTCTL {
let addr = P::BASE + Self::INT_OFFSET + int_nr.number() as usize * Self::INT_SEPARATION;
// SAFETY: valid address
unsafe { intctl::INTCTL::new(addr) }
}
}
151 changes: 151 additions & 0 deletions riscv-peripheral/src/clic/intattr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//! CLIC interrupt attribute register.

use crate::common::{Reg, RW};

/// Privilege Mode
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Mode {
Machine = 0b11,
Supervisor = 0b01,
User = 0b00,
}

/// Trigger type
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Trig {
Level = 0b0,
Edge = 0b1,
}

/// Polarity
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Polarity {
Pos = 0b0,
Neg = 0b1,
}

/// CLIC interrupt attribute register.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct INTATTR {
ptr: *mut u32,
}

impl INTATTR {
const INTATTR_OFFSET: usize = 0x2;

/// Creates a new interrupt attribute register from a base address.
///
/// # Safety
///
/// The base address must point to a valid interrupt attribute register.
#[inline]
pub(crate) const unsafe fn new(address: usize) -> Self {
Self { ptr: address as _ }
}

/// Check which privilege mode this interrupt operates in.
#[inline]
pub fn mode(self) -> Mode {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

match reg.read_bits(6 + 8 * Self::INTATTR_OFFSET, 7 + 8 * Self::INTATTR_OFFSET) {
0b00 => Mode::User,
0b01 => Mode::Supervisor,
0b11 => Mode::Machine,
_ => unreachable!(),
}
}

/// Set the privilege mode this interrupt operates in.
#[inline]
pub fn set_mode(self, mode: Mode) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

reg.write_bits(
6 + 8 * Self::INTATTR_OFFSET,
7 + 8 * Self::INTATTR_OFFSET,
mode as _,
)
}

/// Check the trigger type for this interrupt.
#[inline]
pub fn trig(self) -> Trig {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

match reg.read_bit(1 + 8 * Self::INTATTR_OFFSET) {
false => Trig::Level,
true => Trig::Edge,
}
}

/// Set the trigger type for this interrupt.
#[inline]
pub fn set_trig(self, trig: Trig) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

match trig {
Trig::Level => reg.clear_bit(1 + 8 * Self::INTATTR_OFFSET),
Trig::Edge => reg.set_bit(1 + 8 * Self::INTATTR_OFFSET),
}
}

/// Check the polarity for this interrupt.
#[inline]
pub fn polarity(self) -> Polarity {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

match reg.read_bit(2 + 8 * Self::INTATTR_OFFSET) {
false => Polarity::Pos,
true => Polarity::Neg,
}
}

/// Set the polarity for this interrupt.
#[inline]
pub fn set_polarity(self, polarity: Polarity) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

match polarity {
Polarity::Pos => reg.clear_bit(2 + 8 * Self::INTATTR_OFFSET),
Polarity::Neg => reg.set_bit(2 + 8 * Self::INTATTR_OFFSET),
}
}

/// Check the selective hardware vectoring mode for this interrupt.
#[inline]
#[cfg(feature = "clic-smclicshv")]
pub fn shv(self) -> bool {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

reg.read_bit(0 + 8 * Self::INTATTR_OFFSET)
}

/// Set selective hardware vectoring mode for this interrupt.
#[inline]
#[cfg(feature = "clic-smclicshv")]
pub fn set_shv(self, shv: bool) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

if shv {
reg.set_bit(8 * Self::INTATTR_OFFSET)
} else {
reg.clear_bit(8 * Self::INTATTR_OFFSET)
}
}
}
90 changes: 90 additions & 0 deletions riscv-peripheral/src/clic/intctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! CLIC interrupt control register.

use crate::common::{Reg, RW};

/// CLIC interrupt control register.
///
/// A configurable number of upper bits in clicintctl[i] are assigned to encode the interrupt
/// level.
///
/// The least-significant bits in clicintctl[i] that are not configured to be part of the
/// interrupt level are interrupt priority, which are used to prioritize among interrupts
/// pending-and-enabled at the same privilege mode and interrupt level. The highest-priority
/// interrupt at a given privilege mode and interrupt level is taken first. In case there are
/// multiple pending-and-enabled interrupts at the same highest priority, the highest-numbered
/// interrupt is taken first.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct INTCTL {
ptr: *mut u32,
}

impl INTCTL {
const INTCTL_OFFSET: usize = 0x3;

/// Creates a new interrupt control register from a base address.
///
/// # Safety
///
/// The base address must point to a valid interrupt control register.
#[inline]
pub(crate) const unsafe fn new(address: usize) -> Self {
Self { ptr: address as _ }
}

/// Check interrupt level for this interrupt.
#[inline]
pub fn level(self) -> u8 {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

// TODO: need to figure out how many are actually priority bits and how many are level bits
// and mask accordingly
reg.read_bits(8 * Self::INTCTL_OFFSET, 7 + 8 * Self::INTCTL_OFFSET) as u8
}

/// Set interrupt level for this interrupt.
#[inline]
pub fn set_level(self, level: u8) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

// TODO: need to figure out how many are actually priority bits and how many are level bits
// and mask accordingly
reg.write_bits(
8 * Self::INTCTL_OFFSET,
7 + 8 * Self::INTCTL_OFFSET,
level as _,
)
}

/// Check interrupt priority for this interrupt.
///
/// N.b., 2024-03-11 rt-ss/CLIC does not implement priority bits at all at this time
#[inline]
pub fn priority(self) -> u8 {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

// TODO: need to figure out how many are actually priority bits and how many are level bits
// and mask accordingly
reg.read_bits(8 * Self::INTCTL_OFFSET, 7 + 8 * Self::INTCTL_OFFSET) as u8
}

/// Set interrupt priority for this interrupt.
///
/// N.b., 2024-03-11 rt-ss/CLIC does not implement priority bits at all at this time
#[inline]
pub fn set_priority(self, priority: u8) {
// SAFETY: valid interrupt number
let reg: Reg<u32, RW> = unsafe { Reg::new(self.ptr) };

// TODO: need to figure out how many are actually priority bits and how many are level bits
// and mask accordingly
reg.write_bits(
8 * Self::INTCTL_OFFSET,
7 + 8 * Self::INTCTL_OFFSET,
priority as _,
)
}
}
Loading