ahci: properly use ATA PRDs

This commit is contained in:
Mark Poliakov 2024-12-05 22:02:01 +02:00
parent 3a5f9b6ced
commit 278c63d961
8 changed files with 125 additions and 42 deletions

View File

@ -6,7 +6,7 @@ use libk_mm::{
}; };
use tock_registers::register_structs; use tock_registers::register_structs;
use crate::{data::AtaString, error::AhciError, SECTOR_SIZE}; use crate::{data::AtaString, error::AhciError, MAX_PRD_SIZE, SECTOR_SIZE};
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)] #[repr(u8)]
@ -22,8 +22,16 @@ pub trait AtaCommand {
fn lba(&self) -> u64; fn lba(&self) -> u64;
fn sector_count(&self) -> usize; fn sector_count(&self) -> usize;
fn regions(&self) -> &[(PhysicalAddress, usize)]; fn buffer(&self) -> Option<(PhysicalAddress, usize)>;
unsafe fn into_response(self) -> Self::Response; unsafe fn into_response(self) -> Self::Response;
fn prd_count(&self) -> usize {
if let Some((_, size)) = self.buffer() {
size.div_ceil(MAX_PRD_SIZE)
} else {
0
}
}
} }
register_structs! { register_structs! {
@ -57,13 +65,13 @@ register_structs! {
pub struct AtaIdentify { pub struct AtaIdentify {
buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>, buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>,
regions: [(PhysicalAddress, usize); 1],
} }
pub struct AtaReadDmaEx { pub struct AtaReadDmaEx {
lba: u64, lba: u64,
sector_count: usize, sector_count: usize,
regions: [(PhysicalAddress, usize); 1], buffer_base: PhysicalAddress,
buffer_size: usize,
} }
impl AtaIdentify { impl AtaIdentify {
@ -74,13 +82,7 @@ impl AtaIdentify {
} }
pub fn with_data(buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>) -> Self { pub fn with_data(buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>) -> Self {
Self { Self { buffer }
regions: [(
unsafe { buffer.as_physical_address() },
size_of::<AtaIdentifyResponse>(),
)],
buffer,
}
} }
} }
@ -92,7 +94,8 @@ impl AtaReadDmaEx {
Self { Self {
lba, lba,
sector_count, sector_count,
regions: [(unsafe { buffer.as_physical_address() }, buffer.len())], buffer_base: unsafe { buffer.as_physical_address() },
buffer_size: buffer.len(),
} }
} }
} }
@ -110,8 +113,10 @@ impl AtaCommand for AtaIdentify {
0 0
} }
fn regions(&self) -> &[(PhysicalAddress, usize)] { fn buffer(&self) -> Option<(PhysicalAddress, usize)> {
&self.regions let base = unsafe { self.buffer.as_physical_address() };
let size = size_of::<AtaIdentifyResponse>();
Some((base, size))
} }
unsafe fn into_response(self) -> Self::Response { unsafe fn into_response(self) -> Self::Response {
@ -132,8 +137,8 @@ impl AtaCommand for AtaReadDmaEx {
self.sector_count self.sector_count
} }
fn regions(&self) -> &[(PhysicalAddress, usize)] { fn buffer(&self) -> Option<(PhysicalAddress, usize)> {
&self.regions Some((self.buffer_base, self.buffer_size))
} }
unsafe fn into_response(self) -> Self::Response {} unsafe fn into_response(self) -> Self::Response {}

View File

@ -145,10 +145,28 @@ impl CommandTable {
}; };
} }
let regions = command.regions(); // Setup PRDs
for (i, &(base, size)) in regions.iter().enumerate() { if let Some((base, size)) = command.buffer() {
let last = i == regions.len() - 1; let mut remaining = size;
self.prdt[i] = PhysicalRegionDescriptor::new(base, size, last)?; let mut prd = 0;
while remaining != 0 {
let rem = remaining.min(MAX_PRD_SIZE);
let last = rem <= MAX_PRD_SIZE;
log::trace!(
target: "io",
"prd[{prd}] base={:#x}, size={rem}",
base.add(prd * MAX_PRD_SIZE)
);
self.prdt[prd] =
PhysicalRegionDescriptor::new(base.add(prd * MAX_PRD_SIZE), rem, last)?;
prd += 1;
remaining -= rem;
}
assert_eq!(prd, command.prd_count());
self.prdt[prd..].fill_with(|| PhysicalRegionDescriptor::zeroed());
} }
Ok(()) Ok(())
@ -188,7 +206,7 @@ impl PhysicalRegionDescriptor {
byte_count: usize, byte_count: usize,
is_last: bool, is_last: bool,
) -> Result<Self, AhciError> { ) -> Result<Self, AhciError> {
if byte_count >= MAX_PRD_SIZE { if byte_count > MAX_PRD_SIZE {
return Err(AhciError::RegionTooLarge); return Err(AhciError::RegionTooLarge);
} }

View File

@ -3,6 +3,7 @@ use yggdrasil_abi::error::Error;
#[derive(Debug)] #[derive(Debug)]
pub enum AhciError { pub enum AhciError {
MemoryError(#[allow(dead_code)] Error), MemoryError(#[allow(dead_code)] Error),
InvalidBufferSize(usize),
RegionTooLarge, RegionTooLarge,
DeviceError, DeviceError,
FeatureNotImplemented, FeatureNotImplemented,
@ -12,6 +13,7 @@ impl From<AhciError> for Error {
fn from(value: AhciError) -> Self { fn from(value: AhciError) -> Self {
match value { match value {
// TODO: Error::DeviceError // TODO: Error::DeviceError
AhciError::InvalidBufferSize(_) => Error::InvalidArgument,
AhciError::DeviceError => Error::InvalidArgument, AhciError::DeviceError => Error::InvalidArgument,
AhciError::RegionTooLarge => Error::InvalidArgument, AhciError::RegionTooLarge => Error::InvalidArgument,
AhciError::MemoryError(err) => err, AhciError::MemoryError(err) => err,

View File

@ -36,7 +36,7 @@ mod error;
mod port; mod port;
mod regs; mod regs;
const MAX_PRD_SIZE: usize = 8192; const MAX_PRD_SIZE: usize = 65536;
const MAX_COMMANDS: usize = u32::BITS as usize; const MAX_COMMANDS: usize = u32::BITS as usize;
const SECTOR_SIZE: usize = 512; const SECTOR_SIZE: usize = 512;
const MAX_DRIVES: usize = (b'z' - b'a') as usize; const MAX_DRIVES: usize = (b'z' - b'a') as usize;

View File

@ -19,7 +19,7 @@ use crate::{
data::{CommandListEntry, CommandTable, ReceivedFis, COMMAND_LIST_LENGTH}, data::{CommandListEntry, CommandTable, ReceivedFis, COMMAND_LIST_LENGTH},
error::AhciError, error::AhciError,
regs::{PortRegs, CMD_PENDING, CMD_READY, IE, TFD}, regs::{PortRegs, CMD_PENDING, CMD_READY, IE, TFD},
AhciController, MAX_COMMANDS, SECTOR_SIZE, AhciController, MAX_COMMANDS, MAX_PRD_SIZE, SECTOR_SIZE,
}; };
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
@ -94,7 +94,7 @@ impl PortInner {
table_entry.setup_command(command)?; table_entry.setup_command(command)?;
*list_entry = CommandListEntry::new( *list_entry = CommandListEntry::new(
unsafe { table_entry.as_physical_address() }, unsafe { table_entry.as_physical_address() },
command.regions().len(), command.prd_count(),
)?; )?;
// Sync before send // Sync before send
@ -127,7 +127,8 @@ impl AhciPort {
regs.stop()?; regs.stop()?;
if !ahci.has_64_bit { if !ahci.has_64_bit {
todo!("Handle controllers incapable of 64 bit"); log::error!("Handle controllers incapable of 64 bit");
return Err(AhciError::DeviceError);
} }
let received_fis = PageBox::new(ReceivedFis::zeroed()).map_err(AhciError::MemoryError)?; let received_fis = PageBox::new(ReceivedFis::zeroed()).map_err(AhciError::MemoryError)?;
@ -174,6 +175,7 @@ impl AhciPort {
pub async fn init(&'static self) -> Result<(), AhciError> { pub async fn init(&'static self) -> Result<(), AhciError> {
let identify = self.perform_command(AtaIdentify::create()?).await?; let identify = self.perform_command(AtaIdentify::create()?).await?;
let model = identify.model_number.to_string(); let model = identify.model_number.to_string();
let serial = identify.serial_number.to_string(); let serial = identify.serial_number.to_string();
let lba_count = identify.logical_sector_count(); let lba_count = identify.logical_sector_count();
@ -222,6 +224,10 @@ impl AhciPort {
} }
async fn submit<C: AtaCommand>(&self, command: &C) -> Result<SubmittedCommand, AhciError> { async fn submit<C: AtaCommand>(&self, command: &C) -> Result<SubmittedCommand, AhciError> {
if command.prd_count() > 2 {
log::warn!("TODO: AHCI doesn't like 3+ PRD transfers");
return Err(AhciError::RegionTooLarge);
}
let index = self.allocate_command().await; let index = self.allocate_command().await;
if let Err(error) = self.inner.lock().submit_command(index, command) { if let Err(error) = self.inner.lock().submit_command(index, command) {
self.free_command(index); self.free_command(index);
@ -277,7 +283,7 @@ impl AhciPort {
if ci & (1 << i) == 0 if ci & (1 << i) == 0
&& self.command_completion[i].1.swap(status, Ordering::Release) == CMD_PENDING && self.command_completion[i].1.swap(status, Ordering::Release) == CMD_PENDING
{ {
log::info!("port{}: completion on slot {}", self.index, i); log::trace!(target: "io", "port{}: completion on slot {}", self.index, i);
self.command_completion[i].0.wake(); self.command_completion[i].0.wake();
} }
} }
@ -295,6 +301,10 @@ impl NgBlockDevice for AhciPort {
lba: u64, lba: u64,
buffer: &mut PageSlice<MaybeUninit<u8>>, buffer: &mut PageSlice<MaybeUninit<u8>>,
) -> Result<(), AhciError> { ) -> Result<(), AhciError> {
if buffer.len() % SECTOR_SIZE != 0 {
return Err(AhciError::InvalidBufferSize(buffer.len()));
}
let command = AtaReadDmaEx::new(lba, buffer.len() / SECTOR_SIZE, buffer); let command = AtaReadDmaEx::new(lba, buffer.len() / SECTOR_SIZE, buffer);
self.submit(&command).await?.wait_for_completion().await self.submit(&command).await?.wait_for_completion().await
} }
@ -313,7 +323,6 @@ impl NgBlockDevice for AhciPort {
} }
fn max_blocks_per_request(&self) -> usize { fn max_blocks_per_request(&self) -> usize {
// TODO (MAX_PRD_SIZE * 2) / SECTOR_SIZE
1
} }
} }

View File

@ -7,7 +7,7 @@ pub enum QemuNic {
VirtioPci { mac: Option<String> }, VirtioPci { mac: Option<String> },
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
pub enum QemuDrive { pub enum QemuDrive {
Nvme, Nvme,
Sata, Sata,
@ -73,20 +73,43 @@ impl IntoArgs for QemuDevice {
nic.add_args(command); nic.add_args(command);
} }
Self::Drive { ty, file, serial } => { Self::Drive { ty, file, serial } => {
command.arg("-drive"); match ty {
command.arg(format!("file={},if=none,id=drive0", file.display())); QemuDrive::Nvme => {
command.arg("-drive");
command.arg(format!("file={},if=none,id=drive0", file.display()));
command.arg("-device"); command.arg("-device");
let mut val = match ty { let mut device = "nvme".to_owned();
QemuDrive::Nvme => "nvme".to_owned(), if let Some(serial) = serial {
_ => todo!(), device.push_str(",serial=");
}; device.push_str(serial);
if let Some(serial) = serial { }
val.push_str(",serial="); device.push_str(",drive=drive0");
val.push_str(serial); }
QemuDrive::Sata => {
command.arg("-drive");
command.arg(format!(
"file={},if=ide,bus=0,index=1,id=drive0",
file.display()
));
}
} }
val.push_str(",drive=drive0"); // command.arg("-drive");
command.arg(val); // command.arg(format!("file={},if=none,id=drive0", file.display()));
// command.arg("-device");
// let mut val = match ty {
// QemuDrive::Nvme => "nvme".to_owned(),
// QemuDrive::Sata => "ahci".to_owned(),
// };
// if *ty == QemuDrive::Nvme
// && let Some(serial) = serial
// {
// val.push_str(",serial=");
// val.push_str(serial);
// }
// val.push_str(",drive=drive0");
// command.arg(val);
} }
} }
} }

View File

@ -1,3 +1,5 @@
#![feature(let_chains)]
use std::{ use std::{
fmt::Debug, fmt::Debug,
path::{Path, PathBuf}, path::{Path, PathBuf},

View File

@ -26,6 +26,20 @@ struct QemuNetworkConfig {
mac: String, mac: String,
} }
#[derive(Debug, Default, Clone, Copy, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
enum QemuDiskInterface {
#[default]
Nvme,
Ahci,
}
#[derive(Debug, Default, serde::Deserialize)]
#[serde(rename_all = "kebab-case", default)]
struct QemuDiskConfig {
interface: QemuDiskInterface,
}
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "kebab-case", default)] #[serde(rename_all = "kebab-case", default)]
struct QemuX86_64MachineConfig { struct QemuX86_64MachineConfig {
@ -59,6 +73,7 @@ struct QemuMachineConfig {
#[serde(rename_all = "kebab-case", default)] #[serde(rename_all = "kebab-case", default)]
struct QemuConfig { struct QemuConfig {
network: QemuNetworkConfig, network: QemuNetworkConfig,
disk: QemuDiskConfig,
machine: QemuMachineConfig, machine: QemuMachineConfig,
} }
@ -107,6 +122,15 @@ impl Default for QemuNetworkConfig {
} }
} }
impl From<QemuDiskInterface> for QemuDrive {
fn from(value: QemuDiskInterface) -> Self {
match value {
QemuDiskInterface::Nvme => Self::Nvme,
QemuDiskInterface::Ahci => Self::Sata,
}
}
}
fn make_kernel_bin<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Result<(), Error> { fn make_kernel_bin<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Result<(), Error> {
run_external_command( run_external_command(
"llvm-objcopy", "llvm-objcopy",
@ -234,7 +258,7 @@ fn add_devices_from_config(
} }
if let Some(disk) = disk { if let Some(disk) = disk {
devices.push(QemuDevice::Drive { devices.push(QemuDevice::Drive {
ty: QemuDrive::Nvme, ty: config.disk.interface.into(),
file: disk.clone(), file: disk.clone(),
serial: Some("deadbeef".into()), serial: Some("deadbeef".into()),
}); });