summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Gwynne <dlg@cvs.openbsd.org>2007-03-06 12:25:01 +0000
committerDavid Gwynne <dlg@cvs.openbsd.org>2007-03-06 12:25:01 +0000
commit403cf79981a208350a11823d2c81680c80d57bef (patch)
tree2f92ef6ef30d64b8eeae1ba7fa65cd07e0e36c68
parent36877853486099bd6cb71c72ea0eefb271112f62 (diff)
big changes to the completion path in ahci.c
- add a ccb_done member to the ahci_ccb, which points to a function that is used to complete the current xfer - ccbs no longer rely on an ata_xfer being provided for submission to work - ahci is stupid and has no way to telling you the difference between an empty command slot, and a completed command. we keep track of active commands with ap_active in ahci_port, so we dont try and complete commands we havent actually submitted - ahci_start simple submits a command to the hardware now - provide the start of an interrupt handler for each port (which is not yet called by the controllers interrupt handler) - provide an ahci_poll which is built on top of ahci_start and ahci_port_intr - remove the fake ata_xfers from the softreset path - on completion of an ahci command, sync the relevant dma memory - provide a completion path for ata_xfers which syncs the xfers buffer and calls the xfers completion handler in atascsi.c: - start defining the contents of the response to an ATA IDENTIFY command specific to SATA - implement the faking of scsi inquiries, so now you'll actually see a disk attach to ahci. - start implementing a fake scsi read capacity. it presents a fake geometry though, so dont get too excited when ahci magically makes your disk have a terabyte in size. lots of discussion, help, tweaks, and an ok from pascoe@
-rw-r--r--sys/dev/ata/atascsi.c213
-rw-r--r--sys/dev/pci/ahci.c152
2 files changed, 321 insertions, 44 deletions
diff --git a/sys/dev/ata/atascsi.c b/sys/dev/ata/atascsi.c
index b223a5e476e..34b56334515 100644
--- a/sys/dev/ata/atascsi.c
+++ b/sys/dev/ata/atascsi.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: atascsi.c,v 1.9 2007/02/28 13:38:04 dlg Exp $ */
+/* $OpenBSD: atascsi.c,v 1.10 2007/03/06 12:25:00 dlg Exp $ */
/*
* Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
@@ -32,6 +32,32 @@
#include <dev/ata/atascsi.h>
+/* XXX ata_identify should be in atareg.h */
+
+#define ATA_C_IDENTIFY 0xec
+
+struct ata_identify {
+ u_int16_t config; /* 0 */
+ u_int16_t ncyls; /* 1 (OBSOLETE) */
+ u_int16_t reserved1; /* 2 */
+ u_int16_t nheads; /* 3 (OBSOLETE) */
+ u_int16_t track_size; /* 4 (OBSOLETE) */
+ u_int16_t sector_size; /* 5 (OBSOLETE) */
+ u_int16_t nsectors; /* 6 (OBSOLETE) */
+ u_int16_t reserved2[3]; /* 7 vendor unique */
+ u_int8_t serial[20]; /* 10 */
+ u_int16_t buffer_type; /* 20 */
+ u_int16_t buffer_size; /* 21 */
+ u_int16_t ecc; /* 22 */
+ u_int8_t firmware[8]; /* 23 */
+ u_int8_t model[40]; /* 27 */
+ u_int16_t multi; /* 47 */
+ u_int16_t dwcap; /* 48 */
+ u_int16_t cap; /* 49 */
+ u_int16_t reserved3; /* 50 */
+ /* XXX there's a LOT more of this */
+} __packed;
+
struct atascsi {
struct device *as_dev;
void *as_cookie;
@@ -61,9 +87,16 @@ struct scsi_device atascsi_device = {
int atascsi_probe(struct atascsi *, int);
+struct ata_xfer *ata_setup_identify(struct ata_port *, int);
+void ata_free_identify(struct ata_xfer *);
+void ata_complete_identify(struct ata_xfer *,
+ struct ata_identify *);
+
int atascsi_disk_cmd(struct scsi_xfer *);
int atascsi_disk_inq(struct scsi_xfer *);
+void atascsi_disk_inq_done(struct ata_xfer *);
int atascsi_disk_capacity(struct scsi_xfer *);
+void atascsi_disk_capacity_done(struct ata_xfer *);
int atascsi_disk_sync(struct scsi_xfer *);
int atascsi_disk_sense(struct scsi_xfer *);
@@ -141,16 +174,86 @@ atascsi_detach(struct atascsi *as)
int
atascsi_probe(struct atascsi *as, int port)
{
+ struct ata_port *ap;
+ int type;
+
if (port > as->as_link.adapter_buswidth)
return (ENXIO);
-#if 0
- as->as_ports[port] = as->as_methods->probe(as->as_cookie, port);
-#endif
+ type = as->as_methods->probe(as->as_cookie, port);
+ if (type != ATA_PORT_T_DISK) /* XXX ATAPI too one day */
+ return (ENXIO);
+
+ ap = malloc(sizeof(struct ata_port), M_DEVBUF, M_WAITOK);
+ ap->ap_as = as;
+ ap->ap_port = port;
+ ap->ap_type = type;
+
+ as->as_ports[port] = ap;
return (0);
}
+struct ata_xfer *
+ata_setup_identify(struct ata_port *ap, int nosleep)
+{
+ struct ata_xfer *xa;
+ int s;
+
+ s = splbio();
+ xa = ata_get_xfer(ap, nosleep);
+ splx(s);
+ if (xa == NULL)
+ return (NULL);
+
+ xa->data = malloc(512, M_TEMP, nosleep ? M_NOWAIT : M_WAITOK);
+ if (xa->data == NULL) {
+ s = splbio();
+ ata_put_xfer(xa);
+ splx(s);
+ return (NULL);
+ }
+ bzero(xa->data, 512);
+ xa->datalen = 512;
+
+ xa->cmd.command = ATA_C_IDENTIFY;
+ xa->cmd.st_bmask = 0x40; /* XXX magic WDCS_DRDY */;
+ xa->cmd.st_pmask = 0x00;
+
+ xa->flags = ATA_F_READ;
+
+ return (xa);
+}
+
+void
+ata_free_identify(struct ata_xfer *xa)
+{
+ free(xa->data, M_TEMP);
+ ata_put_xfer(xa);
+}
+
+void
+ata_complete_identify(struct ata_xfer *xa, struct ata_identify *id)
+{
+ u_int16_t *swap;
+ int i;
+
+ bcopy(xa->data, id, sizeof(struct ata_identify));
+ ata_free_identify(xa);
+
+ swap = (u_int16_t *)id->serial;
+ for (i = 0; i < sizeof(id->serial) / sizeof(u_int16_t); i++)
+ swap[i] = swap16(swap[i]);
+
+ swap = (u_int16_t *)id->firmware;
+ for (i = 0; i < sizeof(id->firmware) / sizeof(u_int16_t); i++)
+ swap[i] = swap16(swap[i]);
+
+ swap = (u_int16_t *)id->model;
+ for (i = 0; i < sizeof(id->model) / sizeof(u_int16_t); i++)
+ swap[i] = swap16(swap[i]);
+}
+
int
atascsi_cmd(struct scsi_xfer *xs)
{
@@ -215,7 +318,59 @@ atascsi_disk_cmd(struct scsi_xfer *xs)
int
atascsi_disk_inq(struct scsi_xfer *xs)
{
- return (atascsi_stuffup(xs));
+ struct scsi_link *link = xs->sc_link;
+ struct atascsi *as = link->adapter_softc;
+ struct ata_port *ap = as->as_ports[link->target];
+ struct ata_xfer *xa;
+ int s;
+
+ xa = ata_setup_identify(ap, xs->flags & SCSI_NOSLEEP);
+ if (xa == NULL)
+ return (atascsi_stuffup(xs));
+
+ xa->complete = atascsi_disk_inq_done;
+ xa->atascsi_private = xs;
+ if (xs->flags & SCSI_POLL)
+ xa->flags |= ATA_F_POLL;
+
+ switch (ata_exec(as, xa)) {
+ case ATA_COMPLETE:
+ return (COMPLETE);
+ case ATA_QUEUED:
+ return (SUCCESSFULLY_QUEUED);
+ case ATA_ERROR:
+ s = splbio();
+ ata_free_identify(xa);
+ splx(s);
+ return (atascsi_stuffup(xs));
+ default:
+ panic("unexpected return from ata_exec");
+ }
+}
+
+void
+atascsi_disk_inq_done(struct ata_xfer *xa)
+{
+ struct scsi_xfer *xs = xa->atascsi_private;
+ struct ata_identify id;
+ struct scsi_inquiry_data inq;
+
+ ata_complete_identify(xa, &id);
+
+ bzero(&inq, sizeof(inq));
+
+ inq.device = T_DIRECT;
+ inq.version = 2;
+ inq.response_format = 2;
+ inq.additional_length = 32;
+ bcopy("ATA ", inq.vendor, sizeof(inq.vendor));
+ bcopy(id.model, inq.product, sizeof(inq.product));
+ bcopy(id.firmware, inq.revision, sizeof(inq.revision));
+
+ bcopy(&inq, xs->data, MIN(sizeof(inq), xs->datalen));
+ xs->error = XS_NOERROR;
+
+ scsi_done(xs);
}
int
@@ -227,7 +382,53 @@ atascsi_disk_sync(struct scsi_xfer *xs)
int
atascsi_disk_capacity(struct scsi_xfer *xs)
{
- return (atascsi_stuffup(xs));
+ struct scsi_link *link = xs->sc_link;
+ struct atascsi *as = link->adapter_softc;
+ struct ata_port *ap = as->as_ports[link->target];
+ struct ata_xfer *xa;
+ int s;
+
+ xa = ata_setup_identify(ap, xs->flags & SCSI_NOSLEEP);
+ if (xa == NULL)
+ return (atascsi_stuffup(xs));
+
+ xa->complete = atascsi_disk_capacity_done;
+ xa->atascsi_private = xs;
+ if (xs->flags & SCSI_POLL)
+ xa->flags |= ATA_F_POLL;
+
+ switch (ata_exec(as, xa)) {
+ case ATA_COMPLETE:
+ return (COMPLETE);
+ case ATA_QUEUED:
+ return (SUCCESSFULLY_QUEUED);
+ case ATA_ERROR:
+ s = splbio();
+ ata_free_identify(xa);
+ splx(s);
+ return (atascsi_stuffup(xs));
+ default:
+ panic("unexpected return from ata_exec");
+ }
+}
+
+void
+atascsi_disk_capacity_done(struct ata_xfer *xa)
+{
+ struct scsi_xfer *xs = xa->atascsi_private;
+ struct ata_identify id;
+ struct scsi_read_cap_data rcd;
+
+ ata_complete_identify(xa, &id);
+
+ bzero(&rcd, sizeof(rcd));
+ _lto4b(1024 * 1024 * 1024, rcd.addr);
+ _lto4b(512, rcd.length);
+
+ bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen));
+ xs->error = XS_NOERROR;
+
+ scsi_done(xs);
}
int
diff --git a/sys/dev/pci/ahci.c b/sys/dev/pci/ahci.c
index e46f2a87ff1..d51a4fff5f3 100644
--- a/sys/dev/pci/ahci.c
+++ b/sys/dev/pci/ahci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ahci.c,v 1.73 2007/03/06 09:01:28 dlg Exp $ */
+/* $OpenBSD: ahci.c,v 1.74 2007/03/06 12:25:00 dlg Exp $ */
/*
* Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
@@ -310,12 +310,12 @@ struct ahci_ccb {
int ccb_slot;
struct ahci_port *ccb_port;
- struct ata_xfer *ccb_xa;
-
+ bus_dmamap_t ccb_dmamap;
struct ahci_cmd_hdr *ccb_cmd_hdr;
struct ahci_cmd_table *ccb_cmd_table;
- bus_dmamap_t ccb_dmamap;
+ struct ata_xfer *ccb_xa;
+ void (*ccb_done)(struct ahci_ccb *);
TAILQ_ENTRY(ahci_ccb) ccb_entry;
};
@@ -330,6 +330,7 @@ struct ahci_port {
struct ahci_dmamem *ap_dmamem_cmd_list;
struct ahci_dmamem *ap_dmamem_cmd_table;
+ volatile u_int32_t ap_active;
struct ahci_ccb *ap_ccbs;
TAILQ_HEAD(, ahci_ccb) ap_ccb_free;
@@ -405,9 +406,11 @@ int ahci_port_portreset(struct ahci_port *);
int ahci_port_softreset(struct ahci_port *);
int ahci_load_prdt(struct ahci_ccb *);
-int ahci_start(struct ahci_ccb *);
+void ahci_start(struct ahci_ccb *);
+int ahci_poll(struct ahci_ccb *, int);
int ahci_intr(void *);
+int ahci_port_intr(struct ahci_port *, u_int32_t);
struct ahci_ccb *ahci_get_ccb(struct ahci_port *);
void ahci_put_ccb(struct ahci_port *, struct ahci_ccb *);
@@ -445,6 +448,10 @@ struct atascsi_methods ahci_atascsi_methods = {
ahci_ata_cmd
};
+/* ccb completions */
+void ahci_ata_cmd_done(struct ahci_ccb *);
+void ahci_empty_done(struct ahci_ccb *);
+
const struct ahci_device *
ahci_lookup_device(struct pci_attach_args *pa)
{
@@ -1009,7 +1016,6 @@ ahci_port_softreset(struct ahci_port *ap)
u_int8_t *fis;
int s, rc = EIO;
u_int32_t cmd;
- struct ata_xfer xa;
DPRINTF(AHCI_D_VERBOSE, "%s: soft reset\n", PORTNAME(ap));
@@ -1018,6 +1024,7 @@ ahci_port_softreset(struct ahci_port *ap)
splx(s);
if (ccb == NULL)
goto err;
+ ccb->ccb_done = ahci_empty_done;
/* Save previous command register state */
cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC;
@@ -1055,9 +1062,6 @@ ahci_port_softreset(struct ahci_port *ap)
}
/* Prep first D2H command with SRST feature & clear busy/reset flags */
- bzero(&xa, sizeof(struct ata_xfer));
- xa.flags = ATA_F_POLL | ATA_F_WRITE;
- ccb->ccb_xa = &xa;
cmd_slot = ccb->ccb_cmd_hdr;
bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table));
@@ -1065,34 +1069,27 @@ ahci_port_softreset(struct ahci_port *ap)
fis[0] = 0x27; /* Host to device */
fis[15] = 0x04; /* SRST DEVCTL */
- if (ahci_load_prdt(ccb))
- goto err;
-
+ cmd_slot->prdtl = 0;
cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */
cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_C); /* Clear busy on OK */
cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_R); /* Reset */
cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W); /* Write */
- if (ahci_start(ccb) != ATA_COMPLETE)
+ if (ahci_poll(ccb, 1000) != 0)
goto err;
/* Prep second D2H command to read status and complete reset sequence */
- bzero(&xa, sizeof(struct ata_xfer));
- xa.flags = ATA_F_POLL | ATA_F_WRITE;
- ccb->ccb_xa = &xa;
cmd_slot = ccb->ccb_cmd_hdr;
bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table));
fis = ccb->ccb_cmd_table->cfis;
fis[0] = 0x27; /* Host to device */
- if (ahci_load_prdt(ccb))
- goto err;
-
+ cmd_slot->prdtl = 0;
cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */
cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W);
- if (ahci_start(ccb) != ATA_COMPLETE)
+ if (ahci_poll(ccb, 1000) != 0)
goto err;
if (ahci_pwait_clr(ap, AHCI_PREG_TFD, AHCI_PREG_TFD_STS_BSY |
@@ -1226,12 +1223,31 @@ ahci_load_prdt(struct ahci_ccb *ccb)
}
int
+ahci_poll(struct ahci_ccb *ccb, int timeout)
+{
+ struct ahci_port *ap = ccb->ccb_port;
+ int s;
+
+ s = splbio();
+ ahci_start(ccb);
+ do {
+ if (ahci_port_intr(ap, 1 << ccb->ccb_slot))
+ return (0);
+
+ delay(1000);
+ } while (--timeout > 0);
+ splx(s);
+
+ /* XXX cleanup! */
+
+ return (1);
+}
+
+void
ahci_start(struct ahci_ccb *ccb)
{
struct ahci_port *ap = ccb->ccb_port;
struct ahci_softc *sc = ap->ap_sc;
- int rc = ATA_QUEUED;
- int s;
/* Zero transferred byte count before transfer */
ccb->ccb_cmd_hdr->prdbc = 0;
@@ -1248,22 +1264,8 @@ ahci_start(struct ahci_ccb *ccb)
bus_dmamap_sync(sc->sc_dmat, AHCI_DMA_MAP(ap->ap_dmamem_rfis), 0,
sizeof(struct ahci_rfis), BUS_DMASYNC_PREREAD);
- s = splbio();
+ ap->ap_active |= 1 << ccb->ccb_slot;
ahci_pwrite(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot);
- if (ccb->ccb_xa->flags & ATA_F_POLL) {
- if (ahci_pwait_clr(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot)) {
- /* Command didn't go inactive. XXX: wait longer? */
- printf("%s: polled command didn't go inactive\n",
- PORTNAME(ap));
- /* Shutdown port. XXX: recover via port reset? */
- ahci_port_stop(ap, 0);
- rc = ATA_ERROR;
- } else
- rc = ATA_COMPLETE;
- }
- splx(s);
-
- return (rc);
}
int
@@ -1272,6 +1274,43 @@ ahci_intr(void *arg)
return (0);
}
+int
+ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask)
+{
+ struct ahci_softc *sc = ap->ap_sc;
+ struct ahci_ccb *ccb;
+ u_int32_t ci;
+ int i, intr = 0;
+
+ ci = ~ahci_pread(ap, AHCI_PREG_CI) & ap->ap_active & ci_mask;
+ for (i = 0; i < sc->sc_ncmds; i++) {
+ if (ISSET(ci, 1 << i)) {
+ ccb = &ap->ap_ccbs[i];
+
+ bus_dmamap_sync(sc->sc_dmat,
+ AHCI_DMA_MAP(ap->ap_dmamem_cmd_list),
+ ccb->ccb_slot * sizeof(struct ahci_cmd_hdr),
+ sizeof(struct ahci_cmd_hdr),
+ BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_sync(sc->sc_dmat,
+ AHCI_DMA_MAP(ap->ap_dmamem_cmd_table),
+ ccb->ccb_slot * sizeof(struct ahci_cmd_table),
+ sizeof(struct ahci_cmd_table),
+ BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_sync(sc->sc_dmat,
+ AHCI_DMA_MAP(ap->ap_dmamem_rfis), 0,
+ sizeof(struct ahci_rfis), BUS_DMASYNC_POSTREAD);
+
+ ap->ap_active &= ~(1 << ccb->ccb_slot);
+ ccb->ccb_done(ccb);
+ intr = 1;
+ }
+ }
+
+ return (intr);
+}
+
struct ahci_ccb *
ahci_get_ccb(struct ahci_port *ap)
{
@@ -1474,6 +1513,7 @@ ahci_ata_cmd(void *xsc, struct ata_xfer *xa)
return (ATA_ERROR);
ccb->ccb_xa = xa;
+ ccb->ccb_done = ahci_ata_cmd_done;
cmd_slot = ccb->ccb_cmd_hdr;
bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table));
@@ -1510,5 +1550,41 @@ ahci_ata_cmd(void *xsc, struct ata_xfer *xa)
if (xa->flags & ATA_F_WRITE)
cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W);
- return (ahci_start(ccb));
+ if (1 || xa->flags & ATA_F_POLL) {
+ if (ahci_poll(ccb, 1000) != 0)
+ return (ATA_ERROR);
+ return (ATA_COMPLETE);
+ }
+
+ s = splbio();
+ ahci_start(ccb);
+ splx(s);
+ return (ATA_QUEUED);
+}
+
+void
+ahci_ata_cmd_done(struct ahci_ccb *ccb)
+{
+ struct ahci_port *ap = ccb->ccb_port;
+ struct ahci_softc *sc = ap->ap_sc;
+ struct ata_xfer *xa = ccb->ccb_xa;
+ bus_dmamap_t dmap = ccb->ccb_dmamap;
+
+ if (xa->datalen != 0) {
+ bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize,
+ (xa->flags & ATA_F_READ) ? BUS_DMASYNC_POSTREAD :
+ BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_unload(sc->sc_dmat, dmap);
+ }
+
+ ahci_put_ccb(ap, ccb);
+ xa->state = ATA_S_COMPLETE;
+ xa->complete(xa);
+}
+
+void
+ahci_empty_done(struct ahci_ccb *ccb)
+{
+ /* do nothing */
}