Add IDE ATA read using PIO/DMA

This commit is contained in:
Mark
2019-10-22 01:09:22 +03:00
parent 9acc2c6064
commit 1af3d0dd78
6 changed files with 236 additions and 5 deletions
+3
View File
@@ -22,6 +22,9 @@
#define ATA_CMD_IDENTIFY 0xEC
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO_EX 0x24
#define ATA_CMD_READ_DMA 0xC8
#define ATA_CMD_READ_DMA_EX 0x25
#define ATA_SR_BUSY (1 << 7)
+12
View File
@@ -6,6 +6,12 @@
#define IDE_DEFAULT_BAR2 0x170
#define IDE_DEFAULT_BAR3 0x376
struct ide_prdt_entry {
uint32_t addr;
uint16_t size;
uint16_t attr;
} __attribute__((packed));
struct ide_controller {
uint16_t bar0; // Primary channel
uint16_t bar1; // Primary control port
@@ -14,6 +20,8 @@ struct ide_controller {
uint16_t bar4; // Bus master control
uint8_t irq0, irq1;
uintptr_t dma_page;
struct ide_channel {
uint16_t base;
uint16_t ctrl;
@@ -28,6 +36,7 @@ struct ide_controller {
// 2 1 if slave device
// 3 1 if device is ATAPI (ATA otherwise)
// ...
struct ide_controller *ide;
uint8_t attr;
uint16_t signature;
uint16_t caps;
@@ -37,4 +46,7 @@ struct ide_controller {
} devices[4];
};
int ide_ata_read_pio(struct ide_device *dev, void *buf, size_t nsect, uint64_t lba);
int ide_ata_read_dma(struct ide_device *dev, void *buf, size_t nsect, uint64_t lba);
void ide_init(struct ide_controller *ide);
+2
View File
@@ -5,6 +5,8 @@
#pragma once
#include "sys/types.h"
#define MIN(x, y) ((x) < (y) ? (x) : (y))
void *memcpy(void *restrict dst, const void *restrict src, size_t cnt);
void *memmove(void *dst, const void *src, size_t cnt);
void *memset(void *dst, int v, size_t count);
-2
View File
@@ -6,8 +6,6 @@
#include "sys/debug.h"
#include "sys/mm.h"
// XXX: Choose a better place for this
#define MIN(x, y) ((x) < (y) ? (x) : (y))
// Memory required for a port:
// 1024 - command list (32 * sizeof(ahci_command_header))
// 256 - received FIS
+214 -3
View File
@@ -1,8 +1,12 @@
#include "sys/amd64/hw/ide/ide.h"
#include "sys/amd64/hw/ide/ata.h"
#include "sys/amd64/mm/phys.h"
#include "sys/amd64/hw/io.h"
#include "sys/assert.h"
#include "sys/string.h"
#include "sys/debug.h"
#include "sys/panic.h"
#include "sys/mm.h"
static void ide_reg_write(struct ide_controller *ide, uint8_t channel, uint8_t reg, uint8_t data) {
if (reg > 0x07 && reg < 0x0C) {
@@ -47,7 +51,7 @@ uint8_t ide_reg_read(struct ide_controller *ide, uint8_t channel, uint8_t reg) {
return result;
}
void ide_reg_read_buf(struct ide_controller *ide, uint8_t channel, uint8_t reg, uint32_t *buf, size_t count) {
void ide_reg_read_dbuf(struct ide_controller *ide, uint8_t channel, uint8_t reg, uint32_t *buf, size_t count) {
if (reg > 0x07 && reg < 0x0C) {
ide_reg_write(ide, channel, ATA_REG_CONTROL, 0x80 | ide->channels[channel].no_int);
}
@@ -71,6 +75,30 @@ void ide_reg_read_buf(struct ide_controller *ide, uint8_t channel, uint8_t reg,
}
}
void ide_reg_read_wbuf(struct ide_controller *ide, uint8_t channel, uint8_t reg, uint16_t *buf, size_t count) {
if (reg > 0x07 && reg < 0x0C) {
ide_reg_write(ide, channel, ATA_REG_CONTROL, 0x80 | ide->channels[channel].no_int);
}
while (count) {
if (reg < 0x08) {
*buf++ = inw(ide->channels[channel].base + reg - 0x00);
} else if (reg < 0x0C) {
*buf++ = inw(ide->channels[channel].base + reg - 0x06);
} else if (reg < 0x0E) {
*buf++ = inw(ide->channels[channel].ctrl + reg - 0x0A);
} else if (reg < 0x16) {
*buf++ = inw(ide->channels[channel].bmide + reg - 0x0E);
}
--count;
}
if (reg > 0x07 && reg < 0x0C) {
ide_reg_write(ide, channel, ATA_REG_CONTROL, ide->channels[channel].no_int);
}
}
// TODO: this is bad
#define ide_delay() for (size_t i = 0; i < 1000000; ++i);
@@ -90,6 +118,174 @@ static int ide_wait_busy(struct ide_controller *ide, uint8_t ch) {
return 0;
}
static int ide_wait_poll(struct ide_controller *ide, uint8_t ch) {
// 400ns delay
for(int i = 0; i < 4; i++) {
ide_reg_read(ide, ch, ATA_REG_ALTSTATUS);
}
return ide_wait_busy(ide, ch);
}
int ide_ata_set_addr(struct ide_device *dev, uint64_t lba, size_t nsect) {
struct ide_controller *ide = dev->ide;
uint8_t ch = (dev->attr >> 1) & 1;
uint8_t sl = (dev->attr >> 2) & 1;
uint8_t lba_buf[6] = {0};
int lba_mode;
uint16_t cyl;
uint8_t head, sect = 0;
if (lba >= 0x10000000) {
lba_mode = 2;
// LBA48
lba_buf[0] = lba & 0xFF;
lba_buf[1] = (lba >> 8) & 0xFF;
lba_buf[2] = (lba >> 16) & 0xFF;
lba_buf[3] = (lba >> 24) & 0xFF;
lba_buf[4] = (lba >> 32) & 0xFF;
lba_buf[5] = (lba >> 40) & 0xFF;
} else if (dev->caps & 0x200) {
lba_mode = 1;
// LBA24
lba_buf[0] = lba & 0xFF;
lba_buf[1] = (lba >> 8) & 0xFF;
lba_buf[1] = (lba >> 16) & 0xFF;
} else {
lba_mode = 0;
sect = (lba % 63) + 1;
cyl = (lba + 1 - sect) / (16 * 63);
head = (lba + 1 - sect) % (16 * 63) / (63);
// CHS
lba_buf[0] = sect;
lba_buf[1] = cyl & 0xFF;
lba_buf[2] = cyl >> 8;
}
while (ide_reg_read(ide, ch, ATA_REG_STATUS) & ATA_SR_BUSY);
if (lba_mode) {
ide_reg_write(ide, ch, ATA_REG_HDDEVSEL, 0xE0 | (sl << 4));
} else {
ide_reg_write(ide, ch, ATA_REG_HDDEVSEL, 0xA0 | (sl << 4) | head);
}
if (lba_mode == 2) {
ide_reg_write(ide, ch, ATA_REG_SECCOUNT1, 0);
ide_reg_write(ide, ch, ATA_REG_LBA3, lba_buf[3]);
ide_reg_write(ide, ch, ATA_REG_LBA4, lba_buf[4]);
ide_reg_write(ide, ch, ATA_REG_LBA5, lba_buf[5]);
}
ide_reg_write(ide, ch, ATA_REG_SECCOUNT0, nsect);
ide_reg_write(ide, ch, ATA_REG_LBA0, lba_buf[0]);
ide_reg_write(ide, ch, ATA_REG_LBA1, lba_buf[1]);
ide_reg_write(ide, ch, ATA_REG_LBA2, lba_buf[2]);
return lba_mode;
}
int ide_ata_read_dma(struct ide_device *dev, void *buf, size_t nsect, uint64_t lba) {
struct ide_controller *ide = dev->ide;
uint8_t ch = (dev->attr >> 1) & 1;
uint8_t sl = (dev->attr >> 2) & 1;
// Disable IRQs
ide_reg_write(ide, ch, ATA_REG_CONTROL, ATA_CONTROL_NO_INT);
// TODO: wait until previous DMA operation finishes
struct ide_prdt_entry *ent = (struct ide_prdt_entry *) MM_VIRTUALIZE(ide->dma_page + ch * 2048);
size_t bytes_count = nsect * 512;
size_t bytes_left = bytes_count;
size_t prd_count = (bytes_count + 65535) / 65536;
// TODO: do this properly: check alignments etc
uintptr_t buf_phys = MM_PHYS(buf);
// Fill out PRDT
for (size_t i = 0; i < prd_count; ++i) {
size_t prd_size = MIN(65536, bytes_left);
_assert((buf_phys + i * 65536) < 0xFFFFFFFF);
ent[i].addr = (buf_phys + i * 65536) & 0xFFFFFFFF;
ent[i].size = prd_size == 65536 ? 0 : prd_size;
ent[i].attr = 0;
if (i == prd_count - 1) {
ent[i].attr = 1 << 15;
}
bytes_left -= prd_size;
}
// Start DMA transfer
outb(ide->channels[ch].bmide + 0x00, 1);
int lba_mode = ide_ata_set_addr(dev, lba, nsect);
uint8_t cmd;
if (lba_mode == 2) {
cmd = ATA_CMD_READ_DMA_EX;
} else {
cmd = ATA_CMD_READ_DMA;
}
ide_reg_write(ide, ch, ATA_REG_COMMAND, cmd);
// Wait for the drive to receive the command before sending another one
if (ide_wait_poll(ide, ch) < 0) {
panic("Read failed\n");
}
// TODO: IRQs?
while (1) {
uint8_t status = inb(ide->channels[ch].bmide + 0x02);
if (status & (1 << 1)) {
panic("DMA error\n");
}
if (!(status & (1 << 0))) {
break;
}
}
return 0;
}
int ide_ata_read_pio(struct ide_device *dev, void *buf, size_t nsect, uint64_t lba) {
struct ide_controller *ide = dev->ide;
uint8_t ch = (dev->attr >> 1) & 1;
uint8_t sl = (dev->attr >> 2) & 1;
// Disable IRQs
ide_reg_write(ide, ch, ATA_REG_CONTROL, ATA_CONTROL_NO_INT);
int lba_mode = ide_ata_set_addr(dev, lba, nsect);
uint8_t cmd;
if (lba_mode == 2) {
cmd = ATA_CMD_READ_PIO_EX;
} else {
cmd = ATA_CMD_READ_PIO;
}
ide_reg_write(ide, ch, ATA_REG_COMMAND, cmd);
uint16_t *word = (uint16_t *) buf;
for (size_t i = 0; i < nsect; ++i) {
if (ide_wait_poll(ide, ch) != 0) {
return -1;
}
// Read a sector
ide_reg_read_wbuf(ide, ch, ATA_REG_DATA, word + 256 * i, 256);
}
return 0;
}
static char test_buf[65536 * 2] __attribute__((aligned(65536)));
void ide_init(struct ide_controller *ide) {
// Initialize from PCI IDE information: BARs
ide->channels[0].base = ide->bar0;
@@ -106,11 +302,26 @@ void ide_init(struct ide_controller *ide) {
ide_reg_write(ide, 0, ATA_REG_CONTROL, ATA_CONTROL_NO_INT);
ide_reg_write(ide, 1, ATA_REG_CONTROL, ATA_CONTROL_NO_INT);
char ident_buf[512];
// Setup busmastering DMA
ide->dma_page = amd64_phys_alloc_page();
_assert(ide->dma_page != MM_NADDR);
outb(ide->channels[0].bmide + 0x00, 0);
outb(ide->channels[0].bmide + 0x04, ide->dma_page & 0xFF);
outb(ide->channels[0].bmide + 0x05, (ide->dma_page >> 8) & 0xFF);
outb(ide->channels[0].bmide + 0x06, (ide->dma_page >> 16) & 0xFF);
outb(ide->channels[0].bmide + 0x07, (ide->dma_page >> 24) & 0xFF);
outb(ide->channels[1].bmide + 0x00, 0);
outb(ide->channels[1].bmide + 0x04, (ide->dma_page + 2048) & 0xFF);
outb(ide->channels[1].bmide + 0x05, ((ide->dma_page + 2048) >> 8) & 0xFF);
outb(ide->channels[1].bmide + 0x06, ((ide->dma_page + 2048) >> 16) & 0xFF);
outb(ide->channels[1].bmide + 0x07, ((ide->dma_page + 2048) >> 24) & 0xFF);
char ident_buf[1024];
for (size_t ch = 0; ch < 2; ++ch) {
for (size_t sl = 0; sl < 2; ++sl) {
struct ide_device *dev = &ide->devices[ch * 2 + sl];
dev->ide = ide;
dev->attr = 0;
@@ -142,7 +353,7 @@ void ide_init(struct ide_controller *ide) {
}
// Read result from device's I/O buffer
ide_reg_read_buf(ide, ch, ATA_REG_DATA, (uint32_t *) ident_buf, 128);
ide_reg_read_dbuf(ide, ch, ATA_REG_DATA, (uint32_t *) ident_buf, 128);
// Set device present, channel and slave/master
dev->attr |= (1 << 0) | (ch << 1) | (sl << 2);
+5
View File
@@ -13,6 +13,7 @@ void pci_ide_init(pci_addr_t addr) {
uint32_t irq_config = pci_config_read_dword(addr, 0x3C);
uint32_t class = pci_config_read_dword(addr, 0x08);
uint32_t bar;
uint32_t cmd = pci_config_read_dword(addr, 0x04);
// Read BARs
bar = pci_config_read_dword(addr, 0x10);
@@ -54,6 +55,10 @@ void pci_ide_init(pci_addr_t addr) {
panic("TODO: support IDE controllers with non-legacy IRQs\n");
}
// Enable PCI busmastering for IDE controller
cmd |= (1 << 2);
pci_config_write_dword(addr, 0x04, cmd);
irq_config &= ~0xFF;
irq_config |= 0xFE;
pci_config_write_dword(addr, 0x3C, irq_config);