use super::*;
use alloc::vec::Vec;
use crate::bus::i2c::wires::{MasterWires, pin as line};
use crate::core::device::{Device, ResetKind};
use crate::core::props::{Props, Value};
use crate::core::space::{RegionKind, RegionRef};
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::wire::{Wire, WireId, WireSource};
use crate::dev::atmel::at24c::At24c;
fn regs(ctrl: &Stm32I2c) -> RegionRef {
ctrl.region("").expect("the peripheral maps its registers")
}
fn ops(region: &RegionRef) -> Arc<dyn MemOps> {
match region.kind() {
RegionKind::Io(ops) => Arc::clone(ops),
other => panic!("expected an io region, got {other:?}"),
}
}
fn poke(region: &RegionRef, offset: u64, value: u32) {
ops(region)
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a 32-bit register write");
}
fn peek_with(region: &RegionRef, offset: u64, attrs: MemAttrs) -> u32 {
let mut buf = [0u8; 4];
ops(region)
.read(offset, &mut buf, attrs)
.expect("a 32-bit register read");
u32::from_le_bytes(buf)
}
fn peek(region: &RegionRef, offset: u64) -> u32 {
peek_with(region, offset, MemAttrs::DEFAULT)
}
const CR1: u64 = 0x00;
const CR2: u64 = 0x04;
const OAR1: u64 = 0x08;
const DR: u64 = 0x10;
const SR1: u64 = 0x14;
const SR2: u64 = 0x18;
const CCR: u64 = 0x1c;
const TRISE: u64 = 0x20;
const CCR_VALUE_USED: u32 = 4;
struct Board {
ctrl: Stm32I2c,
region: RegionRef,
eeprom: At24c,
now: u64,
}
impl Board {
fn new(link: Link) -> Board {
let bus = Arc::new(I2cBus::new());
let mut p = Props::new();
let eeprom = At24c::new(&p).expect("an EEPROM");
let ctrl = match link {
Link::Transactional => {
bus.attach(eeprom.slave()).expect("room on the bus");
Stm32I2c::with_bus(Link::Transactional, Some(Arc::clone(&bus)))
}
Link::Wired => {
let ctrl = Stm32I2c::with_bus(Link::Wired, None);
wire_up(&ctrl, &eeprom);
ctrl
}
};
p = Props::new();
let _ = &mut p;
let region = regs(&ctrl);
Board {
ctrl,
region,
eeprom,
now: 0,
}
}
fn step(&mut self, ticks: u64) {
self.now += ticks;
self.ctrl.advance_to(self.now);
self.eeprom.advance_to(self.now);
}
fn wait(&mut self, flags: u32) -> u32 {
for _ in 0..4_000 {
let sr1 = peek_with(&self.region, SR1, MemAttrs::DEBUG);
if sr1 & flags == flags {
return sr1;
}
if sr1 & SR1_AF != 0 {
panic!("the transfer was not acknowledged while waiting for {flags:#x}");
}
self.step(1);
}
panic!(
"gave up waiting for {flags:#x}; SR1 is {:#x}",
peek_with(&self.region, SR1, MemAttrs::DEBUG)
);
}
fn init(&mut self) {
poke(&self.region, CR2, 42);
poke(&self.region, CCR, CCR_VALUE_USED);
poke(&self.region, TRISE, 43);
poke(&self.region, CR1, CR1_PE);
}
fn write_page(&mut self, address: u8, word: u8, data: &[u8]) {
poke(&self.region, CR1, CR1_PE | CR1_ACK | CR1_START);
self.wait(SR1_SB);
peek(&self.region, SR1);
poke(&self.region, DR, u32::from(address << 1));
self.wait(SR1_ADDR);
peek(&self.region, SR1);
peek(&self.region, SR2);
self.wait(SR1_TXE);
poke(&self.region, DR, u32::from(word));
for byte in data {
self.wait(SR1_TXE);
poke(&self.region, DR, u32::from(*byte));
}
self.wait(SR1_TXE | SR1_BTF);
poke(&self.region, CR1, CR1_PE | CR1_ACK | CR1_STOP);
for _ in 0..200 {
if peek_with(&self.region, SR2, MemAttrs::DEBUG) & SR2_MSL == 0 {
return;
}
self.step(1);
}
panic!("the STOP never completed");
}
fn read_from(&mut self, address: u8, word: u8, count: usize) -> Vec<u8> {
poke(&self.region, CR1, CR1_PE | CR1_ACK | CR1_START);
self.wait(SR1_SB);
peek(&self.region, SR1);
poke(&self.region, DR, u32::from(address << 1));
self.wait(SR1_ADDR);
peek(&self.region, SR1);
peek(&self.region, SR2);
self.wait(SR1_TXE);
poke(&self.region, DR, u32::from(word));
self.wait(SR1_BTF);
poke(&self.region, CR1, CR1_PE | CR1_ACK | CR1_START);
self.wait(SR1_SB);
peek(&self.region, SR1);
poke(&self.region, DR, u32::from((address << 1) | 1));
self.wait(SR1_ADDR);
peek(&self.region, SR1);
peek(&self.region, SR2);
let mut out = Vec::new();
for i in 0..count {
if i + 1 == count {
poke(&self.region, CR1, CR1_PE | CR1_STOP);
}
self.wait(SR1_RXNE);
out.push(peek(&self.region, DR) as u8);
}
for _ in 0..200 {
if peek_with(&self.region, SR2, MemAttrs::DEBUG) & SR2_MSL == 0 {
break;
}
self.step(1);
}
out
}
}
fn wire_up(ctrl: &Stm32I2c, eeprom: &At24c) {
let master: &Arc<MasterWires> = ctrl.wires();
let slave = Arc::clone(eeprom.wires());
let scl_ids = [WireId::new(1), WireId::new(2)];
let sda_ids = [WireId::new(3), WireId::new(4)];
let scl = Wire::builder()
.sources(&scl_ids)
.sink(master.sink(line::SCL, &scl_ids), line::SCL)
.sink(slave.sink(line::SCL, &scl_ids), line::SCL)
.build_shared();
let sda = Wire::builder()
.sources(&sda_ids)
.sink(master.sink(line::SDA, &sda_ids), line::SDA)
.sink(slave.sink(line::SDA, &sda_ids), line::SDA)
.build_shared();
master.connect(line::SCL, WireSource::new(Arc::clone(&scl), scl_ids[0]));
master.connect(line::SDA, WireSource::new(Arc::clone(&sda), sda_ids[0]));
slave.connect(line::SCL, WireSource::new(Arc::clone(&scl), scl_ids[1]));
slave.connect(line::SDA, WireSource::new(Arc::clone(&sda), sda_ids[1]));
master.announce();
slave.announce();
}
#[test]
fn the_link_is_a_required_property_and_a_transactional_one_needs_a_bus() {
let p = Props::new();
assert!(
Stm32I2c::new(&p).is_err(),
"`link` has no default, by design"
);
let mut p = Props::new();
p.insert("link", Value::Str("sideways".into()));
let err = Stm32I2c::new(&p).expect_err("an unknown link");
assert!(alloc::format!("{err}").contains("low-speed.md"));
let mut p = Props::new();
p.insert("link", Value::Str("transactional".into()));
let err = Stm32I2c::new(&p).expect_err("no bus to reach");
assert!(alloc::format!("{err}").contains("named bus"));
let mut p = Props::new();
p.insert("link", Value::Str("wired".into()));
assert!(Stm32I2c::new(&p).is_ok(), "a wired link needs no bus");
}
#[test]
fn the_reset_values_are_the_ones_the_reference_manual_gives() {
let ctrl = Stm32I2c::with_bus(Link::Wired, None);
let r = regs(&ctrl);
assert_eq!(peek(&r, CR1), 0);
assert_eq!(peek(&r, CR2), 0);
assert_eq!(peek(&r, OAR1), 0);
assert_eq!(peek(&r, DR), 0);
assert_eq!(peek(&r, SR1), 0);
assert_eq!(peek(&r, SR2), 0);
assert_eq!(peek(&r, CCR), 0);
assert_eq!(peek(&r, TRISE), 2, "§25.6.9's reset value");
}
#[test]
fn the_register_block_takes_half_words_and_words_and_nothing_else() {
let ctrl = Stm32I2c::with_bus(Link::Wired, None);
let r = regs(&ctrl);
let block = ops(&r);
let mut half = [0u8; 2];
assert!(block.read(CR1, &mut half, MemAttrs::DEFAULT).is_ok());
let mut byte = [0u8; 1];
assert!(block.read(CR1, &mut byte, MemAttrs::DEFAULT).is_err());
let mut word = [0u8; 4];
assert!(block.read(0x02, &mut word, MemAttrs::DEFAULT).is_err());
assert!(block.write(CR1, &[1, 0], MemAttrs::DEBUG).is_err());
}
#[test]
fn a_software_reset_puts_everything_back() {
let ctrl = Stm32I2c::with_bus(Link::Wired, None);
let r = regs(&ctrl);
poke(&r, CCR, 0x1234);
poke(&r, CR1, CR1_PE);
poke(&r, CR1, CR1_SWRST);
assert_eq!(peek(&r, CCR), 0);
assert_eq!(peek(&r, CR1), CR1_SWRST);
assert_eq!(peek(&r, TRISE), 2);
}
#[test]
fn addr_is_cleared_by_reading_sr1_and_then_sr2_and_by_nothing_else() {
let mut board = Board::new(Link::Transactional);
board.init();
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
poke(&board.region, DR, 0xa0);
board.wait(SR1_ADDR);
peek(&board.region, SR2);
assert!(
peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_ADDR != 0,
"ADDR must survive an SR2 read that was not preceded by an SR1 read"
);
peek(&board.region, SR1);
peek(&board.region, SR2);
assert_eq!(
peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_ADDR,
0,
"and the documented sequence clears it"
);
}
#[test]
fn a_debug_dump_of_the_whole_block_clears_nothing() {
let mut board = Board::new(Link::Transactional);
board.init();
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
poke(&board.region, DR, 0xa0);
board.wait(SR1_ADDR);
for _ in 0..2 {
for offset in [CR1, CR2, OAR1, 0x0c, DR, SR1, SR2, CCR, TRISE, 0x24] {
let _ = peek_with(&board.region, offset, MemAttrs::DEBUG);
}
}
assert!(
peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_ADDR != 0,
"a debug SR1+SR2 read must not clear ADDR"
);
assert!(
board.ctrl.stretching(),
"and the peripheral is still stalled"
);
peek(&board.region, SR1);
peek(&board.region, SR2);
assert_eq!(peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_ADDR, 0);
}
#[test]
fn a_debug_read_of_the_data_register_pops_nothing() {
let mut board = Board::new(Link::Transactional);
board.init();
board.write_page(0x50, 0x00, &[0x11, 0x22]);
board.step(DEFAULT_EEPROM_WRITE);
let got = board.read_from(0x50, 0x00, 1);
assert_eq!(got, alloc::vec![0x11]);
board.write_page(0x50, 0x00, &[0x33]);
board.step(DEFAULT_EEPROM_WRITE);
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
poke(&board.region, DR, 0xa0);
board.wait(SR1_ADDR);
peek(&board.region, SR1);
peek(&board.region, SR2);
board.wait(SR1_TXE);
poke(&board.region, DR, 0x00);
board.wait(SR1_BTF);
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
poke(&board.region, DR, 0xa1);
board.wait(SR1_ADDR);
peek(&board.region, SR1);
peek(&board.region, SR2);
poke(&board.region, CR1, CR1_PE | CR1_STOP);
board.wait(SR1_RXNE);
let seen = peek_with(&board.region, DR, MemAttrs::DEBUG);
assert_eq!(seen, 0x33);
assert!(
peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_RXNE != 0,
"a debug read of DR must not clear RxNE"
);
assert_eq!(peek(&board.region, DR), 0x33, "and the guest still gets it");
assert_eq!(peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_RXNE, 0);
}
const DEFAULT_EEPROM_WRITE: u64 = crate::dev::atmel::at24c::DEFAULT_WRITE_TICKS;
#[test]
fn a_guest_writes_a_page_to_an_eeprom_and_reads_it_back() {
for link in [Link::Transactional, Link::Wired] {
let mut board = Board::new(link);
board.init();
let page = [0xde_u8, 0xad, 0xbe, 0xef, 0x12, 0x34, 0x56, 0x78];
board.write_page(0x50, 0x08, &page);
board.step(DEFAULT_EEPROM_WRITE);
assert_eq!(
board.eeprom.byte(0x08),
Some(0xde),
"the page did not land under {link}"
);
assert_eq!(
board.read_from(0x50, 0x08, page.len()),
page.to_vec(),
"and reading it back disagreed under {link}"
);
}
}
#[test]
fn both_link_models_write_the_same_page_to_the_same_eeprom() {
let mut results = Vec::new();
for link in [Link::Transactional, Link::Wired] {
let mut board = Board::new(link);
board.init();
board.write_page(0x50, 0x10, &[1, 2, 3, 4, 5, 6, 7, 8]);
board.step(DEFAULT_EEPROM_WRITE);
results.push((board.eeprom.contents(), board.ctrl.ticks()));
}
assert_eq!(results[0].0, results[1].0, "the arrays differ");
assert_eq!(
results[0].1, results[1].1,
"a transfer must cost the same virtual time either way"
);
assert_eq!(&results[0].0[0x10..0x18], &[1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn an_address_nobody_answers_raises_acknowledge_failure() {
for link in [Link::Transactional, Link::Wired] {
let mut board = Board::new(link);
board.init();
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
poke(&board.region, DR, 0x44);
let mut af = false;
for _ in 0..200 {
if peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_AF != 0 {
af = true;
break;
}
board.step(1);
}
assert!(af, "no AF under {link}");
assert_eq!(
peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_ADDR,
0,
"§25.6.6: ADDR is not set after a NACK reception"
);
poke(&board.region, SR1, !SR1_AF);
assert_eq!(peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_AF, 0);
}
}
#[test]
fn the_clock_control_register_is_what_a_transfer_costs() {
let mut board = Board::new(Link::Wired);
board.init();
let start = board.ctrl.ticks();
board.write_page(0x50, 0x00, &[0xaa]);
let spent = board.ctrl.ticks() - start;
let halves = u64::from(START_HALF_PERIODS + 3 * BYTE_HALF_PERIODS + STOP_HALF_PERIODS);
let expected = halves / 2 * (2 * u64::from(CCR_VALUE_USED));
assert_eq!(
spent, expected,
"a transfer costs the bit periods CCR asks for"
);
}
#[test]
fn the_event_and_error_interrupts_follow_their_enable_bits() {
let mut board = Board::new(Link::Transactional);
board.init();
assert_eq!(board.ctrl.ev_level(), Level::Low);
poke(&board.region, CR2, 42 | CR2_ITEVTEN | CR2_ITERREN);
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
assert_eq!(board.ctrl.ev_level(), Level::High, "SB raises EV");
peek(&board.region, SR1);
poke(&board.region, DR, 0x44);
for _ in 0..200 {
if peek_with(&board.region, SR1, MemAttrs::DEBUG) & SR1_AF != 0 {
break;
}
board.step(1);
}
assert_eq!(board.ctrl.er_level(), Level::High, "AF raises ER");
poke(&board.region, SR1, !SR1_AF);
assert_eq!(board.ctrl.er_level(), Level::Low);
}
#[test]
fn a_transfer_part_way_through_its_address_phase_round_trips() {
let mut board = Board::new(Link::Wired);
board.init();
poke(&board.region, CR1, CR1_PE | CR1_ACK | CR1_START);
board.wait(SR1_SB);
peek(&board.region, SR1);
let mut shape = MachineShape::new();
shape.add_device("i2c", ST_I2C_CLASS.name).unwrap();
let mut w = StateWriter::new(shape.clone());
{
let mut chunk = w
.chunk("i2c", ST_I2C_CLASS.name, ST_I2C_CLASS.version)
.unwrap();
board.ctrl.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let other = Stm32I2c::with_bus(Link::Wired, None);
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load(
"i2c",
ST_I2C_CLASS.name,
ST_I2C_CLASS.version,
&Migrations::new(),
)
.unwrap();
other.load(&mut chunk.reader()).unwrap();
let mut w2 = StateWriter::new(shape);
{
let mut chunk = w2
.chunk("i2c", ST_I2C_CLASS.name, ST_I2C_CLASS.version)
.unwrap();
other.save(&mut chunk).unwrap();
}
assert_eq!(w2.to_vec().unwrap(), bytes, "an identical state hash");
let other_region = regs(&other);
for offset in [CR1, CR2, OAR1, DR, SR1, CCR, TRISE] {
assert_eq!(
peek_with(&other_region, offset, MemAttrs::DEBUG),
peek_with(&board.region, offset, MemAttrs::DEBUG),
"register {offset:#04x} differs after a round trip"
);
}
}
#[test]
fn a_reset_keeps_the_tick_and_drops_everything_else() {
let mut board = Board::new(Link::Wired);
board.init();
board.write_page(0x50, 0x00, &[0x01]);
let ticks = board.ctrl.ticks();
assert!(ticks > 0);
board.ctrl.reset(ResetKind::Cold);
assert_eq!(
board.ctrl.ticks(),
ticks,
"`Machine::reset` does not rewind clock domains"
);
assert_eq!(peek(&board.region, CR1), 0);
assert_eq!(peek(&board.region, SR1), 0);
assert_eq!(peek(&board.region, TRISE), 2);
}