I'm writting a little OS with a friend, and I used a lot OSDev wiki. Now, I'm working on an IDE/DMA driver and I have some problems.
The context: We're writting an OS for x86 processors. It is multitask, use one CPU, doesn't have virtual (paged) memory. We run it in VirtualBox (sometimes QEMU or real hardware).
The problem: I try to read something once on the first disk of the first controller, using a DMA method. When I get the corresponding interrupt (IRQ14) the Busmaster status register has the value 0xFF, the IDE status byte is 0x00. If I try a second call, no interrupt happens.
The most weird is that the behaviour depends on configuration in VirtualBox. I mean if I put only one disk on the controller, I get nothing (no interruption after the first read). The PCI configuration space gives me "Int Line = 0x00". But if I put two disks on the controller, I get the behaviour described above and "Int Line = 0xd" . In QEMU, the behaviour is the above's one for one or two disks.
Informations on the method I use:
I read the PCI configuration space to detect IDE controllers. Then, I detect ATA IDE disks (I ignore ATAPI and SATA drives) using the method described in the wiki (http://wiki.osdev.org/ATA_PIO_Mode#Dete ... ialization). Finally I call a method which does the DMA and the ATA commands to read the disk.
I probably forgot a configuration step for the controller or the ATA drive but I don't found it. Can you help me ?
Thank you.
[edit] My DMA code here:
Code: Select all
/* Physical Descriptor structure */
typedef union _prd_t {
uint64_t qword;
struct {
unsigned int phy_mem_add:32;
unsigned int byte_count:16;
unsigned int reserved:15;
unsigned int last:1;
} details __attribute__ ((packed));
} prd_t;
/* DMA registers */
typedef enum _dma_reg_t {
DMA_CMD = 0x0,
DMA_STATUS = 0x2,
DMA_PRDT0 = 0x4,
DMA_PRDT1 = 0x5,
DMA_PRDT2 = 0x6,
DMA_PRDT3 = 0x7,
} dma_reg_t;
/* DMA commands*/
#define DMA_STOP 0x00
#define DMA_START 0x01
#define DMA_WRITE (1 << 3)
#define DMA_READ 0x00
/* One prdt for the tests */
prd_t essai __attribute__ ((aligned (4)));
drive_t *current = NULL;
/* Traitant d'interruptions */
void ide_handler(void) {
uint8_t reg;
/* Acquitter les interruptions des PICs maitres et esclaves */
ACK_IT_S;
ACK_IT_M;
printf("Interruption:\n");
if (!current) {
printf("No request !\n");
return;
}
/* Interruption came from the disk ? */
reg = inb(current->dma_reg + DMA_STATUS);
outb(inb(current->dma_reg + DMA_STATUS)&~1, current->dma_reg + DMA_STATUS); /* Stop DMA */
printf("\tDMA_STATUS = 0x%x\n", reg);
current = NULL;
}
/*
* Initialize DMA giving a prdt and a READ/WRITE command.
*/
static void ide_dma_init(drive_t *drive, int count, char *buffer) {
uint8_t tmp_dma_reg;
/* Construire l'entrée PRD */
essai.details.last = 1;
essai.details.byte_count = (unsigned int) count * 512;
essai.details.phy_mem_add = (unsigned int) buffer;
essai.details.reserved = 0;
// TODO Create a PRDT.
/* Put controller in stad-by */
outb(DMA_STOP, drive->dma_reg + DMA_CMD);
tmp_dma_reg = (uint8_t) inb(drive->dma_reg + DMA_STATUS); /* Effacer interrupt et error */
outb(tmp_dma_reg | 0x6, drive->dma_reg + DMA_STATUS);
/* Load PRDT in DMA register */
outl((uint32_t)&essai, drive->dma_reg + DMA_PRDT0);
/* Start DMA transfert */
outb(DMA_START | DMA_READ, drive->dma_reg + DMA_CMD);
}
/* Send a LBA28 request */
static void ide_dma_lba28_cmd(drive_t *drive, uint64_t block, uint16_t count, uint8_t cmd) {
/* Select MASTER or SLAVE */
outb(drive->level << 4, drive->base_reg + ATA_REG_HDDEVSEL);
ATA_WAIT_READY(drive);
/* Wait for the disk to be ready
* TODO: get and work with the return value */
ide_wait_drive_ready(drive);
/* Select the drive, send high part of LBA */
outb(0xE0 | (drive->level << 4) | ((count & 0xF000000) >> 24), drive->base_reg + ATA_REG_HDDEVSEL);
ide_wait_drive_ready(drive);
/* Activate interruption in controller "control register" */
outb(0, drive->dcr_reg);
/* Send sector count */
outb((uint8_t)count, drive->base_reg + ATA_REG_SECCOUNT);
/* Send LBA address */
outb((uint8_t)(block), drive->base_reg + ATA_REG_LBA0);
outb((uint8_t)(block >> 8), drive->base_reg + ATA_REG_LBA1);
outb((uint8_t)(block >> 16), drive->base_reg + ATA_REG_LBA2);
/* Send command */
outb(cmd, drive->base_reg + ATA_REG_COMMAND);
ATA_WAIT_READY(drive);
}
int ide_dma_read(int drive_num, uint64_t block, uint16_t count, char *buffer)
{
drive_t *drive;
if (UNLIKELY(buffer == NULL)) {
return RET_ERROR;
}
if (UNLIKELY(count == 0)) /* Nothing to do, go out */
return RET_OK;
/* Get the drive structure */
drive = drives[drive_num];
if (UNLIKELY(drive == NULL)) {
return RET_ERROR;
}
/* Block while previous read is not finished
* TODO Block this processus with the scheduler not with its bad way */
while (current != NULL) hlt();
current = drive;
/* Configure the DMA */
ide_dma_init(drive, count, buffer);
/* Perform IDE request */
if (drive->add_mode == LBA28) {
ide_dma_lba28_cmd(drive, block, count, ATA_CMD_READ_DMA_LBA28);
} else if (drive->add_mode == LBA48) {
ide_dma_lba48_cmd(drive, block, count, ATA_CMD_READ_DMA_LBA48);
} else
return RET_ERROR;
return RET_OK;
}