From 318a7a1fa078e5d198bd2b045710726856fcac89 Mon Sep 17 00:00:00 2001 From: Orycterope Date: Sun, 10 Mar 2019 01:00:53 +0000 Subject: [PATCH 1/5] pci: documenting private items --- ahci/src/pci.rs | 37 ++++++++++++++++++++++++++++++------- libuser/src/zero_box.rs | 1 + 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/ahci/src/pci.rs b/ahci/src/pci.rs index a85c43df5..288b66234 100644 --- a/ahci/src/pci.rs +++ b/ahci/src/pci.rs @@ -13,7 +13,23 @@ pub const CONFIG_DATA: u16 = 0xCFC; /// A struct tying the two pci config ports together. struct PciConfigPortsPair { + /// The address port. + /// + /// Write the '''address''' of the config-space register you want to access. + /// + /// An address is formatted as follow: + /// + /// * 31 Enable bit + /// * 30:24 Reserved + /// * 23:16 Bus Number + /// * 15:11 Device Number + /// * 10:8 Function Number + /// * 7:0 Register Offset address: Pio, + /// The data port. + /// + /// After having put the address of the register you want in `.address`, + /// read this port to retrieve its value. data: Pio } @@ -34,7 +50,7 @@ const MAX_REGISTER: u8 = 63; /// A pci device, addressed by its bus number, slot, and function. #[derive(Debug, Copy, Clone)] -#[allow(missing_docs)] +#[allow(clippy::missing_docs_in_private_items)] struct PciDevice { /// The device's bus number. bus: u8, @@ -67,6 +83,7 @@ struct PciDevice { /// Pci header when Header Type == 0x00 (General device). #[derive(Copy, Clone, Debug)] +#[allow(clippy::missing_docs_in_private_items)] struct PciHeader00 { bar0: BAR, bar1: BAR, @@ -88,17 +105,23 @@ struct PciHeader00 { /// Contents of pci config registers 0x4-0xf, structure varies based on Header Type. #[derive(Copy, Clone, Debug)] enum PciHeader { - GeneralDevice(PciHeader00), // header type == 0x00 - PCItoPCIBridge, // header type == 0x01, not implemented - CardBus, // header type == 0x02, not implemented - UnknownHeaderType(u8) // header type == other + /// header type == 0x00 + GeneralDevice(PciHeader00), + /// header type == 0x01, not implemented + PCItoPCIBridge, + /// header type == 0x02, not implemented + CardBus, + /// header type == other + UnknownHeaderType(u8) } /// Base Address Registers. Minimal implementation, does not support 64-bits BARs. #[derive(Copy, Clone, Debug)] enum BAR { - Memory(u32, u32), // a memory space address and its size - Io(u32, u32) // an IO space address + /// a memory space address and its size + Memory(u32, u32), + /// an IO space address + Io(u32, u32) } impl PciDevice { diff --git a/libuser/src/zero_box.rs b/libuser/src/zero_box.rs index 4d9d838f0..903523a98 100644 --- a/libuser/src/zero_box.rs +++ b/libuser/src/zero_box.rs @@ -9,6 +9,7 @@ use core::ops::{Deref, DerefMut}; #[repr(transparent)] #[derive(Debug)] pub struct ZeroBox { + /// The box we secretely wrap. owned_box: Box } From 97d2be0a6195058932da6cc092ba031a579cfb2c Mon Sep 17 00:00:00 2001 From: Orycterope Date: Sat, 9 Mar 2019 19:57:12 +0000 Subject: [PATCH 2/5] ahci driver Introducing the driver for AHCI disks, for ATA and SATA devices. Provides IPC endpoints to read and write some sectors. For now it is a very modest single-threaded driver, blocking on every request until the previous one is completed, but this is prone to change as AHCI supports up to 32 simultaneous outstanding commands. --- Cargo.lock | 2 + ahci/Cargo.toml | 2 + ahci/src/disk.rs | 225 ++++++++ ahci/src/fis.rs | 185 +++++++ ahci/src/hba.rs | 1219 ++++++++++++++++++++++++++++++++++++++++++ ahci/src/main.rs | 163 +++++- libuser/src/error.rs | 27 +- libuser/src/lib.rs | 3 +- 8 files changed, 1815 insertions(+), 11 deletions(-) create mode 100644 ahci/src/disk.rs create mode 100644 ahci/src/fis.rs create mode 100644 ahci/src/hba.rs diff --git a/Cargo.lock b/Cargo.lock index 0325a7f96..cbc748f2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,11 +99,13 @@ dependencies = [ name = "kfs-ahci" version = "0.1.0" dependencies = [ + "bitfield 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "kfs-libuser 0.1.0", "kfs-libutils 0.1.0", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/ahci/Cargo.toml b/ahci/Cargo.toml index 96223c3c7..8504588c1 100644 --- a/ahci/Cargo.toml +++ b/ahci/Cargo.toml @@ -10,6 +10,8 @@ kfs-libuser = { path = "../libuser" } kfs-libutils = { path = "../libutils" } spin = "0.4" log = "0.4.6" +bitfield = "0.13.1" +static_assertions = "0.3.1" [dependencies.lazy_static] features = ["spin_no_std"] diff --git a/ahci/src/disk.rs b/ahci/src/disk.rs new file mode 100644 index 000000000..e451cc1da --- /dev/null +++ b/ahci/src/disk.rs @@ -0,0 +1,225 @@ +//! AHCI Disk + +use alloc::sync::Arc; +use core::fmt::{self, Debug, Formatter}; + +use spin::Mutex; + +use kfs_libuser::error::{Error, AhciError}; +use kfs_libuser::types::SharedMemory; +use kfs_libuser::syscalls::MemoryPermissions; +use kfs_libuser::types::Handle; +use kfs_libuser::zero_box::ZeroBox; + +use crate::hba::*; + +/// An AHCI Disk +/// +/// Manages an AHCI port, and provides functions to read and write sectors. +/// +/// # Memory +/// +/// A disk is responsible for all allocated memory in use by the port. When dropped, the port +/// is put to a stop, pointers to these regions are cleared from the hardware, and the regions +/// are eventually de-allocated. +/// +/// # Lifetime +/// +/// A Disk holds a reference to its `Port Control Registers`, +/// which is located in the root `HBA Memory Registers` mapping (the one found at `BAR5`). +/// +/// As this root mapping will never be unmapped, the lifetime of this reference is `'static`. +pub struct Disk { + + // memory zones + + /// Pointer back to the corresponding Port Control Registers, found at `BAR5[100h]`-`BAR5[10FFh]`. + pub(super) px: &'static mut Px, + /// The allocated Received FIS memory zone that the port uses. + pub(super) rfis: ZeroBox, + /// The allocated Command List memory zone that the port uses. + pub(super) cmd_list: ZeroBox, + /// An allocated Command Table for each implemented Command List slot. + pub(super) cmd_tables: [Option>; 32], + + // info obtained by the IDENTIFY command + + /// Number of addressable sectors of this disk. Each sector is 512 octets. + pub(super) sectors: u64, + /// Indicates if the device supports 48 bit addresses. + pub(super) supports_48_bit: bool, +} + +impl Disk { + /// Returns the number of addressable 512-octet sectors for this disk. + #[inline(never)] + fn sector_count(&self, ) -> Result { + Ok(self.sectors) + } + + /// Reads sectors from disk. + /// + /// Reads `sector_count` sectors starting from `lba`. + #[inline(never)] + fn read_dma(&mut self, buffer: *mut u8, buffer_len: usize, lba: u64, sector_count: u64) -> Result<(), Error> { + if (buffer_len as u64) < sector_count * 512 { + return Err(AhciError::InvalidArg.into()); + } + if lba.checked_add(sector_count).filter(|sum| *sum <= self.sectors).is_none() { + return Err(AhciError::InvalidArg.into()); + } + // todo: AHCI: Read CI and figure out which slot to use + // body: For now AHCI driver is single-threaded and blocking, + // body: which means that the first slot is always available for use. + // body: + // body: If we want to make a multi-threaded implementation, + // body: we will have to implement some logic to choose the slot. + let command_slot_index = 0; + unsafe { + // safe: - we just mapped buffer, so it is valid memory, + // and buffer_len is its length + // otherwise mapping it would have failed. + // - buffer[0..buffer_len] falls in a single mapping, + // we just mapped it. + // - command_slot_index is 0, which is always implemented (spec), + // and we give the cmd_header and cmd_table of this index. + // - px is initialised. + Px::read_dma( + buffer, + buffer_len, + lba, + sector_count, + self.px, + &mut self.cmd_list.slots[command_slot_index], + self.cmd_tables[command_slot_index].as_mut().unwrap(), + command_slot_index, + self.supports_48_bit + )? + } + Ok(()) + } + + /// Writes sectors to disk. + /// + /// Writes `sector_count` sectors starting from `lba`. + #[inline(never)] + fn write_dma(&mut self, buffer: *mut u8, buffer_len: usize, lba: u64, sector_count: u64) -> Result<(), Error> { + if (buffer_len as u64) < sector_count * 512 { + return Err(AhciError::InvalidArg.into()); + } + if lba.checked_add(sector_count).filter(|sum| *sum <= self.sectors).is_none() { + return Err(AhciError::InvalidArg.into()); + } + let command_slot_index = 0; + unsafe { + // safe: - we just mapped buffer, so it is valid memory, + // and buffer_len is its length + // otherwise mapping it would have failed. + // - buffer[0..buffer_len] falls in a single mapping, + // we just mapped it. + // - command_slot_index is 0, which is always implemented (spec), + // and we give the cmd_header and cmd_table of this index. + // - px is initialised. + Px::write_dma( + buffer, + buffer_len, + lba, + sector_count, + self.px, + &mut self.cmd_list.slots[command_slot_index], + self.cmd_tables[command_slot_index].as_mut().unwrap(), + command_slot_index, + self.supports_48_bit + )? + } + Ok(()) + } +} + +impl Drop for Disk { + /// Dropping a disk brings the port to a stop, and clears the pointers from the hardware. + fn drop(&mut self) { + self.px.stop(); + self.px.clear_addresses(); + } +} + +impl Debug for Disk { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("Disk") + .field("sectors", &self.sectors) + .field("px", &self.px) + .finish() + } +} + +/// Interface to a disk. +#[derive(Debug, Clone)] +pub struct IDisk(Arc>); + +impl IDisk { + /// Creates an IDisk from the wrapped [Disk]. + pub fn new(value: Arc>) -> Self { + Self(value) + } +} + +object! { + impl IDisk { + /// Returns the number of addressable 512-octet sectors for this disk. + #[cmdid(0)] + fn sector_count(&mut self,) -> Result<(u64,), Error> { + Ok((self.0.lock().sectors,)) + } + + /// Reads sectors from disk. + /// + /// Reads `sector_count` sectors starting from `lba`. + /// + /// # Error + /// + /// - InvalidArg: + /// - `mapping_size` does not reflect the passed handle's size, or mapping it failed, + /// - `lba`, `sector_count`, or `lba + sector_count` is higher than the number of + /// addressable sectors on this disk, + /// - `sector_count` == 0. + /// - BufferTooScattered: + /// - The passed handle points to memory that is so physically scattered it overflows + /// the PRDT. This can only happen for read/writes of 1985 sectors or more. + /// You should consider retrying with a smaller `sector_count`. + #[cmdid(1)] + fn read_dma(&mut self, handle: Handle, mapping_size: u64, lba: u64, sector_count: u64,) -> Result<(), Error> { + let sharedmem = SharedMemory(handle); + let addr = kfs_libuser::mem::find_free_address(mapping_size as _, 0x1000)?; + let mapped = sharedmem.map(addr, mapping_size as _, MemoryPermissions::empty()) + // no need for permission, only the disk will dma to it. + .map_err(|_| AhciError::InvalidArg)?; + self.0.lock().read_dma(mapped.as_mut_ptr(), mapped.len(), lba, sector_count) + } + + /// Writes sectors to disk. + /// + /// Writes `sector_count` sectors starting from `lba`. + /// + /// # Error + /// + /// - InvalidArg: + /// - `mapping_size` does not reflect the passed handle's size, or mapping it failed, + /// - `lba`, `sector_count`, or `lba + sector_count` is higher than the number of + /// addressable sectors on this disk, + /// - `sector_count` == 0. + /// - BufferTooScattered: + /// - The passed handle points to memory that is so physically scattered it overflows + /// the PRDT. This can only happen for read/writes of 1985 sectors or more. + /// You should consider retrying with a smaller `sector_count`. + #[cmdid(2)] + fn write_dma(&mut self, handle: Handle, mapping_size: u64, lba: u64, sector_count: u64,) -> Result<(), Error> { + let sharedmem = SharedMemory(handle); + let addr = kfs_libuser::mem::find_free_address(mapping_size as _, 0x1000)?; + let mapped = sharedmem.map(addr, mapping_size as _, MemoryPermissions::empty()) + // no need for permission, only the disk will dma to it. + .map_err(|_| AhciError::InvalidArg)?; + self.0.lock().write_dma(mapped.as_mut_ptr(), mapped.len(), lba, sector_count) + } + } +} diff --git a/ahci/src/fis.rs b/ahci/src/fis.rs new file mode 100644 index 000000000..256039e33 --- /dev/null +++ b/ahci/src/fis.rs @@ -0,0 +1,185 @@ +//! Frame Information Structures +//! +//! A FIS is a packet or frame of information that is transferred between the host and device. +//! Refer to the Serial ATA specification for more information. + +#![allow(clippy::missing_docs_in_private_items)] // just read the spec, ok !? + +use kfs_libuser::io::Mmio; + +/// The types of a FIS. +/// +/// Stored on byte 0 of every FIS, determines the length of the structure to be read. +#[repr(u8)] +pub enum FisType { + /// Register FIS - host to device + RegH2D = 0x27, + /// Register FIS - device to host + RegD2H = 0x34, + /// DMA activate FIS - device to host + DmaAct = 0x39, + /// DMA setup FIS - bidirectional + DmaSetup = 0x41, + /// Data FIS - bidirectional + Data = 0x46, + /// BIST activate FIS - bidirectional + Bist = 0x58, + /// PIO setup FIS - device to host + PioSetup = 0x5F, + /// Set device bits FIS - device to host + DevBits = 0xA1 +} + +/// Register FIS - host to device +/// +/// `fis_type` must be set to 0x27. +#[repr(packed)] +pub struct FisRegH2D { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_REG_H2D + + pub pm: Mmio, // Port multiplier, 1: Command, 0: Control + + pub command: Mmio, // Command register + pub featurel: Mmio, // Feature register, 7:0 + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub featureh: Mmio, // Feature register, 15:8 + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub icc: Mmio, // Isochronous command completion + pub control: Mmio, // Control register + + // DWORD 4 + pub rsv1: [Mmio; 4], // Reserved +} + +/// Register FIS - device to host +#[repr(packed)] +pub struct FisRegD2H { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_REG_D2H + + pub pm: Mmio, // Port multiplier, Interrupt bit: 2 + + pub status: Mmio, // Status register + pub error: Mmio, // Error register + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub rsv2: Mmio, // Reserved + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub rsv3: [Mmio; 2], // Reserved + + // DWORD 4 + pub rsv4: [Mmio; 4], // Reserved +} + +/// Data FIS - bidirectional +#[repr(packed)] +pub struct FisData { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_DATA + + pub pm: Mmio, // Port multiplier + + pub rsv1: [Mmio; 2], // Reserved + + // DWORD 1 ~ N + pub data: [Mmio; 252], // Payload +} + +/// PIO setup FIS - device to host +#[repr(packed)] +pub struct FisPioSetup { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_PIO_SETUP + + pub pm: Mmio, // Port multiplier, direction: 4 - device to host, interrupt: 2 + + pub status: Mmio, // Status register + pub error: Mmio, // Error register + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub rsv2: Mmio, // Reserved + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub rsv3: Mmio, // Reserved + pub e_status: Mmio, // New value of status register + + // DWORD 4 + pub tc: Mmio, // Transfer count + pub rsv4: [Mmio; 2], // Reserved +} + +/// DMA setup FIS - bidirectional +#[repr(packed)] +pub struct FisDmaSetup { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_DMA_SETUP + + pub pm: Mmio, // Port multiplier, direction: 4 - device to host, interrupt: 2, auto-activate: 1 + + pub rsv1: [Mmio; 2], // Reserved + + // DWORD 1&2 + pub dma_buffer_id: Mmio, /* DMA Buffer Identifier. Used to Identify DMA buffer in host memory. SATA Spec says host specific and not in Spec. Trying AHCI spec might work. */ + + // DWORD 3 + pub rsv3: Mmio, // More reserved + + // DWORD 4 + pub dma_buffer_offset: Mmio, // Byte offset into buffer. First 2 bits must be 0 + + // DWORD 5 + pub transfer_count: Mmio, // Number of bytes to transfer. Bit 0 must be 0 + + // DWORD 6 + pub rsv6: Mmio, // Reserved +} + +/// Set device bits FIS - device to host +#[repr(packed)] +pub struct FisSetDeviceBits { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_DMA_SETUP + pub i: Mmio, // interrupt bit, 6 + pub status: Mmio, // status hi 6:4, status lo 2:0 + pub error: Mmio, // error 7:0 + + // DWORD 1 + pub _rsv: Mmio, // Reserved +} diff --git a/ahci/src/hba.rs b/ahci/src/hba.rs new file mode 100644 index 000000000..b2d1d9ae6 --- /dev/null +++ b/ahci/src/hba.rs @@ -0,0 +1,1219 @@ +//! HBA structures +//! +//! Based on [Serial ATA AHCI: Specification, Rev. 1.3.1]. +//! In this module, "See spec section N" makes reference to this document. +//! +//! [Serial ATA AHCI: Specification, Rev. 1.3.1]: http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/serial-ata-ahci-spec-rev1-3-1.pdf + +use kfs_libuser::io::{Io, Mmio}; +use kfs_libuser::syscalls::{sleep_thread, query_physical_address}; +use kfs_libuser::mem::{map_mmio, virt_to_phys}; +use kfs_libuser::error::{Error, AhciError}; +use kfs_libuser::zero_box::*; +use core::fmt::{self, Debug, Formatter}; +use core::mem::size_of; +use core::cmp::min; +use core::time::Duration; +use alloc::prelude::*; +use crate::fis::*; +use crate::disk::Disk; +use static_assertions::assert_eq_size; + +// ---------------------------------------------------------------------------------------------- // +// Hba // +// ---------------------------------------------------------------------------------------------- // + +/// HBA memory registers. +/// +/// See spec section 3.1 +/// +/// Found at address in pci configuration register `BAR5`. +#[allow(clippy::missing_docs_in_private_items)] +#[repr(packed)] +pub struct HbaMemoryRegisters { + generic_host_control: GenericHostControl, // 0x00 - 0x2b, generic host control registers + _rsv: [Mmio; 116], // 0x2c - 0x9f, reserved + _rsv_vendor: [Mmio; 96], // 0xa0 - 0xff, vendor specific registers + ports: [Px; 32], // 0x0100 - 0x10ff, port control registers +} + +/// HBA Generic Host Control. +/// +/// See spec section 3.1 +/// +/// Found at address in pci configuration register `BAR5[0x00]-BAR5[0x2B]`. +#[allow(clippy::missing_docs_in_private_items)] +#[repr(packed)] +pub struct GenericHostControl { + cap: Mmio, // 0x00, host capability + ghc: Mmio, // 0x04, global host control + is: Mmio, // 0x08, interrupt status + pi: Mmio, // 0x0c, port implemented + vs: Mmio, // 0x10, version + ccc_ctl: Mmio, // 0x14, command completion coalescing control + ccc_pts: Mmio, // 0x18, command completion coalescing ports + em_loc: Mmio, // 0x1c, enclosure management location + em_ctl: Mmio, // 0x20, enclosure management control + cap2: Mmio, // 0x24, host capabilities extended + bohc: Mmio, // 0x28, bios/os handoff control and status +} + +impl Debug for GenericHostControl { + /// Debug does not access reserved registers. + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("HbaMem") + .field("cap", &self.cap) + .field("ghc", &self.ghc) + .field("is", &self.is) + .field("pi", &self.pi) + .field("vs", &self.vs) + .field("ccc_ctl", &self.ccc_ctl) + .field("ccc_pts", &self.ccc_pts) + .field("em_loc", &self.em_loc) + .field("em_ctl", &self.em_ctl) + .field("cap2", &self.cap2) + .field("bohc", &self.bohc) +// .field("vendor", &"-omitted-") +// .field("ports", &self.iter_ports()) + .finish() + } +} + +bitfield!{ + /// HbaMem.CAP "HBA Capabilities" register bitfield. + /// + /// Defined in section 3.1.1 + #[derive(Clone, Copy)] + struct CAP(u32); + impl Debug; + s64a, _: 31; + sncq, _: 30; + ssntf, _: 29; + smps, _: 28; + sss, _: 27; + salp, _: 26; + sal, _: 25; + sclo, _: 24; + iss, _: 23, 20; + // 19 reserved + sam, _: 18; + spm, _: 17; + fbss, _: 16; + pmd, _: 15; + scc, _: 14; + psc, _: 13; + ncs, _: 12, 8; + cccs, _: 7; + ems, _: 6; + sxs, _: 5; + np, _: 4, 0; +} + +bitfield!{ + /// HbaMem.GHC "Global HBA Control" register bitfield. + /// + /// Defined in section 3.1.2 + #[derive(Clone, Copy)] + struct GHC(u32); + impl Debug; + ae, set_ae: 31; + // 30:03 reserved + mrsm, _: 2; + ie, set_ie: 1; + hr, set_hr: 0; +} + +bitfield!{ + /// HbaMem.CAP2 "HBA Capabilities Extended" register bitfield. + /// + /// Defined in section 3.1.10 + #[derive(Clone, Copy)] + struct CAP2(u32); + impl Debug; + // 31:06 reserved + deso, _: 5; + sadm, _: 4; + sds, _: 3; + apst, _: 2; + nvmp, _: 1; + boh, _: 0; +} + +/* +/// An iterator that yields only implemented ports, according to `PI`. +/// +/// Returns (`port_index`, `reference_to_port`). +pub struct PortIterator<'a> { + ports: &'a mut [Px; 32], + pi: u32, + pos: usize, +} + +impl<'a> Iterator for PortIterator<'a> { + type Item = (usize, &'a mut Px); + + fn next(&mut self) -> Option<::Item> { + while self.pos < 32 { + if (self.pi & (1 << self.pos)) != 0 { + // this port is implemented + self.pos += 1; + return Some((self.pos - 1, &mut self.ports[self.pos - 1])); + } + self.pos += 1; + } + None + } +} + +impl Debug for PortIterator<'_> { + /// Debug prints the implemented port array. + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + f.debug_list() + .entries(self.clone()) + .finish() + } +} +*/ + +impl HbaMemoryRegisters { + /// Initializes an AHCI Controller. + /// + /// `BAR5` is the physical address of HBA Memory Register, usually obtained via PCI discovery. + /// + /// This function will map the root Mmio region, allocate memory for every 'implemented' port, + /// put them in the running state, and return an interface to the plugged devices which is up and running. + /// + /// # Error + /// + /// If an error occurred, this function will stop trying to initialize the HBA and return an + /// empty Vec as if no disks were found. This can happen if: + /// + /// * mapping `BAR5` failed. + /// * GHC.AE is not set (controller is in legacy support mode), because conditions specified in + /// section 10.2 are tedious. + pub fn init(bar5: usize) -> Vec { + let mapping = match map_mmio::(bar5 as _) { + Ok(vaddr) => vaddr, + Err(e) => { + error!("HBA {:#010x}, initialization failed: failed mapping BAR5, {:?}.", bar5, e); + return Vec::new() + } + }; + let ghc_registers = unsafe { + // constructing a reference to the Generic Host Control registers we just mapped. + // safe, nobody has a reference to it yet. + &mut (*mapping).generic_host_control + }; + // check that system software is AHCI aware by setting GHC.AE to ‘1’. + if !ghc_registers.ghc.read().ae() { + error!("HBA {:#010x}, initialization failed: controller is in legacy support mode.", bar5); + return Vec::new(); + } + + // globally disable interrupts for this controller + let mut ghc = ghc_registers.ghc.read(); + ghc.set_ie(false); + ghc_registers.ghc.write(ghc); + + let command_list_len = ghc_registers.cap.read().ncs() as usize + 1; + let pi = ghc_registers.pi.read(); + + let port_registers = unsafe { + // constructing a reference to the port registers we previously mapped. + // safe, it is the only reference to this memory area. + // the reference's lifetime is tied to the returned Disk structure, + // which will never outlive the mapping. + &mut (*mapping).ports + }; + // Initialize ports marked implemented by PI. + port_registers.iter_mut() + .enumerate() + // filter out ports not implemented + .filter(|(index, _)| (pi & (1u32 << index)) != 0) + // init each port, keep only successful ones + .filter_map(|(_port_index, px)| Px::init(px, command_list_len)) + // put that in a vec + .collect() + } +} + +// ---------------------------------------------------------------------------------------------- // +// Port Registers // +// ---------------------------------------------------------------------------------------------- // + +/// HBA Memory Port registers. +/// +/// See spec section 3.3 +/// +/// An array of 1 to 32 Px is found at BAR5 + 0x0100-0x10FF. +/// +/// The list of ports that are actually implemented can be found in `HBA.PI`. +/// Px not implemented must never be accessed. +#[allow(clippy::missing_docs_in_private_items)] +#[repr(packed)] +pub struct Px { + clb: Mmio, // 0x00, command list base address, 1K-byte aligned + fb: Mmio, // 0x08, FIS base address, 256-byte aligned + is: Mmio, // 0x10, interrupt status + ie: Mmio, // 0x14, interrupt enable + cmd: Mmio, // 0x18, command and status + _rsv0: Mmio, // 0x1C, Reserved + tfd: Mmio, // 0x20, task file data + sig: Mmio, // 0x24, signature + ssts: Mmio, // 0x28, SATA status (SCR0:SStatus) + sctl: Mmio, // 0x2C, SATA control (SCR2:SControl) + serr: Mmio, // 0x30, SATA error (SCR1:SError) + sact: Mmio, // 0x34, SATA active (SCR3:SActive) + ci: Mmio, // 0x38, command issue + sntf: Mmio, // 0x3C, SATA notification (SCR4:SNotification) + fbs: Mmio, // 0x40, FIS-based switch control + _rsv1: [Mmio; 11], // 0x44 ~ 0x6F, Reserved + vendor: [Mmio; 4], // 0x70 ~ 0x7F, vendor specific +} + +bitfield!{ + /// `PxIS` "Port x Interrupt status" register bitfield. + /// + /// A '1' indicates a pending interrupt. Write '1' to clear. + /// Refer to spec for actions that must be taken on each interrupt. + /// + /// Defined in section 3.3.5 + #[derive(Clone, Copy)] + struct PxIS(u32); + impl Debug; + cpds, set_cpds: 31; + tfes, set_tfes: 30; + hbfs, set_hbfs: 29; + hbds, set_hbds: 28; + ifs, set_ifs : 27; + infs, set_infs: 26; + // 25 reserved + ofs, set_ofs : 24; + ipms, set_ipms: 23; + prcs, _: 22; + // 21:08 reserved + dmps, set_dmps: 7; + pcs, _: 6; + dps, set_dps : 5; + ufs, _: 4; + sbds, set_sbds: 3; + dss, set_dss : 2; + pss, set_pss : 1; + dhrs, set_dhrs: 0; +} + +impl PxIS { + /// Checks if a PxIS has set any of the bits corresponding to an error. + fn is_err(self) -> bool { + self.tfes() || self.hbfs() || self.hbds() || self.ifs() || self.infs() || self.ofs() + } +} + +bitfield!{ + /// `PxIE` "Port x Interrupt Enable" register bitfield. + /// + /// This register enables and disables the reporting of the corresponding interrupt to system software. + /// When a bit is set (‘1’) and the corresponding interrupt condition is active, + /// then an interrupt is generated. + /// Interrupt sources that are disabled (‘0’) are still reflected in the status registers. + /// + /// This register is symmetrical with the PxIS register. + /// + /// Defined in section 3.3.6 + #[derive(Clone, Copy)] + struct PxIE(u32); + impl Debug; + cpde, set_cpde: 31; + tfee, set_tfee: 30; + hbfe, set_hbfe: 29; + hbde, set_hbde: 28; + ife, set_ife : 27; + infe, set_infe: 26; + // 25 reserved + ofe, set_ofe : 24; + ipme, set_ipme: 23; + prce, set_prce: 22; + // 21:08 reserved + dmpe, set_dmpe: 7; + pce, set_pce : 6; + dpe, set_dpe : 5; + ufe, set_ufe : 4; + sbde, set_sbde: 3; + dse, set_dse : 2; + pse, set_pse : 1; + dhre, set_dhre: 0; +} + +bitfield!{ + /// `PxCMD` "Port x Command and Status" register bitfield. + /// + /// Defined in section 3.3.7 + #[derive(Clone, Copy)] + struct PxCMD(u32); + impl Debug; + cmd, set_cmd : 31,28; + asp, set_asp : 27; + alpe, set_alpe : 26; + dlae, set_dlae : 25; + atapi, set_atapi: 24; + apste, set_apste: 23; + fbscp, _: 22; + esp, _: 21; + cpd, _: 20; + mpsp, _: 19; + hpcp, _: 18; + pma, set_pma : 17; + cps, _: 16; + cr, _: 15; + fr, _: 14; + mpss, _: 13; + ccs, _: 12,8; + // 07:05 reserved + fre, set_fre : 4; + clo, set_clo : 3; + pod, set_pod : 2; + sud, set_sud : 1; + st, set_st : 0; +} + +bitfield!{ + /// `PxTFD` "Port x Task File Data" register bitfield. + /// + /// Defined in section 3.3.8 + #[derive(Clone, Copy)] + struct PxTFD(u32); + impl Debug; + // 31:16 reserved + err, _: 15,8; + bsy, _: 7; + cs0, _: 6,4; + drq, _: 3; + cs1, _: 2,1; + err_flag, _: 0; +} + +bitfield!{ + /// `PxSSTS` "Port x Serial ATA Status" register bitfield. + /// + /// Defined in section 3.3.10 + #[derive(Clone, Copy)] + struct PxSSTS(u32); + impl Debug; + // 31:12 reserved + ipm, _: 11,8; + spd, _: 7,4; + det, _: 3,0; +} + +impl Debug for Px { + /// Debug does not access reserved registers. + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("HbaPort") + .field("PxCLB-PxCLBU", &self.clb) + .field("PxFB-PxFBU", &self.fb) + .field("PxIS", &self.is) + .field("PxIE", &self.ie) + .field("PxCMD", &self.cmd) + .field("PxTFD", &self.tfd) + .field("PxSIG", &self.sig) + .field("PxSSTS", &self.ssts) + .field("PxSCTL", &self.sctl) + .field("PxSERR", &self.serr) + .field("PxSACT", &self.sact) + .field("PxCI", &self.ci) + .field("PxSNTF", &self.sntf) + .field("PxFBS", &self.fbs) + .field("PxVS", &"-omitted-") + .finish() + } +} + +impl Px { + /// Stop this port. + /// + /// Clears `PxCMD.ST`, and waits for `PxCMD.CR` to be cleared. + pub fn stop(&mut self) { + // clear PxCMD.ST + let mut cmd = self.cmd.read(); + if cmd.st() || cmd.cr() { + cmd.set_st(false); + self.cmd.write(cmd); + // 1. wait for 500ms. + // 2. check PxCMD.CR is now cleared. + while { // ugly do-while loop + sleep_thread(Duration::from_millis(500).as_nanos() as _).unwrap(); // wait for 500ms. + self.cmd.read().cr() // == true + } {}; + } + // from now on PxCMD.ST = 0, PxCMD.CR = 0. + } + + /// Start this port. + /// + /// Makes `PxCLB` point to `command_list`, + /// sets `PxCMD.ST`, and waits for `PxCMD.CR` to be set. + /// + /// See section 10.3.1 for conditions to meet before calling this function. + /// + /// # Unsafety + /// + /// Port will continue updating frames pointed to by `command_list`. + /// To prevent that, `clear_addresses` must be called before `command_list` is outlived. + /// + /// # Panics + /// + /// * `PxCMD.FRE` is not set. + /// * `PxCMD.CR` is already set. + /// * A functional device is not present: `probe` returned false. + unsafe fn start(&mut self, command_list: &mut CmdHeaderArray) { + let mut cmd = self.cmd.read(); + assert_eq!(cmd.fre(), true, "Trying to start port: PxCMD.FRE is not set"); + assert_eq!(cmd.cr(), false, "Trying to start port: PxCMD.CR is already set"); + assert_eq!(self.probe(), true, "Trying to start port: No device detected"); + // write PxCLB + self.clb.write( + virt_to_phys(command_list) as u64 + ); + // set PxCMD.ST + cmd.set_st(true); + self.cmd.write(cmd); + // 1. wait for 500ms. + // 2. check PxCMD.CR is now cleared. + while { // ugly do-while loop + sleep_thread(Duration::from_millis(1).as_nanos() as _).unwrap(); // wait a bit. + !self.cmd.read().cr() // == false + } {}; + // from now on PxCMD.ST = 1, PxCMD.CR = 1. + } + + /// Disables FIS Receive. + /// + /// Clears `PxCMD.FRE`, and waits for `PxCMD.FR` to be cleared. + /// + /// See section 10.3.2 for conditions to meet before calling this function. + /// + /// # Panics + /// + /// * Port is running: `PxCMD.ST` or `PxCMD.CR` is set. + pub fn disable_fis_receive(&mut self) { + let mut cmd = self.cmd.read(); + assert_eq!(cmd.st() || cmd.cr(), false, "Trying to disable port FIS Receive: Port is running"); + if cmd.fre() || cmd.fr() { + // clear PxCMD.FRE + cmd.set_fre(false); + self.cmd.write(cmd); + // 1. wait for 500ms. + // 2. wait check PxCMD.FR is now cleared. + while { // ugly do-while loop + sleep_thread(Duration::from_millis(500).as_nanos() as _).unwrap(); + self.cmd.read().fr() // == true + } {}; + } + // from now on PxCMD.FRE = 0, PxCMD.FR = 0. + } + + /// Enable FIS Receive. + /// + /// Make `PxFB` point to `memory_area`, and sets `PxCMD.FRE`. + /// + /// # Unsafety + /// + /// Port will continue writing received FIS to `memory_area`. + /// To prevent that, `clear_addresses` must be called before `memory_area` is outlived. + /// + /// # Panics + /// + /// * Port is running: `PxCMD.ST` or `PxCMD.CR` is set. + /// * FIS Receive is already running: `PxCMD.FRE` or `PxCMD.FR` is set. + pub unsafe fn enable_fis_receive(&mut self, memory_area: &mut ReceivedFis) { + let mut cmd = self.cmd.read(); + assert_eq!(cmd.st() || cmd.cr(), false, "Trying to enable port FIS Receive: Port is running"); + assert_eq!(cmd.fre() || cmd.fr(), false, "Trying to enable port FIS Receive: FIS Receive is already running"); + self.fb.write( + virt_to_phys(memory_area) as u64 + ); + // set PxCMD.FRE + cmd.set_fre(true); + self.cmd.write(cmd); + // wait for PxCMD.FR to be set + while !self.cmd.read().fre() { + sleep_thread(Duration::from_millis(1).as_nanos() as _).unwrap(); + }; + // from now on PxCMD.FRE = 1 + } + + /// Checks if a functional device is present on the port. + /// + /// Determined by `PxTFD.STS.BSY == 0 && PxTFD.STS.DRQ == 0 && (PxSSTS.DET = 3 || PxSSTS.IPM == (2,6,8)` + fn probe(&self) -> bool { + let tfd = self.tfd.read(); + let sts = self.ssts.read(); + + !tfd.bsy() && !tfd.drq() + && (sts.det() == 3 || { match sts.ipm() { 2 | 6 | 8 => true, _ => false } }) + } + + /// Makes `PxFB` and `PxCLB` point to `0x00000000`. + /// + /// This ensures HBA will not write to frames we may no longer own. + /// + /// This function will stop the port and disable FIS Receive. + pub fn clear_addresses(&mut self) { + self.stop(); + self.disable_fis_receive(); + self.clb.write(0x00000000 as u64); + self.fb.write(0x00000000 as u64); + } + + /// Initializes a port, returning a [Disk] to interface with it. + /// + /// If the port is not connected to anything, or initialisation failed, + /// this function returns `None`. + fn init(port_registers: &'static mut Px, command_list_length: usize) -> Option { + port_registers.stop(); + port_registers.disable_fis_receive(); + if !port_registers.probe() { + // If we don't detect anything, don't bother initialising it, just return. + port_registers.clear_addresses(); + return None; + } + + port_registers.ie.write(PxIE(0x00)); // no interrupts + let mut received_fis = ZeroBox::::new_zeroed(); + unsafe { + // safe: when the Disk is dropped we make sure to call `clear_addresses`. + port_registers.enable_fis_receive(&mut *received_fis); + } + let mut cmd_list = ZeroBox::::new_zeroed(); + // init the command list + let mut cmd_tables: [Option>; 32] = Default::default(); + for (i, header) in cmd_list.slots[0..command_list_length].iter_mut().enumerate() { + let mut table = ZeroBox::::new_zeroed(); + unsafe { + // safe: - `table` has just been allocated, it is not pointed to by anyone else. + // - port is stopped. + header.init(&mut table); + } + cmd_tables[i] = Some(table); + } + unsafe { + // safe: when the port is dropped we make sure to call `clear_addresses`. + port_registers.start(&mut cmd_list); + } + // clear PxSERR by writing '1' to every non-reserved bit. + // would have done a bitfield, but it provides no "set all" function. + port_registers.serr.write(0b00000111111111110000111100000011u32); + + let (sectors, supports_48_bit) = unsafe { + // safe: - port is started, + // - index is 0, which is always implemented (required by spec), + // - no command has been issued yet, so CI is clear. + match Self::identify(port_registers, &mut cmd_list.slots[0], cmd_tables[0].as_mut().unwrap(), 0) { + Ok(x) => x, + Err(e) => { + error!("Initializing port failed: IDENTIFY DEVICE command failed. Error: {:?}. Status: {:?}", e, port_registers); + port_registers.stop(); + port_registers.disable_fis_receive(); + port_registers.clear_addresses(); + return None + } + } + }; + + Some(Disk { + px: port_registers, + rfis: received_fis, + cmd_list, + cmd_tables, + sectors, + supports_48_bit + }) + } + + /// Checks if the command issued in `slot` is still running. + /// + /// This function returns true either if the `slot` bit is cleared in `PxCI`, or if an error occurred. + fn command_running(&self, slot: usize) -> bool { + (self.ci.readf(1u32 << slot) || self.tfd.read().bsy()) && !self.is.read().is_err() + } + + /// Polls the port until the command in `slot` is completed, or an error occurred. + // todo: AHCI interrupts - command completion + // body: The AHCI driver does not make any use of AHCI interruptions. + // body: To check that a command has been completed, it polls the port repeatedly + // body: until the PxCI bit cleared. + // body: + // body: This is bad for performances, uses unnecessary time slices, and is an + // body: awful design. + // body: + // body: We need to figure out how to make AHCI interrupts work for us. + // body: + // body: The problem that we faced is the following: + // body: + // body: When enabled, after a command has completed, the HBA sends an irq to the PIC. + // body: The irq kernel-side top-half `acknowledges the irq` (sends EOI to the 8259A), + // body: and delays the actual handling to the userspace-side bottom-half. + // body: It then rets to the interrupted context, and re-enables interrupts by doing so. + // body: + // body: At this point the HBA, whose state has not been resolved, re-triggers an interrupt. + // body: + // body: Hence the kernel is spending 100% cpu time servicing an infinite irq + // body: and the OS completely freezes. + // body: + // body: This happens even while the 8359A is in `edge triggered mode`, which means that the + // body: PCI irq line is cycling through an HIGH and LOW, which is really odd according to the + // body: spec, where the irq are supposed to be "level sensitive". + // body: + // body: I think a little more digging up would be necessary to find out if we just configured + // body: something incorrectly, or if AHCI interrupts truly cannot be handled in the bottom-half. + // body: + // body: In the few open-source microkernels I reviewed that have an AHCI driver: + // body: + // body: - Redox does not implement interrupts (well ... just like us), + // body: - HelenOS seems to allow userspace drivers to have their own top-half routine. + // body: I don't know if they run in ring 0 or ring 3, how the page-tables switching is handled, + // body: but I'm really curious and will try to find out more about it. + // body: - Minix3 seems to be actually using interrupts, through its blockdriver multithreading framework. + // body: I should look this up. + fn wait_command_completion(&self, slot: usize) -> Result<(), Error> { + while self.command_running(slot) { + sleep_thread(0).unwrap(); + } + if self.is.read().is_err() { + Err(AhciError::IoError.into()) + } else { + Ok(()) + } + } + + /// Sends the IDENTIFY DEVICE command to a port to gather some information about it. + /// + /// # Returns + /// + /// - The number of addressable sectors this device possesses. + /// - Whether the device supports 48-bit addresses. + /// + /// # Unsafety + /// + /// * The port must be started + /// * `command_slot_index` must not have its bit set in `PxCI`. + /// * `command_header` and `command_table` must belong to `command_slot_index`'s command slot. + #[allow(clippy::cast_lossless)] // trust me, types won't change + unsafe fn identify(px: &mut Px, command_header: &mut CmdHeader, command_table: &mut CmdTable, command_slot_index: usize) -> Result<(u64, bool), Error> { + + /// The IDENTIFY DEVICE command. See ATA spec. + const ATA_CMD_IDENTIFY: u8 = 0xEC; + + /// The ouptut of the IDENTIFY command. + /// + /// Defined by ATA/ATAPI-5, with SATA modifications. + #[repr(C, align(2))] + struct IdentifyOutput { + /// Array of words, filled by the IDENTIFY command. + bytes: [u16; 256] + } + unsafe impl ZeroInitialized for IdentifyOutput {} + + let mut output = ZeroBox::::new_zeroed(); + + // write the FIS + let fis = &mut command_table.cfis.h2d; + fis.fis_type.write(FisType::RegH2D as u8); + fis.command.write(ATA_CMD_IDENTIFY); + fis.pm.write(1 << 7); // this is an update of the Command register + fis.device.write(0); + + // fill the prdt + unsafe { + // safe: `&mut output` points to valid memory. + command_table.fill_prdt(&mut *output as *mut _ as _, size_of::(), command_header)? + } + + // fill the command header + let mut ch_flags = CmdHeaderFlags(0); + ch_flags.set_c(true); + ch_flags.set_w(false); + ch_flags.set_cfl((size_of::() / 4) as u16); + command_header.flags.write(ch_flags); + + // set PxCI + px.ci.write(1u32 << command_slot_index); + px.wait_command_completion(command_slot_index)?; + + let mut supports_48_bit = true; + + // sectors count is found at output[100-103] for recent 48 bits devices, output[60-61] otherwise. + let mut sectors = (output.bytes[100] as u64) | + ((output.bytes[101] as u64) << 16) | + ((output.bytes[102] as u64) << 32) | + ((output.bytes[103] as u64) << 48); + if sectors == 0 { + sectors = (output.bytes[60] as u64) | ((output.bytes[61] as u64) << 16); + supports_48_bit = false; + }; + + Ok((sectors, supports_48_bit)) + } + + /// Read `sector_count` contiguous sectors from the disk into `buffer`. + /// + /// This function places a command in `command_slot_index`, signals it to the port, + /// and waits for an interruption indicating its completion. + /// + /// Based on `supports_48_bits_addresses`, this function will either use the + /// `READ DMA` or `READ DMA EXT` command. + /// + /// # Unsafety + /// + /// * `buffer` must point to valid memory. + /// * `buffer_len` must reflect `buffer`'s length. + /// * `buffer[0] - buffer[buffer_len - 1]` must fall in a single mapping. + /// * `command_slot_index` must be free to use, implemented, + /// and must point to `command_header` and `command_table`. + /// * `px` must be properly initialized. + /// + /// # Error + /// + /// * `buffer_len` < `sector_count * 512`. + /// * `sector_count` == 0. + /// * `sector_count` is greater than supported maximum (256 for 28-bit devices, 65536 for 48-bit ones). + /// * `lba + sector_count` is not representable on a 28-bit/48-bit address. + /// * all [fill_prdt] errors. + #[allow(clippy::too_many_arguments)] // heh + #[allow(clippy::missing_docs_in_private_items)] + pub unsafe fn read_dma( + buffer: *mut u8, + buffer_len: usize, + lba: u64, + sector_count: u64, + px: &mut Px, + command_header: &mut CmdHeader, + command_table: &mut CmdTable, + command_slot_index: usize, + supports_48_bit: bool) -> Result<(), Error> { + + const ATA_CMD_READ_DMA: u8 = 0xC8; + const ATA_CMD_READ_DMA_EXT: u8 = 0x25; + + if sector_count.checked_mul(512).filter(|sec_size| *sec_size <= (buffer_len as u64)).is_none() { + return Err(AhciError::InvalidArg.into()) + } + if sector_count == 0 || (!supports_48_bit && sector_count > 256) || sector_count > 65536 { + return Err(AhciError::InvalidArg.into()) + } + if (!supports_48_bit && lba.saturating_add(sector_count) >= (1u64 << 28)) + || lba.saturating_add(sector_count) >= (1u64 << 48) { + return Err(AhciError::InvalidArg.into()) + } + // write the FIS + let fis = &mut command_table.cfis.h2d; + fis.fis_type.write(FisType::RegH2D as u8); + fis.pm.write(1 << 7); // this is an update of the Command register + if !supports_48_bit { + // 28 bits + fis.command.write(ATA_CMD_READ_DMA); + fis.countl.write(sector_count as u8); // 0x00 means 256 sectors + fis.counth.write(0); + fis.lba0.write(lba as u8); + fis.lba1.write((lba >> 8) as u8); + fis.lba2.write((lba >> 16) as u8); + fis.lba3.write(0); + fis.lba4.write(0); + fis.lba5.write(0); + fis.device.write((1 << 6) | ((lba >> 24) as u8)); + } else { + // 48 bits + fis.command.write(ATA_CMD_READ_DMA_EXT); + fis.countl.write(sector_count as u8); // 0000h means 65536 sectors + fis.counth.write((sector_count << 8) as u8); + fis.lba0.write(lba as u8); + fis.lba1.write((lba >> 8) as u8); + fis.lba2.write((lba >> 16) as u8); + fis.lba3.write((lba >> 24) as u8); + fis.lba4.write((lba >> 32) as u8); + fis.lba5.write((lba >> 40) as u8); + fis.device.write(1u8 << 6); + }; + + // fill the prdt + unsafe { + // safe: we have the same contract. + command_table.fill_prdt(buffer, buffer_len, command_header)? + } + + // fill the command header + let mut ch_flags = CmdHeaderFlags(0); + ch_flags.set_c(true); + ch_flags.set_w(false); + ch_flags.set_cfl((size_of::() / 4) as u16); + command_header.flags.write(ch_flags); + + // set PxCI + px.ci.write(1u32 << command_slot_index); + px.wait_command_completion(command_slot_index)?; + Ok(()) + } + + /// Write `sector_count` contiguous sectors to the disk from `buffer`. + /// + /// This function places a command in `command_slot_index`, signals it to the port, + /// and waits for an interruption indicating its completion. + /// + /// Based on `supports_48_bits_addresses`, this function will either use the + /// `WRITE DMA` or `WRITE DMA EXT` command. + /// + /// # Unsafety + /// + /// * `buffer` must point to valid memory. + /// * `buffer_len` must reflect `buffer`'s length. + /// * `buffer[0] - buffer[buffer_len - 1]` must fall in a single mapping. + /// * `command_slot_index` must be free to use, implemented, + /// and must point to `command_header` and `command_table`. + /// * `px` must be properly initialized. + /// + /// # Error + /// + /// * `buffer_len` < `sector_count * 512`. + /// * `sector_count` == 0. + /// * `sector_count` is greater than supported maximum (256 for 28-bit devices, 65536 for 48-bit ones). + /// * `lba + sector_count` is not representable on a 28-bit/48-bit address. + /// * all [fill_prdt] errors. + #[allow(clippy::too_many_arguments)] // heh + #[allow(clippy::missing_docs_in_private_items)] + pub unsafe fn write_dma( + buffer: *mut u8, + buffer_len: usize, + lba: u64, + sector_count: u64, + px: &mut Px, + command_header: &mut CmdHeader, + command_table: &mut CmdTable, + command_slot_index: usize, + supports_48_bit: bool) -> Result<(), Error> { + + const ATA_CMD_WRITE_DMA: u8 = 0xCA; + const ATA_CMD_WRITE_DMA_EXT: u8 = 0x35; + + if sector_count.checked_mul(512).filter(|sec_size| *sec_size <= (buffer_len as u64)).is_none() { + return Err(AhciError::InvalidArg.into()) + } + if sector_count == 0 || (!supports_48_bit && sector_count > 256) || sector_count > 65536 { + return Err(AhciError::InvalidArg.into()) + } + if (!supports_48_bit && lba.saturating_add(sector_count) >= (1u64 << 28)) + || lba.saturating_add(sector_count) >= (1u64 << 48) { + return Err(AhciError::InvalidArg.into()) + } + // write the FIS + let fis = &mut command_table.cfis.h2d; + fis.fis_type.write(FisType::RegH2D as u8); + fis.pm.write(1 << 7); // this is an update of the Command register + if !supports_48_bit { + // 28 bits + fis.command.write(ATA_CMD_WRITE_DMA); + fis.countl.write(sector_count as u8); // 0x00 means 256 sectors + fis.counth.write(0); + fis.lba0.write(lba as u8); + fis.lba1.write((lba >> 8) as u8); + fis.lba2.write((lba >> 16) as u8); + fis.lba3.write(0); + fis.lba4.write(0); + fis.lba5.write(0); + fis.device.write((1 << 6) | ((lba >> 24) as u8)); + } else { + // 48 bits + fis.command.write(ATA_CMD_WRITE_DMA_EXT); + fis.countl.write(sector_count as u8); // 0000h means 65536 sectors + fis.counth.write((sector_count << 8) as u8); + fis.lba0.write(lba as u8); + fis.lba1.write((lba >> 8) as u8); + fis.lba2.write((lba >> 16) as u8); + fis.lba3.write((lba >> 24) as u8); + fis.lba4.write((lba >> 32) as u8); + fis.lba5.write((lba >> 40) as u8); + fis.device.write(1u8 << 6); + }; + + // fill the prdt + unsafe { + // safe: we have the same contract. + command_table.fill_prdt(buffer, buffer_len, command_header)? + } + + // fill the command header + let mut ch_flags = CmdHeaderFlags(0); + ch_flags.set_c(true); + ch_flags.set_w(true); + ch_flags.set_cfl((size_of::() / 4) as u16); + command_header.flags.write(ch_flags); + + // set PxCI + px.ci.write(1u32 << command_slot_index); + px.wait_command_completion(command_slot_index)?; + Ok(()) + } +} + +// Implementing Drop to do some clean-up is useless, we never hold a Px, always a reference to one, +// which never calls the drop method. Instead clean-up is the responsibility of the Disk owning the +// reference to an initialized Px. + +// ---------------------------------------------------------------------------------------------- // +// Command List // +// ---------------------------------------------------------------------------------------------- // + +/// Command Header. Pointed to by `PxCLB[i]`. +/// +/// Indicates the PRDT length, and `Command Table` address and its FIS's length. +/// +/// See section 4.2.2 +#[repr(packed)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct CmdHeader { + // DW0 + flags: Mmio, /* Command FIS length in DWORDS, 2 ~ 16, atapi: 4, write - host to device: 2, prefetchable: 1 */ + prdtl: Mmio, // Physical region descriptor table length in entries + // DW1 + prdbc: Mmio, // Physical region descriptor byte count transferred + // DW2, 3 + ctba: Mmio, // Command table descriptor base address + // DW4 - 7 + _rsv1: [Mmio; 4], // Reserved +} + +impl Debug for CmdHeader { + /// Debug does not access reserved registers. + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("HbaCmdHeader") + .field("flags", &self.flags) + .field("prdtl", &self.prdtl) + .field("prdbc", &self.prdbc) + .field("ctba", &self.ctba) + .finish() + } +} + +bitfield!{ + /// Command Header word 0. + /// + /// Defined in section 4.2.2 + #[derive(Clone, Copy)] + pub struct CmdHeaderFlags(u16); + impl Debug; + pmp, set_pmp: 15,12; // Port Multiplier Port + // 11 reserved + c, set_c: 10; // Clear Busy upon R_OK + b, set_b: 9; // BIST + r, set_r: 8; // Reset + p, set_p: 7; // Prefetchable + w, set_w: 6; // Write. Indicates direction is a device write. + a, set_a: 5; // Atapi. Indicates an ATAPI transfer. + cfl, set_cfl: 4,0; // Command FIS Length. +} + +/// The array of 32 [CmdHeader]. +/// +/// This struct is used to allocate the array of command slots used by the port. +/// +/// Its physical address will be written in `PxCLB`. +// required alignment is 1024, coincidentally size is 1024, +// align(size_of(itself)) guarantees to prevent crossing page boundary. +#[repr(C, align(1024))] +#[allow(clippy::missing_docs_in_private_items)] +pub struct CmdHeaderArray { + pub slots: [CmdHeader; 32] +} + +assert_eq_size!(size_CmdHeaderArray; CmdHeaderArray, [u8; 1024]); + +unsafe impl ZeroInitialized for CmdHeaderArray {} + +impl CmdHeader { + /// Initializes a CmdHeader, making it point to its [CmdTable]. + /// + /// # Safety + /// + /// - `command_table` should not already be pointed to by any other CmdHeader. + /// - port must not be running. + pub unsafe fn init(&mut self, command_table: &mut CmdTable) { + let phys_addr = virt_to_phys(command_table as _); + self.ctba.write(phys_addr as u64); + } +} + +// ---------------------------------------------------------------------------------------------- // +// Command Table // +// ---------------------------------------------------------------------------------------------- // + +/// Command Table. +/// +/// Each entry in the command list points to a structure called the command table. +/// It can be found at `PxCLB[i].CTBA`. +/// +/// See section 4.2.3 +// required alignment is 128, but we use align(size_of(itself)) to prevent crossing page boundary. +#[repr(C, align(4096))] +#[allow(clippy::missing_docs_in_private_items)] +pub struct CmdTable { + // 0x00 + cfis: Cfis, // Command FIS + // 0x40 + acmd: [Mmio; 16], // ATAPI command, 12 or 16 bytes + // 0x50 + _rsv: [Mmio; 48], // Reserved + // 0x80 + prdt: [PrdtEntry; 248], // Physical region descriptor table entries, 0 ~ 65535. + // 248 entries fills the rest of the page. +} + +assert_eq_size!(size_CmdTable; CmdTable, [u8; 4096]); + +unsafe impl ZeroInitialized for CmdTable {} + +/// Command FIS. +/// +/// The FIS that will be sent to the device. +/// Bytes 0x00-0x40 of a [CmdTable]. +#[allow(clippy::missing_docs_in_private_items)] +#[repr(C)] +union Cfis { + raw_bytes: [Mmio; 64], + h2d: FisRegH2D, + // ... +} + +assert_eq_size!(size_Cfis; Cfis, [u8; 64]); + +/// Physical Region Descriptor Table entry. +/// +/// Used for DMAs. A physical region is represented as a physical address and its length. +/// The `PRDT` is just a list of regions, which will be filled by AHCI's scatter-gather algorithm. +/// +/// See section 4.2.3.3 +#[allow(clippy::missing_docs_in_private_items)] +#[repr(packed)] +struct PrdtEntry { + dba: Mmio, // Data base address + _rsv0: Mmio, // Reserved + dbc: Mmio, // Byte count, 4M max, interrupt = 1 +} + +impl Debug for PrdtEntry { + /// Debug does not access reserved registers. + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("PrdtEntry") + .field("dba", &self.dba) + .field("dbc", &self.dbc) + .finish() + } +} + + +impl CmdTable { + /// Fills a PRDT with the given buffer. + /// + /// The buffer can be physically scattered, this function will query all its physical regions + /// from the kernel. + /// + /// When finished, this function will update the PRDTL count in `header`. + /// + /// # Unsafety + /// + /// * `buffer` must point to valid memory. + /// * `buffer[0] - buffer[len - 1]` must fall in a single mapping. + /// + /// # Error + /// + /// * AhciError::BufferTooScattered: `buffer` is so scattered it overflows PRDT. + /// * query_physical_address() failed. + /// + /// # Panics + /// + /// * length must be even. + /// * `buffer[0]` must be the very start of the mapping. + pub unsafe fn fill_prdt(&mut self, buffer: *mut u8, mut length: usize, header: &mut CmdHeader) -> Result<(), Error> { + assert_eq!(length % 2, 0, "fill_prdt: length is odd."); + assert_eq!(buffer as usize % 2, 0, "fill_prdt: buffer is not word aligned."); + let mut index = 0; + while length > 0 { + let (mut phys_addr, _, mut phys_len, phys_off) = query_physical_address(buffer as _)?; + phys_addr += phys_off; + phys_len -= phys_off; + // divide into 4M regions. + // Range iterator has no .chunks() T.T + while phys_len > 0 && length > 0 { + let entry = self.prdt.get_mut(index) + .ok_or(AhciError::BufferTooScattered)?; + // if buffer has spare space, ignore it. + let region_len = min(min(phys_len, 0x400000), length); + + entry.dba.write(phys_addr as u64); + entry.dbc.write((region_len - 1) as u32); + + phys_len -= region_len; + phys_addr += region_len; + index += 1; + // also decrement total length. + length -= region_len; + } + } + // Interrupt on Completion on the last PRDT entry + //self.prdt[index - 1].dbc.writef(1u32 << 31, true); + header.prdtl.write(index as u16); + Ok(()) + } +} + +// ---------------------------------------------------------------------------------------------- // +// Received FIS // +// ---------------------------------------------------------------------------------------------- // + +/// Received FIS Structure. Pointed to by `PxFB`. +/// +/// FIS received by the port will be copied to this structure, in the corresponding field. +/// +/// See section 4.2.1 +#[allow(clippy::missing_docs_in_private_items)] +#[repr(C, align(256))] +pub struct ReceivedFis { + dsfis: FisDmaSetup, + _rsv0: [u8; 4], + psfis: FisPioSetup, + _rsv1: [u8; 12], + rfis: FisRegD2H, + _rsv2: [u8; 4], + sdbfis: FisSetDeviceBits, + ufis: [Mmio; 0x40], + _rsv3: [u8; 0x60], +} + +assert_eq_size!(size_ReceivedFis; ReceivedFis, [u8; 0x100]); + +unsafe impl ZeroInitialized for ReceivedFis {} + +impl ReceivedFis { + /// Return a const reference to the last received DMA Setup FIS. + pub fn dsfis(&self) -> &FisDmaSetup { + &self.dsfis + } + + /// Return a const reference to the last received PIO Setup FIS. + pub fn psfis(&self) -> &FisPioSetup { + &self.psfis + } + + /// Return a const reference to the last received D2H Register FIS. + pub fn rfis(&self) -> &FisRegD2H { + &self.rfis + } + + /// Return a const reference to the last received Set Device Bits FIS. + pub fn sdbfis(&self) -> &FisSetDeviceBits { + &self.sdbfis + } + + /// Return a const reference to the last received Unknown FIS. + pub fn ufis(&self) -> &[Mmio; 0x40] { + &self.ufis + } +} diff --git a/ahci/src/main.rs b/ahci/src/main.rs index 9e2ead628..083b3faab 100644 --- a/ahci/src/main.rs +++ b/ahci/src/main.rs @@ -1,4 +1,45 @@ -#![feature(alloc)] +//! AHCI driver module +//! +//! This driver discovers HBAs on the PCI, initialize them, +//! and exposes IPC endpoints to read and write sectors from/to a disk. +//! +//! # Features +//! +//! Here's a list of wonderful features this driver does **not** provide: +//! +//! - ATAPI support +//! - NCQ support +//! - hotplug/remove of a device +//! - hotplug/remove of a controller +//! - interrupts notifying command has been completed (WIP) +//! - real error management +//! - Port Multipliers +//! - PCI-to-PCI bridges +//! +//! # Interface +//! +//! This driver exposes two IPC interfaces: [AhciInterface], registered as `"ahci:\0"`, +//! and some [IDisk]s. +//! +//! Basically at initialization the driver will assign an id to every discovered disk. +//! You can then ask the [AhciInterface] to give you a session to any [IDisk] from its id, +//! and finally read/write some sectors. +//! +//! We read and write disk sectors only by DMA, letting the device do all the copying. +//! Our client will provide us a handle to a shared memory, and we will make the device +//! DMA read/write to it. +//! +//! # Parallelism +//! +//! For now this driver is "highly single-threaded" and blocking, this means that only one +//! request can be outstanding at any moment, and the driver will wait for it to be completed +//! before accepting other requests. +//! +//! This is highly unsatisfying, since AHCI supports up to 32 commands being issued +//! simultaneously. Unfortunately we can't take advantage of that until we manage to +//! make command-completion interrupts work. + +#![feature(alloc, maybe_uninit, box_syntax, untagged_unions, const_vec_new)] #![no_std] // rustc warnings @@ -10,11 +51,8 @@ #![cfg_attr(test, allow(unused_imports))] // rustdoc warnings -// TODO: Write documentation for AHCI -// BODY: Documentation lints are disabled on AHCI for now, to avoid conflicts. -// BODY: Get @orycterope to write some doc \o/ -#![allow(missing_docs)] -#![allow(clippy::missing_docs_in_private_items)] +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] #![deny(intra_doc_link_resolution_failure)] #[macro_use] @@ -23,13 +61,103 @@ extern crate alloc; extern crate kfs_libuser; #[macro_use] extern crate log; +#[macro_use] +extern crate bitfield; mod pci; +mod hba; +mod fis; +mod disk; + +use crate::hba::HbaMemoryRegisters; +use crate::disk::{Disk, IDisk}; +use alloc::prelude::*; +use alloc::sync::Arc; +use kfs_libuser::error::{Error, AhciError}; +use kfs_libuser::ipc::server::{WaitableManager, PortHandler, IWaitable}; +use spin::Mutex; +use kfs_libuser::types::Handle; +use kfs_libuser::syscalls; +use kfs_libuser::ipc::server::SessionWrapper; +/// Array of discovered disk. +/// +/// At startup, the driver will initialize each disk it discovers, +/// and populate this vec. +/// +/// As hotplug/remove of a disk is not supported, this array remains +/// unchanged for the rest of the driver's execution. +/// +/// A disk id is just the index of a disk in this array. +static DISKS: Mutex>>> = Mutex::new(Vec::new()); + +/// Ahci driver initialisation. +/// +/// 1. Discover HBAs on the PCI. +/// 2. For every found HBA: +/// - Initialize each implemented port if we detect it is connected to a device. +/// - Push the created [Disk]s in [DISKS]. +/// 3. Start the event loop. fn main() { - info!("Hello, world!"); - let ahci_devices_list = pci::get_ahci_controllers(); - info!("AHCI devices : {:#x?}", ahci_devices_list); + debug!("AHCI driver starting up"); + let ahci_controllers = pci::get_ahci_controllers(); + debug!("AHCI controllers : {:#x?}", ahci_controllers); + for (bar5, _) in ahci_controllers { + DISKS.lock().extend( + HbaMemoryRegisters::init(bar5 as _) + .drain(..).map(|disk| Arc::new(Mutex::new(disk))) + ); + } + debug!("AHCI initialised disks : {:#x?}", DISKS); + + // event loop + let man = WaitableManager::new(); + let handler = Box::new(PortHandler::::new("ahci:\0").unwrap()); + man.add_waitable(handler as Box); + man.run(); +} + +/// Main interface to the AHCI driver. +/// +/// Registered under the name `"ahci:\0"` to the Service Manager, after the discovery stage. +/// +/// Provides an endpoint to get the number of discovered disks, +/// and another one to get a session to a given disk. +/// +/// As hotplug/remove of a disk is not supported, a disk id remains valid for the whole +/// lifetime of the ahci driver. +#[derive(Default, Debug)] +struct AhciInterface; + +object! { + impl AhciInterface { + /// Returns the number of discovered disks. + /// + /// Any number in the range `0..disk_count()` is considered a valid disk id. + #[cmdid(0)] + fn disk_count(&mut self,) -> Result<(usize,), Error> { + Ok((DISKS.lock().len(),)) + } + + /// Gets the interface to a disk. + /// + /// This creates a session to an [IDisk]. + /// + /// # Error + /// + /// - InvalidArg: `disk_id` is not a valid disk id. + #[cmdid(1)] + fn get_disk(&mut self, manager: &WaitableManager, disk_id: u32,) -> Result<(Handle,), Error> { + let idisk = IDisk::new(Arc::clone( + DISKS.lock().get(disk_id as usize) + .ok_or(AhciError::InvalidArg)? + )); + let (server, client) = syscalls::create_session(false, 0)?; + let wrapper = SessionWrapper::new(server, idisk); + manager.add_waitable(Box::new(wrapper) as Box); + Ok((client.into_handle(),)) + } + } } capabilities!(CAPABILITIES = Capabilities { @@ -47,8 +175,25 @@ capabilities!(CAPABILITIES = Capabilities { kfs_libuser::syscalls::nr::ConnectToNamedPort, kfs_libuser::syscalls::nr::CreateInterruptEvent, kfs_libuser::syscalls::nr::QueryPhysicalAddress, + kfs_libuser::syscalls::nr::MapMmioRegion, + kfs_libuser::syscalls::nr::SendSyncRequestWithUserBuffer, + kfs_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer, + kfs_libuser::syscalls::nr::AcceptSession, + kfs_libuser::syscalls::nr::CreateSession, ], raw_caps: [ + // todo: IRQ capabilities at runtime + // body: Currently IRQ capabilities are declared at compile-time. + // body: + // body: However, for PCI, the IRQ line we want to subscribe to + // body: can only be determined at runtime by reading the `Interrupt Line` register + // body: that has been set-up during POST. + // body: + // body: What would be the proper way to handle such a case ? + // body: + // body: - Declaring every IRQ line in our capabilities, but only effectively using one ? + // body: - Deporting the PIC management to a userspace module, and allow it to accept + // body: dynamic irq capabilities in yet undefined way. kfs_libuser::caps::ioport(pci::CONFIG_ADDRESS + 0), kfs_libuser::caps::ioport(pci::CONFIG_ADDRESS + 1), kfs_libuser::caps::ioport(pci::CONFIG_ADDRESS + 2), kfs_libuser::caps::ioport(pci::CONFIG_ADDRESS + 3), kfs_libuser::caps::ioport(pci::CONFIG_DATA + 0), kfs_libuser::caps::ioport(pci::CONFIG_DATA + 1), kfs_libuser::caps::ioport(pci::CONFIG_DATA + 2), kfs_libuser::caps::ioport(pci::CONFIG_DATA + 3), ] diff --git a/libuser/src/error.rs b/libuser/src/error.rs index b21a91341..ca71323c3 100644 --- a/libuser/src/error.rs +++ b/libuser/src/error.rs @@ -49,6 +49,8 @@ pub enum Error { //Vi(ViError, Backtrace), /// Internal Libuser error. Libuser(LibuserError, Backtrace), + /// Ahci driver error. + Ahci(AhciError, Backtrace), /// An unknown error type. Either someone returned a custom error, or this /// version of libuser is outdated. Unknown(u32, Backtrace) @@ -65,6 +67,7 @@ impl Error { Module::Sm => Error::Sm(SmError(description), Backtrace::new()), //Module::Vi => Error::Vi(ViError(description), Backtrace::new()), Module::Libuser => Error::Libuser(LibuserError(description), Backtrace::new()), + Module::Ahci => Error::Ahci(AhciError(description), Backtrace::new()), _ => Error::Unknown(errcode, Backtrace::new()) } } @@ -78,6 +81,7 @@ impl Error { Error::Sm(err, ..) => err.0 << 9 | Module::Sm.0, //Error::Vi(err, ..) => err.0 << 9 | Module::Vi.0, Error::Libuser(err, ..) => err.0 << 9 | Module::Libuser.0, + Error::Ahci(err, ..) => err.0 << 9 | Module::Ahci.0, Error::Unknown(err, ..) => err, } } @@ -104,7 +108,8 @@ enum_with_val! { Kernel = 1, Sm = 21, Vi = 114, - Libuser = 115 + Libuser = 115, + Ahci = 116, } } @@ -161,3 +166,23 @@ impl From for Error { Error::Sm(error, Backtrace::new()) } } + +enum_with_val! { + /// AHCI driver errors. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct AhciError(u32) { + /// Passed argument were found to be illegal. + InvalidArg = 1, + /// Passed buffer for DMA is too physically scattered. This can only happen for read/writes + /// of 1985 sectors or more. + BufferTooScattered = 2, + /// The hardware reported an error. + IoError = 3, + } +} + +impl From for Error { + fn from(error: AhciError) -> Self { + Error::Ahci(error, Backtrace::new()) + } +} diff --git a/libuser/src/lib.rs b/libuser/src/lib.rs index 2beeccf9a..98eb8a188 100644 --- a/libuser/src/lib.rs +++ b/libuser/src/lib.rs @@ -3,7 +3,7 @@ //! Provides an allocator, various lang items. #![no_std] -#![feature(global_asm, asm, start, lang_items, core_intrinsics, const_fn, alloc)] +#![feature(global_asm, asm, start, lang_items, core_intrinsics, const_fn, alloc, box_syntax, untagged_unions)] // rustc warnings #![warn(unused)] @@ -49,6 +49,7 @@ pub mod error; pub mod allocator; pub mod terminal; pub mod window; +pub mod zero_box; mod log_impl; pub use kfs_libutils::io; From 9641289dab7dd761b06c9b6845ff2315968cb382 Mon Sep 17 00:00:00 2001 From: Orycterope Date: Sat, 9 Mar 2019 20:03:38 +0000 Subject: [PATCH 3/5] libuser: AHCI IPC client interface --- libuser/src/ahci.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++ libuser/src/lib.rs | 1 + 2 files changed, 168 insertions(+) create mode 100644 libuser/src/ahci.rs diff --git a/libuser/src/ahci.rs b/libuser/src/ahci.rs new file mode 100644 index 000000000..b91b59bf6 --- /dev/null +++ b/libuser/src/ahci.rs @@ -0,0 +1,167 @@ +//! Interface to the AHCI driver service + +use crate::types::*; +use crate::sm; +use core::mem; +use crate::error::{Error, SmError}; +use crate::ipc::Message; + +/// Main ahci interface. +/// +/// Can communicate the number of discovered devices, +/// and get an interface to a specific device. +#[derive(Debug)] +pub struct AhciInterface(ClientSession); + +impl AhciInterface { + /// Connects to the ahci service. + pub fn raw_new() -> Result { + use crate::syscalls; + + loop { + let svcname = unsafe { + mem::transmute(*b"ahci:\0\0\0") + }; + let _ = match sm::IUserInterface::raw_new()?.get_service(svcname) { + Ok(s) => return Ok(Self(s)), + Err(Error::Sm(SmError::ServiceNotRegistered, ..)) => syscalls::sleep_thread(0), + Err(err) => return Err(err) + }; + } + } + + /// Asks to the ahci service how many disks it has discovered. + /// + /// [get_disk] accepts disk ids in `0..discovered_disks_count()`. + pub fn discovered_disks_count(&mut self) -> Result { + let mut buf = [0; 0x100]; + + let msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 0); + msg.pack(&mut buf[..]); + + self.0.send_sync_request_with_user_buffer(&mut buf[..])?; + + let res: Message<'_, u32, [_; 0], [_; 0], [_; 0]> = Message::unpack(&buf[..]); + res.error()?; + Ok(res.raw()) + } + + /// Gets the interface to a disk. + /// + /// This creates a session connected to an [IDisk]. + /// + /// `disk_id` should be in `0..discovered_disk_count()`. + pub fn get_disk(&mut self, disk_id: u32) -> Result { + use crate::ipc::Message; + let mut buf = [0; 0x100]; + + let mut msg = Message::<_, [_; 0], [_; 1], [_; 0]>::new_request(None, 1); + msg.push_raw(disk_id); + msg.pack(&mut buf[..]); + + self.0.send_sync_request_with_user_buffer(&mut buf[..])?; + let mut res : Message<'_, (), [_; 0], [_; 0], [_; 1]> = Message::unpack(&buf[..]); + res.error()?; + Ok(IDisk(ClientSession(res.pop_handle_move().unwrap()))) + } +} + +/// Interface to an AHCI device. +/// +/// It can: +/// +/// - get the number of addressable 512-octet sectors on this disk, +/// - read a range of consecutive sectors. +/// - write a range of consecutive sectors. +#[derive(Debug)] +pub struct IDisk(ClientSession); + +impl IDisk { + /// Retrieves the number of addressable 512-octet sectors on this disk. + pub fn sectors_count(&mut self) -> Result { + let mut buf = [0; 0x100]; + + let msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 0); + msg.pack(&mut buf[..]); + + self.0.send_sync_request_with_user_buffer(&mut buf[..])?; + + let res: Message<'_, u64, [_; 0], [_; 0], [_; 0]> = Message::unpack(&buf[..]); + res.error()?; + Ok(res.raw()) + } + + /// Reads sectors from the disk. + /// + /// This IPC call will invoke the AHCI driver and make it copy `sector_count` sectors from the disk + /// to the memory pointed to by `handle`. + /// You should map `handle` in your process to access the copied data. + /// + /// # Error + /// + /// - The handle should contain a buffer at least `sector_count * 512` octets in size. + /// - `mapping_size` should reflect the size of `handle`. + /// - `address..address+sector_count` should be in the range `0..IDisk.sector_count()`. + pub fn read_dma(&mut self, handle: &SharedMemory, mapping_size: u64, address: u64, sector_count: u64) -> Result<(), Error> { + let mut buf = [0; 0x100]; + + #[repr(C)] #[derive(Clone, Copy, Default)] + #[allow(clippy::missing_docs_in_private_items)] + struct InRaw { + mapping_size: u64, + addr: u64, + count: u64, + } + let mut msg = Message::<_, [_; 0], [_; 1], [_; 0]>::new_request(None, 1); + msg.push_raw(InRaw { + mapping_size: mapping_size, + addr: address, + count: sector_count + }); + msg.push_handle_copy(handle.0.as_ref()); + msg.pack(&mut buf[..]); + + self.0.send_sync_request_with_user_buffer(&mut buf[..])?; + + let res: Message<'_, (), [_; 0], [_; 0], [_; 0]> = Message::unpack(&buf[..]); + res.error()?; + Ok(()) + } + + /// Writes sectors to the disk. + /// + /// This IPC call will invoke the AHCI driver and make it copy `sector_count` sectors to the disk + /// from the memory pointed to by `handle`. + /// You should map `handle` in your process first to fill the data to be copied. + /// + /// # Error + /// + /// - The handle should contain a buffer at least `sector_count * 512` octets in size. + /// - `mapping_size` should reflect the size of `handle`. + /// - `address..address+sector_count` should be in the range `0..IDisk.sector_count()`. + pub fn write_dma(&mut self, handle: &SharedMemory, mapping_size: u64, address: u64, sector_count: u64) -> Result<(), Error> { + let mut buf = [0; 0x100]; + + #[repr(C)] #[derive(Clone, Copy, Default)] + #[allow(clippy::missing_docs_in_private_items)] + struct InRaw { + mapping_size: u64, + addr: u64, + count: u64, + } + let mut msg = Message::<_, [_; 0], [_; 1], [_; 0]>::new_request(None, 2); + msg.push_raw(InRaw { + mapping_size: mapping_size, + addr: address, + count: sector_count + }); + msg.push_handle_copy(handle.0.as_ref()); + msg.pack(&mut buf[..]); + + self.0.send_sync_request_with_user_buffer(&mut buf[..])?; + + let res: Message<'_, (), [_; 0], [_; 0], [_; 0]> = Message::unpack(&buf[..]); + res.error()?; + Ok(()) + } +} diff --git a/libuser/src/lib.rs b/libuser/src/lib.rs index 98eb8a188..1e863b988 100644 --- a/libuser/src/lib.rs +++ b/libuser/src/lib.rs @@ -45,6 +45,7 @@ pub mod types; pub mod ipc; pub mod sm; pub mod vi; +pub mod ahci; pub mod error; pub mod allocator; pub mod terminal; From e6cca9bba94d175df5b4645fe89aec5fbf83a586 Mon Sep 17 00:00:00 2001 From: Orycterope Date: Sat, 9 Mar 2019 19:41:01 +0000 Subject: [PATCH 4/5] makefile: qemu AHCI disk Makefile rules to create a 8M disk image, and have it connected in qemu. --- .gitignore | 1 + Makefile.toml | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f6adee89f..cb13f2e23 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ target Cargo.lock isofiles/boot/kfs-* os.iso +DISK.img .idea .gdbinit .gdb_history diff --git a/Makefile.toml b/Makefile.toml index d7e9d94d9..c0eb42e47 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -166,19 +166,47 @@ mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltori ''' ] +[tasks.disk] +workspace = false +description = "Creates an empty disk image." +command = "dd" +args = [ "if=/dev/zero", "of=DISK.img", "count=8M", "iflag=count_bytes" ] + +# because we can't have a dependency on a file ರ_ರ +[tasks.create-disk-if-not-exist] +workspace = false +description = "Invokes the task `disk` if DISK.img does not already exist." +condition_script = [ "[ ! -e DISK.img ]" ] +run_task = "disk" + [tasks.qemu] workspace = false description = "Runs the bootable ISO in qemu." -dependencies = ["iso-release"] +dependencies = ["iso-release", "create-disk-if-not-exist"] command = "qemu-system-i386" -args = ["-cdrom", "os.iso", "-serial", "stdio", "-vnc", "${VNC_PORT}", "-no-reboot", "-enable-kvm"] +args = [ + "-cdrom", "os.iso", + "-serial", "stdio", + "-vnc", "${VNC_PORT}", + "-no-reboot", + "-enable-kvm", + "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", + ] [tasks.qemu-debug] workspace = false description = "Runs the bootable ISO in qemu with gdb support" -dependencies = ["iso"] +dependencies = ["iso", "create-disk-if-not-exist"] command = "qemu-system-i386" -args = ["-cdrom", "os.iso", "-serial", "stdio", "-vnc", "${VNC_PORT}", "-no-reboot", "-gdb", "tcp::${GDB_PORT}", "-S", "-d", "cpu_reset"] +args = [ + "-cdrom", "os.iso", + "-serial", "stdio", + "-vnc", "${VNC_PORT}", + "-no-reboot", + "-gdb", "tcp::${GDB_PORT}", "-S", + "-d", "cpu_reset", + "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", + ] [tasks.doc] workspace = false From 113833b817476a8b839d40b6f8f7e57fa38d19a8 Mon Sep 17 00:00:00 2001 From: Orycterope Date: Mon, 11 Mar 2019 01:26:28 +0100 Subject: [PATCH 5/5] ahci fix documentation links --- ahci/src/hba.rs | 6 ++++-- libuser/src/ahci.rs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ahci/src/hba.rs b/ahci/src/hba.rs index b2d1d9ae6..747cc6fe4 100644 --- a/ahci/src/hba.rs +++ b/ahci/src/hba.rs @@ -778,7 +778,8 @@ impl Px { /// * `sector_count` == 0. /// * `sector_count` is greater than supported maximum (256 for 28-bit devices, 65536 for 48-bit ones). /// * `lba + sector_count` is not representable on a 28-bit/48-bit address. - /// * all [fill_prdt] errors. + /// * query_physical_address() failed. + /// * AhciError::BufferTooScattered: `buffer` is so scattered it overflows PRDT. #[allow(clippy::too_many_arguments)] // heh #[allow(clippy::missing_docs_in_private_items)] pub unsafe fn read_dma( @@ -877,7 +878,8 @@ impl Px { /// * `sector_count` == 0. /// * `sector_count` is greater than supported maximum (256 for 28-bit devices, 65536 for 48-bit ones). /// * `lba + sector_count` is not representable on a 28-bit/48-bit address. - /// * all [fill_prdt] errors. + /// * query_physical_address() failed. + /// * AhciError::BufferTooScattered: `buffer` is so scattered it overflows PRDT. #[allow(clippy::too_many_arguments)] // heh #[allow(clippy::missing_docs_in_private_items)] pub unsafe fn write_dma( diff --git a/libuser/src/ahci.rs b/libuser/src/ahci.rs index b91b59bf6..c4dd08366 100644 --- a/libuser/src/ahci.rs +++ b/libuser/src/ahci.rs @@ -33,6 +33,8 @@ impl AhciInterface { /// Asks to the ahci service how many disks it has discovered. /// /// [get_disk] accepts disk ids in `0..discovered_disks_count()`. + /// + /// [get_disk]: AhciInterface::get_disk pub fn discovered_disks_count(&mut self) -> Result { let mut buf = [0; 0x100];