root/tools/tools/vhba/vhba.c
/*-
 * 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.
 */
/*
 * 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_SPC3;
                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_SPC3;
                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, SCSI_REV_SPC3, 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, SCSI_REV_SPC3, 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);
}