diff options
Diffstat (limited to 'sys/cam/cam_periph.c')
-rw-r--r-- | sys/cam/cam_periph.c | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/sys/cam/cam_periph.c b/sys/cam/cam_periph.c new file mode 100644 index 0000000..3465b03 --- /dev/null +++ b/sys/cam/cam_periph.c @@ -0,0 +1,1493 @@ +/* + * Common functions for CAM "type" (peripheral) drivers. + * + * Copyright (c) 1997, 1998 Justin T. Gibbs. + * Copyright (c) 1997, 1998 Kenneth D. Merry. + * 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. + * + * $Id$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/types.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/devicestat.h> +#include <vm/vm.h> +#include <vm/vm_extern.h> + +#include <cam/cam.h> +#include <cam/cam_conf.h> +#include <cam/cam_ccb.h> +#include <cam/cam_xpt_periph.h> +#include <cam/cam_periph.h> +#include <cam/cam_debug.h> + +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> +#include <cam/scsi/scsi_da.h> +#include <cam/scsi/scsi_pass.h> + +static u_int camperiphnextunit(struct periph_driver *p_drv, + u_int newunit, int wired); +static u_int camperiphunit(struct periph_driver *p_drv, + path_id_t path_id_t, + target_id_t target, lun_id_t lun); +static void camperiphdone(struct cam_periph *periph, + union ccb *done_ccb); +static void camperiphfree(struct cam_periph *periph); + +cam_status +cam_periph_alloc(periph_ctor_t *periph_ctor, periph_dtor_t *periph_dtor, + periph_start_t *periph_start, char *name, cam_periph_type type, + struct cam_path *path, ac_callback_t *ac_callback, + ac_code code, void *arg) +{ + struct periph_driver **p_drv; + struct cam_periph *periph; + struct cam_periph *cur_periph; + path_id_t path_id; + target_id_t target_id; + lun_id_t lun_id; + cam_status status; + u_int init_level; + int s; + + init_level = 0; + /* + * Handle Hot-Plug scenarios. If there is already a peripheral + * of our type assigned to this path, we are likely waiting for + * final close on an old, invalidated, peripheral. If this is + * the case, queue up a deferred call to the peripheral's async + * handler. If it looks like a mistaken re-alloation, complain. + */ + if ((periph = cam_periph_find(path, name)) != NULL) { + + if ((periph->flags & CAM_PERIPH_INVALID) != 0 + && (periph->flags & CAM_PERIPH_NEW_DEV_FOUND) == 0) { + periph->flags |= CAM_PERIPH_NEW_DEV_FOUND; + periph->deferred_callback = ac_callback; + periph->deferred_ac = code; + return (CAM_REQ_INPROG); + } else { + printf("cam_periph_alloc: attempt to re-allocate " + "valid device %s%d rejected\n", + periph->periph_name, periph->unit_number); + } + return (CAM_REQ_INVALID); + } + + periph = (struct cam_periph *)malloc(sizeof(*periph), M_DEVBUF, + M_NOWAIT); + + if (periph == NULL) + return (CAM_RESRC_UNAVAIL); + + init_level++; + + for (p_drv = (struct periph_driver **)periphdriver_set.ls_items; + *p_drv != NULL; p_drv++) { + if (strcmp((*p_drv)->driver_name, name) == 0) + break; + } + + path_id = xpt_path_path_id(path); + target_id = xpt_path_target_id(path); + lun_id = xpt_path_lun_id(path); + bzero(periph, sizeof(*periph)); + cam_init_pinfo(&periph->pinfo); + periph->periph_start = periph_start; + periph->periph_dtor = periph_dtor; + periph->type = type; + periph->periph_name = name; + periph->unit_number = camperiphunit(*p_drv, path_id, target_id, lun_id); + periph->immediate_priority = CAM_PRIORITY_NONE; + periph->refcount = 0; + SLIST_INIT(&periph->ccb_list); + status = xpt_create_path(&path, periph, path_id, target_id, lun_id); + if (status != CAM_REQ_CMP) + goto failure; + + periph->path = path; + init_level++; + + status = xpt_add_periph(periph); + + if (status != CAM_REQ_CMP) + goto failure; + + s = splsoftcam(); + cur_periph = TAILQ_FIRST(&(*p_drv)->units); + while (cur_periph != NULL + && cur_periph->unit_number < periph->unit_number) + cur_periph = TAILQ_NEXT(cur_periph, unit_links); + + if (cur_periph != NULL) + TAILQ_INSERT_BEFORE(cur_periph, periph, unit_links); + else { + TAILQ_INSERT_TAIL(&(*p_drv)->units, periph, unit_links); + (*p_drv)->generation++; + } + + splx(s); + + init_level++; + + status = periph_ctor(periph, arg); + + if (status == CAM_REQ_CMP) + init_level++; + +failure: + switch (init_level) { + case 4: + /* Initialized successfully */ + break; + case 3: + s = splsoftcam(); + TAILQ_REMOVE(&(*p_drv)->units, periph, unit_links); + splx(s); + xpt_remove_periph(periph); + case 2: + xpt_free_path(periph->path); + case 1: + free(periph, M_DEVBUF); + case 0: + /* No cleanup to perform. */ + break; + default: + panic("cam_periph_alloc: Unkown init level"); + } + return(status); +} + +/* + * Find a peripheral structure with the specified path, target, lun, + * and (optionally) type. If the name is NULL, this function will return + * the first peripheral driver that matches the specified path. + */ +struct cam_periph * +cam_periph_find(struct cam_path *path, char *name) +{ + struct periph_driver **p_drv; + struct cam_periph *periph; + int s; + + for (p_drv = (struct periph_driver **)periphdriver_set.ls_items; + *p_drv != NULL; p_drv++) { + + if (name != NULL && (strcmp((*p_drv)->driver_name, name) != 0)) + continue; + + s = splsoftcam(); + for (periph = TAILQ_FIRST(&(*p_drv)->units); periph != NULL; + periph = TAILQ_NEXT(periph, unit_links)) { + if (xpt_path_comp(periph->path, path) == 0) { + splx(s); + return(periph); + } + } + splx(s); + if (name != NULL) + return(NULL); + } + return(NULL); +} + +cam_status +cam_periph_acquire(struct cam_periph *periph) +{ + int s; + + if (periph == NULL) + return(CAM_REQ_CMP_ERR); + + s = splsoftcam(); + periph->refcount++; + splx(s); + + return(CAM_REQ_CMP); +} + +void +cam_periph_release(struct cam_periph *periph) +{ + int s; + + if (periph == NULL) + return; + + s = splsoftcam(); + if ((--periph->refcount == 0) + && (periph->flags & CAM_PERIPH_INVALID)) { + camperiphfree(periph); + } + splx(s); + +} + +/* + * Look for the next unit number that is not currently in use for this + * peripheral type starting at "newunit". Also exclude unit numbers that + * are reserved by for future "hardwiring" unless we already know that this + * is a potential wired device. Only assume that the device is "wired" the + * first time through the loop since after that we'll be looking at unit + * numbers that did not match a wiring entry. + */ +static u_int +camperiphnextunit(struct periph_driver *p_drv, u_int newunit, int wired) +{ + struct cam_periph *periph; + struct cam_periph_config *periph_conf; + char *periph_name; + u_int i; + int s; + + s = splsoftcam(); + periph_name = p_drv->driver_name; + for (;;newunit++) { + + for (periph = TAILQ_FIRST(&p_drv->units); + periph != NULL && periph->unit_number != newunit; + periph = TAILQ_NEXT(periph, unit_links)) + ; + + if (periph != NULL && periph->unit_number == newunit) { + if (wired != 0) { + xpt_print_path(periph->path); + printf("Duplicate Wired Device entry!\n"); + xpt_print_path(periph->path); + printf("Second device will not be wired\n"); + wired = 0; + } + continue; + } + + for (periph_conf = cam_pinit; + wired == 0 && periph_conf->periph_name != NULL; + periph_conf++) { + + /* + * Don't match entries like "da 4" as a wired down + * device, but do match entries like "da 4 target 5" + * or even "da 4 scbus 1". + */ + if (IS_SPECIFIED(periph_conf->periph_unit) + && (!strcmp(periph_name, periph_conf->periph_name)) + && (IS_SPECIFIED(periph_conf->target) + || IS_SPECIFIED(periph_conf->pathid)) + && (newunit == periph_conf->periph_unit)) + break; + } + + if (wired != 0 || periph_conf->periph_name == NULL) + break; + } + splx(s); + return (newunit); +} + +static u_int +camperiphunit(struct periph_driver *p_drv, path_id_t pathid, + target_id_t target, lun_id_t lun) +{ + struct cam_periph_config *periph_conf; + u_int unit; + int hit; + + unit = 0; + hit = 0; + + for (periph_conf = cam_pinit; + periph_conf->periph_name != NULL; + periph_conf++, hit = 0) { + + if (!strcmp(p_drv->driver_name, periph_conf->periph_name) + && IS_SPECIFIED(periph_conf->periph_unit)) { + + if (IS_SPECIFIED(periph_conf->pathid)) { + + if (pathid != periph_conf->pathid) + continue; + hit++; + } + + if (IS_SPECIFIED(periph_conf->target)) { + + if (target != periph_conf->target) + continue; + hit++; + } + + if (IS_SPECIFIED(periph_conf->lun)) { + + if (lun != periph_conf->lun) + continue; + hit++; + } + + if (hit != 0) { + unit = periph_conf->periph_unit; + break; + } + } + } + + /* + * Either start from 0 looking for the next unit or from + * the unit number given in the periph_conf. This way, + * if we have wildcard matches, we don't return the same + * unit number twice. + */ + unit = camperiphnextunit(p_drv, unit, /*wired*/hit); + + return (unit); +} + +void +cam_periph_invalidate(struct cam_periph *periph) +{ + int s; + + periph->flags |= CAM_PERIPH_INVALID; + periph->flags &= ~CAM_PERIPH_NEW_DEV_FOUND; + + s = splsoftcam(); + if (periph->refcount == 0) + camperiphfree(periph); + else if (periph->refcount < 0) + printf("cam_invalidate_periph: refcount < 0!!\n"); + splx(s); +} + +static void +camperiphfree(struct cam_periph *periph) +{ + int s; + struct periph_driver **p_drv; + + for (p_drv = (struct periph_driver **)periphdriver_set.ls_items; + *p_drv != NULL; p_drv++) { + if (strcmp((*p_drv)->driver_name, periph->periph_name) == 0) + break; + } + + if (periph->periph_dtor != NULL) + periph->periph_dtor(periph); + + s = splsoftcam(); + TAILQ_REMOVE(&(*p_drv)->units, periph, unit_links); + (*p_drv)->generation++; + splx(s); + + xpt_remove_periph(periph); + + if (periph->flags & CAM_PERIPH_NEW_DEV_FOUND) { + union ccb ccb; + void *arg; + + switch (periph->deferred_ac) { + case AC_FOUND_DEVICE: + ccb.ccb_h.func_code = XPT_GDEV_TYPE; + xpt_setup_ccb(&ccb.ccb_h, periph->path, /*priority*/ 1); + xpt_action(&ccb); + arg = &ccb; + break; + case AC_PATH_REGISTERED: + ccb.ccb_h.func_code = XPT_PATH_INQ; + xpt_setup_ccb(&ccb.ccb_h, periph->path, /*priority*/ 1); + xpt_action(&ccb); + arg = &ccb; + break; + default: + arg = NULL; + break; + } + periph->deferred_callback(NULL, periph->deferred_ac, + periph->path, arg); + } + xpt_free_path(periph->path); + free(periph, M_DEVBUF); +} + +/* + * Wait interruptibly for an exclusive lock. + */ +int +cam_periph_lock(struct cam_periph *periph, int priority) +{ + int error; + int s; + + while ((periph->flags & CAM_PERIPH_LOCKED) != 0) { + periph->flags |= CAM_PERIPH_LOCK_WANTED; + if ((error = tsleep(periph, priority, "caplck", 0)) != 0) + return error; + } + + if (cam_periph_acquire(periph) != CAM_REQ_CMP) + return(ENXIO); + + periph->flags |= CAM_PERIPH_LOCKED; + return 0; +} + +/* + * Unlock and wake up any waiters. + */ +void +cam_periph_unlock(struct cam_periph *periph) +{ + periph->flags &= ~CAM_PERIPH_LOCKED; + if ((periph->flags & CAM_PERIPH_LOCK_WANTED) != 0) { + periph->flags &= ~CAM_PERIPH_LOCK_WANTED; + wakeup(periph); + } + + cam_periph_release(periph); +} + +/* + * Map user virtual pointers into kernel virtual address space, so we can + * access the memory. This won't work on physical pointers, for now it's + * up to the caller to check for that. (XXX KDM -- should we do that here + * instead?) This also only works for up to MAXPHYS memory. Since we use + * buffers to map stuff in and out, we're limited to the buffer size. + */ +int +cam_periph_mapmem(union ccb *ccb, struct cam_periph_map_info *mapinfo) +{ + int flags, numbufs, i; + u_int8_t **data_ptrs[CAM_PERIPH_MAXMAPS]; + u_int32_t lengths[CAM_PERIPH_MAXMAPS]; + u_int32_t dirs[CAM_PERIPH_MAXMAPS]; + + switch(ccb->ccb_h.func_code) { + case XPT_DEV_MATCH: + if (ccb->cdm.pattern_buf_len > MAXPHYS) { + printf("cam_periph_mapmem: attempt to map %u bytes, " + "which is greater than MAXPHYS(%d)\n", + ccb->cdm.pattern_buf_len, MAXPHYS); + return(E2BIG); + } else if (ccb->cdm.match_buf_len > MAXPHYS) { + printf("cam_periph_mapmem: attempt to map %u bytes, " + "which is greater than MAXPHYS(%d)\n", + ccb->cdm.match_buf_len, MAXPHYS); + return(E2BIG); + } + if (ccb->cdm.match_buf_len == 0) { + printf("cam_periph_mapmem: invalid match buffer " + "length 0\n"); + return(EINVAL); + } + if (ccb->cdm.pattern_buf_len > 0) { + data_ptrs[0] = (u_int8_t **)&ccb->cdm.patterns; + lengths[0] = ccb->cdm.pattern_buf_len; + dirs[0] = CAM_DIR_OUT; + data_ptrs[1] = (u_int8_t **)&ccb->cdm.matches; + lengths[1] = ccb->cdm.match_buf_len; + dirs[1] = CAM_DIR_IN; + numbufs = 2; + } else { + data_ptrs[0] = (u_int8_t **)&ccb->cdm.matches; + lengths[0] = ccb->cdm.match_buf_len; + dirs[0] = CAM_DIR_IN; + numbufs = 1; + } + break; + case XPT_SCSI_IO: + if (ccb->csio.dxfer_len > MAXPHYS) { + printf("cam_periph_mapmem: attempt to map %u bytes, " + "which is greater than MAXPHYS(%d)\n", + ccb->csio.dxfer_len, MAXPHYS); + return(E2BIG); + } + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_NONE) + return(0); + + data_ptrs[0] = &ccb->csio.data_ptr; + lengths[0] = ccb->csio.dxfer_len;; + dirs[0] = ccb->ccb_h.flags & CAM_DIR_MASK; + numbufs = 1; + break; + default: + return(EINVAL); + break; /* NOTREACHED */ + } + + /* this keeps the current process from getting swapped */ + /* + * XXX KDM should I use P_NOSWAP instead? + */ + curproc->p_flag |= P_PHYSIO; + + for (i = 0; i < numbufs; i++) { + flags = 0; + + if (dirs[i] & CAM_DIR_IN) { + flags = B_READ; + if (useracc(*data_ptrs[i], lengths[i], B_READ) == 0){ + printf("cam_periph_mapmem: error, " + "address %#lx, length %d isn't " + "user accessible for READ\n", + (u_long)(*data_ptrs[i]), lengths[i]); + /* + * If we've already mapped one or more + * buffers for this CCB, unmap it (them). + */ + if (i > 0) + cam_periph_unmapmem(ccb, mapinfo); + else + curproc->p_flag &= ~P_PHYSIO; + + return(EACCES); + } + } + + /* + * XXX this check is really bogus, since B_WRITE currently + * is all 0's, and so it is "set" all the time. + */ + if (dirs[i] & CAM_DIR_OUT) { + flags |= B_WRITE; + if (useracc(*data_ptrs[i], lengths[i], B_WRITE) == 0){ + printf("cam_periph_mapmem: error, " + "address %#lx, length %d isn't " + "user accessible for WRITE\n", + (u_long)(*data_ptrs[i]), lengths[i]); + /* + * If we've already mapped one or more + * buffers for this CCB, unmap it (them). + */ + if (i > 0) + cam_periph_unmapmem(ccb, mapinfo); + else + curproc->p_flag &= ~P_PHYSIO; + + return(EACCES); + } + } + + /* + * Get the buffer. + */ + mapinfo->bp[i] = getpbuf(); + + /* save the buffer's data address */ + mapinfo->bp[i]->b_saveaddr = mapinfo->bp[i]->b_data; + + /* put our pointer in the data slot */ + mapinfo->bp[i]->b_data = *data_ptrs[i]; + + /* set the transfer length, we know it's < 64K */ + mapinfo->bp[i]->b_bufsize = lengths[i]; + + /* set the flags */ + mapinfo->bp[i]->b_flags = flags | B_PHYS | B_BUSY; + + /* map the buffer into kernel memory */ + vmapbuf(mapinfo->bp[i]); + + /* set our pointer to the new mapped area */ + *data_ptrs[i] = mapinfo->bp[i]->b_data; + + mapinfo->num_bufs_used++; + } + + return(0); +} + +/* + * Unmap memory segments mapped into kernel virtual address space by + * cam_periph_mapmem(). + */ +void +cam_periph_unmapmem(union ccb *ccb, struct cam_periph_map_info *mapinfo) +{ + int numbufs, i; + u_int8_t **data_ptrs[CAM_PERIPH_MAXMAPS]; + + if (mapinfo->num_bufs_used <= 0) { + /* allow ourselves to be swapped once again */ + curproc->p_flag &= ~P_PHYSIO; + return; + } + + switch (ccb->ccb_h.func_code) { + case XPT_DEV_MATCH: + numbufs = min(mapinfo->num_bufs_used, 2); + + if (numbufs == 1) { + data_ptrs[0] = (u_int8_t **)&ccb->cdm.matches; + } else { + data_ptrs[0] = (u_int8_t **)&ccb->cdm.patterns; + data_ptrs[1] = (u_int8_t **)&ccb->cdm.matches; + } + break; + case XPT_SCSI_IO: + data_ptrs[0] = &ccb->csio.data_ptr; + numbufs = min(mapinfo->num_bufs_used, 1); + break; + default: + /* allow ourselves to be swapped once again */ + curproc->p_flag &= ~P_PHYSIO; + return; + break; /* NOTREACHED */ + } + + for (i = 0; i < numbufs; i++) { + /* Set the user's pointer back to the original value */ + *data_ptrs[i] = mapinfo->bp[i]->b_saveaddr; + + /* unmap the buffer */ + vunmapbuf(mapinfo->bp[i]); + + /* clear the flags we set above */ + mapinfo->bp[i]->b_flags &= ~(B_PHYS|B_BUSY); + + /* release the buffer */ + relpbuf(mapinfo->bp[i]); + } + + /* allow ourselves to be swapped once again */ + curproc->p_flag &= ~P_PHYSIO; +} + +union ccb * +cam_periph_getccb(struct cam_periph *periph, u_int32_t priority) +{ + struct ccb_hdr *ccb_h; + int s; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdgetccb\n")); + + s = splsoftcam(); + + while (periph->ccb_list.slh_first == NULL) { + if (periph->immediate_priority > priority) + periph->immediate_priority = priority; + xpt_schedule(periph, priority); + if ((periph->ccb_list.slh_first != NULL) + && (periph->ccb_list.slh_first->pinfo.priority == priority)) + break; + tsleep(&periph->ccb_list, PRIBIO, "cgticb", 0); + } + + ccb_h = periph->ccb_list.slh_first; + SLIST_REMOVE_HEAD(&periph->ccb_list, periph_links.sle); + splx(s); + return ((union ccb *)ccb_h); +} + +void +cam_periph_ccbwait(union ccb *ccb) +{ + int s; + + s = splsoftcam(); + if ((ccb->ccb_h.pinfo.index != CAM_UNQUEUED_INDEX) + || ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG)) + tsleep(&ccb->ccb_h.cbfcnp, PRIBIO, "cbwait", 0); + + splx(s); +} + +int +cam_periph_ioctl(struct cam_periph *periph, int cmd, caddr_t addr, + int (*error_routine)(union ccb *ccb, + cam_flags camflags, + u_int32_t sense_flags)) +{ + union ccb *ccb; + int error; + int found; + + error = found = 0; + + switch(cmd){ + case CAMGETPASSTHRU: + ccb = cam_periph_getccb(periph, /* priority */ 1); + xpt_setup_ccb(&ccb->ccb_h, + ccb->ccb_h.path, + /*priority*/1); + ccb->ccb_h.func_code = XPT_GDEVLIST; + + /* + * Basically, the point of this is that we go through + * getting the list of devices, until we find a passthrough + * device. In the current version of the CAM code, the + * only way to determine what type of device we're dealing + * with is by its name. + */ + while (found == 0) { + ccb->cgdl.index = 0; + ccb->cgdl.status = CAM_GDEVLIST_MORE_DEVS; + while (ccb->cgdl.status == CAM_GDEVLIST_MORE_DEVS) { + + /* we want the next device in the list */ + xpt_action(ccb); + if (strncmp(ccb->cgdl.periph_name, + "pass", 4) == 0){ + found = 1; + break; + } + } + if ((ccb->cgdl.status == CAM_GDEVLIST_LAST_DEVICE) && + (found == 0)) { + ccb->cgdl.periph_name[0] = '\0'; + ccb->cgdl.unit_number = 0; + break; + } + } + + /* copy the result back out */ + bcopy(ccb, addr, sizeof(union ccb)); + + /* and release the ccb */ + xpt_release_ccb(ccb); + + break; + default: + error = ENOTTY; + break; + } + return(error); +} + +int +cam_periph_runccb(union ccb *ccb, + int (*error_routine)(union ccb *ccb, + cam_flags camflags, + u_int32_t sense_flags), + cam_flags camflags, u_int32_t sense_flags, + struct devstat *ds) +{ + int error; + + error = 0; + + /* + * If the user has supplied a stats structure, and if we understand + * this particular type of ccb, record the transaction start. + */ + if ((ds != NULL) && (ccb->ccb_h.func_code == XPT_SCSI_IO)) + devstat_start_transaction(ds); + + xpt_action(ccb); + + do { + cam_periph_ccbwait(ccb); + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) + error = 0; + else if (error_routine != NULL) + error = (*error_routine)(ccb, camflags, sense_flags); + else + error = 0; + + } while (error == ERESTART); + + if ((ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(ccb->ccb_h.path, + /* relsim_flags */0, + /* openings */0, + /* timeout */0, + /* getcount_only */ FALSE); + + if ((ds != NULL) && (ccb->ccb_h.func_code == XPT_SCSI_IO)) + devstat_end_transaction(ds, + ccb->csio.dxfer_len, + ccb->csio.tag_action & 0xf, + ((ccb->ccb_h.flags & CAM_DIR_MASK) == + CAM_DIR_NONE) ? DEVSTAT_NO_DATA : + (ccb->ccb_h.flags & CAM_DIR_OUT) ? + DEVSTAT_WRITE : + DEVSTAT_READ); + + return(error); +} + +u_int32_t +cam_release_devq(struct cam_path *path, u_int32_t relsim_flags, + u_int32_t openings, u_int32_t timeout, + int getcount_only) +{ + struct ccb_relsim crs; + + xpt_setup_ccb(&crs.ccb_h, path, + /*priority*/1); + crs.ccb_h.func_code = XPT_REL_SIMQ; + crs.ccb_h.flags = getcount_only ? CAM_DEV_QFREEZE : 0; + crs.release_flags = relsim_flags; + crs.openings = openings; + crs.release_timeout = timeout; + xpt_action((union ccb *)&crs); + return (crs.qfrozen_cnt); +} + +#define saved_ccb_ptr ppriv_ptr0 +static void +camperiphdone(struct cam_periph *periph, union ccb *done_ccb) +{ + cam_status status; + int frozen; + int sense; + struct scsi_start_stop_unit *scsi_cmd; + u_int32_t relsim_flags, timeout; + u_int32_t qfrozen_cnt; + + status = done_ccb->ccb_h.status; + frozen = (status & CAM_DEV_QFRZN) != 0; + sense = (status & CAM_AUTOSNS_VALID) != 0; + status &= CAM_STATUS_MASK; + + timeout = 0; + relsim_flags = 0; + + /* + * Unfreeze the queue once if it is already frozen.. + */ + if (frozen != 0) { + qfrozen_cnt = cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*openings*/0, + /*timeout*/0, + /*getcount_only*/0); + } + + switch (status) { + + case CAM_REQ_CMP: + + /* + * If we have successfully taken a device from the not + * ready to ready state, re-scan the device and re-get the + * inquiry information. Many devices (mostly disks) don't + * properly report their inquiry information unless they + * are spun up. + */ + if (done_ccb->ccb_h.func_code == XPT_SCSI_IO) { + scsi_cmd = (struct scsi_start_stop_unit *) + &done_ccb->csio.cdb_io.cdb_bytes; + + if (scsi_cmd->opcode == START_STOP_UNIT) + xpt_async(AC_INQ_CHANGED, + done_ccb->ccb_h.path, NULL); + } + bcopy(done_ccb->ccb_h.saved_ccb_ptr, done_ccb, + sizeof(union ccb)); + + xpt_action(done_ccb); + + break; + case CAM_SCSI_STATUS_ERROR: + scsi_cmd = (struct scsi_start_stop_unit *) + &done_ccb->csio.cdb_io.cdb_bytes; + if (sense != 0) { + struct scsi_sense_data *sense; + int error_code, sense_key, asc, ascq; + + sense = &done_ccb->csio.sense_data; + scsi_extract_sense(sense, &error_code, + &sense_key, &asc, &ascq); + + /* + * If the error is "invalid field in CDB", + * and the load/eject flag is set, turn the + * flag off and try again. This is just in + * case the drive in question barfs on the + * load eject flag. The CAM code should set + * the load/eject flag by default for + * removable media. + */ + + /* XXX KDM + * Should we check to see what the specific + * scsi status is?? Or does it not matter + * since we already know that there was an + * error, and we know what the specific + * error code was, and we know what the + * opcode is.. + */ + if ((scsi_cmd->opcode == START_STOP_UNIT) && + ((scsi_cmd->how & SSS_LOEJ) != 0) && + (asc == 0x24) && (ascq == 0x00) && + (done_ccb->ccb_h.retry_count > 0)) { + + scsi_cmd->how &= ~SSS_LOEJ; + + xpt_action(done_ccb); + + } else if (done_ccb->ccb_h.retry_count > 0) { + /* + * In this case, the error recovery + * command failed, but we've got + * some retries left on it. Give + * it another try. + */ + + /* set the timeout to .5 sec */ + relsim_flags = + RELSIM_RELEASE_AFTER_TIMEOUT; + timeout = 500; + + xpt_action(done_ccb); + + break; + + } else { + /* + * Copy the original CCB back and + * send it back to the caller. + */ + bcopy(done_ccb->ccb_h.saved_ccb_ptr, + done_ccb, sizeof(union ccb)); + + xpt_action(done_ccb); + } + } else { + /* + * Eh?? The command failed, but we don't + * have any sense. What's up with that? + * Fire the CCB again to return it to the + * caller. + */ + bcopy(done_ccb->ccb_h.saved_ccb_ptr, + done_ccb, sizeof(union ccb)); + + xpt_action(done_ccb); + + } + break; + default: + bcopy(done_ccb->ccb_h.saved_ccb_ptr, done_ccb, + sizeof(union ccb)); + + xpt_action(done_ccb); + + break; + } + + /* decrement the retry count */ + if (done_ccb->ccb_h.retry_count > 0) + done_ccb->ccb_h.retry_count--; + + qfrozen_cnt = cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/relsim_flags, + /*openings*/0, + /*timeout*/timeout, + /*getcount_only*/0); +} + +/* + * Generic error handler. Peripheral drivers usually filter + * out the errors that they handle in a unique mannor, then + * call this function. + */ +int +cam_periph_error(union ccb *ccb, cam_flags camflags, + u_int32_t sense_flags, union ccb *save_ccb) +{ + cam_status status; + int frozen; + int sense; + int error; + int openings; + int retry; + u_int32_t relsim_flags; + u_int32_t timeout; + + status = ccb->ccb_h.status; + frozen = (status & CAM_DEV_QFRZN) != 0; + sense = (status & CAM_AUTOSNS_VALID) != 0; + status &= CAM_STATUS_MASK; + relsim_flags = 0; + + + switch (status) { + case CAM_REQ_CMP: + /* decrement the number of retries */ + retry = ccb->ccb_h.retry_count > 0; + if (retry) + ccb->ccb_h.retry_count--; + error = 0; + break; + case CAM_SCSI_STATUS_ERROR: + + switch (ccb->csio.scsi_status) { + case SCSI_STATUS_OK: + case SCSI_STATUS_COND_MET: + case SCSI_STATUS_INTERMED: + case SCSI_STATUS_INTERMED_COND_MET: + error = 0; + break; + case SCSI_STATUS_CMD_TERMINATED: + case SCSI_STATUS_CHECK_COND: + if (sense != 0) { + struct scsi_sense_data *sense; + int error_code, sense_key, asc, ascq; + struct cam_periph *periph; + scsi_sense_action err_action; + struct ccb_getdev cgd; + + sense = &ccb->csio.sense_data; + scsi_extract_sense(sense, &error_code, + &sense_key, &asc, &ascq); + periph = xpt_path_periph(ccb->ccb_h.path); + + /* + * Grab the inquiry data for this device. + */ + xpt_setup_ccb(&cgd.ccb_h, ccb->ccb_h.path, + /*priority*/ 1); + cgd.ccb_h.func_code = XPT_GDEV_TYPE; + xpt_action((union ccb *)&cgd); + + err_action = scsi_error_action(asc, ascq, + &cgd.inq_data); + + /* + * Send a Test Unit Ready to the device. + * If the 'many' flag is set, we send 120 + * test unit ready commands, one every half + * second. Otherwise, we just send one TUR. + * We only want to do this if the retry + * count has not been exhausted. + */ + if (((err_action & SS_MASK) == SS_TUR) + && save_ccb != NULL + && ccb->ccb_h.retry_count > 0) { + + /* decrement the number of retries */ + if ((err_action & + SSQ_DECREMENT_COUNT) != 0) { + retry = 1; + ccb->ccb_h.retry_count--; + } + + bcopy(ccb, save_ccb, sizeof(*save_ccb)); + + /* + * We retry this one every half + * second for a minute. If the + * device hasn't become ready in a + * minute's time, it's unlikely to + * ever become ready. If the table + * doesn't specify SSQ_MANY, we can + * only try this once. Oh well. + */ + if ((err_action & SSQ_MANY) != 0) + scsi_test_unit_ready(&ccb->csio, + /*retries*/120, + camperiphdone, + MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, + /*timeout*/5000); + else + scsi_test_unit_ready(&ccb->csio, + /*retries*/1, + camperiphdone, + MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, + /*timeout*/5000); + + /* release the queue after .5 sec. */ + relsim_flags = + RELSIM_RELEASE_AFTER_TIMEOUT; + timeout = 500; + /* + * Drop the priority to 0 so that + * we are the first to execute. Also + * freeze the queue after this command + * is sent so that we can restore the + * old csio and have it queued in the + * proper order before we let normal + * transactions go to the drive. + */ + ccb->ccb_h.pinfo.priority = 0; + ccb->ccb_h.flags |= CAM_DEV_QFREEZE; + + /* + * Save a pointer to the original + * CCB in the new CCB. + */ + ccb->ccb_h.saved_ccb_ptr = save_ccb; + + error = ERESTART; + } + /* + * Send a start unit command to the device, + * and then retry the command. We only + * want to do this if the retry count has + * not been exhausted. If the user + * specified 0 retries, then we follow + * their request and do not retry. + */ + else if (((err_action & SS_MASK) == SS_START) + && save_ccb != NULL + && ccb->ccb_h.retry_count > 0) { + int le; + + /* decrement the number of retries */ + retry = 1; + ccb->ccb_h.retry_count--; + + /* + * Check for removable media and + * set load/eject flag + * appropriately. + */ + if (SID_IS_REMOVABLE(&cgd.inq_data)) + le = TRUE; + else + le = FALSE; + + /* + * Attempt to start the drive up. + * + * Save the current ccb so it can + * be restored and retried once the + * drive is started up. + */ + bcopy(ccb, save_ccb, sizeof(*save_ccb)); + + scsi_start_stop(&ccb->csio, + /*retries*/1, + camperiphdone, + MSG_SIMPLE_Q_TAG, + /*start*/TRUE, + /*load/eject*/le, + /*immediate*/FALSE, + SSD_FULL_SIZE, + /*timeout*/50000); + /* + * Drop the priority to 0 so that + * we are the first to execute. Also + * freeze the queue after this command + * is sent so that we can restore the + * old csio and have it queued in the + * proper order before we let normal + * transactions go to the drive. + */ + ccb->ccb_h.pinfo.priority = 0; + ccb->ccb_h.flags |= CAM_DEV_QFREEZE; + + /* + * Save a pointer to the original + * CCB in the new CCB. + */ + ccb->ccb_h.saved_ccb_ptr = save_ccb; + + error = ERESTART; + } else if ((sense_flags & SF_RETRY_UA) != 0) { + /* + * XXX KDM this is a *horrible* + * hack. + */ + error = scsi_interpret_sense(ccb, + sense_flags, + &relsim_flags, + &openings, + &timeout, + err_action); + } + + /* + * Theoretically, this code should send a + * test unit ready to the given device, and + * if it returns and error, send a start + * unit command. Since we don't yet have + * the capability to do two-command error + * recovery, just send a start unit. + * XXX KDM fix this! + */ + else if (((err_action & SS_MASK) == SS_TURSTART) + && save_ccb != NULL + && ccb->ccb_h.retry_count > 0) { + int le; + + /* decrement the number of retries */ + retry = 1; + ccb->ccb_h.retry_count--; + + /* + * Check for removable media and + * set load/eject flag + * appropriately. + */ + if (SID_IS_REMOVABLE(&cgd.inq_data)) + le = TRUE; + else + le = FALSE; + + /* + * Attempt to start the drive up. + * + * Save the current ccb so it can + * be restored and retried once the + * drive is started up. + */ + bcopy(ccb, save_ccb, sizeof(*save_ccb)); + + scsi_start_stop(&ccb->csio, + /*retries*/1, + camperiphdone, + MSG_SIMPLE_Q_TAG, + /*start*/TRUE, + /*load/eject*/le, + /*immediate*/FALSE, + SSD_FULL_SIZE, + /*timeout*/50000); + + /* release the queue after .5 sec. */ + relsim_flags = + RELSIM_RELEASE_AFTER_TIMEOUT; + timeout = 500; + /* + * Drop the priority to 0 so that + * we are the first to execute. Also + * freeze the queue after this command + * is sent so that we can restore the + * old csio and have it queued in the + * proper order before we let normal + * transactions go to the drive. + */ + ccb->ccb_h.pinfo.priority = 0; + ccb->ccb_h.flags |= CAM_DEV_QFREEZE; + + /* + * Save a pointer to the original + * CCB in the new CCB. + */ + ccb->ccb_h.saved_ccb_ptr = save_ccb; + + error = ERESTART; + } else { + error = scsi_interpret_sense(ccb, + sense_flags, + &relsim_flags, + &openings, + &timeout, + err_action); + } + } else if (ccb->csio.scsi_status == + SCSI_STATUS_CHECK_COND) { + /* no point in decrementing the retry count */ + panic("cam_periph_error: scsi status of " + "CHECK COND returned but no sense " + "information is availible. " + "Controller should have returned " + "CAM_AUTOSENSE_FAILED"); + /* NOTREACHED */ + error = EIO; + } else if (ccb->ccb_h.retry_count > 0) { + /* + * XXX KDM shouldn't there be a better + * argument to return?? + */ + error = EIO; + } else { + /* decrement the number of retries */ + retry = ccb->ccb_h.retry_count > 0; + if (retry) + ccb->ccb_h.retry_count--; + /* + * If it was aborted with no + * clue as to the reason, just + * retry it again. + */ + error = ERESTART; + } + break; + case SCSI_STATUS_QUEUE_FULL: + { + /* no decrement */ + struct ccb_getdev cgd; + + /* + * First off, find out what the current + * transaction counts are. + */ + xpt_setup_ccb(&cgd.ccb_h, + ccb->ccb_h.path, + /*priority*/1); + cgd.ccb_h.func_code = XPT_GDEV_TYPE; + xpt_action((union ccb *)&cgd); + + /* + * If we were the only transaction active, treat + * the QUEUE FULL as if it were a BUSY condition. + */ + if (cgd.dev_active != 0) { + /* + * Reduce the number of openings to + * be 1 less than the amount it took + * to get a queue full bounded by the + * minimum allowed tag count for this + * device. + */ + openings = cgd.dev_active; + if (openings < cgd.mintags) + openings = cgd.mintags; + if (openings < cgd.dev_active+cgd.dev_openings) + relsim_flags = RELSIM_ADJUST_OPENINGS; + else { + /* + * Some devices report queue full for + * temporary resource shortages. For + * this reason, we allow a minimum + * tag count to be entered via a + * quirk entry to prevent the queue + * count on these devices from falling + * to a pessimisticly low value. We + * still wait for the next successful + * completion, however, before queueing + * more transactions to the device. + */ + relsim_flags = + RELSIM_RELEASE_AFTER_CMDCMPLT; + } + timeout = 0; + error = ERESTART; + break; + } + /* FALLTHROUGH */ + } + case SCSI_STATUS_BUSY: + /* + * Restart the queue after either another + * command completes or a 1 second timeout. + */ + /* + * XXX KDM ask JTG about this again, do we need to + * be looking at the retry count here? + */ + error = ERESTART; + relsim_flags = RELSIM_RELEASE_AFTER_TIMEOUT + | RELSIM_RELEASE_AFTER_CMDCMPLT; + timeout = 1000; + break; + case SCSI_STATUS_RESERV_CONFLICT: + error = EIO; + break; + default: + error = EIO; + break; + } + break; + case CAM_REQ_CMP_ERR: + case CAM_AUTOSENSE_FAIL: + case CAM_CMD_TIMEOUT: + case CAM_UNEXP_BUSFREE: + case CAM_UNCOR_PARITY: + case CAM_DATA_RUN_ERR: + /* decrement the number of retries */ + retry = ccb->ccb_h.retry_count > 0; + if (retry) { + ccb->ccb_h.retry_count--; + error = ERESTART; + } else { + error = EIO; + } + break; + case CAM_UA_ABORT: + case CAM_UA_TERMIO: + case CAM_MSG_REJECT_REC: + /* XXX Don't know that these are correct */ + error = EIO; + break; + case CAM_SEL_TIMEOUT: + { + struct cam_path *newpath; + + error = ENXIO; + + /* Should we do more if we can't create the path?? */ + if (xpt_create_path(&newpath, xpt_path_periph(ccb->ccb_h.path), + xpt_path_path_id(ccb->ccb_h.path), + xpt_path_target_id(ccb->ccb_h.path), + CAM_LUN_WILDCARD) != CAM_REQ_CMP) + break; + /* + * Let peripheral drivers know that this device has gone + * away. + */ + xpt_async(AC_LOST_DEVICE, newpath, NULL); + xpt_free_path(newpath); + + break; + } + case CAM_REQ_INVALID: + case CAM_PATH_INVALID: + case CAM_DEV_NOT_THERE: + case CAM_NO_HBA: + case CAM_PROVIDE_FAIL: + case CAM_REQ_TOO_BIG: + error = EINVAL; + break; + case CAM_SCSI_BUS_RESET: + case CAM_BDR_SENT: + case CAM_REQUEUE_REQ: + /* Unconditional requeue, dammit */ + error = ERESTART; + break; + case CAM_RESRC_UNAVAIL: + case CAM_BUSY: + /* timeout??? */ + default: + /* decrement the number of retries */ + retry = ccb->ccb_h.retry_count > 0; + if (retry) { + ccb->ccb_h.retry_count--; + error = ERESTART; + } else { + /* Check the sense codes */ + error = EIO; + } + break; + } + + /* Attempt a retry */ + if (error == ERESTART || error == 0) { + if (frozen != 0) + ccb->ccb_h.status &= ~CAM_DEV_QFRZN; + + if (error == ERESTART) + xpt_action(ccb); + + if (frozen != 0) { + cam_release_devq(ccb->ccb_h.path, + relsim_flags, + openings, + timeout, + /*getcount_only*/0); + } + } + + + return (error); +} |