diff options
author | David Gwynne <dlg@cvs.openbsd.org> | 2009-09-14 00:03:29 +0000 |
---|---|---|
committer | David Gwynne <dlg@cvs.openbsd.org> | 2009-09-14 00:03:29 +0000 |
commit | 4248e34dda8652ddd46e2885282d566d8168375c (patch) | |
tree | 82bb5feb84ccef77da33aa27666949da6540aff0 /sys/scsi/scsi_base.c | |
parent | 4d196425328e9719c037d7a94b4758298d0b2ad6 (diff) |
rework the scsi midlayer to start addressing some problems i have
with it which became extremely annoying with what mpath wants to
do.
the major change is a new interface for submitting scsi commands.
previously the only way for drivers like sd, cd, st, etc to push
commands onto the hardware was via scsi_scsi_cmd(). the problem
with scsi_scsi_cmd is that it doesnt tell the caller if the command
failed, was queued, or completed unless you shoved a buf down with
it. this is important for mpath which wants to know what the physical
path to the device did so it can report it back to the midlayer
which called it.
this provides a new api which lets drivers like cd/sd/st/mpath etc
allocate an xs, fill it in, and provide a completion routine which
the midlayer will call with the state of the command when it is
finished with it. the caller is then responsible for freeing the
xs.
from the hba side of thing, the return code from the scsi_cmd
entrypoint is largely ignored now, and it is now always the
responsibility of the hba driver to call scsi_done when it has
completed the io, rather than returning COMPLETE and expecting the
midlayer to do it for you.
i have emulated scsi_scsi_cmd on top of this new api so existing
users of it will continue to work. sd(4) has been reworked to use
the new api directly to both demonstrate its use and test that the
new api actually does work.
this diff was mostly written in a day at f2k9. thanks to miod for poking
through hba drivers to help mitigate against fallout from the change to
the COMPLETE semantic. this has been reviewed by krw who didnt spot
anything wrong.
thanks to dave del debbio for testing.
ok deraadt@
Diffstat (limited to 'sys/scsi/scsi_base.c')
-rw-r--r-- | sys/scsi/scsi_base.c | 466 |
1 files changed, 125 insertions, 341 deletions
diff --git a/sys/scsi/scsi_base.c b/sys/scsi/scsi_base.c index 68861a2ad36..76a247a2e62 100644 --- a/sys/scsi/scsi_base.c +++ b/sys/scsi/scsi_base.c @@ -1,4 +1,4 @@ -/* $OpenBSD: scsi_base.c,v 1.135 2009/09/02 14:19:50 dlg Exp $ */ +/* $OpenBSD: scsi_base.c,v 1.136 2009/09/14 00:03:28 dlg Exp $ */ /* $NetBSD: scsi_base.c,v 1.43 1997/04/02 02:29:36 mycroft Exp $ */ /* @@ -50,15 +50,14 @@ #include <scsi/scsi_disk.h> #include <scsi/scsiconf.h> -static __inline struct scsi_xfer *scsi_make_xs(struct scsi_link *, - struct scsi_generic *, int cmdlen, u_char *data_addr, - int datalen, int retries, int timeout, struct buf *, int flags); static __inline void asc2ascii(u_int8_t, u_int8_t ascq, char *result, size_t len); int sc_err1(struct scsi_xfer *); int scsi_interpret_sense(struct scsi_xfer *); char *scsi_decode_sense(struct scsi_sense_data *, int); +void scsi_xs_done(struct scsi_xfer *); + /* Values for flag parameter to scsi_decode_sense. */ #define DECODE_SENSE_KEY 1 #define DECODE_ASC_ASCQ 2 @@ -95,6 +94,7 @@ scsi_init() /* Initialize the scsi_xfer pool. */ pool_init(&scsi_xfer_pool, sizeof(struct scsi_xfer), 0, 0, 0, "scxspl", NULL); + pool_setipl(&scsi_xfer_pool, IPL_BIO); /* Initialize the scsi_plug pool */ pool_init(&scsi_plug_pool, sizeof(struct scsi_plug), 0, 0, 0, "scsiplug", NULL); @@ -183,42 +183,43 @@ scsi_deinit() */ struct scsi_xfer * -scsi_get_xs(struct scsi_link *sc_link, int flags) +scsi_xs_get(struct scsi_link *link, int flags) { - struct scsi_xfer *xs; - int s; - - SC_DEBUG(sc_link, SDEV_DB3, ("scsi_get_xs\n")); + struct scsi_xfer *xs; - s = splbio(); - while (sc_link->openings == 0) { - SC_DEBUG(sc_link, SDEV_DB3, ("sleeping\n")); - if ((flags & SCSI_NOSLEEP) != 0) { - splx(s); - return (NULL); - } - sc_link->flags |= SDEV_WAITING; - if (tsleep(sc_link, PRIBIO|PCATCH, "getxs", 0)) { - /* Bail out on getting a signal. */ - sc_link->flags &= ~SDEV_WAITING; - splx(s); + mtx_enter(&link->mtx); + while (link->openings == 0) { + if (!ISSET(flags, SCSI_NOSLEEP)) { + mtx_leave(&link->mtx); return (NULL); } + + SET(link->flags, SDEV_WAITING); + msleep(link, &link->mtx, PRIBIO, "getxs", 0); } - SC_DEBUG(sc_link, SDEV_DB3, ("calling pool_get\n")); + link->openings--; + mtx_leave(&link->mtx); + + /* pool is shared, link mtx is not */ xs = pool_get(&scsi_xfer_pool, - ((flags & SCSI_NOSLEEP) != 0 ? PR_NOWAIT : PR_WAITOK)); - if (xs != NULL) { - bzero(xs, sizeof(*xs)); - sc_link->openings--; - xs->flags = flags; + ISSET(flags, SCSI_NOSLEEP) ? PR_NOWAIT : PR_WAITOK); + if (xs == NULL) { + mtx_enter(&link->mtx); + link->openings++; + mtx_leave(&link->mtx); } else { - sc_print_addr(sc_link); - printf("cannot allocate scsi xs\n"); + xs->flags = flags; + xs->sc_link = link; + xs->retries = SCSI_RETRIES; + xs->timeout = 0; + bzero(&xs->cmdstore, sizeof(xs->cmdstore)); + xs->cmd = &xs->cmdstore; + xs->cmdlen = 0; + xs->data = NULL; + xs->datalen = 0; + xs->resid = 0; + xs->bp = NULL; } - splx(s); - - SC_DEBUG(sc_link, SDEV_DB3, ("returning\n")); return (xs); } @@ -229,75 +230,21 @@ scsi_get_xs(struct scsi_link *sc_link, int flags) * If another process is waiting for an xs, do a wakeup, let it proceed */ void -scsi_free_xs(struct scsi_xfer *xs, int start) +scsi_xs_put(struct scsi_xfer *xs) { - struct scsi_link *sc_link = xs->sc_link; - - splassert(IPL_BIO); - - SC_DEBUG(sc_link, SDEV_DB3, ("scsi_free_xs\n")); + struct scsi_link *link = xs->sc_link; pool_put(&scsi_xfer_pool, xs); - sc_link->openings++; + + mtx_enter(&link->mtx); + link->openings++; /* If someone is waiting for scsi_xfer, wake them up. */ - if ((sc_link->flags & SDEV_WAITING) != 0) { - sc_link->flags &= ~SDEV_WAITING; - wakeup(sc_link); - } else if (start && sc_link->device->start) { - SC_DEBUG(sc_link, SDEV_DB2, - ("calling private start()\n")); - (*(sc_link->device->start)) (sc_link->device_softc); + if (ISSET(link->flags, SDEV_WAITING)) { + CLR(link->flags, SDEV_WAITING); + wakeup(link); } -} - -/* - * Make a scsi_xfer, and return a pointer to it. - */ -static __inline struct scsi_xfer * -scsi_make_xs(struct scsi_link *sc_link, struct scsi_generic *scsi_cmd, - int cmdlen, u_char *data_addr, int datalen, int retries, int timeout, - struct buf *bp, int flags) -{ - struct scsi_xfer *xs; - - if ((xs = scsi_get_xs(sc_link, flags)) == NULL) - return (NULL); - - /* - * Fill out the scsi_xfer structure. We don't know whose context - * the cmd is in, so copy it. - */ - xs->sc_link = sc_link; - bcopy(scsi_cmd, &xs->cmdstore, cmdlen); - xs->cmd = &xs->cmdstore; - xs->cmdlen = cmdlen; - xs->data = data_addr; - xs->datalen = datalen; - xs->retries = retries; - xs->timeout = timeout; - xs->bp = bp; - - /* - * Set the LUN in the CDB if it fits in the three bits available. This - * may only be needed if we have an older device. However, we also set - * it for more modern SCSI devices "just in case". The old code - * assumed everything newer than SCSI-2 would not need it, but why risk - * it? This was the old conditional: - * - * if ((SCSISPC(sc_link->inqdata.version) <= 2)) - */ - xs->cmd->bytes[0] &= ~SCSI_CMD_LUN_MASK; - if (sc_link->lun < 8) - xs->cmd->bytes[0] |= ((sc_link->lun << SCSI_CMD_LUN_SHIFT) & - SCSI_CMD_LUN_MASK); - -#ifdef SCSIDEBUG - if ((sc_link->flags & SDEV_DB1) != 0) - show_scsi_xs(xs); -#endif /* SCSIDEBUG */ - - return (xs); + mtx_leave(&link->mtx); } /* @@ -759,181 +706,49 @@ scsi_report_luns(struct scsi_link *sc_link, int selectreport, return (error); } -/* - * This routine is called by the scsi interrupt when the transfer is complete. - */ void -scsi_done(struct scsi_xfer *xs) +scsi_xs_exec(struct scsi_xfer *xs) { - struct scsi_link *sc_link = xs->sc_link; - struct buf *bp; - int error; - - SC_DEBUG(sc_link, SDEV_DB2, ("scsi_done\n")); - - splassert(IPL_BIO); - - xs->flags |= ITSDONE; - - /* - * If it's a user level request, bypass all usual completion processing, - * let the user work it out.. We take reponsibility for freeing the - * xs when the user returns (and restarting the device's queue). - */ - if ((xs->flags & SCSI_USER) != 0) { - SC_DEBUG(sc_link, SDEV_DB3, ("calling user done()\n")); - scsi_user_done(xs); /* to take a copy of the sense etc. */ - SC_DEBUG(sc_link, SDEV_DB3, ("returned from user done()\n")); - - scsi_free_xs(xs, 1); /* restarts queue too */ - SC_DEBUG(sc_link, SDEV_DB3, ("returning to adapter\n")); - return; - } - - if (!((xs->flags & (SCSI_NOSLEEP | SCSI_POLL)) == SCSI_NOSLEEP)) { - /* - * if it's a normal upper level request, then ask - * the upper level code to handle error checking - * rather than doing it here at interrupt time - */ - wakeup(xs); - return; - } - - /* - * Go and handle errors now. - * If it returns ERESTART then we should RETRY - */ -retry: - error = sc_err1(xs); - if (error == ERESTART) { - switch ((*(sc_link->adapter->scsi_cmd)) (xs)) { - case SUCCESSFULLY_QUEUED: - return; - - case TRY_AGAIN_LATER: - xs->error = XS_BUSY; - /* FALLTHROUGH */ - case COMPLETE: - goto retry; - } - } - - bp = xs->bp; - if (bp != NULL) { - if (error) { - bp->b_error = error; - bp->b_flags |= B_ERROR; - bp->b_resid = bp->b_bcount; - } else { - bp->b_error = 0; - bp->b_resid = xs->resid; - } - } - - if (sc_link->device->done) { - /* - * Tell the device the operation is actually complete. - * No more will happen with this xfer. This for - * notification of the upper-level driver only; they - * won't be returning any meaningful information to us. - */ - (*sc_link->device->done)(xs); - } - scsi_free_xs(xs, 1); - if (bp != NULL) - biodone(bp); -} - -int -scsi_execute_xs(struct scsi_xfer *xs) -{ - int error, flags, rslt, s; + int rv; + int s; xs->flags &= ~ITSDONE; xs->error = XS_NOERROR; xs->resid = xs->datalen; xs->status = 0; - /* - * Do the transfer. If we are polling we will return: - * COMPLETE, Was poll, and scsi_done has been called - * TRY_AGAIN_LATER, Adapter short resources, try again - * - * if under full steam (interrupts) it will return: - * SUCCESSFULLY_QUEUED, will do a wakeup when complete - * TRY_AGAIN_LATER, (as for polling) - * After the wakeup, we must still check if it succeeded - * - * If we have a SCSI_NOSLEEP (typically because we have a buf) - * we just return. All the error processing and the buffer - * code both expect us to return straight to them, so as soon - * as the command is queued, return. - */ - - /* - * We save the flags here because the xs structure may already - * be freed by scsi_done by the time adapter->scsi_cmd returns. - * - * scsi_done is responsible for freeing the xs if either - * (flags & (SCSI_NOSLEEP | SCSI_POLL)) == SCSI_NOSLEEP - * -or- - * (flags & SCSI_USER) != 0 - * - * Note: SCSI_USER must always be called with SCSI_NOSLEEP - * and never with SCSI_POLL, so the second expression should be - * is equivalent to the first. - */ + rv = xs->sc_link->adapter->scsi_cmd(xs); + switch (rv) { + case NO_CCB: + if (!ISSET(xs->flags, SCSI_POLL) && xs->retries-- > 0) { + timeout_set(&xs->stimeout, + (void (*)(void *))scsi_xs_exec, xs); + timeout_add(&xs->stimeout, 1); + break; + } + /* FALLTHROUGH */ + case TRY_AGAIN_LATER: + /* hahaha, sif... */ - flags = xs->flags; -#ifdef DIAGNOSTIC - if ((flags & (SCSI_USER | SCSI_NOSLEEP)) == SCSI_USER) - panic("scsi_execute_xs: USER without NOSLEEP"); - if ((flags & (SCSI_USER | SCSI_POLL)) == (SCSI_USER | SCSI_POLL)) - panic("scsi_execute_xs: USER with POLL"); -#endif -retry: - rslt = (*(xs->sc_link->adapter->scsi_cmd))(xs); - switch (rslt) { - case SUCCESSFULLY_QUEUED: - if ((flags & (SCSI_NOSLEEP | SCSI_POLL)) == SCSI_NOSLEEP) - return (EJUSTRETURN); -#ifdef DIAGNOSTIC - if (flags & SCSI_NOSLEEP) - panic("scsi_execute_xs: NOSLEEP and POLL"); -#endif + xs->error = XS_DRIVER_STUFFUP; s = splbio(); - /* Since the xs is active we can't bail out on a signal. */ - while ((xs->flags & ITSDONE) == 0) - tsleep(xs, PRIBIO + 1, "scsicmd", 0); + scsi_done(xs); splx(s); - /* FALLTHROUGH */ - case COMPLETE: /* Polling command completed ok */ - if ((flags & (SCSI_NOSLEEP | SCSI_POLL)) == SCSI_NOSLEEP) - return (EJUSTRETURN); - if (xs->bp) - return (EJUSTRETURN); - doit: - SC_DEBUG(xs->sc_link, SDEV_DB3, ("back in cmd()\n")); - if ((error = sc_err1(xs)) != ERESTART) - return (error); - goto retry; - - case TRY_AGAIN_LATER: /* adapter resource shortage */ - xs->error = XS_BUSY; - goto doit; + break; + } +} - case NO_CCB: - return (EAGAIN); +/* + * This routine is called by the adapter when its xs handling is done. + */ +void +scsi_done(struct scsi_xfer *xs) +{ + splassert(IPL_BIO); - default: - panic("scsi_execute_xs: invalid return code (%#x)", rslt); - } + xs->flags |= ITSDONE; -#ifdef DIAGNOSTIC - panic("scsi_execute_xs: impossible"); -#endif - return (EINVAL); + xs->done(xs); } /* @@ -943,54 +758,81 @@ retry: * to associate with the transfer, we need that too. */ int -scsi_scsi_cmd(struct scsi_link *sc_link, struct scsi_generic *scsi_cmd, +scsi_scsi_cmd(struct scsi_link *link, struct scsi_generic *scsi_cmd, int cmdlen, u_char *data_addr, int datalen, int retries, int timeout, struct buf *bp, int flags) { - struct scsi_xfer *xs; - int error; - int s; - - SC_DEBUG(sc_link, SDEV_DB2, ("scsi_cmd\n")); + struct scsi_xfer *xs; + int error; + int s; #ifdef DIAGNOSTIC if (bp != NULL && (flags & SCSI_NOSLEEP) == 0) panic("scsi_scsi_cmd: buffer without nosleep"); #endif - if ((xs = scsi_make_xs(sc_link, scsi_cmd, cmdlen, data_addr, datalen, - retries, timeout, bp, flags)) == NULL) + xs = scsi_xs_get(link, flags); + if (xs == NULL) return (ENOMEM); -#ifdef SCSIDEBUG - if ((sc_link->flags & SDEV_DB1) != 0) - if (xs->datalen && (xs->flags & SCSI_DATA_OUT)) - show_mem(xs->data, min(64, xs->datalen)); -#endif /* SCSIDEBUG */ + memcpy(xs->cmd, scsi_cmd, cmdlen); + xs->cmdlen = cmdlen; + xs->data = data_addr; + xs->datalen = datalen; + xs->retries = retries; + xs->timeout = timeout; - error = scsi_execute_xs(xs); + xs->done = scsi_xs_done; -#ifdef SCSIDEBUG - if ((sc_link->flags & SDEV_DB1) != 0) - if (xs->datalen && (xs->flags & SCSI_DATA_IN)) - show_mem(xs->data, min(64, xs->datalen)); -#endif /* SCSIDEBUG */ + do { + scsi_xs_exec(xs); + if (!ISSET(xs->flags, SCSI_POLL)) { + s = splbio(); + while (!ISSET(xs->flags, ITSDONE)) + tsleep(xs, PRIBIO, "scsicmd", 0); + splx(s); + } - if (error == EJUSTRETURN) - return (0); + error = sc_err1(xs); + } while (error == ERESTART); - s = splbio(); + if (bp != NULL) { + if (error) { + bp->b_error = error; + bp->b_flags |= B_ERROR; + bp->b_resid = bp->b_bcount; + } else { + bp->b_error = 0; + bp->b_resid = xs->resid; + } - if (error == EAGAIN) - scsi_free_xs(xs, 0); /* Don't restart queue. */ - else - scsi_free_xs(xs, 1); + s = splbio(); + biodone(bp); + splx(s); + } + + if (link->device->done) { + /* + * Tell the device the operation is actually complete. + * No more will happen with this xfer. This for + * notification of the upper-level driver only; they + * won't be returning any meaningful information to us. + */ + link->device->done(xs); + } - splx(s); + scsi_xs_put(xs); return (error); } +void +scsi_xs_done(struct scsi_xfer *xs) +{ + if (!ISSET(xs->flags, SCSI_POLL)) + wakeup_one(xs); +} + int sc_err1(struct scsi_xfer *xs) { @@ -1110,10 +952,6 @@ scsi_interpret_sense(struct scsi_xfer *xs) sense->flags & SSD_EOM ? 1 : 0, sense->flags & SSD_FILEMARK ? 1 : 0, sense->extra_len)); -#ifdef SCSIDEBUG - if ((sc_link->flags & SDEV_DB1) != 0) - show_mem((u_char *)&xs->sense, sizeof xs->sense); -#endif /* SCSIDEBUG */ /* * If the device has its own error handler, call it first. @@ -2006,57 +1844,3 @@ scsi_decode_sense(struct scsi_sense_data *sense, int flag) return (rqsbuf); } - -#ifdef SCSIDEBUG -/* - * Given a scsi_xfer, dump the request, in all its glory - */ -void -show_scsi_xs(struct scsi_xfer *xs) -{ - u_char *b = (u_char *) xs->cmd; - int i = 0; - - sc_print_addr(xs->sc_link); - - printf("xs(%p): ", xs); - - printf("flg(0x%x)", xs->flags); - printf("sc_link(%p)", xs->sc_link); - printf("retr(0x%x)", xs->retries); - printf("timo(0x%x)", xs->timeout); - printf("cmd(%p)", xs->cmd); - printf("len(0x%x)", xs->cmdlen); - printf("data(%p)", xs->data); - printf("len(0x%x)", xs->datalen); - printf("res(0x%x)", xs->resid); - printf("err(0x%x)", xs->error); - printf("bp(%p)\n", xs->bp); - - printf("command: "); - - if ((xs->flags & SCSI_RESET) == 0) { - while (i < xs->cmdlen) { - if (i) - printf(","); - printf("%x", b[i++]); - } - printf("-[%d bytes]\n", xs->datalen); - } else - printf("-RESET-\n"); -} - -void -show_mem(u_char *address, int num) -{ - int x; - - printf("------------------------------"); - for (x = 0; x < num; x++) { - if ((x % 16) == 0) - printf("\n%03d: ", x); - printf("%02x ", *address++); - } - printf("\n------------------------------\n"); -} -#endif /* SCSIDEBUG */ |