use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::any::Any;
use core::fmt;
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{Budget, Consumed, LazyHandle, TickCursor};
use crate::core::space::{RegionRef, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter};
use crate::core::sync::AtomicU64;
use crate::core::wire::{DmaPeripheral, IntAck, WireId, WireSink, WireSource};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResetKind {
Cold,
Warm,
Bus,
}
#[derive(Debug, Clone, Copy)]
pub struct PropertySpec {
pub name: &'static str,
pub kind: ValueKind,
pub required: bool,
pub summary: &'static str,
}
pub struct DeviceClass {
pub name: &'static str,
pub version: u32,
pub summary: &'static str,
pub properties: &'static [PropertySpec],
pub construct: fn(&Props) -> Result<Box<dyn Device>>,
}
impl fmt::Debug for DeviceClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceClass")
.field("name", &self.name)
.field("version", &self.version)
.field("properties", &self.properties.len())
.finish()
}
}
pub struct SinkPin {
pub sink: Arc<dyn WireSink>,
pub line: u32,
}
impl fmt::Debug for SinkPin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SinkPin").field("line", &self.line).finish()
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExportId(pub u16);
impl ExportId {
pub const TIMEBASE: ExportId = ExportId(1);
pub const CYCLE_GATE: ExportId = ExportId(2);
pub const DMC_FETCH: ExportId = ExportId(3);
#[must_use]
pub fn name(self) -> Option<&'static str> {
match self {
ExportId::TIMEBASE => Some("timebase"),
ExportId::CYCLE_GATE => Some("cycle gate"),
ExportId::DMC_FETCH => Some("DMC sample fetch"),
_ => None,
}
}
}
impl fmt::Display for ExportId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "export #{}", self.0),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Export {
Cell(Arc<AtomicU64>),
Gate(Arc<dyn CycleGate>),
Opaque(Arc<dyn Any + Send + Sync>),
}
impl Export {
#[must_use]
pub fn gate(&self) -> Option<&Arc<dyn CycleGate>> {
match self {
Export::Gate(g) => Some(g),
_ => None,
}
}
#[must_use]
pub fn opaque(&self) -> Option<&Arc<dyn Any + Send + Sync>> {
match self {
Export::Opaque(h) => Some(h),
_ => None,
}
}
#[must_use]
pub fn cell(&self) -> Option<&Arc<AtomicU64>> {
match self {
Export::Cell(cell) => Some(cell),
_ => None,
}
}
#[must_use]
pub fn shape(&self) -> &'static str {
match self {
Export::Cell(_) => "a 64-bit cell",
Export::Gate(_) => "a cycle gate",
Export::Opaque(_) => "an opaque handle",
}
}
}
pub trait Device: Send + Sync + fmt::Debug {
fn class(&self) -> &'static DeviceClass;
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()>;
fn unrealize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, kind: ResetKind);
fn save(&self, _w: &mut ChunkWriter<'_>) -> Result<()> {
Ok(())
}
fn load(&self, _r: &mut ChunkReader<'_>) -> Result<()> {
Ok(())
}
fn region(&self, _name: &str) -> Option<RegionRef> {
None
}
fn sink(&self, _port: &str, _sources: &[WireId]) -> Option<SinkPin> {
None
}
fn export(&self, _which: ExportId) -> Option<Export> {
None
}
fn connect(&self, port: &str, _source: WireSource) -> Result<()> {
Err(Error::Config {
at: port.to_string(),
message: "this device drives no such pin".to_string(),
})
}
fn int_ack(&self, _port: &str) -> Option<Arc<dyn IntAck>> {
None
}
fn attach_int_ack(&self, _port: &str, _ack: Weak<dyn IntAck>) {}
fn dma_peripheral(&self, _port: &str) -> Option<Arc<dyn DmaPeripheral>> {
None
}
fn attach_dma_peripheral(&self, _port: &str, _peer: Weak<dyn DmaPeripheral>) {}
fn announce(&self, _port: &str) {}
fn combinational(&self) -> bool {
false
}
fn is_runnable(&self) -> bool {
false
}
fn run(&self, _budget: Budget) -> Consumed {
Consumed::default()
}
fn attach_cursor(&self, cursor: TickCursor) {
let _ = cursor;
}
fn event(&self, _token: u64, _deferred: &mut Deferred) {}
fn is_lazy(&self) -> bool {
false
}
fn current_tick(&self) -> u64 {
0
}
fn advance_to(&self, tick: u64) {
let _ = tick;
}
fn next_event_tick(&self) -> Option<u64> {
None
}
fn sampled_every_cycle(&self) -> bool {
false
}
fn attach_lazy(&self, handle: LazyHandle) {
let _ = handle;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arbitration {
Release,
Hold,
Halted,
Steal(u8),
}
pub trait CycleGate: Send + Sync + fmt::Debug {
fn arbitrate(&self, cycle: u64, held: u64, bus: u8, write: bool) -> Arbitration;
}
pub trait Initiator {
fn requester(&self) -> RequesterId;
}
type Action = Box<dyn FnOnce() + Send>;
#[derive(Default)]
pub struct Deferred {
actions: Vec<Action>,
draining: bool,
}
impl fmt::Debug for Deferred {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Deferred")
.field("pending", &self.actions.len())
.field("draining", &self.draining)
.finish()
}
}
impl Deferred {
pub fn new() -> Deferred {
Deferred {
actions: Vec::new(),
draining: false,
}
}
pub fn push(&mut self, action: impl FnOnce() + Send + 'static) {
self.actions.push(Box::new(action));
}
pub fn is_empty(&self) -> bool {
self.actions.is_empty()
}
pub fn len(&self) -> usize {
self.actions.len()
}
pub fn drain(&mut self) -> usize {
if self.draining {
return 0;
}
self.draining = true;
let mut ran = 0;
while !self.actions.is_empty() {
let batch = core::mem::take(&mut self.actions);
for action in batch {
action();
ran += 1;
}
}
self.draining = false;
ran
}
}
#[derive(Debug)]
pub struct RealizeCtx<'a> {
path: &'a str,
requester: RequesterId,
deferred: &'a mut Deferred,
}
impl<'a> RealizeCtx<'a> {
pub fn new(path: &'a str, requester: RequesterId, deferred: &'a mut Deferred) -> Self {
RealizeCtx {
path,
requester,
deferred,
}
}
pub fn path(&self) -> &str {
self.path
}
pub fn requester(&self) -> RequesterId {
self.requester
}
pub fn defer(&mut self, action: impl FnOnce() + Send + 'static) {
self.deferred.push(action);
}
pub fn error(&self, message: impl Into<String>) -> Error {
Error::Config {
at: String::from(self.path),
message: message.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU32, Ordering};
#[test]
fn deferred_actions_run_in_push_order() {
let log = Arc::new(AtomicU32::new(0));
let mut q = Deferred::new();
for i in 1..=3u32 {
let log = Arc::clone(&log);
q.push(move || {
log.store(log.load(Ordering::Relaxed) * 10 + i, Ordering::Relaxed);
});
}
assert_eq!(q.len(), 3);
assert_eq!(q.drain(), 3);
assert_eq!(log.load(Ordering::Relaxed), 123);
assert!(q.is_empty());
}
#[test]
fn an_action_may_queue_more_work_without_recursing() {
let count = Arc::new(AtomicU32::new(0));
let mut q = Deferred::new();
let c = Arc::clone(&count);
q.push(move || {
c.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(q.drain(), 1);
assert_eq!(count.load(Ordering::Relaxed), 1);
}
#[test]
fn draining_an_empty_queue_is_free() {
let mut q = Deferred::new();
assert_eq!(q.drain(), 0);
assert!(q.is_empty());
}
#[derive(Debug)]
struct Publisher {
cell: Arc<AtomicU64>,
}
static PUBLISHER_CLASS: DeviceClass = DeviceClass {
name: "test.publisher",
version: 1,
summary: "publishes a timebase cell, for the export tests",
properties: &[],
construct: |_| {
Ok(Box::new(Publisher {
cell: Arc::new(AtomicU64::new(0)),
}))
},
};
impl Device for Publisher {
fn class(&self) -> &'static DeviceClass {
&PUBLISHER_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
self.cell.store(0, Ordering::Relaxed);
}
fn export(&self, which: ExportId) -> Option<Export> {
(which == ExportId::TIMEBASE).then(|| Export::Cell(Arc::clone(&self.cell)))
}
}
#[test]
fn a_device_publishes_nothing_unless_it_says_so() {
let d = Publisher {
cell: Arc::new(AtomicU64::new(0)),
};
assert!(d.export(ExportId(0x8000)).is_none(), "an unknown id");
assert!(d.export(ExportId::TIMEBASE).is_some());
}
#[test]
fn an_exported_cell_is_shared_and_survives_a_reset() {
let d = Publisher {
cell: Arc::new(AtomicU64::new(0)),
};
let held = d
.export(ExportId::TIMEBASE)
.expect("published")
.cell()
.expect("a cell")
.clone();
d.cell.store(42, Ordering::Relaxed);
assert_eq!(held.load(Ordering::Relaxed), 42, "the consumer sees writes");
d.reset(ResetKind::Cold);
d.cell.store(7, Ordering::Relaxed);
assert_eq!(held.load(Ordering::Relaxed), 7);
}
#[test]
fn export_ids_name_themselves_for_an_error_message() {
assert_eq!(ExportId::TIMEBASE.to_string(), "timebase");
assert_eq!(ExportId(0x8001).to_string(), "export #32769");
assert_eq!(ExportId(0x8001).name(), None);
assert_eq!(
Export::Cell(Arc::new(AtomicU64::new(0))).shape(),
"a 64-bit cell"
);
}
#[test]
fn realize_errors_name_the_instance() {
let mut q = Deferred::new();
let ctx = RealizeCtx::new("pci.0.nvme", RequesterId(7), &mut q);
assert_eq!(ctx.path(), "pci.0.nvme");
assert_eq!(ctx.requester(), RequesterId(7));
let e = ctx.error("cannot map at 0x2000").to_string();
assert!(e.contains("pci.0.nvme"), "{e}");
assert!(e.contains("cannot map"), "{e}");
}
#[test]
fn a_context_can_defer_during_realize() {
let ran = Arc::new(AtomicU32::new(0));
let mut q = Deferred::new();
{
let mut ctx = RealizeCtx::new("cpu", RequesterId::ANONYMOUS, &mut q);
let r = Arc::clone(&ran);
ctx.defer(move || {
r.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(ran.load(Ordering::Relaxed), 0);
}
q.drain();
assert_eq!(ran.load(Ordering::Relaxed), 1);
}
}