use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::machine::realize::Instance;
use super::dt::{DtSource, NodeKind, NodeSpec};
pub const CLASS_NAME: &str = "riscv.syscon";
pub const REGISTER_WINDOW_LEN: u64 = 0x1000;
pub const CMD_PASS: u16 = 0x5555;
pub const CMD_FAIL: u16 = 0x3333;
pub const CMD_RESET: u16 = 0x7777;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Request {
Poweroff,
Fail(u16),
Reboot,
}
#[derive(Debug, Default)]
pub struct Signal {
pending: Mutex<Option<Request>>,
}
impl Signal {
#[must_use]
pub fn new() -> Signal {
Signal {
pending: Mutex::with_rank(LockRank::LEAF, None),
}
}
#[must_use]
pub fn peek(&self) -> Option<Request> {
*self.pending.lock()
}
pub fn take(&self) -> Option<Request> {
self.pending.lock().take()
}
pub fn raise(&self, request: Request) {
let mut pending = self.pending.lock();
if pending.is_none() {
*pending = Some(request);
}
}
pub fn clear(&self) {
*self.pending.lock() = None;
}
}
pub mod signals {
use super::Signal;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::error::Result;
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
pub const KIND: HostKind = HostKind::new("signal");
pub fn open(hosts: &HostObjects, name: &str) -> Result<Arc<Signal>> {
hosts.open(KIND, name, Signal::new)
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<Signal>> {
props.host(KIND, name, Signal::new)
}
pub fn get(hosts: &HostObjects, name: &str) -> Result<Option<Arc<Signal>>> {
hosts.get(KIND, name)
}
pub fn close(hosts: &HostObjects, name: &str) -> bool {
hosts.close(KIND, name)
}
#[must_use]
pub fn names(hosts: &HostObjects) -> Vec<String> {
hosts.names(KIND)
}
}
struct Registers {
signal: Arc<Signal>,
signal_name: String,
out: Mutex<Option<WireSource>>,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Registers")
.field("signal", &self.signal_name)
.field("pending", &self.signal.peek())
.finish()
}
}
#[derive(Debug)]
pub struct Syscon {
regs: Arc<Registers>,
region: RegionRef,
}
impl Syscon {
pub fn new(props: &Props) -> Result<Syscon> {
let mut r = props.reader();
let name = r.or("signal", String::from("power"))?;
r.finish()?;
Ok(Syscon::with_signal(signals::attach(props, &name)?, name))
}
#[must_use]
pub fn with_signal(signal: Arc<Signal>, signal_name: String) -> Syscon {
let regs = Arc::new(Registers {
signal,
signal_name,
out: Mutex::with_rank(LockRank::LEAF, None),
});
let region: RegionRef = Arc::new(Region::io(
"riscv.syscon",
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
Syscon { regs, region }
}
#[must_use]
pub fn signal(&self) -> &Arc<Signal> {
&self.regs.signal
}
#[must_use]
pub fn signal_name(&self) -> &str {
&self.regs.signal_name
}
}
impl MemOps for Registers {
fn read(&self, _offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
if dst.len() != 4 {
return Err(BusError::BadAccess);
}
dst.fill(0);
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if src.len() != 4 || offset != 0 {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
let value = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
let command = value as u16;
let payload = (value >> 16) as u16;
match command {
CMD_PASS => self.signal.raise(Request::Poweroff),
CMD_FAIL => self.signal.raise(Request::Fail(payload)),
CMD_RESET => {
self.signal.raise(Request::Reboot);
let out = self.out.lock().clone();
if let Some(out) = out {
out.pulse(Level::High);
}
}
_ => {}
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U32, Endian::Little)
}
}
impl DtSource for Registers {
fn dt_spec(&self) -> NodeSpec {
NodeSpec {
kind: NodeKind::Syscon {
poweroff: u32::from(CMD_PASS),
reboot: u32::from(CMD_RESET),
},
name: "test",
compatible: &["sifive,test1", "sifive,test0", "syscon"],
cells: alloc::vec![("reg-io-width", alloc::vec![4])],
strings: alloc::vec![],
irq_wire: None,
}
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: 1,
summary: "system controller: a guest writes a magic value to power off, fail, or reboot",
properties: &[PropertySpec {
name: "signal",
kind: ValueKind::Str,
required: false,
summary: "the named signal a request lands on (default \"power\")",
}],
construct: |props| Ok(Box::new(Syscon::new(props)?)),
};
impl Device for Syscon {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
super::dt::publish(
ctx.hosts(),
&self.region,
Arc::downgrade(&self.regs) as Weak<dyn DtSource>,
)
}
fn reset(&self, kind: ResetKind) {
if kind == ResetKind::Cold {
self.regs.signal.clear();
}
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != "reset" {
return Err(Error::Config {
at: port.to_string(),
message: String::from("a system controller drives one pin, `reset`"),
});
}
*self.regs.out.lock() = Some(source);
Ok(())
}
}
impl Instance for Syscon {}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Syscon::new(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("signal", ValueKind::Str))
.region("")
.region("regs")
.port("reset", PortDir::Out)
}
#[cfg(test)]
mod tests {
use super::*;
fn syscon() -> Syscon {
Syscon::with_signal(Arc::new(Signal::new()), "test".to_string())
}
fn poke(s: &Syscon, value: u32) {
s.regs
.write(0, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a word write is legal");
}
#[test]
fn the_magic_values_are_the_three_requests() {
let s = syscon();
poke(&s, u32::from(CMD_PASS));
assert_eq!(s.signal().take(), Some(Request::Poweroff));
poke(&s, u32::from(CMD_RESET));
assert_eq!(s.signal().take(), Some(Request::Reboot));
poke(&s, (0xbeefu32 << 16) | u32::from(CMD_FAIL));
assert_eq!(s.signal().take(), Some(Request::Fail(0xbeef)));
}
#[test]
fn an_unrecognised_command_does_nothing() {
let s = syscon();
poke(&s, 0x1234);
assert_eq!(s.signal().peek(), None);
}
#[test]
fn the_first_request_wins() {
let s = syscon();
poke(&s, (7u32 << 16) | u32::from(CMD_FAIL));
poke(&s, u32::from(CMD_PASS));
assert_eq!(s.signal().peek(), Some(Request::Fail(7)));
}
#[test]
fn a_debug_write_is_refused_and_a_read_is_zero() {
let s = syscon();
assert!(
s.regs
.write(0, &u32::from(CMD_PASS).to_le_bytes(), MemAttrs::DEBUG)
.is_err()
);
assert_eq!(s.signal().peek(), None);
let mut bytes = [0xffu8; 4];
s.regs.read(0, &mut bytes, MemAttrs::DEBUG).unwrap();
assert_eq!(bytes, [0; 4]);
}
#[test]
fn only_an_aligned_word_at_offset_zero_is_a_command() {
let s = syscon();
assert!(s.regs.write(4, &[0u8; 4], MemAttrs::DEFAULT).is_err());
assert!(s.regs.write(0, &[0u8; 2], MemAttrs::DEFAULT).is_err());
}
#[test]
fn a_name_reaches_the_same_signal_from_both_ends() {
let hosts = crate::core::HostObjects::new();
let device_end = signals::open(&hosts, "power").unwrap();
let host_end = signals::open(&hosts, "power").unwrap();
device_end.raise(Request::Poweroff);
assert_eq!(host_end.take(), Some(Request::Poweroff));
assert_eq!(signals::names(&hosts), ["power"]);
assert!(signals::close(&hosts, "power"));
assert!(signals::get(&hosts, "power").unwrap().is_none());
let elsewhere = crate::core::HostObjects::new();
let other = signals::open(&elsewhere, "power").unwrap();
assert!(!alloc::sync::Arc::ptr_eq(&device_end, &other));
}
#[test]
fn a_cold_reset_clears_a_pending_request_and_a_warm_one_does_not() {
let s = syscon();
poke(&s, u32::from(CMD_RESET));
s.reset(ResetKind::Warm);
assert_eq!(
s.signal().peek(),
Some(Request::Reboot),
"the reboot stands"
);
s.reset(ResetKind::Cold);
assert_eq!(s.signal().peek(), None);
}
}