forked from AbleOS/ableos
584 lines
16 KiB
Rust
584 lines
16 KiB
Rust
/*
|
|
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*/
|
|
|
|
use core::num::TryFromIntError;
|
|
|
|
// FIXME: platform agnostic-ify these
|
|
use x86_64::instructions::{interrupts, port::Port};
|
|
use x86_64::structures::paging::{mapper::MapToError, Mapper, Page, PhysFrame, Size4KiB};
|
|
use x86_64::structures::paging::{FrameAllocator, FrameDeallocator};
|
|
use x86_64::VirtAddr;
|
|
|
|
use crate::arch::memory::BootInfoFrameAllocator;
|
|
use crate::devices::pci::check_device;
|
|
|
|
use super::PciDeviceInfo;
|
|
|
|
// FIXME: un-hardcode these
|
|
const PRDT_START: u64 = 0xffff_ffff_0000_0000;
|
|
const BUFFER_START: u64 = 0xffff_ffff_0000_1000;
|
|
|
|
/// ATA logical sector size, in bytes
|
|
const SECTOR_SIZE: u16 = 512;
|
|
|
|
/// Bus Master IDE Command
|
|
const BMIC_OFFSET: u16 = 0;
|
|
/// Bus Master IDE Status
|
|
const BMIS_OFFSET: u16 = 2;
|
|
/// Bus Master IDE Descriptor Table Pointer
|
|
const BMIDTP_OFFSET: u16 = 4;
|
|
|
|
/// Bus Master IDE Secondary Offset
|
|
const BMI_SECONDARY: u16 = 8;
|
|
|
|
/// Primary command block offset
|
|
const PRIMARY_COMMAND: u16 = 0x01F0;
|
|
|
|
/// Secondary command block offset
|
|
const SECONDARY_COMMAND: u16 = 0x0170;
|
|
|
|
/// Data register offset
|
|
const DATA_OFFSET: u16 = 0;
|
|
|
|
/// Sector count register offset
|
|
const SECCOUNT_OFFSET: u16 = 2;
|
|
|
|
/// LBA0 register offset
|
|
const LBA0_OFFSET: u16 = 3;
|
|
|
|
/// LBA1 register offset
|
|
const LBA1_OFFSET: u16 = 4;
|
|
|
|
/// LBA2 register offset
|
|
const LBA2_OFFSET: u16 = 5;
|
|
|
|
/// Drive/Head register offset
|
|
const DRIVE_HEAD_OFFSET: u16 = 6;
|
|
|
|
/// Command/status register offset
|
|
const COMMAND_STATUS_OFFSET: u16 = 7;
|
|
|
|
/// Secondary control block offset
|
|
const SECONDARY_CONTROL: u16 = 0x0374;
|
|
|
|
/// Primary control block offset
|
|
const PRIMARY_CONTROL: u16 = 0x03F4;
|
|
|
|
/// Alternative status offset
|
|
const ALT_STATUS_OFFSET: u16 = 2;
|
|
|
|
/// ATA identification command
|
|
const CMD_IDENTIFY: u8 = 0xEC;
|
|
|
|
/// ATA read using LBA28 DMA command
|
|
const CMD_READ_DMA: u8 = 0xC8;
|
|
|
|
/// ATA read using LBA48 DMA command
|
|
const CMD_READ_DMA_EXT: u8 = 0x25;
|
|
|
|
pub struct PciIde {
|
|
device_info: PciDeviceInfo,
|
|
ide_devices: Vec<IdeDevice>,
|
|
prdt_frame: Option<PhysFrame>,
|
|
buffer_frames: Option<Vec<PhysFrame>>,
|
|
bmiba: u16,
|
|
}
|
|
|
|
impl PciIde {
|
|
// FIXME: make this return a Result
|
|
pub fn new(bus: u8, device: u8) -> Option<Self> {
|
|
let device_info = check_device(bus, device)?;
|
|
device_info.enable_bus_mastering();
|
|
let idetim = unsafe { device_info.read(0, 0x40) };
|
|
trace!("idetim: {idetim:b}");
|
|
// FIXME: enable the right bits in idetim (and sidetim) to use fast timings
|
|
|
|
let mut ide_devices = Vec::with_capacity(4);
|
|
for ch in 0..2 {
|
|
let channel = if ch == 0 {
|
|
Channel::Primary
|
|
} else {
|
|
Channel::Secondary
|
|
};
|
|
'drive: for dr in 0..2 {
|
|
let drive = if dr == 0 { Drive::Master } else { Drive::Slave };
|
|
|
|
unsafe {
|
|
select_drive(drive, channel);
|
|
// FIXME: clear sector count and lba0, lba1, lba2 registers
|
|
let status = ata_send_command(CMD_IDENTIFY, channel);
|
|
if status == 0 {
|
|
continue; // If status = 0, no device
|
|
}
|
|
|
|
loop {
|
|
let status = {
|
|
let addr = if channel.secondary() {
|
|
SECONDARY_COMMAND
|
|
} else {
|
|
PRIMARY_COMMAND
|
|
} + COMMAND_STATUS_OFFSET;
|
|
Port::<u8>::new(addr).read()
|
|
};
|
|
|
|
if status & 1 == 1 {
|
|
// if error (bit 0), device is not ATA
|
|
// FIXME: ATAPI devices
|
|
continue 'drive;
|
|
}
|
|
if !((status >> 7) & 1 == 1) && (status >> 3) & 1 == 1 {
|
|
// BSY cleared, DRQ set, everything is right
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Read identification space of device
|
|
let addr = if channel.secondary() {
|
|
SECONDARY_COMMAND
|
|
} else {
|
|
PRIMARY_COMMAND
|
|
} + DATA_OFFSET;
|
|
let mut buffer = [0_u8; 512];
|
|
read_dword_buffer(addr, buffer.as_mut_ptr() as *mut _, 128);
|
|
// for (i, byte) in buffer.iter().enumerate() {
|
|
// if byte.is_ascii() {
|
|
// trace!("byte {i}: {byte:b}, ascii: {}", *byte as char);
|
|
// } else {
|
|
// trace!("byte {i}: {byte:b}");
|
|
// }
|
|
// }
|
|
|
|
if buffer[99] & 1 != 1 {
|
|
// FIXME: PIO mode support
|
|
error!("IDE drive {channel:?}/{drive:?} does not support DMA");
|
|
continue;
|
|
}
|
|
|
|
// FIXME: CHS support
|
|
let lba48 = (buffer[167] >> 2) & 1 == 1;
|
|
|
|
let size = buffer[200] as u64
|
|
| (buffer[201] as u64) << 8
|
|
| (buffer[202] as u64) << 16
|
|
| (buffer[203] as u64) << 24
|
|
| (buffer[204] as u64) << 32
|
|
| (buffer[205] as u64) << 40
|
|
| (buffer[206] as u64) << 48
|
|
| (buffer[207] as u64) << 54;
|
|
trace!("IDE drive {channel:?}/{drive:?} has {size} sectors");
|
|
|
|
ide_devices.push(IdeDevice {
|
|
channel,
|
|
drive,
|
|
size,
|
|
lba48_support: lba48,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
let bmiba = device_info.bar(4) & 0xFFFFFFFC;
|
|
|
|
Some(Self {
|
|
device_info,
|
|
ide_devices,
|
|
prdt_frame: None,
|
|
buffer_frames: None,
|
|
bmiba: bmiba.try_into().ok()?,
|
|
})
|
|
}
|
|
|
|
pub fn allocate_dma_frame(
|
|
&mut self,
|
|
mapper: &mut impl Mapper<Size4KiB>,
|
|
frame_allocator: &mut BootInfoFrameAllocator,
|
|
) -> Result<(), MapToError<Size4KiB>> {
|
|
use x86_64::structures::paging::PageTableFlags as Flags;
|
|
|
|
let prdt_frame = frame_allocator
|
|
.allocate_frame()
|
|
.ok_or(MapToError::FrameAllocationFailed)?;
|
|
|
|
let buffer_frames = {
|
|
let mut frame = frame_allocator
|
|
.allocate_frame()
|
|
.ok_or(MapToError::FrameAllocationFailed)?;
|
|
while !frame.start_address().is_aligned(0x10000u64) {
|
|
unsafe {
|
|
frame_allocator.deallocate_frame(frame);
|
|
}
|
|
|
|
frame = frame_allocator
|
|
.allocate_frame()
|
|
.ok_or(MapToError::FrameAllocationFailed)?;
|
|
}
|
|
|
|
let mut frames = Vec::with_capacity(16);
|
|
frames.push(frame);
|
|
for _ in 0..15 {
|
|
let frame = frame_allocator
|
|
.allocate_frame()
|
|
.ok_or(MapToError::FrameAllocationFailed)?;
|
|
frames.push(frame);
|
|
}
|
|
|
|
frames
|
|
};
|
|
let flags = Flags::NO_CACHE | Flags::PRESENT | Flags::WRITABLE;
|
|
|
|
unsafe {
|
|
mapper
|
|
.map_to(
|
|
Page::containing_address(VirtAddr::new(PRDT_START)),
|
|
prdt_frame,
|
|
flags,
|
|
frame_allocator,
|
|
)?
|
|
.flush();
|
|
|
|
for (i, frame) in buffer_frames.iter().enumerate() {
|
|
mapper
|
|
.map_to(
|
|
Page::containing_address(VirtAddr::new(BUFFER_START + i as u64 * 0x1000)),
|
|
*frame,
|
|
flags,
|
|
frame_allocator,
|
|
)?
|
|
.flush()
|
|
}
|
|
}
|
|
|
|
self.prdt_frame = Some(prdt_frame);
|
|
self.buffer_frames = Some(buffer_frames);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read(
|
|
&mut self,
|
|
channel: Channel,
|
|
drive: Drive,
|
|
lba: u64,
|
|
sector_count: u16,
|
|
buffer: &mut Vec<u8>,
|
|
) -> Result<(), TryFromIntError> {
|
|
let lba48_support = self
|
|
.ide_devices
|
|
.iter()
|
|
.find(|d| d.channel == channel && d.drive == drive)
|
|
.map(|d| d.lba48_support)
|
|
.unwrap(); // FIXME: make this an error
|
|
let lba48 = lba > 0xFFFFFFF && lba48_support;
|
|
|
|
// FIXME: make this an error
|
|
assert!((lba48 && lba > 0xFFFFFFF) || (!lba48 && lba <= 0xFFFFFFF));
|
|
|
|
let byte_count = sector_count * SECTOR_SIZE;
|
|
|
|
// prepare PRD table
|
|
let prd = PRDT_START as *mut PhysRegionDescriptor;
|
|
unsafe {
|
|
(*prd).data_buffer = self.buffer_frames.as_ref().unwrap()[0]
|
|
.start_address()
|
|
.as_u64()
|
|
.try_into()?;
|
|
(*prd).byte_count = byte_count;
|
|
// this is the end of table
|
|
(*prd).eot = 1 << 7;
|
|
// this byte is reserved, we should probably set it to 0
|
|
(*prd)._0 = 0;
|
|
}
|
|
|
|
unsafe {
|
|
self.load_prdt(channel);
|
|
self.stop(channel);
|
|
self.set_read(channel);
|
|
self.clear_bmi_status(channel);
|
|
select_drive(drive, channel);
|
|
set_lba(channel, lba, sector_count, lba48);
|
|
|
|
if lba48 {
|
|
ata_send_command(CMD_READ_DMA_EXT, channel);
|
|
} else {
|
|
ata_send_command(CMD_READ_DMA, channel);
|
|
}
|
|
|
|
self.start(channel);
|
|
}
|
|
|
|
loop {
|
|
let status = unsafe { self.bmi_status(channel) };
|
|
|
|
// Bit 2 (INT) set?
|
|
if (status >> 2) & 1 == 1 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// FIXME: error handling
|
|
|
|
unsafe {
|
|
// Stop DMA
|
|
self.stop(channel);
|
|
|
|
// Clear the interrupt bit
|
|
self.clear_bmi_status(channel);
|
|
}
|
|
|
|
for i in 0..byte_count as u64 {
|
|
let addr = (BUFFER_START + i) as *mut u8;
|
|
buffer.push(unsafe { *addr });
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn device_info(&self) -> PciDeviceInfo {
|
|
self.device_info
|
|
}
|
|
|
|
unsafe fn load_prdt(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIDTP_OFFSET;
|
|
Port::<u32>::new(addr).write(
|
|
self.prdt_frame
|
|
.unwrap()
|
|
.start_address()
|
|
.as_u64()
|
|
.try_into()
|
|
.unwrap(),
|
|
);
|
|
}
|
|
|
|
unsafe fn start(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIC_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
let mut bmic = port.read();
|
|
// start transfer
|
|
bmic |= 1;
|
|
// write the new bmic
|
|
port.write(bmic);
|
|
}
|
|
|
|
unsafe fn stop(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIC_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
let mut bmic = port.read();
|
|
// stop ongoing transfer
|
|
bmic &= !1;
|
|
// write the new bmic
|
|
port.write(bmic);
|
|
}
|
|
|
|
unsafe fn set_read(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIC_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
let mut bmic = port.read();
|
|
// mark bit 3 as 0 (read)
|
|
bmic &= !(1 << 3);
|
|
// write the new bmic
|
|
port.write(bmic);
|
|
}
|
|
|
|
unsafe fn set_write(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIC_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
let mut bmic = port.read();
|
|
// mark bit 3 as 1 (write)
|
|
bmic |= 1 << 3;
|
|
// write the new bmic
|
|
port.write(bmic);
|
|
}
|
|
|
|
unsafe fn bmi_status(&self, channel: Channel) -> u8 {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIS_OFFSET;
|
|
let mut port = Port::new(addr);
|
|
port.read()
|
|
}
|
|
|
|
unsafe fn clear_bmi_status(&self, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
BMI_SECONDARY
|
|
} else {
|
|
0
|
|
} + self.bmiba
|
|
+ BMIS_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
let mut bmis = port.read();
|
|
// write 1 to bits 1 (DMA error) and 2 (int status) which clears them
|
|
bmis |= 1 << 1 | 1 << 2;
|
|
// write the new bmis
|
|
port.write(bmis);
|
|
}
|
|
}
|
|
|
|
unsafe fn select_drive(drive: Drive, channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
SECONDARY_COMMAND
|
|
} else {
|
|
PRIMARY_COMMAND
|
|
} + DRIVE_HEAD_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
// FIXME: CHS support
|
|
let drive_command = if drive.slave() {
|
|
// slave & LBA
|
|
0b11110000
|
|
} else {
|
|
// master & LBA
|
|
0b11100000
|
|
};
|
|
|
|
// write the new drive/head register
|
|
port.write(drive_command);
|
|
ata_delay(channel);
|
|
}
|
|
|
|
/// Send ATA command and read status afterwards
|
|
unsafe fn ata_send_command(command: u8, channel: Channel) -> u8 {
|
|
let addr = if channel.secondary() {
|
|
SECONDARY_COMMAND
|
|
} else {
|
|
PRIMARY_COMMAND
|
|
} + COMMAND_STATUS_OFFSET;
|
|
let mut port = Port::new(addr);
|
|
port.write(command);
|
|
ata_delay(channel);
|
|
port.read()
|
|
}
|
|
|
|
/// Read the alternate status register 14 times to create a ~420ns delay
|
|
unsafe fn ata_delay(channel: Channel) {
|
|
let addr = if channel.secondary() {
|
|
SECONDARY_CONTROL
|
|
} else {
|
|
PRIMARY_CONTROL
|
|
} + ALT_STATUS_OFFSET;
|
|
let mut port: Port<u8> = Port::new(addr);
|
|
for _ in 0..14 {
|
|
port.read();
|
|
}
|
|
}
|
|
|
|
/// Set LBA and sector count registers. sector_count of 0 means 65536 sectors
|
|
unsafe fn set_lba(channel: Channel, lba: u64, sector_count: u16, lba48: bool) {
|
|
let command_block = if channel.secondary() {
|
|
SECONDARY_COMMAND
|
|
} else {
|
|
PRIMARY_COMMAND
|
|
};
|
|
|
|
let mut seccount = Port::new(command_block + SECCOUNT_OFFSET);
|
|
let mut lba0 = Port::new(command_block + LBA0_OFFSET);
|
|
let mut lba1 = Port::new(command_block + LBA1_OFFSET);
|
|
let mut lba2 = Port::new(command_block + LBA2_OFFSET);
|
|
let mut head = Port::new(command_block + DRIVE_HEAD_OFFSET);
|
|
let head_value: u8 = head.read();
|
|
|
|
let lba_bytes = lba.to_le_bytes();
|
|
let sector_count_bytes = sector_count.to_le_bytes();
|
|
|
|
// write the new LBA & sector count registers
|
|
// FIXME: CHS support
|
|
if lba48 {
|
|
seccount.write(sector_count_bytes[1]);
|
|
lba0.write(lba_bytes[3]);
|
|
lba1.write(lba_bytes[4]);
|
|
lba2.write(lba_bytes[5]);
|
|
} else {
|
|
head.write(head_value | (lba_bytes[3] & 0x0F));
|
|
}
|
|
seccount.write(sector_count_bytes[0]);
|
|
lba0.write(lba_bytes[0]);
|
|
lba1.write(lba_bytes[1]);
|
|
lba2.write(lba_bytes[2]);
|
|
}
|
|
|
|
unsafe fn read_dword_buffer(port: u16, buffer: *mut u32, mut count: u32) {
|
|
// FIXME: this assumes x86-64
|
|
interrupts::without_interrupts(|| {
|
|
asm!("
|
|
cld
|
|
repne
|
|
insd",
|
|
in("di") buffer,
|
|
in("dx") port,
|
|
inout("cx") count,
|
|
)
|
|
});
|
|
}
|
|
|
|
struct IdeDevice {
|
|
pub channel: Channel,
|
|
pub drive: Drive,
|
|
pub size: u64, // in sectors
|
|
pub lba48_support: bool,
|
|
// FIXME: model
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum Channel {
|
|
Primary,
|
|
Secondary,
|
|
}
|
|
|
|
impl Channel {
|
|
fn secondary(&self) -> bool {
|
|
matches!(self, Self::Secondary)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum Drive {
|
|
Master,
|
|
Slave,
|
|
}
|
|
|
|
impl Drive {
|
|
fn slave(&self) -> bool {
|
|
matches!(self, Self::Slave)
|
|
}
|
|
}
|
|
|
|
#[repr(C, packed)]
|
|
struct PhysRegionDescriptor {
|
|
/// Pointer to the data buffer
|
|
pub data_buffer: u32,
|
|
/// Byte count, 64K maximum per PRD transfer. 0 means 64K
|
|
pub byte_count: u16,
|
|
/// Reserved byte
|
|
pub _0: u8,
|
|
/// MSB marks end of transfer
|
|
pub eot: u8,
|
|
}
|