ahci: properly use ATA PRDs
This commit is contained in:
parent
3a5f9b6ced
commit
278c63d961
@ -6,7 +6,7 @@ use libk_mm::{
|
||||
};
|
||||
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)]
|
||||
#[repr(u8)]
|
||||
@ -22,8 +22,16 @@ pub trait AtaCommand {
|
||||
|
||||
fn lba(&self) -> u64;
|
||||
fn sector_count(&self) -> usize;
|
||||
fn regions(&self) -> &[(PhysicalAddress, usize)];
|
||||
fn buffer(&self) -> Option<(PhysicalAddress, usize)>;
|
||||
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! {
|
||||
@ -57,13 +65,13 @@ register_structs! {
|
||||
|
||||
pub struct AtaIdentify {
|
||||
buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>,
|
||||
regions: [(PhysicalAddress, usize); 1],
|
||||
}
|
||||
|
||||
pub struct AtaReadDmaEx {
|
||||
lba: u64,
|
||||
sector_count: usize,
|
||||
regions: [(PhysicalAddress, usize); 1],
|
||||
buffer_base: PhysicalAddress,
|
||||
buffer_size: usize,
|
||||
}
|
||||
|
||||
impl AtaIdentify {
|
||||
@ -74,13 +82,7 @@ impl AtaIdentify {
|
||||
}
|
||||
|
||||
pub fn with_data(buffer: PageBox<MaybeUninit<AtaIdentifyResponse>>) -> Self {
|
||||
Self {
|
||||
regions: [(
|
||||
unsafe { buffer.as_physical_address() },
|
||||
size_of::<AtaIdentifyResponse>(),
|
||||
)],
|
||||
buffer,
|
||||
}
|
||||
Self { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +94,8 @@ impl AtaReadDmaEx {
|
||||
Self {
|
||||
lba,
|
||||
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
|
||||
}
|
||||
|
||||
fn regions(&self) -> &[(PhysicalAddress, usize)] {
|
||||
&self.regions
|
||||
fn buffer(&self) -> Option<(PhysicalAddress, usize)> {
|
||||
let base = unsafe { self.buffer.as_physical_address() };
|
||||
let size = size_of::<AtaIdentifyResponse>();
|
||||
Some((base, size))
|
||||
}
|
||||
|
||||
unsafe fn into_response(self) -> Self::Response {
|
||||
@ -132,8 +137,8 @@ impl AtaCommand for AtaReadDmaEx {
|
||||
self.sector_count
|
||||
}
|
||||
|
||||
fn regions(&self) -> &[(PhysicalAddress, usize)] {
|
||||
&self.regions
|
||||
fn buffer(&self) -> Option<(PhysicalAddress, usize)> {
|
||||
Some((self.buffer_base, self.buffer_size))
|
||||
}
|
||||
|
||||
unsafe fn into_response(self) -> Self::Response {}
|
||||
|
@ -145,10 +145,28 @@ impl CommandTable {
|
||||
};
|
||||
}
|
||||
|
||||
let regions = command.regions();
|
||||
for (i, &(base, size)) in regions.iter().enumerate() {
|
||||
let last = i == regions.len() - 1;
|
||||
self.prdt[i] = PhysicalRegionDescriptor::new(base, size, last)?;
|
||||
// Setup PRDs
|
||||
if let Some((base, size)) = command.buffer() {
|
||||
let mut remaining = size;
|
||||
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(())
|
||||
@ -188,7 +206,7 @@ impl PhysicalRegionDescriptor {
|
||||
byte_count: usize,
|
||||
is_last: bool,
|
||||
) -> Result<Self, AhciError> {
|
||||
if byte_count >= MAX_PRD_SIZE {
|
||||
if byte_count > MAX_PRD_SIZE {
|
||||
return Err(AhciError::RegionTooLarge);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ use yggdrasil_abi::error::Error;
|
||||
#[derive(Debug)]
|
||||
pub enum AhciError {
|
||||
MemoryError(#[allow(dead_code)] Error),
|
||||
InvalidBufferSize(usize),
|
||||
RegionTooLarge,
|
||||
DeviceError,
|
||||
FeatureNotImplemented,
|
||||
@ -12,6 +13,7 @@ impl From<AhciError> for Error {
|
||||
fn from(value: AhciError) -> Self {
|
||||
match value {
|
||||
// TODO: Error::DeviceError
|
||||
AhciError::InvalidBufferSize(_) => Error::InvalidArgument,
|
||||
AhciError::DeviceError => Error::InvalidArgument,
|
||||
AhciError::RegionTooLarge => Error::InvalidArgument,
|
||||
AhciError::MemoryError(err) => err,
|
||||
|
@ -36,7 +36,7 @@ mod error;
|
||||
mod port;
|
||||
mod regs;
|
||||
|
||||
const MAX_PRD_SIZE: usize = 8192;
|
||||
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;
|
||||
|
@ -19,7 +19,7 @@ use crate::{
|
||||
data::{CommandListEntry, CommandTable, ReceivedFis, COMMAND_LIST_LENGTH},
|
||||
error::AhciError,
|
||||
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)]
|
||||
@ -94,7 +94,7 @@ impl PortInner {
|
||||
table_entry.setup_command(command)?;
|
||||
*list_entry = CommandListEntry::new(
|
||||
unsafe { table_entry.as_physical_address() },
|
||||
command.regions().len(),
|
||||
command.prd_count(),
|
||||
)?;
|
||||
|
||||
// Sync before send
|
||||
@ -127,7 +127,8 @@ impl AhciPort {
|
||||
regs.stop()?;
|
||||
|
||||
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)?;
|
||||
@ -174,6 +175,7 @@ impl AhciPort {
|
||||
|
||||
pub async fn init(&'static self) -> Result<(), AhciError> {
|
||||
let identify = self.perform_command(AtaIdentify::create()?).await?;
|
||||
|
||||
let model = identify.model_number.to_string();
|
||||
let serial = identify.serial_number.to_string();
|
||||
let lba_count = identify.logical_sector_count();
|
||||
@ -222,6 +224,10 @@ impl AhciPort {
|
||||
}
|
||||
|
||||
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;
|
||||
if let Err(error) = self.inner.lock().submit_command(index, command) {
|
||||
self.free_command(index);
|
||||
@ -277,7 +283,7 @@ impl AhciPort {
|
||||
if ci & (1 << i) == 0
|
||||
&& 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();
|
||||
}
|
||||
}
|
||||
@ -295,6 +301,10 @@ impl NgBlockDevice for AhciPort {
|
||||
lba: u64,
|
||||
buffer: &mut PageSlice<MaybeUninit<u8>>,
|
||||
) -> Result<(), AhciError> {
|
||||
if buffer.len() % SECTOR_SIZE != 0 {
|
||||
return Err(AhciError::InvalidBufferSize(buffer.len()));
|
||||
}
|
||||
|
||||
let command = AtaReadDmaEx::new(lba, buffer.len() / SECTOR_SIZE, buffer);
|
||||
self.submit(&command).await?.wait_for_completion().await
|
||||
}
|
||||
@ -313,7 +323,6 @@ impl NgBlockDevice for AhciPort {
|
||||
}
|
||||
|
||||
fn max_blocks_per_request(&self) -> usize {
|
||||
// TODO
|
||||
1
|
||||
(MAX_PRD_SIZE * 2) / SECTOR_SIZE
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ pub enum QemuNic {
|
||||
VirtioPci { mac: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum QemuDrive {
|
||||
Nvme,
|
||||
Sata,
|
||||
@ -73,20 +73,43 @@ impl IntoArgs for QemuDevice {
|
||||
nic.add_args(command);
|
||||
}
|
||||
Self::Drive { ty, file, serial } => {
|
||||
match ty {
|
||||
QemuDrive::Nvme => {
|
||||
command.arg("-drive");
|
||||
command.arg(format!("file={},if=none,id=drive0", file.display()));
|
||||
|
||||
command.arg("-device");
|
||||
let mut val = match ty {
|
||||
QemuDrive::Nvme => "nvme".to_owned(),
|
||||
_ => todo!(),
|
||||
};
|
||||
let mut device = "nvme".to_owned();
|
||||
if let Some(serial) = serial {
|
||||
val.push_str(",serial=");
|
||||
val.push_str(serial);
|
||||
device.push_str(",serial=");
|
||||
device.push_str(serial);
|
||||
}
|
||||
val.push_str(",drive=drive0");
|
||||
command.arg(val);
|
||||
device.push_str(",drive=drive0");
|
||||
}
|
||||
QemuDrive::Sata => {
|
||||
command.arg("-drive");
|
||||
command.arg(format!(
|
||||
"file={},if=ide,bus=0,index=1,id=drive0",
|
||||
file.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
// command.arg("-drive");
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![feature(let_chains)]
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -26,6 +26,20 @@ struct QemuNetworkConfig {
|
||||
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)]
|
||||
#[serde(rename_all = "kebab-case", default)]
|
||||
struct QemuX86_64MachineConfig {
|
||||
@ -59,6 +73,7 @@ struct QemuMachineConfig {
|
||||
#[serde(rename_all = "kebab-case", default)]
|
||||
struct QemuConfig {
|
||||
network: QemuNetworkConfig,
|
||||
disk: QemuDiskConfig,
|
||||
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> {
|
||||
run_external_command(
|
||||
"llvm-objcopy",
|
||||
@ -234,7 +258,7 @@ fn add_devices_from_config(
|
||||
}
|
||||
if let Some(disk) = disk {
|
||||
devices.push(QemuDevice::Drive {
|
||||
ty: QemuDrive::Nvme,
|
||||
ty: config.disk.interface.into(),
|
||||
file: disk.clone(),
|
||||
serial: Some("deadbeef".into()),
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user