summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorChristopher Pascoe <pascoe@cvs.openbsd.org>2007-04-07 14:38:48 +0000
committerChristopher Pascoe <pascoe@cvs.openbsd.org>2007-04-07 14:38:48 +0000
commit50ebb156ba0e470c523f33fab9732d0b0b3c5f26 (patch)
tree06de14205aa9b1f8033d3e07037aa9fd53712bd2 /sys
parent7bee71b610a2c9a77c8a47c859664cdf320f19d6 (diff)
Rework command issue/completion flow to be more like AHCI's and switch to
using interrupts to detect command completion. Report on command timeouts (but no recovery yet).
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/ic/sili.c163
1 files changed, 145 insertions, 18 deletions
diff --git a/sys/dev/ic/sili.c b/sys/dev/ic/sili.c
index fe044c157e8..f65911f00d2 100644
--- a/sys/dev/ic/sili.c
+++ b/sys/dev/ic/sili.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sili.c,v 1.22 2007/04/07 14:15:14 pascoe Exp $ */
+/* $OpenBSD: sili.c,v 1.23 2007/04/07 14:38:47 pascoe Exp $ */
/*
* Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
@@ -35,6 +35,7 @@
#ifdef SILI_DEBUG
#define SILI_D_VERBOSE (1<<0)
+#define SILI_D_INTR (1<<1)
int silidebug = SILI_D_VERBOSE;
@@ -75,6 +76,8 @@ struct sili_port {
TAILQ_HEAD(, sili_ccb) sp_free_ccbs;
+ volatile u_int32_t sp_active;
+
#ifdef SILI_DEBUG
char sp_name[16];
#define PORTNAME(_sp) ((_sp)->sp_name)
@@ -138,6 +141,11 @@ void sili_post_indirect(struct sili_port *,
u_int32_t sili_signature(struct sili_port *, u_int);
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 *);
+
+/* port interrupt handler */
+u_int32_t sili_port_intr(struct sili_port *, u_int32_t);
/* atascsi interface */
int sili_ata_probe(void *, int);
@@ -153,6 +161,7 @@ struct atascsi_methods sili_atascsi_methods = {
/* completion paths */
void sili_ata_cmd_done(struct sili_ccb *);
+void sili_ata_cmd_timeout(void *);
int
sili_attach(struct sili_softc *sc)
@@ -188,14 +197,74 @@ sili_detach(struct sili_softc *sc, int flags)
return (0);
}
+u_int32_t
+sili_port_intr(struct sili_port *sp, u_int32_t slotmask)
+{
+ u_int32_t is, pss_saved, pss_masked;
+ u_int32_t processed = 0;
+ volatile u_int32_t *active = &sp->sp_active;
+ int slot;
+ struct sili_ccb *ccb;
+
+ is = sili_pread(sp, SILI_PREG_IS);
+ pss_saved = sili_pread(sp, SILI_PREG_PSS);
+
+ /* Ack port interrupt only if checking all command slots. */
+ if (slotmask == SILI_PREG_PSS_ALL_SLOTS)
+ sili_pwrite(sp, SILI_PREG_IS, is);
+
+#ifdef SILI_DEBUG
+ if ((pss_saved & SILI_PREG_PSS_ALL_SLOTS) != *active ||
+ ((is >> 16) & ~SILI_PREG_IS_CMDCOMP)) {
+ DPRINTF(SILI_D_INTR, "%s: IS: 0x%08x (0x%b), PSS: %08x, "
+ "active: %08x\n", PORTNAME(sp), is, is >> 16, SILI_PFMT_IS,
+ pss_saved, *active);
+ }
+#endif
+
+ /* Only interested in slot status bits. */
+ pss_saved &= SILI_PREG_PSS_ALL_SLOTS;
+
+ /* Command slot is complete if its bit in PSS is 0 but 1 in active. */
+ pss_masked = ~pss_saved & *active;
+ while (pss_masked) {
+ slot = ffs(pss_masked) - 1;
+ ccb = &sp->sp_ccbs[slot];
+ pss_masked &= ~(1 << slot);
+
+ DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s\n",
+ PORTNAME(sp), slot, ccb->ccb_xa.state == ATA_S_ERROR ?
+ " (error)" : "");
+
+ *active &= ~(1 << slot);
+ sili_ata_cmd_done(ccb);
+
+ processed |= 1 << slot;
+ }
+
+ return processed;
+}
+
int
sili_intr(void *arg)
{
-#if 0
struct sili_softc *sc = arg;
-#endif
+ u_int32_t is;
+ int port;
- return (0);
+ is = sili_read(sc, SILI_REG_GIS);
+ if (is == 0)
+ return (0);
+ sili_write(sc, SILI_REG_GIS, is);
+ DPRINTF(SILI_D_INTR, "sili_intr, GIS: %08x\n", is);
+
+ while (is & SILI_REG_GIS_PIS_MASK) {
+ port = ffs(is) - 1;
+ sili_port_intr(&sc->sc_ports[port], SILI_PREG_PSS_ALL_SLOTS);
+ is &= ~(1 << port);
+ }
+
+ return (1);
}
int
@@ -520,6 +589,7 @@ sili_ata_probe(void *xsc, int port)
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_PORTRESET);
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_A32B);
+ sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_NOINTCLR);
if (!sili_pwait_eq(sp, SILI_PREG_SSTS, SATA_SStatus_DET,
SATA_SStatus_DET_DEV, 1000))
@@ -562,6 +632,11 @@ sili_ata_probe(void *xsc, int port)
if (sili_ccb_alloc(sp) != 0)
return (ATA_PORT_T_NONE);
+ /* 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 |
+ SILI_PREG_IE_CMDCOMP);
+
return (port_type);
}
@@ -592,7 +667,7 @@ sili_ata_cmd(struct ata_xfer *xa)
} else {
ata = ccb->ccb_cmd;
- ata->control = htole16(SILI_PRB_INTERRUPT_MASK);
+ ata->control = 0;
sgl = ata->sgl;
sgllen = sizeofa(ata->sgl);
@@ -604,17 +679,21 @@ sili_ata_cmd(struct ata_xfer *xa)
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_cmds),
xa->tag * SILI_CMD_LEN, SILI_CMD_LEN, BUS_DMASYNC_PREWRITE);
+ timeout_set(&xa->stimeout, sili_ata_cmd_timeout, ccb);
+
xa->state = ATA_S_PENDING;
-#if 0
- sili_post_direct(sp, 0, &ccb->ccb_prb, sizeof(ccb->ccb_prb));
-#endif
+ if (xa->flags & ATA_F_POLL) {
+ sili_poll(ccb, xa->timeout, sili_ata_cmd_timeout);
+ return (ATA_COMPLETE);
+ }
+
+ timeout_add(&xa->stimeout, (xa->timeout * hz) / 1000);
+
s = splbio();
- sili_post_indirect(sp, ccb);
- sili_ata_cmd_done(ccb);
+ sili_start(sp, ccb);
splx(s);
-
- return (ATA_COMPLETE);
+ return (ATA_QUEUED);
failcmd:
s = splbio();
@@ -633,11 +712,7 @@ sili_ata_cmd_done(struct sili_ccb *ccb)
splassert(IPL_BIO);
- if (!sili_pwait_eq(sp, SILI_PREG_PSS, (1 << ccb->ccb_xa.tag), 0,
- ccb->ccb_xa.timeout)) {
- printf("%s: cmd failed\n", PORTNAME(sp));
- xa->state = ATA_S_ERROR;
- }
+ timeout_del(&xa->stimeout);
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_cmds),
xa->tag * SILI_CMD_LEN, SILI_CMD_LEN, BUS_DMASYNC_POSTWRITE);
@@ -646,8 +721,21 @@ sili_ata_cmd_done(struct sili_ccb *ccb)
if (xa->state == ATA_S_ONCHIP)
xa->state = ATA_S_COMPLETE;
+#ifdef DIAGNOSTIC
+ else if (xa->state != ATA_S_ERROR && xa->state != ATA_S_TIMEOUT)
+ printf("%s: invalid ata_xfer state %02x in sili_ata_cmd_done, "
+ "slot %d\n", PORTNAME(sp), xa->state, xa->tag);
+#endif
+ if (xa->state != ATA_S_TIMEOUT)
+ xa->complete(xa);
+}
- xa->complete(xa);
+void
+sili_ata_cmd_timeout(void *xccb)
+{
+ struct sili_ccb *ccb = xccb;
+
+ printf("%s: ccb %p timed out\n", PORTNAME(ccb->ccb_port), ccb);
}
int
@@ -730,6 +818,45 @@ sili_unload(struct sili_ccb *ccb)
xa->resid = xa->datalen - sili_pread(sp, SILI_PREG_RX_COUNT(xa->tag));
}
+int
+sili_poll(struct sili_ccb *ccb, int timeout, void (*timeout_fn)(void *))
+{
+ struct sili_port *sp = ccb->ccb_port;
+ int s;
+
+ s = splbio();
+ sili_start(sp, ccb);
+ do {
+ if (ISSET(sili_port_intr(sp, SILI_PREG_PSS_ALL_SLOTS),
+ 1 << ccb->ccb_xa.tag)) {
+ splx(s);
+ return (0);
+ }
+
+ delay(1000);
+ } while (--timeout > 0);
+
+ /* Run timeout while at splbio, otherwise sili_intr could interfere. */
+ if (timeout_fn != NULL)
+ timeout_fn(ccb);
+
+ splx(s);
+
+ return (1);
+}
+
+void
+sili_start(struct sili_port *sp, struct sili_ccb *ccb)
+{
+ int slot = ccb->ccb_xa.tag;
+
+ splassert(IPL_BIO);
+ KASSERT(ccb->ccb_xa.state == ATA_S_PENDING);
+
+ sp->sp_active |= 1 << slot;
+ sili_post_indirect(sp, ccb);
+}
+
struct ata_xfer *
sili_ata_get_xfer(void *xsc, int port)
{