use super::*;
use crate::core::sync::{AtomicU32, Ordering};
use crate::core::wire::{Level, Wire, WireId, WireIdAllocator, WireSink};
use crate::dev::ata::disk::{
self, Identity, Position, SECTOR, ST_DRDY, ST_DRQ, ST_DSC, ST_ERR, default_geometry,
};
use alloc::vec::Vec;
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
}
impl Probe {
fn high(&self) -> bool {
self.level.load(Ordering::Relaxed) != 0
}
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
}
}
fn stamped(position: Position) -> Arc<AtaDisk> {
let id = Identity::new(4096, default_geometry(4096), true, 16).expect("a valid drive");
let disk = AtaDisk::with_identity(id, position).expect("it fits in host memory");
for lba in 0..4096u64 {
disk.write_media(lba * SECTOR, &stamp(lba))
.expect("in range");
}
Arc::new(disk)
}
fn stamp(lba: u64) -> Vec<u8> {
let mut out = alloc::vec![0u8; SECTOR as usize];
for (i, byte) in out.iter_mut().enumerate() {
*byte = (lba as u8) ^ (i as u8) ^ 0x5a;
}
out[0] = lba as u8;
out[1] = (lba >> 8) as u8;
out
}
struct Rig {
ide: Ide,
cmd: CommandBlock,
ctl: ControlBlock,
irq: Arc<Probe>,
}
fn rig_with(master: bool, slave: bool) -> Rig {
let bays = [Arc::new(Bay::new()), Arc::new(Bay::new())];
if master {
bays[0]
.fit(stamped(Position::Device0))
.expect("an empty bay");
}
if slave {
bays[1]
.fit(stamped(Position::Device1))
.expect("an empty bay");
}
let ide = Ide::with_bays(
[Arc::clone(&bays[0]), Arc::clone(&bays[1])],
[String::from("ide0-master"), String::from("ide0-slave")],
);
let cmd = CommandBlock(Arc::clone(&ide.channel));
let ctl = ControlBlock(Arc::clone(&ide.channel));
let ids = WireIdAllocator::new();
let irq = Arc::new(Probe::default());
let id = ids.alloc();
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&irq) as Arc<dyn WireSink>, 0)
.build_shared();
ide.connect("irq", WireSource::new(wire, id))
.expect("the pin exists");
Rig { ide, cmd, ctl, irq }
}
fn rig() -> Rig {
rig_with(true, false)
}
impl Rig {
fn inb(&self, offset: u64) -> u8 {
let mut byte = [0u8; 1];
self.cmd
.read(offset, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn peekb(&self, offset: u64) -> u8 {
let mut byte = [0u8; 1];
self.cmd
.read(offset, &mut byte, MemAttrs::DEBUG)
.expect("a debug byte read is legal");
byte[0]
}
fn outb(&self, offset: u64, value: u8) {
self.cmd
.write(offset, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn inw(&self) -> u16 {
let mut word = [0u8; 2];
self.cmd
.read(0, &mut word, MemAttrs::DEFAULT)
.expect("a word read of the data port is legal");
u16::from_le_bytes(word)
}
fn outw(&self, value: u16) {
self.cmd
.write(0, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a word write of the data port is legal");
}
fn alt(&self) -> u8 {
let mut byte = [0u8; 1];
self.ctl
.read(0, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn devctl(&self, value: u8) {
self.ctl
.write(0, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn read_sector(&self, lba: u32) -> Vec<u8> {
self.outb(6, 0xe0 | ((lba >> 24) as u8 & 0x0f)); self.outb(2, 1); self.outb(3, lba as u8);
self.outb(4, (lba >> 8) as u8);
self.outb(5, (lba >> 16) as u8);
self.outb(7, disk::cmd::READ_SECTORS);
assert_ne!(self.alt() & ST_DRQ, 0, "the drive never raised DRQ");
let mut out = Vec::with_capacity(SECTOR as usize);
for _ in 0..256 {
out.extend_from_slice(&self.inw().to_le_bytes());
}
out
}
}
#[test]
fn the_eight_offsets_name_the_eight_registers() {
assert_eq!(register_at(0), Reg::Data);
assert_eq!(register_at(1), Reg::Feature);
assert_eq!(register_at(2), Reg::SectorCount);
assert_eq!(register_at(3), Reg::LbaLow);
assert_eq!(register_at(4), Reg::LbaMid);
assert_eq!(register_at(5), Reg::LbaHigh);
assert_eq!(register_at(6), Reg::Device);
assert_eq!(register_at(7), Reg::Command);
assert_eq!(register_at(15), Reg::Command);
}
#[test]
fn the_channel_publishes_both_of_its_windows_and_nothing_else() {
let rig = rig();
assert_eq!(
rig.ide.region("").expect("the default region").len(),
COMMAND_WINDOW_LEN
);
assert_eq!(
rig.ide.region("regs").expect("the command block").len(),
COMMAND_WINDOW_LEN
);
assert_eq!(
rig.ide.region("ctl").expect("the control block").len(),
CONTROL_WINDOW_LEN
);
assert!(rig.ide.region("data").is_none());
}
#[test]
fn an_empty_channel_reads_ones() {
let rig = rig_with(false, false);
for offset in 0..8 {
assert_eq!(rig.inb(offset), 0xff, "port offset {offset}");
}
assert_eq!(rig.alt(), 0xff);
assert!(!rig.irq.high());
}
#[test]
fn a_lone_master_answers_for_the_slave_that_is_not_there() {
let rig = rig();
assert_eq!(rig.inb(7), ST_DRDY | ST_DSC, "the master is there");
rig.outb(6, 0xf0); assert_eq!(rig.inb(7), 0x00, "and device 1 is not");
assert_eq!(rig.alt(), 0x00);
for offset in 1..7 {
assert_eq!(rig.inb(offset), 0x00, "port offset {offset}");
}
rig.outb(6, 0xe0);
assert_eq!(rig.inb(7), ST_DRDY | ST_DSC);
}
#[test]
fn selecting_device_1_switches_which_drive_answers() {
let rig = rig_with(true, true);
rig.outb(6, 0xe0);
rig.outb(2, 0x11);
rig.outb(6, 0xf0);
rig.outb(2, 0x22);
assert_eq!(rig.inb(2), 0x22, "device 1 kept its own sector count");
rig.outb(6, 0xe0);
assert_eq!(rig.inb(2), 0x11, "and device 0 kept its own");
}
#[test]
fn a_sector_reaches_the_host_a_word_at_a_time() {
let rig = rig();
assert_eq!(rig.read_sector(1234), stamp(1234));
assert_eq!(rig.alt(), ST_DRDY | ST_DSC, "and the command is over");
}
#[test]
fn a_sector_written_through_the_ports_lands_on_the_medium() {
let rig = rig();
let payload = stamp(0x321);
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 0x21);
rig.outb(4, 0x03);
rig.outb(5, 0x00);
rig.outb(7, disk::cmd::WRITE_SECTORS);
assert_ne!(rig.alt() & ST_DRQ, 0, "the drive is asking for the block");
for pair in payload.chunks(2) {
rig.outw(u16::from(pair[0]) | (u16::from(pair[1]) << 8));
}
assert_eq!(rig.inb(7) & ST_ERR, 0);
let drive = rig
.ide
.drive(Position::Device0)
.expect("the master is fitted");
let mut got = alloc::vec![0u8; SECTOR as usize];
drive
.read_media(0x321 * SECTOR, &mut got)
.expect("in range");
assert_eq!(got, payload);
assert_eq!(rig.read_sector(0x321), payload, "and it reads back");
}
#[test]
fn a_byte_wide_read_of_the_data_port_still_shifts_a_whole_word() {
let rig = rig();
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 0);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
let expected = stamp(0);
assert_eq!(rig.inb(0), expected[0], "the low half of the first word");
assert_eq!(rig.inb(0), expected[2], "and then the low half of the next");
}
#[test]
fn a_wide_access_to_a_task_file_register_is_refused() {
let rig = rig();
let mut two = [0u8; 2];
assert!(rig.cmd.read(2, &mut two, MemAttrs::DEFAULT).is_err());
assert!(rig.cmd.write(2, &two, MemAttrs::DEFAULT).is_err());
let mut four = [0u8; 4];
assert!(rig.cmd.read(4, &mut four, MemAttrs::DEFAULT).is_err());
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 5);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
rig.cmd
.read(0, &mut four, MemAttrs::DEFAULT)
.expect("a doubleword at offset zero");
assert_eq!(four, stamp(5)[..4]);
}
#[test]
fn the_status_port_acknowledges_and_the_alternate_status_port_does_not() {
let rig = rig();
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 9);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
assert!(rig.irq.high(), "INTRQ reached the pin");
for _ in 0..4 {
assert_ne!(rig.alt() & ST_DRQ, 0);
assert!(
rig.irq.high(),
"the alternate status acknowledged something"
);
}
let _ = rig.inb(7);
assert!(!rig.irq.high(), "the status register did not acknowledge");
}
#[test]
fn a_debug_read_acknowledges_nothing_and_advances_nothing() {
let rig = rig();
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 11);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
assert!(rig.irq.high());
for _ in 0..4 {
assert_eq!(rig.peekb(7), ST_DRDY | ST_DSC | ST_DRQ);
assert!(rig.irq.high(), "a debug read of 0x1f7 acknowledged it");
}
let mut a = [0u8; 2];
let mut b = [0u8; 2];
rig.cmd.read(0, &mut a, MemAttrs::DEBUG).expect("legal");
rig.cmd.read(0, &mut b, MemAttrs::DEBUG).expect("legal");
assert_eq!(a, b, "a debug read of 0x1f0 advanced the buffer");
let mut out = Vec::new();
for _ in 0..256 {
out.extend_from_slice(&rig.inw().to_le_bytes());
}
assert_eq!(out, stamp(11));
}
#[test]
fn a_debug_write_is_refused_in_both_blocks() {
let rig = rig();
for offset in 0..8 {
assert!(
rig.cmd.write(offset, &[0x00], MemAttrs::DEBUG).is_err(),
"a debug write to command block offset {offset} was accepted"
);
}
assert!(rig.ctl.write(0, &[0x00], MemAttrs::DEBUG).is_err());
}
#[test]
fn the_interrupt_pin_follows_nien_and_the_selected_drive() {
let rig = rig_with(true, true);
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 3);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
assert!(rig.irq.high());
rig.outb(6, 0xf0);
assert!(!rig.irq.high());
rig.outb(6, 0xe0);
assert!(rig.irq.high(), "and coming back brings it up again");
rig.devctl(disk::CTL_NIEN);
assert!(!rig.irq.high());
rig.devctl(0);
assert!(rig.irq.high());
}
#[test]
fn a_reset_through_the_control_block_reaches_both_drives() {
let rig = rig_with(true, true);
rig.outb(6, 0xe0);
rig.outb(2, 0x33);
rig.outb(6, 0xf0);
rig.outb(2, 0x44);
rig.devctl(disk::CTL_SRST);
rig.devctl(0);
assert_eq!(rig.inb(2), 0x01, "device 1 did not take the reset");
rig.outb(6, 0xe0);
assert_eq!(rig.inb(2), 0x01, "device 0 did not take the reset");
assert!(!rig.irq.high(), "and a software reset raises no interrupt");
}
#[test]
fn a_channel_reset_re_drives_the_pin() {
let rig = rig();
rig.outb(6, 0xe0);
rig.outb(2, 1);
rig.outb(3, 2);
rig.outb(4, 0);
rig.outb(5, 0);
rig.outb(7, disk::cmd::READ_SECTORS);
assert!(rig.irq.high());
rig.ide
.drive(Position::Device0)
.expect("a master")
.power_on_reset();
rig.ide.reset(ResetKind::Cold);
assert!(!rig.irq.high(), "a reset left a stale level on the pin");
}
#[test]
fn a_channel_will_not_take_one_bay_twice() {
let mut props = Props::new();
props.insert("master", "ata0");
props.insert("slave", "ata0");
assert!(
Ide::new(&props).is_err(),
"two positions on one cable cannot be the same bay"
);
}
#[test]
fn the_default_bays_are_the_two_this_module_documents() {
let ide = Ide::new(&Props::new()).expect("no property is required");
assert_eq!(ide.bay_names(), [DEFAULT_MASTER_BAY, DEFAULT_SLAVE_BAY]);
assert!(ide.drive(Position::Device0).is_none(), "and both are empty");
}