use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use crate::common::{HartId, LineAddr};
use crate::sim::components::ComponentId;
use crate::sim::handle::{Handle, HandleCtx};
use crate::sim::packet::{AccessSize, HitLevel, MemOp, MemRespData, MesiState, Packet, WriteData};
use crate::soc::devices::Device;
const PLIC_PRIORITY_BASE: u64 = 0x000000;
const PLIC_PENDING_BASE: u64 = 0x001000;
const PLIC_ENABLE_BASE: u64 = 0x002000;
const PLIC_CONTEXT_BASE: u64 = 0x200000;
const ENABLE_STRIDE: u64 = 0x80;
const CONTEXT_STRIDE: u64 = 0x1000;
const CONTEXTS_PER_HART: usize = 2;
const ENABLE_WORDS_PER_CONTEXT: usize = 32;
const NUM_SOURCES: usize = 1024;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ExternalIrqs {
pub meip: bool,
pub seip: bool,
}
#[derive(Debug)]
pub struct Plic {
base_addr: u64,
priorities: Vec<u32>,
pending: Vec<u32>,
enables: Vec<Vec<u32>>,
thresholds: Vec<u32>,
claims: Vec<u32>,
updates: VecDeque<Vec<u32>>,
}
const UPDATE_DELAY_CYCLES: usize = 3;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlicState {
pub priorities: Vec<u32>,
pub pending: Vec<u32>,
pub enables: Vec<Vec<u32>>,
pub thresholds: Vec<u32>,
pub claims: Vec<u32>,
}
impl Plic {
#[must_use]
pub fn state(&self) -> PlicState {
PlicState {
priorities: self.priorities.clone(),
pending: self.pending.clone(),
enables: self.enables.clone(),
thresholds: self.thresholds.clone(),
claims: self.claims.clone(),
}
}
pub fn set_state(&mut self, state: &PlicState) {
fn copy<T: Copy>(into: &mut [T], from: &[T]) {
for (slot, value) in into.iter_mut().zip(from) {
*slot = *value;
}
}
copy(&mut self.priorities, &state.priorities);
copy(&mut self.pending, &state.pending);
for (into, from) in self.enables.iter_mut().zip(&state.enables) {
copy(into, from);
}
copy(&mut self.thresholds, &state.thresholds);
copy(&mut self.claims, &state.claims);
self.updates.clear();
}
pub fn new(base_addr: u64, hart_count: usize) -> Self {
let contexts = hart_count * CONTEXTS_PER_HART;
Self {
base_addr,
priorities: vec![0; NUM_SOURCES],
pending: vec![0; NUM_SOURCES / 32],
enables: vec![vec![0u32; ENABLE_WORDS_PER_CONTEXT]; contexts],
thresholds: vec![0; contexts],
claims: vec![0; contexts],
updates: VecDeque::new(),
}
}
#[must_use]
pub const fn context_count(&self) -> usize {
self.thresholds.len()
}
pub fn update_irqs(&mut self, mask: u64) {
self.pending[0] = (mask & 0xFFFF_FFFF) as u32;
self.pending[1] = (mask >> 32) as u32;
}
pub fn check_interrupts(&mut self) {
let computed = (0..self.context_count()).map(|ctx| self.calc_max_id(ctx)).collect();
self.updates.push_back(computed);
if self.updates.len() > UPDATE_DELAY_CYCLES
&& let Some(visible) = self.updates.pop_front()
{
self.claims = visible;
}
}
#[must_use]
pub fn hart_lines(&self, hart: HartId) -> ExternalIrqs {
let m_ctx = hart.as_index() * CONTEXTS_PER_HART;
ExternalIrqs {
meip: self.claims.get(m_ctx).is_some_and(|c| *c != 0),
seip: self.claims.get(m_ctx + 1).is_some_and(|c| *c != 0),
}
}
fn calc_max_id(&self, ctx: usize) -> u32 {
let threshold = self.thresholds[ctx];
let num_words = std::cmp::min(self.pending.len(), self.enables[ctx].len());
let mut max_prio = 0;
let mut max_id = 0;
for word in 0..num_words {
let active = self.pending[word] & self.enables[ctx][word];
if active == 0 {
continue;
}
for bit in 0..32 {
let irq_id = word * 32 + bit;
if irq_id == 0 {
continue;
}
if (active & (1 << bit)) != 0 && irq_id < self.priorities.len() {
let prio = self.priorities[irq_id];
if prio > max_prio && prio > threshold {
max_prio = prio;
max_id = irq_id as u32;
}
}
}
}
max_id
}
fn clear_pending(&mut self, irq_id: u32) {
if irq_id > 0 && (irq_id as usize) < NUM_SOURCES {
let idx = irq_id as usize / 32;
let bit = 1u32 << (irq_id % 32);
if idx < self.pending.len() {
self.pending[idx] &= !bit;
}
}
}
}
impl Plic {
fn read_u32_reg(&mut self, offset: u64) -> u32 {
#[allow(clippy::absurd_extreme_comparisons)]
if (PLIC_PRIORITY_BASE..PLIC_PENDING_BASE).contains(&offset) {
let idx = (offset - PLIC_PRIORITY_BASE) as usize / 4;
if idx < self.priorities.len() {
return self.priorities[idx];
}
} else if (PLIC_PENDING_BASE..PLIC_ENABLE_BASE).contains(&offset) {
let idx = (offset - PLIC_PENDING_BASE) as usize / 4;
if idx < self.pending.len() {
return self.pending[idx];
}
} else if (PLIC_ENABLE_BASE..PLIC_CONTEXT_BASE).contains(&offset) {
let rel = offset - PLIC_ENABLE_BASE;
let ctx = (rel / ENABLE_STRIDE) as usize;
let word_idx = ((rel % ENABLE_STRIDE) / 4) as usize;
if ctx < self.context_count() && word_idx < ENABLE_WORDS_PER_CONTEXT {
return self.enables[ctx][word_idx];
}
} else if offset >= PLIC_CONTEXT_BASE {
let ctx = ((offset - PLIC_CONTEXT_BASE) / CONTEXT_STRIDE) as usize;
let reg = offset & 0xFFF;
if ctx < self.context_count() {
if reg == 0 {
return self.thresholds[ctx];
}
if reg == 4 {
let irq_id = self.claims[ctx];
self.clear_pending(irq_id);
return irq_id;
}
}
}
0
}
fn write_u32_reg(&mut self, offset: u64, val: u32) {
#[allow(clippy::absurd_extreme_comparisons)]
if (PLIC_PRIORITY_BASE..PLIC_PENDING_BASE).contains(&offset) {
let idx = (offset - PLIC_PRIORITY_BASE) as usize / 4;
if idx < self.priorities.len() {
self.priorities[idx] = val;
}
} else if (PLIC_ENABLE_BASE..PLIC_CONTEXT_BASE).contains(&offset) {
let rel = offset - PLIC_ENABLE_BASE;
let ctx = (rel / ENABLE_STRIDE) as usize;
let word_idx = ((rel % ENABLE_STRIDE) / 4) as usize;
if ctx < self.context_count() && word_idx < ENABLE_WORDS_PER_CONTEXT {
self.enables[ctx][word_idx] = val;
}
} else if offset >= PLIC_CONTEXT_BASE {
let ctx = ((offset - PLIC_CONTEXT_BASE) / CONTEXT_STRIDE) as usize;
let reg = offset & 0xFFF;
if ctx < self.context_count() {
if reg == 0 {
self.thresholds[ctx] = val;
}
if reg == 4 {
self.clear_pending(val);
self.claims[ctx] = 0;
}
}
}
}
}
impl Handle for Plic {
fn handle(&mut self, packet: Packet, source: ComponentId, ctx: &mut HandleCtx<'_>) {
if let Packet::MemReq { req_id, paddr, size, op, .. } = packet {
let offset = paddr.val().saturating_sub(self.base_addr);
let value: u64 = match (size, op) {
(
AccessSize::B4 | AccessSize::B8,
MemOp::Read | MemOp::ReadOwn | MemOp::Fetch | MemOp::Atomic { .. },
) => u64::from(self.read_u32_reg(offset)),
(
AccessSize::B1,
MemOp::Read | MemOp::ReadOwn | MemOp::Fetch | MemOp::Atomic { .. },
) => {
let aligned = offset & !3;
let shift = (offset & 3) * 8;
u64::from((self.read_u32_reg(aligned) >> shift) as u8)
}
(
AccessSize::B2,
MemOp::Read | MemOp::ReadOwn | MemOp::Fetch | MemOp::Atomic { .. },
) => {
let aligned = offset & !3;
let shift = (offset & 3) * 8;
u64::from((self.read_u32_reg(aligned) >> shift) as u16)
}
(
AccessSize::B4 | AccessSize::B8,
MemOp::Write { data: WriteData::Small(val), .. },
) => {
self.write_u32_reg(offset, val as u32);
0
}
_ => 0,
};
ctx.scheduler.schedule(
ctx.cycle + ctx.config.system.device_access_cycles(self.name()),
source,
ctx.self_id,
Packet::MemResp {
req_id,
line_addr: LineAddr::from_phys(paddr, 64),
data: MemRespData::Small(value),
hit_level: HitLevel::Mmio,
state: MesiState::Exclusive,
},
);
}
}
}
impl Device for Plic {
fn name(&self) -> &'static str {
"PLIC"
}
fn address_range(&self) -> (u64, u64) {
(self.base_addr, 0x4000000)
}
fn quiet_ticks(&self) -> Option<u64> {
let settled = self.updates.len() == UPDATE_DELAY_CYCLES
&& (0..self.context_count()).all(|ctx| {
let claim = self.calc_max_id(ctx);
self.claims[ctx] == claim && self.updates.iter().all(|update| update[ctx] == claim)
});
if settled { None } else { Some(0) }
}
fn checkpoint(&self) -> Option<serde_json::Value> {
serde_json::to_value(self.state()).ok()
}
fn restore(&mut self, state: &serde_json::Value) -> Result<(), String> {
let state = serde_json::from_value::<PlicState>(state.clone())
.map_err(|error| format!("PLIC state: {error}"))?;
self.set_state(&state);
Ok(())
}
fn as_plic_mut(&mut self) -> Option<&mut Plic> {
Some(self)
}
}