use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{Budget, Consumed, LazyHandle};
use crate::core::space::{RegionRef, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter};
use crate::core::wire::{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()
}
}
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 connect(&self, port: &str, _source: WireSource) -> Result<()> {
Err(Error::Config {
at: port.to_string(),
message: "this device drives no such pin".to_string(),
})
}
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 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 attach_lazy(&self, handle: LazyHandle) {
let _ = handle;
}
}
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());
}
#[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);
}
}