use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::RamStore;
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::dev::ata::medium::{self, Medium, Snapshot};
use crate::machine::realize::Instance;
use crate::machine::validate::{ClassSchema, PropSchema};
pub mod taskfile;
pub const CLASS_NAME: &str = "ata.disk";
const STATE_VERSION: u32 = 2;
pub const SECTOR: u64 = 512;
pub const LBA28_LIMIT: u64 = 1 << 28;
pub const LBA48_LIMIT: u64 = 1 << 48;
pub const MAX_IDENTIFY_CYLINDERS: u64 = 16383;
pub const ST_BSY: u8 = 0x80;
pub const ST_DRDY: u8 = 0x40;
pub const ST_DF: u8 = 0x20;
pub const ST_DSC: u8 = 0x10;
pub const ST_DRQ: u8 = 0x08;
pub const ST_ERR: u8 = 0x01;
const ST_IDLE: u8 = ST_DRDY | ST_DSC;
pub const ERR_UNC: u8 = 0x40;
pub const ERR_IDNF: u8 = 0x10;
pub const ERR_ABRT: u8 = 0x04;
pub const ERR_TK0NF: u8 = 0x02;
const DIAGNOSTIC_PASSED: u8 = 0x01;
pub const DEV_SELECT: u8 = 0x10;
pub const DEV_LBA: u8 = 0x40;
pub const DEV_HEAD: u8 = 0x0f;
const DEV_OBSOLETE: u8 = 0xa0;
pub const CTL_HOB: u8 = 0x80;
pub const CTL_SRST: u8 = 0x04;
pub const CTL_NIEN: u8 = 0x02;
pub mod cmd {
pub const NOP: u8 = 0x00;
pub const RECALIBRATE: u8 = 0x10;
pub const READ_SECTORS: u8 = 0x20;
pub const READ_SECTORS_NORETRY: u8 = 0x21;
pub const READ_SECTORS_EXT: u8 = 0x24;
pub const READ_DMA_EXT: u8 = 0x25;
pub const READ_NATIVE_MAX_EXT: u8 = 0x27;
pub const READ_MULTIPLE_EXT: u8 = 0x29;
pub const WRITE_SECTORS: u8 = 0x30;
pub const WRITE_SECTORS_NORETRY: u8 = 0x31;
pub const WRITE_SECTORS_EXT: u8 = 0x34;
pub const WRITE_DMA_EXT: u8 = 0x35;
pub const WRITE_MULTIPLE_EXT: u8 = 0x39;
pub const VERIFY_SECTORS: u8 = 0x40;
pub const VERIFY_SECTORS_NORETRY: u8 = 0x41;
pub const VERIFY_SECTORS_EXT: u8 = 0x42;
pub const SEEK: u8 = 0x70;
pub const DIAGNOSTIC: u8 = 0x90;
pub const INIT_DEVICE_PARAMS: u8 = 0x91;
pub const IDENTIFY_PACKET: u8 = 0xa1;
pub const READ_MULTIPLE: u8 = 0xc4;
pub const WRITE_MULTIPLE: u8 = 0xc5;
pub const SET_MULTIPLE: u8 = 0xc6;
pub const READ_DMA: u8 = 0xc8;
pub const READ_DMA_NORETRY: u8 = 0xc9;
pub const WRITE_DMA: u8 = 0xca;
pub const WRITE_DMA_NORETRY: u8 = 0xcb;
pub const STANDBY_IMMEDIATE: u8 = 0xe0;
pub const IDLE_IMMEDIATE: u8 = 0xe1;
pub const STANDBY: u8 = 0xe2;
pub const IDLE: u8 = 0xe3;
pub const CHECK_POWER_MODE: u8 = 0xe5;
pub const SLEEP: u8 = 0xe6;
pub const FLUSH_CACHE: u8 = 0xe7;
pub const FLUSH_CACHE_EXT: u8 = 0xea;
pub const IDENTIFY: u8 = 0xec;
pub const SET_FEATURES: u8 = 0xef;
pub const READ_NATIVE_MAX: u8 = 0xf8;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Position {
#[default]
Device0,
Device1,
}
impl Position {
#[must_use]
fn dev_bit(self) -> bool {
self == Position::Device1
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Position::Device0 => "master",
Position::Device1 => "slave",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reg {
Data,
Feature,
SectorCount,
LbaLow,
LbaMid,
LbaHigh,
Device,
Command,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Geometry {
pub cylinders: u16,
pub heads: u8,
pub sectors: u8,
}
impl Geometry {
#[must_use]
pub fn addressable(&self) -> u64 {
u64::from(self.cylinders) * u64::from(self.heads) * u64::from(self.sectors)
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.cylinders > 0 && self.heads > 0 && self.sectors > 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Address {
Chs {
cylinder: u16,
head: u8,
sector: u8,
},
Lba28(u32),
Lba48(u64),
}
impl Address {
#[must_use]
pub fn to_lba(self, geometry: &Geometry) -> Option<u64> {
match self {
Address::Chs {
cylinder,
head,
sector,
} => {
if !geometry.is_valid()
|| sector == 0
|| sector > geometry.sectors
|| head >= geometry.heads
{
return None;
}
let heads = u64::from(geometry.heads);
let spt = u64::from(geometry.sectors);
Some((u64::from(cylinder) * heads + u64::from(head)) * spt + u64::from(sector) - 1)
}
Address::Lba28(lba) => Some(u64::from(lba)),
Address::Lba48(lba) => Some(lba),
}
}
#[must_use]
pub fn from_lba(lba: u64, geometry: &Geometry) -> Option<Address> {
if !geometry.is_valid() {
return None;
}
let heads = u64::from(geometry.heads);
let spt = u64::from(geometry.sectors);
let sector = lba % spt + 1;
let track = lba / spt;
let head = track % heads;
let cylinder = track / heads;
if cylinder > u64::from(u16::MAX) {
return None;
}
Some(Address::Chs {
cylinder: cylinder as u16,
head: head as u8,
sector: sector as u8,
})
}
}
#[derive(Debug, Clone)]
pub struct Identity {
pub sectors: u64,
pub geometry: Geometry,
pub model: String,
pub serial: String,
pub firmware: String,
pub read_only: bool,
pub lba48: bool,
pub dma: bool,
pub max_multiple: u8,
}
impl Identity {
pub fn new(
sectors: u64,
geometry: Geometry,
lba48: bool,
max_multiple: u8,
) -> Result<Identity> {
if sectors == 0 {
return Err(config(String::from("a drive holds at least one sector")));
}
if !lba48 && sectors > LBA28_LIMIT {
return Err(config(format!(
"{sectors} sector(s) needs 48-bit addressing, which this drive has turned off"
)));
}
if sectors > LBA48_LIMIT {
return Err(config(format!(
"{sectors} sector(s) is more than 48-bit addressing can name"
)));
}
if !geometry.is_valid() || geometry.heads > 16 {
return Err(config(format!(
"{}/{}/{} is not a translation the Device register can express",
geometry.cylinders, geometry.heads, geometry.sectors
)));
}
if max_multiple == 0 || !max_multiple.is_power_of_two() {
return Err(config(format!(
"`multiple` is a power of two block size in sectors, and {max_multiple} is not one"
)));
}
Ok(Identity {
sectors,
geometry,
model: String::from("RSEMU HARDDISK"),
serial: String::from("RSEMU00000000000001"),
firmware: String::from("1.0"),
read_only: false,
lba48,
dma: false,
max_multiple,
})
}
#[must_use]
pub fn capacity(&self) -> u64 {
self.sectors * SECTOR
}
}
#[must_use]
pub fn default_geometry(sectors: u64) -> Geometry {
let mut heads: u64 = 16;
let mut spt: u64 = 63;
while heads > 1 && sectors < heads * spt {
heads /= 2;
}
while spt > 1 && sectors < heads * spt {
spt -= 1;
}
let cylinders = (sectors / (heads * spt)).clamp(1, MAX_IDENTIFY_CYLINDERS);
Geometry {
cylinders: cylinders as u16,
heads: heads as u8,
sectors: spt as u8,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Fifo {
current: u8,
previous: u8,
}
impl Fifo {
fn write(&mut self, value: u8) {
self.previous = self.current;
self.current = value;
}
fn read(&self, hob: bool) -> u8 {
if hob { self.previous } else { self.current }
}
fn load(&mut self, current: u8, previous: u8) {
self.current = current;
self.previous = previous;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Chs,
Lba28,
Lba48,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Transfer {
out: bool,
next: u64,
left: u64,
block: u32,
dma: bool,
mode: Mode,
last: u64,
}
#[derive(Debug)]
struct Volatile {
selected: bool,
device: u8,
error: u8,
status: u8,
control: u8,
features: Fifo,
count: Fifo,
lba_low: Fifo,
lba_mid: Fifo,
lba_high: Fifo,
irq: bool,
in_reset: bool,
multiple: u8,
current: Geometry,
buf: Vec<u8>,
pos: usize,
xfer: Option<Transfer>,
}
impl Volatile {
fn power_on(position: Position, geometry: Geometry) -> Volatile {
let mut state = Volatile {
selected: position == Position::Device0,
device: 0,
error: DIAGNOSTIC_PASSED,
status: ST_IDLE,
control: 0,
features: Fifo::default(),
count: Fifo::default(),
lba_low: Fifo::default(),
lba_mid: Fifo::default(),
lba_high: Fifo::default(),
irq: false,
in_reset: false,
multiple: 0,
current: geometry,
buf: Vec::new(),
pos: 0,
xfer: None,
};
state.signature();
state
}
fn hob(&self) -> bool {
self.control & CTL_HOB != 0
}
fn signature(&mut self) {
self.error = DIAGNOSTIC_PASSED;
self.count.load(1, 0);
self.lba_low.load(1, 0);
self.lba_mid.load(0, 0);
self.lba_high.load(0, 0);
self.device &= DEV_SELECT;
self.status = ST_IDLE;
self.buf.clear();
self.pos = 0;
self.xfer = None;
}
}
pub struct AtaDisk {
id: Identity,
position: Position,
media: Arc<dyn Medium>,
state: Mutex<Volatile>,
}
impl fmt::Debug for AtaDisk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AtaDisk")
.field("sectors", &self.id.sectors)
.field("geometry", &self.id.geometry)
.field("position", &self.position)
.field("read_only", &self.id.read_only)
.field("medium", &self.media)
.finish_non_exhaustive()
}
}
impl AtaDisk {
pub fn new(props: &Props) -> Result<Option<AtaDisk>> {
let mut r = props.reader();
let size = r.or_size("size", 0)?;
let media = r.optional_media("image")?;
let slot = media.map(crate::core::props::Media::name);
let image = media.map(crate::core::props::Media::to_bytes);
let read_only = r.or("readonly", false)?;
let lba48 = r.or("lba48", true)?;
let dma = r.or("dma", false)?;
let max_multiple = r.or_range("multiple", 16u64, 1..=128)? as u8;
let position = match r.or_enum("position", "master", &["master", "slave"])? {
"slave" => Position::Device1,
_ => Position::Device0,
};
let cylinders = r.optional::<u64>("cylinders")?;
let heads = r.optional::<u64>("heads")?;
let sectors = r.optional::<u64>("sectors")?;
let model = r.or_str("model", "RSEMU HARDDISK")?.to_string();
let serial = r.or_str("serial", "RSEMU0000000000000001")?.to_string();
let firmware = r.or_str("firmware", "1.0")?.to_string();
let bay = r.optional_str("bay")?;
r.finish()?;
let supplied = match props.hosts() {
Some(hosts) => {
let name = slot.unwrap_or_else(|| bay.unwrap_or(super::DEFAULT_BAY));
medium::get(hosts, name)?.and_then(|slot| slot.take())
}
None => None,
};
let bytes = match (&supplied, size, image.as_ref()) {
(Some(medium), _, _) => medium.capacity(),
(None, 0, Some(image)) => image.len() as u64,
(None, size, _) => size,
};
if bytes == 0 {
return Ok(None);
}
if !bytes.is_multiple_of(SECTOR) {
return Err(config(format!(
"a drive holds a whole number of {SECTOR}-byte sectors, and {bytes} bytes is not \
a whole number of them"
)));
}
let total = bytes / SECTOR;
let geometry = match (cylinders, heads, sectors) {
(None, None, None) => default_geometry(total),
(Some(c), Some(h), Some(s)) => {
if c == 0
|| c > u64::from(u16::MAX)
|| !(1..=16).contains(&h)
|| !(1..=255).contains(&s)
{
return Err(config(format!(
"{c}/{h}/{s} is not a translation an ATA drive can report"
)));
}
Geometry {
cylinders: c as u16,
heads: h as u8,
sectors: s as u8,
}
}
_ => {
return Err(config(String::from(
"`cylinders`, `heads` and `sectors` come as a set: give all three or none",
)));
}
};
let mut id = Identity::new(total, geometry, lba48, max_multiple)?;
id.read_only = read_only || supplied.as_ref().is_some_and(|m| m.is_read_only());
id.dma = dma;
id.model = model;
id.serial = serial;
id.firmware = firmware;
if let Some(supplied) = supplied {
return AtaDisk::with_medium(id, position, supplied).map(Some);
}
let disk = AtaDisk::with_identity(id, position)?;
if let Some(image) = image {
if image.len() as u64 > bytes {
return Err(config(format!(
"the bound image is {} byte(s) and the drive holds {bytes}",
image.len()
)));
}
disk.load_image(0, &image)?;
}
Ok(Some(disk))
}
pub fn with_identity(id: Identity, position: Position) -> Result<AtaDisk> {
let bytes = id.capacity();
if usize::try_from(bytes).is_err() {
return Err(config(format!(
"a drive of {bytes} byte(s) is larger than this host's address space"
)));
}
AtaDisk::with_medium(id, position, Arc::new(RamStore::new(bytes)))
}
pub fn with_medium(
id: Identity,
position: Position,
media: Arc<dyn Medium>,
) -> Result<AtaDisk> {
let bytes = id.capacity();
if media.capacity() != bytes {
return Err(config(format!(
"the drive holds {bytes} byte(s) and the medium holds {}",
media.capacity()
)));
}
let geometry = id.geometry;
Ok(AtaDisk {
id,
position,
media,
state: Mutex::with_rank(LockRank::DEVICE, Volatile::power_on(position, geometry)),
})
}
#[must_use]
pub fn identity(&self) -> &Identity {
&self.id
}
#[must_use]
pub fn position(&self) -> Position {
self.position
}
#[must_use]
pub fn is_selected(&self) -> bool {
self.state.lock().selected
}
#[must_use]
pub fn irq_asserted(&self) -> bool {
let state = self.state.lock();
state.irq && state.control & CTL_NIEN == 0
}
#[must_use]
pub fn read_alt_status(&self) -> u8 {
self.state.lock().status
}
#[must_use]
pub fn current_geometry(&self) -> Geometry {
self.state.lock().current
}
#[must_use]
pub fn multiple(&self) -> u8 {
self.state.lock().multiple
}
pub fn read_media(&self, offset: u64, dst: &mut [u8]) -> Result<()> {
self.media
.read_at(offset, dst)
.map_err(|e| medium::error_at(offset, e))
}
#[must_use]
pub fn medium(&self) -> &Arc<dyn Medium> {
&self.media
}
pub fn flush_media(&self) -> Result<()> {
self.media.flush().map_err(|e| medium::error_at(0, e))
}
pub fn write_media(&self, offset: u64, src: &[u8]) -> Result<()> {
self.media
.write_at(offset, src)
.map_err(|e| medium::error_at(offset, e))
}
pub fn load_image(&self, offset: u64, bytes: &[u8]) -> Result<()> {
self.media.write_at(offset, bytes).map_err(|_| {
config(format!(
"an image of {} byte(s) at {offset:#x} does not fit on a drive of {}",
bytes.len(),
self.id.capacity()
))
})
}
pub fn contents(&self) -> Result<Vec<u8>> {
let mut out = alloc::vec![0u8; self.id.capacity() as usize];
self.media
.read_at(0, &mut out)
.map_err(|e| medium::error_at(0, e))?;
Ok(out)
}
pub fn power_on_reset(&self) {
let mut state = self.state.lock();
*state = Volatile::power_on(self.position, self.id.geometry);
}
pub fn write_reg(&self, reg: Reg, value: u16) {
let mut state = self.state.lock();
let byte = value as u8;
if reg == Reg::Device {
state.device = byte;
state.selected = (byte & DEV_SELECT != 0) == self.position.dev_bit();
return;
}
if !state.selected {
return;
}
if state.in_reset {
return;
}
match reg {
Reg::Data => self.write_data(&mut state, value),
Reg::Feature => state.features.write(byte),
Reg::SectorCount => state.count.write(byte),
Reg::LbaLow => state.lba_low.write(byte),
Reg::LbaMid => state.lba_mid.write(byte),
Reg::LbaHigh => state.lba_high.write(byte),
Reg::Command => self.command(&mut state, byte),
Reg::Device => unreachable!("handled above"),
}
}
pub fn read_reg(&self, reg: Reg, debug: bool) -> u16 {
let mut state = self.state.lock();
let hob = state.hob();
match reg {
Reg::Data => self.read_data(&mut state, debug),
Reg::Feature => u16::from(state.error),
Reg::SectorCount => u16::from(state.count.read(hob)),
Reg::LbaLow => u16::from(state.lba_low.read(hob)),
Reg::LbaMid => u16::from(state.lba_mid.read(hob)),
Reg::LbaHigh => u16::from(state.lba_high.read(hob)),
Reg::Device => u16::from(state.device | DEV_OBSOLETE),
Reg::Command => {
if !debug {
state.irq = false;
}
u16::from(state.status)
}
}
}
pub fn write_device_control(&self, value: u8) {
let mut state = self.state.lock();
let was = state.control;
state.control = value;
match (was & CTL_SRST != 0, value & CTL_SRST != 0) {
(false, true) => {
state.in_reset = true;
state.status = ST_BSY;
state.irq = false;
}
(true, false) => {
state.in_reset = false;
state.signature();
state.irq = false;
}
_ => {}
}
}
fn read_data(&self, state: &mut Volatile, debug: bool) -> u16 {
if state.status & ST_DRQ == 0 {
return 0;
}
let at = state.pos;
if at >= state.buf.len() {
return 0;
}
let lo = u16::from(state.buf[at]);
let hi = u16::from(state.buf.get(at + 1).copied().unwrap_or(0));
let word = lo | (hi << 8);
if debug {
return word;
}
state.pos = at + 2;
if state.pos >= state.buf.len() {
self.block_consumed(state);
}
word
}
fn write_data(&self, state: &mut Volatile, value: u16) {
if state.status & ST_DRQ == 0 {
return;
}
let Some(xfer) = state.xfer.as_ref() else {
return;
};
if !xfer.out {
return;
}
let at = state.pos;
if at >= state.buf.len() {
return;
}
state.buf[at] = value as u8;
if at + 1 < state.buf.len() {
state.buf[at + 1] = (value >> 8) as u8;
}
state.pos = at + 2;
if state.pos >= state.buf.len() {
self.block_filled(state);
}
}
fn block_consumed(&self, state: &mut Volatile) {
state.status &= !ST_DRQ;
let Some(xfer) = state.xfer.clone() else {
state.buf.clear();
state.pos = 0;
return;
};
if xfer.left == 0 {
self.complete(state, xfer.last, xfer.mode);
return;
}
self.fill_block(state);
}
fn block_filled(&self, state: &mut Volatile) {
let Some(mut xfer) = state.xfer.clone() else {
return;
};
let count = (state.buf.len() as u64) / SECTOR;
let wrote = self.media.write_at(xfer.next * SECTOR, &state.buf[..]);
state.status &= !ST_DRQ;
if let Err(e) = wrote {
self.fail(state, medium::error_bit(e), xfer.next, xfer.mode);
return;
}
xfer.last = xfer.next + count - 1;
xfer.next += count;
state.xfer = Some(xfer.clone());
if xfer.left == 0 {
self.complete(state, xfer.last, xfer.mode);
} else {
self.open_block(state);
}
state.irq = true;
}
fn fill_block(&self, state: &mut Volatile) {
let Some(mut xfer) = state.xfer.clone() else {
return;
};
let count = u64::from(xfer.block).min(xfer.left);
let mut buf = alloc::vec![0u8; (count * SECTOR) as usize];
if let Err(e) = self.media.read_at(xfer.next * SECTOR, &mut buf) {
self.fail(state, medium::error_bit(e), xfer.next, xfer.mode);
return;
}
xfer.last = xfer.next + count - 1;
xfer.next += count;
xfer.left -= count;
state.xfer = Some(xfer);
state.buf = buf;
state.pos = 0;
state.status = ST_IDLE | ST_DRQ;
state.irq = true;
}
fn open_block(&self, state: &mut Volatile) {
let Some(mut xfer) = state.xfer.clone() else {
return;
};
let count = u64::from(xfer.block).min(xfer.left);
xfer.left -= count;
state.xfer = Some(xfer);
state.buf = alloc::vec![0u8; (count * SECTOR) as usize];
state.pos = 0;
state.status = ST_IDLE | ST_DRQ;
}
fn one_block(&self, state: &mut Volatile, bytes: Vec<u8>) {
state.xfer = None;
state.buf = bytes;
state.pos = 0;
state.status = ST_IDLE | ST_DRQ;
state.error = 0;
state.irq = true;
}
fn complete(&self, state: &mut Volatile, last: u64, mode: Mode) {
state.status = ST_IDLE;
state.error = 0;
state.xfer = None;
state.buf.clear();
state.pos = 0;
state.count.load(0, 0);
self.store_address(state, last, mode);
state.irq = true;
}
fn fail(&self, state: &mut Volatile, error: u8, at: u64, mode: Mode) {
state.status = ST_IDLE | ST_ERR;
state.error = error;
state.xfer = None;
state.buf.clear();
state.pos = 0;
self.store_address(state, at, mode);
state.irq = true;
}
fn abort(&self, state: &mut Volatile) {
state.status = ST_IDLE | ST_ERR;
state.error = ERR_ABRT;
state.xfer = None;
state.buf.clear();
state.pos = 0;
state.irq = true;
}
fn succeed(&self, state: &mut Volatile) {
state.status = ST_IDLE;
state.error = 0;
state.irq = true;
}
fn store_address(&self, state: &mut Volatile, lba: u64, mode: Mode) {
match mode {
Mode::Chs => {
if let Some(Address::Chs {
cylinder,
head,
sector,
}) = Address::from_lba(lba, &state.current)
{
state.lba_low.load(sector, 0);
state.lba_mid.load(cylinder as u8, 0);
state.lba_high.load((cylinder >> 8) as u8, 0);
state.device = (state.device & !DEV_HEAD) | (head & DEV_HEAD);
}
}
Mode::Lba28 => {
state.lba_low.load(lba as u8, 0);
state.lba_mid.load((lba >> 8) as u8, 0);
state.lba_high.load((lba >> 16) as u8, 0);
state.device = (state.device & !DEV_HEAD) | ((lba >> 24) as u8 & DEV_HEAD);
}
Mode::Lba48 => {
state.lba_low.load(lba as u8, (lba >> 24) as u8);
state.lba_mid.load((lba >> 8) as u8, (lba >> 32) as u8);
state.lba_high.load((lba >> 16) as u8, (lba >> 40) as u8);
}
}
}
fn mode_of(state: &Volatile, ext: bool) -> Mode {
if ext {
Mode::Lba48
} else if state.device & DEV_LBA != 0 {
Mode::Lba28
} else {
Mode::Chs
}
}
fn address(state: &Volatile, mode: Mode) -> Address {
match mode {
Mode::Chs => Address::Chs {
cylinder: u16::from(state.lba_mid.current)
| (u16::from(state.lba_high.current) << 8),
head: state.device & DEV_HEAD,
sector: state.lba_low.current,
},
Mode::Lba28 => Address::Lba28(
u32::from(state.lba_low.current)
| (u32::from(state.lba_mid.current) << 8)
| (u32::from(state.lba_high.current) << 16)
| (u32::from(state.device & DEV_HEAD) << 24),
),
Mode::Lba48 => Address::Lba48(
u64::from(state.lba_low.current)
| (u64::from(state.lba_mid.current) << 8)
| (u64::from(state.lba_high.current) << 16)
| (u64::from(state.lba_low.previous) << 24)
| (u64::from(state.lba_mid.previous) << 32)
| (u64::from(state.lba_high.previous) << 40),
),
}
}
fn count_of(state: &Volatile, mode: Mode) -> u64 {
if mode == Mode::Lba48 {
let n = u64::from(state.count.current) | (u64::from(state.count.previous) << 8);
if n == 0 { 65536 } else { n }
} else {
let n = u64::from(state.count.current);
if n == 0 { 256 } else { n }
}
}
fn command(&self, state: &mut Volatile, opcode: u8) {
state.irq = false;
state.status = ST_IDLE;
state.error = 0;
state.xfer = None;
state.buf.clear();
state.pos = 0;
let family = opcode & 0xf0;
match opcode {
cmd::IDENTIFY => {
let block = self.identify_block(state);
self.one_block(state, block);
}
cmd::IDENTIFY_PACKET => {
self.abort(state);
}
cmd::READ_SECTORS | cmd::READ_SECTORS_NORETRY => {
self.transfer(state, false, false, 1, false);
}
cmd::READ_SECTORS_EXT => self.transfer(state, false, true, 1, false),
cmd::WRITE_SECTORS | cmd::WRITE_SECTORS_NORETRY => {
self.transfer(state, true, false, 1, false);
}
cmd::WRITE_SECTORS_EXT => self.transfer(state, true, true, 1, false),
cmd::READ_DMA | cmd::READ_DMA_NORETRY => self.dma_transfer(state, false, false),
cmd::READ_DMA_EXT => self.dma_transfer(state, false, true),
cmd::WRITE_DMA | cmd::WRITE_DMA_NORETRY => self.dma_transfer(state, true, false),
cmd::WRITE_DMA_EXT => self.dma_transfer(state, true, true),
cmd::READ_MULTIPLE => self.multiple_transfer(state, false, false),
cmd::READ_MULTIPLE_EXT => self.multiple_transfer(state, false, true),
cmd::WRITE_MULTIPLE => self.multiple_transfer(state, true, false),
cmd::WRITE_MULTIPLE_EXT => self.multiple_transfer(state, true, true),
cmd::VERIFY_SECTORS | cmd::VERIFY_SECTORS_NORETRY => self.verify(state, false),
cmd::VERIFY_SECTORS_EXT => self.verify(state, true),
cmd::SET_MULTIPLE => self.set_multiple(state),
cmd::INIT_DEVICE_PARAMS => self.init_device_params(state),
cmd::DIAGNOSTIC => {
state.signature();
state.irq = true;
}
cmd::FLUSH_CACHE | cmd::FLUSH_CACHE_EXT => {
match self.media.flush() {
Ok(()) => self.succeed(state),
Err(e) => {
let last = self.id.sectors - 1;
self.fail(state, medium::error_bit(e), last, Mode::Lba28);
}
}
}
cmd::SET_FEATURES => self.set_features(state),
cmd::READ_NATIVE_MAX => {
let last = self.id.sectors - 1;
self.succeed(state);
self.store_address(state, last, Mode::Lba28);
state.device |= DEV_LBA;
}
cmd::READ_NATIVE_MAX_EXT => {
let last = self.id.sectors - 1;
self.succeed(state);
self.store_address(state, last, Mode::Lba48);
state.device |= DEV_LBA;
}
cmd::STANDBY_IMMEDIATE
| cmd::IDLE_IMMEDIATE
| cmd::STANDBY
| cmd::IDLE
| cmd::SLEEP => self.succeed(state),
cmd::CHECK_POWER_MODE => {
state.count.load(0xff, 0);
self.succeed(state);
}
cmd::NOP => {
self.abort(state);
}
_ if family == cmd::RECALIBRATE => {
self.succeed(state);
let mode = Self::mode_of(state, false);
self.store_address(state, 0, mode);
}
_ if family == cmd::SEEK => self.seek(state),
_ => self.abort(state),
}
}
fn dma_transfer(&self, state: &mut Volatile, out: bool, ext: bool) {
if !self.id.dma {
self.abort(state);
return;
}
self.transfer(state, out, ext, 1, true);
}
fn transfer(&self, state: &mut Volatile, out: bool, ext: bool, block: u32, dma: bool) {
if ext && !self.id.lba48 {
self.abort(state);
return;
}
if out && self.id.read_only {
self.abort(state);
return;
}
let mode = Self::mode_of(state, ext);
let count = Self::count_of(state, mode);
let Some(lba) = Self::address(state, mode).to_lba(&state.current) else {
self.no_such_address(state, ERR_IDNF);
return;
};
if lba >= self.id.sectors || count > self.id.sectors - lba {
self.fail(state, ERR_IDNF, lba.min(self.id.sectors - 1), mode);
return;
}
state.xfer = Some(Transfer {
out,
next: lba,
left: count,
block,
dma,
mode,
last: lba,
});
if out {
self.open_block(state);
} else {
self.fill_block(state);
}
}
fn multiple_transfer(&self, state: &mut Volatile, out: bool, ext: bool) {
let block = state.multiple;
if block == 0 {
self.abort(state);
return;
}
self.transfer(state, out, ext, u32::from(block), false);
}
fn verify(&self, state: &mut Volatile, ext: bool) {
if ext && !self.id.lba48 {
self.abort(state);
return;
}
let mode = Self::mode_of(state, ext);
let count = Self::count_of(state, mode);
let Some(lba) = Self::address(state, mode).to_lba(&state.current) else {
self.no_such_address(state, ERR_IDNF);
return;
};
if lba >= self.id.sectors || count > self.id.sectors - lba {
self.fail(state, ERR_IDNF, lba.min(self.id.sectors - 1), mode);
return;
}
let last = lba + count - 1;
self.complete(state, last, mode);
}
fn seek(&self, state: &mut Volatile) {
let mode = Self::mode_of(state, false);
let Some(lba) = Self::address(state, mode).to_lba(&state.current) else {
self.no_such_address(state, ERR_IDNF);
return;
};
if lba >= self.id.sectors {
self.fail(state, ERR_IDNF, self.id.sectors - 1, mode);
return;
}
self.succeed(state);
}
fn set_multiple(&self, state: &mut Volatile) {
let requested = state.count.current;
if requested == 0 || !requested.is_power_of_two() || requested > self.id.max_multiple {
self.abort(state);
return;
}
state.multiple = requested;
self.succeed(state);
}
fn init_device_params(&self, state: &mut Volatile) {
let sectors = state.count.current;
let heads = (state.device & DEV_HEAD) + 1;
if sectors == 0 {
self.abort(state);
return;
}
let per_cylinder = u64::from(heads) * u64::from(sectors);
let cylinders = (self.id.sectors / per_cylinder).min(u64::from(u16::MAX));
state.current = Geometry {
cylinders: cylinders as u16,
heads,
sectors,
};
self.succeed(state);
}
fn set_features(&self, state: &mut Volatile) {
const SET_TRANSFER_MODE: u8 = 0x03;
const ENABLE_WRITE_CACHE: u8 = 0x02;
const DISABLE_WRITE_CACHE: u8 = 0x82;
const DISABLE_READ_LOOKAHEAD: u8 = 0x55;
const ENABLE_READ_LOOKAHEAD: u8 = 0xaa;
const DISABLE_REVERT_ON_POWER_UP: u8 = 0x66;
const ENABLE_REVERT_ON_POWER_UP: u8 = 0xcc;
match state.features.current {
SET_TRANSFER_MODE => {
let class = state.count.current >> 3;
let ok = match class {
0 | 1 => true,
0b00100 | 0b01000 => self.id.dma,
_ => false,
};
if ok {
self.succeed(state);
} else {
self.abort(state);
}
}
ENABLE_WRITE_CACHE
| DISABLE_WRITE_CACHE
| DISABLE_READ_LOOKAHEAD
| ENABLE_READ_LOOKAHEAD
| DISABLE_REVERT_ON_POWER_UP
| ENABLE_REVERT_ON_POWER_UP => self.succeed(state),
_ => self.abort(state),
}
}
fn no_such_address(&self, state: &mut Volatile, error: u8) {
state.status = ST_IDLE | ST_ERR;
state.error = error;
state.xfer = None;
state.buf.clear();
state.pos = 0;
state.irq = true;
}
fn identify_block(&self, state: &Volatile) -> Vec<u8> {
let mut w = [0u16; 256];
let id = &self.id;
w[0] = 0x0040;
w[1] = id.geometry.cylinders;
w[3] = u16::from(id.geometry.heads);
w[6] = u16::from(id.geometry.sectors);
put_string(&mut w[10..20], &id.serial);
put_string(&mut w[23..27], &id.firmware);
put_string(&mut w[27..47], &id.model);
w[47] = 0x8000 | u16::from(id.max_multiple);
w[49] = (1 << 9) | (1 << 10) | (1 << 11) | if id.dma { 1 << 8 } else { 0 };
w[50] = 0x4000;
w[51] = 0x0200;
w[53] = 0x0003 | if id.dma { 0x0004 } else { 0 };
w[54] = state.current.cylinders;
w[55] = u16::from(state.current.heads);
w[56] = u16::from(state.current.sectors);
let chs_capacity = state.current.addressable().min(u64::from(u32::MAX));
w[57] = chs_capacity as u16;
w[58] = (chs_capacity >> 16) as u16;
w[59] = if state.multiple == 0 {
0
} else {
0x0100 | u16::from(state.multiple)
};
let lba28 = id.sectors.min(LBA28_LIMIT - 1);
w[60] = lba28 as u16;
w[61] = (lba28 >> 16) as u16;
if id.dma {
w[63] = 0x0007 | (1 << (8 + 2));
w[88] = 0x003f | (1 << (8 + 5));
}
w[64] = 0x0003;
w[67] = 120;
w[68] = 120;
w[80] = (1 << 4) | (1 << 5) | (1 << 6);
w[83] = 0x4000 | if id.lba48 { 1 << 10 } else { 0 };
w[84] = 0x4000;
w[86] = if id.lba48 { 1 << 10 } else { 0 };
w[87] = 0x4000;
if id.lba48 {
w[100] = id.sectors as u16;
w[101] = (id.sectors >> 16) as u16;
w[102] = (id.sectors >> 32) as u16;
w[103] = (id.sectors >> 48) as u16;
}
let mut out = alloc::vec![0u8; 512];
for (i, word) in w.iter().enumerate() {
out[i * 2] = *word as u8;
out[i * 2 + 1] = (*word >> 8) as u8;
}
out[510] = 0xa5;
let sum: u8 = out[..511].iter().fold(0u8, |a, b| a.wrapping_add(*b));
out[511] = 0u8.wrapping_sub(sum);
out
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
match self.media.snapshot() {
Snapshot::Capture => w.write_bytes(&self.contents()?)?,
Snapshot::Reference => {
self.media.flush().map_err(|e| medium::error_at(0, e))?;
w.write_bytes(self.media.describe().as_bytes())?;
}
Snapshot::Refuse => {
return Err(Error::State(format!(
"this drive's medium ({}) refuses to be snapshotted",
self.media.describe()
)));
}
}
let state = self.state.lock();
w.write_bool(state.selected)?;
w.write_u8(state.device)?;
w.write_u8(state.error)?;
w.write_u8(state.status)?;
w.write_u8(state.control)?;
for fifo in [
state.features,
state.count,
state.lba_low,
state.lba_mid,
state.lba_high,
] {
w.write_u8(fifo.current)?;
w.write_u8(fifo.previous)?;
}
w.write_bool(state.irq)?;
w.write_bool(state.in_reset)?;
w.write_u8(state.multiple)?;
w.write_u16(state.current.cylinders)?;
w.write_u8(state.current.heads)?;
w.write_u8(state.current.sectors)?;
w.write_bytes(&state.buf)?;
w.write_u64(state.pos as u64)?;
match &state.xfer {
None => w.write_bool(false)?,
Some(x) => {
w.write_bool(true)?;
w.write_bool(x.out)?;
w.write_bool(x.dma)?;
w.write_u64(x.next)?;
w.write_u64(x.left)?;
w.write_u32(x.block)?;
w.write_u8(match x.mode {
Mode::Chs => 0,
Mode::Lba28 => 1,
Mode::Lba48 => 2,
})?;
w.write_u64(x.last)?;
}
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let bytes: &[u8] = r.read_bytes()?;
match self.media.snapshot() {
Snapshot::Capture => {
if bytes.len() as u64 != self.id.capacity() {
return Err(Error::State(format!(
"the snapshot holds a drive of {} byte(s), this one holds {}",
bytes.len(),
self.id.capacity()
)));
}
self.media
.write_at(0, bytes)
.map_err(|e| Error::State(format!("the drive refused the snapshot: {e}")))?;
}
Snapshot::Reference => {
let want = self.media.describe();
if bytes != want.as_bytes() {
return Err(Error::State(format!(
"the snapshot references a different medium: it names `{}` and this \
drive holds `{want}`",
alloc::string::String::from_utf8_lossy(&bytes[..bytes.len().min(120)])
)));
}
}
Snapshot::Refuse => {
return Err(Error::State(format!(
"this drive's medium ({}) refuses to be snapshotted",
self.media.describe()
)));
}
}
let selected = r.read_bool()?;
let device = r.read_u8()?;
let error = r.read_u8()?;
let status = r.read_u8()?;
let control = r.read_u8()?;
let mut fifos = [Fifo::default(); 5];
for fifo in &mut fifos {
fifo.current = r.read_u8()?;
fifo.previous = r.read_u8()?;
}
let irq = r.read_bool()?;
let in_reset = r.read_bool()?;
let multiple = r.read_u8()?;
let current = Geometry {
cylinders: r.read_u16()?,
heads: r.read_u8()?,
sectors: r.read_u8()?,
};
let buf = r.read_bytes()?.to_vec();
let pos = r.read_u64()?;
if pos > buf.len() as u64 {
return Err(Error::State(format!(
"a snapshot buffer position of {pos} is past the {} byte(s) it holds",
buf.len()
)));
}
let xfer = if r.read_bool()? {
let out = r.read_bool()?;
let dma = r.read_bool()?;
let next = r.read_u64()?;
let left = r.read_u64()?;
let block = r.read_u32()?;
let mode = match r.read_u8()? {
0 => Mode::Chs,
1 => Mode::Lba28,
2 => Mode::Lba48,
other => {
return Err(Error::State(format!(
"{other} is not an addressing mode this drive has"
)));
}
};
let last = r.read_u64()?;
if block == 0 || next > self.id.sectors || left > self.id.sectors {
return Err(Error::State(format!(
"a snapshot transfer of {left} sector(s) from {next} is not one this drive \
could have started"
)));
}
Some(Transfer {
out,
dma,
next,
left,
block,
mode,
last,
})
} else {
None
};
let mut state = self.state.lock();
state.selected = selected;
state.device = device;
state.error = error;
state.status = status;
state.control = control;
state.features = fifos[0];
state.count = fifos[1];
state.lba_low = fifos[2];
state.lba_mid = fifos[3];
state.lba_high = fifos[4];
state.irq = irq;
state.in_reset = in_reset;
state.multiple = multiple;
state.current = current;
state.buf = buf;
state.pos = pos as usize;
state.xfer = xfer;
Ok(())
}
}
fn put_string(words: &mut [u16], text: &str) {
let bytes = text.as_bytes();
for (i, word) in words.iter_mut().enumerate() {
let hi = bytes.get(i * 2).copied().unwrap_or(b' ');
let lo = bytes.get(i * 2 + 1).copied().unwrap_or(b' ');
let hi = if hi.is_ascii_graphic() || hi == b' ' {
hi
} else {
b' '
};
let lo = if lo.is_ascii_graphic() || lo == b' ' {
lo
} else {
b' '
};
*word = (u16::from(hi) << 8) | u16::from(lo);
}
}
fn config(message: String) -> Error {
Error::Config {
at: String::from(CLASS_NAME),
message,
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "an ATA hard disk: the command block, the command set and CHS/LBA addressing",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: false,
summary: "how many bytes the drive holds; absent or zero takes the image's length, \
and with no image that is an empty bay",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the media slot holding the initial contents; the rest reads zero",
},
PropertySpec {
name: "bay",
kind: ValueKind::Str,
required: false,
summary: "the named drive bay this drive is fitted in (default `ata0`)",
},
PropertySpec {
name: "position",
kind: ValueKind::Str,
required: false,
summary: "`master` (device 0, the default) or `slave` (device 1)",
},
PropertySpec {
name: "readonly",
kind: ValueKind::Bool,
required: false,
summary: "write protect the medium: a write command aborts",
},
PropertySpec {
name: "lba48",
kind: ValueKind::Bool,
required: false,
summary: "advertise and accept the 48-bit Address feature set (default true)",
},
PropertySpec {
name: "dma",
kind: ValueKind::Bool,
required: false,
summary: "advertise and accept the DMA data transfer protocols and the READ/WRITE \
DMA command family (default false); a bus-mastering host adapter wants it",
},
PropertySpec {
name: "multiple",
kind: ValueKind::Uint,
required: false,
summary: "the largest READ/WRITE MULTIPLE block, in sectors; a power of two",
},
PropertySpec {
name: "cylinders",
kind: ValueKind::Uint,
required: false,
summary: "the default CHS translation's cylinders; give all three or none",
},
PropertySpec {
name: "heads",
kind: ValueKind::Uint,
required: false,
summary: "the default CHS translation's heads, 1 to 16",
},
PropertySpec {
name: "sectors",
kind: ValueKind::Uint,
required: false,
summary: "the default CHS translation's sectors per track, 1 to 255",
},
PropertySpec {
name: "model",
kind: ValueKind::Str,
required: false,
summary: "the IDENTIFY model string, forty characters",
},
PropertySpec {
name: "serial",
kind: ValueKind::Str,
required: false,
summary: "the IDENTIFY serial number; a constant, because a run must be reproducible",
},
PropertySpec {
name: "firmware",
kind: ValueKind::Str,
required: false,
summary: "the IDENTIFY firmware revision, eight characters",
},
],
construct: |props| Ok(Box::new(DiskDevice::new(props)?)),
};
#[derive(Debug)]
pub struct DiskDevice {
drive: Option<Arc<AtaDisk>>,
bay: String,
}
impl DiskDevice {
pub fn new(props: &Props) -> Result<DiskDevice> {
let bay = props
.get("bay")
.and_then(crate::core::props::Value::as_str)
.unwrap_or(super::DEFAULT_BAY)
.to_string();
let Some(disk) = AtaDisk::new(props)? else {
super::bays::attach(props, &bay)?;
return Ok(DiskDevice { drive: None, bay });
};
let drive = Arc::new(disk);
let holder = super::bays::attach(props, &bay)?;
holder.fit(Arc::clone(&drive)).map_err(|_| {
config(format!(
"two drives were fitted in the bay called `{bay}`; give one of them another `bay`"
))
})?;
Ok(DiskDevice {
drive: Some(drive),
bay,
})
}
#[must_use]
pub fn drive(&self) -> Option<&Arc<AtaDisk>> {
self.drive.as_ref()
}
#[must_use]
pub fn bay(&self) -> &str {
&self.bay
}
}
impl Device for DiskDevice {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
if let Some(drive) = &self.drive {
drive.power_on_reset();
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
match &self.drive {
None => w.write_bool(false),
Some(drive) => {
w.write_bool(true)?;
drive.save(w)
}
}
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let occupied = r.read_bool()?;
match (&self.drive, occupied) {
(Some(drive), true) => drive.load(r),
(None, false) => Ok(()),
(Some(_), false) => Err(Error::State(String::from(
"the snapshot has an empty bay and this machine has a drive in it",
))),
(None, true) => Err(Error::State(String::from(
"the snapshot has a drive and this machine's bay is empty",
))),
}
}
}
impl Instance for DiskDevice {}
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(DiskDevice::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size))
.prop(PropSchema::new("image", ValueKind::Media))
.prop(PropSchema::new("bay", ValueKind::Str))
.prop(PropSchema::new("position", ValueKind::Str).values(&["master", "slave"]))
.prop(PropSchema::new("readonly", ValueKind::Bool))
.prop(PropSchema::new("lba48", ValueKind::Bool))
.prop(PropSchema::new("dma", ValueKind::Bool))
.prop(PropSchema::new("multiple", ValueKind::Uint).range(1, 128))
.prop(PropSchema::new("cylinders", ValueKind::Uint).range(1, 65535))
.prop(PropSchema::new("heads", ValueKind::Uint).range(1, 16))
.prop(PropSchema::new("sectors", ValueKind::Uint).range(1, 255))
.prop(PropSchema::new("model", ValueKind::Str))
.prop(PropSchema::new("serial", ValueKind::Str))
.prop(PropSchema::new("firmware", ValueKind::Str))
}
#[cfg(test)]
mod tests;