use alloc::string::String;
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::{BusError, Result};
use crate::core::space::{
AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult, RequesterId,
};
use crate::core::state::{Sink, Source};
use crate::core::sync::{AtomicBool, AtomicU32, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::dev::ata::bays::Bay;
use crate::dev::ata::disk::{CTL_SRST, ST_BSY, ST_DRQ, ST_ERR};
use crate::dev::ata::{AtaDisk, Phase, Registers, Taskfile};
pub const AHCI_RANK: LockRank = LockRank::new(0x5a10);
pub const MAX_PORTS: usize = 8;
pub const REGISTER_LEN: u64 = 0x800;
const PORT_BASE: u64 = 0x100;
const PORT_STRIDE: u64 = 0x80;
const COMMAND_SLOTS: u32 = 32;
const HEADER_LEN: u64 = 32;
const COMMAND_LIST_LEN: u64 = HEADER_LEN * COMMAND_SLOTS as u64;
const RECEIVED_FIS_LEN: u64 = 256;
const PRDT_OFFSET: u64 = 0x80;
const PRD_LEN: u64 = 16;
const MAX_PRDT: u32 = 65535;
const MAX_TRANSFER: u64 = 65536 * 512;
const MAX_BLOCKS: u64 = 65537;
const MAX_COMMANDS_PER_RUN: u32 = MAX_PORTS as u32 * COMMAND_SLOTS * 2;
const CHUNK: usize = 512;
const VERSION: u32 = 0x0001_0301;
const REG_CAP: u64 = 0x00;
const REG_GHC: u64 = 0x04;
const REG_IS: u64 = 0x08;
const REG_PI: u64 = 0x0c;
const REG_VS: u64 = 0x10;
const REG_CAP2: u64 = 0x24;
const GHC_AE: u32 = 1 << 31;
const GHC_IE: u32 = 1 << 1;
const GHC_HR: u32 = 1 << 0;
const PORT_CLB: u64 = 0x00;
const PORT_CLBU: u64 = 0x04;
const PORT_FB: u64 = 0x08;
const PORT_FBU: u64 = 0x0c;
const PORT_IS: u64 = 0x10;
const PORT_IE: u64 = 0x14;
const PORT_CMD: u64 = 0x18;
const PORT_TFD: u64 = 0x20;
const PORT_SIG: u64 = 0x24;
const PORT_SSTS: u64 = 0x28;
const PORT_SCTL: u64 = 0x2c;
const PORT_SERR: u64 = 0x30;
const PORT_SACT: u64 = 0x34;
const PORT_CI: u64 = 0x38;
const IS_DHRS: u32 = 1 << 0;
const IS_PSS: u32 = 1 << 1;
const IS_DPS: u32 = 1 << 5;
const IS_PCS: u32 = 1 << 6;
const IS_OFS: u32 = 1 << 24;
const IS_IFS: u32 = 1 << 27;
const IS_HBFS: u32 = 1 << 29;
const IS_TFES: u32 = 1 << 30;
const FATAL: u32 = IS_HBFS | IS_IFS | IS_TFES;
const IS_WRITE_CLEAR: u32 = 0xfd80_00af;
const IE_IMPLEMENTED: u32 = 0xfdc0_00ff;
const CMD_ST: u32 = 1 << 0;
const CMD_SUD: u32 = 1 << 1;
const CMD_POD: u32 = 1 << 2;
const CMD_CLO: u32 = 1 << 3;
const CMD_FRE: u32 = 1 << 4;
const CMD_FR: u32 = 1 << 14;
const CMD_CR: u32 = 1 << 15;
const CMD_WRITABLE: u32 = CMD_ST | CMD_CLO | CMD_FRE | (1 << 24) | (1 << 25);
const CMD_CCS_SHIFT: u32 = 8;
const CMD_CCS_MASK: u32 = 0x1f << CMD_CCS_SHIFT;
const SERR_DIAG_X: u32 = 1 << 26;
const DET_MASK: u32 = 0xf;
const DET_INIT: u32 = 1;
const DET_OFFLINE: u32 = 4;
const SSTS_READY: u32 = 0x0000_0133;
const TFD_RESET: u32 = 0x0000_007f;
const SIG_UNKNOWN: u32 = 0xffff_ffff;
const FIS_H2D: u8 = 0x27;
const FIS_D2H: u8 = 0x34;
const FIS_PIO_SETUP: u8 = 0x5f;
const REGISTER_FIS_LEN: usize = 20;
const PSFIS_AT: u64 = 0x20;
const RFIS_AT: u64 = 0x40;
const H2D_C: u8 = 1 << 7;
const D2H_I: u8 = 1 << 6;
const PIO_D: u8 = 1 << 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PortState {
clb: u64,
fb: u64,
is: u32,
ie: u32,
cmd: u32,
tfd: u32,
sctl: u32,
serr: u32,
sact: u32,
ci: u32,
sig: u32,
ctl: u8,
parked: u32,
}
impl PortState {
const fn new() -> PortState {
PortState {
clb: 0,
fb: 0,
is: 0,
ie: 0,
cmd: CMD_SUD | CMD_POD,
tfd: TFD_RESET,
sctl: 0,
serr: 0,
sact: 0,
ci: 0,
sig: SIG_UNKNOWN,
ctl: 0,
parked: 0,
}
}
const fn running(&self) -> bool {
self.cmd & CMD_CR != 0
}
const fn receiving(&self) -> bool {
self.cmd & CMD_FR != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct State {
ghc: u32,
is: u32,
ports: [PortState; MAX_PORTS],
}
impl State {
const fn new() -> State {
State {
ghc: GHC_AE,
is: 0,
ports: [PortState::new(); MAX_PORTS],
}
}
}
struct Link {
space: Option<Weak<AddressSpace>>,
irq: Option<WireSource>,
}
impl fmt::Debug for Link {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Link")
.field("space", &self.space.as_ref().map(|s| s.strong_count()))
.field("irq", &self.irq.is_some())
.finish()
}
}
#[derive(Debug)]
struct Port {
bay: Arc<Bay>,
name: String,
}
pub struct Hba {
ports: Vec<Port>,
state: Mutex<State>,
link: Mutex<Link>,
requester: AtomicU32,
irq_level: AtomicU32,
master: AtomicBool,
intx_disabled: AtomicBool,
busy: AtomicBool,
}
impl fmt::Debug for Hba {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Hba");
s.field("ports", &self.ports.len());
match self.state.try_lock() {
Some(state) => s.field("ghc", &state.ghc).field("is", &state.is),
None => s.field("state", &"<in use>"),
};
s.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum After {
Irq,
Run,
HbaReset,
Comreset(usize),
}
impl Hba {
#[must_use]
pub fn new(bays: Vec<(String, Arc<Bay>)>) -> Hba {
let ports = bays
.into_iter()
.take(MAX_PORTS)
.map(|(name, bay)| Port { bay, name })
.collect();
Hba {
ports,
state: Mutex::with_rank(AHCI_RANK, State::new()),
link: Mutex::with_rank(
LockRank::WIRE,
Link {
space: None,
irq: None,
},
),
requester: AtomicU32::new(RequesterId::ANONYMOUS.0),
irq_level: AtomicU32::new(0),
master: AtomicBool::new(false),
intx_disabled: AtomicBool::new(false),
busy: AtomicBool::new(false),
}
}
#[must_use]
pub fn ports(&self) -> usize {
self.ports.len()
}
#[must_use]
pub fn bay_name(&self, index: usize) -> Option<&str> {
self.ports.get(index).map(|p| p.name.as_str())
}
pub fn attach_space(&self, space: &Arc<AddressSpace>, requester: RequesterId) {
let mut link = self.link.lock();
link.space = Some(Arc::downgrade(space));
drop(link);
self.requester.store(requester.0, Ordering::Relaxed);
}
pub fn connect_irq(&self, source: WireSource) {
self.link.lock().irq = Some(source);
self.refresh_irq();
}
pub fn set_master(&self, enabled: bool) {
self.master.store(enabled, Ordering::Relaxed);
}
pub fn set_intx_disabled(&self, disabled: bool) {
self.intx_disabled.store(disabled, Ordering::Relaxed);
self.refresh_irq();
}
#[must_use]
pub fn interrupt_pending(&self) -> bool {
self.irq_level.load(Ordering::Relaxed) != 0
}
#[must_use]
pub fn irq_level(&self) -> Level {
Level::from_bool(self.interrupt_pending() && !self.intx_disabled.load(Ordering::Relaxed))
}
fn space(&self) -> Option<Arc<AddressSpace>> {
self.link.lock().space.as_ref().and_then(Weak::upgrade)
}
fn attrs(&self) -> MemAttrs {
MemAttrs::DEFAULT.with_requester(RequesterId(self.requester.load(Ordering::Relaxed)))
}
fn drive(&self, index: usize) -> Option<Arc<AtaDisk>> {
self.ports.get(index).and_then(|p| p.bay.drive())
}
fn occupied(&self, index: usize) -> bool {
self.ports.get(index).is_some_and(|p| p.bay.is_occupied())
}
}
impl Hba {
pub fn refresh_irq(&self) {
let pending = {
let mut state = self.state.lock();
let mut is = state.is;
for (i, port) in state.ports.iter().enumerate() {
if port.is & port.ie != 0 {
is |= 1 << i;
}
}
state.is = is;
state.ghc & GHC_IE != 0 && is != 0
};
self.irq_level.store(u32::from(pending), Ordering::Relaxed);
let level = self.irq_level();
let out = self.link.lock().irq.clone();
if let Some(out) = out {
out.set(level);
}
}
pub fn reset(&self) {
self.reset_registers();
self.master.store(false, Ordering::Relaxed);
self.intx_disabled.store(false, Ordering::Relaxed);
self.refresh_irq();
}
fn hba_reset(&self) {
self.reset_registers();
self.refresh_irq();
}
fn reset_registers(&self) {
for index in 0..self.ports.len() {
if let Some(drive) = self.drive(index) {
drive.power_on_reset();
}
}
let answered: Vec<Option<Registers>> = (0..self.ports.len())
.map(|index| self.drive(index).map(|d| d.taskfile_registers()))
.collect();
let mut state = self.state.lock();
*state = State::new();
for (port, regs) in state.ports.iter_mut().zip(answered) {
if let Some(regs) = regs {
port.tfd = tfd_of(®s);
port.sig = signature_of(®s);
}
}
}
}
impl Hba {
fn cap(&self) -> u32 {
let np = (self.ports.len().max(1) - 1) as u32;
(1 << 31)
| (1 << 24)
| (3 << 20)
| (1 << 18)
| (1 << 15)
| ((COMMAND_SLOTS - 1) << 8)
| np
}
fn pi(&self) -> u32 {
if self.ports.is_empty() {
0
} else {
(1u32 << self.ports.len()) - 1
}
}
fn ssts(&self, index: usize, sctl: u32) -> u32 {
if !self.occupied(index) {
return 0;
}
match sctl & DET_MASK {
DET_INIT => 1,
DET_OFFLINE => DET_OFFLINE,
_ => SSTS_READY,
}
}
fn read_dword(&self, offset: u64) -> u32 {
if offset >= PORT_BASE {
let index = ((offset - PORT_BASE) / PORT_STRIDE) as usize;
let within = (offset - PORT_BASE) % PORT_STRIDE;
if index >= self.ports.len() {
return 0;
}
if within == PORT_SSTS {
let sctl = self.state.lock().ports[index].sctl;
return self.ssts(index, sctl);
}
let port = self.state.lock().ports[index];
return match within {
PORT_CLB => port.clb as u32,
PORT_CLBU => (port.clb >> 32) as u32,
PORT_FB => port.fb as u32,
PORT_FBU => (port.fb >> 32) as u32,
PORT_IS => port.is,
PORT_IE => port.ie,
PORT_CMD => port.cmd,
PORT_TFD => port.tfd,
PORT_SIG => port.sig,
PORT_SCTL => port.sctl,
PORT_SERR => port.serr,
PORT_SACT => port.sact,
PORT_CI => port.ci,
_ => 0,
};
}
match offset {
REG_CAP => self.cap(),
REG_GHC => self.state.lock().ghc,
REG_IS => self.state.lock().is,
REG_PI => self.pi(),
REG_VS => VERSION,
REG_CAP2 => 0,
_ => 0,
}
}
}
impl Hba {
fn write_dword(&self, offset: u64, value: u32) {
let after = if offset >= PORT_BASE {
let index = ((offset - PORT_BASE) / PORT_STRIDE) as usize;
let within = (offset - PORT_BASE) % PORT_STRIDE;
if index >= self.ports.len() {
return;
}
let mut state = self.state.lock();
Hba::write_port(&mut state.ports[index], index, within, value)
} else {
let mut state = self.state.lock();
match offset {
REG_GHC => {
if value & GHC_HR != 0 {
After::HbaReset
} else {
state.ghc = GHC_AE | (value & GHC_IE);
After::Irq
}
}
REG_IS => {
state.is &= !value;
After::Irq
}
_ => After::Irq,
}
};
match after {
After::Irq => self.refresh_irq(),
After::Run => {
self.run();
self.refresh_irq();
}
After::HbaReset => self.hba_reset(),
After::Comreset(index) => {
self.comreset(index);
self.refresh_irq();
}
}
}
fn write_port(port: &mut PortState, index: usize, within: u64, value: u32) -> After {
match within {
PORT_CLB => {
port.clb = (port.clb & 0xffff_ffff_0000_0000) | u64::from(value & !0x3ff);
After::Irq
}
PORT_CLBU => {
port.clb = (port.clb & 0xffff_ffff) | (u64::from(value) << 32);
After::Irq
}
PORT_FB => {
port.fb = (port.fb & 0xffff_ffff_0000_0000) | u64::from(value & !0xff);
After::Irq
}
PORT_FBU => {
port.fb = (port.fb & 0xffff_ffff) | (u64::from(value) << 32);
After::Irq
}
PORT_IS => {
port.is &= !(value & IS_WRITE_CLEAR);
After::Irq
}
PORT_IE => {
port.ie = value & IE_IMPLEMENTED;
After::Irq
}
PORT_CMD => {
let was_started = port.cmd & CMD_ST != 0;
let keep = port.cmd & !CMD_WRITABLE;
port.cmd = keep | (value & CMD_WRITABLE) | CMD_SUD | CMD_POD;
if port.cmd & CMD_FRE != 0 {
port.cmd |= CMD_FR;
} else {
port.cmd &= !CMD_FR;
}
if value & CMD_CLO != 0 {
port.tfd &= !u32::from(ST_BSY | ST_DRQ);
port.cmd &= !CMD_CLO;
}
let started = port.cmd & CMD_ST != 0;
if was_started && !started {
port.cmd &= !(CMD_CR | CMD_CCS_MASK);
port.ci = 0;
port.sact = 0;
port.parked = 0;
} else if !was_started && started {
port.cmd = (port.cmd | CMD_CR) & !CMD_CCS_MASK;
}
if port.running() {
After::Run
} else {
After::Irq
}
}
PORT_SCTL => {
let before = port.sctl & DET_MASK;
port.sctl = value;
if before != value & DET_MASK {
After::Comreset(index)
} else {
After::Irq
}
}
PORT_SERR => {
port.serr &= !value;
if port.serr & SERR_DIAG_X == 0 {
port.is &= !IS_PCS;
}
After::Irq
}
PORT_SACT => {
port.sact |= value;
After::Irq
}
PORT_CI => {
if port.cmd & CMD_ST != 0 {
port.ci |= value;
if port.running() {
return After::Run;
}
}
After::Irq
}
_ => After::Irq,
}
}
fn comreset(&self, index: usize) {
let det = self.state.lock().ports[index].sctl & DET_MASK;
let drive = self.drive(index);
if det == DET_INIT {
if let Some(drive) = &drive {
drive.power_on_reset();
}
let mut state = self.state.lock();
let port = &mut state.ports[index];
port.ci = 0;
port.sact = 0;
port.parked = 0;
port.ctl = 0;
port.tfd = if drive.is_some() {
u32::from(ST_BSY)
} else {
TFD_RESET
};
return;
}
if det != 0 {
return;
}
let Some(drive) = drive else {
let mut state = self.state.lock();
state.ports[index].tfd = TFD_RESET;
state.ports[index].sig = SIG_UNKNOWN;
return;
};
let regs = drive.taskfile_registers();
let irq = drive.taskfile_acknowledge();
let (fb, receiving) = {
let state = self.state.lock();
(state.ports[index].fb, state.ports[index].receiving())
};
if receiving {
self.post_fis(fb + RFIS_AT, &d2h_fis(®s, false));
}
let _ = irq;
let mut state = self.state.lock();
let port = &mut state.ports[index];
port.tfd = tfd_of(®s);
port.sig = signature_of(®s);
port.serr |= SERR_DIAG_X;
port.is |= IS_PCS;
}
}
fn tfd_of(regs: &Registers) -> u32 {
(u32::from(regs.error) << 8) | u32::from(regs.status)
}
fn signature_of(regs: &Registers) -> u32 {
(((regs.lba >> 16) as u32 & 0xff) << 24)
| (((regs.lba >> 8) as u32 & 0xff) << 16)
| ((regs.lba as u32 & 0xff) << 8)
| u32::from(regs.count as u8)
}
fn d2h_fis(regs: &Registers, interrupt: bool) -> [u8; REGISTER_FIS_LEN] {
let mut fis = [0u8; REGISTER_FIS_LEN];
fis[0] = FIS_D2H;
fis[1] = if interrupt { D2H_I } else { 0 };
fis[2] = regs.status;
fis[3] = regs.error;
fis[4] = regs.lba as u8;
fis[5] = (regs.lba >> 8) as u8;
fis[6] = (regs.lba >> 16) as u8;
fis[7] = regs.device;
fis[8] = (regs.lba >> 24) as u8;
fis[9] = (regs.lba >> 32) as u8;
fis[10] = (regs.lba >> 40) as u8;
fis[12] = regs.count as u8;
fis[13] = (regs.count >> 8) as u8;
fis
}
fn pio_setup_fis(
regs: &Registers,
status_before: u8,
end_status: u8,
device_to_host: bool,
count: u16,
interrupt: bool,
) -> [u8; REGISTER_FIS_LEN] {
let mut fis = [0u8; REGISTER_FIS_LEN];
fis[0] = FIS_PIO_SETUP;
fis[1] = if interrupt { D2H_I } else { 0 } | if device_to_host { PIO_D } else { 0 };
fis[2] = status_before;
fis[3] = regs.error;
fis[4] = regs.lba as u8;
fis[5] = (regs.lba >> 8) as u8;
fis[6] = (regs.lba >> 16) as u8;
fis[7] = regs.device;
fis[8] = (regs.lba >> 24) as u8;
fis[9] = (regs.lba >> 32) as u8;
fis[10] = (regs.lba >> 40) as u8;
fis[12] = regs.count as u8;
fis[13] = (regs.count >> 8) as u8;
fis[15] = end_status;
fis[16] = count as u8;
fis[17] = (count >> 8) as u8;
fis
}
fn le32(bytes: &[u8]) -> u32 {
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
#[derive(Debug, Clone, Copy)]
struct Job {
port: usize,
slot: u32,
clb: u64,
fb: u64,
receiving: bool,
}
#[derive(Debug, Clone, Copy)]
struct Header {
cfl: u64,
prdtl: u32,
clear_busy: bool,
ctba: u64,
}
#[derive(Debug)]
struct Prdt {
base: u64,
len: u32,
next: u32,
addr: u64,
left: u64,
interrupt: bool,
fired: bool,
}
impl Prdt {
fn new(base: u64, len: u32) -> Prdt {
Prdt {
base,
len: len.min(MAX_PRDT),
next: 0,
addr: 0,
left: 0,
interrupt: false,
fired: false,
}
}
fn take(&mut self, hba: &Hba, space: &AddressSpace, want: usize) -> Option<(u64, usize)> {
while self.left == 0 {
if self.next >= self.len {
return None;
}
let at = self.base + u64::from(self.next) * PRD_LEN;
let mut raw = [0u8; PRD_LEN as usize];
if space.read_bytes(at, &mut raw, hba.attrs()).is_err() {
return None;
}
self.next += 1;
let dw3 = le32(&raw[12..16]);
self.addr = (u64::from(le32(&raw[0..4])) & !1) | (u64::from(le32(&raw[4..8])) << 32);
self.left = u64::from(dw3 & 0x003f_ffff) + 1;
self.interrupt = dw3 & (1 << 31) != 0;
}
let n = core::cmp::min(self.left, want as u64) as usize;
Some((self.addr, n))
}
fn advance(&mut self, n: usize) {
self.addr += n as u64;
self.left -= n as u64;
if self.left == 0 && self.interrupt {
self.fired = true;
}
}
}
impl Hba {
fn has_work(&self) -> bool {
let state = self.state.lock();
state
.ports
.iter()
.take(self.ports.len())
.any(|p| p.running() && p.ci & !p.parked != 0)
}
fn next_job(&self) -> Option<Job> {
let mut state = self.state.lock();
for index in 0..self.ports.len() {
let port = &mut state.ports[index];
if !port.running() {
continue;
}
let live = port.ci & !port.parked;
if live == 0 {
continue;
}
let slot = live.trailing_zeros();
port.cmd = (port.cmd & !CMD_CCS_MASK) | ((slot << CMD_CCS_SHIFT) & CMD_CCS_MASK);
return Some(Job {
port: index,
slot,
clb: port.clb,
fb: port.fb,
receiving: port.receiving(),
});
}
None
}
pub fn run(&self) {
if !self.master.load(Ordering::Relaxed) {
return;
}
if self.busy.swap(true, Ordering::AcqRel) {
return;
}
let mut budget = MAX_COMMANDS_PER_RUN;
loop {
while let Some(job) = self.next_job() {
self.execute(&job);
budget -= 1;
if budget == 0 {
break;
}
}
self.busy.store(false, Ordering::Release);
if budget == 0 || !self.has_work() || self.busy.swap(true, Ordering::AcqRel) {
break;
}
}
}
fn port_fatal(&self, index: usize, bit: u32) {
let mut state = self.state.lock();
let port = &mut state.ports[index];
port.is |= bit;
port.cmd &= !CMD_CR;
}
fn post_fis(&self, at: u64, fis: &[u8]) -> bool {
let Some(space) = self.space() else {
return false;
};
space.write_bytes(at, fis, self.attrs()).is_ok()
}
fn execute(&self, job: &Job) {
let Some(space) = self.space() else {
self.port_fatal(job.port, IS_HBFS);
return;
};
let at = job.clb + u64::from(job.slot) * HEADER_LEN;
debug_assert!(u64::from(job.slot) * HEADER_LEN < COMMAND_LIST_LEN);
let mut raw = [0u8; HEADER_LEN as usize];
if space.read_bytes(at, &mut raw, self.attrs()).is_err() {
self.port_fatal(job.port, IS_HBFS);
return;
}
let dw0 = le32(&raw[0..4]);
let header = Header {
cfl: u64::from(dw0 & 0x1f),
prdtl: dw0 >> 16,
clear_busy: dw0 & (1 << 10) != 0,
ctba: (u64::from(le32(&raw[8..12])) & !0x7f) | (u64::from(le32(&raw[12..16])) << 32),
};
if !(2..=16).contains(&header.cfl) {
self.port_fatal(job.port, IS_IFS);
return;
}
let mut cfis = [0u8; 64];
let len = (header.cfl * 4) as usize;
if space
.read_bytes(header.ctba, &mut cfis[..len], self.attrs())
.is_err()
{
self.port_fatal(job.port, IS_HBFS);
return;
}
if cfis[0] != FIS_H2D {
self.port_fatal(job.port, IS_IFS);
return;
}
let Some(drive) = self.drive(job.port) else {
self.port_fatal(job.port, IS_IFS);
return;
};
if cfis[1] & H2D_C == 0 {
self.control_fis(job, &drive, &header, cfis[15]);
} else {
self.command_fis(job, &space, &drive, &header, &cfis);
}
}
fn control_fis(&self, job: &Job, drive: &Arc<AtaDisk>, header: &Header, control: u8) {
let previous = self.state.lock().ports[job.port].ctl;
drive.write_device_control(control);
let released = previous & CTL_SRST != 0 && control & CTL_SRST == 0;
let regs = drive.taskfile_registers();
let interrupt = drive.taskfile_acknowledge();
if released && job.receiving {
self.post_fis(job.fb + RFIS_AT, &d2h_fis(®s, interrupt));
}
let mut state = self.state.lock();
let port = &mut state.ports[job.port];
port.ctl = control;
port.tfd = tfd_of(®s);
if released {
port.sig = signature_of(®s);
port.ci &= !port.parked;
port.parked = 0;
port.ci &= !(1 << job.slot);
if interrupt {
port.is |= IS_DHRS;
}
} else if header.clear_busy {
port.tfd &= !u32::from(ST_BSY | ST_DRQ);
port.ci &= !(1 << job.slot);
} else {
port.parked |= 1 << job.slot;
}
}
fn command_fis(
&self,
job: &Job,
space: &AddressSpace,
drive: &Arc<AtaDisk>,
header: &Header,
cfis: &[u8; 64],
) {
let tf = Taskfile {
command: cfis[2],
feature: u16::from(cfis[3]) | (u16::from(cfis[11]) << 8),
count: u16::from(cfis[12]) | (u16::from(cfis[13]) << 8),
lba: u64::from(cfis[4])
| (u64::from(cfis[5]) << 8)
| (u64::from(cfis[6]) << 16)
| (u64::from(cfis[8]) << 24)
| (u64::from(cfis[9]) << 32)
| (u64::from(cfis[10]) << 40),
device: cfis[7],
};
{
let mut state = self.state.lock();
state.ports[job.port].tfd |= u32::from(ST_BSY);
}
let mut phase = drive.taskfile_start(&tf);
let mut prdt = Prdt::new(header.ctba + PRDT_OFFSET, header.prdtl);
let mut scratch = [0u8; CHUNK];
let mut moved: u64 = 0;
let mut blocks: u64 = 0;
let mut pio = false;
let mut trouble: u32 = 0;
while let Phase::Data { out, dma, block } = phase {
blocks += 1;
if blocks > MAX_BLOCKS || moved.saturating_add(block) > MAX_TRANSFER {
trouble |= IS_OFS;
break;
}
let before = drive.taskfile_registers().status;
let mut left = block;
while left > 0 {
let want = core::cmp::min(left as usize, CHUNK);
let Some((addr, n)) = prdt.take(self, space, want) else {
trouble |= IS_OFS;
break;
};
let did = if out {
if space
.read_bytes(addr, &mut scratch[..n], self.attrs())
.is_err()
{
trouble |= IS_HBFS;
break;
}
drive.taskfile_write(&scratch[..n])
} else {
let got = drive.taskfile_read(&mut scratch[..n]) as usize;
if space
.write_bytes(addr, &scratch[..got], self.attrs())
.is_err()
{
trouble |= IS_HBFS;
break;
}
got as u64
};
if did == 0 {
trouble |= IS_IFS;
break;
}
prdt.advance(did as usize);
moved += did;
left -= did;
}
if trouble != 0 {
break;
}
let regs = drive.taskfile_registers();
if !dma {
pio = true;
let interrupt = drive.taskfile_acknowledge();
if job.receiving {
self.post_fis(
job.fb + PSFIS_AT,
&pio_setup_fis(®s, before, regs.status, !out, block as u16, interrupt),
);
}
let mut state = self.state.lock();
let port = &mut state.ports[job.port];
port.tfd = tfd_of(®s);
if interrupt {
port.is |= IS_PSS;
}
}
phase = drive.taskfile_phase();
}
let mut write_overflow = false;
if trouble & IS_OFS != 0 {
match drive.taskfile_phase() {
Phase::Data { out: false, .. } => self.discard(drive),
Phase::Data { out: true, .. } => write_overflow = true,
Phase::Done => {}
}
}
let regs = drive.taskfile_registers();
let interrupt = drive.taskfile_acknowledge();
let mut prdbc = [0u8; 4];
prdbc.copy_from_slice(&(moved as u32).to_le_bytes());
let bc_at = job.clb + u64::from(job.slot) * HEADER_LEN + 4;
if space.write_bytes(bc_at, &prdbc, self.attrs()).is_err() {
trouble |= IS_HBFS;
}
if !pio && job.receiving {
self.post_fis(job.fb + RFIS_AT, &d2h_fis(®s, interrupt));
}
let failed = regs.status & ST_ERR != 0;
let mut raised = trouble;
if failed {
raised |= IS_TFES;
}
let mut state = self.state.lock();
let port = &mut state.ports[job.port];
port.tfd = tfd_of(®s);
if prdt.fired {
port.is |= IS_DPS;
}
if !pio && interrupt {
port.is |= IS_DHRS;
}
port.is |= raised;
if raised & FATAL != 0 || write_overflow {
port.cmd &= !CMD_CR;
} else {
port.ci &= !(1 << job.slot);
}
}
fn discard(&self, drive: &Arc<AtaDisk>) {
let mut scratch = [0u8; CHUNK];
for _ in 0..MAX_BLOCKS {
match drive.taskfile_phase() {
Phase::Data { out: false, .. } => {
if drive.taskfile_read(&mut scratch) == 0 {
return;
}
}
_ => return,
}
}
}
}
impl Hba {
pub fn save<S: Sink + ?Sized>(&self, w: &mut S) -> Result<()> {
let state = *self.state.lock();
w.write_u32(state.ghc)?;
w.write_u32(state.is)?;
w.write_u64(self.ports.len() as u64)?;
for port in state.ports.iter().take(self.ports.len()) {
w.write_u64(port.clb)?;
w.write_u64(port.fb)?;
w.write_u32(port.is)?;
w.write_u32(port.ie)?;
w.write_u32(port.cmd)?;
w.write_u32(port.tfd)?;
w.write_u32(port.sctl)?;
w.write_u32(port.serr)?;
w.write_u32(port.sact)?;
w.write_u32(port.ci)?;
w.write_u32(port.sig)?;
w.write_u32(port.parked)?;
w.write_u8(port.ctl)?;
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, r: &mut S) -> Result<()> {
let ghc = r.read_u32()?;
let is = r.read_u32()?;
let count = r.read_u64()?;
if count != self.ports.len() as u64 {
return Err(crate::core::error::Error::State(alloc::format!(
"the snapshot has {count} port(s) and this adapter has {}",
self.ports.len()
)));
}
let mut ports = [PortState::new(); MAX_PORTS];
for port in ports.iter_mut().take(self.ports.len()) {
port.clb = r.read_u64()?;
port.fb = r.read_u64()?;
port.is = r.read_u32()?;
port.ie = r.read_u32()?;
port.cmd = r.read_u32()?;
port.tfd = r.read_u32()?;
port.sctl = r.read_u32()?;
port.serr = r.read_u32()?;
port.sact = r.read_u32()?;
port.ci = r.read_u32()?;
port.sig = r.read_u32()?;
port.parked = r.read_u32()?;
port.ctl = r.read_u8()?;
port.cmd = (port.cmd & (CMD_WRITABLE | CMD_CR | CMD_FR)) | CMD_SUD | CMD_POD;
if port.cmd & CMD_FRE == 0 {
port.cmd &= !CMD_FR;
}
}
{
let mut state = self.state.lock();
state.ghc = GHC_AE | (ghc & GHC_IE);
state.is = is;
state.ports = ports;
}
self.refresh_irq();
Ok(())
}
}
impl MemOps for Hba {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
if offset.saturating_add(dst.len() as u64) > REGISTER_LEN {
return Err(BusError::BadAccess);
}
match dst.len() {
4 => {
if !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
dst.copy_from_slice(&self.read_dword(offset).to_le_bytes());
}
8 => {
if !offset.is_multiple_of(8) {
return Err(BusError::BadAccess);
}
let value = u64::from(self.read_dword(offset))
| (u64::from(self.read_dword(offset + 4)) << 32);
dst.copy_from_slice(&value.to_le_bytes());
}
_ => return Err(BusError::BadAccess),
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
if offset.saturating_add(src.len() as u64) > REGISTER_LEN {
return Err(BusError::BadAccess);
}
match src.len() {
4 => {
if !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
self.write_dword(offset, le32(src));
}
8 => {
if !offset.is_multiple_of(8) {
return Err(BusError::BadAccess);
}
self.write_dword(offset, le32(&src[0..4]));
self.write_dword(offset + 4, le32(&src[4..8]));
}
_ => return Err(BusError::BadAccess),
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO
.with_widths(Width::U32, Width::U64)
.with_natural_alignment(true)
.with_endian(Endian::Little)
}
}
#[must_use]
pub fn structure_sizes() -> (u64, u64) {
(COMMAND_LIST_LEN, RECEIVED_FIS_LEN)
}
#[must_use]
pub fn port_offset(index: usize) -> u64 {
PORT_BASE + index as u64 * PORT_STRIDE
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod probe {
use super::*;
#[test]
fn the_register_window_holds_every_port() {
assert!(port_offset(MAX_PORTS - 1) + PORT_STRIDE <= REGISTER_LEN);
assert!(REGISTER_LEN.is_power_of_two());
}
#[test]
fn the_command_table_layout_matches_the_specification() {
assert_eq!(PRDT_OFFSET, 64 + 16 + 48);
assert_eq!(COMMAND_LIST_LEN, 1024);
assert_eq!(RECEIVED_FIS_LEN, 256);
}
#[test]
fn a_vector_of_zero_ports_still_reports_something_legal() {
let hba = Hba::new(Vec::new());
assert_eq!(hba.pi(), 0);
assert_eq!(hba.cap() & 0x1f, 0);
}
}