/* $NetBSD: scsipi_base.c,v 1.193 2024/10/29 15:50:07 nat Exp $ */ /*- * Copyright (c) 1998, 1999, 2000, 2002, 2003, 2004 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Charles M. Hannum; by Jason R. Thorpe of the Numerical Aerospace * Simulation Facility, NASA Ames Research Center. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: scsipi_base.c,v 1.193 2024/10/29 15:50:07 nat Exp $"); #ifdef _KERNEL_OPT #include "opt_scsi.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SDT_PROVIDER_DEFINE(scsi); SDT_PROBE_DEFINE3(scsi, base, tag, get, "struct scsipi_xfer *"/*xs*/, "uint8_t"/*tag*/, "uint8_t"/*type*/); SDT_PROBE_DEFINE3(scsi, base, tag, put, "struct scsipi_xfer *"/*xs*/, "uint8_t"/*tag*/, "uint8_t"/*type*/); SDT_PROBE_DEFINE3(scsi, base, adapter, request__start, "struct scsipi_channel *"/*chan*/, "scsipi_adapter_req_t"/*req*/, "void *"/*arg*/); SDT_PROBE_DEFINE3(scsi, base, adapter, request__done, "struct scsipi_channel *"/*chan*/, "scsipi_adapter_req_t"/*req*/, "void *"/*arg*/); SDT_PROBE_DEFINE1(scsi, base, queue, batch__start, "struct scsipi_channel *"/*chan*/); SDT_PROBE_DEFINE2(scsi, base, queue, run, "struct scsipi_channel *"/*chan*/, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, queue, batch__done, "struct scsipi_channel *"/*chan*/); SDT_PROBE_DEFINE1(scsi, base, xfer, execute, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, enqueue, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, done, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, redone, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, complete, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, restart, "struct scsipi_xfer *"/*xs*/); SDT_PROBE_DEFINE1(scsi, base, xfer, free, "struct scsipi_xfer *"/*xs*/); static int scsipi_complete(struct scsipi_xfer *); static void scsipi_request_sense(struct scsipi_xfer *); static int scsipi_enqueue(struct scsipi_xfer *); static void scsipi_run_queue(struct scsipi_channel *chan); static void scsipi_completion_thread(void *); static void scsipi_get_tag(struct scsipi_xfer *); static void scsipi_put_tag(struct scsipi_xfer *); static int scsipi_get_resource(struct scsipi_channel *); static void scsipi_put_resource(struct scsipi_channel *); static void scsipi_async_event_max_openings(struct scsipi_channel *, struct scsipi_max_openings *); static void scsipi_async_event_channel_reset(struct scsipi_channel *); static void scsipi_channel_freeze_locked(struct scsipi_channel *, int); static void scsipi_adapter_lock(struct scsipi_adapter *adapt); static void scsipi_adapter_unlock(struct scsipi_adapter *adapt); static void scsipi_update_timeouts(struct scsipi_xfer *xs); static struct pool scsipi_xfer_pool; int scsipi_xs_count = 0; /* * scsipi_init: * * Called when a scsibus or atapibus is attached to the system * to initialize shared data structures. */ void scsipi_init(void) { static int scsipi_init_done; if (scsipi_init_done) return; scsipi_init_done = 1; /* Initialize the scsipi_xfer pool. */ pool_init(&scsipi_xfer_pool, sizeof(struct scsipi_xfer), 0, 0, 0, "scxspl", NULL, IPL_BIO); pool_prime(&scsipi_xfer_pool, 1); scsipi_ioctl_init(); } /* * scsipi_channel_init: * * Initialize a scsipi_channel when it is attached. */ int scsipi_channel_init(struct scsipi_channel *chan) { struct scsipi_adapter *adapt = chan->chan_adapter; int i; /* Initialize shared data. */ scsipi_init(); /* Initialize the queues. */ TAILQ_INIT(&chan->chan_queue); TAILQ_INIT(&chan->chan_complete); for (i = 0; i < SCSIPI_CHAN_PERIPH_BUCKETS; i++) LIST_INIT(&chan->chan_periphtab[i]); /* * Create the asynchronous completion thread. */ if (kthread_create(PRI_NONE, 0, NULL, scsipi_completion_thread, chan, &chan->chan_thread, "%s", chan->chan_name)) { aprint_error_dev(adapt->adapt_dev, "unable to create completion thread for " "channel %d\n", chan->chan_channel); panic("scsipi_channel_init"); } return 0; } /* * scsipi_channel_shutdown: * * Shutdown a scsipi_channel. */ void scsipi_channel_shutdown(struct scsipi_channel *chan) { mutex_enter(chan_mtx(chan)); /* * Shut down the completion thread. */ chan->chan_tflags |= SCSIPI_CHANT_SHUTDOWN; cv_broadcast(chan_cv_complete(chan)); /* * Now wait for the thread to exit. */ while (chan->chan_thread != NULL) cv_wait(chan_cv_thread(chan), chan_mtx(chan)); mutex_exit(chan_mtx(chan)); } static uint32_t scsipi_chan_periph_hash(uint64_t t, uint64_t l) { uint32_t hash; hash = hash32_buf(&t, sizeof(t), HASH32_BUF_INIT); hash = hash32_buf(&l, sizeof(l), hash); return hash & SCSIPI_CHAN_PERIPH_HASHMASK; } /* * scsipi_insert_periph: * * Insert a periph into the channel. */ void scsipi_insert_periph(struct scsipi_channel *chan, struct scsipi_periph *periph) { uint32_t hash; hash = scsipi_chan_periph_hash(periph->periph_target, periph->periph_lun); mutex_enter(chan_mtx(chan)); LIST_INSERT_HEAD(&chan->chan_periphtab[hash], periph, periph_hash); mutex_exit(chan_mtx(chan)); } /* * scsipi_remove_periph: * * Remove a periph from the channel. */ void scsipi_remove_periph(struct scsipi_channel *chan, struct scsipi_periph *periph) { LIST_REMOVE(periph, periph_hash); } /* * scsipi_lookup_periph: * * Lookup a periph on the specified channel. */ static struct scsipi_periph * scsipi_lookup_periph_internal(struct scsipi_channel *chan, int target, int lun, bool lock) { struct scsipi_periph *periph; uint32_t hash; if (target >= chan->chan_ntargets || lun >= chan->chan_nluns) return NULL; hash = scsipi_chan_periph_hash(target, lun); if (lock) mutex_enter(chan_mtx(chan)); LIST_FOREACH(periph, &chan->chan_periphtab[hash], periph_hash) { if (periph->periph_target == target && periph->periph_lun == lun) break; } if (lock) mutex_exit(chan_mtx(chan)); return periph; } struct scsipi_periph * scsipi_lookup_periph_locked(struct scsipi_channel *chan, int target, int lun) { return scsipi_lookup_periph_internal(chan, target, lun, false); } struct scsipi_periph * scsipi_lookup_periph(struct scsipi_channel *chan, int target, int lun) { return scsipi_lookup_periph_internal(chan, target, lun, true); } /* * scsipi_get_resource: * * Allocate a single xfer `resource' from the channel. * * NOTE: Must be called with channel lock held */ static int scsipi_get_resource(struct scsipi_channel *chan) { struct scsipi_adapter *adapt = chan->chan_adapter; if (chan->chan_flags & SCSIPI_CHAN_OPENINGS) { if (chan->chan_openings > 0) { chan->chan_openings--; return 1; } return 0; } if (adapt->adapt_openings > 0) { adapt->adapt_openings--; return 1; } return 0; } /* * scsipi_grow_resources: * * Attempt to grow resources for a channel. If this succeeds, * we allocate one for our caller. * * NOTE: Must be called with channel lock held */ static inline int scsipi_grow_resources(struct scsipi_channel *chan) { if (chan->chan_flags & SCSIPI_CHAN_CANGROW) { if ((chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) { mutex_exit(chan_mtx(chan)); scsipi_adapter_request(chan, ADAPTER_REQ_GROW_RESOURCES, NULL); mutex_enter(chan_mtx(chan)); return scsipi_get_resource(chan); } /* * ask the channel thread to do it. It'll have to thaw the * queue */ scsipi_channel_freeze_locked(chan, 1); chan->chan_tflags |= SCSIPI_CHANT_GROWRES; cv_broadcast(chan_cv_complete(chan)); return 0; } return 0; } /* * scsipi_put_resource: * * Free a single xfer `resource' to the channel. * * NOTE: Must be called with channel lock held */ static void scsipi_put_resource(struct scsipi_channel *chan) { struct scsipi_adapter *adapt = chan->chan_adapter; if (chan->chan_flags & SCSIPI_CHAN_OPENINGS) chan->chan_openings++; else adapt->adapt_openings++; } /* * scsipi_get_tag: * * Get a tag ID for the specified xfer. * * NOTE: Must be called with channel lock held */ static void scsipi_get_tag(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; int bit, tag; u_int word; KASSERT(mutex_owned(chan_mtx(periph->periph_channel))); bit = 0; /* XXX gcc */ for (word = 0; word < PERIPH_NTAGWORDS; word++) { bit = ffs(periph->periph_freetags[word]); if (bit != 0) break; } #ifdef DIAGNOSTIC if (word == PERIPH_NTAGWORDS) { scsipi_printaddr(periph); printf("no free tags\n"); panic("scsipi_get_tag"); } #endif bit -= 1; periph->periph_freetags[word] &= ~(1U << bit); tag = (word << 5) | bit; /* XXX Should eventually disallow this completely. */ if (tag >= periph->periph_openings) { scsipi_printaddr(periph); printf("WARNING: tag %d greater than available openings %d\n", tag, periph->periph_openings); } xs->xs_tag_id = tag; SDT_PROBE3(scsi, base, tag, get, xs, xs->xs_tag_id, xs->xs_tag_type); } /* * scsipi_put_tag: * * Put the tag ID for the specified xfer back into the pool. * * NOTE: Must be called with channel lock held */ static void scsipi_put_tag(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; int word, bit; KASSERT(mutex_owned(chan_mtx(periph->periph_channel))); SDT_PROBE3(scsi, base, tag, put, xs, xs->xs_tag_id, xs->xs_tag_type); word = xs->xs_tag_id >> 5; bit = xs->xs_tag_id & 0x1f; periph->periph_freetags[word] |= (1U << bit); } /* * scsipi_get_xs: * * Allocate an xfer descriptor and associate it with the * specified peripheral. If the peripheral has no more * available command openings, we either block waiting for * one to become available, or fail. * * When this routine is called with the channel lock held * the flags must include XS_CTL_NOSLEEP. */ struct scsipi_xfer * scsipi_get_xs(struct scsipi_periph *periph, int flags) { struct scsipi_xfer *xs; bool lock = (flags & XS_CTL_NOSLEEP) == 0; SC_DEBUG(periph, SCSIPI_DB3, ("scsipi_get_xs\n")); KASSERT(!cold); #ifdef DIAGNOSTIC /* * URGENT commands can never be ASYNC. */ if ((flags & (XS_CTL_URGENT|XS_CTL_ASYNC)) == (XS_CTL_URGENT|XS_CTL_ASYNC)) { scsipi_printaddr(periph); printf("URGENT and ASYNC\n"); panic("scsipi_get_xs"); } #endif /* * Wait for a command opening to become available. Rules: * * - All xfers must wait for an available opening. * Exception: URGENT xfers can proceed when * active == openings, because we use the opening * of the command we're recovering for. * - if the periph has sense pending, only URGENT & REQSENSE * xfers may proceed. * * - If the periph is recovering, only URGENT xfers may * proceed. * * - If the periph is currently executing a recovery * command, URGENT commands must block, because only * one recovery command can execute at a time. */ if (lock) mutex_enter(chan_mtx(periph->periph_channel)); for (;;) { if (flags & XS_CTL_URGENT) { if (periph->periph_active > periph->periph_openings) goto wait_for_opening; if (periph->periph_flags & PERIPH_SENSE) { if ((flags & XS_CTL_REQSENSE) == 0) goto wait_for_opening; } else { if ((periph->periph_flags & PERIPH_RECOVERY_ACTIVE) != 0) goto wait_for_opening; periph->periph_flags |= PERIPH_RECOVERY_ACTIVE; } break; } if (periph->periph_active >= periph->periph_openings || (periph->periph_flags & PERIPH_RECOVERING) != 0) goto wait_for_opening; periph->periph_active++; KASSERT(mutex_owned(chan_mtx(periph->periph_channel))); break; wait_for_opening: if (flags & XS_CTL_NOSLEEP) { KASSERT(!lock); return NULL; } KASSERT(lock); SC_DEBUG(periph, SCSIPI_DB3, ("sleeping\n")); periph->periph_flags |= PERIPH_WAITING; cv_wait(periph_cv_periph(periph), chan_mtx(periph->periph_channel)); } if (lock) mutex_exit(chan_mtx(periph->periph_channel)); SC_DEBUG(periph, SCSIPI_DB3, ("calling pool_get\n")); xs = pool_get(&scsipi_xfer_pool, ((flags & XS_CTL_NOSLEEP) != 0 ? PR_NOWAIT : PR_WAITOK)); if (xs == NULL) { if (lock) mutex_enter(chan_mtx(periph->periph_channel)); if (flags & XS_CTL_URGENT) { if ((flags & XS_CTL_REQSENSE) == 0) periph->periph_flags &= ~PERIPH_RECOVERY_ACTIVE; } else periph->periph_active--; if (lock) mutex_exit(chan_mtx(periph->periph_channel)); scsipi_printaddr(periph); printf("unable to allocate %sscsipi_xfer\n", (flags & XS_CTL_URGENT) ? "URGENT " : ""); } SC_DEBUG(periph, SCSIPI_DB3, ("returning\n")); if (xs != NULL) { memset(xs, 0, sizeof(*xs)); callout_init(&xs->xs_callout, 0); xs->xs_periph = periph; xs->xs_control = flags; xs->xs_status = 0; if ((flags & XS_CTL_NOSLEEP) == 0) mutex_enter(chan_mtx(periph->periph_channel)); TAILQ_INSERT_TAIL(&periph->periph_xferq, xs, device_q); KASSERT(mutex_owned(chan_mtx(periph->periph_channel))); if ((flags & XS_CTL_NOSLEEP) == 0) mutex_exit(chan_mtx(periph->periph_channel)); } return xs; } /* * scsipi_put_xs: * * Release an xfer descriptor, decreasing the outstanding command * count for the peripheral. If there is a thread waiting for * an opening, wake it up. If not, kick any queued I/O the * peripheral may have. * * NOTE: Must be called with channel lock held */ void scsipi_put_xs(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; int flags = xs->xs_control; SDT_PROBE1(scsi, base, xfer, free, xs); SC_DEBUG(periph, SCSIPI_DB3, ("scsipi_free_xs\n")); KASSERT(mutex_owned(chan_mtx(periph->periph_channel))); TAILQ_REMOVE(&periph->periph_xferq, xs, device_q); callout_destroy(&xs->xs_callout); pool_put(&scsipi_xfer_pool, xs); #ifdef DIAGNOSTIC if ((periph->periph_flags & PERIPH_RECOVERY_ACTIVE) != 0 && periph->periph_active == 0) { scsipi_printaddr(periph); printf("recovery without a command to recovery for\n"); panic("scsipi_put_xs"); } #endif if (flags & XS_CTL_URGENT) { if ((flags & XS_CTL_REQSENSE) == 0) periph->periph_flags &= ~PERIPH_RECOVERY_ACTIVE; } else periph->periph_active--; if (periph->periph_active == 0 && (periph->periph_flags & PERIPH_WAITDRAIN) != 0) { periph->periph_flags &= ~PERIPH_WAITDRAIN; cv_broadcast(periph_cv_active(periph)); } if (periph->periph_flags & PERIPH_WAITING) { periph->periph_flags &= ~PERIPH_WAITING; cv_broadcast(periph_cv_periph(periph)); } else { if (periph->periph_switch->psw_start != NULL && device_is_active(periph->periph_dev)) { SC_DEBUG(periph, SCSIPI_DB2, ("calling private start()\n")); (*periph->periph_switch->psw_start)(periph); } } } /* * scsipi_channel_freeze: * * Freeze a channel's xfer queue. */ void scsipi_channel_freeze(struct scsipi_channel *chan, int count) { bool lock = chan_running(chan) > 0; if (lock) mutex_enter(chan_mtx(chan)); chan->chan_qfreeze += count; if (lock) mutex_exit(chan_mtx(chan)); } static void scsipi_channel_freeze_locked(struct scsipi_channel *chan, int count) { chan->chan_qfreeze += count; } /* * scsipi_channel_thaw: * * Thaw a channel's xfer queue. */ void scsipi_channel_thaw(struct scsipi_channel *chan, int count) { bool lock = chan_running(chan) > 0; if (lock) mutex_enter(chan_mtx(chan)); chan->chan_qfreeze -= count; /* * Don't let the freeze count go negative. * * Presumably the adapter driver could keep track of this, * but it might just be easier to do this here so as to allow * multiple callers, including those outside the adapter driver. */ if (chan->chan_qfreeze < 0) { chan->chan_qfreeze = 0; } if (lock) mutex_exit(chan_mtx(chan)); /* * until the channel is running */ if (!lock) return; /* * Kick the channel's queue here. Note, we may be running in * interrupt context (softclock or HBA's interrupt), so the adapter * driver had better not sleep. */ if (chan->chan_qfreeze == 0) scsipi_run_queue(chan); } /* * scsipi_channel_timed_thaw: * * Thaw a channel after some time has expired. This will also * run the channel's queue if the freeze count has reached 0. */ void scsipi_channel_timed_thaw(void *arg) { struct scsipi_channel *chan = arg; scsipi_channel_thaw(chan, 1); } /* * scsipi_periph_freeze: * * Freeze a device's xfer queue. */ void scsipi_periph_freeze_locked(struct scsipi_periph *periph, int count) { periph->periph_qfreeze += count; } /* * scsipi_periph_thaw: * * Thaw a device's xfer queue. */ void scsipi_periph_thaw_locked(struct scsipi_periph *periph, int count) { periph->periph_qfreeze -= count; #ifdef DIAGNOSTIC if (periph->periph_qfreeze < 0) { static const char pc[] = "periph freeze count < 0"; scsipi_printaddr(periph); printf("%s\n", pc); panic(pc); } #endif if (periph->periph_qfreeze == 0 && (periph->periph_flags & PERIPH_WAITING) != 0) cv_broadcast(periph_cv_periph(periph)); } void scsipi_periph_freeze(struct scsipi_periph *periph, int count) { mutex_enter(chan_mtx(periph->periph_channel)); scsipi_periph_freeze_locked(periph, count); mutex_exit(chan_mtx(periph->periph_channel)); } void scsipi_periph_thaw(struct scsipi_periph *periph, int count) { mutex_enter(chan_mtx(periph->periph_channel)); scsipi_periph_thaw_locked(periph, count); mutex_exit(chan_mtx(periph->periph_channel)); } /* * scsipi_periph_timed_thaw: * * Thaw a device after some time has expired. */ void scsipi_periph_timed_thaw(void *arg) { struct scsipi_periph *periph = arg; struct scsipi_channel *chan = periph->periph_channel; callout_stop(&periph->periph_callout); mutex_enter(chan_mtx(chan)); scsipi_periph_thaw_locked(periph, 1); if ((periph->periph_channel->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) { /* * Kick the channel's queue here. Note, we're running in * interrupt context (softclock), so the adapter driver * had better not sleep. */ mutex_exit(chan_mtx(chan)); scsipi_run_queue(periph->periph_channel); } else { /* * Tell the completion thread to kick the channel's queue here. */ periph->periph_channel->chan_tflags |= SCSIPI_CHANT_KICK; cv_broadcast(chan_cv_complete(chan)); mutex_exit(chan_mtx(chan)); } } /* * scsipi_wait_drain: * * Wait for a periph's pending xfers to drain. */ void scsipi_wait_drain(struct scsipi_periph *periph) { struct scsipi_channel *chan = periph->periph_channel; mutex_enter(chan_mtx(chan)); while (periph->periph_active != 0) { periph->periph_flags |= PERIPH_WAITDRAIN; cv_wait(periph_cv_active(periph), chan_mtx(chan)); } mutex_exit(chan_mtx(chan)); } /* * scsipi_kill_pending: * * Kill off all pending xfers for a periph. * * NOTE: Must be called with channel lock held */ void scsipi_kill_pending(struct scsipi_periph *periph) { struct scsipi_channel *chan = periph->periph_channel; (*chan->chan_bustype->bustype_kill_pending)(periph); while (periph->periph_active != 0) { periph->periph_flags |= PERIPH_WAITDRAIN; cv_wait(periph_cv_active(periph), chan_mtx(chan)); } } /* * scsipi_print_cdb: * prints a command descriptor block (for debug purpose, error messages, * SCSIVERBOSE, ...) */ void scsipi_print_cdb(struct scsipi_generic *cmd) { int i, j; printf("0x%02x", cmd->opcode); switch (CDB_GROUPID(cmd->opcode)) { case CDB_GROUPID_0: j = CDB_GROUP0; break; case CDB_GROUPID_1: j = CDB_GROUP1; break; case CDB_GROUPID_2: j = CDB_GROUP2; break; case CDB_GROUPID_3: j = CDB_GROUP3; break; case CDB_GROUPID_4: j = CDB_GROUP4; break; case CDB_GROUPID_5: j = CDB_GROUP5; break; case CDB_GROUPID_6: j = CDB_GROUP6; break; case CDB_GROUPID_7: j = CDB_GROUP7; break; default: j = 0; } if (j == 0) j = sizeof (cmd->bytes); for (i = 0; i < j-1; i++) /* already done the opcode */ printf(" %02x", cmd->bytes[i]); } /* * scsipi_interpret_sense: * * Look at the returned sense and act on the error, determining * the unix error number to pass back. (0 = report no error) * * NOTE: If we return ERESTART, we are expected to have * thawed the device! * * THIS IS THE DEFAULT ERROR HANDLER FOR SCSI DEVICES. */ int scsipi_interpret_sense(struct scsipi_xfer *xs) { struct scsi_sense_data *sense; struct scsipi_periph *periph = xs->xs_periph; u_int8_t key; int error; u_int32_t info; static const char *error_mes[] = { "soft error (corrected)", "not ready", "medium error", "non-media hardware failure", "illegal request", "unit attention", "readonly device", "no data found", "vendor unique", "copy aborted", "command aborted", "search returned equal", "volume overflow", "verify miscompare", "unknown error key" }; sense = &xs->sense.scsi_sense; #ifdef SCSIPI_DEBUG if (periph->periph_flags & SCSIPI_DB1) { int count, len; scsipi_printaddr(periph); printf(" sense debug information:\n"); printf("\tcode 0x%x valid %d\n", SSD_RCODE(sense->response_code), sense->response_code & SSD_RCODE_VALID ? 1 : 0); printf("\tseg 0x%x key 0x%x ili 0x%x eom 0x%x fmark 0x%x\n", sense->segment, SSD_SENSE_KEY(sense->flags), sense->flags & SSD_ILI ? 1 : 0, sense->flags & SSD_EOM ? 1 : 0, sense->flags & SSD_FILEMARK ? 1 : 0); printf("\ninfo: 0x%x 0x%x 0x%x 0x%x followed by %d " "extra bytes\n", sense->info[0], sense->info[1], sense->info[2], sense->info[3], sense->extra_len); len = SSD_ADD_BYTES_LIM(sense); printf("\textra (up to %d bytes): ", len); for (count = 0; count < len; count++) printf("0x%x ", sense->csi[count]); printf("\n"); } #endif /* * If the periph has its own error handler, call it first. * If it returns a legit error value, return that, otherwise * it wants us to continue with normal error processing. */ if (periph->periph_switch->psw_error != NULL) { SC_DEBUG(periph, SCSIPI_DB2, ("calling private err_handler()\n")); error = (*periph->periph_switch->psw_error)(xs); if (error != EJUSTRETURN) return error; } /* otherwise use the default */ switch (SSD_RCODE(sense->response_code)) { /* * Old SCSI-1 and SASI devices respond with * codes other than 70. */ case 0x00: /* no error (command completed OK) */ return 0; case 0x04: /* drive not ready after it was selected */ if ((periph->periph_flags & PERIPH_REMOVABLE) != 0) periph->periph_flags &= ~PERIPH_MEDIA_LOADED; if ((xs->xs_control & XS_CTL_IGNORE_NOT_READY) != 0) return 0; /* XXX - display some sort of error here? */ return EIO; case 0x20: /* invalid command */ if ((xs->xs_control & XS_CTL_IGNORE_ILLEGAL_REQUEST) != 0) return 0; return EINVAL; case 0x25: /* invalid LUN (Adaptec ACB-4000) */ return EACCES; /* * If it's code 70, use the extended stuff and * interpret the key */ case 0x71: /* delayed error */ scsipi_printaddr(periph); key = SSD_SENSE_KEY(sense->flags); printf(" DEFERRED ERROR, key = 0x%x\n", key); /* FALLTHROUGH */ case 0x70: if ((sense->response_code & SSD_RCODE_VALID) != 0) info = _4btol(sense->info); else info = 0; key = SSD_SENSE_KEY(sense->flags); switch (key) { case SKEY_NO_SENSE: case SKEY_RECOVERED_ERROR: if (xs->resid == xs->datalen && xs->datalen) { /* * Why is this here? */ xs->resid = 0; /* not short read */ } error = 0; break; case SKEY_EQUAL: error = 0; break; case SKEY_NOT_READY: if ((periph->periph_flags & PERIPH_REMOVABLE) != 0) periph->periph_flags &= ~PERIPH_MEDIA_LOADED; if ((xs->xs_control & XS_CTL_IGNORE_NOT_READY) != 0) return 0; if (sense->asc == 0x3A) { error = ENODEV; /* Medium not present */ if (xs->xs_control & XS_CTL_SILENT_NODEV) return error; } else error = EIO; if ((xs->xs_control & XS_CTL_SILENT) != 0) return error; break; case SKEY_ILLEGAL_REQUEST: if ((xs->xs_control & XS_CTL_IGNORE_ILLEGAL_REQUEST) != 0) return 0; /* * Handle the case where a device reports * Logical Unit Not Supported during discovery. */ if ((xs->xs_control & XS_CTL_DISCOVERY) != 0 && sense->asc == 0x25 && sense->ascq == 0x00) return EINVAL; if ((xs->xs_control & XS_CTL_SILENT) != 0) return EIO; error = EINVAL; break; case SKEY_UNIT_ATTENTION: if (sense->asc == 0x29 && sense->ascq == 0x00) { /* device or bus reset */ return ERESTART; } if ((periph->periph_flags & PERIPH_REMOVABLE) != 0) periph->periph_flags &= ~PERIPH_MEDIA_LOADED; if ((xs->xs_control & XS_CTL_IGNORE_MEDIA_CHANGE) != 0 || /* XXX Should reupload any transient state. */ (periph->periph_flags & PERIPH_REMOVABLE) == 0) { return ERESTART; } if ((xs->xs_control & XS_CTL_SILENT) != 0) return EIO; error = EIO; break; case SKEY_DATA_PROTECT: error = EROFS; break; case SKEY_BLANK_CHECK: error = 0; break; case SKEY_ABORTED_COMMAND: if (xs->xs_retries != 0) { xs->xs_retries--; error = ERESTART; } else error = EIO; break; case SKEY_VOLUME_OVERFLOW: error = ENOSPC; break; case SKEY_MEDIUM_ERROR: if (xs->xs_retries != 0) { xs->xs_retries--; error = ERESTART; } else error = EIO; break; default: error = EIO; break; } /* Print verbose decode if appropriate and possible */ if ((key == 0) || ((xs->xs_control & XS_CTL_SILENT) != 0) || (scsipi_print_sense(xs, 0) != 0)) return error; /* Print brief(er) sense information */ scsipi_printaddr(periph); printf("%s", error_mes[key - 1]); if ((sense->response_code & SSD_RCODE_VALID) != 0) { switch (key) { case SKEY_NOT_READY: case SKEY_ILLEGAL_REQUEST: case SKEY_UNIT_ATTENTION: case SKEY_DATA_PROTECT: break; case SKEY_BLANK_CHECK: printf(", requested size: %d (decimal)", info); break; case SKEY_ABORTED_COMMAND: if (xs->xs_retries) printf(", retrying"); printf(", cmd 0x%x, info 0x%x", xs->cmd->opcode, info); break; default: printf(", info = %d (decimal)", info); } } if (sense->extra_len != 0) { int n; printf(", data ="); for (n = 0; n < sense->extra_len; n++) printf(" %02x", sense->csi[n]); } printf("\n"); return error; /* * Some other code, just report it */ default: #if defined(SCSIDEBUG) || defined(DEBUG) { static const char *uc = "undecodable sense error"; int i; u_int8_t *cptr = (u_int8_t *) sense; scsipi_printaddr(periph); if (xs->cmd == &xs->cmdstore) { printf("%s for opcode 0x%x, data=", uc, xs->cmdstore.opcode); } else { printf("%s, data=", uc); } for (i = 0; i < sizeof (sense); i++) printf(" 0x%02x", *(cptr++) & 0xff); printf("\n"); } #else scsipi_printaddr(periph); printf("Sense Error Code 0x%x", SSD_RCODE(sense->response_code)); if ((sense->response_code & SSD_RCODE_VALID) != 0) { struct scsi_sense_data_unextended *usense = (struct scsi_sense_data_unextended *)sense; printf(" at block no. %d (decimal)", _3btol(usense->block)); } printf("\n"); #endif return EIO; } } /* * scsipi_test_unit_ready: * * Issue a `test unit ready' request. */ int scsipi_test_unit_ready(struct scsipi_periph *periph, int flags) { struct scsi_test_unit_ready cmd; int retries; /* some ATAPI drives don't support TEST UNIT READY. Sigh */ if (periph->periph_quirks & PQUIRK_NOTUR) return 0; if (flags & XS_CTL_DISCOVERY) retries = 0; else retries = SCSIPIRETRIES; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_TEST_UNIT_READY; return scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0, retries, 10000, NULL, flags); } static const struct scsipi_inquiry3_pattern { const char vendor[8]; const char product[16]; const char revision[4]; } scsipi_inquiry3_quirk[] = { { "ES-6600 ", "", "" }, }; static int scsipi_inquiry3_ok(const struct scsipi_inquiry_data *ib) { for (size_t i = 0; i < __arraycount(scsipi_inquiry3_quirk); i++) { const struct scsipi_inquiry3_pattern *q = &scsipi_inquiry3_quirk[i]; #define MATCH(field) \ (q->field[0] ? memcmp(ib->field, q->field, sizeof(ib->field)) == 0 : 1) if (MATCH(vendor) && MATCH(product) && MATCH(revision)) return 0; } return 1; } /* * scsipi_inquire: * * Ask the device about itself. */ int scsipi_inquire(struct scsipi_periph *periph, struct scsipi_inquiry_data *inqbuf, int flags) { struct scsipi_inquiry cmd; int error; int retries; if (flags & XS_CTL_DISCOVERY) retries = 0; else retries = SCSIPIRETRIES; /* * If we request more data than the device can provide, it SHOULD just * return a short response. However, some devices error with an * ILLEGAL REQUEST sense code, and yet others have even more special * failure modes (such as the GL641USB flash adapter, which goes loony * and sends corrupted CRCs). To work around this, and to bring our * behavior more in line with other OSes, we do a shorter inquiry, * covering all the SCSI-2 information, first, and then request more * data iff the "additional length" field indicates there is more. * - mycroft, 2003/10/16 */ memset(&cmd, 0, sizeof(cmd)); cmd.opcode = INQUIRY; cmd.length = SCSIPI_INQUIRY_LENGTH_SCSI2; error = scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)inqbuf, SCSIPI_INQUIRY_LENGTH_SCSI2, retries, 10000, NULL, flags | XS_CTL_DATA_IN); if (!error && inqbuf->additional_length > SCSIPI_INQUIRY_LENGTH_SCSI2 - 4) { if (scsipi_inquiry3_ok(inqbuf)) { #if 0 printf("inquire: addlen=%d, retrying\n", inqbuf->additional_length); #endif cmd.length = SCSIPI_INQUIRY_LENGTH_SCSI3; error = scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)inqbuf, SCSIPI_INQUIRY_LENGTH_SCSI3, retries, 10000, NULL, flags | XS_CTL_DATA_IN); #if 0 printf("inquire: error=%d\n", error); #endif } } #ifdef SCSI_OLD_NOINQUIRY /* * Kludge for the Adaptec ACB-4000 SCSI->MFM translator. * This board doesn't support the INQUIRY command at all. */ if (error == EINVAL || error == EACCES) { /* * Conjure up an INQUIRY response. */ inqbuf->device = (error == EINVAL ? SID_QUAL_LU_PRESENT : SID_QUAL_LU_NOTPRESENT) | T_DIRECT; inqbuf->dev_qual2 = 0; inqbuf->version = 0; inqbuf->response_format = SID_FORMAT_SCSI1; inqbuf->additional_length = SCSIPI_INQUIRY_LENGTH_SCSI2 - 4; inqbuf->flags1 = inqbuf->flags2 = inqbuf->flags3 = 0; memcpy(inqbuf->vendor, "ADAPTEC ACB-4000 ", 28); error = 0; } /* * Kludge for the Emulex MT-02 SCSI->QIC translator. * This board gives an empty response to an INQUIRY command. */ else if (error == 0 && inqbuf->device == (SID_QUAL_LU_PRESENT | T_DIRECT) && inqbuf->dev_qual2 == 0 && inqbuf->version == 0 && inqbuf->response_format == SID_FORMAT_SCSI1) { /* * Fill out the INQUIRY response. */ inqbuf->device = (SID_QUAL_LU_PRESENT | T_SEQUENTIAL); inqbuf->dev_qual2 = SID_REMOVABLE; inqbuf->additional_length = SCSIPI_INQUIRY_LENGTH_SCSI2 - 4; inqbuf->flags1 = inqbuf->flags2 = inqbuf->flags3 = 0; memcpy(inqbuf->vendor, "EMULEX MT-02 QIC ", 28); } #endif /* SCSI_OLD_NOINQUIRY */ return error; } /* * scsipi_prevent: * * Prevent or allow the user to remove the media */ int scsipi_prevent(struct scsipi_periph *periph, int type, int flags) { struct scsi_prevent_allow_medium_removal cmd; if (periph->periph_quirks & PQUIRK_NODOORLOCK) return 0; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL; cmd.how = type; return (scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0, SCSIPIRETRIES, 5000, NULL, flags)); } /* * scsipi_start: * * Send a START UNIT. */ int scsipi_start(struct scsipi_periph *periph, int type, int flags) { struct scsipi_start_stop cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = START_STOP; cmd.byte2 = 0x00; cmd.how = type; return scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0, SCSIPIRETRIES, (type & SSS_START) ? 60000 : 10000, NULL, flags); } /* * scsipi_mode_sense, scsipi_mode_sense_big: * get a sense page from a device */ int scsipi_mode_sense(struct scsipi_periph *periph, int byte2, int page, struct scsi_mode_parameter_header_6 *data, int len, int flags, int retries, int timeout) { struct scsi_mode_sense_6 cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MODE_SENSE_6; cmd.byte2 = byte2; cmd.page = page; cmd.length = len & 0xff; return scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_IN); } int scsipi_mode_sense_big(struct scsipi_periph *periph, int byte2, int page, struct scsi_mode_parameter_header_10 *data, int len, int flags, int retries, int timeout) { struct scsi_mode_sense_10 cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MODE_SENSE_10; cmd.byte2 = byte2; cmd.page = page; _lto2b(len, cmd.length); return scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_IN); } int scsipi_mode_select(struct scsipi_periph *periph, int byte2, struct scsi_mode_parameter_header_6 *data, int len, int flags, int retries, int timeout) { struct scsi_mode_select_6 cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MODE_SELECT_6; cmd.byte2 = byte2; cmd.length = len & 0xff; return scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_OUT); } int scsipi_mode_select_big(struct scsipi_periph *periph, int byte2, struct scsi_mode_parameter_header_10 *data, int len, int flags, int retries, int timeout) { struct scsi_mode_select_10 cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MODE_SELECT_10; cmd.byte2 = byte2; _lto2b(len, cmd.length); return scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_OUT); } /* * scsipi_get_opcodeinfo: * * query the device for supported commands and their timeout * building a timeout lookup table if timeout information is available. */ void scsipi_get_opcodeinfo(struct scsipi_periph *periph) { u_int8_t *data; int len = 16*1024; int rc; int retries; struct scsi_repsuppopcode cmd; /* refrain from asking for supported opcodes */ if (periph->periph_quirks & PQUIRK_NOREPSUPPOPC || periph->periph_type == T_PROCESSOR || /* spec. */ periph->periph_type == T_CDROM) /* spec. */ return; scsipi_free_opcodeinfo(periph); /* * query REPORT SUPPORTED OPERATION CODES * if OK * enumerate all codes * if timeout exists insert maximum into opcode table */ data = malloc(len, M_DEVBUF, M_WAITOK|M_ZERO); memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MAINTENANCE_IN; cmd.svcaction = RSOC_REPORT_SUPPORTED_OPCODES; cmd.repoption = RSOC_RCTD|RSOC_ALL; _lto4b(len, cmd.alloclen); /* loop to skip any UNIT ATTENTIONS at this point */ retries = 3; do { rc = scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)data, len, 0, 60000, NULL, XS_CTL_DATA_IN|XS_CTL_SILENT); #ifdef SCSIPI_DEBUG if (rc != 0) { SC_DEBUG(periph, SCSIPI_DB3, ("SCSI_MAINTENANCE_IN" "[RSOC_REPORT_SUPPORTED_OPCODES] command" " failed: rc=%d, retries=%d\n", rc, retries)); } #endif } while (rc == EIO && retries-- > 0); if (rc == 0) { int count; int dlen = _4btol(data); u_int8_t *c = data + 4; SC_DEBUG(periph, SCSIPI_DB3, ("supported opcode timeout-values loaded\n")); SC_DEBUG(periph, SCSIPI_DB3, ("CMD LEN SA spec nom. time cmd timeout\n")); struct scsipi_opcodes *tot = malloc(sizeof(struct scsipi_opcodes), M_DEVBUF, M_WAITOK|M_ZERO); count = 0; while (tot != NULL && dlen >= (int)sizeof(struct scsi_repsupopcode_all_commands_descriptor)) { struct scsi_repsupopcode_all_commands_descriptor *acd = (struct scsi_repsupopcode_all_commands_descriptor *)c; #ifdef SCSIPI_DEBUG int cdblen = _2btol((const u_int8_t *)&acd->cdblen); #endif dlen -= sizeof(struct scsi_repsupopcode_all_commands_descriptor); c += sizeof(struct scsi_repsupopcode_all_commands_descriptor); SC_DEBUG(periph, SCSIPI_DB3, ("0x%02x(%2d) ", acd->opcode, cdblen)); tot->opcode_info[acd->opcode].ti_flags = SCSIPI_TI_VALID; if (acd->flags & RSOC_ACD_SERVACTV) { SC_DEBUGN(periph, SCSIPI_DB3, ("0x%02x%02x ", acd->serviceaction[0], acd->serviceaction[1])); } else { SC_DEBUGN(periph, SCSIPI_DB3, (" ")); } if (acd->flags & RSOC_ACD_CTDP && dlen >= (int)sizeof(struct scsi_repsupopcode_timeouts_descriptor)) { struct scsi_repsupopcode_timeouts_descriptor *td = (struct scsi_repsupopcode_timeouts_descriptor *)c; long nomto = _4btol(td->nom_process_timeout); long cmdto = _4btol(td->cmd_process_timeout); long t = (cmdto > nomto) ? cmdto : nomto; dlen -= sizeof(struct scsi_repsupopcode_timeouts_descriptor); c += sizeof(struct scsi_repsupopcode_timeouts_descriptor); SC_DEBUGN(periph, SCSIPI_DB3, ("0x%02x %10ld %10ld", td->cmd_specific, nomto, cmdto)); if (t > tot->opcode_info[acd->opcode].ti_timeout) { tot->opcode_info[acd->opcode].ti_timeout = t; ++count; } } SC_DEBUGN(periph, SCSIPI_DB3,("\n")); } if (count > 0) { periph->periph_opcs = tot; } else { free(tot, M_DEVBUF); SC_DEBUG(periph, SCSIPI_DB3, ("no usable timeout values available\n")); } } else { SC_DEBUG(periph, SCSIPI_DB3, ("SCSI_MAINTENANCE_IN" "[RSOC_REPORT_SUPPORTED_OPCODES] failed error=%d" " - no device provided timeout " "values available\n", rc)); } free(data, M_DEVBUF); } /* * scsipi_update_timeouts: * Override timeout value if device/config provided * timeouts are available. */ static void scsipi_update_timeouts(struct scsipi_xfer *xs) { struct scsipi_opcodes *opcs; u_int8_t cmd; int timeout; struct scsipi_opinfo *oi; if (xs->timeout <= 0) { return; } opcs = xs->xs_periph->periph_opcs; if (opcs == NULL) { return; } cmd = xs->cmd->opcode; oi = &opcs->opcode_info[cmd]; timeout = 1000 * (int)oi->ti_timeout; if (timeout > xs->timeout && timeout < 86400000) { /* * pick up device configured timeouts if they * are longer than the requested ones but less * than a day */ #ifdef SCSIPI_DEBUG if ((oi->ti_flags & SCSIPI_TI_LOGGED) == 0) { SC_DEBUG(xs->xs_periph, SCSIPI_DB3, ("Overriding command 0x%02x " "timeout of %d with %d ms\n", cmd, xs->timeout, timeout)); oi->ti_flags |= SCSIPI_TI_LOGGED; } #endif xs->timeout = timeout; } } /* * scsipi_free_opcodeinfo: * * free the opcode information table */ void scsipi_free_opcodeinfo(struct scsipi_periph *periph) { if (periph->periph_opcs != NULL) { free(periph->periph_opcs, M_DEVBUF); } periph->periph_opcs = NULL; } /* * scsipi_done: * * This routine is called by an adapter's interrupt handler when * an xfer is completed. */ void scsipi_done(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; struct scsipi_channel *chan = periph->periph_channel; int freezecnt; SC_DEBUG(periph, SCSIPI_DB2, ("scsipi_done\n")); #ifdef SCSIPI_DEBUG if (periph->periph_dbflags & SCSIPI_DB1) show_scsipi_cmd(xs); #endif mutex_enter(chan_mtx(chan)); SDT_PROBE1(scsi, base, xfer, done, xs); /* * The resource this command was using is now free. */ if (xs->xs_status & XS_STS_DONE) { /* XXX in certain circumstances, such as a device * being detached, a xs that has already been * scsipi_done()'d by the main thread will be done'd * again by scsibusdetach(). Putting the xs on the * chan_complete queue causes list corruption and * everyone dies. This prevents that, but perhaps * there should be better coordination somewhere such * that this won't ever happen (and can be turned into * a KASSERT(). */ SDT_PROBE1(scsi, base, xfer, redone, xs); mutex_exit(chan_mtx(chan)); goto out; } scsipi_put_resource(chan); xs->xs_periph->periph_sent--; /* * If the command was tagged, free the tag. */ if (XS_CTL_TAGTYPE(xs) != 0) scsipi_put_tag(xs); else periph->periph_flags &= ~PERIPH_UNTAG; /* Mark the command as `done'. */ xs->xs_status |= XS_STS_DONE; #ifdef DIAGNOSTIC if ((xs->xs_control & (XS_CTL_ASYNC|XS_CTL_POLL)) == (XS_CTL_ASYNC|XS_CTL_POLL)) panic("scsipi_done: ASYNC and POLL"); #endif /* * If the xfer had an error of any sort, freeze the * periph's queue. Freeze it again if we were requested * to do so in the xfer. */ freezecnt = 0; if (xs->error != XS_NOERROR) freezecnt++; if (xs->xs_control & XS_CTL_FREEZE_PERIPH) freezecnt++; if (freezecnt != 0) scsipi_periph_freeze_locked(periph, freezecnt); /* * record the xfer with a pending sense, in case a SCSI reset is * received before the thread is waked up. */ if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) { periph->periph_flags |= PERIPH_SENSE; periph->periph_xscheck = xs; } /* * If this was an xfer that was not to complete asynchronously, * let the requesting thread perform error checking/handling * in its context. */ if ((xs->xs_control & XS_CTL_ASYNC) == 0) { /* * If it's a polling job, just return, to unwind the * call graph. We don't need to restart the queue, * because polling jobs are treated specially, and * are really only used during crash dumps anyway * (XXX or during boot-time autoconfiguration of * ATAPI devices). */ if (xs->xs_control & XS_CTL_POLL) { mutex_exit(chan_mtx(chan)); return; } cv_broadcast(xs_cv(xs)); mutex_exit(chan_mtx(chan)); goto out; } /* * Catch the extremely common case of I/O completing * without error; no use in taking a context switch * if we can handle it in interrupt context. */ if (xs->error == XS_NOERROR) { mutex_exit(chan_mtx(chan)); (void) scsipi_complete(xs); goto out; } /* * There is an error on this xfer. Put it on the channel's * completion queue, and wake up the completion thread. */ TAILQ_INSERT_TAIL(&chan->chan_complete, xs, channel_q); cv_broadcast(chan_cv_complete(chan)); mutex_exit(chan_mtx(chan)); out: /* * If there are more xfers on the channel's queue, attempt to * run them. */ scsipi_run_queue(chan); } /* * scsipi_complete: * * Completion of a scsipi_xfer. This is the guts of scsipi_done(). * * NOTE: This routine MUST be called with valid thread context * except for the case where the following two conditions are * true: * * xs->error == XS_NOERROR * XS_CTL_ASYNC is set in xs->xs_control * * The semantics of this routine can be tricky, so here is an * explanation: * * 0 Xfer completed successfully. * * ERESTART Xfer had an error, but was restarted. * * anything else Xfer had an error, return value is Unix * errno. * * If the return value is anything but ERESTART: * * - If XS_CTL_ASYNC is set, `xs' has been freed back to * the pool. * - If there is a buf associated with the xfer, * it has been biodone()'d. */ static int scsipi_complete(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; struct scsipi_channel *chan = periph->periph_channel; int error; SDT_PROBE1(scsi, base, xfer, complete, xs); #ifdef DIAGNOSTIC if ((xs->xs_control & XS_CTL_ASYNC) != 0 && xs->bp == NULL) panic("scsipi_complete: XS_CTL_ASYNC but no buf"); #endif /* * If command terminated with a CHECK CONDITION, we need to issue a * REQUEST_SENSE command. Once the REQUEST_SENSE has been processed * we'll have the real status. * Must be processed with channel lock held to avoid missing * a SCSI bus reset for this command. */ mutex_enter(chan_mtx(chan)); if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) { /* request sense for a request sense ? */ if (xs->xs_control & XS_CTL_REQSENSE) { scsipi_printaddr(periph); printf("request sense for a request sense ?\n"); /* XXX maybe we should reset the device ? */ /* we've been frozen because xs->error != XS_NOERROR */ scsipi_periph_thaw_locked(periph, 1); mutex_exit(chan_mtx(chan)); if (xs->resid < xs->datalen) { printf("we read %d bytes of sense anyway:\n", xs->datalen - xs->resid); scsipi_print_sense_data((void *)xs->data, 0); } return EINVAL; } mutex_exit(chan_mtx(chan)); // XXX allows other commands to queue or run scsipi_request_sense(xs); } else mutex_exit(chan_mtx(chan)); /* * If it's a user level request, bypass all usual completion * processing, let the user work it out.. */ if ((xs->xs_control & XS_CTL_USERCMD) != 0) { SC_DEBUG(periph, SCSIPI_DB3, ("calling user done()\n")); mutex_enter(chan_mtx(chan)); if (xs->error != XS_NOERROR) scsipi_periph_thaw_locked(periph, 1); mutex_exit(chan_mtx(chan)); scsipi_user_done(xs); SC_DEBUG(periph, SCSIPI_DB3, ("returned from user done()\n ")); return 0; } switch (xs->error) { case XS_NOERROR: error = 0; break; case XS_SENSE: case XS_SHORTSENSE: error = (*chan->chan_bustype->bustype_interpret_sense)(xs); break; case XS_RESOURCE_SHORTAGE: /* * XXX Should freeze channel's queue. */ scsipi_printaddr(periph); printf("adapter resource shortage\n"); /* FALLTHROUGH */ case XS_BUSY: if (xs->error == XS_BUSY && xs->status == SCSI_QUEUE_FULL) { struct scsipi_max_openings mo; /* * We set the openings to active - 1, assuming that * the command that got us here is the first one that * can't fit into the device's queue. If that's not * the case, I guess we'll find out soon enough. */ mo.mo_target = periph->periph_target; mo.mo_lun = periph->periph_lun; if (periph->periph_active < periph->periph_openings) mo.mo_openings = periph->periph_active - 1; else mo.mo_openings = periph->periph_openings - 1; #ifdef DIAGNOSTIC if (mo.mo_openings < 0) { scsipi_printaddr(periph); printf("QUEUE FULL resulted in < 0 openings\n"); panic("scsipi_done"); } #endif if (mo.mo_openings == 0) { scsipi_printaddr(periph); printf("QUEUE FULL resulted in 0 openings\n"); mo.mo_openings = 1; } scsipi_async_event(chan, ASYNC_EVENT_MAX_OPENINGS, &mo); error = ERESTART; } else if (xs->xs_retries != 0) { xs->xs_retries--; /* * Wait one second, and try again. */ mutex_enter(chan_mtx(chan)); if ((xs->xs_control & XS_CTL_POLL) || (chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) { /* XXX: quite extreme */ kpause("xsbusy", false, hz, chan_mtx(chan)); } else if (!callout_pending(&periph->periph_callout)) { scsipi_periph_freeze_locked(periph, 1); callout_reset(&periph->periph_callout, hz, scsipi_periph_timed_thaw, periph); } mutex_exit(chan_mtx(chan)); error = ERESTART; } else error = EBUSY; break; case XS_REQUEUE: error = ERESTART; break; case XS_SELTIMEOUT: case XS_TIMEOUT: /* * If the device hasn't gone away, honor retry counts. * * Note that if we're in the middle of probing it, * it won't be found because it isn't here yet so * we won't honor the retry count in that case. */ if (scsipi_lookup_periph(chan, periph->periph_target, periph->periph_lun) && xs->xs_retries != 0) { xs->xs_retries--; error = ERESTART; } else error = EIO; break; case XS_RESET: if (xs->xs_control & XS_CTL_REQSENSE) { /* * request sense interrupted by reset: signal it * with EINTR return code. */ error = EINTR; } else { if (xs->xs_retries != 0) { xs->xs_retries--; error = ERESTART; } else error = EIO; } break; case XS_DRIVER_STUFFUP: scsipi_printaddr(periph); printf("generic HBA error\n"); error = EIO; break; default: scsipi_printaddr(periph); printf("invalid return code from adapter: %d\n", xs->error); error = EIO; break; } mutex_enter(chan_mtx(chan)); if (error == ERESTART) { SDT_PROBE1(scsi, base, xfer, restart, xs); /* * If we get here, the periph has been thawed and frozen * again if we had to issue recovery commands. Alternatively, * it may have been frozen again and in a timed thaw. In * any case, we thaw the periph once we re-enqueue the * command. Once the periph is fully thawed, it will begin * operation again. */ xs->error = XS_NOERROR; xs->status = SCSI_OK; xs->xs_status &= ~XS_STS_DONE; xs->xs_requeuecnt++; error = scsipi_enqueue(xs); if (error == 0) { scsipi_periph_thaw_locked(periph, 1); mutex_exit(chan_mtx(chan)); return ERESTART; } } /* * scsipi_done() freezes the queue if not XS_NOERROR. * Thaw it here. */ if (xs->error != XS_NOERROR) scsipi_periph_thaw_locked(periph, 1); mutex_exit(chan_mtx(chan)); if (periph->periph_switch->psw_done) periph->periph_switch->psw_done(xs, error); mutex_enter(chan_mtx(chan)); if (xs->xs_control & XS_CTL_ASYNC) scsipi_put_xs(xs); mutex_exit(chan_mtx(chan)); return error; } /* * Issue a request sense for the given scsipi_xfer. Called when the xfer * returns with a CHECK_CONDITION status. Must be called in valid thread * context. */ static void scsipi_request_sense(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; int flags, error; struct scsi_request_sense cmd; periph->periph_flags |= PERIPH_SENSE; /* if command was polling, request sense will too */ flags = xs->xs_control & XS_CTL_POLL; /* Polling commands can't sleep */ if (flags) flags |= XS_CTL_NOSLEEP; flags |= XS_CTL_REQSENSE | XS_CTL_URGENT | XS_CTL_DATA_IN | XS_CTL_THAW_PERIPH | XS_CTL_FREEZE_PERIPH; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_REQUEST_SENSE; cmd.length = sizeof(struct scsi_sense_data); error = scsipi_command(periph, (void *)&cmd, sizeof(cmd), (void *)&xs->sense.scsi_sense, sizeof(struct scsi_sense_data), 0, 1000, NULL, flags); periph->periph_flags &= ~PERIPH_SENSE; periph->periph_xscheck = NULL; switch (error) { case 0: /* we have a valid sense */ xs->error = XS_SENSE; return; case EINTR: /* REQUEST_SENSE interrupted by bus reset. */ xs->error = XS_RESET; return; case EIO: /* request sense couldn't be performed */ /* * XXX this isn't quite right but we don't have anything * better for now */ xs->error = XS_DRIVER_STUFFUP; return; default: /* Notify that request sense failed. */ xs->error = XS_DRIVER_STUFFUP; scsipi_printaddr(periph); printf("request sense failed with error %d\n", error); return; } } /* * scsipi_enqueue: * * Enqueue an xfer on a channel. */ static int scsipi_enqueue(struct scsipi_xfer *xs) { struct scsipi_channel *chan = xs->xs_periph->periph_channel; struct scsipi_xfer *qxs; SDT_PROBE1(scsi, base, xfer, enqueue, xs); /* * If the xfer is to be polled, and there are already jobs on * the queue, we can't proceed. */ KASSERT(mutex_owned(chan_mtx(chan))); if ((xs->xs_control & XS_CTL_POLL) != 0 && TAILQ_FIRST(&chan->chan_queue) != NULL) { xs->error = XS_DRIVER_STUFFUP; return EAGAIN; } /* * If we have an URGENT xfer, it's an error recovery command * and it should just go on the head of the channel's queue. */ if (xs->xs_control & XS_CTL_URGENT) { TAILQ_INSERT_HEAD(&chan->chan_queue, xs, channel_q); goto out; } /* * If this xfer has already been on the queue before, we * need to reinsert it in the correct order. That order is: * * Immediately before the first xfer for this periph * with a requeuecnt less than xs->xs_requeuecnt. * * Failing that, at the end of the queue. (We'll end up * there naturally.) */ if (xs->xs_requeuecnt != 0) { for (qxs = TAILQ_FIRST(&chan->chan_queue); qxs != NULL; qxs = TAILQ_NEXT(qxs, channel_q)) { if (qxs->xs_periph == xs->xs_periph && qxs->xs_requeuecnt < xs->xs_requeuecnt) break; } if (qxs != NULL) { TAILQ_INSERT_AFTER(&chan->chan_queue, qxs, xs, channel_q); goto out; } } TAILQ_INSERT_TAIL(&chan->chan_queue, xs, channel_q); out: if (xs->xs_control & XS_CTL_THAW_PERIPH) scsipi_periph_thaw_locked(xs->xs_periph, 1); return 0; } /* * scsipi_run_queue: * * Start as many xfers as possible running on the channel. */ static void scsipi_run_queue(struct scsipi_channel *chan) { struct scsipi_xfer *xs; struct scsipi_periph *periph; SDT_PROBE1(scsi, base, queue, batch__start, chan); for (;;) { mutex_enter(chan_mtx(chan)); /* * If the channel is frozen, we can't do any work right * now. */ if (chan->chan_qfreeze != 0) { mutex_exit(chan_mtx(chan)); break; } /* * Look for work to do, and make sure we can do it. */ for (xs = TAILQ_FIRST(&chan->chan_queue); xs != NULL; xs = TAILQ_NEXT(xs, channel_q)) { periph = xs->xs_periph; if ((periph->periph_sent >= periph->periph_openings) || periph->periph_qfreeze != 0 || (periph->periph_flags & PERIPH_UNTAG) != 0) continue; if ((periph->periph_flags & (PERIPH_RECOVERING | PERIPH_SENSE)) != 0 && (xs->xs_control & XS_CTL_URGENT) == 0) continue; /* * We can issue this xfer! */ goto got_one; } /* * Can't find any work to do right now. */ mutex_exit(chan_mtx(chan)); break; got_one: /* * Have an xfer to run. Allocate a resource from * the adapter to run it. If we can't allocate that * resource, we don't dequeue the xfer. */ if (scsipi_get_resource(chan) == 0) { /* * Adapter is out of resources. If the adapter * supports it, attempt to grow them. */ if (scsipi_grow_resources(chan) == 0) { /* * Wasn't able to grow resources, * nothing more we can do. */ if (xs->xs_control & XS_CTL_POLL) { scsipi_printaddr(xs->xs_periph); printf("polling command but no " "adapter resources"); /* We'll panic shortly... */ } mutex_exit(chan_mtx(chan)); /* * XXX: We should be able to note that * XXX: that resources are needed here! */ break; } /* * scsipi_grow_resources() allocated the resource * for us. */ } /* * We have a resource to run this xfer, do it! */ TAILQ_REMOVE(&chan->chan_queue, xs, channel_q); /* * If the command is to be tagged, allocate a tag ID * for it. */ if (XS_CTL_TAGTYPE(xs) != 0) scsipi_get_tag(xs); else periph->periph_flags |= PERIPH_UNTAG; periph->periph_sent++; mutex_exit(chan_mtx(chan)); SDT_PROBE2(scsi, base, queue, run, chan, xs); scsipi_adapter_request(chan, ADAPTER_REQ_RUN_XFER, xs); } SDT_PROBE1(scsi, base, queue, batch__done, chan); } /* * scsipi_execute_xs: * * Begin execution of an xfer, waiting for it to complete, if necessary. */ int scsipi_execute_xs(struct scsipi_xfer *xs) { struct scsipi_periph *periph = xs->xs_periph; struct scsipi_channel *chan = periph->periph_channel; int oasync, async, poll, error; KASSERT(!cold); scsipi_update_timeouts(xs); (chan->chan_bustype->bustype_cmd)(xs); xs->xs_status &= ~XS_STS_DONE; xs->error = XS_NOERROR; xs->resid = xs->datalen; xs->status = SCSI_OK; SDT_PROBE1(scsi, base, xfer, execute, xs); #ifdef SCSIPI_DEBUG if (xs->xs_periph->periph_dbflags & SCSIPI_DB3) { printf("scsipi_execute_xs: "); show_scsipi_xs(xs); printf("\n"); } #endif /* * Deal with command tagging: * * - If the device's current operating mode doesn't * include tagged queueing, clear the tag mask. * * - If the device's current operating mode *does* * include tagged queueing, set the tag_type in * the xfer to the appropriate byte for the tag * message. */ if ((PERIPH_XFER_MODE(periph) & PERIPH_CAP_TQING) == 0 || (xs->xs_control & XS_CTL_REQSENSE)) { xs->xs_control &= ~XS_CTL_TAGMASK; xs->xs_tag_type = 0; } else { /* * If the request doesn't specify a tag, give Head * tags to URGENT operations and Simple tags to * everything else. */ if (XS_CTL_TAGTYPE(xs) == 0) { if (xs->xs_control & XS_CTL_URGENT) xs->xs_control |= XS_CTL_HEAD_TAG; else xs->xs_control |= XS_CTL_SIMPLE_TAG; } switch (XS_CTL_TAGTYPE(xs)) { case XS_CTL_ORDERED_TAG: xs->xs_tag_type = MSG_ORDERED_Q_TAG; break; case XS_CTL_SIMPLE_TAG: xs->xs_tag_type = MSG_SIMPLE_Q_TAG; break; case XS_CTL_HEAD_TAG: xs->xs_tag_type = MSG_HEAD_OF_Q_TAG; break; default: scsipi_printaddr(periph); printf("invalid tag mask 0x%08x\n", XS_CTL_TAGTYPE(xs)); panic("scsipi_execute_xs"); } } /* If the adapter wants us to poll, poll. */ if (chan->chan_adapter->adapt_flags & SCSIPI_ADAPT_POLL_ONLY) xs->xs_control |= XS_CTL_POLL; /* * If we don't yet have a completion thread, or we are to poll for * completion, clear the ASYNC flag. */ oasync = (xs->xs_control & XS_CTL_ASYNC); if (chan->chan_thread == NULL || (xs->xs_control & XS_CTL_POLL) != 0) xs->xs_control &= ~XS_CTL_ASYNC; async = (xs->xs_control & XS_CTL_ASYNC); poll = (xs->xs_control & XS_CTL_POLL); #ifdef DIAGNOSTIC if (oasync != 0 && xs->bp == NULL) panic("scsipi_execute_xs: XS_CTL_ASYNC but no buf"); #endif /* * Enqueue the transfer. If we're not polling for completion, this * should ALWAYS return `no error'. */ error = scsipi_enqueue(xs); if (error) { if (poll == 0) { scsipi_printaddr(periph); printf("not polling, but enqueue failed with %d\n", error); panic("scsipi_execute_xs"); } scsipi_printaddr(periph); printf("should have flushed queue?\n"); goto free_xs; } mutex_exit(chan_mtx(chan)); restarted: scsipi_run_queue(chan); mutex_enter(chan_mtx(chan)); /* * The xfer is enqueued, and possibly running. If it's to be * completed asynchronously, just return now. */ if (async) return 0; /* * Not an asynchronous command; wait for it to complete. */ while ((xs->xs_status & XS_STS_DONE) == 0) { if (poll) { scsipi_printaddr(periph); printf("polling command not done\n"); panic("scsipi_execute_xs"); } cv_wait(xs_cv(xs), chan_mtx(chan)); } /* * Command is complete. scsipi_done() has awakened us to perform * the error handling. */ mutex_exit(chan_mtx(chan)); error = scsipi_complete(xs); if (error == ERESTART) goto restarted; /* * If it was meant to run async and we cleared async ourselves, * don't return an error here. It has already been handled */ if (oasync) error = 0; /* * Command completed successfully or fatal error occurred. Fall * into.... */ mutex_enter(chan_mtx(chan)); free_xs: scsipi_put_xs(xs); mutex_exit(chan_mtx(chan)); /* * Kick the queue, keep it running in case it stopped for some * reason. */ scsipi_run_queue(chan); mutex_enter(chan_mtx(chan)); return error; } /* * scsipi_completion_thread: * * This is the completion thread. We wait for errors on * asynchronous xfers, and perform the error handling * function, restarting the command, if necessary. */ static void scsipi_completion_thread(void *arg) { struct scsipi_channel *chan = arg; struct scsipi_xfer *xs; if (chan->chan_init_cb) (*chan->chan_init_cb)(chan, chan->chan_init_cb_arg); mutex_enter(chan_mtx(chan)); chan->chan_flags |= SCSIPI_CHAN_TACTIVE; for (;;) { xs = TAILQ_FIRST(&chan->chan_complete); if (xs == NULL && chan->chan_tflags == 0) { /* nothing to do; wait */ cv_wait(chan_cv_complete(chan), chan_mtx(chan)); continue; } if (chan->chan_tflags & SCSIPI_CHANT_CALLBACK) { /* call chan_callback from thread context */ chan->chan_tflags &= ~SCSIPI_CHANT_CALLBACK; chan->chan_callback(chan, chan->chan_callback_arg); continue; } if (chan->chan_tflags & SCSIPI_CHANT_GROWRES) { /* attempt to get more openings for this channel */ chan->chan_tflags &= ~SCSIPI_CHANT_GROWRES; mutex_exit(chan_mtx(chan)); scsipi_adapter_request(chan, ADAPTER_REQ_GROW_RESOURCES, NULL); scsipi_channel_thaw(chan, 1); if (chan->chan_tflags & SCSIPI_CHANT_GROWRES) kpause("scsizzz", FALSE, hz/10, NULL); mutex_enter(chan_mtx(chan)); continue; } if (chan->chan_tflags & SCSIPI_CHANT_KICK) { /* explicitly run the queues for this channel */ chan->chan_tflags &= ~SCSIPI_CHANT_KICK; mutex_exit(chan_mtx(chan)); scsipi_run_queue(chan); mutex_enter(chan_mtx(chan)); continue; } if (chan->chan_tflags & SCSIPI_CHANT_SHUTDOWN) { break; } if (xs) { TAILQ_REMOVE(&chan->chan_complete, xs, channel_q); mutex_exit(chan_mtx(chan)); /* * Have an xfer with an error; process it. */ (void) scsipi_complete(xs); /* * Kick the queue; keep it running if it was stopped * for some reason. */ scsipi_run_queue(chan); mutex_enter(chan_mtx(chan)); } } chan->chan_thread = NULL; /* In case parent is waiting for us to exit. */ cv_broadcast(chan_cv_thread(chan)); mutex_exit(chan_mtx(chan)); kthread_exit(0); } /* * scsipi_thread_call_callback: * * request to call a callback from the completion thread */ int scsipi_thread_call_callback(struct scsipi_channel *chan, void (*callback)(struct scsipi_channel *, void *), void *arg) { mutex_enter(chan_mtx(chan)); if ((chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) { /* kernel thread doesn't exist yet */ mutex_exit(chan_mtx(chan)); return ESRCH; } if (chan->chan_tflags & SCSIPI_CHANT_CALLBACK) { mutex_exit(chan_mtx(chan)); return EBUSY; } scsipi_channel_freeze(chan, 1); chan->chan_callback = callback; chan->chan_callback_arg = arg; chan->chan_tflags |= SCSIPI_CHANT_CALLBACK; cv_broadcast(chan_cv_complete(chan)); mutex_exit(chan_mtx(chan)); return 0; } /* * scsipi_async_event: * * Handle an asynchronous event from an adapter. */ void scsipi_async_event(struct scsipi_channel *chan, scsipi_async_event_t event, void *arg) { bool lock = chan_running(chan) > 0; if (lock) mutex_enter(chan_mtx(chan)); switch (event) { case ASYNC_EVENT_MAX_OPENINGS: scsipi_async_event_max_openings(chan, (struct scsipi_max_openings *)arg); break; case ASYNC_EVENT_XFER_MODE: if (chan->chan_bustype->bustype_async_event_xfer_mode) { chan->chan_bustype->bustype_async_event_xfer_mode( chan, arg); } break; case ASYNC_EVENT_RESET: scsipi_async_event_channel_reset(chan); break; } if (lock) mutex_exit(chan_mtx(chan)); } /* * scsipi_async_event_max_openings: * * Update the maximum number of outstanding commands a * device may have. */ static void scsipi_async_event_max_openings(struct scsipi_channel *chan, struct scsipi_max_openings *mo) { struct scsipi_periph *periph; int minlun, maxlun; if (mo->mo_lun == -1) { /* * Wildcarded; apply it to all LUNs. */ minlun = 0; maxlun = chan->chan_nluns - 1; } else minlun = maxlun = mo->mo_lun; /* XXX This could really suck with a large LUN space. */ for (; minlun <= maxlun; minlun++) { periph = scsipi_lookup_periph_locked(chan, mo->mo_target, minlun); if (periph == NULL) continue; if (mo->mo_openings < periph->periph_openings) periph->periph_openings = mo->mo_openings; else if (mo->mo_openings > periph->periph_openings && (periph->periph_flags & PERIPH_GROW_OPENINGS) != 0) periph->periph_openings = mo->mo_openings; } } /* * scsipi_set_xfer_mode: * * Set the xfer mode for the specified I_T Nexus. */ void scsipi_set_xfer_mode(struct scsipi_channel *chan, int target, int immed) { struct scsipi_xfer_mode xm; struct scsipi_periph *itperiph; int lun; /* * Go to the minimal xfer mode. */ xm.xm_target = target; xm.xm_mode = 0; xm.xm_period = 0; /* ignored */ xm.xm_offset = 0; /* ignored */ /* * Find the first LUN we know about on this I_T Nexus. */ for (itperiph = NULL, lun = 0; lun < chan->chan_nluns; lun++) { itperiph = scsipi_lookup_periph(chan, target, lun); if (itperiph != NULL) break; } if (itperiph != NULL) { xm.xm_mode = itperiph->periph_cap; /* * Now issue the request to the adapter. */ scsipi_adapter_request(chan, ADAPTER_REQ_SET_XFER_MODE, &xm); /* * If we want this to happen immediately, issue a dummy * command, since most adapters can't really negotiate unless * they're executing a job. */ if (immed != 0) { (void) scsipi_test_unit_ready(itperiph, XS_CTL_DISCOVERY | XS_CTL_IGNORE_ILLEGAL_REQUEST | XS_CTL_IGNORE_NOT_READY | XS_CTL_IGNORE_MEDIA_CHANGE); } } } /* * scsipi_channel_reset: * * handle scsi bus reset * called with channel lock held */ static void scsipi_async_event_channel_reset(struct scsipi_channel *chan) { struct scsipi_xfer *xs, *xs_next; struct scsipi_periph *periph; int target, lun; /* * Channel has been reset. Also mark as reset pending REQUEST_SENSE * commands; as the sense is not available any more. * can't call scsipi_done() from here, as the command has not been * sent to the adapter yet (this would corrupt accounting). */ for (xs = TAILQ_FIRST(&chan->chan_queue); xs != NULL; xs = xs_next) { xs_next = TAILQ_NEXT(xs, channel_q); if (xs->xs_control & XS_CTL_REQSENSE) { TAILQ_REMOVE(&chan->chan_queue, xs, channel_q); xs->error = XS_RESET; if ((xs->xs_control & XS_CTL_ASYNC) != 0) TAILQ_INSERT_TAIL(&chan->chan_complete, xs, channel_q); } } cv_broadcast(chan_cv_complete(chan)); /* Catch xs with pending sense which may not have a REQSENSE xs yet */ for (target = 0; target < chan->chan_ntargets; target++) { if (target == chan->chan_id) continue; for (lun = 0; lun < chan->chan_nluns; lun++) { periph = scsipi_lookup_periph_locked(chan, target, lun); if (periph) { xs = periph->periph_xscheck; if (xs) xs->error = XS_RESET; } } } } /* * scsipi_target_detach: * * detach all periph associated with a I_T * must be called from valid thread context */ int scsipi_target_detach(struct scsipi_channel *chan, int target, int lun, int flags) { struct scsipi_periph *periph; device_t tdev; int ctarget, mintarget, maxtarget; int clun, minlun, maxlun; int error = 0; if (target == -1) { mintarget = 0; maxtarget = chan->chan_ntargets; } else { if (target == chan->chan_id) return EINVAL; if (target < 0 || target >= chan->chan_ntargets) return EINVAL; mintarget = target; maxtarget = target + 1; } if (lun == -1) { minlun = 0; maxlun = chan->chan_nluns; } else { if (lun < 0 || lun >= chan->chan_nluns) return EINVAL; minlun = lun; maxlun = lun + 1; } /* for config_detach */ KERNEL_LOCK(1, curlwp); mutex_enter(chan_mtx(chan)); for (ctarget = mintarget; ctarget < maxtarget; ctarget++) { if (ctarget == chan->chan_id) continue; for (clun = minlun; clun < maxlun; clun++) { periph = scsipi_lookup_periph_locked(chan, ctarget, clun); if (periph == NULL) continue; tdev = periph->periph_dev; mutex_exit(chan_mtx(chan)); error = config_detach(tdev, flags); if (error) goto out; mutex_enter(chan_mtx(chan)); KASSERT(scsipi_lookup_periph_locked(chan, ctarget, clun) == NULL); } } mutex_exit(chan_mtx(chan)); out: KERNEL_UNLOCK_ONE(curlwp); return error; } /* * scsipi_adapter_addref: * * Add a reference to the adapter pointed to by the provided * link, enabling the adapter if necessary. */ int scsipi_adapter_addref(struct scsipi_adapter *adapt) { int error = 0; if (atomic_inc_uint_nv(&adapt->adapt_refcnt) == 1 && adapt->adapt_enable != NULL) { scsipi_adapter_lock(adapt); error = scsipi_adapter_enable(adapt, 1); scsipi_adapter_unlock(adapt); if (error) atomic_dec_uint(&adapt->adapt_refcnt); } return error; } /* * scsipi_adapter_delref: * * Delete a reference to the adapter pointed to by the provided * link, disabling the adapter if possible. */ void scsipi_adapter_delref(struct scsipi_adapter *adapt) { membar_release(); if (atomic_dec_uint_nv(&adapt->adapt_refcnt) == 0 && adapt->adapt_enable != NULL) { membar_acquire(); scsipi_adapter_lock(adapt); (void) scsipi_adapter_enable(adapt, 0); scsipi_adapter_unlock(adapt); } } static struct scsipi_syncparam { int ss_factor; int ss_period; /* ns * 100 */ } scsipi_syncparams[] = { { 0x08, 625 }, /* FAST-160 (Ultra320) */ { 0x09, 1250 }, /* FAST-80 (Ultra160) */ { 0x0a, 2500 }, /* FAST-40 40MHz (Ultra2) */ { 0x0b, 3030 }, /* FAST-40 33MHz (Ultra2) */ { 0x0c, 5000 }, /* FAST-20 (Ultra) */ }; static const int scsipi_nsyncparams = sizeof(scsipi_syncparams) / sizeof(scsipi_syncparams[0]); int scsipi_sync_period_to_factor(int period /* ns * 100 */) { int i; for (i = 0; i < scsipi_nsyncparams; i++) { if (period <= scsipi_syncparams[i].ss_period) return scsipi_syncparams[i].ss_factor; } return (period / 100) / 4; } int scsipi_sync_factor_to_period(int factor) { int i; for (i = 0; i < scsipi_nsyncparams; i++) { if (factor == scsipi_syncparams[i].ss_factor) return scsipi_syncparams[i].ss_period; } return (factor * 4) * 100; } int scsipi_sync_factor_to_freq(int factor) { int i; for (i = 0; i < scsipi_nsyncparams; i++) { if (factor == scsipi_syncparams[i].ss_factor) return 100000000 / scsipi_syncparams[i].ss_period; } return 10000000 / ((factor * 4) * 10); } static inline void scsipi_adapter_lock(struct scsipi_adapter *adapt) { if ((adapt->adapt_flags & SCSIPI_ADAPT_MPSAFE) == 0) KERNEL_LOCK(1, NULL); } static inline void scsipi_adapter_unlock(struct scsipi_adapter *adapt) { if ((adapt->adapt_flags & SCSIPI_ADAPT_MPSAFE) == 0) KERNEL_UNLOCK_ONE(NULL); } void scsipi_adapter_minphys(struct scsipi_channel *chan, struct buf *bp) { struct scsipi_adapter *adapt = chan->chan_adapter; scsipi_adapter_lock(adapt); (adapt->adapt_minphys)(bp); scsipi_adapter_unlock(chan->chan_adapter); } void scsipi_adapter_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg) { struct scsipi_adapter *adapt = chan->chan_adapter; scsipi_adapter_lock(adapt); SDT_PROBE3(scsi, base, adapter, request__start, chan, req, arg); (adapt->adapt_request)(chan, req, arg); SDT_PROBE3(scsi, base, adapter, request__done, chan, req, arg); scsipi_adapter_unlock(adapt); } int scsipi_adapter_ioctl(struct scsipi_channel *chan, u_long cmd, void *data, int flag, struct proc *p) { struct scsipi_adapter *adapt = chan->chan_adapter; int error; if (adapt->adapt_ioctl == NULL) return ENOTTY; scsipi_adapter_lock(adapt); error = (adapt->adapt_ioctl)(chan, cmd, data, flag, p); scsipi_adapter_unlock(adapt); return error; } int scsipi_adapter_enable(struct scsipi_adapter *adapt, int enable) { int error; scsipi_adapter_lock(adapt); error = (adapt->adapt_enable)(adapt->adapt_dev, enable); scsipi_adapter_unlock(adapt); return error; } #ifdef SCSIPI_DEBUG /* * Given a scsipi_xfer, dump the request, in all its glory */ void show_scsipi_xs(struct scsipi_xfer *xs) { printf("xs(%p): ", xs); printf("xs_control(0x%08x)", xs->xs_control); printf("xs_status(0x%08x)", xs->xs_status); printf("periph(%p)", xs->xs_periph); printf("retr(0x%x)", xs->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)", xs->bp); show_scsipi_cmd(xs); } void show_scsipi_cmd(struct scsipi_xfer *xs) { u_char *b = (u_char *) xs->cmd; int i = 0; scsipi_printaddr(xs->xs_periph); printf(" command: "); if ((xs->xs_control & XS_CTL_RESET) == 0) { while (i < xs->cmdlen) { if (i) printf(","); printf("0x%x", b[i++]); } printf("-[%d bytes]\n", xs->datalen); if (xs->datalen) show_mem(xs->data, uimin(64, 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 /* SCSIPI_DEBUG */