diff options
Diffstat (limited to 'tools/tools/vhba/vhba.c')
-rw-r--r-- | tools/tools/vhba/vhba.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/tools/tools/vhba/vhba.c b/tools/tools/vhba/vhba.c new file mode 100644 index 0000000..fd0dce6 --- /dev/null +++ b/tools/tools/vhba/vhba.c @@ -0,0 +1,431 @@ +/*- + * Copyright (c) 2010 by Panasas, Inc. + * 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 immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 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$ */ +/* + * Virtual HBA infrastructure, to be used for testing as well as other cute hacks. + */ +#include "vhba.h" +static vhba_softc_t *vhba; + +#ifndef VHBA_MOD +#define VHBA_MOD "vhba" +#endif + +static void vhba_action(struct cam_sim *, union ccb *); +static void vhba_poll(struct cam_sim *); + +static int +vhba_attach(vhba_softc_t *vhba) +{ + TAILQ_INIT(&vhba->actv); + TAILQ_INIT(&vhba->done); + vhba->devq = cam_simq_alloc(VHBA_MAXCMDS); + if (vhba->devq == NULL) { + return (ENOMEM); + } + vhba->sim = cam_sim_alloc(vhba_action, vhba_poll, VHBA_MOD, vhba, 0, &vhba->lock, VHBA_MAXCMDS, VHBA_MAXCMDS, vhba->devq); + if (vhba->sim == NULL) { + cam_simq_free(vhba->devq); + return (ENOMEM); + } + vhba_init(vhba); + mtx_lock(&vhba->lock); + if (xpt_bus_register(vhba->sim, 0, 0) != CAM_SUCCESS) { + cam_sim_free(vhba->sim, TRUE); + mtx_unlock(&vhba->lock); + return (EIO); + } + mtx_unlock(&vhba->lock); + return (0); +} + +static void +vhba_detach(vhba_softc_t *vhba) +{ + /* + * We can't be called with anything queued up. + */ + vhba_fini(vhba); + xpt_bus_deregister(cam_sim_path(vhba->sim)); + cam_sim_free(vhba->sim, TRUE); +} + +static void +vhba_poll(struct cam_sim *sim) +{ + vhba_softc_t *vhba = cam_sim_softc(sim); + vhba_kick(vhba); +} + +static void +vhba_action(struct cam_sim *sim, union ccb *ccb) +{ + struct ccb_trans_settings *cts; + vhba_softc_t *vhba; + + vhba = cam_sim_softc(sim); + if (vhba->private == NULL) { + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + return; + } + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + ccb->ccb_h.status &= ~CAM_STATUS_MASK; + ccb->ccb_h.status |= CAM_REQ_INPROG; + TAILQ_INSERT_TAIL(&vhba->actv, &ccb->ccb_h, sim_links.tqe); + vhba_kick(vhba); + return; + + case XPT_RESET_DEV: + ccb->ccb_h.status = CAM_REQ_CMP; + break; + + case XPT_GET_TRAN_SETTINGS: + cts = &ccb->cts; + cts->protocol_version = SCSI_REV_SPC2; + cts->protocol = PROTO_SCSI; + cts->transport_version = 0; + cts->transport = XPORT_PPB; + ccb->ccb_h.status = CAM_REQ_CMP; + break; + + case XPT_CALC_GEOMETRY: + cam_calc_geometry(&ccb->ccg, 1); + break; + + case XPT_RESET_BUS: /* Reset the specified bus */ + ccb->ccb_h.status = CAM_REQ_CMP; + break; + + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; + cpi->max_target = VHBA_MAXTGT - 1; + cpi->max_lun = 16383; + cpi->hba_misc = PIM_NOBUSRESET; + cpi->initiator_id = cpi->max_target + 1; + cpi->transport = XPORT_PPB; + cpi->base_transfer_speed = 1000000; + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_SPC2; + strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strlcpy(cpi->hba_vid, "FakeHBA", HBA_IDLEN); + strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->ccb_h.status = CAM_REQ_CMP; + break; + } + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + break; + } + xpt_done(ccb); +} + +/* + * Common support + */ +void +vhba_fill_sense(struct ccb_scsiio *csio, uint8_t key, uint8_t asc, uint8_t ascq) +{ + csio->ccb_h.status = CAM_SCSI_STATUS_ERROR|CAM_AUTOSNS_VALID; + csio->scsi_status = SCSI_STATUS_CHECK_COND; + csio->sense_data.error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; + csio->sense_data.flags = key; + csio->sense_data.extra_len = 10; + csio->sense_data.add_sense_code = asc; + csio->sense_data.add_sense_code_qual = ascq; + csio->sense_len = sizeof (csio->sense_data); +} + +int +vhba_rwparm(uint8_t *cdb, uint64_t *offset, uint32_t *tl, uint64_t nblks, uint32_t blk_shift) +{ + uint32_t cnt; + uint64_t lba; + + switch (cdb[0]) { + case WRITE_16: + case READ_16: + cnt = (((uint32_t)cdb[10]) << 24) | + (((uint32_t)cdb[11]) << 16) | + (((uint32_t)cdb[12]) << 8) | + ((uint32_t)cdb[13]); + + lba = (((uint64_t)cdb[2]) << 56) | + (((uint64_t)cdb[3]) << 48) | + (((uint64_t)cdb[4]) << 40) | + (((uint64_t)cdb[5]) << 32) | + (((uint64_t)cdb[6]) << 24) | + (((uint64_t)cdb[7]) << 16) | + (((uint64_t)cdb[8]) << 8) | + ((uint64_t)cdb[9]); + break; + case WRITE_12: + case READ_12: + cnt = (((uint32_t)cdb[6]) << 16) | + (((uint32_t)cdb[7]) << 8) | + ((u_int32_t)cdb[8]); + + lba = (((uint32_t)cdb[2]) << 24) | + (((uint32_t)cdb[3]) << 16) | + (((uint32_t)cdb[4]) << 8) | + ((uint32_t)cdb[5]); + break; + case WRITE_10: + case READ_10: + cnt = (((uint32_t)cdb[7]) << 8) | + ((u_int32_t)cdb[8]); + + lba = (((uint32_t)cdb[2]) << 24) | + (((uint32_t)cdb[3]) << 16) | + (((uint32_t)cdb[4]) << 8) | + ((uint32_t)cdb[5]); + break; + case WRITE_6: + case READ_6: + cnt = cdb[4]; + if (cnt == 0) { + cnt = 256; + } + lba = (((uint32_t)cdb[1] & 0x1f) << 16) | + (((uint32_t)cdb[2]) << 8) | + ((uint32_t)cdb[3]); + break; + default: + return (-1); + } + + if (lba + cnt > nblks) { + return (-1); + } + *tl = cnt << blk_shift; + *offset = lba << blk_shift; + return (0); +} + +void +vhba_default_cmd(struct ccb_scsiio *csio, lun_id_t max_lun, uint8_t *sparse_lun_map) +{ + char junk[128]; + const uint8_t niliqd[SHORT_INQUIRY_LENGTH] = { + 0x7f, 0x0, 0x5, 0x2, 32, 0, 0, 0x32, + 'P', 'A', 'N', 'A', 'S', 'A', 'S', ' ', + 'N', 'U', 'L', 'L', ' ', 'D', 'E', 'V', + 'I', 'C', 'E', ' ', ' ', ' ', ' ', ' ', + '0', '0', '0', '1' + }; + const uint8_t iqd[SHORT_INQUIRY_LENGTH] = { + 0, 0x0, 0x5, 0x2, 32, 0, 0, 0x32, + 'P', 'A', 'N', 'A', 'S', 'A', 'S', ' ', + 'V', 'I', 'R', 'T', ' ', 'M', 'E', 'M', + 'O', 'R', 'Y', ' ', 'D', 'I', 'S', 'K', + '0', '0', '0', '1' + }; + const uint8_t vp0data[6] = { 0, 0, 0, 0x2, 0, 0x80 }; + const uint8_t vp80data[36] = { 0, 0x80, 0, 0x20 }; + int i, attached_lun; + uint8_t *cdb, *ptr, status; + uint32_t data_len, nlun; + + data_len = 0; + status = SCSI_STATUS_OK; + + memset(&csio->sense_data, 0, sizeof (csio->sense_data)); + cdb = csio->cdb_io.cdb_bytes; + + attached_lun = 1; + if (csio->ccb_h.target_lun >= max_lun) { + attached_lun = 0; + } else if (sparse_lun_map) { + i = csio->ccb_h.target_lun & 0x7; + if ((sparse_lun_map[csio->ccb_h.target_lun >> 3] & (1 << i)) == 0) { + attached_lun = 0; + } + } + if (attached_lun == 0 && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) { + vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0); + return; + } + + switch (cdb[0]) { + case REQUEST_SENSE: + data_len = csio->dxfer_len; + if (cdb[4] < csio->dxfer_len) + data_len = cdb[4]; + if (data_len) { + memset(junk, 0, sizeof (junk)); + junk[0] = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; + junk[2] = SSD_KEY_NO_SENSE; + junk[7] = 10; + memcpy(csio->data_ptr, junk, + (data_len > sizeof junk)? sizeof junk : data_len); + } + csio->resid = csio->dxfer_len - data_len; + break; + case INQUIRY: + i = 0; + if ((cdb[1] & 0x1f) == SI_EVPD) { + if ((cdb[2] != 0 && cdb[2] != 0x80) || cdb[3] || cdb[5]) { + i = 1; + } + } else if ((cdb[1] & 0x1f) || cdb[2] || cdb[3] || cdb[5]) { + i = 1; + } + if (i) { + vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0); + break; + } + if (attached_lun == 0) { + if (cdb[1] & 0x1f) { + vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0); + break; + } + memcpy(junk, niliqd, sizeof (niliqd)); + data_len = sizeof (niliqd); + } else if (cdb[1] & 0x1f) { + if (cdb[2] == 0) { + memcpy(junk, vp0data, sizeof (vp0data)); + data_len = sizeof (vp0data); + } else { + memcpy(junk, vp80data, sizeof (vp80data)); + snprintf(&junk[4], sizeof (vp80data) - 4, "TGT%dLUN%d", csio->ccb_h.target_id, csio->ccb_h.target_lun); + for (i = 0; i < sizeof (vp80data); i++) { + if (junk[i] == 0) { + junk[i] = ' '; + } + } + } + data_len = sizeof (vp80data); + } else { + memcpy(junk, iqd, sizeof (iqd)); + data_len = sizeof (iqd); + } + if (data_len > cdb[4]) { + data_len = cdb[4]; + } + if (data_len) { + memcpy(csio->data_ptr, junk, data_len); + } + csio->resid = csio->dxfer_len - data_len; + break; + case TEST_UNIT_READY: + case SYNCHRONIZE_CACHE: + case START_STOP: + case RESERVE: + case RELEASE: + break; + + case REPORT_LUNS: + if (csio->dxfer_len) { + memset(csio->data_ptr, 0, csio->dxfer_len); + } + ptr = NULL; + for (nlun = i = 0; i < max_lun; i++) { + if (sparse_lun_map) { + if ((sparse_lun_map[i >> 3] & (1 << (i & 0x7))) == 0) { + continue; + } + } + ptr = &csio->data_ptr[8 + ((nlun++) << 3)]; + if ((ptr + 8) > &csio->data_ptr[csio->dxfer_len]) { + continue; + } + if (i >= 256) { + ptr[0] = 0x40 | ((i >> 8) & 0x3f); + } + ptr[1] = i; + } + junk[0] = (nlun << 3) >> 24; + junk[1] = (nlun << 3) >> 16; + junk[2] = (nlun << 3) >> 8; + junk[3] = (nlun << 3); + memset(junk+4, 0, 4); + if (csio->dxfer_len) { + u_int amt; + + amt = MIN(csio->dxfer_len, 8); + memcpy(csio->data_ptr, junk, amt); + amt = MIN((nlun << 3) + 8, csio->dxfer_len); + csio->resid = csio->dxfer_len - amt; + } + break; + + default: + vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x20, 0x0); + break; + } +} + +void +vhba_set_status(struct ccb_hdr *ccbh, cam_status status) +{ + ccbh->status &= ~CAM_STATUS_MASK; + ccbh->status |= status; + if (status != CAM_REQ_CMP) { + if ((ccbh->status & CAM_DEV_QFRZN) == 0) { + ccbh->status |= CAM_DEV_QFRZN; + xpt_freeze_devq(ccbh->path, 1); + } + } +} + +int +vhba_modprobe(module_t mod, int cmd, void *arg) +{ + int error = 0; + + switch (cmd) { + case MOD_LOAD: + vhba = malloc(sizeof (*vhba), M_DEVBUF, M_WAITOK|M_ZERO); + mtx_init(&vhba->lock, "vhba", NULL, MTX_DEF); + error = vhba_attach(vhba); + if (error) { + mtx_destroy(&vhba->lock); + free(vhba, M_DEVBUF); + } + break; + case MOD_UNLOAD: + mtx_lock(&vhba->lock); + if (TAILQ_FIRST(&vhba->done) || TAILQ_FIRST(&vhba->actv)) { + error = EBUSY; + mtx_unlock(&vhba->lock); + break; + } + vhba_detach(vhba); + mtx_unlock(&vhba->lock); + mtx_destroy(&vhba->lock); + free(vhba, M_DEVBUF); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} |