Skip to content

Commit

Permalink
Initial commit, add basic structures and APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
hky1999 committed Dec 18, 2024
0 parents commit 5861a3b
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 0 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on: [push, pull_request]

jobs:
ci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [riscv64gc-unknown-none-elf]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rust-src, clippy, rustfmt
targets: ${{ matrix.targets }}
- name: Check rust version
run: rustc --version --verbose
- name: Check code format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- name: Build
run: cargo build --target ${{ matrix.targets }} --all-features
- name: Unit test
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
run: cargo test --target ${{ matrix.targets }} -- --nocapture

doc:
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
contents: write
env:
default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Build docs
continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
run: |
cargo doc --no-deps --all-features
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
- name: Deploy to Github Pages
if: ${{ github.ref == env.default-branch }}
uses: JamesIves/github-pages-deploy-action@v4
with:
single-commit: true
branch: gh-pages
folder: target/doc
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
/.vscode
.DS_Store
Cargo.lock
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "riscv_plic"
version = "0.1.0"
edition = "2021"
authors = ["Keyang Hu <[email protected]>"]
description = "RISC-V platform-level interrupt controller (PLIC) register definitions and basic operations"
license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
homepage = "https://github.com/arceos-org/arceos"
repository = "https://github.com/arceos-org/riscv_plic"
documentation = "https://docs.rs/riscv_plic"
keywords = ["arceos", "riscv", "riscv64", "plic", "interrupt-controller"]
categories = ["embedded", "no-std", "hardware-support", "os"]

[dependencies]
tock-registers = "0.8"
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# riscv_plic

[![Crates.io](https://img.shields.io/crates/v/riscv_plic)](https://crates.io/crates/riscv_plic)
[![Docs.rs](https://docs.rs/riscv_plic/badge.svg)](https://docs.rs/riscv_plic)
[![CI](https://github.com/arceos-org/riscv_plic/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/riscv_plic/actions/workflows/ci.yml)

RISC-V platform-level interrupt controller (PLIC) register definitions and basic operations.

The official documentation: <https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc/>
299 changes: 299 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
//! RISC-V Platform-Level Interrupt Controller
//! https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc
#![no_std]
#![feature(const_option)]
#![feature(const_nonnull_new)]

use core::num::NonZeroU32;
use core::ptr::NonNull;

use tock_registers::{
interfaces::{Readable, Writeable},
register_structs,
registers::{ReadOnly, ReadWrite},
};

/// See §1.
const SOURCE_NUM: usize = 1024;
/// See §1.
const CONTEXT_NUM: usize = 15872;

const U32_BITS: usize = u32::BITS as usize;

register_structs! {
#[allow(non_snake_case)]
ContextLocal {
/// Priority Threshold
/// - The base address of Priority Thresholds register block is located at 4K alignment starts from offset 0x200000.
(0x0000 => PriorityThreshold: ReadWrite<u32>),
/// Interrupt Claim/complete Process
/// - The Interrupt Claim Process register is context based and is located at (4K alignment + 4) starts from offset 0x200000.
(0x0004 => InterruptClaimComplete: ReadWrite<u32>),
(0x0008 => _reserved_0),
(0x1000 => @END),
}
}

register_structs! {
#[allow(non_snake_case)]
InterruptEnableCtxX {
/// Priority Threshold
/// - The base address of Priority Thresholds register block is located at 4K alignment starts from offset 0x200000.
(0x00 => InterruptSources: [ReadWrite<u32>; SOURCE_NUM / U32_BITS]),
(0x80 => @END),
}
}

register_structs! {
#[allow(non_snake_case)]
PLICRegs {
/// Interrupt Source Priority #0 to #1023
(0x000000 => InterruptPriority: [ReadWrite<u32>; SOURCE_NUM]),
/// Interrupt Pending Bit of Interrupt Source #0 to #N
/// 0x001000: Interrupt Source #0 to #31 Pending Bits
/// ...
/// 0x00107C: Interrupt Source #992 to #1023 Pending Bits
(0x001000 => InterruptPending: [ReadOnly<u32>; 0x20]),
(0x001080 => _reserved_0),
/// Interrupt Enable Bit of Interrupt Source #0 to #1023 for 15872 contexts
(0x002000 => InterruptEnableCtxX: [InterruptEnableCtxX; CONTEXT_NUM]),
(0x1F2000 => _reserved_1),
/// 4096 * 15872 = 65011712(0x3e000 00) bytes
/// Priority Threshold for 15872 contexts
/// - The base address of Priority Thresholds register block is located at 4K alignment starts from offset 0x200000.
/// Interrupt Claim Process for 15872 contexts
/// - The Interrupt Claim Process register is context based and is located at (4K alignment + 4) starts from offset 0x200000.
/// - The Interrupt Completion registers are context based and located at the same address with Interrupt Claim Process register, which is at (4K alignment + 4) starts from offset 0x200000.
(0x200000 => Contexts: [ContextLocal; CONTEXT_NUM]),
(0x4000000 => @END),
}
}

/// Trait for enums of external interrupt source.
///
/// See §1.4.
pub trait InterruptSource {
/// The identifier number of the interrupt source.
fn id(self) -> NonZeroU32;
}

/// A hart context is a given privilege mode on a given hart.
///
/// See §1.1.
pub trait HartContext {
/// See §6.
///
/// > How PLIC organizes interrupts for the contexts (Hart and privilege mode)
/// > is out of RISC-V PLIC specification scope, however it must be spec-out
/// > in vendor’s PLIC specification.
fn index(self) -> usize;
}

pub struct Plic {
base: NonNull<PLICRegs>,
}

unsafe impl Send for Plic {}
unsafe impl Sync for Plic {}

impl Plic {
/// Create a new instance of the PLIC from the base address.
pub const fn new(base: *mut u8) -> Self {
Self {
base: NonNull::new(base).unwrap().cast(),
}
}

/// Initialize the PLIC by context, setting the priority threshold to 0.
pub fn init_by_context<C>(&mut self, context: C)
where
C: HartContext,
{
self.regs().Contexts[context.index()]
.PriorityThreshold
.set(0);
}

const fn regs(&self) -> &PLICRegs {
unsafe { self.base.as_ref() }
}

/// Sets priority for interrupt `source` to `value`.
///
/// Write `0` to priority `value` effectively disables this interrupt `source`, for the priority
/// value 0 is reserved for "never interrupt" by the PLIC specification.
///
/// The lowest active priority is priority `1`. The maximum priority depends on PLIC implementation
/// and can be detected with [`Plic::probe_priority_bits`].
///
/// See §4.
#[inline]
pub fn set_priority<S>(&self, source: S, value: u32)
where
S: InterruptSource,
{
self.regs().InterruptPriority[source.id().get() as usize].set(value);
}

/// Gets priority for interrupt `source`.
///
/// See §4.
#[inline]
pub fn get_priority<S>(&self, source: S) -> u32
where
S: InterruptSource,
{
self.regs().InterruptPriority[source.id().get() as usize].get()
}

/// Probe maximum level of priority for interrupt `source`.
///
/// See §4.
#[inline]
pub fn probe_priority_bits<S>(&self, source: S) -> u32
where
S: InterruptSource,
{
let source = source.id().get() as usize;
self.regs().InterruptPriority[source].set(!0);
self.regs().InterruptPriority[source].get()
}

/// Check if interrupt `source` is pending.
///
/// See §5.
#[inline]
pub fn is_pending<S>(&self, source: S) -> bool
where
S: InterruptSource,
{
let (group, index) = parse_group_and_index(source.id().get() as usize);
self.regs().InterruptPending[group].get() & (1 << index) != 0
}

/// Enable interrupt `source` in `context`.
///
/// See §6.
#[inline]
pub fn enable<S, C>(&self, source: S, context: C)
where
S: InterruptSource,
C: HartContext,
{
let context = context.index();
let (group, index) = parse_group_and_index(source.id().get() as usize);

let value = self.regs().InterruptEnableCtxX[context].InterruptSources[group].get();
self.regs().InterruptEnableCtxX[context].InterruptSources[group].set(value | 1 << index);
}

/// Disable interrupt `source` in `context`.
///
/// See §6.
#[inline]
pub fn disable<S, C>(&self, source: S, context: C)
where
S: InterruptSource,
C: HartContext,
{
let context = context.index();
let (group, index) = parse_group_and_index(source.id().get() as usize);

let value = self.regs().InterruptEnableCtxX[context].InterruptSources[group].get();
self.regs().InterruptEnableCtxX[context].InterruptSources[group].set(value & !(1 << index));
}

/// Check if interrupt `source` is enabled in `context`.
///
/// See §6.
#[inline]
pub fn is_enabled<S, C>(&self, source: S, context: C) -> bool
where
S: InterruptSource,
C: HartContext,
{
let context = context.index();
let (group, index) = parse_group_and_index(source.id().get() as usize);

self.regs().InterruptEnableCtxX[context].InterruptSources[group].get() & (1 << index) != 0
}

/// Get interrupt threshold in `context`.
///
/// See §7.
#[inline]
pub fn get_threshold<C>(&self, context: C) -> u32
where
C: HartContext,
{
self.regs().Contexts[context.index()]
.PriorityThreshold
.get()
}

/// Set interrupt threshold for `context` to `value`.
///
/// See §7.
#[inline]
pub fn set_threshold<C>(&self, context: C, value: u32)
where
C: HartContext,
{
self.regs().Contexts[context.index()]
.PriorityThreshold
.set(value);
}

/// Probe maximum supported threshold value the `context` supports.
///
/// See §7.
#[inline]
pub fn probe_threshold_bits<C>(&self, context: C) -> u32
where
C: HartContext,
{
let context = context.index();
self.regs().Contexts[context].PriorityThreshold.set(!0);
self.regs().Contexts[context].PriorityThreshold.get()
}

/// Claim an interrupt in `context`, returning its source.
///
/// It is always legal for a hart to perform a claim even if `EIP` is not set.
/// A hart could set threshold to maximum to disable interrupt notification, but it does not mean
/// interrupt source has stopped to send interrupt signals. In this case, hart would instead
/// poll for active interrupt by periodically calling the `claim` function.
///
/// See §8.
#[inline]
pub fn claim<C>(&self, context: C) -> Option<NonZeroU32>
where
C: HartContext,
{
NonZeroU32::new(
self.regs().Contexts[context.index()]
.InterruptClaimComplete
.get(),
)
}

/// Mark that interrupt identified by `source` is completed in `context`.
///
/// See §9.
#[inline]
pub fn complete<C, S>(&self, context: C, source: S)
where
C: HartContext,
S: InterruptSource,
{
self.regs().Contexts[context.index()]
.InterruptClaimComplete
.set(source.id().get());
}
}

fn parse_group_and_index(source: usize) -> (usize, usize) {
let group = source / U32_BITS;
let index = source % U32_BITS;
(group, index)
}

0 comments on commit 5861a3b

Please sign in to comment.