276 lines
7.9 KiB
Rust
276 lines
7.9 KiB
Rust
#![feature(generic_const_exprs)]
|
|
#![allow(incomplete_features)]
|
|
#![no_std]
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::{format, sync::Arc, vec::Vec};
|
|
use bytemuck::Zeroable;
|
|
use data::ReceivedFis;
|
|
use device_api::{
|
|
device::Device,
|
|
interrupt::{InterruptAffinity, InterruptHandler},
|
|
};
|
|
use error::AhciError;
|
|
use libk::{device::manager::probe_partitions, fs::devfs, task::runtime};
|
|
use libk_mm::{address::AsPhysicalAddress, device::DeviceMemoryIo, PageBox};
|
|
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
|
|
use port::AhciPort;
|
|
use regs::{PortRegs, Regs};
|
|
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
|
|
use ygg_driver_pci::{
|
|
device::{PciDeviceInfo, PreferredInterruptMode},
|
|
PciCommandRegister, PciConfigurationSpace,
|
|
};
|
|
use yggdrasil_abi::{error::Error, io::FileMode};
|
|
|
|
use crate::regs::{Version, CAP, GHC, SSTS};
|
|
|
|
mod command;
|
|
mod data;
|
|
mod error;
|
|
mod port;
|
|
mod regs;
|
|
|
|
const MAX_PRD_SIZE: usize = 65536;
|
|
const MAX_COMMANDS: usize = u32::BITS as usize;
|
|
const SECTOR_SIZE: usize = 512;
|
|
const MAX_DRIVES: usize = (b'z' - b'a') as usize;
|
|
|
|
pub struct AhciController {
|
|
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
|
ports: OneTimeInit<Vec<Arc<AhciPort>>>,
|
|
received_fis_buffers: OneTimeInit<[Option<PageBox<ReceivedFis>>; 16]>,
|
|
|
|
version: Version,
|
|
max_port_count: usize,
|
|
ahci_only: bool,
|
|
has_64_bit: bool,
|
|
}
|
|
|
|
impl AhciController {
|
|
async fn late_init(self: Arc<Self>) -> Result<(), AhciError> {
|
|
log::info!("Initializing AHCI SATA Controller {:?}", self.version);
|
|
|
|
let regs = self.regs.lock();
|
|
|
|
regs.GHC.modify(GHC::HR::SET);
|
|
|
|
while regs.GHC.matches_all(GHC::HR::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
if !self.ahci_only {
|
|
regs.GHC.modify(GHC::AE::SET);
|
|
}
|
|
|
|
let pi = regs.PI.get();
|
|
|
|
let mut ports = Vec::new();
|
|
|
|
drop(regs);
|
|
|
|
let mut fis_buffers = [const { None }; 16];
|
|
// Allocate FIS receive buffers for the ports
|
|
for (i, fis_buffer_slot) in fis_buffers.iter_mut().enumerate().take(self.max_port_count) {
|
|
if pi & (1 << i) == 0 {
|
|
continue;
|
|
}
|
|
|
|
let regs = self.regs.lock();
|
|
let port = ®s.PORTS[i];
|
|
|
|
let buffer = PageBox::new(ReceivedFis::zeroed()).map_err(AhciError::MemoryError)?;
|
|
port.set_received_fis_address_64(unsafe { buffer.as_physical_address() });
|
|
*fis_buffer_slot = Some(buffer);
|
|
}
|
|
|
|
self.received_fis_buffers.init(fis_buffers);
|
|
|
|
for i in 0..self.max_port_count {
|
|
if pi & (1 << i) == 0 {
|
|
continue;
|
|
}
|
|
|
|
let regs = self.regs.lock();
|
|
let port = ®s.PORTS[i];
|
|
|
|
if !port.SSTS.matches_all(SSTS::DET::Online + SSTS::IPM::Active) {
|
|
continue;
|
|
}
|
|
|
|
port.start()?;
|
|
|
|
// TODO wait here
|
|
|
|
let sig = port.SIG.get();
|
|
if sig != PortRegs::SIG_SATA {
|
|
log::warn!("Skipping unknown port {} with signature {:#x}", i, sig);
|
|
continue;
|
|
}
|
|
|
|
let port = unsafe { regs.extract(|regs| ®s.PORTS[i]) };
|
|
|
|
drop(regs);
|
|
|
|
let port = match AhciPort::create(port, self.clone(), i) {
|
|
Ok(port) => port,
|
|
Err(error) => {
|
|
log::warn!("Port {} init error: {:?}", i, error);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
ports.push(port);
|
|
}
|
|
|
|
let ports = self.ports.init(ports);
|
|
|
|
// Enable global HC interrupts
|
|
self.regs.lock().GHC.modify(GHC::IE::SET);
|
|
|
|
// Setup the detected ports
|
|
for (i, port) in ports.iter().enumerate() {
|
|
log::info!("Init port {}", i);
|
|
port.init_inner().await?;
|
|
}
|
|
|
|
// Dump info about the drives
|
|
for (i, port) in ports.iter().enumerate() {
|
|
let info = port.info().unwrap();
|
|
log::info!(
|
|
"Port {}: model={:?}, serial={:?}, lba_count={}",
|
|
i,
|
|
info.model,
|
|
info.serial,
|
|
info.lba_count
|
|
);
|
|
}
|
|
|
|
for port in ports.iter() {
|
|
register_sata_drive(port.clone(), true);
|
|
}
|
|
|
|
log::debug!("All ports initialized");
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl InterruptHandler for AhciController {
|
|
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
|
|
let regs = self.regs.lock();
|
|
|
|
let is = regs.IS.get();
|
|
if is != 0 {
|
|
if let Some(ports) = self.ports.try_get() {
|
|
// Clear global interrupt status
|
|
regs.IS.set(u32::MAX);
|
|
|
|
for port in ports {
|
|
if is & (1 << port.index) != 0 {
|
|
port.handle_pending_interrupts();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
}
|
|
|
|
impl Device for AhciController {
|
|
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
|
|
// Do the init in background
|
|
runtime::spawn(self.late_init())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
"AHCI Controller"
|
|
}
|
|
}
|
|
|
|
static SATA_DRIVES: IrqSafeSpinlock<Vec<Arc<AhciPort>>> = IrqSafeSpinlock::new(Vec::new());
|
|
|
|
pub fn probe(info: &PciDeviceInfo) -> Result<Arc<dyn Device>, Error> {
|
|
let bar5 = info.config_space.bar(5).ok_or(Error::InvalidOperation)?;
|
|
let bar5 = bar5.as_memory().ok_or(Error::InvalidOperation)?;
|
|
|
|
let mut cmd = PciCommandRegister::from_bits_retain(info.config_space.command());
|
|
cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO);
|
|
cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER;
|
|
info.config_space.set_command(cmd.bits());
|
|
|
|
info.init_interrupts(PreferredInterruptMode::Msi(true))?;
|
|
|
|
// // TODO support regular PCI interrupts (ACPI dependency)
|
|
// let Some(mut msi) = info.config_space.capability::<MsiCapability>() else {
|
|
// log::warn!("Ignoring AHCI: does not support MSI (and the OS doesn't yet support PCI IRQ)");
|
|
// return Err(Error::InvalidOperation);
|
|
// };
|
|
|
|
// Map the registers
|
|
let regs = unsafe { DeviceMemoryIo::<Regs>::map(bar5, Default::default()) }?;
|
|
|
|
let version = Version::try_from(regs.VS.get())?;
|
|
let ahci_only = regs.CAP.matches_all(CAP::SAM::SET);
|
|
let max_port_count = regs.CAP.read(CAP::NP) as usize;
|
|
let has_64_bit = regs.CAP.matches_all(CAP::S64A::SET);
|
|
|
|
// TODO extract Number of Command Slots
|
|
|
|
let ahci = Arc::new(AhciController {
|
|
regs: IrqSafeSpinlock::new(regs),
|
|
ports: OneTimeInit::new(),
|
|
received_fis_buffers: OneTimeInit::new(),
|
|
version,
|
|
max_port_count,
|
|
ahci_only,
|
|
has_64_bit,
|
|
});
|
|
|
|
// TODO use multiple vectors if capable
|
|
info.map_interrupt(InterruptAffinity::Any, ahci.clone())?;
|
|
|
|
Ok(ahci)
|
|
}
|
|
|
|
pub fn register_sata_drive(drive: Arc<AhciPort>, probe: bool) {
|
|
let index = {
|
|
let mut drives = SATA_DRIVES.lock();
|
|
let index = drives.len();
|
|
if index >= MAX_DRIVES {
|
|
log::error!("Cannot add a SATA drive: too many of them");
|
|
return;
|
|
}
|
|
drives.push(drive.clone());
|
|
index
|
|
};
|
|
let letter = (index as u8 + b'a') as char;
|
|
|
|
let name = format!("sd{letter}");
|
|
log::info!("Register SATA drive: {name}");
|
|
|
|
devfs::add_named_block_device(drive.clone(), name.clone(), FileMode::new(0o600)).ok();
|
|
|
|
if probe {
|
|
runtime::spawn(async move {
|
|
let name = name;
|
|
log::info!("Probing partitions for {name}");
|
|
probe_partitions(drive, |index, partition| {
|
|
let partition_name = format!("{name}{}", index + 1);
|
|
devfs::add_named_block_device(
|
|
Arc::new(partition),
|
|
partition_name,
|
|
FileMode::new(0o600),
|
|
)
|
|
.ok();
|
|
})
|
|
.await
|
|
.ok();
|
|
})
|
|
.ok();
|
|
}
|
|
}
|