ableos/ableos/src/devices/pci/piix.rs

190 lines
5.8 KiB
Rust

/*
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
*
* SPDX-License-Identifier: MPL-2.0
*/
use core::num::TryFromIntError;
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 super::port::Port;
use super::{check_device, PciDeviceInfo};
const PRDT_START: u64 = 0x_ffff_ffff_0000_0000;
const BUFFER_START: u64 = 0x_ffff_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;
pub struct Piix {
device_info: PciDeviceInfo,
prdt_frame: Option<PhysFrame>,
buffer_frames: Option<Vec<PhysFrame>>,
bmiba: u16,
}
impl Piix {
// FIXME: make this return a Result
pub fn new(bus: u8, device: u8) -> Option<Self> {
let device_info = check_device(bus, device)?;
trace!("device_info: {device_info}");
let bmiba = device_info.bars[4] & 0xFFFFFFFC;
Some(Self {
device_info,
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(&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;
}
unsafe {
self.stop(false);
self.set_read(false);
self.clear_status(false);
// (*bmiba).bmidtpp = self.prdt_frame.unwrap().start_address().as_u64().try_into()?;
}
Ok(())
}
unsafe fn stop(&self, secondary: bool) {
let addr = if secondary { BMI_SECONDARY } else { 0 } + self.bmiba + BMIC_OFFSET;
let mut bmic = Port::<u8>::new(addr).read();
// stop ongoing transfer
bmic &= !1;
// write the new bmic
Port::<u8>::new(addr).write(bmic);
}
unsafe fn set_read(&self, secondary: bool) {
let addr = if secondary { BMI_SECONDARY } else { 0 } + self.bmiba + BMIC_OFFSET;
let mut bmic = Port::<u8>::new(addr).read();
// mark bit 3 as 0 (read)
bmic &= !(1 << 3);
// write the new bmic
Port::<u8>::new(addr).write(bmic);
}
unsafe fn set_write(&self, secondary: bool) {
let addr = if secondary { BMI_SECONDARY } else { 0 } + self.bmiba + BMIC_OFFSET;
let mut bmic = Port::<u8>::new(addr).read();
// mark bit 3 as 1 (write)
bmic |= 1 << 3;
// write the new bmic
Port::<u8>::new(addr).write(bmic);
}
unsafe fn clear_status(&self, secondary: bool) {
let addr = if secondary { BMI_SECONDARY } else { 0 } + self.bmiba + BMIS_OFFSET;
let mut bmis = Port::<u8>::new(addr).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::<u8>::new(addr).write(bmis);
}
}
#[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,
}