VirtualBox ATA DMA Read timing out (Primary Channel Only)
Posted: Fri Feb 25, 2011 7:23 pm
This is a bug that's been bothering me for a while.
My ATA DMA driver works seemingly perfectly on Bochs and Qemu (real hardware needs to be tested sometime), but on VirtualBox (Using the PIIX4 controller), reads on the first channel (disks 0 and 1) time out (I have a 2 second timeout), while for the same code on the secondary channel, it works.
Here is my ATA driver code, with the debugging prints removed to keep the length down, the original source is avaliable at http://www.mutabah.net/source/acess2/Mo ... /io.c.html
Does anyone know of caveats when working with the VBox driver?
My ATA DMA driver works seemingly perfectly on Bochs and Qemu (real hardware needs to be tested sometime), but on VirtualBox (Using the PIIX4 controller), reads on the first channel (disks 0 and 1) time out (I have a 2 second timeout), while for the same code on the secondary channel, it works.
Here is my ATA driver code, with the debugging prints removed to keep the length down, the original source is avaliable at http://www.mutabah.net/source/acess2/Mo ... /io.c.html
Code: Select all
int ATA_ReadDMA(Uint8 Disk, Uint64 Address, Uint Count, void *Buffer)
{
int cont = (Disk>>1)&1; // Controller ID
int disk = Disk & 1;
Uint16 base;
Sint64 timeoutTime;
Uint8 val;
// Check if the count is small enough
if(Count > MAX_DMA_SECTORS) {
Log_Warning("ATA", "Passed too many sectors for a bulk DMA read (%i > %i)",
Count, MAX_DMA_SECTORS);
return 1;
}
// Get exclusive access to the disk controller
Mutex_Acquire( &glaATA_ControllerLock[ cont ] );
// Set Size
gATA_PRDTs[ cont ].Bytes = Count * SECTOR_SIZE;
// Get Port Base
base = ATA_GetBasePort(Disk);
// Reset IRQ Flag
gaATA_IRQs[cont] = 0;
// Set up transfer
if( Address > 0x0FFFFFFF ) // Use LBA48
{
outb(base+0x6, 0x40 | (disk << 4));
outb(base+0x2, 0 >> 8); // Upper Sector Count
outb(base+0x3, Address >> 24); // Low 2 Addr
outb(base+0x4, Address >> 28); // Mid 2 Addr
outb(base+0x5, Address >> 32); // High 2 Addr
}
else
{
// Magic, Disk, High Address nibble
outb(base+0x06, 0xE0 | (disk << 4) | ((Address >> 24) & 0x0F));
}
outb(base+0x01, 0x01); //?
outb(base+0x02, (Uint8) Count); // Sector Count
outb(base+0x03, (Uint8) Address); // Low Addr
outb(base+0x04, (Uint8) (Address >> 8)); // Middle Addr
outb(base+0x05, (Uint8) (Address >> 16)); // High Addr
// HACK: Ensure the PRDT is reset
ATA_int_BusMasterWriteDWord(cont*8+4, gaATA_PRDT_PAddrs[cont]);
if( Address > 0x0FFFFFFF )
outb(base+0x07, HDD_DMA_R48); // Read Command (LBA48)
else
outb(base+0x07, HDD_DMA_R28); // Read Command (LBA28)
// Start transfer
ATA_int_BusMasterWriteByte( cont * 8, 9 ); // Read and start
// Wait for transfer to complete
timeoutTime = now() + ATA_TIMEOUT;
while( gaATA_IRQs[cont] == 0 && now() < timeoutTime) HALT();
// Complete Transfer
ATA_int_BusMasterWriteByte( cont * 8, 8 ); // Read and stop
val = inb(base+0x7);
if( gaATA_IRQs[cont] == 0 ) {
// Release controller lock
Mutex_Release( &glaATA_ControllerLock[ cont ] );
Log_Warning("ATA",
"Read timeout on disk %i (Reading sector 0x%llx)",
Disk, Address);
// Return error
return 1;
}
else {
// Copy to destination buffer
memcpy( Buffer, gATA_Buffers[cont], Count*SECTOR_SIZE );
// Release controller lock
Mutex_Release( &glaATA_ControllerLock[ cont ] );
return 0;
}
}