metal-cos/sys/dev/x86/ide.c

489 lines
11 KiB
C

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/kassert.h>
#include <sys/kmem.h>
#include <sys/spinlock.h>
#include <sys/disk.h>
#include "ioport.h"
#include "../ata.h"
/*
* IDE Definitions
*/
#define IDE_PRIMARY_BASE 0x1F0
#define IDE_PRIMARY_DEVCTL 0x3F6
#define IDE_PRIMARY_IRQ 14
#define IDE_SECONDARY_BASE 0x170
#define IDE_SECONDARY_DEVCTL 0x376
#define IDE_SECONDARY_IRQ 15
// Port Offsets
#define IDE_DATAPORT 0
#define IDE_FEATURES 1
#define IDE_SECTORCOUNT 2
#define IDE_LBALOW 3
#define IDE_LBAMID 4
#define IDE_LBAHIGH 5
#define IDE_DRIVE 6
#define IDE_COMMAND 7 /* Write */
#define IDE_STATUS 7 /* Read */
// IDE Commands (PIO)
#define IDE_CMD_READ 0x20
#define IDE_CMD_READ_EXT 0x24
#define IDE_CMD_WRITE 0x30
#define IDE_CMD_WRITE_EXT 0x34
#define IDE_CMD_FLUSH 0xE7
#define IDE_CMD_IDENTIFY 0xEC
// Status
#define IDE_STATUS_ERR 0x01 /* Error */
#define IDE_STATUS_DRQ 0x08 /* Data Ready */
#define IDE_STATUS_SRV 0x10 /* Overlapped-mode Service Request */
#define IDE_STATUS_DF 0x20 /* Drive Fault Error */
#define IDE_STATUS_RDY 0x40 /* Ready */
#define IDE_STATUS_BSY 0x80 /* Busy */
#define IDE_CONTROL_SRST 0x04 /* Software Reset */
#define IDE_SECTOR_SIZE 512
typedef struct IDE
{
uint16_t base; // Base Port
uint16_t devctl; // Device Control
uint8_t lastDriveCode; // Last Drive Code
Spinlock lock;
} IDE;
typedef struct IDEDrive
{
IDE *ide; // IDE Controller
int drive; // Drive Number
bool lba48; // Supports 48-bit LBA
uint64_t size; // Size of Disk
} IDEDrive;
bool IDE_HasController(IDE *ide);
void IDE_Reset(IDE *ide);
void IDE_Identify(IDE *ide, int drive);
int IDE_Read(Disk *disk, void *buf, SGArray *sga, DiskCB, void *arg);
int IDE_Write(Disk *disk, void *buf, SGArray *sga, DiskCB, void *arg);
int IDE_Flush(Disk *disk, void *buf, SGArray *sga, DiskCB, void *arg);
int IDE_ReadOne(IDEDrive *drive, void *buf, uint64_t off, uint64_t len);
int IDE_WriteOne(IDEDrive *drive, void *buf, uint64_t off, uint64_t len);
IDE primary;
IDEDrive primaryDrives[2];
void
IDE_Init()
{
ASSERT(sizeof(ATAIdentifyDevice) == 512);
primary.base = IDE_PRIMARY_BASE;
primary.devctl = IDE_PRIMARY_DEVCTL;
Spinlock_Init(&primary.lock, "IDE Primary Controller Lock",
SPINLOCK_TYPE_NORMAL);
if (!IDE_HasController(&primary)) {
kprintf("IDE: No controller detected\n");
return;
}
Spinlock_Lock(&primary.lock);
IDE_Reset(&primary);
IDE_Identify(&primary, 0);
IDE_Identify(&primary, 1);
Spinlock_Unlock(&primary.lock);
}
int
IDEWaitForBusy(IDE *ide, bool wait)
{
uint8_t status;
ASSERT(Spinlock_IsHeld(&ide->lock));
if (wait) {
inb(ide->base + IDE_STATUS);
inb(ide->base + IDE_STATUS);
inb(ide->base + IDE_STATUS);
inb(ide->base + IDE_STATUS);
}
while (1) {
status = inb(ide->base + IDE_STATUS);
if ((status & IDE_STATUS_BSY) == 0)
return status;
}
return 0xFF;
}
bool
IDE_HasController(IDE *ide)
{
outb(ide->base + IDE_LBALOW, 0x41);
outb(ide->base + IDE_LBAMID, 0x4D);
if (inb(ide->base + IDE_LBALOW) != 0x41)
return false;
if (inb(ide->base + IDE_LBAMID) != 0x4D)
return false;
return true;
}
void
IDE_Reset(IDE *ide)
{
uint8_t status;
outb(ide->devctl, IDE_CONTROL_SRST);
outb(ide->devctl, 0);
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_RDY) != IDE_STATUS_RDY) {
Log(ide, "Controller not ready!\n");
return;
}
}
void
IDE_SwapAndTruncateString(char *str, int len)
{
int i;
ASSERT(len % 2 == 0);
for (i = 0; i < len/2; i++)
{
char tmp = str[2*i];
str[2*i] = str[2*i+1];
str[2*i+1] = tmp;
}
for (i = len - 1; i > 0; i--) {
if (str[i] == ' ' || str[i] == '\0')
str[i] = '\0';
else
break;
}
}
void
IDE_Identify(IDE *ide, int drive)
{
uint8_t driveCode;
uint8_t status;
ATAIdentifyDevice ident;
ASSERT(drive == 0 || drive == 1);
if (drive == 0)
driveCode = 0xA0;
else
driveCode = 0xB0;
outb(ide->base + IDE_DRIVE, driveCode);
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_ERR) != 0) {
Log(ide, "Error selecting drive %d\n", drive);
return;
}
ide->lastDriveCode = driveCode;
outb(ide->base + IDE_SECTORCOUNT, 0x00);
outb(ide->base + IDE_LBALOW, 0x00);
outb(ide->base + IDE_LBAMID, 0x00);
outb(ide->base + IDE_LBAHIGH, 0x00);
outb(ide->base + IDE_COMMAND, IDE_CMD_IDENTIFY);
status = inb(ide->base + IDE_STATUS);
if (status == 0) {
Log(ide, "Drive %d not present\n", drive);
return;
}
// XXX: Need timeout
while (1) {
if ((status & IDE_STATUS_BSY) == 0)
break;
status = inb(ide->base + IDE_STATUS);
}
if ((status & IDE_STATUS_ERR) != 0) {
Log(ide, "Error trying to identify drive %d\n", drive);
return;
}
insw(ide->base, (void *)&ident, 256);
// Cleanup model and serial for printing
char model[41];
char serial[21];
memcpy(&model[0], &ident.model[0], 40);
model[40] = '\0';
IDE_SwapAndTruncateString(&model[0], 40);
memcpy(&serial[0], &ident.serial[0], 20);
serial[20] = '\0';
IDE_SwapAndTruncateString(&serial[0], 20);
Log(ide, "Drive %d Model: %s Serial: %s\n", drive, model, serial);
Log(ide, "Drive %d %llu Sectors (%llu MBs)\n",
drive, ident.lbaSectors, ident.lbaSectors / 2048ULL);
primaryDrives[drive].ide = &primary;
primaryDrives[drive].drive = drive;
primaryDrives[drive].lba48 = (ident.lbaSectors > (1 << 24));
primaryDrives[drive].size = ident.lbaSectors;
// Register Disk
Disk *disk = PAlloc_AllocPage();
if (!disk) {
Panic("IDE: No memory!\n");
}
disk->handle = &primaryDrives[drive];
disk->ctrlNo = 0;
disk->diskNo = drive;
disk->sectorSize = IDE_SECTOR_SIZE;
disk->sectorCount = ident.lbaSectors;
disk->diskSize = IDE_SECTOR_SIZE * ident.lbaSectors;
disk->read = IDE_Read;
disk->write = IDE_Write;
disk->flush = IDE_Flush;
Disk_AddDisk(disk);
}
int
IDE_Read(Disk *disk, void *buf, SGArray *sga, DiskCB cb, void *arg)
{
int i;
int status;
IDEDrive *idedrive;
idedrive = disk->handle;
for (i = 0; i < sga->len; i++) {
status = IDE_ReadOne(idedrive,
buf,
sga->entries[i].offset / 512,
sga->entries[i].length / 512);
buf += sga->entries[i].length;
if (status < 0)
return status;
}
return 0;
}
int
IDE_Write(Disk *disk, void *buf, SGArray *sga, DiskCB cb, void *arg)
{
int i;
int status;
IDEDrive *idedrive;
idedrive = disk->handle;
for (i = 0; i < sga->len; i++) {
status = IDE_WriteOne(idedrive,
buf,
sga->entries[i].offset / 512,
sga->entries[i].length / 512);
buf += sga->entries[i].length;
if (status < 0)
return status;
}
return 0;
}
int
IDE_Flush(Disk *disk, void *buf, SGArray *sga, DiskCB cb, void *arg)
{
uint8_t driveCode;
IDE *ide;
IDEDrive *idedrive;
idedrive = disk->handle;
ide = idedrive->ide;
if (idedrive->drive == 0)
driveCode = 0xA0;
else
driveCode = 0xB0;
Spinlock_Lock(&ide->lock);
outb(ide->base + IDE_DRIVE, driveCode);
outb(ide->base + IDE_COMMAND, IDE_CMD_FLUSH);
IDEWaitForBusy(ide, false);
Spinlock_Unlock(&ide->lock);
return 0;
}
int
IDE_ReadOne(IDEDrive *drive, void *buf, uint64_t off, uint64_t len)
{
bool lba48 = false;
uint8_t driveCode;
uint8_t status;
IDE *ide = drive->ide;
DLOG(ide, "read %llx %llx\n", off, len);
ASSERT(drive->drive == 0 || drive->drive == 1);
if (drive->drive == 0)
driveCode = lba48 ? 0x40 : 0xE0;
else
driveCode = lba48 ? 0x50 : 0xF0;
ASSERT(len < 0x10000);
Spinlock_Lock(&ide->lock);
if (driveCode != ide->lastDriveCode) {
outb(ide->base + IDE_DRIVE, driveCode);
// Need to wait for select to complete
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error selecting drive %d\n", drive->drive);
return -1;
}
ide->lastDriveCode = driveCode;
}
if (lba48) {
outb(ide->base + IDE_SECTORCOUNT, len >> 8);
outb(ide->base + IDE_LBALOW, off >> 24);
outb(ide->base + IDE_LBAMID, off >> 32);
outb(ide->base + IDE_LBAHIGH, off >> 40);
}
outb(ide->base + IDE_SECTORCOUNT, len);
outb(ide->base + IDE_LBALOW, off & 0xff);
outb(ide->base + IDE_LBAMID, (off >> 8) & 0xff);
outb(ide->base + IDE_LBAHIGH, (off >> 16) & 0xff);
if (lba48)
outb(ide->base + IDE_COMMAND, IDE_CMD_READ_EXT);
else
outb(ide->base + IDE_COMMAND, IDE_CMD_READ);
status = IDEWaitForBusy(ide, false);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error trying read from drive %d\n", drive->drive);
return -1;
}
int sectors;
for (sectors = 0; sectors < len; sectors++)
{
uint8_t *b = buf + sectors * IDE_SECTOR_SIZE;
insw(ide->base + IDE_DATAPORT, b, 256);
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error reading from drive %d\n", drive->drive);
return -1;
}
}
Spinlock_Unlock(&ide->lock);
return 0;
}
int
IDE_WriteOne(IDEDrive *drive, void *buf, uint64_t off, uint64_t len)
{
bool lba48 = false;
uint8_t driveCode;
uint8_t status;
IDE *ide = drive->ide;
DLOG(ide, "read %llx %llx\n", off, len);
ASSERT(drive->drive == 0 || drive->drive == 1);
if (drive->drive == 0)
driveCode = lba48 ? 0x40 : 0xE0;
else
driveCode = lba48 ? 0x50 : 0xF0;
ASSERT(len < 0x10000);
Spinlock_Lock(&ide->lock);
if (driveCode != ide->lastDriveCode) {
outb(ide->base + IDE_DRIVE, driveCode);
// Need to wait for select to complete
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error selecting drive %d\n", drive->drive);
return -1;
}
ide->lastDriveCode = driveCode;
}
if (lba48) {
outb(ide->base + IDE_SECTORCOUNT, len >> 8);
outb(ide->base + IDE_LBALOW, off >> 24);
outb(ide->base + IDE_LBAMID, off >> 32);
outb(ide->base + IDE_LBAHIGH, off >> 40);
}
outb(ide->base + IDE_SECTORCOUNT, len);
outb(ide->base + IDE_LBALOW, off & 0xff);
outb(ide->base + IDE_LBAMID, (off >> 8) & 0xff);
outb(ide->base + IDE_LBAHIGH, (off >> 16) & 0xff);
if (lba48)
outb(ide->base + IDE_COMMAND, IDE_CMD_WRITE_EXT);
else
outb(ide->base + IDE_COMMAND, IDE_CMD_WRITE);
status = IDEWaitForBusy(ide, false);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error trying read from drive %d\n", drive->drive);
return -1;
}
int sectors;
for (sectors = 0; sectors < len; sectors++)
{
uint8_t *b = buf + sectors * IDE_SECTOR_SIZE;
outsw(ide->base + IDE_DATAPORT, b, 256);
status = IDEWaitForBusy(ide, true);
if ((status & IDE_STATUS_ERR) != 0) {
Spinlock_Unlock(&ide->lock);
Log(ide, "Error reading from drive %d\n", drive->drive);
return -1;
}
}
Spinlock_Unlock(&ide->lock);
// XXX: Flush cache ...
return 0;
}