/* * Implementation of the Target Mode 'Black Hole device' for CAM. * * Copyright (c) 1999 Justin T. Gibbs. * 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, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD: src/sys/cam/scsi/scsi_targ_bh.c,v 1.4.2.6 2003/11/14 11:31:25 simokawa Exp $ */ #include #include #include #include #include #include #include #include #include #include #include "../cam.h" #include "../cam_ccb.h" #include "../cam_extend.h" #include "../cam_periph.h" #include "../cam_queue.h" #include "../cam_xpt_periph.h" #include "../cam_debug.h" #include "../cam_sim.h" #include "scsi_all.h" #include "scsi_message.h" MALLOC_DEFINE(M_SCSIBH, "SCSI bh", "SCSI blackhole buffers"); typedef enum { TARGBH_STATE_NORMAL, TARGBH_STATE_EXCEPTION, TARGBH_STATE_TEARDOWN } targbh_state; typedef enum { TARGBH_FLAG_NONE = 0x00, TARGBH_FLAG_LUN_ENABLED = 0x01 } targbh_flags; typedef enum { TARGBH_CCB_WORKQ, TARGBH_CCB_WAITING } targbh_ccb_types; #define MAX_ACCEPT 8 #define MAX_IMMEDIATE 16 #define MAX_BUF_SIZE 256 /* Max inquiry/sense/mode page transfer */ /* Offsets into our private CCB area for storing accept information */ #define ccb_type ppriv_field0 #define ccb_descr ppriv_ptr1 /* We stick a pointer to the originating accept TIO in each continue I/O CCB */ #define ccb_atio ppriv_ptr1 TAILQ_HEAD(ccb_queue, ccb_hdr); struct targbh_softc { struct ccb_queue pending_queue; struct ccb_queue work_queue; struct ccb_queue unknown_atio_queue; struct devstat device_stats; targbh_state state; targbh_flags flags; u_int init_level; u_int inq_data_len; struct ccb_accept_tio *accept_tio_list; struct ccb_hdr_slist immed_notify_slist; }; struct targbh_cmd_desc { struct ccb_accept_tio* atio_link; u_int data_resid; /* How much left to transfer */ u_int data_increment;/* Amount to send before next disconnect */ void* data; /* The data. Can be from backing_store or not */ void* backing_store;/* Backing store allocated for this descriptor*/ u_int max_size; /* Size of backing_store */ u_int32_t timeout; u_int8_t status; /* Status to return to initiator */ }; static struct scsi_inquiry_data no_lun_inq_data = { T_NODEVICE | (SID_QUAL_BAD_LU << 5), 0, /* version */2, /* format version */2 }; static struct scsi_sense_data no_lun_sense_data = { SSD_CURRENT_ERROR|SSD_ERRCODE_VALID, 0, SSD_KEY_NOT_READY, { 0, 0, 0, 0 }, /*extra_len*/offsetof(struct scsi_sense_data, fru) - offsetof(struct scsi_sense_data, extra_len), { 0, 0, 0, 0 }, /* Logical Unit Not Supported */ /*ASC*/0x25, /*ASCQ*/0 }; static const int request_sense_size = offsetof(struct scsi_sense_data, fru); static periph_init_t targbhinit; static void targbhasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static cam_status targbhenlun(struct cam_periph *periph); static cam_status targbhdislun(struct cam_periph *periph); static periph_ctor_t targbhctor; static periph_dtor_t targbhdtor; static periph_start_t targbhstart; static void targbhdone(struct cam_periph *periph, union ccb *done_ccb); #ifdef NOTYET static int targbherror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); #endif static struct targbh_cmd_desc* targbhallocdescr(void); static void targbhfreedescr(struct targbh_cmd_desc *buf); static struct periph_driver targbhdriver = { targbhinit, "targbh", TAILQ_HEAD_INITIALIZER(targbhdriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(targbh, targbhdriver); static void targbhinit(void) { cam_status status; /* * Install a global async callback. This callback will * receive async callbacks like "new path registered". */ status = xpt_register_async(AC_PATH_REGISTERED | AC_PATH_DEREGISTERED, targbhasync, NULL, NULL); if (status != CAM_REQ_CMP) { kprintf("targbh: Failed to attach master async callback " "due to status 0x%x!\n", status); } } static void targbhasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_path *new_path; struct ccb_pathinq *cpi; path_id_t bus_path_id; cam_status status; cpi = (struct ccb_pathinq *)arg; if (code == AC_PATH_REGISTERED) bus_path_id = cpi->ccb_h.path_id; else bus_path_id = xpt_path_path_id(path); /* * Allocate a peripheral instance for * this target instance. */ status = xpt_create_path(&new_path, NULL, bus_path_id, CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (status != CAM_REQ_CMP) { kprintf("targbhasync: Unable to create path " "due to status 0x%x\n", status); return; } switch (code) { case AC_PATH_REGISTERED: { /* Only attach to controllers that support target mode */ if ((cpi->target_sprt & PIT_PROCESSOR) == 0) break; status = cam_periph_alloc(targbhctor, NULL, targbhdtor, targbhstart, "targbh", CAM_PERIPH_BIO, new_path, targbhasync, AC_PATH_REGISTERED, cpi); break; } case AC_PATH_DEREGISTERED: { struct cam_periph *periph; if ((periph = cam_periph_find(new_path, "targbh")) != NULL) cam_periph_invalidate(periph); break; } default: break; } xpt_free_path(new_path); } /* Attempt to enable our lun */ static cam_status targbhenlun(struct cam_periph *periph) { union ccb *immed_ccb; struct targbh_softc *softc; cam_status status; int i; softc = (struct targbh_softc *)periph->softc; if ((softc->flags & TARGBH_FLAG_LUN_ENABLED) != 0) return (CAM_REQ_CMP); immed_ccb = xpt_alloc_ccb(); xpt_setup_ccb(&immed_ccb->ccb_h, periph->path, /*priority*/1); immed_ccb->ccb_h.func_code = XPT_EN_LUN; /* Don't need support for any vendor specific commands */ immed_ccb->cel.grp6_len = 0; immed_ccb->cel.grp7_len = 0; immed_ccb->cel.enable = 1; xpt_action(immed_ccb); status = immed_ccb->ccb_h.status; xpt_free_ccb(&immed_ccb->ccb_h); if (status != CAM_REQ_CMP) { xpt_print(periph->path, "targbhenlun - Enable Lun Rejected with status 0x%x\n", status); return (status); } softc->flags |= TARGBH_FLAG_LUN_ENABLED; /* * Build up a buffer of accept target I/O * operations for incoming selections. */ for (i = 0; i < MAX_ACCEPT; i++) { struct ccb_accept_tio *atio; atio = &xpt_alloc_ccb()->atio; atio->ccb_h.ccb_descr = targbhallocdescr(); xpt_setup_ccb(&atio->ccb_h, periph->path, /*priority*/1); atio->ccb_h.func_code = XPT_ACCEPT_TARGET_IO; atio->ccb_h.cbfcnp = targbhdone; xpt_action((union ccb *)atio); status = atio->ccb_h.status; if (status != CAM_REQ_INPROG) { targbhfreedescr(atio->ccb_h.ccb_descr); xpt_free_ccb(&atio->ccb_h); break; } ((struct targbh_cmd_desc*)atio->ccb_h.ccb_descr)->atio_link = softc->accept_tio_list; softc->accept_tio_list = atio; } if (i == 0) { xpt_print(periph->path, "targbhenlun - Could not allocate accept tio CCBs: status " "= 0x%x\n", status); targbhdislun(periph); return (CAM_REQ_CMP_ERR); } /* * Build up a buffer of immediate notify CCBs * so the SIM can tell us of asynchronous target mode events. */ for (i = 0; i < MAX_ACCEPT; i++) { struct ccb_immed_notify *inot; inot = &xpt_alloc_ccb()->cin; xpt_setup_ccb(&inot->ccb_h, periph->path, /*priority*/1); inot->ccb_h.func_code = XPT_IMMED_NOTIFY; inot->ccb_h.cbfcnp = targbhdone; xpt_action((union ccb *)inot); status = inot->ccb_h.status; if (status != CAM_REQ_INPROG) { xpt_free_ccb(&inot->ccb_h); break; } SLIST_INSERT_HEAD(&softc->immed_notify_slist, &inot->ccb_h, periph_links.sle); } if (i == 0) { xpt_print(periph->path, "targbhenlun - Could not allocate immediate notify " "CCBs: status = 0x%x\n", status); targbhdislun(periph); return (CAM_REQ_CMP_ERR); } return (CAM_REQ_CMP); } static cam_status targbhdislun(struct cam_periph *periph) { union ccb *ccb; struct targbh_softc *softc; struct ccb_accept_tio* atio; struct ccb_hdr *ccb_h; u_int32_t status; softc = (struct targbh_softc *)periph->softc; if ((softc->flags & TARGBH_FLAG_LUN_ENABLED) == 0) return CAM_REQ_CMP; /* XXX Block for Continue I/O completion */ ccb = xpt_alloc_ccb(); /* Kill off all ACCECPT and IMMEDIATE CCBs */ while ((atio = softc->accept_tio_list) != NULL) { softc->accept_tio_list = ((struct targbh_cmd_desc*)atio->ccb_h.ccb_descr)->atio_link; xpt_setup_ccb(&ccb->cab.ccb_h, periph->path, /*priority*/1); ccb->cab.ccb_h.func_code = XPT_ABORT; ccb->cab.abort_ccb = (union ccb *)atio; xpt_action(ccb); } while ((ccb_h = SLIST_FIRST(&softc->immed_notify_slist)) != NULL) { SLIST_REMOVE_HEAD(&softc->immed_notify_slist, periph_links.sle); xpt_setup_ccb(&ccb->cab.ccb_h, periph->path, /*priority*/1); ccb->cab.ccb_h.func_code = XPT_ABORT; ccb->cab.abort_ccb = (union ccb *)ccb_h; xpt_action(ccb); /* XXX xpt_free_ccb(ccb_h); needed? */ } /* * Dissable this lun. */ xpt_setup_ccb(&ccb->cel.ccb_h, periph->path, /*priority*/1); ccb->cel.ccb_h.func_code = XPT_EN_LUN; ccb->cel.enable = 0; xpt_action(ccb); status = ccb->cel.ccb_h.status; xpt_free_ccb(&ccb->ccb_h); if (status != CAM_REQ_CMP) kprintf("targbhdislun - Disabling lun on controller failed " "with status 0x%x\n", status); else softc->flags &= ~TARGBH_FLAG_LUN_ENABLED; return (status); } static cam_status targbhctor(struct cam_periph *periph, void *arg) { struct targbh_softc *softc; /* Allocate our per-instance private storage */ softc = kmalloc(sizeof(*softc), M_SCSIBH, M_INTWAIT | M_ZERO); TAILQ_INIT(&softc->pending_queue); TAILQ_INIT(&softc->work_queue); softc->accept_tio_list = NULL; SLIST_INIT(&softc->immed_notify_slist); softc->state = TARGBH_STATE_NORMAL; periph->softc = softc; softc->init_level++; return (targbhenlun(periph)); } static void targbhdtor(struct cam_periph *periph) { struct targbh_softc *softc; softc = (struct targbh_softc *)periph->softc; softc->state = TARGBH_STATE_TEARDOWN; targbhdislun(periph); switch (softc->init_level) { case 0: panic("targdtor - impossible init level"); case 1: /* FALLTHROUGH */ default: /* XXX Wait for callback of targbhdislun() */ sim_lock_sleep(softc, 0, "targbh", hz/2, periph->sim->lock); kfree(softc, M_SCSIBH); break; } } static void targbhstart(struct cam_periph *periph, union ccb *start_ccb) { struct targbh_softc *softc; struct ccb_hdr *ccbh; struct ccb_accept_tio *atio; struct targbh_cmd_desc *desc; struct ccb_scsiio *csio; ccb_flags flags; softc = (struct targbh_softc *)periph->softc; ccbh = TAILQ_FIRST(&softc->work_queue); if (periph->immediate_priority <= periph->pinfo.priority) { start_ccb->ccb_h.ccb_type = TARGBH_CCB_WAITING; SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h, periph_links.sle); periph->immediate_priority = CAM_PRIORITY_NONE; wakeup(&periph->ccb_list); } else if (ccbh == NULL) { xpt_release_ccb(start_ccb); } else { TAILQ_REMOVE(&softc->work_queue, ccbh, periph_links.tqe); TAILQ_INSERT_HEAD(&softc->pending_queue, ccbh, periph_links.tqe); atio = (struct ccb_accept_tio*)ccbh; desc = (struct targbh_cmd_desc *)atio->ccb_h.ccb_descr; /* Is this a tagged request? */ flags = atio->ccb_h.flags & (CAM_DIS_DISCONNECT|CAM_TAG_ACTION_VALID|CAM_DIR_MASK); csio = &start_ccb->csio; /* * If we are done with the transaction, tell the * controller to send status and perform a CMD_CMPLT. * If we have associated sense data, see if we can * send that too. */ if (desc->data_resid == desc->data_increment) { flags |= CAM_SEND_STATUS; if (atio->sense_len) { csio->sense_len = atio->sense_len; csio->sense_data = atio->sense_data; flags |= CAM_SEND_SENSE; } } cam_fill_ctio(csio, /*retries*/2, targbhdone, flags, (flags & CAM_TAG_ACTION_VALID)? MSG_SIMPLE_Q_TAG : 0, atio->tag_id, atio->init_id, desc->status, /*data_ptr*/desc->data_increment == 0 ? NULL : desc->data, /*dxfer_len*/desc->data_increment, /*timeout*/desc->timeout); /* Override our wildcard attachment */ start_ccb->ccb_h.target_id = atio->ccb_h.target_id; start_ccb->ccb_h.target_lun = atio->ccb_h.target_lun; start_ccb->ccb_h.ccb_type = TARGBH_CCB_WORKQ; start_ccb->ccb_h.ccb_atio = atio; CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("Sending a CTIO\n")); xpt_action(start_ccb); /* * If the queue was frozen waiting for the response * to this ATIO (for instance disconnection was disallowed), * then release it now that our response has been queued. */ if ((atio->ccb_h.status & CAM_DEV_QFRZN) != 0) { cam_release_devq(periph->path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); atio->ccb_h.status &= ~CAM_DEV_QFRZN; } ccbh = TAILQ_FIRST(&softc->work_queue); } if (ccbh != NULL) xpt_schedule(periph, /*priority*/1); } static void targbhdone(struct cam_periph *periph, union ccb *done_ccb) { struct targbh_softc *softc; softc = (struct targbh_softc *)periph->softc; if (done_ccb->ccb_h.ccb_type == TARGBH_CCB_WAITING) { /* Caller will release the CCB */ wakeup(&done_ccb->ccb_h.cbfcnp); return; } switch (done_ccb->ccb_h.func_code) { case XPT_ACCEPT_TARGET_IO: { struct ccb_accept_tio *atio; struct targbh_cmd_desc *descr; u_int8_t *cdb; int priority; atio = &done_ccb->atio; descr = (struct targbh_cmd_desc*)atio->ccb_h.ccb_descr; cdb = atio->cdb_io.cdb_bytes; if (softc->state == TARGBH_STATE_TEARDOWN || atio->ccb_h.status == CAM_REQ_ABORTED) { targbhfreedescr(descr); xpt_free_ccb(&done_ccb->ccb_h); return; } /* * Determine the type of incoming command and * setup our buffer for a response. */ switch (cdb[0]) { case INQUIRY: { struct scsi_inquiry *inq; inq = (struct scsi_inquiry *)cdb; CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("Saw an inquiry!\n")); /* * Validate the command. We don't * support any VPD pages, so complain * if EVPD is set. */ if ((inq->byte2 & SI_EVPD) != 0 || inq->page_code != 0) { atio->ccb_h.flags &= ~CAM_DIR_MASK; atio->ccb_h.flags |= CAM_DIR_NONE; /* * This needs to have other than a * no_lun_sense_data response. */ atio->sense_data = no_lun_sense_data; atio->sense_len = sizeof(no_lun_sense_data); descr->data_resid = 0; descr->data_increment = 0; descr->status = SCSI_STATUS_CHECK_COND; break; } /* * Direction is always relative * to the initator. */ atio->ccb_h.flags &= ~CAM_DIR_MASK; atio->ccb_h.flags |= CAM_DIR_IN; descr->data = &no_lun_inq_data; descr->data_resid = MIN(sizeof(no_lun_inq_data), SCSI_CDB6_LEN(inq->length)); descr->data_increment = descr->data_resid; descr->timeout = 5 * 1000; descr->status = SCSI_STATUS_OK; break; } case REQUEST_SENSE: { struct scsi_request_sense *rsense; rsense = (struct scsi_request_sense *)cdb; /* Refer to static sense data */ atio->ccb_h.flags &= ~CAM_DIR_MASK; atio->ccb_h.flags |= CAM_DIR_IN; descr->data = &no_lun_sense_data; descr->data_resid = request_sense_size; descr->data_resid = MIN(descr->data_resid, SCSI_CDB6_LEN(rsense->length)); descr->data_increment = descr->data_resid; descr->timeout = 5 * 1000; descr->status = SCSI_STATUS_OK; break; } default: /* Constant CA, tell initiator */ /* Direction is always relative to the initator */ atio->ccb_h.flags &= ~CAM_DIR_MASK; atio->ccb_h.flags |= CAM_DIR_NONE; atio->sense_data = no_lun_sense_data; atio->sense_len = sizeof (no_lun_sense_data); descr->data_resid = 0; descr->data_increment = 0; descr->timeout = 5 * 1000; descr->status = SCSI_STATUS_CHECK_COND; break; } /* Queue us up to receive a Continue Target I/O ccb. */ if ((atio->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) { TAILQ_INSERT_HEAD(&softc->work_queue, &atio->ccb_h, periph_links.tqe); priority = 0; } else { TAILQ_INSERT_TAIL(&softc->work_queue, &atio->ccb_h, periph_links.tqe); priority = 1; } xpt_schedule(periph, priority); break; } case XPT_CONT_TARGET_IO: { struct ccb_accept_tio *atio; struct targbh_cmd_desc *desc; CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("Received completed CTIO\n")); atio = (struct ccb_accept_tio*)done_ccb->ccb_h.ccb_atio; desc = (struct targbh_cmd_desc *)atio->ccb_h.ccb_descr; TAILQ_REMOVE(&softc->pending_queue, &atio->ccb_h, periph_links.tqe); /* * We could check for CAM_SENT_SENSE bein set here, * but since we're not maintaining any CA/UA state, * there's no point. */ atio->sense_len = 0; done_ccb->ccb_h.flags &= ~CAM_SEND_SENSE; done_ccb->ccb_h.status &= ~CAM_SENT_SENSE; /* * Any errors will not change the data we return, * so make sure the queue is not left frozen. * XXX - At some point there may be errors that * leave us in a connected state with the * initiator... */ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { kprintf("Releasing Queue\n"); cam_release_devq(done_ccb->ccb_h.path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); done_ccb->ccb_h.status &= ~CAM_DEV_QFRZN; } desc->data_resid -= desc->data_increment; xpt_release_ccb(done_ccb); if (softc->state != TARGBH_STATE_TEARDOWN) { /* * Send the original accept TIO back to the * controller to handle more work. */ CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, ("Returning ATIO to target\n")); /* Restore wildcards */ atio->ccb_h.target_id = CAM_TARGET_WILDCARD; atio->ccb_h.target_lun = CAM_LUN_WILDCARD; xpt_action((union ccb *)atio); break; } else { targbhfreedescr(desc); xpt_free_ccb(&atio->ccb_h); } break; } case XPT_IMMED_NOTIFY: { int frozen; frozen = (done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0; if (softc->state == TARGBH_STATE_TEARDOWN || done_ccb->ccb_h.status == CAM_REQ_ABORTED) { kprintf("Freed an immediate notify\n"); xpt_free_ccb(&done_ccb->ccb_h); } else { /* Requeue for another immediate event */ xpt_action(done_ccb); } if (frozen != 0) cam_release_devq(periph->path, /*relsim_flags*/0, /*opening reduction*/0, /*timeout*/0, /*getcount_only*/0); break; } default: panic("targbhdone: Unexpected ccb opcode"); break; } } #ifdef NOTYET static int targbherror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { return 0; } #endif static struct targbh_cmd_desc * targbhallocdescr(void) { struct targbh_cmd_desc* descr; /* Allocate the targbh_descr structure */ descr = kmalloc(sizeof(*descr), M_SCSIBH, M_INTWAIT | M_ZERO); /* Allocate buffer backing store */ descr->backing_store = kmalloc(MAX_BUF_SIZE, M_SCSIBH, M_INTWAIT); descr->max_size = MAX_BUF_SIZE; return (descr); } static void targbhfreedescr(struct targbh_cmd_desc *descr) { kfree(descr->backing_store, M_SCSIBH); kfree(descr, M_SCSIBH); }