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 MSIP_BASE: u64 = 0x0000;
const MTIMECMP_BASE: u64 = 0x4000;
const MTIME_OFFSET: u64 = 0xBFF8;
const CLINT_SIZE: u64 = 0x10000;
const MSIP_STRIDE: u64 = 4;
const MTIMECMP_STRIDE: u64 = 8;
#[derive(Debug)]
pub struct Clint {
base_addr: u64,
mtime: u64,
mtimecmp: Vec<u64>,
msip: Vec<u32>,
divider: u64,
counter: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClintState {
pub mtime: u64,
pub mtimecmp: Vec<u64>,
pub msip: Vec<u32>,
pub counter: u64,
}
impl Clint {
#[must_use]
pub fn state(&self) -> ClintState {
ClintState {
mtime: self.mtime,
mtimecmp: self.mtimecmp.clone(),
msip: self.msip.clone(),
counter: self.counter,
}
}
pub fn set_state(&mut self, state: &ClintState) {
self.mtime = state.mtime;
self.counter = state.counter;
for (slot, value) in self.mtimecmp.iter_mut().zip(&state.mtimecmp) {
*slot = *value;
}
for (slot, value) in self.msip.iter_mut().zip(&state.msip) {
*slot = *value;
}
}
pub fn new(base_addr: u64, divider: u64, hart_count: usize) -> Self {
Self {
base_addr,
mtime: 0,
mtimecmp: vec![u64::MAX; hart_count],
msip: vec![0; hart_count],
divider: if divider == 0 { 1 } else { divider },
counter: 0,
}
}
#[must_use]
pub const fn mtime(&self) -> u64 {
self.mtime
}
#[must_use]
pub fn msip_pending(&self, hart: HartId) -> bool {
self.msip.get(hart.as_index()).is_some_and(|msip| (msip & 1) != 0)
}
#[must_use]
pub fn timer_pending(&self, hart: HartId) -> bool {
self.mtimecmp.get(hart.as_index()).is_some_and(|cmp| self.mtime >= *cmp)
}
#[must_use]
pub const fn ticks_until_mtime(&self, value: u64) -> Option<u64> {
if value <= self.mtime {
return None;
}
let first = self.divider - self.counter;
Some(first.saturating_add((value - self.mtime - 1).saturating_mul(self.divider)))
}
fn read_window(&self, offset: u64) -> u64 {
let aligned = offset & !7;
if aligned < MTIMECMP_BASE {
let first = (aligned - MSIP_BASE) / MSIP_STRIDE;
let lo = self.msip.get(first as usize).copied().unwrap_or(0);
let hi = self.msip.get(first as usize + 1).copied().unwrap_or(0);
return u64::from(lo) | (u64::from(hi) << 32);
}
if aligned == MTIME_OFFSET {
return self.mtime;
}
if aligned >= MTIMECMP_BASE {
let hart = (aligned - MTIMECMP_BASE) / MTIMECMP_STRIDE;
return self.mtimecmp.get(hart as usize).copied().unwrap_or(0);
}
0
}
fn read_register(&self, offset: u64, size: AccessSize) -> u64 {
let window = self.read_window(offset);
let shift = (offset & 7) * 8;
match size {
AccessSize::B8 | AccessSize::Line => window,
AccessSize::B4 => (window >> shift) & 0xFFFF_FFFF,
AccessSize::B2 => (window >> shift) & 0xFFFF,
AccessSize::B1 => (window >> shift) & 0xFF,
AccessSize::Part(_) | AccessSize::Span(_) => 0,
}
}
fn write_word(&mut self, offset: u64, val: u32) {
let aligned = offset & !3;
if aligned < MTIMECMP_BASE {
let hart = ((aligned - MSIP_BASE) / MSIP_STRIDE) as usize;
if let Some(msip) = self.msip.get_mut(hart) {
*msip = val & 1;
}
return;
}
if aligned == MTIME_OFFSET {
self.mtime = (self.mtime & 0xFFFF_FFFF_0000_0000) | u64::from(val);
return;
}
if aligned == MTIME_OFFSET + 4 {
self.mtime = (self.mtime & 0x0000_0000_FFFF_FFFF) | (u64::from(val) << 32);
return;
}
if aligned >= MTIMECMP_BASE {
let hart = ((aligned - MTIMECMP_BASE) / MTIMECMP_STRIDE) as usize;
let upper_half = (aligned & 4) != 0;
if let Some(cmp) = self.mtimecmp.get_mut(hart) {
*cmp = if upper_half {
(*cmp & 0x0000_0000_FFFF_FFFF) | (u64::from(val) << 32)
} else {
(*cmp & 0xFFFF_FFFF_0000_0000) | u64::from(val)
};
}
}
}
fn write_register(&mut self, offset: u64, size: AccessSize, val: u64) {
match size {
AccessSize::B8 => {
let aligned = offset & !7;
self.write_word(aligned, val as u32);
self.write_word(aligned + 4, (val >> 32) as u32);
}
AccessSize::B4 => self.write_word(offset, val as u32),
AccessSize::B1
| AccessSize::B2
| AccessSize::Part(_)
| AccessSize::Span(_)
| AccessSize::Line => {}
}
}
}
impl Handle for Clint {
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 = match op {
MemOp::Read | MemOp::ReadOwn | MemOp::Fetch | MemOp::Atomic { .. } => {
self.read_register(offset, size)
}
MemOp::Write { data: WriteData::Small(val), .. } => {
self.write_register(offset, size, val);
0
}
MemOp::Write { .. }
| MemOp::Writeback { .. }
| MemOp::Maintain { .. }
| MemOp::Prefetch { .. } => 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 Clint {
fn name(&self) -> &'static str {
"CLINT"
}
fn address_range(&self) -> (u64, u64) {
(self.base_addr, CLINT_SIZE)
}
fn tick(&mut self) -> bool {
self.counter += 1;
if self.counter >= self.divider {
self.mtime = self.mtime.wrapping_add(1);
self.counter = 0;
}
false
}
fn quiet_ticks(&self) -> Option<u64> {
self.mtimecmp
.iter()
.filter_map(|&compare| self.ticks_until_mtime(compare))
.min()
.map(|ticks| ticks - 1)
}
fn skip_ticks(&mut self, ticks: u64) {
let total = self.counter + ticks;
self.mtime = self.mtime.wrapping_add(total / self.divider);
self.counter = total % self.divider;
}
fn as_clint(&self) -> Option<&Clint> {
Some(self)
}
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::<ClintState>(state.clone())
.map_err(|error| format!("CLINT state: {error}"))?;
self.set_state(&state);
Ok(())
}
fn as_clint_mut(&mut self) -> Option<&mut Clint> {
Some(self)
}
}