I'm trying out using PCI Busmastering ATA DMA to read from a hard disk.
I've been having trouble getting it to do reads. A single interrupt occurs and the status byte for DMA reads 0x04, but no data is transferred to the space in memory designated by the PRD. For testing, I'm currently using a PRDT with a single PRD in it.
The order of steps I'm taking:
Clear the command byte
Set bit 3 in the status byte
Send the appropriate values to the to PCI config register
Set bit 2 in the PCI command register
Allocate a frame and retrieve its start address
Create a PRDT (physical region descriptor table) with one PRD, and retrieve its start address
Send the PRDT address to the DMA_PRDT_ADDR register
Reset IRQ status by setting bit 2 in the DMA status byte
Set bit 3 in the DMA command byte to set data transfer direction to read
Clear bits 0 and 2 in the DMA status byte
Set the PRIMARY_BUS_SELECT register for the drive with the drive value, the sector count, and the most significant 8 bits of the LBA
Set the SECTOR_COUNT register and the other 3 LBA registers
Set the COMMAND_IO register to 0xC8 in the drive to set it to DMA read
Set bit 1 of the command byte to start the transfer
The largest issue has been figuring out how to debug the problem, because I'm unaware of many intermediary steps where progress can be checked to narrow down the issue.
Thanks for the help and time!
Code:
pub fn read_from_disk_v2(drive: u8, lba: u32, sector_count: u32) -> Result<PhysicalAddress, ()> {
//Resetting bus master command register,
unsafe{
DMA_PRIM_COMMAND_BYTE.outb(0);
DMA_PRIM_STATUS_BYTE.outb(4);
}
//selects the PCI command register: the device is currently hardcoded to bus 0, slot 1, function 1, and the value to access the command register is an offset of 4
//following steps for this as outlined in http://wiki.osdev.org/PCI#Configuration_Space_Access_Mechanism_.231
PCI_CONFIG_ADDRESS_PORT.outb((1 << 11) | (1 << 8) | (4 & 0xFC) | 0x8000_0000);
//writes the value in the PCI data register to have it transferred to the selected PCI register, using this to set bit 2 in the command register
PCI_DATA_ADDRESS_PORT.outb(4);
//Frame allocation occurs here
if let Some(frame) = allocate_frame() {
let addr = frame.start_address();
let start_addr: u32 = addr as u32;
//prdt table defined: currently one PRD in PRDT for testing
//sector count is multiplied by 512 because 512 bytes in a sector
let prdt: [u64;1] = [start_addr as u64 | (sector_count as u64) *512 << 32 | 1 << 63];
let prdt_ref = &prdt[0] as *const u64;
//gets the physical address of the prdt and sends that to the DMA prdt register
let prdt_paddr = assign_prdt_paddr();
DMA_PRIM_PRDT_ADDR.outl(prdt_paddr);
DMA_PRIM_STATUS_BYTE.outb(0x04); // to reset the interrupt 0x14 status
//set bit 3 to set direction of controller for "read"
//http://wiki.osdev.org/ATA/ATAPI_using_DMA#The_Command_Byte states bit 3 value = 8, need to check if that's a typo
DMA_PRIM_COMMAND_BYTE.outb(0x08);
//get original value in the DMA status register and clear bits 0 and 2
let original_status = DMA_PRIM_STATUS_BYTE.inb();
//0xFA is value to clear bits 0 and 2 (0b11111010 in binary)
unsafe{DMA_PRIM_STATUS_BYTE.outb(original_status & 0xFA);}
//selects the drive (0xE0 for primary in this case) and sends top 8 bits of LBA (0 for testing) and sector count(1 in this case) to appropriate registers
let master_select: u8 = 0xE0 | (0 << 4) | ((lba >> 24) & 0x0F) as u8;
PRIMARY_BUS_SELECT.outl(master_select);
//enters the sector count and the other 3 bytes of the LBA to the appropriate registers
SECTORCOUNT.outb(1);
LBALO.outb(lba);
LBAMID.outb(lba>>8);
LBAHI.outb(lba>>16);
//sets the value in the command_io register to 0xC8, the code for DMA read
COMMAND_IO.outb(0xC8);
//setting bit 1 of the command byte starts the transfer
DMA_PRIM_COMMAND_BYTE.outb(0x08 | 0x01);
return Ok(start_addr);
}
return Err(());
}