diff options
author | Dale Rahn <drahn@cvs.openbsd.org> | 2011-01-26 21:41:01 +0000 |
---|---|---|
committer | Dale Rahn <drahn@cvs.openbsd.org> | 2011-01-26 21:41:01 +0000 |
commit | c2a07274c68726b615403e10632b43beb8eac23d (patch) | |
tree | eecf311d61eff5cac481c733225373a69df908bf /sys/dev | |
parent | c39e9b783d98196c42dbb88a452736eb8bc25f28 (diff) |
Add port multiplier support, has been in snaps for a while with no reported
issues. No actual OKs, but general acknowledgement and 'get it in' from several.
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/ata/atascsi.c | 448 | ||||
-rw-r--r-- | sys/dev/ata/atascsi.h | 30 | ||||
-rw-r--r-- | sys/dev/ic/sili.c | 828 | ||||
-rw-r--r-- | sys/dev/ic/silireg.h | 27 | ||||
-rw-r--r-- | sys/dev/pci/ahci.c | 1138 |
5 files changed, 2209 insertions, 262 deletions
diff --git a/sys/dev/ata/atascsi.c b/sys/dev/ata/atascsi.c index dbd0c6e98ad..3d19bbc7133 100644 --- a/sys/dev/ata/atascsi.c +++ b/sys/dev/ata/atascsi.c @@ -1,7 +1,9 @@ -/* $OpenBSD: atascsi.c,v 1.99 2011/01/12 21:00:04 kettenis Exp $ */ +/* $OpenBSD: atascsi.c,v 1.100 2011/01/26 21:41:00 drahn Exp $ */ /* * Copyright (c) 2007 David Gwynne <dlg@openbsd.org> + * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com> + * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -30,6 +32,7 @@ #include <scsi/scsiconf.h> #include <dev/ata/atascsi.h> +#include <dev/ata/pmreg.h> #include <sys/ataio.h> @@ -39,7 +42,7 @@ struct atascsi { struct device *as_dev; void *as_cookie; - struct atascsi_port **as_ports; + struct atascsi_host_port **as_host_ports; struct atascsi_methods *as_methods; struct scsi_adapter as_switch; @@ -50,12 +53,40 @@ struct atascsi { int as_ncqdepth; }; +/* + * atascsi_host_port is a port attached to the host controller, and + * only holds the details relevant to the host controller. + * atascsi_port is any port, including ports on port multipliers, and + * it holds details of the device attached to the port. + * + * When there is a port multiplier attached to a port, the ahp_ports + * array in the atascsi_host_port struct contains one atascsi_port for + * each port, and one for the control port (port 15). The index into + * the array is the LUN used to address the port. For the control port, + * the LUN is 0, and for the port multiplier ports, the LUN is the + * port number plus one. + * + * When there is no port multiplier attached to a port, the ahp_ports + * array contains a single entry for the device. The LUN and port number + * for this entry are both 0. + */ + +struct atascsi_host_port { + struct scsi_iopool ahp_iopool; + struct atascsi *ahp_as; + int ahp_port; + int ahp_nports; + + struct atascsi_port **ahp_ports; +}; + struct atascsi_port { struct ata_identify ap_identify; - struct scsi_iopool ap_iopool; + struct atascsi_host_port *ap_host_port; struct atascsi *ap_as; - int ap_port; + int ap_pmp_port; int ap_type; + int ap_ncqdepth; int ap_features; #define ATA_PORT_F_NCQ 0x1 #define ATA_PORT_F_TRIM 0x2 @@ -98,6 +129,12 @@ void atascsi_disk_start_stop_done(struct ata_xfer *); void atascsi_atapi_cmd(struct scsi_xfer *); void atascsi_atapi_cmd_done(struct ata_xfer *); +void atascsi_pmp_cmd(struct scsi_xfer *); +void atascsi_pmp_cmd_done(struct ata_xfer *); +void atascsi_pmp_sense(struct scsi_xfer *xs); +void atascsi_pmp_inq(struct scsi_xfer *xs); + + void atascsi_passthru_12(struct scsi_xfer *); void atascsi_passthru_16(struct scsi_xfer *); int atascsi_passthru_map(struct scsi_xfer *, u_int8_t, u_int8_t); @@ -117,6 +154,7 @@ u_int ata_identify_block_logical_align(struct ata_identify *); void *atascsi_io_get(void *); void atascsi_io_put(void *, void *); +struct atascsi_port * atascsi_lookup_port(struct scsi_link *); struct atascsi * atascsi_attach(struct device *self, struct atascsi_attach_args *aaa) @@ -141,12 +179,12 @@ atascsi_attach(struct device *self, struct atascsi_attach_args *aaa) as->as_link.adapter = &as->as_switch; as->as_link.adapter_softc = as; as->as_link.adapter_buswidth = aaa->aaa_nports; - as->as_link.luns = 1; /* XXX port multiplier as luns */ + as->as_link.luns = SATA_PMP_MAX_PORTS; as->as_link.adapter_target = aaa->aaa_nports; as->as_link.openings = 1; - as->as_ports = malloc(sizeof(struct atascsi_port *) * aaa->aaa_nports, - M_DEVBUF, M_WAITOK | M_ZERO); + as->as_host_ports = malloc(sizeof(struct atascsi_host_port *) * + aaa->aaa_nports, M_DEVBUF, M_WAITOK | M_ZERO); bzero(&saa, sizeof(saa)); saa.saa_sc_link = &as->as_link; @@ -167,44 +205,71 @@ atascsi_detach(struct atascsi *as, int flags) if (rv != 0) return (rv); - free(as->as_ports, M_DEVBUF); + free(as->as_host_ports, M_DEVBUF); free(as, M_DEVBUF); return (0); } int -atascsi_probe_dev(struct atascsi *as, int port) +atascsi_probe_dev(struct atascsi *as, int port, int lun) { - return (scsi_probe_target(as->as_scsibus, port)); + if (lun == 0) { + return (scsi_probe_target(as->as_scsibus, port)); + } else { + return (scsi_probe_lun(as->as_scsibus, port, lun)); + } } int -atascsi_detach_dev(struct atascsi *as, int port, int flags) +atascsi_detach_dev(struct atascsi *as, int port, int lun, int flags) +{ + if (lun == 0) { + return (scsi_detach_target(as->as_scsibus, port, flags)); + } else { + return (scsi_detach_lun(as->as_scsibus, port, lun, flags)); + } +} + +struct atascsi_port * +atascsi_lookup_port(struct scsi_link *link) { - return (scsi_detach_target(as->as_scsibus, port, flags)); + struct atascsi *as = link->adapter_softc; + struct atascsi_host_port *ahp; + + if (link->target > as->as_link.adapter_buswidth) + return (NULL); + + ahp = as->as_host_ports[link->target]; + if (link->lun >= ahp->ahp_nports) + return (NULL); + + return (ahp->ahp_ports[link->lun]); } int atascsi_probe(struct scsi_link *link) { - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap; - struct ata_xfer *xa; - struct ata_identify *identify; - int port, type, qdepth; - int rv; - u_int16_t cmdset; - - /* revisit this when we do port multipliers */ - if (link->lun > 0) - return (ENXIO); + struct atascsi *as = link->adapter_softc; + struct atascsi_host_port *ahp; + struct atascsi_port *ap; + struct ata_xfer *xa; + struct ata_identify *identify; + int port, type, qdepth; + int rv; + u_int16_t cmdset; port = link->target; if (port > as->as_link.adapter_buswidth) return (ENXIO); - type = as->as_methods->probe(as->as_cookie, port); + /* if this is a PMP port, check it's valid */ + if (link->lun > 0) { + if (link->lun > as->as_host_ports[port]->ahp_nports) + return (ENXIO); + } + + type = as->as_methods->probe(as->as_cookie, port, link->lun); switch (type) { case ATA_PORT_T_DISK: break; @@ -212,6 +277,14 @@ atascsi_probe(struct scsi_link *link) link->flags |= SDEV_ATAPI; link->quirks |= SDEV_ONLYBIG; break; + case ATA_PORT_T_PM: + if (link->lun != 0) { + printf("%s.%d.%d: Port multipliers cannot be nested\n", + as->as_dev->dv_xname, port, link->lun); + rv = ENODEV; + goto unsupported; + } + break; default: rv = ENODEV; goto unsupported; @@ -219,45 +292,89 @@ atascsi_probe(struct scsi_link *link) ap = malloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO); ap->ap_as = as; - ap->ap_port = port; + + if (link->lun == 0) { + ahp = malloc(sizeof(*ahp), M_DEVBUF, M_WAITOK | M_ZERO); + ahp->ahp_as = as; + ahp->ahp_port = port; + + scsi_iopool_init(&ahp->ahp_iopool, ahp, atascsi_io_get, + atascsi_io_put); + + as->as_host_ports[port] = ahp; + + if (type == ATA_PORT_T_PM) { + ahp->ahp_nports = SATA_PMP_MAX_PORTS; + ap->ap_pmp_port = SATA_PMP_CONTROL_PORT; + } else { + ahp->ahp_nports = 1; + ap->ap_pmp_port = 0; + } + ahp->ahp_ports = malloc(sizeof(struct atascsi_port *) * + ahp->ahp_nports, M_DEVBUF, M_WAITOK | M_ZERO); + } else { + ahp = as->as_host_ports[port]; + ap->ap_pmp_port = link->lun - 1; + } + + ap->ap_host_port = ahp; ap->ap_type = type; - scsi_iopool_init(&ap->ap_iopool, ap, atascsi_io_get, atascsi_io_put); - link->pool = &ap->ap_iopool; + link->pool = &ahp->ahp_iopool; + + /* fetch the device info, except for port multipliers */ + if (type != ATA_PORT_T_PM) { + + /* devices attached to port multipliers tend not to be + * spun up at this point, and sometimes this prevents + * identification from working, so we retry a few times + * with a fairly long delay. + */ + int count = 5; + do { + xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP); + if (xa == NULL) + panic("no free xfers on a new port"); + /* XXX dma reachable */ + identify = malloc(sizeof(*identify), M_TEMP, M_WAITOK); + xa->pmp_port = ap->ap_pmp_port; + xa->data = identify; + xa->datalen = sizeof(*identify); + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; + xa->fis->command = (type == ATA_PORT_T_DISK) ? + ATA_C_IDENTIFY : ATA_C_IDENTIFY_PACKET; + xa->fis->device = 0; + xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; + xa->timeout = 1000; + xa->complete = ata_polled_complete; + xa->atascsi_private = &ahp->ahp_iopool; + ata_exec(as, xa); + rv = ata_polled(xa); + if (rv == 0) { + bcopy(identify, &ap->ap_identify, + sizeof(ap->ap_identify)); + free(identify, M_TEMP); + break; + } + free(identify, M_TEMP); + delay(5000000); + } while (count--); - /* fetch the device info */ - xa = scsi_io_get(&ap->ap_iopool, SCSI_NOSLEEP); - if (xa == NULL) - panic("no free xfers on a new port"); - identify = malloc(sizeof(*identify), M_TEMP, M_WAITOK); /* XXX dma reachable */ - xa->data = identify; - xa->datalen = sizeof(*identify); - xa->fis->flags = ATA_H2D_FLAGS_CMD; - xa->fis->command = (type == ATA_PORT_T_DISK) ? - ATA_C_IDENTIFY : ATA_C_IDENTIFY_PACKET; - xa->fis->device = 0; - xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; - xa->timeout = 1000; - xa->complete = ata_polled_complete; - xa->atascsi_private = &ap->ap_iopool; - ata_exec(as, xa); - rv = ata_polled(xa); - if (rv != 0) { - free(identify, M_TEMP); - goto error; + if (rv != 0) { + goto error; + } } - bcopy(identify, &ap->ap_identify, sizeof(ap->ap_identify)); - free(identify, M_TEMP); - as->as_ports[port] = ap; + ahp->ahp_ports[link->lun] = ap; if (type != ATA_PORT_T_DISK) return (0); if (as->as_capability & ASAA_CAP_NCQ && - ISSET(letoh16(ap->ap_identify.satacap), ATA_SATACAP_NCQ)) { - qdepth = ATA_QDEPTH(letoh16(ap->ap_identify.qdepth)); - qdepth = MIN(qdepth, as->as_ncqdepth); + ISSET(letoh16(ap->ap_identify.satacap), ATA_SATACAP_NCQ) && + (link->lun == 0 || as->as_capability & ASAA_CAP_PMP_NCQ)) { + ap->ap_ncqdepth = ATA_QDEPTH(letoh16(ap->ap_identify.qdepth)); + qdepth = MIN(ap->ap_ncqdepth, as->as_ncqdepth); if (ISSET(as->as_capability, ASAA_CAP_NEEDS_RESERVED)) qdepth--; @@ -268,14 +385,16 @@ atascsi_probe(struct scsi_link *link) link->openings = qdepth; /* - * XXX throw away any xfers that have tag numbers - * higher than what the device supports. + * XXX for directly attached devices, throw away any xfers + * that have tag numbers higher than what the device supports. */ - while (qdepth--) { - xa = scsi_io_get(&ap->ap_iopool, SCSI_NOSLEEP); - if (xa->tag < link->openings) { - xa->state = ATA_S_COMPLETE; - scsi_io_put(&ap->ap_iopool, xa); + if (link->lun == 0) { + while (qdepth--) { + xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP); + if (xa->tag < link->openings) { + xa->state = ATA_S_COMPLETE; + scsi_io_put(&ahp->ahp_iopool, xa); + } } } } @@ -289,32 +408,34 @@ atascsi_probe(struct scsi_link *link) /* Enable write cache if supported */ if (ISSET(cmdset, ATA_IDENTIFY_WRITECACHE)) { - xa = scsi_io_get(&ap->ap_iopool, SCSI_NOSLEEP); + xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP); if (xa == NULL) panic("no free xfers on a new port"); xa->fis->command = ATA_C_SET_FEATURES; xa->fis->features = ATA_SF_WRITECACHE_EN; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; xa->timeout = 1000; xa->complete = ata_polled_complete; - xa->atascsi_private = &ap->ap_iopool; + xa->pmp_port = ap->ap_pmp_port; + xa->atascsi_private = &ahp->ahp_iopool; ata_exec(as, xa); ata_polled(xa); /* we dont care if it doesnt work */ } /* Enable read lookahead if supported */ if (ISSET(cmdset, ATA_IDENTIFY_LOOKAHEAD)) { - xa = scsi_io_get(&ap->ap_iopool, SCSI_NOSLEEP); + xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP); if (xa == NULL) panic("no free xfers on a new port"); xa->fis->command = ATA_C_SET_FEATURES; xa->fis->features = ATA_SF_LOOKAHEAD_EN; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; xa->timeout = 1000; xa->complete = ata_polled_complete; - xa->atascsi_private = &ap->ap_iopool; + xa->pmp_port = ap->ap_pmp_port; + xa->atascsi_private = &ahp->ahp_iopool; ata_exec(as, xa); ata_polled(xa); /* we dont care if it doesnt work */ } @@ -326,15 +447,16 @@ atascsi_probe(struct scsi_link *link) * checking if the device sends a command abort to tell us it doesn't * support it */ - xa = scsi_io_get(&ap->ap_iopool, SCSI_NOSLEEP); + xa = scsi_io_get(&ahp->ahp_iopool, SCSI_NOSLEEP); if (xa == NULL) panic("no free xfers on a new port"); xa->fis->command = ATA_C_SEC_FREEZE_LOCK; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; xa->timeout = 1000; xa->complete = ata_polled_complete; - xa->atascsi_private = &ap->ap_iopool; + xa->pmp_port = ap->ap_pmp_port; + xa->atascsi_private = &ahp->ahp_iopool; ata_exec(as, xa); ata_polled(xa); /* we dont care if it doesnt work */ @@ -342,41 +464,53 @@ atascsi_probe(struct scsi_link *link) error: free(ap, M_DEVBUF); unsupported: - as->as_methods->free(as->as_cookie, port); + + as->as_methods->free(as->as_cookie, port, link->lun); return (rv); } void atascsi_free(struct scsi_link *link) { - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap; - int port; - - if (link->lun > 0) - return; + struct atascsi *as = link->adapter_softc; + struct atascsi_host_port *ahp; + struct atascsi_port *ap; + int port; port = link->target; if (port > as->as_link.adapter_buswidth) return; - ap = as->as_ports[port]; - if (ap == NULL) + ahp = as->as_host_ports[port]; + if (ahp == NULL) return; + if (link->lun >= ahp->ahp_nports) + return; + + ap = ahp->ahp_ports[link->lun]; free(ap, M_DEVBUF); - as->as_ports[port] = NULL; + ahp->ahp_ports[link->lun] = NULL; - as->as_methods->free(as->as_cookie, port); + as->as_methods->free(as->as_cookie, port, link->lun); + + if (link->lun == ahp->ahp_nports - 1) { + /* we've already freed all of ahp->ahp_ports, now + * free ahp itself. this relies on the order luns are + * detached in scsi_detach_target(). + */ + free(ahp, M_DEVBUF); + as->as_host_ports[port] = NULL; + } } void atascsi_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; + ap = atascsi_lookup_port(link); if (ap == NULL) { atascsi_done(xs, XS_DRIVER_STUFFUP); return; @@ -389,6 +523,9 @@ atascsi_cmd(struct scsi_xfer *xs) case ATA_PORT_T_ATAPI: atascsi_atapi_cmd(xs); break; + case ATA_PORT_T_PM: + atascsi_pmp_cmd(xs); + break; case ATA_PORT_T_NONE: default: @@ -402,13 +539,15 @@ atascsi_disk_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; int flags = 0; struct ata_fis_h2d *fis; u_int64_t lba; u_int32_t sector_count; + ap = atascsi_lookup_port(link); + switch (xs->cmd->opcode) { case READ_COMMAND: case READ_BIG: @@ -474,12 +613,13 @@ atascsi_disk_cmd(struct scsi_xfer *xs) fis = xa->fis; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; fis->lba_low = lba & 0xff; fis->lba_mid = (lba >> 8) & 0xff; fis->lba_high = (lba >> 16) & 0xff; if (ISSET(ap->ap_features, ATA_PORT_F_NCQ) && + (xa->tag < ap->ap_ncqdepth) && !(xs->flags & SCSI_POLL)) { /* Use NCQ */ xa->flags |= ATA_F_NCQ; @@ -514,6 +654,7 @@ atascsi_disk_cmd(struct scsi_xfer *xs) xa->datalen = xs->datalen; xa->complete = atascsi_disk_cmd_done; xa->timeout = xs->timeout; + xa->pmp_port = ap->ap_pmp_port; xa->atascsi_private = xs; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; @@ -587,8 +728,9 @@ atascsi_disk_inquiry(struct scsi_xfer *xs) { struct scsi_inquiry_data inq; struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; + + ap = atascsi_lookup_port(link); bzero(&inq, sizeof(inq)); @@ -636,10 +778,10 @@ void atascsi_disk_vpd_serial(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_vpd_serial pg; + ap = atascsi_lookup_port(link); bzero(&pg, sizeof(pg)); pg.hdr.device = T_DIRECT; @@ -657,8 +799,7 @@ void atascsi_disk_vpd_ident(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct { struct scsi_vpd_hdr hdr; struct scsi_vpd_devid_hdr devid_hdr; @@ -667,6 +808,7 @@ atascsi_disk_vpd_ident(struct scsi_xfer *xs) u_int8_t *p; size_t pg_len; + ap = atascsi_lookup_port(link); bzero(&pg, sizeof(pg)); if (letoh16(ap->ap_identify.features87) & ATA_ID_F87_WWN) { pg_len = 8; @@ -708,10 +850,10 @@ void atascsi_disk_vpd_limits(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_vpd_disk_limits pg; + ap = atascsi_lookup_port(link); bzero(&pg, sizeof(pg)); pg.hdr.device = T_DIRECT; pg.hdr.page_code = SI_PG_DISK_LIMITS; @@ -729,10 +871,10 @@ void atascsi_disk_vpd_info(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_vpd_disk_info pg; + ap = atascsi_lookup_port(link); bzero(&pg, sizeof(pg)); pg.hdr.device = T_DIRECT; pg.hdr.page_code = SI_PG_DISK_INFO; @@ -751,7 +893,7 @@ atascsi_disk_write_same_16(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_write_same_16 *cdb; struct ata_xfer *xa = xs->io; struct ata_fis_h2d *fis; @@ -764,6 +906,7 @@ atascsi_disk_write_same_16(struct scsi_xfer *xs) return; } + ap = atascsi_lookup_port(link); cdb = (struct scsi_write_same_16 *)xs->cmd; if (cdb->flags != WRITE_SAME_F_UNMAP || @@ -785,6 +928,7 @@ atascsi_disk_write_same_16(struct scsi_xfer *xs) xa->data = xs->data; xa->datalen = xs->datalen; xa->flags = ATA_F_WRITE; + xa->pmp_port = ap->ap_pmp_port; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; xa->complete = atascsi_disk_write_same_16_done; @@ -797,7 +941,7 @@ atascsi_disk_write_same_16(struct scsi_xfer *xs) memcpy(xa->data, &desc, sizeof(desc)); fis = xa->fis; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; fis->command = ATA_C_DSM; fis->features = ATA_DSM_TRIM; @@ -835,6 +979,7 @@ atascsi_disk_sync(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; if (xs->cmdlen != sizeof(struct scsi_synchronize_cache)) { @@ -842,16 +987,18 @@ atascsi_disk_sync(struct scsi_xfer *xs) return; } + ap = atascsi_lookup_port(link); xa->datalen = 0; xa->flags = ATA_F_READ; xa->complete = atascsi_disk_sync_done; /* Spec says flush cache can take >30 sec, so give it at least 45. */ xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout; xa->atascsi_private = xs; + xa->pmp_port = ap->ap_pmp_port; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->fis->command = ATA_C_FLUSH_CACHE; xa->fis->device = 0; @@ -955,11 +1102,11 @@ void atascsi_disk_capacity(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_read_cap_data rcd; u_int64_t capacity; + ap = atascsi_lookup_port(link); if (xs->cmdlen != sizeof(struct scsi_read_capacity)) { atascsi_done(xs, XS_DRIVER_STUFFUP); return; @@ -982,12 +1129,12 @@ void atascsi_disk_capacity16(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; - struct atascsi *as = link->adapter_softc; - struct atascsi_port *ap = as->as_ports[link->target]; + struct atascsi_port *ap; struct scsi_read_cap_data_16 rcd; u_int align; u_int16_t lowest_aligned = 0; + ap = atascsi_lookup_port(link); if (xs->cmdlen != sizeof(struct scsi_read_capacity_16)) { atascsi_done(xs, XS_DRIVER_STUFFUP); return; @@ -1054,6 +1201,7 @@ atascsi_passthru_12(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; struct scsi_ata_passthru_12 *cdb; struct ata_fis_h2d *fis; @@ -1071,8 +1219,9 @@ atascsi_passthru_12(struct scsi_xfer *xs) return; } + ap = atascsi_lookup_port(link); fis = xa->fis; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; fis->command = cdb->command; fis->features = cdb->features; fis->lba_low = cdb->lba_low; @@ -1080,6 +1229,7 @@ atascsi_passthru_12(struct scsi_xfer *xs) fis->lba_high = cdb->lba_high; fis->device = cdb->device; fis->sector_count = cdb->sector_count; + xa->pmp_port = ap->ap_pmp_port; ata_exec(as, xa); } @@ -1089,6 +1239,7 @@ atascsi_passthru_16(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; struct scsi_ata_passthru_16 *cdb; struct ata_fis_h2d *fis; @@ -1106,8 +1257,9 @@ atascsi_passthru_16(struct scsi_xfer *xs) return; } + ap = atascsi_lookup_port(link); fis = xa->fis; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; fis->command = cdb->command; fis->features = cdb->features[1]; fis->lba_low = cdb->lba_low[1]; @@ -1120,6 +1272,7 @@ atascsi_passthru_16(struct scsi_xfer *xs) fis->features_exp = cdb->features[0]; fis->sector_count = cdb->sector_count[1]; fis->sector_count_exp = cdb->sector_count[0]; + xa->pmp_port = ap->ap_pmp_port; ata_exec(as, xa); } @@ -1172,6 +1325,7 @@ atascsi_disk_start_stop(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; struct scsi_start_stop *ss = (struct scsi_start_stop *)xs->cmd; @@ -1190,16 +1344,18 @@ atascsi_disk_start_stop(struct scsi_xfer *xs) * zero gets translated into an ATA FLUSH CACHE command * followed by an ATA STANDBY IMMEDIATE command. */ + ap = atascsi_lookup_port(link); xa->datalen = 0; xa->flags = ATA_F_READ; xa->complete = atascsi_disk_start_stop_done; /* Spec says flush cache can take >30 sec, so give it at least 45. */ xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout; + xa->pmp_port = ap->ap_pmp_port; xa->atascsi_private = xs; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->fis->command = ATA_C_FLUSH_CACHE; xa->fis->device = 0; @@ -1212,6 +1368,7 @@ atascsi_disk_start_stop_done(struct ata_xfer *xa) struct scsi_xfer *xs = xa->atascsi_private; struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; switch (xa->state) { case ATA_S_COMPLETE: @@ -1234,17 +1391,19 @@ atascsi_disk_start_stop_done(struct ata_xfer *xa) * The FLUSH CACHE command completed succesfully; now issue * the STANDBY IMMEDATE command. */ + ap = atascsi_lookup_port(link); xa->datalen = 0; xa->flags = ATA_F_READ; xa->state = ATA_S_SETUP; xa->complete = atascsi_disk_cmd_done; /* Spec says flush cache can take >30 sec, so give it at least 45. */ xa->timeout = (xs->timeout < 45000) ? 45000 : xs->timeout; + xa->pmp_port = ap->ap_pmp_port; xa->atascsi_private = xs; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; - xa->fis->flags = ATA_H2D_FLAGS_CMD; + xa->fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; xa->fis->command = ATA_C_STANDBY_IMMED; xa->fis->device = 0; @@ -1256,6 +1415,7 @@ atascsi_atapi_cmd(struct scsi_xfer *xs) { struct scsi_link *link = xs->sc_link; struct atascsi *as = link->adapter_softc; + struct atascsi_port *ap; struct ata_xfer *xa = xs->io; struct ata_fis_h2d *fis; @@ -1269,17 +1429,20 @@ atascsi_atapi_cmd(struct scsi_xfer *xs) default: xa->flags = ATA_F_PACKET; } + xa->flags |= ATA_F_GET_RFIS; + ap = atascsi_lookup_port(link); xa->data = xs->data; xa->datalen = xs->datalen; xa->complete = atascsi_atapi_cmd_done; xa->timeout = xs->timeout; + xa->pmp_port = ap->ap_pmp_port; xa->atascsi_private = xs; if (xs->flags & SCSI_POLL) xa->flags |= ATA_F_POLL; fis = xa->fis; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | ap->ap_pmp_port; fis->command = ATA_C_PACKET; fis->device = 0; fis->sector_count = xa->tag << 3; @@ -1331,6 +1494,70 @@ atascsi_atapi_cmd_done(struct ata_xfer *xa) } void +atascsi_pmp_cmd(struct scsi_xfer *xs) +{ + switch (xs->cmd->opcode) { + case REQUEST_SENSE: + atascsi_pmp_sense(xs); + return; + case INQUIRY: + atascsi_pmp_inq(xs); + return; + + case TEST_UNIT_READY: + case PREVENT_ALLOW: + atascsi_done(xs, XS_NOERROR); + return; + + default: + atascsi_done(xs, XS_DRIVER_STUFFUP); + return; + } +} + +void +atascsi_pmp_sense(struct scsi_xfer *xs) +{ + struct scsi_sense_data *sd = (struct scsi_sense_data *)xs->data; + + bzero(xs->data, xs->datalen); + sd->error_code = SSD_ERRCODE_CURRENT; + sd->flags = SKEY_NO_SENSE; + + atascsi_done(xs, XS_NOERROR); +} + +void +atascsi_pmp_inq(struct scsi_xfer *xs) +{ + struct scsi_inquiry_data inq; + struct scsi_inquiry *in_inq = (struct scsi_inquiry *)xs->cmd; + + if (ISSET(in_inq->flags, SI_EVPD)) { + /* any evpd pages we need to support here? */ + atascsi_done(xs, XS_DRIVER_STUFFUP); + return; + } + + inq.device = 0x1E; /* "well known logical unit" seems reasonable */ + inq.version = 0x05; /* SPC-3? */ + inq.response_format = 2; + inq.additional_length = 32; + inq.flags |= SID_CmdQue; + bcopy("ATA ", inq.vendor, sizeof(inq.vendor)); + + /* should use the data from atascsi_pmp_identify here? + * not sure how useful the chip id is, but maybe it'd be + * nice to include the number of ports. + */ + bcopy("Port Multiplier", inq.product, sizeof(inq.product)); + bcopy(" ", inq.revision, sizeof(inq.revision)); + + bcopy(&inq, xs->data, MIN(sizeof(inq), xs->datalen)); + atascsi_done(xs, XS_NOERROR); +} + +void atascsi_done(struct scsi_xfer *xs, int error) { xs->error = error; @@ -1346,11 +1573,11 @@ ata_exec(struct atascsi *as, struct ata_xfer *xa) void * atascsi_io_get(void *cookie) { - struct atascsi_port *ap = cookie; - struct atascsi *as = ap->ap_as; - struct ata_xfer *xa; + struct atascsi_host_port *ahp = cookie; + struct atascsi *as = ahp->ahp_as; + struct ata_xfer *xa; - xa = as->as_methods->ata_get_xfer(as->as_cookie, ap->ap_port); + xa = as->as_methods->ata_get_xfer(as->as_cookie, ahp->ahp_port); if (xa != NULL) xa->fis->type = ATA_FIS_TYPE_H2D; @@ -1416,4 +1643,3 @@ ata_swapcopy(void *src, void *dst, size_t len) for (i = 0; i < len; i++) d[i] = swap16(s[i]); } - diff --git a/sys/dev/ata/atascsi.h b/sys/dev/ata/atascsi.h index 2f516bd142a..19e2a981baf 100644 --- a/sys/dev/ata/atascsi.h +++ b/sys/dev/ata/atascsi.h @@ -1,7 +1,9 @@ -/* $OpenBSD: atascsi.h,v 1.44 2010/09/23 11:41:54 dlg Exp $ */ +/* $OpenBSD: atascsi.h,v 1.45 2011/01/26 21:41:00 drahn Exp $ */ /* * Copyright (c) 2007 David Gwynne <dlg@openbsd.org> + * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com> + * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,6 +35,8 @@ struct scsi_link; #define ATA_C_READDMA 0xc8 #define ATA_C_WRITEDMA 0xca #define ATA_C_STANDBY_IMMED 0xe0 +#define ATA_C_READ_PM 0xe4 +#define ATA_C_WRITE_PM 0xe8 #define ATA_C_FLUSH_CACHE 0xe7 #define ATA_C_FLUSH_CACHE_EXT 0xea /* lba48 */ #define ATA_C_IDENTIFY 0xec @@ -201,6 +205,8 @@ struct ata_fis_h2d { u_int8_t sector_count_exp; u_int8_t reserved0; u_int8_t control; +#define ATA_FIS_CONTROL_SRST 0x04 +#define ATA_FIS_CONTROL_4BIT 0x08 u_int8_t reserved1; u_int8_t reserved2; @@ -302,9 +308,11 @@ struct ata_xfer { #define ATA_F_PACKET (1<<5) #define ATA_F_NCQ (1<<6) #define ATA_F_DONE (1<<7) -#define ATA_FMT_FLAGS "\020" "\007DONE" "\007NCQ" \ - "\006PACKET" "\005PIO" "\004POLL" \ - "\003NOWAIT" "\002WRITE" "\001READ" +#define ATA_F_GET_RFIS (1<<8) +#define ATA_FMT_FLAGS "\020" "\011GET_RFIS" "\010DONE" \ + "\007NCQ" "\006PACKET" "\005PIO" \ + "\004POLL" "\003NOWAIT" "\002WRITE" \ + "\001READ" volatile int state; #define ATA_S_SETUP 0 @@ -318,6 +326,8 @@ struct ata_xfer { void *atascsi_private; + int pmp_port; + void (*ata_put_xfer)(struct ata_xfer *); }; @@ -326,9 +336,9 @@ struct ata_xfer { */ struct atascsi_methods { - int (*probe)(void *, int); - void (*free)(void *, int); - struct ata_xfer * (*ata_get_xfer)(void *, int ); + int (*probe)(void *, int, int); + void (*free)(void *, int, int); + struct ata_xfer * (*ata_get_xfer)(void *, int); void (*ata_cmd)(struct ata_xfer *); }; @@ -343,16 +353,18 @@ struct atascsi_attach_args { int aaa_capability; #define ASAA_CAP_NCQ (1 << 0) #define ASAA_CAP_NEEDS_RESERVED (1 << 1) +#define ASAA_CAP_PMP_NCQ (1 << 2) }; #define ATA_PORT_T_NONE 0 #define ATA_PORT_T_DISK 1 #define ATA_PORT_T_ATAPI 2 +#define ATA_PORT_T_PM 3 struct atascsi *atascsi_attach(struct device *, struct atascsi_attach_args *); int atascsi_detach(struct atascsi *, int); -int atascsi_probe_dev(struct atascsi *, int); -int atascsi_detach_dev(struct atascsi *, int, int); +int atascsi_probe_dev(struct atascsi *, int, int); +int atascsi_detach_dev(struct atascsi *, int, int, int); void ata_complete(struct ata_xfer *); diff --git a/sys/dev/ic/sili.c b/sys/dev/ic/sili.c index f8caf97ccf2..c045c7c7280 100644 --- a/sys/dev/ic/sili.c +++ b/sys/dev/ic/sili.c @@ -1,7 +1,9 @@ -/* $OpenBSD: sili.c,v 1.46 2010/08/05 20:21:36 kettenis Exp $ */ +/* $OpenBSD: sili.c,v 1.47 2011/01/26 21:41:00 drahn Exp $ */ /* * Copyright (c) 2007 David Gwynne <dlg@openbsd.org> + * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com> + * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,6 +30,7 @@ #include <machine/bus.h> #include <dev/ata/atascsi.h> +#include <dev/ata/pmreg.h> #include <dev/ic/silireg.h> #include <dev/ic/silivar.h> @@ -46,6 +49,16 @@ int silidebug = SILI_D_VERBOSE; #define DPRINTF(m, a...) #endif +/* these can be used to simulate read and write errors on specific PMP ports */ +#undef SILI_ERROR_TEST +int sili_error_pmp_ports = 0; /* bitmask containing ports to fail*/ +int sili_error_test_inv_p = 500; /* 1/P(error) */ +int sili_error_restart_type = SILI_PREG_PCS_PORTINIT; /* or _DEVRESET */ + +#ifdef SILI_ERROR_TEST +#include <dev/rndvar.h> +#endif + struct cfdriver sili_cd = { NULL, "sili", DV_DULL }; @@ -87,6 +100,13 @@ struct sili_port { TAILQ_HEAD(, sili_ccb) sp_active_ccbs; TAILQ_HEAD(, sili_ccb) sp_deferred_ccbs; + int sp_port; + int sp_pmp_ports; + int sp_active_pmp_ports; + int sp_pmp_error_recovery; /* port bitmask */ + volatile u_int32_t sp_err_active; /* cmd bitmask */ + volatile u_int32_t sp_err_cmds; /* cmd bitmask */ + #ifdef SILI_DEBUG char sp_name[16]; #define PORTNAME(_sp) ((_sp)->sp_name) @@ -150,21 +170,42 @@ void sili_post_indirect(struct sili_port *, void sili_pread_fis(struct sili_port *, u_int, struct ata_fis_d2h *); u_int32_t sili_signature(struct sili_port *, u_int); +u_int32_t sili_port_softreset(struct sili_port *sp); int sili_load(struct sili_ccb *, struct sili_sge *, int); void sili_unload(struct sili_ccb *); int sili_poll(struct sili_ccb *, int, void (*)(void *)); void sili_start(struct sili_port *, struct sili_ccb *); -int sili_read_ncq_error(struct sili_port *, int *); +int sili_read_ncq_error(struct sili_port *, int *, int); +int sili_pmp_port_start_error_recovery(struct sili_port *, + int); +void sili_pmp_port_do_error_recovery(struct sili_port *, + int, u_int32_t *); +void sili_port_clear_commands(struct sili_port *sp); + +/* pmp operations */ +int sili_pmp_read(struct sili_port *, int, int, + u_int32_t *); +int sili_pmp_write(struct sili_port *, int, int, u_int32_t); +int sili_pmp_phy_status(struct sili_port *, int, + u_int32_t *); +int sili_pmp_identify(struct sili_port *, int *); /* port interrupt handler */ u_int32_t sili_port_intr(struct sili_port *, int); /* atascsi interface */ -int sili_ata_probe(void *, int); -void sili_ata_free(void *, int); +int sili_ata_probe(void *, int, int); +void sili_ata_free(void *, int, int); struct ata_xfer *sili_ata_get_xfer(void *, int); void sili_ata_put_xfer(struct ata_xfer *); void sili_ata_cmd(struct ata_xfer *); +int sili_pmp_portreset(struct sili_softc *, int, int); +int sili_pmp_softreset(struct sili_softc *, int, int); + +#ifdef SILI_ERROR_TEST +void sili_simulate_error(struct sili_ccb *ccb, + int *need_restart, int *err_port); +#endif struct atascsi_methods sili_atascsi_methods = { sili_ata_probe, @@ -176,6 +217,9 @@ struct atascsi_methods sili_atascsi_methods = { /* completion paths */ void sili_ata_cmd_done(struct sili_ccb *, int); void sili_ata_cmd_timeout(void *); +void sili_dummy_done(struct ata_xfer *); + +void sili_pmp_op_timeout(void *); int sili_attach(struct sili_softc *sc) @@ -199,7 +243,7 @@ sili_attach(struct sili_softc *sc) aaa.aaa_minphys = NULL; aaa.aaa_nports = sc->sc_nports; aaa.aaa_ncmds = SILI_MAX_CMDS; - aaa.aaa_capability = ASAA_CAP_NCQ; + aaa.aaa_capability = ASAA_CAP_NCQ | ASAA_CAP_PMP_NCQ; sc->sc_atascsi = atascsi_attach(&sc->sc_dev, &aaa); @@ -226,21 +270,145 @@ sili_detach(struct sili_softc *sc, int flags) void sili_resume(struct sili_softc *sc) { - int i; + int i, j; /* bounce the controller */ sili_write(sc, SILI_REG_GC, SILI_REG_GC_GR); sili_write(sc, SILI_REG_GC, 0x0); - for (i = 0; i < sc->sc_nports; i++) - sili_ata_probe(sc, i); + for (i = 0; i < sc->sc_nports; i++) { + if (sili_ata_probe(sc, i, 0) == ATA_PORT_T_PM) { + struct sili_port *sp = &sc->sc_ports[i]; + for (j = 0; j < sp->sp_pmp_ports; j++) { + sili_ata_probe(sc, i, j); + } + } + } +} + +int +sili_pmp_port_start_error_recovery(struct sili_port *sp, int err_port) +{ + struct sili_ccb *ccb; + + sp->sp_pmp_error_recovery |= (1 << err_port); + + /* create a bitmask of active commands on non-error ports */ + sp->sp_err_active = 0; + TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) { + int bit = (1 << ccb->ccb_xa.pmp_port); + if ((sp->sp_pmp_error_recovery & bit) == 0) { + DPRINTF(SILI_D_VERBOSE, "%s: slot %d active on port " + "%d\n", PORTNAME(sp), ccb->ccb_xa.tag, + ccb->ccb_xa.pmp_port); + sp->sp_err_active |= (1 << ccb->ccb_xa.tag); + } + } + + if (sp->sp_err_active == 0) { + DPRINTF(SILI_D_VERBOSE, "%s: no other PMP ports active\n", + PORTNAME(sp)); + sp->sp_pmp_error_recovery = 0; + return (0); + } + + /* set port resume */ + sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_RESUME); + + DPRINTF(SILI_D_VERBOSE, "%s: beginning error recovery (port %d); " + "error port mask %x, active slot mask %x\n", PORTNAME(sp), err_port, + sp->sp_pmp_error_recovery, sp->sp_err_active); + return (1); } +void +sili_port_clear_commands(struct sili_port *sp) +{ + int port; + + DPRINTF(SILI_D_VERBOSE, "%s: clearing active commands\n", + PORTNAME(sp)); + + /* clear port resume */ + sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_RESUME); + delay(10000); + + /* clear port status and port active for all ports */ + for (port = 0; port < 16; port++) { + sili_pwrite(sp, SILI_PREG_PMP_STATUS(port), 0); + sili_pwrite(sp, SILI_PREG_PMP_QACTIVE(port), 0); + } +} + +void +sili_pmp_port_do_error_recovery(struct sili_port *sp, int slot, + u_int32_t *need_restart) +{ + if (sp->sp_pmp_error_recovery == 0) { + return; + } + + /* have all outstanding commands finished yet? */ + if (sp->sp_err_active != 0) { + DPRINTF(SILI_D_VERBOSE, "%s: PMP error recovery still waiting " + "for %x\n", PORTNAME(sp), sp->sp_err_active); + *need_restart = 0; + return; + } + + sili_port_clear_commands(sp); + + /* get the main error recovery code to reset the port and + * resubmit commands. it will also reset the error recovery flags. + */ + *need_restart = SILI_PREG_PCS_PORTINIT; + DPRINTF(SILI_D_VERBOSE, "%s: PMP error recovery complete\n", + PORTNAME(sp)); +} + +#ifdef SILI_ERROR_TEST +void +sili_simulate_error(struct sili_ccb *ccb, int *need_restart, int *err_port) +{ + struct sili_port *sp = ccb->ccb_port; + + if (*need_restart == 0 && + ((1 << ccb->ccb_xa.pmp_port) & sili_error_pmp_ports)) { + switch (ccb->ccb_xa.fis->command) { + case ATA_C_WRITE_FPDMA: + case ATA_C_READ_FPDMA: + case ATA_C_WRITEDMA_EXT: + case ATA_C_READDMA_EXT: + case ATA_C_WRITEDMA: + case ATA_C_READDMA: + if ((arc4random() % sili_error_test_inv_p) == 0) { + printf("%s: faking error on slot %d\n", + PORTNAME(sp), ccb->ccb_xa.tag); + ccb->ccb_xa.state = ATA_S_ERROR; + *need_restart = sili_error_restart_type; + *err_port = ccb->ccb_xa.pmp_port; + + ccb->ccb_port->sp_err_cmds |= + (1 << ccb->ccb_xa.tag); + } + break; + + default: + /* leave other commands alone, we only want to mess + * with normal read/write ops + */ + break; + } + } +} +#endif + u_int32_t sili_port_intr(struct sili_port *sp, int timeout_slot) { u_int32_t is, pss_saved, pss_masked; u_int32_t processed = 0, need_restart = 0; + u_int32_t err_port = 0; int slot; struct sili_ccb *ccb; @@ -273,29 +441,63 @@ sili_port_intr(struct sili_port *sp, int timeout_slot) case SILI_PREG_CE_DATAFISERROR: /* Extract error from command slot in LRAM. */ sili_pread_fis(sp, err_slot, &ccb->ccb_xa.rfis); + err_port = ccb->ccb_xa.pmp_port; break; case SILI_PREG_CE_SDBERROR: - /* No NCQ commands active? Treat as a normal error. */ - sactive = sili_pread(sp, SILI_PREG_SACT);/* XXX Pmult */ - if (sactive == 0) - break; - /* Extract real NCQ error slot & RFIS from log page. */ - if (!sili_read_ncq_error(sp, &err_slot)) { + if (sp->sp_pmp_ports > 0) { + /* get the PMP port number for the error */ + err_port = (sili_pread(sp, SILI_PREG_CONTEXT) + >> SILI_PREG_CONTEXT_PMPORT_SHIFT) & + SILI_PREG_CONTEXT_PMPORT_MASK; + DPRINTF(SILI_D_VERBOSE, "%s: error port is " + "%d\n", PORTNAME(sp), err_port); + + /* were there any NCQ commands active for + * the port? + */ + sactive = sili_pread(sp, + SILI_PREG_PMP_QACTIVE(err_port)); + DPRINTF(SILI_D_VERBOSE, "%s: error SActive " + "%x\n", PORTNAME(sp), sactive); + if (sactive == 0) + break; + } else { + /* No NCQ commands active? Treat as a normal + * error. + */ + sactive = sili_pread(sp, SILI_PREG_SACT); + if (sactive == 0) + break; + } + + /* Extract real NCQ error slot & RFIS from + * log page. + */ + if (!sili_read_ncq_error(sp, &err_slot, err_port)) { /* got real err_slot */ + DPRINTF(SILI_D_VERBOSE, "%s.%d: error slot " + "%d\n", PORTNAME(sp), err_port, err_slot); ccb = &sp->sp_ccbs[err_slot]; break; } + DPRINTF(SILI_D_VERBOSE, "%s.%d: failed to get error " + "slot\n", PORTNAME(sp), err_port); /* failed to get error or not NCQ */ /* FALLTHROUGH */ default: /* All other error types are fatal. */ - printf("%s: fatal error (%d), aborting active slots " + if (err_code != SILI_PREG_CE_SDBERROR) { + err_port = (sili_pread(sp, SILI_PREG_CONTEXT) + >> SILI_PREG_CONTEXT_PMPORT_SHIFT) & + SILI_PREG_CONTEXT_PMPORT_MASK; + } + printf("%s.%d: fatal error (%d), aborting active slots " "(%08x) and resetting device.\n", PORTNAME(sp), - err_code, pss_saved); + err_port, err_code, pss_saved); while (pss_saved) { slot = ffs(pss_saved) - 1; pss_saved &= ~(1 << slot); @@ -308,12 +510,14 @@ sili_port_intr(struct sili_port *sp, int timeout_slot) goto fatal; } - DPRINTF(SILI_D_VERBOSE, "%s: %serror, code %d, slot %d, " - "active %08x\n", PORTNAME(sp), sactive ? "NCQ " : "", - err_code, err_slot, sp->sp_active); + DPRINTF(SILI_D_VERBOSE, "%s.%d: %serror, code %d, slot %d, " + "active %08x\n", PORTNAME(sp), err_port, + sactive ? "NCQ " : "", err_code, err_slot, sp->sp_active); /* Clear the failed commmand in saved PSS so cmd_done runs. */ pss_saved &= ~(1 << err_slot); + /* Track errored commands until we finish recovery */ + sp->sp_err_cmds |= (1 << err_slot); KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP); ccb->ccb_xa.state = ATA_S_ERROR; @@ -334,8 +538,13 @@ fatal: KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP); ccb->ccb_xa.state = ATA_S_TIMEOUT; - /* Reset device to abort all commands (including this one). */ - need_restart = SILI_PREG_PCS_DEVRESET; + /* Reinitialise the port and clear all active commands */ + need_restart = SILI_PREG_PCS_PORTINIT; + + err_port = ccb->ccb_xa.pmp_port; + sp->sp_err_cmds |= (1 << timeout_slot); + + sili_port_clear_commands(sp); } /* Command slot is complete if its bit in PSS is 0 but 1 in active. */ @@ -345,31 +554,79 @@ fatal: ccb = &sp->sp_ccbs[slot]; pss_masked &= ~(1 << slot); - DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s\n", + /* copy the rfis into the ccb if we were asked for it */ + if (ccb->ccb_xa.state == ATA_S_ONCHIP && + ccb->ccb_xa.flags & ATA_F_GET_RFIS) { + sili_pread_fis(sp, slot, &ccb->ccb_xa.rfis); + } + +#ifdef SILI_ERROR_TEST + /* introduce random errors on reads and writes for testing */ + sili_simulate_error(ccb, &need_restart, &err_port); +#endif + + DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s%s\n", PORTNAME(sp), slot, ccb->ccb_xa.state == ATA_S_ERROR ? " (error)" : (ccb->ccb_xa.state == ATA_S_TIMEOUT ? - " (timeout)" : "")); + " (timeout)" : ""), + ccb->ccb_xa.flags & ATA_F_NCQ ? " (ncq)" : ""); sili_ata_cmd_done(ccb, need_restart); processed |= 1 << slot; + + sili_pmp_port_do_error_recovery(sp, slot, &need_restart); } if (need_restart) { + + if (sp->sp_pmp_error_recovery) { + if (sp->sp_err_active != 0) { + DPRINTF(SILI_D_VERBOSE, "%s: still waiting for " + "non-error commands to finish; port mask " + "%x, slot mask %x\n", PORTNAME(sp), + sp->sp_pmp_error_recovery, + sp->sp_err_active); + return (processed); + } + } else if (timeout_slot < 0 && sp->sp_pmp_ports > 0) { + /* wait until all other commands have finished before + * attempting to reinit the port. + */ + DPRINTF(SILI_D_VERBOSE, "%s: error on port with PMP " + "attached, error port %d\n", PORTNAME(sp), + err_port); + if (sili_pmp_port_start_error_recovery(sp, err_port)) { + DPRINTF(SILI_D_VERBOSE, "%s: need to wait for " + "other commands to finish\n", PORTNAME(sp)); + return (processed); + } + } else if (sp->sp_pmp_ports > 0) { + DPRINTF(SILI_D_VERBOSE, "%s: timeout on PMP port\n", + PORTNAME(sp)); + } else { + DPRINTF(SILI_D_VERBOSE, "%s: error on non-PMP port\n", + PORTNAME(sp)); + } + /* Re-enable transfers on port. */ sili_pwrite(sp, SILI_PREG_PCS, need_restart); + if (!sili_pwait_eq(sp, SILI_PREG_PCS, need_restart, 0, 5000)) { + printf("%s: port reset bit didn't clear after error\n", + PORTNAME(sp)); + } if (!sili_pwait_eq(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRDY, SILI_PREG_PCS_PORTRDY, 1000)) { printf("%s: couldn't restart port after error\n", PORTNAME(sp)); } + sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_RESUME); - /* Restart CCBs in the order they were originally queued. */ - pss_masked = pss_saved; + /* check that our active CCB list matches the restart mask */ + pss_masked = pss_saved & ~(sp->sp_err_cmds); + DPRINTF(SILI_D_VERBOSE, "%s: restart mask %x\n", + PORTNAME(sp), pss_masked); TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) { - DPRINTF(SILI_D_VERBOSE, "%s: restarting slot %d " - "after error, state %02x\n", PORTNAME(sp), - ccb->ccb_xa.tag, ccb->ccb_xa.state); if (!(pss_masked & (1 << ccb->ccb_xa.tag))) { panic("sili_intr: slot %d not active in " "pss_masked: %08x, state %02x", @@ -377,12 +634,69 @@ fatal: ccb->ccb_xa.state); } pss_masked &= ~(1 << ccb->ccb_xa.tag); + } + if (pss_masked != 0) { + printf("%s: mask excluding active slots: %x\n", + PORTNAME(sp), pss_masked); + } + KASSERT(pss_masked == 0); + + /* if we had a timeout on a PMP port, do a portreset. + * exclude the control port here as there isn't a real + * device there to reset. + */ + if (timeout_slot >= 0 && sp->sp_pmp_ports > 0 && + err_port != 15) { + + DPRINTF(SILI_D_VERBOSE, + "%s.%d: doing portreset after timeout\n", + PORTNAME(sp), err_port); + sili_pmp_portreset(sp->sp_sc, sp->sp_port, err_port); + + /* wait a bit to let the port settle down */ + delay(2000000); + } + + /* if we sent a device reset to a PMP, we need to reset the + * devices behind it too. + */ + if (need_restart == SILI_PREG_PCS_DEVRESET && + sp->sp_pmp_ports > 0) { + int port_type; + int i; + + port_type = sili_port_softreset(sp); + if (port_type != ATA_PORT_T_PM) { + /* device disappeared or changed type? */ + printf("%s: expected to find a port multiplier," + " got %d\n", PORTNAME(sp), port_type); + } + + /* and now portreset all active ports */ + for (i = 0; i < sp->sp_pmp_ports; i++) { + struct sili_softc *sc = sp->sp_sc; + if ((sp->sp_active_pmp_ports & (1 << i)) == 0) + continue; + + if (sili_pmp_portreset(sc, sp->sp_port, i)) { + printf("%s.%d: failed to portreset " + "after error\n", PORTNAME(sp), i); + } + } + } + + /* Restart CCBs in the order they were originally queued. */ + TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) { + DPRINTF(SILI_D_VERBOSE, "%s: restarting slot %d " + "after error, state %02x\n", PORTNAME(sp), + ccb->ccb_xa.tag, ccb->ccb_xa.state); KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP); sili_post_indirect(sp, ccb); } - KASSERT(pss_masked == 0); - + sp->sp_err_cmds = 0; + sp->sp_pmp_error_recovery = 0; + /* * Finally, run atascsi completion for any finished CCBs. If * we had run these during cmd_done above, any ccbs that their @@ -391,6 +705,9 @@ fatal: while ((ccb = TAILQ_FIRST(&sp->sp_deferred_ccbs)) != NULL) { TAILQ_REMOVE(&sp->sp_deferred_ccbs, ccb, ccb_entry); + DPRINTF(SILI_D_VERBOSE, "%s: running deferred " + "completion for slot %d, state %02x\n", + PORTNAME(sp), ccb->ccb_xa.tag, ccb->ccb_xa.state); KASSERT(ccb->ccb_xa.state == ATA_S_COMPLETE || ccb->ccb_xa.state == ATA_S_ERROR || ccb->ccb_xa.state == ATA_S_TIMEOUT); @@ -437,6 +754,7 @@ sili_ports_alloc(struct sili_softc *sc) sp = &sc->sc_ports[i]; sp->sp_sc = sc; + sp->sp_port = i; #ifdef SILI_DEBUG snprintf(sp->sp_name, sizeof(sp->sp_name), "%s.%d", DEVNAME(sc), i); @@ -550,6 +868,13 @@ sili_get_ccb(struct sili_port *sp) { struct sili_ccb *ccb; + /* don't allow new commands to start while doing PMP error + * recovery + */ + if (sp->sp_pmp_error_recovery != 0) { + return (NULL); + } + mtx_enter(&sp->sp_free_ccb_mtx); ccb = TAILQ_FIRST(&sp->sp_free_ccbs); if (ccb != NULL) { @@ -753,28 +1078,193 @@ sili_signature(struct sili_port *sp, u_int slot) return (sig_hi | sig_lo); } +void +sili_dummy_done(struct ata_xfer *xa) +{ +} + int -sili_ata_probe(void *xsc, int port) +sili_pmp_portreset(struct sili_softc *sc, int port, int pmp_port) { - struct sili_softc *sc = xsc; - struct sili_port *sp = &sc->sc_ports[port]; - struct sili_prb_softreset sreset; - u_int32_t signature; - int port_type; + struct sili_port *sp; + u_int32_t data; + int loop; + + sp = &sc->sc_ports[port]; + DPRINTF(SILI_D_VERBOSE, "%s: resetting pmp port %d\n", PORTNAME(sp), + pmp_port); + + if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1)) + goto err; + if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL, + SATA_PM_SCTL_IPM_DISABLED)) + goto err; + delay(10000); + + /* enable PHY by writing 1 then 0 to Scontrol DET field, using + * Write Port Multiplier commands + */ + data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_INIT | + SATA_PM_SCTL_SPD_ANY; + if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL, data)) + goto err; + delay(100000); + + if (sili_pmp_phy_status(sp, pmp_port, &data)) { + printf("%s: cannot clear phy status for PMP probe\n", + PORTNAME(sp)); + goto err; + } + + sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1); + data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_NONE; + if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL, data)) + goto err; + delay(100000); + + /* wait for PHYRDY by polling SStatus */ + for (loop = 3; loop; loop--) { + if (sili_pmp_read(sp, pmp_port, SATA_PMREG_SSTS, &data)) + goto err; + if (data & SATA_PM_SSTS_DET) + break; + delay(100000); + } + if (loop == 0) { + DPRINTF(SILI_D_VERBOSE, "%s.%d: port appears to be unplugged\n", + PORTNAME(sp), pmp_port); + goto err; + } + + /* give it a bit more time to complete negotiation */ + for (loop = 30; loop; loop--) { + if (sili_pmp_read(sp, pmp_port, SATA_PMREG_SSTS, &data)) + goto err; + if ((data & SATA_PM_SSTS_DET) == SATA_PM_SSTS_DET_DEV) + break; + delay(10000); + } + if (loop == 0) { + printf("%s.%d: device may be powered down\n", PORTNAME(sp), + pmp_port); + goto err; + } - sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_PORTRESET); - sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_A32B); + DPRINTF(SILI_D_VERBOSE, "%s.%d: device detected; SStatus=%08x\n", + PORTNAME(sp), pmp_port, data); - if (!sili_pwait_eq(sp, SILI_PREG_SSTS, SATA_SStatus_DET, - SATA_SStatus_DET_DEV, 1000)) + /* clear the X-bit and all other error bits in Serror (PCSR[1]) */ + sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1); + return (0); + +err: + DPRINTF(SILI_D_VERBOSE, "%s.%d: port reset failed\n", PORTNAME(sp), + pmp_port); + sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1); + return (1); +} + +void +sili_pmp_op_timeout(void *cookie) +{ + struct sili_ccb *ccb = cookie; + struct sili_port *sp = ccb->ccb_port; + int s; + + switch (ccb->ccb_xa.state) { + case ATA_S_PENDING: + TAILQ_REMOVE(&sp->sp_active_ccbs, ccb, ccb_entry); + ccb->ccb_xa.state = ATA_S_TIMEOUT; + break; + case ATA_S_ONCHIP: + KASSERT(sp->sp_active == (1 << ccb->ccb_xa.tag)); + s = splbio(); + sili_port_intr(sp, ccb->ccb_xa.tag); + splx(s); + break; + case ATA_S_ERROR: + /* don't do anything? */ + break; + default: + panic("%s: sili_pmp_op_timeout: ccb in bad state %d", + PORTNAME(sp), ccb->ccb_xa.state); + } +} + +int +sili_pmp_softreset(struct sili_softc *sc, int port, int pmp_port) +{ + struct sili_ccb *ccb; + struct sili_prb *prb; + struct sili_port *sp; + struct ata_fis_h2d *fis; + u_int32_t data; + u_int32_t signature; + + sp = &sc->sc_ports[port]; + + ccb = sili_get_ccb(sp); + ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_GET_RFIS; + ccb->ccb_xa.complete = sili_dummy_done; + ccb->ccb_xa.pmp_port = pmp_port; + + prb = ccb->ccb_cmd; + bzero(prb, sizeof(*prb)); + fis = (struct ata_fis_h2d *)&prb->fis; + fis->flags = pmp_port; + prb->control = SILI_PRB_SOFT_RESET; + + ccb->ccb_xa.state = ATA_S_PENDING; + + if (sili_poll(ccb, 8000, sili_pmp_op_timeout) != 0) { + DPRINTF(SILI_D_VERBOSE, "%s.%d: softreset FIS failed\n", + PORTNAME(sp), pmp_port); + + sili_put_ccb(ccb); + /* don't return a valid device type here so the caller knows + * it can retry if it wants to + */ + return (-1); + } + + signature = ccb->ccb_xa.rfis.sector_count | + (ccb->ccb_xa.rfis.lba_low << 8) | + (ccb->ccb_xa.rfis.lba_mid << 16) | + (ccb->ccb_xa.rfis.lba_high << 24); + DPRINTF(SILI_D_VERBOSE, "%s.%d: signature: %08x\n", PORTNAME(sp), + pmp_port, signature); + + sili_put_ccb(ccb); + + /* clear phy status and error bits */ + if (sili_pmp_phy_status(sp, pmp_port, &data)) { + printf("%s.%d: cannot clear phy status after softreset\n", + PORTNAME(sp), pmp_port); + } + sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1); + + /* classify the device based on its signature */ + switch (signature) { + case SATA_SIGNATURE_DISK: + return (ATA_PORT_T_DISK); + case SATA_SIGNATURE_ATAPI: + return (ATA_PORT_T_ATAPI); + case SATA_SIGNATURE_PORT_MULTIPLIER: + return (ATA_PORT_T_NONE); + default: return (ATA_PORT_T_NONE); + } +} - DPRINTF(SILI_D_VERBOSE, "%s: SSTS 0x%08x\n", PORTNAME(sp), - sili_pread(sp, SILI_PREG_SSTS)); +u_int32_t +sili_port_softreset(struct sili_port *sp) +{ + struct sili_prb_softreset sreset; + u_int32_t signature; bzero(&sreset, sizeof(sreset)); sreset.control = htole16(SILI_PRB_SOFT_RESET | SILI_PRB_INTERRUPT_MASK); - /* XXX sreset fis pmp field */ + sreset.fis[1] = SATA_PMP_CONTROL_PORT; /* we use slot 0 */ sili_post_direct(sp, 0, &sreset, sizeof(sreset)); @@ -792,20 +1282,110 @@ sili_ata_probe(void *xsc, int port) switch (signature) { case SATA_SIGNATURE_DISK: - port_type = ATA_PORT_T_DISK; - break; + return (ATA_PORT_T_DISK); case SATA_SIGNATURE_ATAPI: - port_type = ATA_PORT_T_ATAPI; - break; + return (ATA_PORT_T_ATAPI); case SATA_SIGNATURE_PORT_MULTIPLIER: + return (ATA_PORT_T_PM); default: return (ATA_PORT_T_NONE); } +} + +int +sili_ata_probe(void *xsc, int port, int lun) +{ + struct sili_softc *sc = xsc; + struct sili_port *sp = &sc->sc_ports[port]; + int port_type; + + /* handle pmp port probes */ + if (lun != 0) { + int i; + int rc; + int pmp_port = lun - 1; + + if (lun > sp->sp_pmp_ports) + return (ATA_PORT_T_NONE); + + for (i = 0; i < 2; i++) { + if (sili_pmp_portreset(sc, port, pmp_port)) { + continue; + } + + /* small delay between attempts to allow error + * conditions to settle down. this doesn't seem + * to affect portreset operations, just + * commands sent to the device. + */ + if (i != 0) { + delay(5000000); + } + + rc = sili_pmp_softreset(sc, port, pmp_port); + switch (rc) { + case -1: + /* possibly try again */ + break; + case ATA_PORT_T_DISK: + case ATA_PORT_T_ATAPI: + /* mark this port as active */ + sp->sp_active_pmp_ports |= (1 << pmp_port); + default: + return (rc); + } + } + DPRINTF(SILI_D_VERBOSE, "%s.%d: probe failed\n", PORTNAME(sp), + pmp_port); + return (ATA_PORT_T_NONE); + } + + sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRESET); + delay(10000); + sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_PORTRESET); + + sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTINIT); + if (!sili_pwait_eq(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRDY, + SILI_PREG_PCS_PORTRDY, 1000)) { + printf("%s: couldn't initialize port\n", PORTNAME(sp)); + return (ATA_PORT_T_NONE); + } + + sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_A32B); + + if (!sili_pwait_eq(sp, SILI_PREG_SSTS, SATA_SStatus_DET, + SATA_SStatus_DET_DEV, 2000)) { + DPRINTF(SILI_D_VERBOSE, "%s: unattached\n", PORTNAME(sp)); + return (ATA_PORT_T_NONE); + } + + DPRINTF(SILI_D_VERBOSE, "%s: SSTS 0x%08x\n", PORTNAME(sp), + sili_pread(sp, SILI_PREG_SSTS)); + + port_type = sili_port_softreset(sp); + if (port_type == ATA_PORT_T_NONE) + return (port_type); /* allocate port resources */ if (sili_ccb_alloc(sp) != 0) return (ATA_PORT_T_NONE); + /* do PMP probe now that we can talk to the device */ + if (port_type == ATA_PORT_T_PM) { + int i; + + sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PMEN); + + if (sili_pmp_identify(sp, &sp->sp_pmp_ports)) { + return (ATA_PORT_T_NONE); + } + + /* reset all the PMP ports to wake devices up */ + for (i = 0; i < sp->sp_pmp_ports; i++) { + sili_pmp_portreset(sp->sp_sc, sp->sp_port, i); + } + } + /* enable port interrupts */ sili_write(sc, SILI_REG_GC, sili_read(sc, SILI_REG_GC) | 1 << port); sili_pwrite(sp, SILI_PREG_IES, SILI_PREG_IE_CMDERR | @@ -815,15 +1395,17 @@ sili_ata_probe(void *xsc, int port) } void -sili_ata_free(void *xsc, int port) +sili_ata_free(void *xsc, int port, int lun) { struct sili_softc *sc = xsc; struct sili_port *sp = &sc->sc_ports[port]; - if (sp->sp_ccbs != NULL) - sili_ccb_free(sp); + if (lun == 0) { + if (sp->sp_ccbs != NULL) + sili_ccb_free(sp); - /* XXX we should do more here */ + /* XXX we should do more here */ + } } void @@ -838,7 +1420,7 @@ sili_ata_cmd(struct ata_xfer *xa) int sgllen; int s; - KASSERT(xa->state == ATA_S_SETUP); + KASSERT(xa->state == ATA_S_SETUP || xa->state == ATA_S_TIMEOUT); if (xa->flags & ATA_F_PACKET) { atapi = ccb->ccb_cmd; @@ -905,6 +1487,11 @@ sili_ata_cmd_done(struct sili_ccb *ccb, int defer_completion) TAILQ_REMOVE(&sp->sp_active_ccbs, ccb, ccb_entry); sp->sp_active &= ~(1 << xa->tag); + if (sp->sp_err_active & (1 << xa->tag)) { + sp->sp_err_active &= ~(1 << xa->tag); + DPRINTF(SILI_D_VERBOSE, "%s: slot %d complete, error mask now " + "%x\n", PORTNAME(sp), xa->tag, sp->sp_err_active); + } if (xa->state == ATA_S_ONCHIP) xa->state = ATA_S_COMPLETE; @@ -1031,7 +1618,7 @@ sili_poll(struct sili_ccb *ccb, int timeout, void (*timeout_fn)(void *)) do { if (sili_port_intr(sp, -1) & (1 << ccb->ccb_xa.tag)) { splx(s); - return (0); + return (ccb->ccb_xa.state != ATA_S_COMPLETE); } delay(1000); @@ -1053,6 +1640,7 @@ sili_start(struct sili_port *sp, struct sili_ccb *ccb) splassert(IPL_BIO); KASSERT(ccb->ccb_xa.state == ATA_S_PENDING); + KASSERT(sp->sp_pmp_error_recovery == 0); TAILQ_INSERT_TAIL(&sp->sp_active_ccbs, ccb, ccb_entry); sp->sp_active |= 1 << slot; @@ -1062,7 +1650,7 @@ sili_start(struct sili_port *sp, struct sili_ccb *ccb) } int -sili_read_ncq_error(struct sili_port *sp, int *err_slotp) +sili_read_ncq_error(struct sili_port *sp, int *err_slotp, int pmp_port) { struct sili_softc *sc = sp->sp_sc; struct sili_prb_ata read_10h; @@ -1092,7 +1680,7 @@ sili_read_ncq_error(struct sili_port *sp, int *err_slotp) fis = (struct ata_fis_h2d *)read_10h.fis; fis->type = ATA_FIS_TYPE_H2D; - fis->flags = ATA_H2D_FLAGS_CMD; /* XXX fis pmp field */ + fis->flags = ATA_H2D_FLAGS_CMD | pmp_port; fis->command = ATA_C_READ_LOG_EXT; fis->lba_low = 0x10; /* queued error log page (10h) */ fis->sector_count = 1; /* number of sectors (1) */ @@ -1146,7 +1734,6 @@ sili_ata_get_xfer(void *xsc, int port) ccb = sili_get_ccb(sp); if (ccb == NULL) { - printf("sili_ata_get_xfer: NULL ccb\n"); return (NULL); } @@ -1162,3 +1749,132 @@ sili_ata_put_xfer(struct ata_xfer *xa) sili_put_ccb(ccb); } + +/* PMP register ops */ +int +sili_pmp_read(struct sili_port *sp, int target, int which, u_int32_t *datap) +{ + struct sili_ccb *ccb; + struct sili_prb *prb; + struct ata_fis_h2d *fis; + int error; + + ccb = sili_get_ccb(sp); + if (ccb == NULL) { + printf("%s: NULL ccb!\n", PORTNAME(sp)); + return (1); + } + ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_GET_RFIS; + ccb->ccb_xa.complete = sili_dummy_done; + ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT; + ccb->ccb_xa.state = ATA_S_PENDING; + + prb = ccb->ccb_cmd; + bzero(prb, sizeof(*prb)); + fis = (struct ata_fis_h2d *)&prb->fis; + fis->type = ATA_FIS_TYPE_H2D; + fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT; + fis->command = ATA_C_READ_PM; + fis->features = which; + fis->device = target | ATA_H2D_DEVICE_LBA; + fis->control = ATA_FIS_CONTROL_4BIT; + + if (sili_poll(ccb, 1000, sili_pmp_op_timeout) != 0) { + printf("sili_pmp_read(%d, %d) failed\n", target, which); + error = 1; + } else { + *datap = ccb->ccb_xa.rfis.sector_count | + (ccb->ccb_xa.rfis.lba_low << 8) | + (ccb->ccb_xa.rfis.lba_mid << 16) | + (ccb->ccb_xa.rfis.lba_high << 24); + error = 0; + } + sili_put_ccb(ccb); + return (error); +} + +int +sili_pmp_write(struct sili_port *sp, int target, int which, u_int32_t data) +{ + struct sili_ccb *ccb; + struct sili_prb *prb; + struct ata_fis_h2d *fis; + int error; + + ccb = sili_get_ccb(sp); + if (ccb == NULL) { + printf("%s: NULL ccb!\n", PORTNAME(sp)); + return (1); + } + ccb->ccb_xa.complete = sili_dummy_done; + ccb->ccb_xa.flags = ATA_F_POLL; + ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT; + ccb->ccb_xa.state = ATA_S_PENDING; + + prb = ccb->ccb_cmd; + bzero(prb, sizeof(*prb)); + fis = (struct ata_fis_h2d *)&prb->fis; + fis->type = ATA_FIS_TYPE_H2D; + fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT; + fis->command = ATA_C_WRITE_PM; + fis->features = which; + fis->device = target | ATA_H2D_DEVICE_LBA; + fis->sector_count = (u_int8_t)data; + fis->lba_low = (u_int8_t)(data >> 8); + fis->lba_mid = (u_int8_t)(data >> 16); + fis->lba_high = (u_int8_t)(data >> 24); + fis->control = ATA_FIS_CONTROL_4BIT; + + error = sili_poll(ccb, 1000, sili_pmp_op_timeout); + sili_put_ccb(ccb); + return (error); +} + +int +sili_pmp_phy_status(struct sili_port *sp, int target, u_int32_t *datap) +{ + int error; + + error = sili_pmp_read(sp, target, SATA_PMREG_SSTS, datap); + if (error == 0) + error = sili_pmp_write(sp, target, SATA_PMREG_SERR, -1); + if (error) + *datap = 0; + + return (error); +} + +int +sili_pmp_identify(struct sili_port *sp, int *ret_nports) +{ + u_int32_t chipid; + u_int32_t rev; + u_int32_t nports; + u_int32_t features; + u_int32_t enabled; + + if (sili_pmp_read(sp, 15, 0, &chipid) || + sili_pmp_read(sp, 15, 1, &rev) || + sili_pmp_read(sp, 15, 2, &nports) || + sili_pmp_read(sp, 15, SATA_PMREG_FEA, &features) || + sili_pmp_read(sp, 15, SATA_PMREG_FEAEN, &enabled)) { + printf("%s: port multiplier identification failed\n", + PORTNAME(sp)); + return (1); + } + + nports &= 0x0F; + + /* ignore SEMB port on SiI3726 port multiplier chips */ + if (chipid == 0x37261095) { + nports--; + } + + printf("%s: port multiplier found: chip=%08x rev=0x%b nports=%d, " + "features: 0x%b, enabled: 0x%b\n", PORTNAME(sp), chipid, rev, + SATA_PFMT_PM_REV, nports, features, SATA_PFMT_PM_FEA, enabled, + SATA_PFMT_PM_FEA); + + *ret_nports = nports; + return (0); +} diff --git a/sys/dev/ic/silireg.h b/sys/dev/ic/silireg.h index 3744b7a954d..23f616298d5 100644 --- a/sys/dev/ic/silireg.h +++ b/sys/dev/ic/silireg.h @@ -1,7 +1,9 @@ -/* $OpenBSD: silireg.h,v 1.20 2007/04/07 13:02:52 pascoe Exp $ */ +/* $OpenBSD: silireg.h,v 1.21 2011/01/26 21:41:00 drahn Exp $ */ /* * Copyright (c) 2007 David Gwynne <dlg@openbsd.org> + * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com> + * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -66,7 +68,17 @@ #define SILI_PREG_SIG_HI_SHIFT 8 #define SILI_PREG_SIG_LO(_s) (SILI_PREG_SLOT(_s) + 0x14) #define SILI_PREG_SIG_LO_MASK 0xff -/* XXX PMP Bits */ + +#define SILI_PREG_PMP_BASE 0xF80 /* PMP Device Status/QActive Registers */ +#define SILI_PREG_PMP_STATUS(_p) (SILI_PREG_PMP_BASE + (_p * 8)) +#define SILI_PREG_PMP_QACTIVE(_p) (SILI_PREG_PMP_BASE + 4 + (_p * 8)) +#define SILI_PREG_PMP_STATUS_PIO 0x000000FF +#define SILI_PREG_PMP_STATUS_ACTIVE 0x00000F00 +#define SILI_PREG_PMP_STATUS_BUSY (1 << 13) +#define SILI_PREG_PMP_STATUS_NCQ (1 << 14) +#define SILI_PREG_PMP_STATUS_LEGACY_Q (1 << 15) +#define SILI_PREG_PMP_STATUS_PENDING (1 << 16) + #define SILI_PREG_PCS 0x1000 /* Port Control Set / Status */ #define SILI_PREG_PCS_PORTRDY (1<<31) /* Port Ready */ #define SILI_PREG_PCS_OOBB (1<<25) /* OOB Bypass */ @@ -159,11 +171,20 @@ #define SILI_PREG_PSS_ALL_SLOTS 0x7fffffff #define SILI_PREG_CAR_LO(_s) (0x1c00 + ((_s) * 0x8)) /* Cmd Activate Reg */ #define SILI_PREG_CAR_HI(_s) (0x1c00 + ((_s) * 0x8) + 0x4) -#define SILI_PREG_CONTEXT 0x1e0f /* Port Context Register */ +#define SILI_PREG_CONTEXT 0x1e04 /* Port Context Register */ +#define SILI_PREG_CONTEXT_SLOT_MASK 0x1F +#define SILI_PREG_CONTEXT_PMPORT_MASK 0x0F +#define SILI_PREG_CONTEXT_SLOT_SHIFT 0 +#define SILI_PREG_CONTEXT_PMPORT_SHIFT 5 #define SILI_PREG_SCTL 0x1f00 /* SControl */ +#define SILI_PREG_SCTL_PMP 0x000F0000 +#define SILI_PREG_SCTL_PMP_SHIFT 16 #define SILI_PREG_SSTS 0x1f04 /* SStatus */ #define SILI_PREG_SERR 0x1f08 /* SError */ #define SILI_PREG_SACT 0x1f0c /* SActive */ +#define SILI_PREG_SNOT 0x1f10 /* SNotification */ + + struct sili_sge { diff --git a/sys/dev/pci/ahci.c b/sys/dev/pci/ahci.c index 319b210f521..b126a7dc991 100644 --- a/sys/dev/pci/ahci.c +++ b/sys/dev/pci/ahci.c @@ -1,7 +1,9 @@ -/* $OpenBSD: ahci.c,v 1.169 2010/08/31 17:13:44 deraadt Exp $ */ +/* $OpenBSD: ahci.c,v 1.170 2011/01/26 21:41:00 drahn Exp $ */ /* * Copyright (c) 2006 David Gwynne <dlg@openbsd.org> + * Copyright (c) 2010 Conformal Systems LLC <info@conformal.com> + * Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,6 +35,7 @@ #include <dev/pci/pcidevs.h> #include <dev/ata/atascsi.h> +#include <dev/ata/pmreg.h> /* change to AHCI_DEBUG for dmesg spam */ #define NO_AHCI_DEBUG @@ -253,6 +256,16 @@ int ahcidebug = AHCI_D_VERBOSE; #define AHCI_PREG_CI_ALL_SLOTS 0xffffffff #define AHCI_PREG_SNTF 0x3c /* SNotification */ +#define AHCI_PREG_FBS 0x40 /* FIS-based Switching Control */ +#define AHCI_PREG_FBS_DWE 0xf0000 /* Device With Error */ +#define AHCI_PREG_FBS_ADO 0xf000 /* Active Device Optimization */ +#define AHCI_PREG_FBS_DEV 0xf00 /* Device To Issue */ +#define AHCI_PREG_FBS_SDE (1<<2) /* Single Device Error */ +#define AHCI_PREG_FBS_DEC (1<<1) /* Device Error Clear */ +#define AHCI_PREG_FBS_EN (1<<0) /* Enable */ + + + struct ahci_cmd_hdr { u_int16_t flags; #define AHCI_CMD_LIST_FLAG_CFL 0x001f /* Command FIS Length */ @@ -263,6 +276,7 @@ struct ahci_cmd_hdr { #define AHCI_CMD_LIST_FLAG_B (1<<9) /* BIST */ #define AHCI_CMD_LIST_FLAG_C (1<<10) /* Clear Busy upon R_OK */ #define AHCI_CMD_LIST_FLAG_PMP 0xf000 /* Port Multiplier Port */ +#define AHCI_CMD_LIST_FLAG_PMP_SHIFT 12 u_int16_t prdtl; /* sgl len */ u_int32_t prdbc; /* transferred byte count */ @@ -352,6 +366,7 @@ struct ahci_port { volatile u_int32_t ap_active; volatile u_int32_t ap_active_cnt; volatile u_int32_t ap_sactive; + volatile u_int32_t ap_pmp_ncq_port; struct ahci_ccb *ap_ccbs; TAILQ_HEAD(, ahci_ccb) ap_ccb_free; @@ -360,7 +375,13 @@ struct ahci_port { u_int32_t ap_state; #define AP_S_NORMAL 0 -#define AP_S_FATAL_ERROR 1 +#define AP_S_PMP_PROBE 1 +#define AP_S_PMP_PORT_PROBE 2 +#define AP_S_FATAL_ERROR 3 + + int ap_pmp_ports; + int ap_port; + int ap_pmp_ignore_ifs; /* For error recovery. */ #ifdef DIAGNOSTIC @@ -394,6 +415,7 @@ struct ahci_softc { int sc_flags; #define AHCI_F_NO_NCQ (1<<0) #define AHCI_F_IGN_FR (1<<1) +#define AHCI_F_IPMS_PROBE (1<<2) /* IPMS on failed PMP probe */ u_int sc_ncmds; @@ -428,6 +450,8 @@ void ahci_ati_sb_idetoahci(struct ahci_softc *, struct pci_attach_args *pa); int ahci_ati_sb600_attach(struct ahci_softc *, struct pci_attach_args *); +int ahci_ati_sb700_attach(struct ahci_softc *, + struct pci_attach_args *); int ahci_amd_hudson2_attach(struct ahci_softc *, struct pci_attach_args *); int ahci_nvidia_mcp_attach(struct ahci_softc *, @@ -441,6 +465,16 @@ static const struct ahci_device ahci_devices[] = { NULL, ahci_ati_sb600_attach }, { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_1, NULL, ahci_ati_sb600_attach }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_2, + NULL, ahci_ati_sb700_attach }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_3, + NULL, ahci_ati_sb700_attach }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_4, + NULL, ahci_ati_sb700_attach }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_5, + NULL, ahci_ati_sb700_attach }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_SBX00_SATA_6, + NULL, ahci_ati_sb700_attach }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_AHCI_2, NULL, ahci_nvidia_mcp_attach }, @@ -499,6 +533,7 @@ void ahci_unmap_regs(struct ahci_softc *); int ahci_map_intr(struct ahci_softc *, struct pci_attach_args *, pci_intr_handle_t); void ahci_unmap_intr(struct ahci_softc *); +void ahci_enable_interrupts(struct ahci_port *); int ahci_init(struct ahci_softc *); int ahci_port_alloc(struct ahci_softc *, u_int); @@ -509,7 +544,11 @@ int ahci_port_start(struct ahci_port *, int); int ahci_port_stop(struct ahci_port *, int); int ahci_port_clo(struct ahci_port *); int ahci_port_softreset(struct ahci_port *); -int ahci_port_portreset(struct ahci_port *); +int ahci_port_portreset(struct ahci_port *, int); +int ahci_port_signature(struct ahci_port *); +int ahci_pmp_port_softreset(struct ahci_port *, int); +int ahci_pmp_port_portreset(struct ahci_port *, int); +int ahci_pmp_port_probe(struct ahci_port *ap, int pmp_port); int ahci_load_prdt(struct ahci_ccb *); void ahci_unload_prdt(struct ahci_ccb *); @@ -529,7 +568,10 @@ void ahci_put_ccb(struct ahci_ccb *); struct ahci_ccb *ahci_get_err_ccb(struct ahci_port *); void ahci_put_err_ccb(struct ahci_ccb *); -int ahci_port_read_ncq_error(struct ahci_port *, int *); +struct ahci_ccb *ahci_get_pmp_ccb(struct ahci_port *); +void ahci_put_pmp_ccb(struct ahci_ccb *); + +int ahci_port_read_ncq_error(struct ahci_port *, int *, int); struct ahci_dmamem *ahci_dmamem_alloc(struct ahci_softc *, size_t); void ahci_dmamem_free(struct ahci_softc *, @@ -544,6 +586,18 @@ u_int32_t ahci_pread(struct ahci_port *, bus_size_t); void ahci_pwrite(struct ahci_port *, bus_size_t, u_int32_t); int ahci_pwait_eq(struct ahci_port *, bus_size_t, u_int32_t, u_int32_t, int); +void ahci_flush_tfd(struct ahci_port *ap); +u_int32_t ahci_active_mask(struct ahci_port *); +void ahci_pmp_probe_timeout(void *); + +/* pmp operations */ +int ahci_pmp_read(struct ahci_port *, int, int, + u_int32_t *); +int ahci_pmp_write(struct ahci_port *, int, int, u_int32_t); +int ahci_pmp_phy_status(struct ahci_port *, int, + u_int32_t *); +int ahci_pmp_identify(struct ahci_port *, int *); + /* Wait for all bits in _b to be cleared */ #define ahci_pwait_clr(_ap, _r, _b, _n) \ @@ -556,8 +610,8 @@ int ahci_pwait_eq(struct ahci_port *, bus_size_t, /* provide methods for atascsi to call */ -int ahci_ata_probe(void *, int); -void ahci_ata_free(void *, int); +int ahci_ata_probe(void *, int, int); +void ahci_ata_free(void *, int, int); struct ata_xfer * ahci_ata_get_xfer(void *, int); void ahci_ata_put_xfer(struct ata_xfer *); void ahci_ata_cmd(struct ata_xfer *); @@ -571,6 +625,7 @@ struct atascsi_methods ahci_atascsi_methods = { /* ccb completions */ void ahci_ata_cmd_done(struct ahci_ccb *); +void ahci_pmp_cmd_done(struct ahci_ccb *); void ahci_ata_cmd_timeout(void *); void ahci_empty_done(struct ahci_ccb *); @@ -632,8 +687,15 @@ ahci_ati_sb600_attach(struct ahci_softc *sc, struct pci_attach_args *pa) { ahci_ati_sb_idetoahci(sc, pa); - sc->sc_flags |= AHCI_F_IGN_FR; + sc->sc_flags |= AHCI_F_IGN_FR | AHCI_F_IPMS_PROBE; + + return (0); +} +int +ahci_ati_sb700_attach(struct ahci_softc *sc, struct pci_attach_args *pa) +{ + sc->sc_flags |= AHCI_F_IPMS_PROBE; return (0); } @@ -803,8 +865,18 @@ noccc: aaa.aaa_ncmds = sc->sc_ncmds; aaa.aaa_capability = ASAA_CAP_NEEDS_RESERVED; if (!(sc->sc_flags & AHCI_F_NO_NCQ) && - (sc->sc_cap & AHCI_REG_CAP_SNCQ)) - aaa.aaa_capability |= ASAA_CAP_NCQ; + (sc->sc_cap & AHCI_REG_CAP_SNCQ)) { + aaa.aaa_capability |= ASAA_CAP_NCQ | ASAA_CAP_PMP_NCQ; + /* XXX enabling ASAA_CAP_PMP_NCQ with FBS: + * - some error recovery work required (single device vs port + * errors) + * - probably need to look at storing our active ccb queue + * differently so we can group ncq and non-ncq commands + * for different ports. as long as we preserve the order for + * each port, we can reorder commands to get more ncq + * commands to run in parallel. + */ + } sc->sc_atascsi = atascsi_attach(&sc->sc_dev, &aaa); @@ -989,6 +1061,22 @@ ahci_init(struct ahci_softc *sc) return (0); } +void +ahci_enable_interrupts(struct ahci_port *ap) +{ + ahci_pwrite(ap, AHCI_PREG_IE, AHCI_PREG_IE_TFEE | AHCI_PREG_IE_HBFE | + AHCI_PREG_IE_IFE | AHCI_PREG_IE_OFE | AHCI_PREG_IE_DPE | + AHCI_PREG_IE_UFE | + ((ap->ap_sc->sc_cap & AHCI_REG_CAP_SSNTF) ? AHCI_PREG_IE_IPME : 0) | +#ifdef AHCI_COALESCE + ((ap->ap_sc->sc_ccc_ports & (1 << ap->ap_port)) ? 0 : + (AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE)) +#else + AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE +#endif + ); +} + int ahci_port_alloc(struct ahci_softc *sc, u_int port) { @@ -1011,6 +1099,7 @@ ahci_port_alloc(struct ahci_softc *sc, u_int port) snprintf(ap->ap_name, sizeof(ap->ap_name), "%s.%d", DEVNAME(sc), port); #endif + ap->ap_port = port; sc->sc_ports[port] = ap; if (bus_space_subregion(sc->sc_iot, sc->sc_ioh, @@ -1050,6 +1139,11 @@ ahci_port_alloc(struct ahci_softc *sc, u_int port) ahci_pwrite(ap, AHCI_PREG_SCTL, 0); } + /* XXX FBS - need to allocate 16x ahci_rfis struct? - but we don't + * know if there's a PMP attached or if the HBA supports FBS yet.. + * reallocate when we enable FBS? + */ + /* Allocate RFIS */ ap->ap_dmamem_rfis = ahci_dmamem_alloc(sc, sizeof(struct ahci_rfis)); if (ap->ap_dmamem_rfis == NULL) @@ -1137,7 +1231,8 @@ nomem: ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC, 1); /* Reset port */ - rc = ahci_port_portreset(ap); + rc = ahci_port_portreset(ap, 1); + switch (rc) { case ENODEV: switch (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) { @@ -1173,8 +1268,9 @@ nomem: default: break; } - DPRINTF(AHCI_D_VERBOSE, "%s: detected device on port %d\n", - DEVNAME(sc), port); + + DPRINTF(AHCI_D_VERBOSE, "%s: detected device on port %d; %d\n", + DEVNAME(sc), port, rc); /* Enable command transfers on port */ if (ahci_port_start(ap, 0)) { @@ -1187,17 +1283,7 @@ nomem: ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS)); ahci_write(sc, AHCI_REG_IS, 1 << port); - /* Enable port interrupts */ - ahci_pwrite(ap, AHCI_PREG_IE, AHCI_PREG_IE_TFEE | AHCI_PREG_IE_HBFE | - AHCI_PREG_IE_IFE | AHCI_PREG_IE_OFE | AHCI_PREG_IE_DPE | - AHCI_PREG_IE_UFE | -#ifdef AHCI_COALESCE - ((sc->sc_ccc_ports & (1 << port)) ? 0 : (AHCI_PREG_IE_SDBE | - AHCI_PREG_IE_DHRE)) -#else - AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE -#endif - ); + ahci_enable_interrupts(ap); freeport: if (rc != 0) @@ -1302,7 +1388,7 @@ ahci_port_init(struct ahci_softc *sc, u_int port) ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC, 1); /* Reset port */ - rc = ahci_port_portreset(ap); + rc = ahci_port_portreset(ap, 1); switch (rc) { case ENODEV: switch (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) { @@ -1341,6 +1427,27 @@ ahci_port_init(struct ahci_softc *sc, u_int port) DPRINTF(AHCI_D_VERBOSE, "%s: detected device on port %d\n", DEVNAME(sc), port); + if (ap->ap_pmp_ports > 0) { + int p; + + for (p = 0; p < ap->ap_pmp_ports; p++) { + int sig; + + /* might need to do a portreset first here? */ + + /* softreset the port */ + if (ahci_pmp_port_softreset(ap, p)) { + printf("%s.%d: unable to probe PMP port due to" + " softreset failure\n", PORTNAME(ap), p); + continue; + } + + sig = ahci_port_signature(ap); + printf("%s.%d: port signature returned %d\n", + PORTNAME(ap), p, sig); + } + } + /* Enable command transfers on port */ if (ahci_port_start(ap, 0)) { printf("%s: failed to start command DMA on port %d, " @@ -1352,17 +1459,7 @@ ahci_port_init(struct ahci_softc *sc, u_int port) ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS)); ahci_write(sc, AHCI_REG_IS, 1 << port); - /* Enable port interrupts */ - ahci_pwrite(ap, AHCI_PREG_IE, AHCI_PREG_IE_TFEE | AHCI_PREG_IE_HBFE | - AHCI_PREG_IE_IFE | AHCI_PREG_IE_OFE | AHCI_PREG_IE_DPE | - AHCI_PREG_IE_UFE | -#ifdef AHCI_COALESCE - ((sc->sc_ccc_ports & (1 << port)) ? 0 : (AHCI_PREG_IE_SDBE | - AHCI_PREG_IE_DHRE)) -#else - AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE -#endif - ); + ahci_enable_interrupts(ap); reterr: return (rc); @@ -1373,6 +1470,8 @@ ahci_port_start(struct ahci_port *ap, int fre_only) { u_int32_t r; + /* XXX FBS: possibly turn FBS on here */ + /* Turn on FRE (and ST) */ r = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; r |= AHCI_PREG_CMD_FRE; @@ -1433,6 +1532,8 @@ ahci_port_stop(struct ahci_port *ap, int stop_fis_rx) ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_FR, 1)) return (2); + /* XXX FBS: possibly disable FBS here? */ + return (0); } @@ -1496,6 +1597,10 @@ ahci_port_softreset(struct ahci_port *ap) /* Clear port errors to permit TFD transfer */ ahci_pwrite(ap, AHCI_PREG_SERR, ahci_pread(ap, AHCI_PREG_SERR)); + /* XXX FBS - need to ensure we don't enable FBS here, since we're + * resetting stuff + * (AHCI spec 9.3.8) + */ /* Restart port */ if (ahci_port_start(ap, 0)) { printf("%s: failed to start port, cannot softreset\n", @@ -1519,8 +1624,8 @@ ahci_port_softreset(struct ahci_port *ap) bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table)); fis = ccb->ccb_cmd_table->cfis; - fis[0] = 0x27; /* Host to device */ - fis[15] = 0x04; /* SRST DEVCTL */ + fis[0] = ATA_FIS_TYPE_H2D; + fis[15] = ATA_FIS_CONTROL_SRST; cmd_slot->prdtl = 0; cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ @@ -1533,7 +1638,7 @@ ahci_port_softreset(struct ahci_port *ap) goto err; /* Prep second D2H command to read status and complete reset sequence */ - fis[0] = 0x27; /* Host to device */ + fis[0] = ATA_FIS_TYPE_H2D; fis[15] = 0; cmd_slot->prdtl = 0; @@ -1574,13 +1679,336 @@ err: return (rc); } +int +ahci_pmp_port_softreset(struct ahci_port *ap, int pmp_port) +{ + struct ahci_ccb *ccb = NULL; + u_int32_t data; + int count; + int rc; + int s; + struct ahci_cmd_hdr *cmd_slot; + u_int8_t *fis; + + /* XXX FBS: ensure fbs is disabled on ap, since we're resetting + * devices (AHCI section 9.3.8) + */ + + s = splbio(); + /* ignore spurious IFS errors while resetting */ + printf("%s: now ignoring IFS\n", PORTNAME(ap)); + ap->ap_pmp_ignore_ifs = 1; + + count = 2; + rc = 0; + do { + if (ccb != NULL) { + ahci_put_pmp_ccb(ccb); + ccb = NULL; + } + + if (ahci_pmp_phy_status(ap, pmp_port, &data)) { + printf("%s.%d: unable to clear PHY status\n", + PORTNAME(ap), pmp_port); + } + ahci_pwrite(ap, AHCI_PREG_SERR, -1); + /* maybe don't do this on the first loop: */ + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IFS); + ahci_pmp_write(ap, pmp_port, SATA_PMREG_SERR, -1); + + /* send first softreset FIS */ + ccb = ahci_get_pmp_ccb(ap); + cmd_slot = ccb->ccb_cmd_hdr; + bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table)); + + fis = ccb->ccb_cmd_table->cfis; + fis[0] = ATA_FIS_TYPE_H2D; + fis[1] = pmp_port; + fis[15] = ATA_FIS_CONTROL_SRST | ATA_FIS_CONTROL_4BIT; + + cmd_slot->prdtl = 0; + cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_C); + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_R); + cmd_slot->flags |= htole16(pmp_port << + AHCI_CMD_LIST_FLAG_PMP_SHIFT); + + ccb->ccb_xa.state = ATA_S_PENDING; + + DPRINTF(AHCI_D_VERBOSE, "%s.%d: sending PMP softreset cmd\n", + PORTNAME(ap), pmp_port); + if (ahci_poll(ccb, 1000, ahci_pmp_probe_timeout) != 0) { + printf("%s.%d: PMP port softreset cmd failed\n", + PORTNAME(ap), pmp_port); + rc = EBUSY; + if (count > 0) { + /* probably delay a while to allow + * it to settle down? + */ + } + continue; + } + + /* send signature FIS */ + bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table)); + fis[0] = ATA_FIS_TYPE_H2D; + fis[1] = pmp_port; + fis[15] = ATA_FIS_CONTROL_4BIT; + + cmd_slot->prdtl = 0; + cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ + cmd_slot->flags |= htole16(pmp_port << + AHCI_CMD_LIST_FLAG_PMP_SHIFT); + + DPRINTF(AHCI_D_VERBOSE, "%s.%d: sending PMP probe status cmd\n", + PORTNAME(ap), pmp_port); + ccb->ccb_xa.state = ATA_S_PENDING; + if (ahci_poll(ccb, 5000, ahci_pmp_probe_timeout) != 0) { + DPRINTF(AHCI_D_VERBOSE, "%s.%d: PMP probe status cmd " + "failed\n", PORTNAME(ap), pmp_port); + rc = EBUSY; + if (count > 0) { + /* sleep a while? */ + } + continue; + } + + fis[15] = 0; + break; + } while (count--); + + if (ccb != NULL) { + ahci_put_pmp_ccb(ccb); + ccb = NULL; + } + + /* clean up a bit */ + ahci_pmp_write(ap, pmp_port, SATA_PMREG_SERR, -1); + ahci_pwrite(ap, AHCI_PREG_SERR, -1); + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IFS); + ap->ap_pmp_ignore_ifs = 0; + printf("%s: no longer ignoring IFS\n", PORTNAME(ap)); + splx(s); + + return (rc); +} + +int +ahci_pmp_port_probe(struct ahci_port *ap, int pmp_port) +{ + int sig; + + ap->ap_state = AP_S_PMP_PORT_PROBE; + + printf("%s.%d: probing pmp port\n", PORTNAME(ap), pmp_port); + if (ahci_pmp_port_portreset(ap, pmp_port)) { + printf("%s.%d: unable to probe PMP port; portreset failed\n", + PORTNAME(ap), pmp_port); + ap->ap_state = AP_S_NORMAL; + return (ATA_PORT_T_NONE); + } + + if (ahci_pmp_port_softreset(ap, pmp_port)) { + printf("%s.%d: unable to probe PMP port due to softreset " + "failure\n", PORTNAME(ap), pmp_port); + ap->ap_state = AP_S_NORMAL; + return (ATA_PORT_T_NONE); + } + + sig = ahci_port_signature(ap); + printf("%s.%d: port signature returned %d\n", PORTNAME(ap), pmp_port, + sig); + ap->ap_state = AP_S_NORMAL; + return (sig); +} + + +void +ahci_flush_tfd(struct ahci_port *ap) +{ + u_int32_t r; + + r = ahci_pread(ap, AHCI_PREG_SERR); + if (r & AHCI_PREG_SERR_DIAG_X) + ahci_pwrite(ap, AHCI_PREG_SERR, AHCI_PREG_SERR_DIAG_X); +} + +u_int32_t +ahci_active_mask(struct ahci_port *ap) +{ + u_int32_t mask; + + mask = ahci_pread(ap, AHCI_PREG_CI); + if (ap->ap_sc->sc_cap & AHCI_REG_CAP_SNCQ) + mask |= ahci_pread(ap, AHCI_PREG_SACT); + return mask; +} + +void +ahci_pmp_probe_timeout(void *cookie) +{ + struct ahci_ccb *ccb = cookie; + struct ahci_port *ap = ccb->ccb_port; + u_int32_t mask; + + DPRINTF(AHCI_D_VERBOSE, "%s: PMP probe cmd timed out\n", PORTNAME(ap)); + switch (ccb->ccb_xa.state) { + case ATA_S_PENDING: + TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry); + ccb->ccb_xa.state = ATA_S_TIMEOUT; + break; + + case ATA_S_ONCHIP: + case ATA_S_ERROR: /* currently mostly here for the ATI SBx00 quirk */ + /* clear the command on-chip */ + KASSERT(ap->ap_active == (1 << ccb->ccb_slot) && + ap->ap_sactive == 0); + ahci_port_stop(ap, 0); + ahci_port_start(ap, 0); + + if (ahci_active_mask(ap) != 0) { + ahci_port_stop(ap, 0); + ahci_port_start(ap, 0); + mask = ahci_active_mask(ap); + if (mask != 0) { + printf("%s: ahci_pmp_probe_timeout: failed to " + "clear active cmds: %08x\n", PORTNAME(ap), + mask); + } + } + + ccb->ccb_xa.state = ATA_S_TIMEOUT; + ap->ap_active &= ~(1 << ccb->ccb_slot); + KASSERT(ap->ap_active_cnt > 0); + --ap->ap_active_cnt; + printf("%s: timed out %d, active %x, count %d\n", PORTNAME(ap), + ccb->ccb_slot, ap->ap_active, ap->ap_active_cnt); + break; + + default: + panic("%s: ahci_pmp_probe_timeout: ccb in bad state %d", + PORTNAME(ap), ccb->ccb_xa.state); + } +} + +int +ahci_port_signature(struct ahci_port *ap) +{ + u_int32_t sig; + + sig = ahci_pread(ap, AHCI_PREG_SIG); + if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) + return (ATA_PORT_T_ATAPI); + else if ((sig & 0xffff0000) == (SATA_SIGNATURE_PORT_MULTIPLIER & + 0xffff0000)) + return (ATA_PORT_T_PM); + else + return (ATA_PORT_T_DISK); +} + +int +ahci_pmp_port_portreset(struct ahci_port *ap, int pmp_port) +{ + u_int32_t cmd, data; + int loop; + int rc = 1; + int s; + + s = splbio(); + DPRINTF(AHCI_D_VERBOSE, "%s.%d: PMP port reset\n", PORTNAME(ap), + pmp_port); + + cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; + + /* turn off power management and disable the PHY */ + data = AHCI_PREG_SCTL_IPM_DISABLED; + /* maybe add AHCI_PREG_SCTL_DET_DISABLE */ + if (ahci_pmp_write(ap, pmp_port, SATA_PMREG_SERR, -1)) + goto err; + if (ahci_pmp_write(ap, pmp_port, SATA_PMREG_SCTL, data)) + goto err; + delay(10000); + + /* start COMRESET */ + data = AHCI_PREG_SCTL_IPM_DISABLED | AHCI_PREG_SCTL_DET_INIT; + if ((ap->ap_sc->sc_dev.dv_cfdata->cf_flags & 0x01) != 0) { + DPRINTF(AHCI_D_VERBOSE, "%s.%d: forcing GEN1\n", PORTNAME(ap), + pmp_port); + data |= AHCI_PREG_SCTL_SPD_GEN1; + } else + data |= AHCI_PREG_SCTL_SPD_ANY; + + if (ahci_pmp_write(ap, pmp_port, SATA_PMREG_SCTL, data)) + goto err; + + /* give it a while to settle down */ + delay(100000); + + if (ahci_pmp_phy_status(ap, pmp_port, &data)) { + printf("%s.%d: cannot clear PHY status\n", PORTNAME(ap), + pmp_port); + } + + /* start trying to negotiate */ + ahci_pmp_write(ap, pmp_port, SATA_PMREG_SERR, -1); + data = AHCI_PREG_SCTL_IPM_DISABLED | AHCI_PREG_SCTL_DET_NONE; + if (ahci_pmp_write(ap, pmp_port, SATA_PMREG_SCTL, data)) + goto err; + + /* give it a while to detect */ + for (loop = 3; loop; --loop) { + if (ahci_pmp_read(ap, pmp_port, SATA_PMREG_SSTS, &data)) + goto err; + if (data & AHCI_PREG_SSTS_DET) + break; + delay(100000); + } + if (loop == 0) { + printf("%s.%d: port is unplugged\n", PORTNAME(ap), pmp_port); + goto err; + } + + /* give it even longer to fully negotiate */ + for (loop = 30; loop; --loop) { + if (ahci_pmp_read(ap, pmp_port, SATA_PMREG_SSTS, &data)) + goto err; + if ((data & AHCI_PREG_SSTS_DET) == AHCI_PREG_SSTS_DET_DEV) + break; + delay(100000); + } + + if (loop == 0) { + printf("%s.%d: device is not negotiating\n", PORTNAME(ap), + pmp_port); + goto err; + } + + /* device detected */ + printf("%s.%d: device detected\n", PORTNAME(ap), pmp_port); + + /* clean up a bit */ + delay(100000); + ahci_pmp_write(ap, pmp_port, SATA_PMREG_SERR, -1); + ahci_pwrite(ap, AHCI_PREG_SERR, -1); + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IFS); + + rc = 0; +err: + splx(s); + return (rc); +} + /* AHCI port reset, Section 10.4.2 */ int -ahci_port_portreset(struct ahci_port *ap) +ahci_port_portreset(struct ahci_port *ap, int pmp) { u_int32_t cmd, r; - int rc; + int rc, count, pmp_rc, s; + struct ahci_cmd_hdr *cmd_slot; + struct ahci_ccb *ccb = NULL; + u_int8_t *fis = NULL; + s = splbio(); DPRINTF(AHCI_D_VERBOSE, "%s: port reset\n", PORTNAME(ap)); /* Save previous command register state */ @@ -1609,23 +2037,225 @@ ahci_port_portreset(struct ahci_port *ap) if (ahci_pwait_eq(ap, AHCI_PREG_SSTS, AHCI_PREG_SSTS_DET, AHCI_PREG_SSTS_DET_DEV, 1)) { rc = ENODEV; + if (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) { + /* this may be a port multiplier with no device + * on port 0, so still do the pmp check if requested. + */ + } else { + goto err; + } + } else { + /* Clear SERR (incl X bit), so TFD can update */ + ahci_pwrite(ap, AHCI_PREG_SERR, ahci_pread(ap, AHCI_PREG_SERR)); + + /* Wait for device to become ready */ + if (ahci_pwait_clr(ap, AHCI_PREG_TFD, AHCI_PREG_TFD_STS_BSY | + AHCI_PREG_TFD_STS_DRQ | AHCI_PREG_TFD_STS_ERR, 3)) { + /* even if the device doesn't wake up, check if there's + * a port multiplier there + */ + rc = EBUSY; + } else { + rc = 0; + } + } + + if (pmp == 0 || + !ISSET(ahci_read(ap->ap_sc, AHCI_REG_CAP), AHCI_REG_CAP_SPM)) { goto err; } - /* Clear SERR (incl X bit), so TFD can update */ - ahci_pwrite(ap, AHCI_PREG_SERR, ahci_pread(ap, AHCI_PREG_SERR)); + pmp_rc = 0; + count = 2; + do { + DPRINTF(AHCI_D_VERBOSE, "%s: PMP probe %d\n", PORTNAME(ap), + count); + if (ccb != NULL) { + ahci_put_pmp_ccb(ccb); + ccb = NULL; + } + ahci_port_stop(ap, 0); + ap->ap_state = AP_S_PMP_PROBE; - /* Wait for device to become ready */ - if (ahci_pwait_clr(ap, AHCI_PREG_TFD, AHCI_PREG_TFD_STS_BSY | - AHCI_PREG_TFD_STS_DRQ | AHCI_PREG_TFD_STS_ERR, 3)) { - rc = EBUSY; - goto err; + /* set PMA in cmd reg */ + cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; + if ((cmd & AHCI_PREG_CMD_PMA) == 0) { + cmd |= AHCI_PREG_CMD_PMA; + ahci_pwrite(ap, AHCI_PREG_CMD, cmd); + } + + /* Flush errors and request CLO unconditionally, + * then start the port + */ + r = ahci_pread(ap, AHCI_PREG_SERR); + if (r & AHCI_PREG_SERR_DIAG_X) + ahci_pwrite(ap, AHCI_PREG_SERR, + AHCI_PREG_SERR_DIAG_X); + + /* Request CLO */ + ahci_port_clo(ap); + + /* Clear port errors to permit TFD transfer */ + r = ahci_pread(ap, AHCI_PREG_SERR); + ahci_pwrite(ap, AHCI_PREG_SERR, r); + + /* XXX FBS: ensure we don't enable FBS here, since + * we're resetting the port + * (AHCI section 9.3.8) + */ + /* Restart port */ + if (ahci_port_start(ap, 0)) { + rc = 1; + printf("%s: failed to start port, cannot probe PMP\n", + PORTNAME(ap)); + break; + } + + /* Check whether CLO worked */ + if (ahci_pwait_clr(ap, AHCI_PREG_TFD, + AHCI_PREG_TFD_STS_BSY | AHCI_PREG_TFD_STS_DRQ, 1)) { + u_int32_t cap; + + cap = ahci_read(ap->ap_sc, AHCI_REG_CAP); + printf("%s: CLO %s, need port reset\n", + PORTNAME(ap), + ISSET(cap, AHCI_REG_CAP_SCLO) + ? "failed" : "unsupported"); + pmp_rc = EBUSY; + break; + } + + /* Prep first command with SRST feature & + * clear busy/reset flags + */ + ccb = ahci_get_pmp_ccb(ap); + cmd_slot = ccb->ccb_cmd_hdr; + bzero(ccb->ccb_cmd_table, + sizeof(struct ahci_cmd_table)); + + fis = ccb->ccb_cmd_table->cfis; + fis[0] = ATA_FIS_TYPE_H2D; + fis[1] = SATA_PMP_CONTROL_PORT; + fis[15] = ATA_FIS_CONTROL_SRST | ATA_FIS_CONTROL_4BIT; + + cmd_slot->prdtl = 0; + cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_C); + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_R); + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_PMP); + + DPRINTF(AHCI_D_VERBOSE, "%s: sending PMP reset cmd\n", + PORTNAME(ap)); + ccb->ccb_xa.state = ATA_S_PENDING; + if (ahci_poll(ccb, 1000, ahci_pmp_probe_timeout) != 0) { + DPRINTF(AHCI_D_VERBOSE, "%s: PMP reset cmd failed\n", + PORTNAME(ap)); + pmp_rc = EBUSY; + continue; + } + + if (ahci_pwait_clr(ap, AHCI_PREG_TFD, + AHCI_PREG_TFD_STS_BSY | AHCI_PREG_TFD_STS_DRQ, 1)) { + printf("%s: port busy after first PMP probe FIS\n", + PORTNAME(ap)); + } + + /* clear errors in case the device + * didn't reset cleanly + */ + ahci_flush_tfd(ap); + r = ahci_pread(ap, AHCI_PREG_SERR); + ahci_pwrite(ap, AHCI_PREG_SERR, r); + + /* Prep second command to read status and + * complete reset sequence + */ + bzero(ccb->ccb_cmd_table, + sizeof(struct ahci_cmd_table)); + fis[0] = ATA_FIS_TYPE_H2D; + fis[1] = SATA_PMP_CONTROL_PORT; + fis[15] = ATA_FIS_CONTROL_4BIT; + + cmd_slot->prdtl = 0; + cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ + cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_PMP); + + DPRINTF(AHCI_D_VERBOSE, "%s: sending PMP probe status cmd\n", + PORTNAME(ap)); + ccb->ccb_xa.state = ATA_S_PENDING; + if (ahci_poll(ccb, 5000, ahci_pmp_probe_timeout) != 0) { + DPRINTF(AHCI_D_VERBOSE, "%s: PMP probe status " + "cmd failed\n", PORTNAME(ap)); + pmp_rc = EBUSY; + continue; + } + + /* apparently we need to retry at least once + * to get the right signature + */ + fis[15] = 0; + pmp_rc = 0; + } while (--count); + + if (ccb != NULL) { + ahci_put_pmp_ccb(ccb); + ccb = NULL; + } + + if (ap->ap_state == AP_S_PMP_PROBE) { + ap->ap_state = AP_S_NORMAL; + } + + if (pmp_rc == 0) { + if (ahci_port_signature(ap) != ATA_PORT_T_PM) { + DPRINTF(AHCI_D_VERBOSE, "%s: device is not a PMP\n", + PORTNAME(ap)); + pmp_rc = EBUSY; + } else { + DPRINTF(AHCI_D_VERBOSE, "%s: PMP found\n", + PORTNAME(ap)); + } + } + + if (pmp_rc == 0) { + if (ahci_pmp_identify(ap, &ap->ap_pmp_ports)) { + pmp_rc = EBUSY; + } else { + /* XXX enable FBS if available */ + rc = 0; + } + } + + /* if PMP detection failed, so turn off the PMA bit and + * reset the port again + */ + if (pmp_rc != 0) { + DPRINTF(AHCI_D_VERBOSE, "%s: no PMP found, resetting " + "the port\n", PORTNAME(ap)); + ahci_port_stop(ap, 0); + ahci_port_clo(ap); + cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; + cmd &= ~AHCI_PREG_CMD_PMA; + ahci_pwrite(ap, AHCI_PREG_CMD, cmd); + + ahci_pwrite(ap, AHCI_PREG_IE, 0); + ahci_port_stop(ap, 0); + if (ap->ap_sc->sc_cap & AHCI_REG_CAP_SSNTF) + ahci_pwrite(ap, AHCI_PREG_SNTF, -1); + ahci_flush_tfd(ap); + ahci_pwrite(ap, AHCI_PREG_SERR, -1); + + ahci_pwrite(ap, AHCI_PREG_IS, -1); + + ahci_enable_interrupts(ap); + + ahci_port_portreset(ap, 0); } - rc = 0; err: /* Restore preserved port state */ ahci_pwrite(ap, AHCI_PREG_CMD, cmd); + splx(s); return (rc); } @@ -1729,6 +2359,16 @@ ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)) splx(s); return (0); } + if (ccb->ccb_xa.state == ATA_S_ERROR) { + printf("%s: ccb in slot %d errored\n", PORTNAME(ap), + ccb->ccb_slot); + /* pretend it timed out? */ + if (timeout_fn != NULL) { + timeout_fn(ccb); + } + splx(s); + return (1); + } delay(1000); } while (--timeout > 0); @@ -1763,17 +2403,29 @@ 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); + /* XXX FBS: need to figure out whether we still need to keep NCQ and + * non-queued commands separate when FBS is in use. I guess probably + * not? it's not particularly clear from the spec.. + */ + if (ccb->ccb_xa.flags & ATA_F_NCQ) { /* Issue NCQ commands only when there are no outstanding * standard commands. */ - if (ap->ap_active != 0 || !TAILQ_EMPTY(&ap->ap_ccb_pending)) + if (ap->ap_active != 0 || !TAILQ_EMPTY(&ap->ap_ccb_pending) || + (ap->ap_sactive != 0 && + ap->ap_pmp_ncq_port != ccb->ccb_xa.pmp_port)) { TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry); - else { + } else { + /* XXX FBS: if using FBS, set AHCI_PREG_FBS_DEV + * to the port number + */ + KASSERT(ap->ap_active_cnt == 0); ap->ap_sactive |= (1 << ccb->ccb_slot); ccb->ccb_xa.state = ATA_S_ONCHIP; ahci_pwrite(ap, AHCI_PREG_SACT, 1 << ccb->ccb_slot); ahci_pwrite(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot); + ap->ap_pmp_ncq_port = ccb->ccb_xa.pmp_port; } } else { /* Wait for all NCQ commands to finish before issuing standard @@ -1781,6 +2433,10 @@ ahci_start(struct ahci_ccb *ccb) if (ap->ap_sactive != 0 || ap->ap_active_cnt == 2) TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry); else if (ap->ap_active_cnt < 2) { + /* XXX FBS: if using FBS, set AHCI_PREG_FBS_DEV to the + * port number + */ + ap->ap_active |= 1 << ccb->ccb_slot; ccb->ccb_xa.state = ATA_S_ONCHIP; ahci_pwrite(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot); @@ -1801,19 +2457,32 @@ ahci_issue_pending_ncq_commands(struct ahci_port *ap) if (nextccb == NULL || !(nextccb->ccb_xa.flags & ATA_F_NCQ)) return; - /* Start all the NCQ commands at the head of the pending list. */ + /* XXX FBS: + * - set AHCI_PREG_FBS_DEV for each command + * - one write to AHCI_PREG_CI per command + */ + + /* Start all the NCQ commands at the head of the pending list. + * If a port multiplier is attached to the port, we can only + * issue commands for one of its ports at a time. + */ + if (ap->ap_sactive != NULL && + ap->ap_pmp_ncq_port != nextccb->ccb_xa.pmp_port) { + return; + } + + ap->ap_pmp_ncq_port = nextccb->ccb_xa.pmp_port; do { TAILQ_REMOVE(&ap->ap_ccb_pending, nextccb, ccb_entry); sact_change |= 1 << nextccb->ccb_slot; nextccb->ccb_xa.state = ATA_S_ONCHIP; nextccb = TAILQ_FIRST(&ap->ap_ccb_pending); - } while (nextccb && (nextccb->ccb_xa.flags & ATA_F_NCQ)); + } while (nextccb && (nextccb->ccb_xa.flags & ATA_F_NCQ) && + (nextccb->ccb_xa.pmp_port == ap->ap_pmp_ncq_port)); ap->ap_sactive |= sact_change; ahci_pwrite(ap, AHCI_PREG_SACT, sact_change); ahci_pwrite(ap, AHCI_PREG_CI, sact_change); - - return; } void @@ -1823,12 +2492,18 @@ ahci_issue_pending_commands(struct ahci_port *ap, int last_was_ncq) nextccb = TAILQ_FIRST(&ap->ap_ccb_pending); if (nextccb && (nextccb->ccb_xa.flags & ATA_F_NCQ)) { - KASSERT(last_was_ncq == 0); /* otherwise it should have - * been started already. */ + if (last_was_ncq) { + KASSERT(nextccb->ccb_xa.pmp_port != + ap->ap_pmp_ncq_port); + /* otherwise it should have been started already */ + } else { + ap->ap_active_cnt--; + } /* Issue NCQ commands only when there are no outstanding - * standard commands. */ - ap->ap_active_cnt--; + * standard commands, and previous NCQ commands for other + * PMP ports have finished. + */ if (ap->ap_active == 0) ahci_issue_pending_ncq_commands(ap); else @@ -1847,6 +2522,7 @@ ahci_issue_pending_commands(struct ahci_port *ap, int last_was_ncq) TAILQ_REMOVE(&ap->ap_ccb_pending, nextccb, ccb_entry); ap->ap_active |= 1 << nextccb->ccb_slot; nextccb->ccb_xa.state = ATA_S_ONCHIP; + /* XXX FBS: set AHCI_PREG_FBS_DEV here */ ahci_pwrite(ap, AHCI_PREG_CI, 1 << nextccb->ccb_slot); if (last_was_ncq) ap->ap_active_cnt++; @@ -1911,6 +2587,7 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) struct ahci_softc *sc = ap->ap_sc; u_int32_t is, ci_saved, ci_masked, processed = 0; int slot, need_restart = 0; + int process_error = 0; struct ahci_ccb *ccb; volatile u_int32_t *active; #ifdef DIAGNOSTIC @@ -1939,8 +2616,32 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) active = &ap->ap_active; } - /* Command failed. See AHCI 1.1 spec 6.2.2.1 and 6.2.2.2. */ if (is & AHCI_PREG_IS_TFES) { + process_error = 1; + } else if (is & AHCI_PREG_IS_DHRS) { + u_int32_t tfd; + u_int32_t cmd; + u_int32_t serr; + + tfd = ahci_pread(ap, AHCI_PREG_TFD); + cmd = ahci_pread(ap, AHCI_PREG_CMD); + serr = ahci_pread(ap, AHCI_PREG_SERR); + if ((tfd & AHCI_PREG_TFD_STS_ERR) && + (cmd & AHCI_PREG_CMD_CR) == 0) { + DPRINTF(AHCI_D_VERBOSE, "%s: DHRS error, TFD: %b, SERR:" + " %b, DIAG: %b\n", PORTNAME(ap), tfd, + AHCI_PFMT_TFD_STS, AHCI_PREG_SERR_ERR(serr), + AHCI_PFMT_SERR_ERR, AHCI_PREG_SERR_DIAG(serr), + AHCI_PFMT_SERR_DIAG); + process_error = 1; + } else { + /* rfis copy back is in the normal execution path */ + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_DHRS); + } + } + + /* Command failed. See AHCI 1.1 spec 6.2.2.1 and 6.2.2.2. */ + if (process_error) { u_int32_t tfd, serr; int err_slot; @@ -1993,22 +2694,54 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) /* If device hasn't cleared its busy status, try to idle it. */ if (ISSET(tfd, AHCI_PREG_TFD_STS_BSY | AHCI_PREG_TFD_STS_DRQ)) { - printf("%s: attempting to idle device\n", PORTNAME(ap)); - if (ahci_port_softreset(ap)) { - printf("%s: failed to soft reset device\n", + + if (ap->ap_state == AP_S_PMP_PORT_PROBE) { + /* can't reset the port here, just make sure + * the probe fails and the port still works. + */ + } else if (ap->ap_pmp_ports != 0 && err_slot != -1) { + printf("%s: error on PMP port %d, idling " + "device\n", PORTNAME(ap), + ccb->ccb_xa.pmp_port); + if (ahci_pmp_port_softreset(ap, + ccb->ccb_xa.pmp_port) == 0) { + printf("%s: unable to softreset port " + "%d\n", PORTNAME(ap), + ccb->ccb_xa.pmp_port); + if (ahci_pmp_port_portreset(ap, + ccb->ccb_xa.pmp_port)) { + printf("%s: failed to port " + " reset %d, giving up on " + "it\n", PORTNAME(ap), + ccb->ccb_xa.pmp_port); + goto fatal; + } + } + } else { + printf("%s: attempting to idle device\n", PORTNAME(ap)); - if (ahci_port_portreset(ap)) { - printf("%s: failed to port reset " - "device, give up on it\n", - PORTNAME(ap)); - goto fatal; + if (ahci_port_softreset(ap)) { + printf("%s: failed to soft reset " + "device\n", PORTNAME(ap)); + if (ahci_port_portreset(ap, 0)) { + printf("%s: failed to port " + "reset device, give up on " + "it\n", PORTNAME(ap)); + goto fatal; + } } } /* Had to reset device, can't gather extended info. */ } else if (ap->ap_sactive) { - /* Recover the NCQ error from log page 10h. */ - ahci_port_read_ncq_error(ap, &err_slot); + /* Recover the NCQ error from log page 10h. + * XXX FBS: need to do things to figure out where the + * error came from. without FBS, we know the PMP port + * responsible because we can only have queued commands + * active for one port at a time. + */ + ahci_port_read_ncq_error(ap, &err_slot, + ap->ap_pmp_ncq_port); if (err_slot < 0) goto failall; @@ -2026,7 +2759,7 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) */ if (err_slot == -1) { if (ahci_port_softreset(ap) != 0 && - ahci_port_portreset(ap) != 0) { + ahci_port_portreset(ap, 0) != 0) { printf("%s: couldn't reset after NCQ error, " "disabling device.\n", PORTNAME(ap)); goto fatal; @@ -2056,6 +2789,35 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) #endif } + /* ATI SBx00 AHCI controllers respond to PMP probes with IPMS interrupts + * when there's a normal SATA device attached. + */ + if ((ap->ap_state == AP_S_PMP_PROBE) && + (ap->ap_sc->sc_flags & AHCI_F_IPMS_PROBE) && + (is & AHCI_PREG_IS_IPMS)) { + slot = AHCI_PREG_CMD_CCS(ahci_pread(ap, AHCI_PREG_CMD)); + DPRINTF(AHCI_D_INTR, "%s: slot %d received IPMS\n", + PORTNAME(ap), slot); + + ccb = &ap->ap_ccbs[slot]; + ccb->ccb_xa.state = ATA_S_ERROR; + + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IPMS); + is &= ~AHCI_PREG_IS_IPMS; + } + + /* ignore IFS errors while resetting a PMP port */ + if ((is & AHCI_PREG_IS_IFS) /*&& ap->ap_pmp_ignore_ifs*/) { + DPRINTF(AHCI_D_INTR, "%s: ignoring IFS while resetting PMP " + "port\n", PORTNAME(ap)); + + need_restart = 1; + ahci_pwrite(ap, AHCI_PREG_SERR, -1); + ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IFS); + is &= ~AHCI_PREG_IS_IFS; + goto failall; + } + /* Check for remaining errors - they are fatal. */ if (is & (AHCI_PREG_IS_TFES | AHCI_PREG_IS_HBFS | AHCI_PREG_IS_IFS | AHCI_PREG_IS_OFS | AHCI_PREG_IS_UFS)) { @@ -2122,6 +2884,14 @@ failall: sizeof(struct ahci_rfis), BUS_DMASYNC_POSTREAD); *active &= ~(1 << ccb->ccb_slot); + /* Copy the rfis into the ccb if we were asked for it */ + if (ccb->ccb_xa.state == ATA_S_ONCHIP && + ccb->ccb_xa.flags & ATA_F_GET_RFIS) { + memcpy(&ccb->ccb_xa.rfis, + ap->ap_rfis->rfis, + sizeof(struct ata_fis_d2h)); + } + ccb->ccb_done(ccb); processed |= 1 << ccb->ccb_slot; @@ -2148,6 +2918,11 @@ failall: "re-enabling%s slots %08x\n", PORTNAME(ap), ap->ap_sactive ? " NCQ" : "", ci_saved); + /* XXX FBS: + * - need to set AHCI_PREG_FBS_DEV for each command + * - can't do multiple commands with a single write to + * AHCI_PREG_CI + */ if (ap->ap_sactive) ahci_pwrite(ap, AHCI_PREG_SACT, ci_saved); ahci_pwrite(ap, AHCI_PREG_CI, ci_saved); @@ -2266,8 +3041,60 @@ ahci_put_err_ccb(struct ahci_ccb *ccb) #endif } +struct ahci_ccb * +ahci_get_pmp_ccb(struct ahci_port *ap) +{ + struct ahci_ccb *ccb; + u_int32_t sact; + + /* some PMP commands need to be issued on slot 1, + * particularly the command that clears SRST and + * fetches the device signature. + * + * ensure the chip is idle and ccb 1 is available. + */ + splassert(IPL_BIO); + + sact = ahci_pread(ap, AHCI_PREG_SACT); + if (sact != 0) + printf("ahci_get_pmp_ccb; SACT %08x != 0\n", sact); + KASSERT(ahci_pread(ap, AHCI_PREG_CI) == 0); + + ccb = &ap->ap_ccbs[1]; + KASSERT(ccb->ccb_xa.state == ATA_S_PUT); + ccb->ccb_xa.flags = 0; + ccb->ccb_done = ahci_pmp_cmd_done; + + mtx_enter(&ap->ap_ccb_mtx); + TAILQ_REMOVE(&ap->ap_ccb_free, ccb, ccb_entry); + mtx_leave(&ap->ap_ccb_mtx); + + return ccb; +} + +void +ahci_put_pmp_ccb(struct ahci_ccb *ccb) +{ + struct ahci_port *ap = ccb->ccb_port; + u_int32_t sact; + + /* make sure this is the right ccb */ + KASSERT(ccb == &ap->ap_ccbs[1]); + + /* No commands may be active on the chip */ + sact = ahci_pread(ap, AHCI_PREG_SACT); + if (sact != 0) + printf("ahci_port_err_ccb_restore but SACT %08x != 0?\n", sact); + KASSERT(ahci_pread(ap, AHCI_PREG_CI) == 0); + + ccb->ccb_xa.state = ATA_S_PUT; + mtx_enter(&ap->ap_ccb_mtx); + TAILQ_INSERT_TAIL(&ap->ap_ccb_free, ccb, ccb_entry); + mtx_leave(&ap->ap_ccb_mtx); +} + int -ahci_port_read_ncq_error(struct ahci_port *ap, int *err_slotp) +ahci_port_read_ncq_error(struct ahci_port *ap, int *err_slotp, int pmp_port) { struct ahci_ccb *ccb; struct ahci_cmd_hdr *cmd_slot; @@ -2294,7 +3121,7 @@ ahci_port_read_ncq_error(struct ahci_port *ap, int *err_slotp) fis = (struct ata_fis_h2d *)ccb->ccb_cmd_table->cfis; fis->type = ATA_FIS_TYPE_H2D; - fis->flags = ATA_H2D_FLAGS_CMD; + fis->flags = ATA_H2D_FLAGS_CMD | pmp_port; fis->command = ATA_C_READ_LOG_EXT; fis->lba_low = 0x10; /* queued error log page (10h) */ fis->sector_count = 1; /* number of sectors (1) */ @@ -2304,6 +3131,7 @@ ahci_port_read_ncq_error(struct ahci_port *ap, int *err_slotp) fis->device = 0; cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ + cmd_slot->flags |= htole16(pmp_port << AHCI_CMD_LIST_FLAG_PMP_SHIFT); if (ahci_load_prdt(ccb) != 0) { rc = ENOMEM; /* XXX caller must abort all commands */ @@ -2472,24 +3300,27 @@ ahci_pwait_eq(struct ahci_port *ap, bus_size_t r, u_int32_t mask, } int -ahci_ata_probe(void *xsc, int port) +ahci_ata_probe(void *xsc, int port, int lun) { struct ahci_softc *sc = xsc; struct ahci_port *ap = sc->sc_ports[port]; - u_int32_t sig; if (ap == NULL) return (ATA_PORT_T_NONE); - sig = ahci_pread(ap, AHCI_PREG_SIG); - if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) - return (ATA_PORT_T_ATAPI); - else - return (ATA_PORT_T_DISK); + if (lun != 0) { + int pmp_port = lun - 1; + if (pmp_port >= ap->ap_pmp_ports) { + return (ATA_PORT_T_NONE); + } + return (ahci_pmp_port_probe(ap, pmp_port)); + } else { + return (ahci_port_signature(ap)); + } } void -ahci_ata_free(void *xsc, int port) +ahci_ata_free(void *xsc, int port, int lun) { } @@ -2538,6 +3369,8 @@ ahci_ata_cmd(struct ata_xfer *xa) cmd_slot = ccb->ccb_cmd_hdr; cmd_slot->flags = htole16(5); /* FIS length (in DWORDs) */ + cmd_slot->flags |= htole16(xa->pmp_port << + AHCI_CMD_LIST_FLAG_PMP_SHIFT); if (xa->flags & ATA_F_WRITE) cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W); @@ -2572,6 +3405,19 @@ failcmd: } void +ahci_pmp_cmd_done(struct ahci_ccb *ccb) +{ + struct ata_xfer *xa = &ccb->ccb_xa; + + if (xa->state == ATA_S_ONCHIP || xa->state == ATA_S_ERROR) + ahci_issue_pending_commands(ccb->ccb_port, + xa->flags & ATA_F_NCQ); + + xa->state = ATA_S_COMPLETE; +} + + +void ahci_ata_cmd_done(struct ahci_ccb *ccb) { struct ata_xfer *xa = &ccb->ccb_xa; @@ -2649,9 +3495,9 @@ ahci_ata_cmd_timeout(void *arg) /* Reset port to abort running command. */ if (ccb_was_started) { DPRINTF(AHCI_D_TIMEOUT, "%s: resetting port to abort%s command " - "in slot %d, active %08x\n", PORTNAME(ap), ncq_cmd ? " NCQ" - : "", ccb->ccb_slot, *active); - if (ahci_port_softreset(ap) != 0 && ahci_port_portreset(ap) + "in slot %d, pmp port %d, active %08x\n", PORTNAME(ap), + ncq_cmd ? " NCQ" : "", ccb->ccb_slot, xa->pmp_port, *active); + if (ahci_port_softreset(ap) != 0 && ahci_port_portreset(ap, 0) != 0) { printf("%s: failed to reset port during timeout " "handling, disabling it\n", PORTNAME(ap)); @@ -2690,3 +3536,129 @@ ahci_empty_done(struct ahci_ccb *ccb) { ccb->ccb_xa.state = ATA_S_COMPLETE; } + +int +ahci_pmp_read(struct ahci_port *ap, int target, int which, u_int32_t *datap) +{ + struct ahci_ccb *ccb; + struct ata_fis_h2d *fis; + int error; + + ccb = ahci_get_pmp_ccb(ap); + if (ccb == NULL) { + printf("%s: NULL ccb!\n", PORTNAME(ap)); + return (1); + } + ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_GET_RFIS; + ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT; + ccb->ccb_xa.state = ATA_S_PENDING; + + bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table)); + fis = (struct ata_fis_h2d *)ccb->ccb_cmd_table->cfis; + fis->type = ATA_FIS_TYPE_H2D; + fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT; + fis->command = ATA_C_READ_PM; + fis->features = which; + fis->device = target | ATA_H2D_DEVICE_LBA; + fis->control = ATA_FIS_CONTROL_4BIT; + + if (ahci_poll(ccb, 1000, ahci_pmp_probe_timeout) != 0) { + error = 1; + } else { + *datap = ccb->ccb_xa.rfis.sector_count | + (ccb->ccb_xa.rfis.lba_low << 8) | + (ccb->ccb_xa.rfis.lba_mid << 16) | + (ccb->ccb_xa.rfis.lba_high << 24); + error = 0; + } + ahci_put_pmp_ccb(ccb); + return (error); +} + +int +ahci_pmp_write(struct ahci_port *ap, int target, int which, u_int32_t data) +{ + struct ahci_ccb *ccb; + struct ata_fis_h2d *fis; + int error; + + ccb = ahci_get_pmp_ccb(ap); + if (ccb == NULL) { + printf("%s: NULL ccb!\n", PORTNAME(ap)); + return (1); + } + ccb->ccb_xa.flags = ATA_F_POLL; + ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT; + ccb->ccb_xa.state = ATA_S_PENDING; + + bzero(ccb->ccb_cmd_table, sizeof(struct ahci_cmd_table)); + fis = (struct ata_fis_h2d *)ccb->ccb_cmd_table->cfis; + fis->type = ATA_FIS_TYPE_H2D; + fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT; + fis->command = ATA_C_WRITE_PM; + fis->features = which; + fis->device = target | ATA_H2D_DEVICE_LBA; + fis->sector_count = (u_int8_t)data; + fis->lba_low = (u_int8_t)(data >> 8); + fis->lba_mid = (u_int8_t)(data >> 16); + fis->lba_high = (u_int8_t)(data >> 24); + fis->control = ATA_FIS_CONTROL_4BIT; + + error = ahci_poll(ccb, 1000, ahci_pmp_probe_timeout); + ahci_put_pmp_ccb(ccb); + return (error); +} + +int +ahci_pmp_phy_status(struct ahci_port *ap, int target, u_int32_t *datap) +{ + int error; + + error = ahci_pmp_read(ap, target, SATA_PMREG_SSTS, datap); + if (error == 0) + error = ahci_pmp_write(ap, target, SATA_PMREG_SERR, -1); + if (error) + *datap = 0; + + return (error); +} + +int +ahci_pmp_identify(struct ahci_port *ap, int *ret_nports) +{ + u_int32_t chipid; + u_int32_t rev; + u_int32_t nports; + u_int32_t features; + u_int32_t enabled; + int s; + + s = splbio(); + + if (ahci_pmp_read(ap, 15, 0, &chipid) || + ahci_pmp_read(ap, 15, 1, &rev) || + ahci_pmp_read(ap, 15, 2, &nports) || + ahci_pmp_read(ap, 15, SATA_PMREG_FEA, &features) || + ahci_pmp_read(ap, 15, SATA_PMREG_FEAEN, &enabled)) { + printf("%s: port multiplier identification failed\n", + PORTNAME(ap)); + splx(s); + return (1); + } + splx(s); + + nports &= 0x0F; + + /* ignore SEMB port on SiI3726 port multiplier chips */ + if (chipid == 0x37261095) { + nports--; + } + + printf("%s: port multiplier found: chip=%08x rev=0x%b nports=%d, " + "features: 0x%b, enabled: 0x%b\n", PORTNAME(ap), chipid, rev, + SATA_PFMT_PM_REV, nports, features, SATA_PFMT_PM_FEA, enabled, + SATA_PFMT_PM_FEA); + + *ret_nports = nports; + return (0); +} |