pub mod hba;
#[cfg(test)]
mod tests;
pub use hba::{AHCI_RANK, Hba, MAX_PORTS, REGISTER_LEN};
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::bus::pci::{Bar, Bars, Bdf, ConfigSpace, PciBus, PciFunction, buses, config};
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::{AddressSpace, MemAttrs, Perms, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::wire::WireSource;
use crate::dev::ata::bays;
use crate::machine::realize::{BindCtx, Instance};
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "ahci.hba";
const STATE_VERSION: u32 = 1;
pub const DEFAULT_BAY_PREFIX: &str = "sata";
pub mod pin {
pub const IRQ: &str = "irq";
}
const COMMAND_IMPLEMENTED: u16 = config::COMMAND_MEMORY | config::COMMAND_MASTER | COMMAND_INTX_OFF;
const COMMAND_INTX_OFF: u16 = 0x0400;
const STATUS_INTERRUPT: u8 = 0x08;
const CLASS_STORAGE: u8 = 0x01;
const SUBCLASS_SATA: u8 = 0x06;
const PROGIF_AHCI: u8 = 0x01;
const ABAR: u8 = 5;
struct Function {
config: Mutex<ConfigSpace>,
bars: Bars,
hba: Arc<Hba>,
}
impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Function");
match self.config.try_lock() {
Some(c) => s.field(
"command",
&(u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8),
),
None => s.field("command", &"<in use>"),
};
s.field("bars", &self.bars).finish()
}
}
impl Function {
fn fresh_config(vendor: u16, device: u16, revision: u8) -> ConfigSpace {
let mut c = ConfigSpace::new();
c.hardwire(config::VENDOR_ID, u32::from(vendor), 2);
c.hardwire(config::DEVICE_ID, u32::from(device), 2);
c.hardwire(config::COMMAND, 0x0000, 2);
c.hardwire(config::STATUS, 0x0200, 2);
c.hardwire(config::REVISION_ID, u32::from(revision), 1);
c.hardwire(config::CLASS_CODE, u32::from(PROGIF_AHCI), 1);
c.hardwire(config::CLASS_CODE + 1, u32::from(SUBCLASS_SATA), 1);
c.hardwire(config::CLASS_CODE + 2, u32::from(CLASS_STORAGE), 1);
c.hardwire(config::HEADER_TYPE, 0x00, 1);
c.hardwire(config::INTERRUPT_PIN, 0x01, 1);
c.allow(config::COMMAND, 2);
c.allow(config::CACHE_LINE_SIZE, 1);
c.allow(config::LATENCY_TIMER, 1);
c.allow(config::INTERRUPT_LINE, 1);
c
}
fn command(&self) -> u16 {
let c = self.config.lock();
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8
}
fn apply_command(&self, command: u16) {
self.hba.set_master(command & config::COMMAND_MASTER != 0);
self.hba.set_intx_disabled(command & COMMAND_INTX_OFF != 0);
}
}
impl PciFunction for Function {
fn config_read(&self, offset: u16, dst: &mut [u8], _attrs: MemAttrs) {
self.config.lock().read(offset, dst);
self.bars.config_read(offset, dst);
if self.hba.interrupt_pending() {
for (i, slot) in dst.iter_mut().enumerate() {
if offset.saturating_add(i as u16) == config::STATUS {
*slot |= STATUS_INTERRUPT;
}
}
}
if self.bars.is_stale() {
self.bars.sync(self.command(), false);
}
}
fn config_write(&self, offset: u16, src: &[u8], attrs: MemAttrs) {
if attrs.debug {
return;
}
let bars_moved = self.bars.config_write(offset, src);
let (command_moved, command) = {
let mut c = self.config.lock();
let moved = c.write(offset, src);
let raw =
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8;
let kept = raw & COMMAND_IMPLEMENTED;
if kept != raw {
c.set_byte(config::COMMAND, kept as u8);
c.set_byte(config::COMMAND + 1, (kept >> 8) as u8);
}
(
moved
&& offset < config::COMMAND + 2
&& offset.saturating_add(src.len() as u16) > config::COMMAND,
kept,
)
};
if command_moved {
self.apply_command(command);
}
if bars_moved || command_moved || self.bars.is_stale() {
self.bars.sync(command, false);
}
}
}
#[derive(Debug)]
pub struct Ahci {
regs: Arc<Function>,
hba: Arc<Hba>,
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
}
impl Ahci {
pub fn new(props: &Props) -> Result<Ahci> {
let mut r = props.reader();
let bus_name = r.or_str("bus", "pci0")?.to_string();
let device_no = r.or_range("device", 5u64, 0..=u64::from(crate::bus::pci::MAX_DEVICE))?;
let function_no = r.or_range(
"function",
0u64,
0..=u64::from(crate::bus::pci::MAX_FUNCTION),
)?;
let vendor = r.or_range("vendor-id", 0x1234u64, 0..=0xffff)?;
let device = r.or_range("device-id", 0x2922u64, 0..=0xffff)?;
let revision = r.or_range("revision", 0u64, 0..=255)?;
let ports = r.or_range("ports", 1u64, 1..=MAX_PORTS as u64)?;
let prefix = r.or_str("bays", DEFAULT_BAY_PREFIX)?.to_string();
r.finish()?;
let mut table: Vec<(String, Arc<bays::Bay>)> = Vec::new();
for index in 0..ports {
let name = format!("{prefix}{index}");
let bay = bays::attach(props, &name)?;
table.push((name, bay));
}
let bus = buses::attach(props, &bus_name)?;
let at = Bdf::new(0, device_no as u8, function_no as u8)?;
Ahci::with_bus(bus, at, vendor as u16, device as u16, revision as u8, table)
}
pub fn with_bus(
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
bays: Vec<(String, Arc<bays::Bay>)>,
) -> Result<Ahci> {
let hba = Arc::new(Hba::new(bays));
let region: RegionRef = Arc::new(Region::io(
"ahci.abar",
REGISTER_LEN,
Arc::clone(&hba) as Arc<dyn crate::core::space::MemOps>,
));
let bars = Bars::new().with(ABAR, Bar::memory(REGISTER_LEN).decoding(region, Perms::RW))?;
Ok(Ahci {
regs: Arc::new(Function {
config: Mutex::with_rank(
LockRank::DEVICE,
Function::fresh_config(vendor, device, revision),
),
bars,
hba: Arc::clone(&hba),
}),
hba,
bus,
at,
vendor,
device,
revision,
})
}
#[must_use]
pub fn address(&self) -> Bdf {
self.at
}
#[must_use]
pub fn hba(&self) -> &Arc<Hba> {
&self.hba
}
#[must_use]
pub fn bars(&self) -> &Bars {
&self.regs.bars
}
#[must_use]
pub fn command(&self) -> u16 {
self.regs.command()
}
pub fn attach_space(
&self,
space: &Arc<AddressSpace>,
requester: crate::core::space::RequesterId,
) -> Result<()> {
self.hba.attach_space(space, requester);
self.regs.bars.install(space, self.regs.command())
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a Serial ATA host bus adapter as a PCI function: the AHCI 1.3.1 register block, \
the command list, the received-FIS area and a PRDT-walking bus master over ATA \
drives in named bays",
properties: &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the PCI fabric this adapter is on (default `pci0`)",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device number it answers at on bus 0 (default 5)",
},
PropertySpec {
name: "function",
kind: ValueKind::Uint,
required: false,
summary: "the function number, 0-7 (default 0)",
},
PropertySpec {
name: "vendor-id",
kind: ValueKind::Uint,
required: false,
summary: "the vendor identification (default 0x1234)",
},
PropertySpec {
name: "device-id",
kind: ValueKind::Uint,
required: false,
summary: "the device identification (default 0x2922)",
},
PropertySpec {
name: "revision",
kind: ValueKind::Uint,
required: false,
summary: "the revision identification byte (default 0)",
},
PropertySpec {
name: "ports",
kind: ValueKind::Uint,
required: false,
summary: "how many Serial ATA ports it implements, 1 to 8 (default 1)",
},
PropertySpec {
name: "bays",
kind: ValueKind::Str,
required: false,
summary: "the drive bay name prefix: port n looks in `<bays>n` (default `sata`)",
},
],
construct: |props| Ok(Box::new(Ahci::new(props)?)),
};
impl Device for Ahci {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
self.bus
.attach(self.at, Arc::clone(&self.regs) as Arc<dyn PciFunction>)
}
fn reset(&self, _kind: ResetKind) {
*self.regs.config.lock() = Function::fresh_config(self.vendor, self.device, self.revision);
self.regs.bars.reset();
self.hba.reset();
self.regs.bars.sync(self.regs.command(), true);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_bytes(self.regs.config.lock().bytes())?;
for value in self.regs.bars.latches() {
w.write_u32(value)?;
}
self.hba.save(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let config: &[u8] = r.read_bytes()?;
let mut latches = [0u32; Bars::COUNT as usize];
for slot in &mut latches {
*slot = r.read_u32()?;
}
{
let mut c = self.regs.config.lock();
*c = Function::fresh_config(self.vendor, self.device, self.revision);
c.restore(config);
}
self.regs.bars.set_latches(&latches);
self.hba.load(r)?;
let command = self.regs.command();
self.regs.apply_command(command);
self.regs.bars.sync(command, true);
Ok(())
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != pin::IRQ {
return Err(Error::Config {
at: String::from(port),
message: format!("an AHCI adapter drives `{}` and nothing else", pin::IRQ),
});
}
self.hba.connect_irq(source);
Ok(())
}
fn announce(&self, _port: &str) {
self.hba.refresh_irq();
}
}
impl Instance for Ahci {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: String::from(
"an AHCI adapter masters the memory its command lists live in, and places its \
register block with a base address register: add `space = mem` to the object",
),
})?;
self.attach_space(space, ctx.requester())
}
}
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(Ahci::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(
PropSchema::new("device", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_DEVICE)),
)
.prop(
PropSchema::new("function", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_FUNCTION)),
)
.prop(PropSchema::new("vendor-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("device-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("revision", ValueKind::Uint).range(0, 255))
.prop(PropSchema::new("ports", ValueKind::Uint).range(1, MAX_PORTS as u64))
.prop(PropSchema::new("bays", ValueKind::Str))
.port(pin::IRQ, PortDir::Out)
}