diff options
author | David Gwynne <dlg@cvs.openbsd.org> | 2007-03-06 12:25:01 +0000 |
---|---|---|
committer | David Gwynne <dlg@cvs.openbsd.org> | 2007-03-06 12:25:01 +0000 |
commit | 403cf79981a208350a11823d2c81680c80d57bef (patch) | |
tree | 2f92ef6ef30d64b8eeae1ba7fa65cd07e0e36c68 | |
parent | 36877853486099bd6cb71c72ea0eefb271112f62 (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.c | 213 | ||||
-rw-r--r-- | sys/dev/pci/ahci.c | 152 |
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 */ } |