/*- * Copyright (c) 2004, 2005 Silicon Graphics International Corp. * All rights reserved. * * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * substantially similar to the "NO WARRANTY" disclaimer below * ("Disclaimer") and any redistribution must be conditioned upon * including a substantially similar Disclaimer requirement for further * binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. * * $Id: //depot/users/kenm/FreeBSD-test2/sys/cam/ctl/ctl_frontend_internal.c#5 $ */ /* * CTL kernel internal frontend target driver. This allows kernel-level * clients to send commands into CTL. * * This has elements of a FETD (e.g. it has to set tag numbers, initiator, * port, target, and LUN) and elements of an initiator (LUN discovery and * probing, error recovery, command initiation). Even though this has some * initiator type elements, this is not intended to be a full fledged * initiator layer. It is only intended to send a limited number of * commands to a well known target layer. * * To be able to fulfill the role of a full initiator layer, it would need * a whole lot more functionality. * * Author: Ken Merry * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Task structure: * - overall metatask, different potential metatask types (e.g. forced * shutdown, gentle shutdown) * - forced shutdown metatask: * - states: report luns, pending, done? * - list of luns pending, with the relevant I/O for that lun attached. * This would allow moving ahead on LUNs with no errors, and going * into error recovery on LUNs with problems. Per-LUN states might * include inquiry, stop/offline, done. * * Use LUN enable for LUN list instead of getting it manually? We'd still * need inquiry data for each LUN. * * How to handle processor LUN w.r.t. found/stopped counts? */ #ifdef oldapi typedef enum { CFI_TASK_NONE, CFI_TASK_SHUTDOWN, CFI_TASK_STARTUP } cfi_tasktype; struct cfi_task_startstop { int total_luns; int luns_complete; int luns_failed; cfi_cb_t callback; void *callback_arg; /* XXX KDM add more fields here */ }; union cfi_taskinfo { struct cfi_task_startstop startstop; }; struct cfi_metatask { cfi_tasktype tasktype; cfi_mt_status status; union cfi_taskinfo taskinfo; void *cfi_context; STAILQ_ENTRY(cfi_metatask) links; }; #endif typedef enum { CFI_ERR_RETRY = 0x000, CFI_ERR_FAIL = 0x001, CFI_ERR_LUN_RESET = 0x002, CFI_ERR_MASK = 0x0ff, CFI_ERR_NO_DECREMENT = 0x100 } cfi_error_action; typedef enum { CFI_ERR_SOFT, CFI_ERR_HARD } cfi_error_policy; typedef enum { CFI_LUN_INQUIRY, CFI_LUN_READCAPACITY, CFI_LUN_READCAPACITY_16, CFI_LUN_READY } cfi_lun_state; struct cfi_lun { struct ctl_id target_id; int lun_id; struct scsi_inquiry_data inq_data; uint64_t num_blocks; uint32_t blocksize; int blocksize_powerof2; uint32_t cur_tag_num; cfi_lun_state state; struct cfi_softc *softc; STAILQ_HEAD(, cfi_lun_io) io_list; STAILQ_ENTRY(cfi_lun) links; }; struct cfi_lun_io { struct cfi_lun *lun; struct cfi_metatask *metatask; cfi_error_policy policy; void (*done_function)(union ctl_io *io); union ctl_io *ctl_io; struct cfi_lun_io *orig_lun_io; STAILQ_ENTRY(cfi_lun_io) links; }; typedef enum { CFI_NONE = 0x00, CFI_ONLINE = 0x01, } cfi_flags; struct cfi_softc { struct ctl_port port; char fe_name[40]; struct mtx lock; cfi_flags flags; STAILQ_HEAD(, cfi_lun) lun_list; STAILQ_HEAD(, cfi_metatask) metatask_list; }; MALLOC_DEFINE(M_CTL_CFI, "ctlcfi", "CTL CFI"); static uma_zone_t cfi_lun_zone; static uma_zone_t cfi_metatask_zone; static struct cfi_softc fetd_internal_softc; int cfi_init(void); void cfi_shutdown(void) __unused; static void cfi_online(void *arg); static void cfi_offline(void *arg); static int cfi_lun_enable(void *arg, struct ctl_id target_id, int lun_id); static int cfi_lun_disable(void *arg, struct ctl_id target_id, int lun_id); static void cfi_datamove(union ctl_io *io); static cfi_error_action cfi_checkcond_parse(union ctl_io *io, struct cfi_lun_io *lun_io); static cfi_error_action cfi_error_parse(union ctl_io *io, struct cfi_lun_io *lun_io); static void cfi_init_io(union ctl_io *io, struct cfi_lun *lun, struct cfi_metatask *metatask, cfi_error_policy policy, int retries, struct cfi_lun_io *orig_lun_io, void (*done_function)(union ctl_io *io)); static void cfi_done(union ctl_io *io); static void cfi_lun_probe_done(union ctl_io *io); static void cfi_lun_probe(struct cfi_lun *lun, int have_lock); static void cfi_metatask_done(struct cfi_softc *softc, struct cfi_metatask *metatask); static void cfi_metatask_bbr_errorparse(struct cfi_metatask *metatask, union ctl_io *io); static void cfi_metatask_io_done(union ctl_io *io); static void cfi_err_recovery_done(union ctl_io *io); static void cfi_lun_io_done(union ctl_io *io); static struct ctl_frontend cfi_frontend = { .name = "kernel", .init = cfi_init, .shutdown = cfi_shutdown, }; CTL_FRONTEND_DECLARE(ctlcfi, cfi_frontend); int cfi_init(void) { struct cfi_softc *softc; struct ctl_port *port; int retval; softc = &fetd_internal_softc; port = &softc->port; retval = 0; if (sizeof(struct cfi_lun_io) > CTL_PORT_PRIV_SIZE) { printf("%s: size of struct cfi_lun_io %zd > " "CTL_PORT_PRIV_SIZE %d\n", __func__, sizeof(struct cfi_lun_io), CTL_PORT_PRIV_SIZE); } memset(softc, 0, sizeof(*softc)); mtx_init(&softc->lock, "CTL frontend mutex", NULL, MTX_DEF); STAILQ_INIT(&softc->lun_list); STAILQ_INIT(&softc->metatask_list); sprintf(softc->fe_name, "kernel"); port->frontend = &cfi_frontend; port->port_type = CTL_PORT_INTERNAL; port->num_requested_ctl_io = 100; port->port_name = softc->fe_name; port->port_online = cfi_online; port->port_offline = cfi_offline; port->onoff_arg = softc; port->lun_enable = cfi_lun_enable; port->lun_disable = cfi_lun_disable; port->targ_lun_arg = softc; port->fe_datamove = cfi_datamove; port->fe_done = cfi_done; port->max_targets = 15; port->max_target_id = 15; if (ctl_port_register(port) != 0) { printf("%s: internal frontend registration failed\n", __func__); return (0); } cfi_lun_zone = uma_zcreate("cfi_lun", sizeof(struct cfi_lun), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); cfi_metatask_zone = uma_zcreate("cfi_metatask", sizeof(struct cfi_metatask), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); return (0); } void cfi_shutdown(void) { struct cfi_softc *softc; softc = &fetd_internal_softc; /* * XXX KDM need to clear out any I/O pending on each LUN. */ if (ctl_port_deregister(&softc->port) != 0) printf("%s: ctl_frontend_deregister() failed\n", __func__); uma_zdestroy(cfi_lun_zone); uma_zdestroy(cfi_metatask_zone); } static void cfi_online(void *arg) { struct cfi_softc *softc; struct cfi_lun *lun; softc = (struct cfi_softc *)arg; softc->flags |= CFI_ONLINE; /* * Go through and kick off the probe for each lun. Should we check * the LUN flags here to determine whether or not to probe it? */ mtx_lock(&softc->lock); STAILQ_FOREACH(lun, &softc->lun_list, links) cfi_lun_probe(lun, /*have_lock*/ 1); mtx_unlock(&softc->lock); } static void cfi_offline(void *arg) { struct cfi_softc *softc; softc = (struct cfi_softc *)arg; softc->flags &= ~CFI_ONLINE; } static int cfi_lun_enable(void *arg, struct ctl_id target_id, int lun_id) { struct cfi_softc *softc; struct cfi_lun *lun; int found; softc = (struct cfi_softc *)arg; found = 0; mtx_lock(&softc->lock); STAILQ_FOREACH(lun, &softc->lun_list, links) { if ((lun->target_id.id == target_id.id) && (lun->lun_id == lun_id)) { found = 1; break; } } mtx_unlock(&softc->lock); /* * If we already have this target/LUN, there is no reason to add * it to our lists again. */ if (found != 0) return (0); lun = uma_zalloc(cfi_lun_zone, M_NOWAIT | M_ZERO); if (lun == NULL) { printf("%s: unable to allocate LUN structure\n", __func__); return (1); } lun->target_id = target_id; lun->lun_id = lun_id; lun->cur_tag_num = 0; lun->state = CFI_LUN_INQUIRY; lun->softc = softc; STAILQ_INIT(&lun->io_list); mtx_lock(&softc->lock); STAILQ_INSERT_TAIL(&softc->lun_list, lun, links); mtx_unlock(&softc->lock); cfi_lun_probe(lun, /*have_lock*/ 0); return (0); } static int cfi_lun_disable(void *arg, struct ctl_id target_id, int lun_id) { struct cfi_softc *softc; struct cfi_lun *lun; int found; softc = (struct cfi_softc *)arg; found = 0; /* * XXX KDM need to do an invalidate and then a free when any * pending I/O has completed. Or do we? CTL won't free a LUN * while any I/O is pending. So we won't get this notification * unless any I/O we have pending on a LUN has completed. */ mtx_lock(&softc->lock); STAILQ_FOREACH(lun, &softc->lun_list, links) { if ((lun->target_id.id == target_id.id) && (lun->lun_id == lun_id)) { found = 1; break; } } if (found != 0) STAILQ_REMOVE(&softc->lun_list, lun, cfi_lun, links); mtx_unlock(&softc->lock); if (found == 0) { printf("%s: can't find target %ju lun %d\n", __func__, (uintmax_t)target_id.id, lun_id); return (1); } uma_zfree(cfi_lun_zone, lun); return (0); } static void cfi_datamove(union ctl_io *io) { struct ctl_sg_entry *ext_sglist, *kern_sglist; struct ctl_sg_entry ext_entry, kern_entry; int ext_sglen, ext_sg_entries, kern_sg_entries; int ext_sg_start, ext_offset; int len_to_copy, len_copied; int kern_watermark, ext_watermark; int ext_sglist_malloced; struct ctl_scsiio *ctsio; int i, j; ext_sglist_malloced = 0; ext_sg_start = 0; ext_offset = 0; ext_sglist = NULL; CTL_DEBUG_PRINT(("%s\n", __func__)); ctsio = &io->scsiio; /* * If this is the case, we're probably doing a BBR read and don't * actually need to transfer the data. This will effectively * bit-bucket the data. */ if (ctsio->ext_data_ptr == NULL) goto bailout; /* * To simplify things here, if we have a single buffer, stick it in * a S/G entry and just make it a single entry S/G list. */ if (ctsio->io_hdr.flags & CTL_FLAG_EDPTR_SGLIST) { int len_seen; ext_sglen = ctsio->ext_sg_entries * sizeof(*ext_sglist); ext_sglist = (struct ctl_sg_entry *)malloc(ext_sglen, M_CTL_CFI, M_WAITOK); ext_sglist_malloced = 1; if (memcpy(ext_sglist, ctsio->ext_data_ptr, ext_sglen) != 0) { ctl_set_internal_failure(ctsio, /*sks_valid*/ 0, /*retry_count*/ 0); goto bailout; } ext_sg_entries = ctsio->ext_sg_entries; len_seen = 0; for (i = 0; i < ext_sg_entries; i++) { if ((len_seen + ext_sglist[i].len) >= ctsio->ext_data_filled) { ext_sg_start = i; ext_offset = ctsio->ext_data_filled - len_seen; break; } len_seen += ext_sglist[i].len; } } else { ext_sglist = &ext_entry; ext_sglist->addr = ctsio->ext_data_ptr; ext_sglist->len = ctsio->ext_data_len; ext_sg_entries = 1; ext_sg_start = 0; ext_offset = ctsio->ext_data_filled; } if (ctsio->kern_sg_entries > 0) { kern_sglist = (struct ctl_sg_entry *)ctsio->kern_data_ptr; kern_sg_entries = ctsio->kern_sg_entries; } else { kern_sglist = &kern_entry; kern_sglist->addr = ctsio->kern_data_ptr; kern_sglist->len = ctsio->kern_data_len; kern_sg_entries = 1; } kern_watermark = 0; ext_watermark = ext_offset; len_copied = 0; for (i = ext_sg_start, j = 0; i < ext_sg_entries && j < kern_sg_entries;) { uint8_t *ext_ptr, *kern_ptr; len_to_copy = MIN(ext_sglist[i].len - ext_watermark, kern_sglist[j].len - kern_watermark); ext_ptr = (uint8_t *)ext_sglist[i].addr; ext_ptr = ext_ptr + ext_watermark; if (io->io_hdr.flags & CTL_FLAG_BUS_ADDR) { /* * XXX KDM fix this! */ panic("need to implement bus address support"); #if 0 kern_ptr = bus_to_virt(kern_sglist[j].addr); #endif } else kern_ptr = (uint8_t *)kern_sglist[j].addr; kern_ptr = kern_ptr + kern_watermark; kern_watermark += len_to_copy; ext_watermark += len_to_copy; if ((ctsio->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { CTL_DEBUG_PRINT(("%s: copying %d bytes to user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, kern_ptr, ext_ptr)); memcpy(ext_ptr, kern_ptr, len_to_copy); } else { CTL_DEBUG_PRINT(("%s: copying %d bytes from user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, ext_ptr, kern_ptr)); memcpy(kern_ptr, ext_ptr, len_to_copy); } len_copied += len_to_copy; if (ext_sglist[i].len == ext_watermark) { i++; ext_watermark = 0; } if (kern_sglist[j].len == kern_watermark) { j++; kern_watermark = 0; } } ctsio->ext_data_filled += len_copied; CTL_DEBUG_PRINT(("%s: ext_sg_entries: %d, kern_sg_entries: %d\n", __func__, ext_sg_entries, kern_sg_entries)); CTL_DEBUG_PRINT(("%s: ext_data_len = %d, kern_data_len = %d\n", __func__, ctsio->ext_data_len, ctsio->kern_data_len)); /* XXX KDM set residual?? */ bailout: if (ext_sglist_malloced != 0) free(ext_sglist, M_CTL_CFI); io->scsiio.be_move_done(io); return; } /* * For any sort of check condition, busy, etc., we just retry. We do not * decrement the retry count for unit attention type errors. These are * normal, and we want to save the retry count for "real" errors. Otherwise, * we could end up with situations where a command will succeed in some * situations and fail in others, depending on whether a unit attention is * pending. Also, some of our error recovery actions, most notably the * LUN reset action, will cause a unit attention. * * We can add more detail here later if necessary. */ static cfi_error_action cfi_checkcond_parse(union ctl_io *io, struct cfi_lun_io *lun_io) { cfi_error_action error_action; int error_code, sense_key, asc, ascq; /* * Default to retrying the command. */ error_action = CFI_ERR_RETRY; scsi_extract_sense_len(&io->scsiio.sense_data, io->scsiio.sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); switch (error_code) { case SSD_DEFERRED_ERROR: case SSD_DESC_DEFERRED_ERROR: error_action |= CFI_ERR_NO_DECREMENT; break; case SSD_CURRENT_ERROR: case SSD_DESC_CURRENT_ERROR: default: { switch (sense_key) { case SSD_KEY_UNIT_ATTENTION: error_action |= CFI_ERR_NO_DECREMENT; break; case SSD_KEY_HARDWARE_ERROR: /* * This is our generic "something bad happened" * error code. It often isn't recoverable. */ if ((asc == 0x44) && (ascq == 0x00)) error_action = CFI_ERR_FAIL; break; case SSD_KEY_NOT_READY: /* * If the LUN is powered down, there likely isn't * much point in retrying right now. */ if ((asc == 0x04) && (ascq == 0x02)) error_action = CFI_ERR_FAIL; /* * If the LUN is offline, there probably isn't much * point in retrying, either. */ if ((asc == 0x04) && (ascq == 0x03)) error_action = CFI_ERR_FAIL; break; } } } return (error_action); } static cfi_error_action cfi_error_parse(union ctl_io *io, struct cfi_lun_io *lun_io) { cfi_error_action error_action; error_action = CFI_ERR_RETRY; switch (io->io_hdr.io_type) { case CTL_IO_SCSI: switch (io->io_hdr.status & CTL_STATUS_MASK) { case CTL_SCSI_ERROR: switch (io->scsiio.scsi_status) { case SCSI_STATUS_RESERV_CONFLICT: /* * For a reservation conflict, we'll usually * want the hard error recovery policy, so * we'll reset the LUN. */ if (lun_io->policy == CFI_ERR_HARD) error_action = CFI_ERR_LUN_RESET; else error_action = CFI_ERR_RETRY; break; case SCSI_STATUS_CHECK_COND: default: error_action = cfi_checkcond_parse(io, lun_io); break; } break; default: error_action = CFI_ERR_RETRY; break; } break; case CTL_IO_TASK: /* * In theory task management commands shouldn't fail... */ error_action = CFI_ERR_RETRY; break; default: printf("%s: invalid ctl_io type %d\n", __func__, io->io_hdr.io_type); panic("%s: invalid ctl_io type %d\n", __func__, io->io_hdr.io_type); break; } return (error_action); } static void cfi_init_io(union ctl_io *io, struct cfi_lun *lun, struct cfi_metatask *metatask, cfi_error_policy policy, int retries, struct cfi_lun_io *orig_lun_io, void (*done_function)(union ctl_io *io)) { struct cfi_lun_io *lun_io; io->io_hdr.nexus.initid.id = 7; io->io_hdr.nexus.targ_port = lun->softc->port.targ_port; io->io_hdr.nexus.targ_target.id = lun->target_id.id; io->io_hdr.nexus.targ_lun = lun->lun_id; io->io_hdr.retries = retries; lun_io = (struct cfi_lun_io *)io->io_hdr.port_priv; io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = lun_io; lun_io->lun = lun; lun_io->metatask = metatask; lun_io->ctl_io = io; lun_io->policy = policy; lun_io->orig_lun_io = orig_lun_io; lun_io->done_function = done_function; /* * We only set the tag number for SCSI I/Os. For task management * commands, the tag number is only really needed for aborts, so * the caller can set it if necessary. */ switch (io->io_hdr.io_type) { case CTL_IO_SCSI: io->scsiio.tag_num = lun->cur_tag_num++; break; case CTL_IO_TASK: default: break; } } static void cfi_done(union ctl_io *io) { struct cfi_lun_io *lun_io; struct cfi_softc *softc; struct cfi_lun *lun; lun_io = (struct cfi_lun_io *) io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; lun = lun_io->lun; softc = lun->softc; /* * Very minimal retry logic. We basically retry if we got an error * back, and the retry count is greater than 0. If we ever want * more sophisticated initiator type behavior, the CAM error * recovery code in ../common might be helpful. */ if (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) && (io->io_hdr.retries > 0)) { ctl_io_status old_status; cfi_error_action error_action; error_action = cfi_error_parse(io, lun_io); switch (error_action & CFI_ERR_MASK) { case CFI_ERR_FAIL: goto done; break; /* NOTREACHED */ case CFI_ERR_LUN_RESET: { union ctl_io *new_io; struct cfi_lun_io *new_lun_io; new_io = ctl_alloc_io(softc->port.ctl_pool_ref); ctl_zero_io(new_io); new_io->io_hdr.io_type = CTL_IO_TASK; new_io->taskio.task_action = CTL_TASK_LUN_RESET; cfi_init_io(new_io, /*lun*/ lun_io->lun, /*metatask*/ NULL, /*policy*/ CFI_ERR_SOFT, /*retries*/ 0, /*orig_lun_io*/lun_io, /*done_function*/ cfi_err_recovery_done); new_lun_io = (struct cfi_lun_io *) new_io->io_hdr.port_priv; mtx_lock(&lun->softc->lock); STAILQ_INSERT_TAIL(&lun->io_list, new_lun_io, links); mtx_unlock(&lun->softc->lock); io = new_io; break; } case CFI_ERR_RETRY: default: if ((error_action & CFI_ERR_NO_DECREMENT) == 0) io->io_hdr.retries--; break; } old_status = io->io_hdr.status; io->io_hdr.status = CTL_STATUS_NONE; #if 0 io->io_hdr.flags &= ~CTL_FLAG_ALREADY_DONE; #endif io->io_hdr.flags &= ~CTL_FLAG_ABORT; io->io_hdr.flags &= ~CTL_FLAG_SENT_2OTHER_SC; if (ctl_queue(io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); io->io_hdr.status = old_status; } else return; } done: lun_io->done_function(io); } static void cfi_lun_probe_done(union ctl_io *io) { struct cfi_lun *lun; struct cfi_lun_io *lun_io; lun_io = (struct cfi_lun_io *) io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; lun = lun_io->lun; switch (lun->state) { case CFI_LUN_INQUIRY: { if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) { /* print out something here?? */ printf("%s: LUN %d probe failed because inquiry " "failed\n", __func__, lun->lun_id); ctl_io_error_print(io, NULL); } else { if (SID_TYPE(&lun->inq_data) != T_DIRECT) { char path_str[40]; lun->state = CFI_LUN_READY; ctl_scsi_path_string(io, path_str, sizeof(path_str)); printf("%s", path_str); scsi_print_inquiry(&lun->inq_data); } else { lun->state = CFI_LUN_READCAPACITY; cfi_lun_probe(lun, /*have_lock*/ 0); } } mtx_lock(&lun->softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&lun->softc->lock); ctl_free_io(io); break; } case CFI_LUN_READCAPACITY: case CFI_LUN_READCAPACITY_16: { uint64_t maxlba; uint32_t blocksize; maxlba = 0; blocksize = 0; if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) { printf("%s: LUN %d probe failed because READ CAPACITY " "failed\n", __func__, lun->lun_id); ctl_io_error_print(io, NULL); } else { if (lun->state == CFI_LUN_READCAPACITY) { struct scsi_read_capacity_data *rdcap; rdcap = (struct scsi_read_capacity_data *) io->scsiio.ext_data_ptr; maxlba = scsi_4btoul(rdcap->addr); blocksize = scsi_4btoul(rdcap->length); if (blocksize == 0) { printf("%s: LUN %d has invalid " "blocksize 0, probe aborted\n", __func__, lun->lun_id); } else if (maxlba == 0xffffffff) { lun->state = CFI_LUN_READCAPACITY_16; cfi_lun_probe(lun, /*have_lock*/ 0); } else lun->state = CFI_LUN_READY; } else { struct scsi_read_capacity_data_long *rdcap_long; rdcap_long = (struct scsi_read_capacity_data_long *) io->scsiio.ext_data_ptr; maxlba = scsi_8btou64(rdcap_long->addr); blocksize = scsi_4btoul(rdcap_long->length); if (blocksize == 0) { printf("%s: LUN %d has invalid " "blocksize 0, probe aborted\n", __func__, lun->lun_id); } else lun->state = CFI_LUN_READY; } } if (lun->state == CFI_LUN_READY) { char path_str[40]; lun->num_blocks = maxlba + 1; lun->blocksize = blocksize; /* * If this is true, the blocksize is a power of 2. * We already checked for 0 above. */ if (((blocksize - 1) & blocksize) == 0) { int i; for (i = 0; i < 32; i++) { if ((blocksize & (1 << i)) != 0) { lun->blocksize_powerof2 = i; break; } } } ctl_scsi_path_string(io, path_str,sizeof(path_str)); printf("%s", path_str); scsi_print_inquiry(&lun->inq_data); printf("%s %ju blocks, blocksize %d\n", path_str, (uintmax_t)maxlba + 1, blocksize); } mtx_lock(&lun->softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&lun->softc->lock); free(io->scsiio.ext_data_ptr, M_CTL_CFI); ctl_free_io(io); break; } case CFI_LUN_READY: default: mtx_lock(&lun->softc->lock); /* How did we get here?? */ STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&lun->softc->lock); ctl_free_io(io); break; } } static void cfi_lun_probe(struct cfi_lun *lun, int have_lock) { if (have_lock == 0) mtx_lock(&lun->softc->lock); if ((lun->softc->flags & CFI_ONLINE) == 0) { if (have_lock == 0) mtx_unlock(&lun->softc->lock); return; } if (have_lock == 0) mtx_unlock(&lun->softc->lock); switch (lun->state) { case CFI_LUN_INQUIRY: { struct cfi_lun_io *lun_io; union ctl_io *io; io = ctl_alloc_io(lun->softc->port.ctl_pool_ref); ctl_scsi_inquiry(io, /*data_ptr*/(uint8_t *)&lun->inq_data, /*data_len*/ sizeof(lun->inq_data), /*byte2*/ 0, /*page_code*/ 0, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); cfi_init_io(io, /*lun*/ lun, /*metatask*/ NULL, /*policy*/ CFI_ERR_SOFT, /*retries*/ 5, /*orig_lun_io*/ NULL, /*done_function*/ cfi_lun_probe_done); lun_io = (struct cfi_lun_io *)io->io_hdr.port_priv; if (have_lock == 0) mtx_lock(&lun->softc->lock); STAILQ_INSERT_TAIL(&lun->io_list, lun_io, links); if (have_lock == 0) mtx_unlock(&lun->softc->lock); if (ctl_queue(io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); ctl_free_io(io); } break; } case CFI_LUN_READCAPACITY: case CFI_LUN_READCAPACITY_16: { struct cfi_lun_io *lun_io; uint8_t *dataptr; union ctl_io *io; io = ctl_alloc_io(lun->softc->port.ctl_pool_ref); dataptr = malloc(sizeof(struct scsi_read_capacity_data_long), M_CTL_CFI, M_NOWAIT); if (dataptr == NULL) { printf("%s: unable to allocate SCSI read capacity " "buffer for target %ju lun %d\n", __func__, (uintmax_t)lun->target_id.id, lun->lun_id); return; } if (lun->state == CFI_LUN_READCAPACITY) { ctl_scsi_read_capacity(io, /*data_ptr*/ dataptr, /*data_len*/ sizeof(struct scsi_read_capacity_data_long), /*addr*/ 0, /*reladr*/ 0, /*pmi*/ 0, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); } else { ctl_scsi_read_capacity_16(io, /*data_ptr*/ dataptr, /*data_len*/ sizeof(struct scsi_read_capacity_data_long), /*addr*/ 0, /*reladr*/ 0, /*pmi*/ 0, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); } cfi_init_io(io, /*lun*/ lun, /*metatask*/ NULL, /*policy*/ CFI_ERR_SOFT, /*retries*/ 7, /*orig_lun_io*/ NULL, /*done_function*/ cfi_lun_probe_done); lun_io = (struct cfi_lun_io *)io->io_hdr.port_priv; if (have_lock == 0) mtx_lock(&lun->softc->lock); STAILQ_INSERT_TAIL(&lun->io_list, lun_io, links); if (have_lock == 0) mtx_unlock(&lun->softc->lock); if (ctl_queue(io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); free(dataptr, M_CTL_CFI); ctl_free_io(io); } break; } case CFI_LUN_READY: default: /* Why were we called? */ break; } } static void cfi_metatask_done(struct cfi_softc *softc, struct cfi_metatask *metatask) { mtx_lock(&softc->lock); STAILQ_REMOVE(&softc->metatask_list, metatask, cfi_metatask, links); mtx_unlock(&softc->lock); /* * Return status to the caller. Caller allocated storage, and is * responsible for calling cfi_free_metatask to release it once * they've seen the status. */ metatask->callback(metatask->callback_arg, metatask); } static void cfi_metatask_bbr_errorparse(struct cfi_metatask *metatask, union ctl_io *io) { int error_code, sense_key, asc, ascq; if (metatask->tasktype != CFI_TASK_BBRREAD) return; if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) { metatask->status = CFI_MT_SUCCESS; metatask->taskinfo.bbrread.status = CFI_BBR_SUCCESS; return; } if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SCSI_ERROR) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_ERROR; return; } metatask->taskinfo.bbrread.scsi_status = io->scsiio.scsi_status; memcpy(&metatask->taskinfo.bbrread.sense_data, &io->scsiio.sense_data, MIN(sizeof(metatask->taskinfo.bbrread.sense_data), sizeof(io->scsiio.sense_data))); if (io->scsiio.scsi_status == SCSI_STATUS_RESERV_CONFLICT) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_RESERV_CONFLICT; return; } if (io->scsiio.scsi_status != SCSI_STATUS_CHECK_COND) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_SCSI_ERROR; return; } scsi_extract_sense_len(&io->scsiio.sense_data, io->scsiio.sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); switch (error_code) { case SSD_DEFERRED_ERROR: case SSD_DESC_DEFERRED_ERROR: metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_SCSI_ERROR; break; case SSD_CURRENT_ERROR: case SSD_DESC_CURRENT_ERROR: default: { struct scsi_sense_data *sense; sense = &io->scsiio.sense_data; if ((asc == 0x04) && (ascq == 0x02)) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_LUN_STOPPED; } else if ((asc == 0x04) && (ascq == 0x03)) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_LUN_OFFLINE_CTL; } else if ((asc == 0x44) && (ascq == 0x00)) { #ifdef NEEDTOPORT if (sense->sense_key_spec[0] & SSD_SCS_VALID) { uint16_t retry_count; retry_count = sense->sense_key_spec[1] << 8 | sense->sense_key_spec[2]; if (((retry_count & 0xf000) == CSC_RAIDCORE) && ((retry_count & 0x0f00) == CSC_SHELF_SW) && ((retry_count & 0xff) == RC_STS_DEVICE_OFFLINE)) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_LUN_OFFLINE_RC; } else { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_SCSI_ERROR; } } else { #endif /* NEEDTOPORT */ metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_SCSI_ERROR; #ifdef NEEDTOPORT } #endif } else { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_SCSI_ERROR; } break; } } } static void cfi_metatask_io_done(union ctl_io *io) { struct cfi_lun_io *lun_io; struct cfi_metatask *metatask; struct cfi_softc *softc; struct cfi_lun *lun; lun_io = (struct cfi_lun_io *) io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; lun = lun_io->lun; softc = lun->softc; metatask = lun_io->metatask; switch (metatask->tasktype) { case CFI_TASK_STARTUP: case CFI_TASK_SHUTDOWN: { int failed, done, is_start; failed = 0; done = 0; if (metatask->tasktype == CFI_TASK_STARTUP) is_start = 1; else is_start = 0; mtx_lock(&softc->lock); if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) metatask->taskinfo.startstop.luns_complete++; else { metatask->taskinfo.startstop.luns_failed++; failed = 1; } if ((metatask->taskinfo.startstop.luns_complete + metatask->taskinfo.startstop.luns_failed) >= metatask->taskinfo.startstop.total_luns) done = 1; mtx_unlock(&softc->lock); if (failed != 0) { printf("%s: LUN %d %s request failed\n", __func__, lun_io->lun->lun_id, (is_start == 1) ? "start" : "stop"); ctl_io_error_print(io, &lun_io->lun->inq_data); } if (done != 0) { if (metatask->taskinfo.startstop.luns_failed > 0) metatask->status = CFI_MT_ERROR; else metatask->status = CFI_MT_SUCCESS; cfi_metatask_done(softc, metatask); } mtx_lock(&softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&softc->lock); ctl_free_io(io); break; } case CFI_TASK_BBRREAD: { /* * Translate the SCSI error into an enumeration. */ cfi_metatask_bbr_errorparse(metatask, io); mtx_lock(&softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&softc->lock); ctl_free_io(io); cfi_metatask_done(softc, metatask); break; } default: /* * This shouldn't happen. */ mtx_lock(&softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&softc->lock); ctl_free_io(io); break; } } static void cfi_err_recovery_done(union ctl_io *io) { struct cfi_lun_io *lun_io, *orig_lun_io; struct cfi_lun *lun; union ctl_io *orig_io; lun_io = (struct cfi_lun_io *)io->io_hdr.port_priv; orig_lun_io = lun_io->orig_lun_io; orig_io = orig_lun_io->ctl_io; lun = lun_io->lun; if (io->io_hdr.status != CTL_SUCCESS) { printf("%s: error recovery action failed. Original " "error:\n", __func__); ctl_io_error_print(orig_lun_io->ctl_io, &lun->inq_data); printf("%s: error from error recovery action:\n", __func__); ctl_io_error_print(io, &lun->inq_data); printf("%s: trying original command again...\n", __func__); } mtx_lock(&lun->softc->lock); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); mtx_unlock(&lun->softc->lock); ctl_free_io(io); orig_io->io_hdr.retries--; orig_io->io_hdr.status = CTL_STATUS_NONE; if (ctl_queue(orig_io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); STAILQ_REMOVE(&lun->io_list, orig_lun_io, cfi_lun_io, links); ctl_free_io(orig_io); } } static void cfi_lun_io_done(union ctl_io *io) { struct cfi_lun *lun; struct cfi_lun_io *lun_io; lun_io = (struct cfi_lun_io *) io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; lun = lun_io->lun; if (lun_io->metatask == NULL) { printf("%s: I/O has no metatask pointer, discarding\n", __func__); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); ctl_free_io(io); return; } cfi_metatask_io_done(io); } void cfi_action(struct cfi_metatask *metatask) { struct cfi_softc *softc; softc = &fetd_internal_softc; mtx_lock(&softc->lock); STAILQ_INSERT_TAIL(&softc->metatask_list, metatask, links); if ((softc->flags & CFI_ONLINE) == 0) { mtx_unlock(&softc->lock); metatask->status = CFI_MT_PORT_OFFLINE; cfi_metatask_done(softc, metatask); return; } else mtx_unlock(&softc->lock); switch (metatask->tasktype) { case CFI_TASK_STARTUP: case CFI_TASK_SHUTDOWN: { union ctl_io *io; int da_luns, ios_allocated, do_start; struct cfi_lun *lun; STAILQ_HEAD(, ctl_io_hdr) tmp_io_list; da_luns = 0; ios_allocated = 0; STAILQ_INIT(&tmp_io_list); if (metatask->tasktype == CFI_TASK_STARTUP) do_start = 1; else do_start = 0; mtx_lock(&softc->lock); STAILQ_FOREACH(lun, &softc->lun_list, links) { if (lun->state != CFI_LUN_READY) continue; if (SID_TYPE(&lun->inq_data) != T_DIRECT) continue; da_luns++; io = ctl_alloc_io_nowait(softc->port.ctl_pool_ref); if (io != NULL) { ios_allocated++; STAILQ_INSERT_TAIL(&tmp_io_list, &io->io_hdr, links); } } if (ios_allocated < da_luns) { printf("%s: error allocating ctl_io for %s\n", __func__, (do_start == 1) ? "startup" : "shutdown"); da_luns = ios_allocated; } metatask->taskinfo.startstop.total_luns = da_luns; STAILQ_FOREACH(lun, &softc->lun_list, links) { struct cfi_lun_io *lun_io; if (lun->state != CFI_LUN_READY) continue; if (SID_TYPE(&lun->inq_data) != T_DIRECT) continue; io = (union ctl_io *)STAILQ_FIRST(&tmp_io_list); if (io == NULL) break; STAILQ_REMOVE(&tmp_io_list, &io->io_hdr, ctl_io_hdr, links); ctl_scsi_start_stop(io, /*start*/ do_start, /*load_eject*/ 0, /*immediate*/ 0, /*power_conditions*/ SSS_PC_START_VALID, /*onoffline*/ 1, /*ctl_tag_type*/ CTL_TAG_ORDERED, /*control*/ 0); cfi_init_io(io, /*lun*/ lun, /*metatask*/ metatask, /*policy*/ CFI_ERR_HARD, /*retries*/ 3, /*orig_lun_io*/ NULL, /*done_function*/ cfi_lun_io_done); lun_io = (struct cfi_lun_io *) io->io_hdr.port_priv; STAILQ_INSERT_TAIL(&lun->io_list, lun_io, links); if (ctl_queue(io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); ctl_free_io(io); metatask->taskinfo.startstop.total_luns--; } } if (STAILQ_FIRST(&tmp_io_list) != NULL) { printf("%s: error: tmp_io_list != NULL\n", __func__); for (io = (union ctl_io *)STAILQ_FIRST(&tmp_io_list); io != NULL; io = (union ctl_io *)STAILQ_FIRST(&tmp_io_list)) { STAILQ_REMOVE(&tmp_io_list, &io->io_hdr, ctl_io_hdr, links); ctl_free_io(io); } } mtx_unlock(&softc->lock); break; } case CFI_TASK_BBRREAD: { union ctl_io *io; struct cfi_lun *lun; struct cfi_lun_io *lun_io; cfi_bbrread_status status; int req_lun_num; uint32_t num_blocks; status = CFI_BBR_SUCCESS; req_lun_num = metatask->taskinfo.bbrread.lun_num; mtx_lock(&softc->lock); STAILQ_FOREACH(lun, &softc->lun_list, links) { if (lun->lun_id != req_lun_num) continue; if (lun->state != CFI_LUN_READY) { status = CFI_BBR_LUN_UNCONFIG; break; } else break; } if (lun == NULL) status = CFI_BBR_NO_LUN; if (status != CFI_BBR_SUCCESS) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = status; mtx_unlock(&softc->lock); cfi_metatask_done(softc, metatask); break; } /* * Convert the number of bytes given into blocks and check * that the number of bytes is a multiple of the blocksize. * CTL will verify that the LBA is okay. */ if (lun->blocksize_powerof2 != 0) { if ((metatask->taskinfo.bbrread.len & (lun->blocksize - 1)) != 0) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_BAD_LEN; cfi_metatask_done(softc, metatask); break; } num_blocks = metatask->taskinfo.bbrread.len >> lun->blocksize_powerof2; } else { /* * XXX KDM this could result in floating point * division, which isn't supported in the kernel on * x86 at least. */ if ((metatask->taskinfo.bbrread.len % lun->blocksize) != 0) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_BAD_LEN; cfi_metatask_done(softc, metatask); break; } /* * XXX KDM this could result in floating point * division in some cases. */ num_blocks = metatask->taskinfo.bbrread.len / lun->blocksize; } io = ctl_alloc_io_nowait(softc->port.ctl_pool_ref); if (io == NULL) { metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_NO_MEM; mtx_unlock(&softc->lock); cfi_metatask_done(softc, metatask); break; } /* * XXX KDM need to do a read capacity to get the blocksize * for this device. */ ctl_scsi_read_write(io, /*data_ptr*/ NULL, /*data_len*/ metatask->taskinfo.bbrread.len, /*read_op*/ 1, /*byte2*/ 0, /*minimum_cdb_size*/ 0, /*lba*/ metatask->taskinfo.bbrread.lba, /*num_blocks*/ num_blocks, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); cfi_init_io(io, /*lun*/ lun, /*metatask*/ metatask, /*policy*/ CFI_ERR_SOFT, /*retries*/ 3, /*orig_lun_io*/ NULL, /*done_function*/ cfi_lun_io_done); lun_io = (struct cfi_lun_io *)io->io_hdr.port_priv; STAILQ_INSERT_TAIL(&lun->io_list, lun_io, links); if (ctl_queue(io) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); STAILQ_REMOVE(&lun->io_list, lun_io, cfi_lun_io, links); ctl_free_io(io); metatask->status = CFI_MT_ERROR; metatask->taskinfo.bbrread.status = CFI_BBR_ERROR; mtx_unlock(&softc->lock); cfi_metatask_done(softc, metatask); break; } mtx_unlock(&softc->lock); break; } default: panic("invalid metatask type %d", metatask->tasktype); break; /* NOTREACHED */ } } struct cfi_metatask * cfi_alloc_metatask(int can_wait) { struct cfi_metatask *metatask; struct cfi_softc *softc; softc = &fetd_internal_softc; metatask = uma_zalloc(cfi_metatask_zone, (can_wait ? M_WAITOK : M_NOWAIT) | M_ZERO); if (metatask == NULL) return (NULL); metatask->status = CFI_MT_NONE; return (metatask); } void cfi_free_metatask(struct cfi_metatask *metatask) { uma_zfree(cfi_metatask_zone, metatask); } /* * vim: ts=8 */