forked from AbleOS/ableos
551 lines
16 KiB
Rust
551 lines
16 KiB
Rust
|
/*
|
||
|
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
|
||
|
*
|
||
|
* SPDX-License-Identifier: MPL-2.0
|
||
|
*/
|
||
|
|
||
|
use core::mem;
|
||
|
use core::num::TryFromIntError;
|
||
|
|
||
|
use x86_64::instructions::port::Port;
|
||
|
use x86_64::instructions::{hlt, interrupts};
|
||
|
use x86_64::structures::paging::{FrameAllocator, FrameDeallocator};
|
||
|
// FIXME: platform agnostic paging stuff
|
||
|
use x86_64::structures::paging::{mapper::MapToError, Mapper, Page, PhysFrame, Size4KiB};
|
||
|
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;
|
||
|
|
||
|
/// 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 LBA48 DMA command
|
||
|
const CMD_READ_DMA_EXT: u8 = 0x25;
|
||
|
|
||
|
pub struct PiixIde {
|
||
|
device_info: PciDeviceInfo,
|
||
|
ide_devices: Vec<IdeDevice>,
|
||
|
prdt_frame: Option<PhysFrame>,
|
||
|
buffer_frames: Option<Vec<PhysFrame>>,
|
||
|
bmiba: u16,
|
||
|
}
|
||
|
|
||
|
impl PiixIde {
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
if (buffer[167] >> 2) & 1 != 1 {
|
||
|
// FIXME: 24-bit LBA and CHS support
|
||
|
error!("IDE drive {channel:?}/{drive:?} does not support 48-bit LBA");
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) -> Result<(), TryFromIntError> {
|
||
|
// 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()?;
|
||
|
// we want to read 512 bytes
|
||
|
(*prd).byte_count = 512;
|
||
|
// 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::Primary);
|
||
|
self.stop(Channel::Primary);
|
||
|
self.set_read(Channel::Primary);
|
||
|
self.clear_bmi_status(Channel::Primary);
|
||
|
select_drive(Drive::Master, Channel::Primary);
|
||
|
set_lba(Channel::Primary, 0, 1);
|
||
|
ata_send_command(CMD_READ_DMA_EXT, Channel::Primary);
|
||
|
self.start(Channel::Primary);
|
||
|
|
||
|
loop {
|
||
|
let status = self.bmi_status(Channel::Primary);
|
||
|
trace!("read status: 0b{status:b}");
|
||
|
|
||
|
// Bit 2 (INT) set?
|
||
|
if (status >> 2) & 1 == 1 {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FIXME: error handling
|
||
|
|
||
|
// Stop DMA
|
||
|
self.stop(Channel::Primary);
|
||
|
|
||
|
// Clear the interrupt bit
|
||
|
self.clear_bmi_status(Channel::Primary);
|
||
|
|
||
|
for i in 0..512 {
|
||
|
let addr = (BUFFER_START + i) as *mut u8;
|
||
|
trace!("byte {i}: {}", *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) {
|
||
|
// FIXME: CHS and LBA24 support
|
||
|
assert!(lba < 0xFFFFFFFFFFFF);
|
||
|
|
||
|
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 lba_bytes = lba.to_le_bytes();
|
||
|
let sector_count_bytes = sector_count.to_le_bytes();
|
||
|
|
||
|
// write the new LBA & sector count registers
|
||
|
// if LBA48 {
|
||
|
seccount.write(sector_count_bytes[1]);
|
||
|
lba0.write(lba_bytes[3]);
|
||
|
lba1.write(lba_bytes[4]);
|
||
|
lba2.write(lba_bytes[5]);
|
||
|
// }
|
||
|
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
|
||
|
// FIXME: model
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone, Debug)]
|
||
|
enum Channel {
|
||
|
Primary,
|
||
|
Secondary,
|
||
|
}
|
||
|
|
||
|
impl Channel {
|
||
|
fn secondary(&self) -> bool {
|
||
|
matches!(self, Self::Secondary)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone, Debug)]
|
||
|
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,
|
||
|
}
|