use alloc::boxed::Box;
use alloc::format;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::BusError;
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU32, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink, WireSource};
use crate::machine::Instance;
use crate::machine::validate::{ClassSchema, PortDir, PropSchema, port_index};
const CLASS_NAME: &str = "st.gpio";
const STATE_VERSION: u32 = 1;
pub const PINS: u32 = 16;
pub const REGISTER_BYTES: u64 = 0x28;
const MODE_OUTPUT: u32 = 0b01;
const MODE_ALTERNATE: u32 = 0b10;
const LCKK: u32 = 1 << 16;
const LCK_MASK: u32 = 0xffff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct State {
moder: u32,
otyper: u32,
ospeedr: u32,
pupdr: u32,
odr: u32,
lckr: u32,
afr: [u32; 2],
lock_step: Option<(u8, u32)>,
locked: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ResetValues {
pub moder: u32,
pub ospeedr: u32,
pub pupdr: u32,
}
impl State {
fn reset(values: &ResetValues) -> State {
State {
moder: values.moder,
otyper: 0,
ospeedr: values.ospeedr,
pupdr: values.pupdr,
odr: 0,
lckr: 0,
afr: [0; 2],
lock_step: None,
locked: false,
}
}
fn mode(&self, n: u32) -> u32 {
(self.moder >> (n * 2)) & 0b11
}
}
struct Registers {
state: Mutex<State>,
reset_values: ResetValues,
pads: Pads,
out: Mutex<[Option<WireSource>; PINS as usize]>,
}
#[derive(Debug, Default)]
struct Pads {
external: AtomicU32,
alternate: AtomicU32,
}
impl Pads {
fn get(&self, kind: PadKind) -> u32 {
match kind {
PadKind::External => self.external.load(Ordering::Acquire),
PadKind::Alternate => self.alternate.load(Ordering::Acquire),
}
}
fn set(&self, kind: PadKind, n: u32, high: bool) {
let slot = match kind {
PadKind::External => &self.external,
PadKind::Alternate => &self.alternate,
};
let bit = 1u32 << n;
if high {
slot.fetch_or(bit, Ordering::Release);
} else {
slot.fetch_and(!bit, Ordering::Release);
}
}
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
match self.state.try_lock() {
Some(state) => s.field("state", &*state),
None => s.field("state", &"<locked>"),
};
s.field("reset", &self.reset_values).finish()
}
}
impl Registers {
fn pin_levels(&self, state: &State) -> u32 {
let external = self.pads.get(PadKind::External);
let alternate = self.pads.get(PadKind::Alternate);
let mut out = 0u32;
for n in 0..PINS {
let bit = 1u32 << n;
let level = match state.mode(n) {
MODE_OUTPUT => state.odr & bit,
MODE_ALTERNATE => alternate & bit,
_ => external & bit,
};
out |= level;
}
out
}
fn refresh_pins(&self) {
let levels = {
let state = self.state.lock();
self.pin_levels(&state)
};
let sources: Vec<Option<WireSource>> = self.out.lock().clone().into_iter().collect();
for (n, source) in sources.iter().enumerate() {
let Some(source) = source else { continue };
source.set(Level::from_bool(levels & (1 << n) != 0));
}
}
fn is_locked(state: &State, n: u32) -> bool {
state.locked && state.lckr & (1 << n) != 0
}
fn write_paired(state: &State, old: u32, value: u32) -> u32 {
let mut out = value;
for n in 0..PINS {
if Registers::is_locked(state, n) {
let mask = 0b11 << (n * 2);
out = (out & !mask) | (old & mask);
}
}
out
}
fn write_single(state: &State, old: u32, value: u32) -> u32 {
let mut out = value & 0xffff;
for n in 0..PINS {
if Registers::is_locked(state, n) {
let mask = 1 << n;
out = (out & !mask) | (old & mask);
}
}
out
}
fn write_afr(state: &State, old: u32, value: u32, half: u32) -> u32 {
let mut out = value;
for slot in 0..8u32 {
let n = half * 8 + slot;
if Registers::is_locked(state, n) {
let mask = 0xf << (slot * 4);
out = (out & !mask) | (old & mask);
}
}
out
}
fn write_lckr(state: &mut State, value: u32) {
if state.locked {
return;
}
let mask = value & LCK_MASK;
let key = value & LCKK != 0;
state.lock_step = match (state.lock_step, key) {
(None, true) => Some((1, mask)),
(Some((1, m)), false) if m == mask => Some((2, mask)),
(Some((2, m)), true) if m == mask => Some((3, mask)),
(_, true) => Some((1, mask)),
(_, false) => None,
};
state.lckr = mask;
}
fn read_lckr(state: &mut State, debug: bool) -> u32 {
if !debug
&& !state.locked
&& let Some((3, mask)) = state.lock_step
{
state.locked = true;
state.lckr = mask;
state.lock_step = None;
}
state.lckr | if state.locked { LCKK } else { 0 }
}
fn read_register(&self, offset: u64, debug: bool) -> (u32, bool) {
let mut state = self.state.lock();
let value = match offset {
0x00 => state.moder,
0x04 => state.otyper,
0x08 => state.ospeedr,
0x0c => state.pupdr,
0x10 => self.pin_levels(&state),
0x14 => state.odr,
0x18 => 0,
0x1c => Registers::read_lckr(&mut state, debug),
0x20 => state.afr[0],
0x24 => state.afr[1],
_ => 0,
};
(value, false)
}
fn write_register(&self, offset: u64, value: u32) -> bool {
let mut state = self.state.lock();
match offset {
0x00 => state.moder = Registers::write_paired(&state, state.moder, value),
0x04 => state.otyper = Registers::write_single(&state, state.otyper, value),
0x08 => state.ospeedr = Registers::write_paired(&state, state.ospeedr, value),
0x0c => state.pupdr = Registers::write_paired(&state, state.pupdr, value),
0x10 => return false,
0x14 => state.odr = value & 0xffff,
0x18 => {
let odr = (state.odr & !(value >> 16)) | (value & 0xffff);
state.odr = odr & 0xffff;
}
0x1c => Registers::write_lckr(&mut state, value),
0x20 => state.afr[0] = Registers::write_afr(&state, state.afr[0], value, 0),
0x24 => state.afr[1] = Registers::write_afr(&state, state.afr[1], value, 1),
_ => return false,
}
true
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = dst else {
return Err(BusError::BadAccess);
};
let (value, moved) = self.read_register(offset & !3, attrs.debug);
let bytes = value.to_le_bytes();
(*a, *b, *c, *d) = (bytes[0], bytes[1], bytes[2], bytes[3]);
if moved {
self.refresh_pins();
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
let value = u32::from_le_bytes([*a, *b, *c, *d]);
if self.write_register(offset & !3, value) {
self.refresh_pins();
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U32, Endian::Little)
}
}
#[derive(Debug)]
pub struct Gpio {
regs: Arc<Registers>,
region: RegionRef,
pins: Mutex<Vec<Arc<PadPin>>>,
}
impl Gpio {
pub fn new(props: &Props) -> Result<Gpio> {
let mut r = props.reader();
let moder = r.or_range("moder-reset", 0u64, 0..=u64::from(u32::MAX))? as u32;
let ospeedr = r.or_range("ospeedr-reset", 0u64, 0..=u64::from(u32::MAX))? as u32;
let pupdr = r.or_range("pupdr-reset", 0u64, 0..=u64::from(u32::MAX))? as u32;
r.finish()?;
Ok(Gpio::with_reset(ResetValues {
moder,
ospeedr,
pupdr,
}))
}
#[must_use]
pub fn with_reset(reset_values: ResetValues) -> Gpio {
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::reset(&reset_values)),
reset_values,
pads: Pads::default(),
out: Mutex::with_rank(LockRank::WIRE, [const { None }; PINS as usize]),
});
let region = Arc::new(Region::io(
"gpio",
REGISTER_BYTES,
Arc::clone(®s) as Arc<dyn MemOps>,
));
Gpio {
regs,
region,
pins: Mutex::with_rank(LockRank::DEVICE, Vec::new()),
}
}
#[must_use]
pub fn pin_levels(&self) -> u32 {
let state = self.regs.state.lock();
self.regs.pin_levels(&state)
}
pub fn set_external(&self, n: u32, level: bool) {
if n >= PINS {
return;
}
self.regs.pads.set(PadKind::External, n, level);
self.regs.refresh_pins();
}
pub fn set_alternate(&self, n: u32, level: bool) {
if n >= PINS {
return;
}
self.regs.pads.set(PadKind::Alternate, n, level);
self.regs.refresh_pins();
}
#[must_use]
pub fn alternate_function(&self, n: u32) -> u8 {
if n >= PINS {
return 0;
}
let state = self.regs.state.lock();
let (half, slot) = if n < 8 { (0, n) } else { (1, n - 8) };
((state.afr[half] >> (slot * 4)) & 0xf) as u8
}
}
impl Device for Gpio {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
*self.regs.state.lock() = State::reset(&self.regs.reset_values);
self.regs.refresh_pins();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = *self.regs.state.lock();
for value in [
state.moder,
state.otyper,
state.ospeedr,
state.pupdr,
state.odr,
state.lckr,
state.afr[0],
state.afr[1],
] {
w.write_u32(value)?;
}
match state.lock_step {
None => w.write_u8(0)?,
Some((step, mask)) => {
w.write_u8(step)?;
w.write_u32(mask)?;
}
}
w.write_bool(state.locked)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::reset(&self.regs.reset_values);
state.moder = r.read_u32()?;
state.otyper = r.read_u32()?;
state.ospeedr = r.read_u32()?;
state.pupdr = r.read_u32()?;
state.odr = r.read_u32()?;
state.lckr = r.read_u32()?;
state.afr[0] = r.read_u32()?;
state.afr[1] = r.read_u32()?;
let step = r.read_u8()?;
state.lock_step = match step {
0 => None,
1..=3 => Some((step, r.read_u32()?)),
other => {
return Err(Error::State(format!(
"snapshot is at step {other} of a three-write lock sequence"
)));
}
};
state.locked = r.read_bool()?;
*self.regs.state.lock() = state;
self.regs.refresh_pins();
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let n = port_index(port, "p", PINS).ok_or_else(|| Error::Config {
at: port.to_string(),
message: format!("a GPIO port drives `p0`…`p{}`", PINS - 1),
})?;
self.regs.out.lock()[n as usize] = Some(source);
self.regs.refresh_pins();
Ok(())
}
fn announce(&self, port: &str) {
if port_index(port, "p", PINS).is_some() {
self.regs.refresh_pins();
}
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let (n, kind) = match port_index(port, "in", PINS) {
Some(n) => (n, PadKind::External),
None => (port_index(port, "af", PINS)?, PadKind::Alternate),
};
let pin = Arc::new(PadPin::new(Arc::clone(&self.regs), n, kind, sources));
self.pins.lock().push(Arc::clone(&pin));
Some(SinkPin { sink: pin, line: n })
}
}
impl Instance for Gpio {}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "STM32 GPIO port: MODER/OTYPER/OSPEEDR/PUPDR, IDR/ODR, the atomic BSRR, LCKR and AFR",
properties: &[
PropertySpec {
name: "moder-reset",
kind: ValueKind::Uint,
required: false,
summary: "MODER's reset value (0xa8000000 for GPIOA, 0x280 for GPIOB, else 0)",
},
PropertySpec {
name: "ospeedr-reset",
kind: ValueKind::Uint,
required: false,
summary: "OSPEEDR's reset value (0xc0 for GPIOB, else 0)",
},
PropertySpec {
name: "pupdr-reset",
kind: ValueKind::Uint,
required: false,
summary: "PUPDR's reset value (0x64000000 for GPIOA, 0x100 for GPIOB, else 0)",
},
],
construct: |props| Ok(Box::new(Gpio::new(props)?)),
};
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(Gpio::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("moder-reset", ValueKind::Uint).range(0, u64::from(u32::MAX)))
.prop(PropSchema::new("ospeedr-reset", ValueKind::Uint).range(0, u64::from(u32::MAX)))
.prop(PropSchema::new("pupdr-reset", ValueKind::Uint).range(0, u64::from(u32::MAX)))
.region("")
.region("regs")
.port_bank("p", PortDir::Out, PINS)
.port_bank("in", PortDir::In, PINS)
.port_bank("af", PortDir::In, PINS)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PadKind {
External,
Alternate,
}
#[derive(Debug)]
pub struct PadPin {
regs: Arc<Registers>,
pin: u32,
kind: PadKind,
inputs: FanIn,
resolve: Resolve,
}
impl PadPin {
fn new(regs: Arc<Registers>, pin: u32, kind: PadKind, sources: &[WireId]) -> PadPin {
PadPin {
regs,
pin,
kind,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn pin(&self) -> u32 {
self.pin
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for PadPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let high = self.inputs.resolve(self.resolve).is_high();
self.regs.pads.set(self.kind, self.pin, high);
self.regs.refresh_pins();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::props::Value;
use crate::core::registry::Registry;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::wire::{Wire, WireIdAllocator};
fn port() -> Gpio {
Gpio::with_reset(ResetValues::default())
}
fn peek(gpio: &Gpio, offset: u64) -> u32 {
let mut word = [0u8; 4];
gpio.regs
.read(offset, &mut word, MemAttrs::DEFAULT)
.expect("a word read is legal");
u32::from_le_bytes(word)
}
fn peek_debug(gpio: &Gpio, offset: u64) -> u32 {
let mut word = [0u8; 4];
gpio.regs
.read(offset, &mut word, MemAttrs::DEBUG)
.expect("a word read is legal");
u32::from_le_bytes(word)
}
fn poke(gpio: &Gpio, offset: u64, value: u32) {
gpio.regs
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a word write is legal");
}
fn as_output(gpio: &Gpio, n: u32) {
let moder = peek(gpio, 0x00) | (MODE_OUTPUT << (n * 2));
poke(gpio, 0x00, moder);
}
#[test]
fn bsrr_sets_and_resets_without_a_read_modify_write() {
let gpio = port();
as_output(&gpio, 5);
as_output(&gpio, 7);
as_output(&gpio, 9);
poke(&gpio, 0x18, 1 << 5);
assert_eq!(peek(&gpio, 0x14), 1 << 5, "ODR");
poke(&gpio, 0x18, 1 << 7);
poke(&gpio, 0x18, 1 << (5 + 16));
assert_eq!(peek(&gpio, 0x14), 1 << 7);
poke(&gpio, 0x18, (1 << 9) | (1 << (9 + 16)));
assert_eq!(peek(&gpio, 0x14), (1 << 7) | (1 << 9));
assert_eq!(peek(&gpio, 0x18), 0);
}
#[test]
fn moder_decides_which_of_the_three_drives_a_pin() {
let gpio = port();
poke(&gpio, 0x14, 0xffff);
gpio.set_external(3, true);
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 1 << 3, "IDR follows the pad");
gpio.set_external(3, false);
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 0, "not ODR");
as_output(&gpio, 3);
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 1 << 3);
gpio.set_external(3, false);
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 1 << 3, "the port wins");
poke(&gpio, 0x00, MODE_ALTERNATE << (3 * 2));
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 0);
gpio.set_alternate(3, true);
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 1 << 3);
poke(&gpio, 0x00, 0b11 << (3 * 2));
assert_eq!(peek(&gpio, 0x10) & (1 << 3), 0);
}
#[test]
fn a_pin_output_reaches_a_wire() {
#[derive(Debug, Default)]
struct Probe {
level: crate::core::sync::AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level.store(
u32::from(level.is_high()),
crate::core::sync::Ordering::Relaxed,
);
}
}
let gpio = port();
let ids = WireIdAllocator::new();
let id = ids.alloc();
let probe = Arc::new(Probe::default());
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
Device::connect(&gpio, "p13", WireSource::new(wire, id)).expect("a port drives p13");
assert!(
Device::connect(&gpio, "p16", dummy_source()).is_err(),
"a port is sixteen pins wide"
);
let level = || probe.level.load(crate::core::sync::Ordering::Relaxed);
as_output(&gpio, 13);
poke(&gpio, 0x18, 1 << 13);
assert_eq!(level(), 1);
poke(&gpio, 0x18, 1 << (13 + 16));
assert_eq!(level(), 0);
}
#[test]
fn an_input_pin_reaches_idr_and_survives_the_handover() {
let gpio = port();
let src = WireId::new(1);
let pin = Device::sink(&gpio, "in9", &[src]).expect("in9");
assert_eq!(pin.line, 9);
let weak = Arc::downgrade(&pin.sink);
drop(pin);
let alive = weak.upgrade().expect("the port still owns the pin");
alive.set_level(src, 0, Level::High);
assert_eq!(peek(&gpio, 0x10), 1 << 9);
let af = Device::sink(&gpio, "af9", &[src]).expect("af9");
af.sink.set_level(src, 0, Level::High);
assert_eq!(peek(&gpio, 0x10), 1 << 9);
assert!(Device::sink(&gpio, "in16", &[src]).is_none());
assert!(
Device::sink(&gpio, "p0", &[src]).is_none(),
"p0 is an output"
);
}
#[test]
fn the_lock_needs_its_whole_key_sequence() {
let gpio = port();
poke(&gpio, 0x1c, LCKK | 0x0001);
poke(&gpio, 0x1c, 0x0001);
assert_eq!(peek(&gpio, 0x1c) & LCKK, 0, "not committed yet");
poke(&gpio, 0x00, 0xffff_ffff);
assert_eq!(peek(&gpio, 0x00), 0xffff_ffff, "still writable");
poke(&gpio, 0x00, 0);
poke(&gpio, 0x1c, LCKK | 0x0003);
poke(&gpio, 0x1c, 0x0003);
poke(&gpio, 0x1c, LCKK | 0x0003);
assert_eq!(peek(&gpio, 0x1c) & LCKK, LCKK, "committed by the read");
poke(&gpio, 0x00, 0xffff_ffff);
assert_eq!(
peek(&gpio, 0x00),
0xffff_fff0,
"the locked pins kept their MODER bits"
);
poke(&gpio, 0x04, 0xffff);
assert_eq!(peek(&gpio, 0x04), 0xfffc);
poke(&gpio, 0x20, 0xffff_ffff);
assert_eq!(peek(&gpio, 0x20), 0xffff_ff00);
poke(&gpio, 0x14, 0xffff);
assert_eq!(peek(&gpio, 0x14), 0xffff);
Device::reset(&gpio, ResetKind::Cold);
poke(&gpio, 0x00, 0xffff_ffff);
assert_eq!(peek(&gpio, 0x00), 0xffff_ffff);
}
#[test]
fn a_debug_access_changes_nothing() {
let gpio = port();
poke(&gpio, 0x1c, LCKK | 0x0001);
poke(&gpio, 0x1c, 0x0001);
poke(&gpio, 0x1c, LCKK | 0x0001);
assert_eq!(peek_debug(&gpio, 0x1c) & LCKK, 0, "not committed by a peek");
assert_eq!(peek(&gpio, 0x1c) & LCKK, LCKK, "committed by a real read");
let gpio = port();
assert_eq!(
gpio.regs.write(0x18, &1u32.to_le_bytes(), MemAttrs::DEBUG),
Err(BusError::BadAccess)
);
assert_eq!(peek(&gpio, 0x14), 0);
}
#[test]
fn a_reset_restores_the_ports_own_values_and_not_its_neighbours() {
let porta = Gpio::with_reset(ResetValues {
moder: 0xa800_0000,
ospeedr: 0,
pupdr: 0x6400_0000,
});
assert_eq!(peek(&porta, 0x00), 0xa800_0000);
assert_eq!(peek(&porta, 0x0c), 0x6400_0000);
poke(&porta, 0x00, 0);
Device::reset(&porta, ResetKind::Warm);
assert_eq!(peek(&porta, 0x00), 0xa800_0000);
}
#[test]
fn a_reset_does_not_invent_a_level_for_an_input_pin() {
let gpio = port();
gpio.set_external(4, true);
Device::reset(&gpio, ResetKind::Cold);
assert_eq!(
peek(&gpio, 0x10) & (1 << 4),
1 << 4,
"whatever is driving a pin is still driving it"
);
}
#[test]
fn only_a_full_word_is_a_legal_access() {
let gpio = port();
let mut byte = [0u8; 1];
assert_eq!(
gpio.regs.read(0x14, &mut byte, MemAttrs::DEFAULT),
Err(BusError::BadAccess)
);
assert_eq!(
gpio.regs.constraints(),
AccessConstraints::word(Width::U32, Endian::Little)
);
}
#[test]
fn the_alternate_function_nibble_reads_back() {
let gpio = port();
poke(&gpio, 0x20, 7 << (2 * 4));
assert_eq!(gpio.alternate_function(2), 7);
poke(&gpio, 0x24, 0xb << (5 * 4));
assert_eq!(gpio.alternate_function(13), 0xb);
assert_eq!(gpio.alternate_function(0), 0);
}
#[test]
fn a_snapshot_round_trips_to_identical_state() {
let saved = port();
as_output(&saved, 12);
poke(&saved, 0x18, 1 << 12);
poke(&saved, 0x04, 0x00f0);
poke(&saved, 0x20, 0x0765_4321);
poke(&saved, 0x1c, LCKK | 0x0001);
poke(&saved, 0x1c, 0x0001);
poke(&saved, 0x1c, LCKK | 0x0001);
assert_eq!(peek(&saved, 0x1c) & LCKK, LCKK);
let mut shape = MachineShape::new();
shape.add_device("gpio", CLASS_NAME).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("gpio", CLASS_NAME, STATE_VERSION).unwrap();
Device::save(&saved, &mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = port();
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("gpio", CLASS_NAME, STATE_VERSION, &Migrations::new())
.unwrap();
Device::load(&restored, &mut chunk.reader()).unwrap();
let before: Vec<u32> = (0..10).map(|i| peek_debug(&saved, i * 4)).collect();
let after: Vec<u32> = (0..10).map(|i| peek_debug(&restored, i * 4)).collect();
assert_eq!(before, after);
poke(&restored, 0x00, 0xffff_ffff);
assert_eq!(peek(&restored, 0x00), 0xffff_fffc);
}
#[test]
fn a_property_this_class_does_not_know_is_a_typo() {
let props = Props::new().with("moder-reset", Value::from(0xa800_0000u64));
assert_eq!(peek(&Gpio::new(&props).unwrap(), 0x00), 0xa800_0000);
let props = Props::new().with("moder_reset", Value::from(0u64));
assert!(Gpio::new(&props).is_err());
}
#[test]
fn the_class_is_registrable_and_constructs_through_the_registry() {
let mut reg = Registry::new();
register(&mut reg).unwrap();
assert!(register(&mut reg).is_err(), "twice is a collision");
let device = reg.create(CLASS_NAME, &Props::new()).unwrap();
assert_eq!(device.class().name, CLASS_NAME);
}
#[test]
fn the_schema_and_the_device_agree_about_pins_and_regions() {
let gpio = port();
let schema = schema();
let src = WireId::new(1);
for n in [0u32, 15] {
assert!(schema.port_named(&format!("p{n}")).is_some());
assert!(schema.port_named(&format!("in{n}")).is_some());
assert!(schema.port_named(&format!("af{n}")).is_some());
assert!(Device::sink(&gpio, &format!("in{n}"), &[src]).is_some());
assert!(Device::sink(&gpio, &format!("af{n}"), &[src]).is_some());
}
assert!(schema.port_named("p16").is_none());
assert!(Device::region(&gpio, "").is_some());
assert!(Device::region(&gpio, "regs").is_some());
assert!(Device::region(&gpio, "pins").is_none());
}
fn dummy_source() -> WireSource {
let id = WireId::new(9);
WireSource::new(Wire::builder().source(id).build_shared(), id)
}
}