398 lines
12 KiB
C
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);
|
|
} |