quinn-os/ahci.c
2022-06-29 21:47:22 +10:00

398 lines
12 KiB
C

#include "pvec.h"
#include "console.h"
#include "ahci.h"
#include "pci.h"
#include "memory.h"
#include "pata.h"
#include "string.h"
#include "hd.h"
#define PAGE_SIZE 4096
#define SATA_SIG_ATA 0x00000101 // SATA drive
#define SATA_SIG_ATAPI 0xEB140101 // SATAPI drive
#define SATA_SIG_SEMB 0xC33C0101 // Enclosure management bridge
#define SATA_SIG_PM 0x96690101 // Port multiplier
#define AHCI_DEV_NULL 0
#define AHCI_DEV_SATA 1
#define AHCI_DEV_SEMB 2
#define AHCI_DEV_PM 3
#define AHCI_DEV_SATAPI 4
#define HBA_PORT_IPM_ACTIVE 1
#define HBA_PORT_DET_PRESENT 3
#define HBA_PxCMD_ST 0x0001
#define HBA_PxCMD_FRE 0x0010
#define HBA_PxCMD_FR 0x4000
#define HBA_PxCMD_CR 0x8000
#define HBA_PxIS_TFES (1 << 30)
struct ahci_device_t **ahci_devices;
int ahci_device_count = 0;
#define ATA_DEV_BUSY 0x80
#define ATA_DEV_DRQ 0x08
// Start command engine
void ahci_start_cmd(HBA_PORT *port) {
// Wait until CR (bit15) is cleared
while (port->cmd & HBA_PxCMD_CR)
;
// Set FRE (bit4) and ST (bit0)
port->cmd |= HBA_PxCMD_FRE;
port->cmd |= HBA_PxCMD_ST;
}
// Stop command engine
void ahci_stop_cmd(HBA_PORT *port) {
// Clear ST (bit0)
port->cmd &= ~HBA_PxCMD_ST;
// Clear FRE (bit4)
port->cmd &= ~HBA_PxCMD_FRE;
// Wait until FR (bit14), CR (bit15) are cleared
while (1) {
if (port->cmd & HBA_PxCMD_FR)
continue;
if (port->cmd & HBA_PxCMD_CR)
continue;
break;
}
}
// Find a free command list slot
int ahci_find_cmdslot(struct ahci_device_t *dev, int pt) {
int cmdslots = (dev->abar->cap & 0x0f00) >> 8;
HBA_PORT *port = &dev->abar->ports[pt];
// If not set in SACT and CI, the slot is free
unsigned long slots = (port->sact | port->ci);
for (int i = 0; i < cmdslots; i++) {
if ((slots & 1) == 0)
return i;
slots >>= 1;
}
kprintf("Cannot find free command list entry\n");
return -1;
}
int ahci_write_sectors(struct ahci_device_t *dev, int pt, unsigned long startl, unsigned long starth, unsigned long count, unsigned char *buf) {
HBA_PORT *port = &dev->abar->ports[pt];
memcpy((char *)dev->devices[pt]->prdt_base, (char *)buf, 512);
port->is = (unsigned long)-1; // Clear pending interrupt bits
int spin = 0; // Spin lock timeout counter
int slot = ahci_find_cmdslot(dev, pt);
if (slot == -1) {
return 0;
}
HBA_CMD_HEADER *cmdheader = (HBA_CMD_HEADER *)(dev->devices[pt]->base + (port->clb - dev->devices[pt]->base_phys));
cmdheader += slot;
cmdheader->cfl = sizeof(FIS_REG_H2D) / sizeof(unsigned long); // Command FIS size
cmdheader->w = 1; // Read from device
cmdheader->prdtl = 1; // PRDT entries count
HBA_CMD_TBL *cmdtbl = (HBA_CMD_TBL *)(dev->devices[pt]->base + (cmdheader[dev->devices[pt]->port].ctba - dev->devices[pt]->base_phys));
memset(cmdtbl, 0, sizeof(HBA_CMD_TBL) + (cmdheader->prdtl - 1) * sizeof(HBA_PRDT_ENTRY));
cmdtbl->prdt_entry[0].dba = (unsigned long)dev->devices[pt]->prdt_base_phys;
cmdtbl->prdt_entry[0].dbc = 511; // 8K bytes (this value should always be set to 1 less than the actual value)
cmdtbl->prdt_entry[0].i = 0;
// Setup command
FIS_REG_H2D *cmdfis = (FIS_REG_H2D *)(&cmdtbl->cfis);
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1; // Command
cmdfis->command = ATA_CMD_WRITE_DMA_EXT;
cmdfis->lba0 = (unsigned char)startl;
cmdfis->lba1 = (unsigned char)(startl >> 8);
cmdfis->lba2 = (unsigned char)(startl >> 16);
cmdfis->device = 1 << 6; // LBA mode
cmdfis->lba3 = (unsigned char)(startl >> 24);
cmdfis->lba4 = (unsigned char)starth;
cmdfis->lba5 = (unsigned char)(starth >> 8);
cmdfis->countl = count & 0xFF;
cmdfis->counth = (count >> 8) & 0xFF;
// The below loop waits until the port is no longer busy before issuing a new command
while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) {
spin++;
}
if (spin == 1000000) {
kprintf("Port is hung\n");
return 0;
}
port->ci = 1 << slot; // Issue command
// Wait for completion
while (1) {
// In some longer duration reads, it may be helpful to spin on the DPS bit
// in the PxIS port field as well (1 << 5)
if ((port->ci & (1 << slot)) == 0)
break;
if (port->is & HBA_PxIS_TFES) // Task file error
{
kprintf("Write disk error 1\n");
return 0;
}
}
// Check again
if (port->is & HBA_PxIS_TFES) {
kprintf("Write disk error 2\n");
return 0;
}
return 1;
}
int ahci_read_sectors(struct ahci_device_t *dev, int pt, unsigned long startl, unsigned long starth, unsigned long count, unsigned char *buf) {
HBA_PORT *port = &dev->abar->ports[pt];
port->is = (unsigned long)-1; // Clear pending interrupt bits
int spin = 0; // Spin lock timeout counter
int slot = ahci_find_cmdslot(dev, pt);
if (slot == -1) {
return 0;
}
HBA_CMD_HEADER *cmdheader = (HBA_CMD_HEADER *)(dev->devices[pt]->base + (port->clb - dev->devices[pt]->base_phys));
cmdheader += slot;
cmdheader->cfl = sizeof(FIS_REG_H2D) / sizeof(unsigned long); // Command FIS size
cmdheader->w = 0; // Read from device
cmdheader->prdtl = 1; // PRDT entries count
HBA_CMD_TBL *cmdtbl = (HBA_CMD_TBL *)(dev->devices[pt]->base + (cmdheader[dev->devices[pt]->port].ctba - dev->devices[pt]->base_phys));
memset(cmdtbl, 0, sizeof(HBA_CMD_TBL) + (cmdheader->prdtl - 1) * sizeof(HBA_PRDT_ENTRY));
cmdtbl->prdt_entry[0].dba = (unsigned long)dev->devices[pt]->prdt_base_phys;
cmdtbl->prdt_entry[0].dbc = 511; // 8K bytes (this value should always be set to 1 less than the actual value)
cmdtbl->prdt_entry[0].i = 0;
// Setup command
FIS_REG_H2D *cmdfis = (FIS_REG_H2D *)(&cmdtbl->cfis);
cmdfis->fis_type = FIS_TYPE_REG_H2D;
cmdfis->c = 1; // Command
cmdfis->command = ATA_CMD_READ_DMA_EXT;
cmdfis->lba0 = (unsigned char)startl;
cmdfis->lba1 = (unsigned char)(startl >> 8);
cmdfis->lba2 = (unsigned char)(startl >> 16);
cmdfis->device = 1 << 6; // LBA mode
cmdfis->lba3 = (unsigned char)(startl >> 24);
cmdfis->lba4 = (unsigned char)starth;
cmdfis->lba5 = (unsigned char)(starth >> 8);
cmdfis->countl = count & 0xFF;
cmdfis->counth = (count >> 8) & 0xFF;
// The below loop waits until the port is no longer busy before issuing a new command
while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) {
spin++;
}
if (spin == 1000000) {
kprintf("Port is hung\n");
return 0;
}
port->ci = 1 << slot; // Issue command
// Wait for completion
while (1) {
// In some longer duration reads, it may be helpful to spin on the DPS bit
// in the PxIS port field as well (1 << 5)
if ((port->ci & (1 << slot)) == 0)
break;
if (port->is & HBA_PxIS_TFES) // Task file error
{
kprintf("Read disk error 1\n");
return 0;
}
}
// Check again
if (port->is & HBA_PxIS_TFES) {
kprintf("Read disk error 2\n");
return 0;
}
memcpy((char *)buf, (char *)dev->devices[pt]->prdt_base, 512);
return 1;
}
void ahci_port_rebase(struct ahci_slot_t *dev, HBA_PORT *port, int portno) {
ahci_stop_cmd(port); // Stop command engine
// Command list offset: 1K*portno
// Command list entry size = 32
// Command list entry maxim count = 32
// Command list maxim size = 32*32 = 1K per port
port->clb = dev->base_phys + (portno << 10);
port->clbu = 0;
memset((void *)(dev->base + (port->clb - dev->base_phys)), 0, 1024);
// FIS offset: 32K+256*portno
// FIS entry size = 256 bytes per port
port->fb = dev->base_phys + (32 << 10) + (portno << 8);
port->fbu = 0;
memset((void *)(dev->base + (port->fb - dev->base_phys)), 0, 256);
// Command table offset: 40K + 8K*portno
// Command table size = 256*32 = 8K per port
HBA_CMD_HEADER *cmdheader = (HBA_CMD_HEADER *)(dev->base + (port->clb - dev->base_phys));
for (int i = 0; i < 32; i++) {
cmdheader[i].prdtl = 1; // 8 prdt entries per command table
// 256 bytes per command table, 64+16+48+16*8
// Command table offset: 40K + 8K*portno + cmdheader_index*256
cmdheader[i].ctba = dev->base_phys + (40 << 10) + (portno << 13) + (i << 8);
cmdheader[i].ctbau = 0;
memset((void *)(dev->base + (cmdheader[i].ctba - dev->base_phys)), 0, 256);
}
ahci_start_cmd(port); // Start command engine
}
// Check device type
static int ahci_check_type(HBA_PORT *port) {
unsigned long ssts = port->ssts;
unsigned char ipm = (ssts >> 8) & 0x0F;
unsigned char det = ssts & 0x0F;
if (det != HBA_PORT_DET_PRESENT) // Check drive status
return AHCI_DEV_NULL;
if (ipm != HBA_PORT_IPM_ACTIVE)
return AHCI_DEV_NULL;
switch (port->sig) {
case SATA_SIG_ATAPI:
return AHCI_DEV_SATAPI;
case SATA_SIG_SEMB:
return AHCI_DEV_SEMB;
case SATA_SIG_PM:
return AHCI_DEV_PM;
default:
return AHCI_DEV_SATA;
}
}
void ahci_irq_isr(struct regs *r) {
for (int i = 0; i < ahci_device_count; i++) {
if (ahci_devices[i]->pci_dev->irq == r->int_no - 32) {
if (ahci_devices[i]->abar->is) {
for (int j = 0; j < ahci_devices[i]->count; j++) {
if (ahci_devices[i]->abar->ports[ahci_devices[i]->devices[j]->port].is) {
// check errors...
// kprintf("IRQ Set on port %p serr %p\n", ahci_devices[i]->abar->ports[ahci_devices[i]->devices[j]->port].is,
// ahci_devices[i]->abar->ports[ahci_devices[i]->devices[j]->port].serr);
ahci_devices[i]->abar->ports[ahci_devices[i]->devices[j]->port].is = -1;
}
}
ahci_devices[i]->abar->is = -1;
}
}
}
}
void ahci_probe_port(struct ahci_device_t *dev) {
// Search disk in implemented ports
unsigned long pi = dev->abar->pi;
int i = 0;
while (i < 32) {
if (pi & 1) {
int dt = ahci_check_type(&dev->abar->ports[i]);
if (dt == AHCI_DEV_SATA) {
kprintf("SATA drive found at port %d\n", i);
if (dev->count == 0) {
dev->devices = (struct ahci_slot_t **)malloc(sizeof(struct ahci_slot_t *));
} else {
dev->devices = (struct ahci_slot_t **)realloc(dev->devices, sizeof(struct ahci_slot_t *) * (dev->count + 1));
}
struct ahci_slot_t *new_dev = (struct ahci_slot_t *)malloc(sizeof(struct ahci_slot_t));
dev->devices[dev->count++] = new_dev;
new_dev->base_phys = (unsigned long)mem_alloc_pages(32);
new_dev->base = mem_pci_sbrk(32 * PAGE_SIZE);
new_dev->port = i;
new_dev->prdt_base_phys = (unsigned long)mem_alloc_pages(1);
new_dev->prdt_base = mem_pci_sbrk(1 * PAGE_SIZE);
for (int j = 0; j < 32; j++) {
mem_map_page(new_dev->base_phys + (j * PAGE_SIZE), new_dev->base + (j * PAGE_SIZE), 3);
}
for (int j = 0; j < 1; j++) {
mem_map_page(new_dev->prdt_base_phys + (j * PAGE_SIZE), new_dev->prdt_base + (j * PAGE_SIZE), 3);
}
ahci_port_rebase(new_dev, &dev->abar->ports[i], i);
} else if (dt == AHCI_DEV_SATAPI) {
// kprintf("SATAPI drive found at port %d\n", i);
} else if (dt == AHCI_DEV_SEMB) {
// kprintf("SEMB drive found at port %d\n", i);
} else if (dt == AHCI_DEV_PM) {
// kprintf("PM drive found at port %d\n", i);
} else {
// kprintf("No drive found at port %d\n", i);
}
}
pi >>= 1;
i++;
}
}
void init_ahci() {
struct pci_device *pci_dev;
int devcount = 0;
unsigned long virt;
unsigned long phys;
while (pci_find_device(0x01, 0x06, &pci_dev, devcount)) {
devcount++;
pci_set_master(pci_dev, 1);
pci_set_mem_enable(pci_dev, 1);
irq_install_handler(pci_dev->irq, ahci_irq_isr, 1);
phys = pci_dev->base[5];
virt = mem_pci_sbrk(pci_dev->size[5]);
for (int i = 0; i < pci_dev->size[5]; i += PAGE_SIZE) {
mem_map_page(phys + (i * PAGE_SIZE), virt + (i * PAGE_SIZE), 3);
}
if (ahci_device_count == 0) {
ahci_devices = (struct ahci_device_t **)malloc(sizeof(struct ahci_device_t *));
} else {
ahci_devices = (struct ahci_device_t **)realloc(ahci_devices, sizeof(struct ahci_device_t *) * (ahci_device_count + 1));
}
struct ahci_device_t *new_dev = (struct ahci_device_t *)malloc(sizeof(struct ahci_device_t));
ahci_devices[ahci_device_count++] = new_dev;
new_dev->pci_dev = pci_dev;
new_dev->abar = (volatile HBA_MEM *)virt;
new_dev->count = 0;
ahci_probe_port(new_dev);
if (new_dev->count > 0) {
init_ahci_hd(new_dev);
}
}
kprintf("Found %d AHCI Controllers\n", devcount);
}