diff options
Diffstat (limited to 'sys/dev/ic/ahci.c')
-rw-r--r-- | sys/dev/ic/ahci.c | 284 |
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); |