summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDale Rahn <drahn@cvs.openbsd.org>2011-01-26 21:41:01 +0000
committerDale Rahn <drahn@cvs.openbsd.org>2011-01-26 21:41:01 +0000
commitc2a07274c68726b615403e10632b43beb8eac23d (patch)
treeeecf311d61eff5cac481c733225373a69df908bf
parentc39e9b783d98196c42dbb88a452736eb8bc25f28 (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.
-rw-r--r--sys/dev/ata/atascsi.c448
-rw-r--r--sys/dev/ata/atascsi.h30
-rw-r--r--sys/dev/ic/sili.c828
-rw-r--r--sys/dev/ic/silireg.h27
-rw-r--r--sys/dev/pci/ahci.c1138
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);
+}