Orion
Barry Importing existing Orion kernel d41a53c (3 years, 3 months ago)
/*
* This file is the driver for the PCI IDE devices, which handles access to
* ATA/ATAPI devices. It detects the drives connected to the system, and
* handles any access to them - with the ability to do DMA if possible. This
* driver will access the drives in the best available way, presenting a uniform
* interface for any other code to use. It also adds a device node to the DevFS
* mount for every device, to provide access to the rest of the OS easily.
*/
#include <stdint.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "../drivers.h"
#include "../pci.h"
#include "../../mem/frame.h"
#include "../../proc/proc.h"
#include "../../vfs/vfs.h"
#include "../../io.h"
#include "../../screen.h"
struct partition {
uint8_t drive;
uint8_t shead, ssect, scyl;
uint8_t type;
uint8_t ehead, esect, ecyl;
uint32_t lba;
uint32_t size;
};
size_t ata_read(File *file, char *buf, size_t size, off_t offset);
size_t ata_write(File *file, char *buf, size_t size, off_t offset);
int ata_open(File *file);
FileOps ideFileOps = {
.read = ata_read,
.write = ata_write,
.open = ata_open,
};
Driver ideDriver = {
.major = 2,
.ops = &ideFileOps,
.next = NULL,
};
page_t ataDmaArea;
uint8_t ide_read(uint8_t chan, uint8_t reg);
void ide_write(uint8_t chan, uint8_t reg, uint8_t data);
/* Status */
#define ATA_SR_BSY 0x80 /* Busy */
#define ATA_SR_DRDY 0x40 /* Drive ready */
#define ATA_SR_DF 0x20 /* Drive write fault */
#define ATA_SR_DSC 0x10 /* Drive seek complete */
#define ATA_SR_DRQ 0x08 /* Data request ready */
#define ATA_SR_CORR 0x04 /* Corrected data */
#define ATA_SR_IDX 0x02 /* Index */
#define ATA_SR_ERR 0x01 /* Error */
/* Error */
#define ATA_ER_BBK 0x80 /* Bad block */
#define ATA_ER_UNC 0x40 /* Uncorrectable data */
#define ATA_ER_MC 0x20 /* Media changed */
#define ATA_ER_IDNF 0x10 /* ID mark not found */
#define ATA_ER_MCR 0x08 /* Media change request */
#define ATA_ER_ABRT 0x04 /* Command aborted */
#define ATA_ER_T0NF 0x02 /* Track 0 not found */
#define ATA_ER_AMNF 0x01 /* No address mark */
/* Command */
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO_EXT 0x24
#define ATA_CMD_READ_DMA 0xC8
#define ATA_CMD_READ_DMA_EXT 0x25
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_WRITE_PIO_EXT 0x34
#define ATA_CMD_WRITE_DMA 0xCA
#define ATA_CMD_WRITE_DMA_EXT 0x35
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_CMD_CACHE_FLUSH_EXT 0xEA
#define ATA_CMD_PACKET 0xA0
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_IDENTIFY 0xEC
#define ATAPI_CMD_READ 0xA8
#define ATAPI_CMD_EJECT 0x1B
#define ATA_IDENT_DEVICETYPE 0
#define ATA_IDENT_CYLINDERS 2
#define ATA_IDENT_HEADS 6
#define ATA_IDENT_SECTORS 12
#define ATA_IDENT_SERIAL 20
#define ATA_IDENT_MODEL 54
#define ATA_IDENT_CAPABILITIES 98
#define ATA_IDENT_FIELDVALID 106
#define ATA_IDENT_MAX_LBA 120
#define ATA_IDENT_COMMANDSETS 164
#define ATA_IDENT_MAX_LBA_EXT 200
#define IDE_ATA 0
#define IDE_ATAPI 1
#define ATA_MASTER 0
#define ATA_SLAVE 1
#define ATA_PRIMARY 0
#define ATA_SECONDARY 1
#define ATA_READ 0
#define ATA_WRITE 1
/* Registers */
#define ATA_REG_DATA 0x0
#define ATA_REG_ERROR 0x1
#define ATA_REG_FEATURES 0x1
#define ATA_REG_SECCOUNT0 0x2
#define ATA_REG_LBA0 0x3
#define ATA_REG_LBA1 0x4
#define ATA_REG_LBA2 0x5
#define ATA_REG_HDDEVSEL 0x6
#define ATA_REG_COMMAND 0x7
#define ATA_REG_STATUS 0x7
#define ATA_REG_SECCOUNT1 0x8
#define ATA_REG_LBA3 0x9
#define ATA_REG_LBA4 0xA
#define ATA_REG_LBA5 0xB
#define ATA_REG_CONTROL 0xC
#define ATA_REG_ALTSTATUS 0xC
#define ATA_REG_DEVADDRESS 0xD
struct IDEChanRegs {
uint16_t base; /* IO base */
uint16_t ctrl; /* Control base */
uint16_t bmide; /* Bus Master IDE */
uint8_t noint; /* No interrupt */
} channels[2];
uint8_t ideBuf[2048] = {0};
volatile unsigned static char ideIrqInvoked = 0;
unsigned static char atapiPacket[12] = {0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
struct IDEDevice {
uint8_t reserved;
uint8_t channel;
uint8_t drive;
uint16_t type;
uint16_t signature;
uint16_t capabilities;
uint32_t commandSets;
uint32_t size;
uint8_t model[41];
} ideDevices[4];
/* Read channel */
uint8_t
ide_read(uint8_t chan, uint8_t reg)
{
uint8_t result;
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
if (reg < 0x08)
result = inb(channels[chan].base + reg - 0x00);
else if (reg < 0x0C)
result = inb(channels[chan].base + reg - 0x06);
else if (reg < 0x0E)
result = inb(channels[chan].ctrl + reg - 0x0A);
else if (reg < 0x16)
result = inb(channels[chan].bmide + reg - 0x0E);
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
return result;
}
/* Write channel */
void
ide_write(uint8_t chan, uint8_t reg, uint8_t data)
{
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
if (reg < 0x08)
outb(channels[chan].base + reg - 0x00, data);
else if (reg < 0x0C)
outb(channels[chan].base + reg - 0x06, data);
else if (reg < 0x0E)
outb(channels[chan].ctrl + reg - 0x0A, data);
else if (reg < 0x16)
outb(channels[chan].bmide + reg - 0x0E, data);
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
}
/* Read identification space */
void
ide_read_buffer(uint8_t chan, uint8_t reg, void *buffer, uint32_t quads)
{
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, 0x80 | channels[chan].noint);
asm volatile("pushw %es; movw %ds, %ax; movw %ax, %es");
if (reg < 0x08)
insl(channels[chan].base + reg - 0x00, buffer, quads);
else if (reg < 0x0C)
insl(channels[chan].base + reg - 0x06, buffer, quads);
else if (reg < 0x0E)
insl(channels[chan].ctrl + reg - 0x0A, buffer, quads);
else if (reg < 0x16)
insl(channels[chan].bmide + reg - 0x0E, buffer, quads);
asm volatile("popw %es");
if (reg > 0x07 && reg < 0x0C)
ide_write(chan, ATA_REG_CONTROL, channels[chan].noint);
}
/* Poll for status after command */
uint8_t
ide_polling(uint8_t chan, uint32_t check)
{
uint8_t i, state;
/* Wait for BSY to set */
for (i = 0; i < 4; i++)
ide_read(chan, ATA_REG_ALTSTATUS);
/* Wait for BSY to clear */
while (ide_read(chan, ATA_REG_STATUS) & ATA_SR_BSY);
if (check) {
state = ide_read(chan, ATA_REG_STATUS);
if (state & ATA_SR_ERR)
return 2;
if (state & ATA_SR_DF)
return 1;
if ((state & ATA_SR_DRQ) == 0)
return 3;
}
return 0;
}
/* Print an IDE error */
uint8_t
ide_print_error(uint32_t drive, uint8_t err)
{
if (err == 0)
return err;
uint8_t st;
kprintf("IDE:");
if (err == 1) {
kprintf(" - Device fault");
err = 19;
} else if (err == 2) {
st = ide_read(ideDevices[drive].channel, ATA_REG_ERROR);
if (st & ATA_ER_AMNF) {
kprintf(" - No address mark found");
err = 7;
}
if (st & ATA_ER_T0NF) {
kprintf(" - No media or media error");
err = 3;
}
if (st & ATA_ER_ABRT) {
kprintf(" - Command aborted");
err = 20;
}
if (st & ATA_ER_MCR) {
kprintf(" - No media or media error");
err = 3;
}
if (st & ATA_ER_IDNF) {
kprintf(" - ID mark not found");
err = 21;
}
if (st & ATA_ER_MC) {
kprintf(" - No media or media error");
err = 3;
}
if (st & ATA_ER_UNC) {
kprintf(" - Uncorrectable data error");
err = 22;
}
if (st & ATA_ER_BBK) {
kprintf(" - Bad sectors");
err = 13;
}
} else if (err == 3) {
kprintf(" - Read nothing");
err = 23;
} else if (err == 4) {
kprintf(" - Write protected");
err = 8;
}
kprintf(" - [%s %s] %s",
(char *[]){"Primary","Secondary"}[ideDevices[drive].channel],
(char *[]){"Master","Slave"}[ideDevices[drive].drive],
ideDevices[drive].model);
return err;
}
/* Initialise an IDE drive */
void
ide_drive_init(uint32_t bar0, uint32_t bar1, uint32_t bar2,
uint32_t bar3, uint32_t bar4)
{
channels[ATA_PRIMARY].base = (bar0 & ~3) + 0x1F0 * (!bar0);
channels[ATA_PRIMARY].ctrl = (bar1 & ~3) + 0x3F6 * (!bar1);
channels[ATA_SECONDARY].base = (bar2 & ~3) + 0x170 * (!bar2);
channels[ATA_SECONDARY].ctrl = (bar3 & ~3) + 0x376 * (!bar3);
channels[ATA_PRIMARY].bmide = (bar4 & ~3) + 0;
channels[ATA_SECONDARY].bmide = (bar4 & ~3) + 8;
/* Disable IRQs */
ide_write(ATA_PRIMARY, ATA_REG_CONTROL, 2);
ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);
uint32_t count = 0;
uint8_t i, j, k, err, type, status;
for (i = 0; i < 2; i++)
for (j = 0; j < 2; j++) {
err = 0;
type = IDE_ATA;
ideDevices[count].reserved = 0;
ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4));
sleep(1);
ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
sleep(1);
if (ide_read(i, ATA_REG_STATUS) == 0)
continue;
while (1) {
status = ide_read(i, ATA_REG_STATUS);
if ((status & ATA_SR_ERR)) {
err = 1;
break;
}
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ))
break;
}
if (err) {
uint8_t cl = ide_read(i, ATA_REG_LBA1);
uint8_t ch = ide_read(i, ATA_REG_LBA2);
if (cl == 0x14 && ch == 0xEB)
type = IDE_ATAPI;
else if (cl == 0x69 && ch == 0x96)
type = IDE_ATAPI;
else
continue;
ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
sleep(1);
}
ide_read_buffer(i, ATA_REG_DATA, ideBuf, 128);
uint16_t sig, cap, set;
sig = *((uint16_t *) (ideBuf + ATA_IDENT_DEVICETYPE));
cap = *((uint16_t *) (ideBuf + ATA_IDENT_CAPABILITIES));
set = *((uint16_t *) (ideBuf + ATA_IDENT_COMMANDSETS));
ideDevices[count].reserved = 1;
ideDevices[count].type = type;
ideDevices[count].channel = i;
ideDevices[count].drive = j;
ideDevices[count].signature = sig;
ideDevices[count].capabilities = cap;
ideDevices[count].commandSets = set;
uint32_t size;
if (ideDevices[count].commandSets & (1 << 26))
size = *((uint32_t *) (ideBuf + ATA_IDENT_MAX_LBA_EXT));
else
size = *((uint32_t *) (ideBuf + ATA_IDENT_MAX_LBA));
ideDevices[count].size = size;
for (k = 0; k < 40; k += 2) {
ideDevices[count].model[k] =
ideBuf[ATA_IDENT_MODEL + k + 1];
ideDevices[count].model[k + 1] =
ideBuf[ATA_IDENT_MODEL + k];
}
ideDevices[count].model[40] = 0;
count++;
}
}
/* Wait for an IRQ to arrive */
void
ide_wait_irq(void)
{
while (!ideIrqInvoked);
ideIrqInvoked = 0;
}
/* Access an ATA drive */
uint8_t
ide_ata_access(uint8_t write, uint8_t drive, uint32_t lba, uint8_t sectors,
uint16_t selector, uint32_t edi)
{
uint8_t lbaMode, dma, cmd;
uint8_t lbaIO[6];
uint32_t chan = ideDevices[drive].channel;
uint32_t slave = ideDevices[drive].drive;
uint32_t bus = channels[chan].base;
uint32_t words = 256;
uint16_t cyl, i;
uint8_t head, sect, err;
ide_write(chan, ATA_REG_CONTROL,
channels[chan].noint = (ideIrqInvoked = 0) + 2);
if (lba >= 0x10000000) {
/* LBA48 */
lbaMode = 2;
lbaIO[0] = (lba & 0x000000FF) >> 0;
lbaIO[1] = (lba & 0x0000FF00) >> 8;
lbaIO[2] = (lba & 0x00FF0000) >> 16;
lbaIO[3] = (lba & 0xFF000000) >> 24;
lbaIO[4] = 0;
lbaIO[5] = 0;
head = 0;
} else if (ideDevices[drive].capabilities & 0x200) {
/* LBA28 */
lbaMode = 1;
lbaIO[0] = (lba & 0x000000FF) >> 0;
lbaIO[1] = (lba & 0x0000FF00) >> 8;
lbaIO[2] = (lba & 0x00FF0000) >> 16;
lbaIO[3] = 0;
lbaIO[4] = 0;
lbaIO[5] = 0;
head = (lba & 0x0F000000) >> 24;
} else {
/* CHS */
lbaMode = 0;
sect = (lba % 63) + 1;
cyl = (lba + 1 - sect) / (16 * 63);
lbaIO[0] = sect;
lbaIO[1] = (cyl >> 0) & 0xFF;
lbaIO[2] = (cyl >> 8) & 0xFF;
lbaIO[3] = 0;
lbaIO[4] = 0;
lbaIO[5] = 0;
head = (lba + 1 - sect) % (16 * 63) / 63;
}
dma = 0; /* XXX */
while (ide_read(chan, ATA_REG_STATUS) & ATA_SR_BSY);
if (lbaMode == 0)
ide_write(chan, ATA_REG_HDDEVSEL, 0xA0 | (slave << 4) | head);
else
ide_write(chan, ATA_REG_HDDEVSEL, 0xE0 | (slave << 4) | head);
if (lbaMode == 2) {
ide_write(chan, ATA_REG_SECCOUNT1, 0);
ide_write(chan, ATA_REG_LBA3, lbaIO[3]);
ide_write(chan, ATA_REG_LBA4, lbaIO[4]);
ide_write(chan, ATA_REG_LBA5, lbaIO[5]);
}
ide_write(chan, ATA_REG_SECCOUNT0, sectors);
ide_write(chan, ATA_REG_LBA0, lbaIO[0]);
ide_write(chan, ATA_REG_LBA1, lbaIO[1]);
ide_write(chan, ATA_REG_LBA2, lbaIO[2]);
if (lbaMode == 0 && !dma && !write) cmd = ATA_CMD_READ_PIO;
if (lbaMode == 1 && !dma && !write) cmd = ATA_CMD_READ_PIO;
if (lbaMode == 2 && !dma && !write) cmd = ATA_CMD_READ_PIO_EXT;
if (lbaMode == 0 && dma && !write) cmd = ATA_CMD_READ_DMA;
if (lbaMode == 1 && dma && !write) cmd = ATA_CMD_READ_DMA;
if (lbaMode == 2 && dma && !write) cmd = ATA_CMD_READ_DMA_EXT;
if (lbaMode == 0 && !dma && write) cmd = ATA_CMD_WRITE_PIO;
if (lbaMode == 1 && !dma && write) cmd = ATA_CMD_WRITE_PIO;
if (lbaMode == 2 && !dma && write) cmd = ATA_CMD_WRITE_PIO_EXT;
if (lbaMode == 0 && dma && write) cmd = ATA_CMD_WRITE_DMA;
if (lbaMode == 1 && dma && write) cmd = ATA_CMD_WRITE_DMA;
if (lbaMode == 2 && dma && write) cmd = ATA_CMD_WRITE_DMA_EXT;
ide_write(chan, ATA_REG_COMMAND, cmd);
if (dma) {
if (write) {
/* TODO */
} else {
/* TODO */
}
} else {
if (write) {
for (i = 0; i < sectors; i++) {
ide_polling(chan, 0);
outsw(bus, (void *) edi, words);
}
ide_write(chan, ATA_REG_COMMAND, (uint8_t []) {
ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH_EXT,
}[lbaMode]);
ide_polling(chan, 0);
} else {
for (i = 0; i < sectors; i++) {
if (err = ide_polling(chan, 1))
return err;
insw(bus, (void *) edi, words);
edi += words * 2;
}
}
}
return 0;
}
/* IDE IRQ handler */
void
ide_irq(InterruptFrame *frame)
{
ideIrqInvoked = 1;
}
/* Initialise an IDE drive */
void
ide_init(uint8_t bus, uint8_t slot, uint8_t func)
{
register_driver(&ideDriver);
uint8_t class, subclass, progif, header;
class = pci_read_byte(bus, slot, func, 11);
subclass = pci_read_byte(bus, slot, func, 10);
progif = pci_read_byte(bus, slot, func, 9);
header = pci_read_byte(bus, slot, func, 14);
uint8_t primaryNative = 0, primaryChange = 0,
secondaryNative = 0, secondaryChange = 0,
busMaster = 0;
if (progif & (1 << 0))
primaryNative = 1;
if (progif & (1 << 1))
primaryChange = 1;
if (progif & (1 << 2))
secondaryNative = 1;
if (progif & (1 << 3))
secondaryChange = 1;
if (progif & (1 << 7))
busMaster = 1;
uint32_t bar0 = 0x1F0, bar1 = 0x3F6,
bar2 = 0x170, bar3 = 0x376,
bar4 = 0x00;
if (primaryNative) {
bar0 = pci_read_dword(bus, slot, func, 0x10);
bar1 = pci_read_dword(bus, slot, func, 0x14);
}
if (secondaryNative) {
bar2 = pci_read_dword(bus, slot, func, 0x18);
bar3 = pci_read_dword(bus, slot, func, 0x1C);
}
if (busMaster)
bar4 = pci_read_dword(bus, slot, func, 0x20);
register_interrupt(14, ide_irq);
ataDmaArea = alloc_frames(1);
ide_drive_init(0x1F0, 0x3F6, 0x170, 0x376, 0x00);
uint8_t i, j;
char name[16];
for (i = 0; i < 4; i++) {
if (!ideDevices[i].reserved)
continue;
sprintf(name, "/dev/%cd%c",
(char []){'h','c'}[ideDevices[i].type],
(char []){'a','b'}[ideDevices[i].channel]);
mknod(name, S_IFBLK | 0600, MKDEV(ideDriver.major, i * 16));
kprintf(" %s drive [%s] %d MB - %s",
(char *[]){"ATA", "ATAPI"}[ideDevices[i].type],
name, ideDevices[i].size / 1024 / 2,
ideDevices[i].model);
/* Attempt to read partition table */
if (ideDevices[i].type != 0)
continue;
ide_ata_access(0, i, 0, 0x02, 1, ataDmaArea);
struct partition *part;
for (j = 0; j < 4; j++) {
part = (void *) ataDmaArea + 446 + (j * 16);
if (!part->type)
continue;
sprintf(name, "/dev/%cd%c%d",
(char []){'h','c'}[ideDevices[i].type],
(char []){'a','b'}[ideDevices[i].channel],
j + 1);
int asdf = mknod(name, S_IFBLK | 0600,
MKDEV(ideDriver.major, (i * 16) + (j + 1)));
kprintf(" Partition %d: %#.8x - %#.8x",
j+1, part->lba*512,
(part->lba + part->size) * 512);
}
}
}
/* Read a drive */
size_t
ata_read(File *file, char *buf, size_t size, off_t offset)
{
uint16_t min, bufOffset = offset % 512;
size_t count = 0;
uint16_t dev = MINOR(file->inode->dev) / 16,
parti = MINOR(file->inode->dev) % 16;
uint32_t lba;
struct partition *part;
if (parti) {
ide_ata_access(0, dev, 0, 1, 0x02, ataDmaArea);
part = (void *) ataDmaArea + 446 + ((parti - 1) * 16);
if (offset > (part->lba + part->size) * 512)
return 0;
if (offset + size > (part->lba + part->size) * 512)
size = ((part->lba + part->size) * 512) - offset;
} else {
if (offset > ideDevices[dev].size * 512)
return 0;
if (offset + size > ideDevices[dev].size * 512)
size = (ideDevices[dev].size * 512) - offset;
}
while (size + bufOffset) {
min = (size > 0x1000) ? 0x1000 : size;
lba = (parti ? part->lba : 0) + ((offset + count) / 512);
ide_ata_access(0, dev, lba, 8, 0x02, ataDmaArea);
memcpy((void *) (buf + count),
(void *) ataDmaArea + bufOffset,
min);
size -= min;
count += min;
bufOffset = 0;
}
return count;
}
/* Write a drive */
size_t
ata_write(File *file, char *buf, size_t size, off_t offset)
{
uint16_t min;
size_t count = 0;
uint16_t dev = MINOR(file->inode->dev) / 16,
parti = MINOR(file->inode->dev) % 16;
uint32_t lba;
struct partition *part;
if (parti) {
ide_ata_access(0, dev, 0, 1, 0x02, ataDmaArea);
part = (void *) ataDmaArea + 446 + ((parti - 1) * 16);
if (offset > (part->lba + part->size) * 512)
return 0;
if (offset + size > (part->lba + part->size) * 512)
size = ((part->lba + part->size) * 512) - offset;
} else {
if (offset > ideDevices[dev].size * 512)
return 0;
if (offset + size > ideDevices[dev].size * 512)
size = (ideDevices[dev].size * 512) - offset;
}
while (size) {
min = (size > 0x1000) ? 0x1000 : size;
memcpy((void *) ataDmaArea,
// (void *) (((uint32_t) (buf + count) / 512) * 512),
(void *) (buf + count),
min);
lba = (parti ? part->lba : 0) + ((offset + count) / 512);
ide_ata_access(1, dev, lba, 8, 0x02, ataDmaArea);
size -= min;
count += min;
}
return count;
/*
* TODO: Currently, offset is sector-aligned, which we don't always
* want. This should be fixed by basically checking the offset
* for this and reading into the dma buffer before we write to the
* adjusted offset in the buffer, this way the surrounding data
* isn't deleted by accident with bad alignment.
*/
}
/* Open a drive */
int
ata_open(File *file)
{
if (ideDevices[MINOR(file->inode->dev)/16].reserved)
return 0;
if (ideDevices[MINOR(file->inode->dev)/16].type != 0)
return -EINVAL;
return -ENODEV;
}