summaryrefslogtreecommitdiff
path: root/sys/dev/ic/ahci.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ic/ahci.c')
-rw-r--r--sys/dev/ic/ahci.c284
1 files changed, 212 insertions, 72 deletions
diff --git a/sys/dev/ic/ahci.c b/sys/dev/ic/ahci.c
index 8b72cde..5dbf4a7 100644
--- a/sys/dev/ic/ahci.c
+++ b/sys/dev/ic/ahci.c
@@ -41,10 +41,12 @@
#include <dev/timer.h>
#include <dev/ic/ahcivar.h>
#include <dev/ic/ahciregs.h>
+#include <dev/dcdr/cache.h>
#include <fs/devfs.h>
#include <fs/ctlfs.h>
#include <vm/dynalloc.h>
#include <vm/physmem.h>
+#include <machine/cdefs.h>
#include <string.h>
#define pr_trace(fmt, ...) kprintf("ahci: " fmt, ##__VA_ARGS__)
@@ -55,6 +57,8 @@ static struct bdevsw ahci_bdevsw;
static struct hba_device *devs;
static struct pci_device *ahci_dev;
static struct timer tmr;
+static struct ahci_hba g_hba;
+static struct driver_var __driver_var;
/*
* Poll register to have 'bits' set/unset.
@@ -94,6 +98,18 @@ ahci_poll_reg(volatile uint32_t *reg, uint32_t bits, bool pollset)
return 0;
}
+static struct hba_device *
+ahci_get_dev(dev_t dev)
+{
+ for (int i = 0; i < devs_max; ++i) {
+ if (devs[i].dev == dev) {
+ return &devs[i];
+ }
+ }
+
+ return NULL;
+}
+
/*
* Allocate a command slot for a port on
* the HBA.
@@ -103,7 +119,6 @@ ahci_alloc_cmdslot(struct ahci_hba *hba, struct hba_port *port)
{
uint32_t slotlist;
- slotlist = port->ci | port->sact;
slotlist = mmio_read32(&port->ci);
slotlist |= mmio_read32(&port->sact);
@@ -315,22 +330,24 @@ hba_port_chkerr(struct hba_port *port)
static int
hba_port_reset(struct ahci_hba *hba, struct hba_port *port)
{
- uint32_t sctl, ssts;
- uint8_t det, ipm;
- int error;
+ uint32_t sctl, ssts, cmd;
+ uint8_t det, ipm, spd;
+ uint32_t elapsed = 0;
+
+ sctl = mmio_read32(&port->sctl);
/*
- * The port must not be in an idle state when a
- * COMRESET is sent over the interface as some
- * chipsets do not know how to handle this...
- *
- * After bringing up the port, send a COMRESET
- * over the interface for roughly ~2ms.
+ * Transmit a COMRESET to the device. If the HBA
+ * supports staggered spin-up, we'll need to set
+ * the PxCMD.SUD bit as well.
*/
- hba_port_start(port);
- sctl = mmio_read32(&port->sctl);
sctl = (sctl & ~0x0F) | AHCI_DET_COMRESET;
mmio_write32(&port->sctl, sctl);
+ if (hba->sss) {
+ cmd = mmio_read32(&port->cmd);
+ cmd |= AHCI_PXCMD_SUD;
+ mmio_write32(&port->cmd, cmd);
+ }
/*
* Wait for the link to become reestablished
@@ -340,34 +357,50 @@ hba_port_reset(struct ahci_hba *hba, struct hba_port *port)
sctl &= ~AHCI_DET_COMRESET;
mmio_write32(&port->sctl, sctl);
- /*
- * Now we'll need to grab some power management
- * and detection flags as the port must have
- * a device present along with an active
- * interface.
- */
- ssts = mmio_read32(&port->ssts);
- det = AHCI_PXSCTL_DET(ssts);
- ipm = AHCI_PXSSTS_IPM(ssts);
+ for (;;) {
+ if (elapsed >= AHCI_TIMEOUT) {
+ break;
+ }
+ ssts = mmio_read32(&port->ssts);
+ det = AHCI_PXSSTS_DET(ssts);
+ if (det == AHCI_DET_COMM) {
+ break;
+ }
- /* If there is no device, fake success */
- if (det == AHCI_DET_NULL) {
- return 0;
+ tmr.msleep(10);
+ elapsed += 10;
}
+ ipm = AHCI_PXSSTS_IPM(ssts);
+ spd = AHCI_PXSSTS_SPD(ssts);
+
+ if (det == AHCI_DET_PRESENT) {
+ pr_error("SATA link timeout\n");
+ return -EAGAIN;
+ }
if (det != AHCI_DET_COMM) {
- pr_trace("failed to establish link\n");
return -EAGAIN;
}
+ /*
+ * Ensure the interface is in an active
+ * state.
+ */
if (ipm != AHCI_IPM_ACTIVE) {
- pr_trace("device interface not active\n");
+ pr_error("device interface not active\n");
return -EAGAIN;
}
- if ((error = hba_port_stop(port)) < 0) {
- pr_trace("failed to stop port\n");
- return error;
+ switch (spd) {
+ case AHCI_SPD_GEN1:
+ pr_trace("SATA link rate @ ~1.5 Gb/s\n");
+ break;
+ case AHCI_SPD_GEN2:
+ pr_trace("SATA link rate @ ~3 Gb/s\n");
+ break;
+ case AHCI_SPD_GEN3:
+ pr_trace("SATA link rate @ ~6 Gb/s\n");
+ break;
}
return 0;
@@ -416,12 +449,14 @@ ahci_submit_cmd(struct ahci_hba *hba, struct hba_port *port, uint8_t slot)
* SATA device.
*/
static int
-ahci_identify(struct ahci_hba *hba, struct hba_port *port)
+ahci_identify(struct ahci_hba *hba, struct hba_device *dp)
{
paddr_t base, buf;
+ struct hba_port *port;
struct ahci_cmd_hdr *cmdhdr;
struct ahci_cmdtab *cmdtbl;
struct ahci_fis_h2d *fis;
+ uint16_t *p;
int cmdslot, status;
buf = vm_alloc_frame(1);
@@ -430,6 +465,7 @@ ahci_identify(struct ahci_hba *hba, struct hba_port *port)
return -ENOMEM;
}
+ port = dp->io;
cmdslot = ahci_alloc_cmdslot(hba, port);
if (cmdslot < 0) {
pr_trace("failed to alloc cmdslot\n");
@@ -461,6 +497,9 @@ ahci_identify(struct ahci_hba *hba, struct hba_port *port)
}
ahci_dump_identity(PHYS_TO_VIRT(buf));
+ p = (uint16_t *)PHYS_TO_VIRT(buf);
+ dp->nlba = (p[61] << 16) | p[60];
+ pr_trace("max block size: %d\n", dp->nlba);
done:
vm_free_frame(buf, 1);
return status;
@@ -470,7 +509,7 @@ done:
* Send a read/write command to a SATA drive
*
* @hba: Host bus adapter of target port
- * @port: Port to send over
+ * @dev: Device to send over
* @sio: System I/O descriptor
* @write: If true, data pointed to by `sio` will be written
*
@@ -480,14 +519,21 @@ done:
* - The `offset` field in `sio` is the LBA address.
*/
static int
-ahci_sata_rw(struct ahci_hba *hba, struct hba_port *port, struct sio_txn *sio,
+ahci_sata_rw(struct ahci_hba *hba, struct hba_device *dev, struct sio_txn *sio,
bool write)
{
paddr_t base, buf;
+ char *p, *dest;
+ bool dcdr_hit = false;
+ struct hba_port *port;
+ struct dcdr_lookup dcd_lookup;
+ struct dcd *dcd;
struct ahci_cmd_hdr *cmdhdr;
struct ahci_cmdtab *cmdtbl;
struct ahci_fis_h2d *fis;
int cmdslot, status;
+ size_t nblocks, cur_lba;
+ size_t len;
if (sio == NULL) {
return -EINVAL;
@@ -496,6 +542,60 @@ ahci_sata_rw(struct ahci_hba *hba, struct hba_port *port, struct sio_txn *sio,
return -EINVAL;
}
+ port = dev->io;
+
+ /*
+ * Compute how many blocks can be cached.
+ *
+ * XXX: We do not want to fill the entire DCDR
+ * with a single drive read to reduce the
+ * frequency of DCDR evictions.
+ *
+ * TODO: We should also take advantage of logical
+ * block coalescing.
+ */
+ nblocks = sio->len;
+ if (nblocks >= AHCI_DCDR_CAP) {
+ nblocks = AHCI_DCDR_CAP / 2;
+ }
+
+ /*
+ * If we are reading the drive, see if we have
+ * anything in the cache.
+ *
+ * XXX: If there is a break in the cache and we
+ * have a miss inbetween, other DCDs are
+ * ignored. Wonder how we can mitigate
+ * fragmentation.
+ */
+ cur_lba = sio->offset;
+ len = sio->len;
+ for (size_t i = 0; i < nblocks && !write; ++i) {
+ status = dcdr_lookup(dev->dcdr, cur_lba, &dcd_lookup);
+ if (status != 0) {
+ break;
+ }
+ if (len == 0) {
+ break;
+ }
+
+ dcdr_hit = true;
+ dcd = dcd_lookup.dcd_res;
+
+ /* Hit, copy the cached data */
+ dest = &((char *)sio->buf)[i * 512];
+ p = dcd->block;
+ memcpy(dest, p, 512);
+
+ ++cur_lba;
+ --len;
+ }
+
+ /* Did we get everything already? */
+ if (len == 0) {
+ return 0;
+ }
+
buf = VIRT_TO_PHYS(sio->buf);
cmdslot = ahci_alloc_cmdslot(hba, port);
if (cmdslot < 0) {
@@ -524,22 +624,32 @@ ahci_sata_rw(struct ahci_hba *hba, struct hba_port *port, struct sio_txn *sio,
fis->device = (1 << 6); /* LBA */
/* Setup LBA */
- fis->lba0 = sio->offset & 0xFF;
- fis->lba1 = (sio->offset >> 8) & 0xFF;
- fis->lba2 = (sio->offset >> 16) & 0xFF;
- fis->lba3 = (sio->offset >> 24) & 0xFF;
- fis->lba4 = (sio->offset >> 32) & 0xFF;
- fis->lba5 = (sio->offset >> 40) & 0xFF;
+ fis->lba0 = cur_lba & 0xFF;
+ fis->lba1 = (cur_lba >> 8) & 0xFF;
+ fis->lba2 = (cur_lba >> 16) & 0xFF;
+ fis->lba3 = (cur_lba >> 24) & 0xFF;
+ fis->lba4 = (cur_lba >> 32) & 0xFF;
+ fis->lba5 = (cur_lba >> 40) & 0xFF;
/* Setup count */
- fis->countl = sio->len & 0xFF;
- fis->counth = (sio->len >> 8) & 0xFF;
+ fis->countl = len & 0xFF;
+ fis->counth = (len >> 8) & 0xFF;
- pr_trace("SUBMIT: RIGHT!\n");
if ((status = ahci_submit_cmd(hba, port, cmdslot)) != 0) {
return status;
}
+ /* Don't cache again on hit */
+ if (!write && dcdr_hit) {
+ return 0;
+ }
+
+ /* Cache our read */
+ for (size_t i = 0; i < nblocks; ++i) {
+ cur_lba = sio->offset + i;
+ p = sio->buf;
+ dcdr_cachein(dev->dcdr, &p[i * 512], cur_lba);
+ }
return 0;
}
@@ -549,7 +659,6 @@ sata_dev_rw(dev_t dev, struct sio_txn *sio, bool write)
const size_t BSIZE = 512;
struct sio_txn wr_sio;
struct hba_device *devp;
- struct ahci_hba *hba;
size_t block_count, len;
off_t block_off, read_off;
char *buf;
@@ -561,11 +670,11 @@ sata_dev_rw(dev_t dev, struct sio_txn *sio, bool write)
if (sio->len == 0 || sio->buf == NULL) {
return -EINVAL;
}
- if (dev >= devs_max) {
+ if (dev > devs_max) {
return -ENODEV;
}
- devp = &devs[dev];
+ devp = ahci_get_dev(dev);
if (__unlikely(devp == NULL)) {
return -ENODEV;
}
@@ -595,14 +704,14 @@ sata_dev_rw(dev_t dev, struct sio_txn *sio, bool write)
wr_sio.buf = buf;
wr_sio.len = block_count;
wr_sio.offset = block_off;
- status = ahci_sata_rw(hba, devp->io, &wr_sio, write);
+ status = ahci_sata_rw(&g_hba, devp, &wr_sio, write);
if (status == 0 && !write) {
read_off = sio->offset & (BSIZE - 1);
memcpy(sio->buf, buf + read_off, sio->len);
}
dynfree(buf);
- return status;
+ return sio->len;
}
/*
@@ -611,10 +720,46 @@ sata_dev_rw(dev_t dev, struct sio_txn *sio, bool write)
static int
ahci_dev_read(dev_t dev, struct sio_txn *sio, int flags)
{
+ while (DRIVER_DEFERRED()) {
+ md_pause();
+ }
+
return sata_dev_rw(dev, sio, false);
}
/*
+ * Device interface write
+ */
+static int
+ahci_dev_write(dev_t dev, struct sio_txn *sio, int flags)
+{
+ while (DRIVER_DEFERRED()) {
+ md_pause();
+ }
+
+ return sata_dev_rw(dev, sio, true);
+}
+
+/*
+ * Device interface number of blocks
+ */
+static int
+ahci_dev_bsize(dev_t dev)
+{
+ struct hba_device *dp;
+
+ while (DRIVER_DEFERRED()) {
+ md_pause();
+ }
+
+ if ((dp = ahci_get_dev(dev)) == NULL) {
+ return -ENODEV;
+ }
+
+ return dp->nlba;
+}
+
+/*
* Initialize a drive on an HBA port
*
* @hba: HBA descriptor
@@ -629,27 +774,19 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
struct hba_device *dp;
struct ctlfs_dev dev;
size_t clen, pagesz;
- uint32_t lo, hi, ssts;
- uint8_t det;
+ uint32_t lo, hi, sig;
paddr_t fra, cmdlist, tmp;
int error;
pagesz = DEFAULT_PAGESIZE;
port = &abar->ports[portno];
- /* Is anything on the port? */
- ssts = mmio_read32(&port->ssts);
- det = AHCI_PXSCTL_DET(ssts);
- switch (det) {
- case AHCI_DET_NULL:
- /* No device attached */
- return 0;
- case AHCI_DET_PRESENT:
- if ((error = hba_port_reset(hba, port)) < 0) {
- pr_trace("failed to reset port %d\n", portno);
- return error;
- }
- break;
+ if ((error = hba_port_reset(hba, port)) < 0) {
+ return error;
+ }
+ sig = mmio_read32(&port->sig);
+ if (sig == ATAPI_SIG) {
+ return -ENOTSUP;
}
pr_trace("found device @ port %d\n", portno);
@@ -658,6 +795,12 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
dp->hba = hba;
dp->dev = portno;
+ dp->dcdr = dcdr_alloc(512, AHCI_DCDR_CAP);
+ if (dp->dcdr == NULL) {
+ pr_error("failed to alloc dcdr\n");
+ return -ENOMEM;
+ }
+
/* Allocate a command list */
clen = ALIGN_UP(hba->nslots * AHCI_CMDENTRY_SIZE, pagesz);
clen /= pagesz;
@@ -677,8 +820,6 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
}
dp->fra = PHYS_TO_VIRT(fra);
- memset(dp->cmdlist, 0, clen * pagesz);
- memset(dp->fra, 0, pagesz);
/* Write the command list */
lo = cmdlist & 0xFFFFFFFF;
@@ -697,7 +838,6 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
tmp = vm_alloc_frame(1);
dp->cmdlist[i].prdtl = 1;
dp->cmdlist[i].ctba = tmp;
- memset(PHYS_TO_VIRT(tmp), 0, pagesz);
}
mmio_write32(&port->serr, 0xFFFFFFFF);
@@ -712,7 +852,7 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
return error;
}
- ahci_identify(hba, port);
+ ahci_identify(hba, dp);
if (hba->major == 0) {
hba->major = dev_alloc_major();
@@ -733,7 +873,7 @@ ahci_init_port(struct ahci_hba *hba, uint32_t portno)
dev.devname = devname;
dev.ops = &g_sata_bsize_ops;
ctlfs_create_entry("bsize", &dev);
- return devfs_create_entry(devname, hba->major, dp->dev, 0444);
+ return devfs_create_entry(devname, hba->major, dp->dev, 060444);
}
/*
@@ -841,10 +981,9 @@ ahci_init(void)
{
struct pci_lookup lookup;
int status;
- struct ahci_hba hba;
void *abar_vap = NULL;
- hba.major = 0;
+ g_hba.major = 0;
lookup.pci_class = 0x01;
lookup.pci_subclass = 0x06;
@@ -890,14 +1029,15 @@ ahci_init(void)
}
ahci_init_pci();
- hba.io = (struct hba_memspace*)abar_vap;
- ahci_hba_init(&hba);
+ g_hba.io = (struct hba_memspace*)abar_vap;
+ ahci_hba_init(&g_hba);
return 0;
}
static struct bdevsw ahci_bdevsw = {
.read = ahci_dev_read,
- .write = nowrite
+ .write = ahci_dev_write,
+ .bsize = ahci_dev_bsize
};
-DRIVER_EXPORT(ahci_init);
+DRIVER_DEFER(ahci_init);