Add the VHBA package. It is here in tools because it's really a testbed.
Sponsored by: Panasas MFC after: 1 month
This commit is contained in:
parent
78893c5f63
commit
f6ce2bf5e8
29
tools/tools/vhba/Makefile
Normal file
29
tools/tools/vhba/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
# $FreeBSD$
|
||||
#
|
||||
# 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.
|
||||
|
||||
SUBDIR= simple medium lots faulty rptluns
|
||||
|
||||
.include <bsd.subdir.mk>
|
16
tools/tools/vhba/README
Normal file
16
tools/tools/vhba/README
Normal file
@ -0,0 +1,16 @@
|
||||
$FreeBSD$
|
||||
Tue Jun 8 15:02:02 PDT 2010
|
||||
|
||||
This packages is a testbed for a number of purposes and consists
|
||||
of two pieces.
|
||||
|
||||
The first piece is a simple SIM driver for FreeBSD. It provides
|
||||
*just enough* framework to be useful, plus some nominally common
|
||||
code responses for code sharing purposes.
|
||||
|
||||
The second piece(s) are underlying implementations which make various
|
||||
virtual devices implemented under the VHBA itself. The current ones
|
||||
are pretty much used to stress and test the FreeBSD CAM framework
|
||||
itself- this is why this is in the tool directory.
|
||||
|
||||
Clearly other connections and possibilities exist as well.
|
7
tools/tools/vhba/faulty/Makefile
Normal file
7
tools/tools/vhba/faulty/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# $FreeBSD$
|
||||
KMOD= vfaulty
|
||||
SRCS= vhba_faulty.c vhba.c
|
||||
CFLAGS += -I${.CURDIR}/.. -DVHBA_MOD=\"vfaulty\"
|
||||
VPATH= ${.CURDIR}/..
|
||||
|
||||
.include <bsd.kmod.mk>
|
349
tools/tools/vhba/faulty/vhba_faulty.c
Normal file
349
tools/tools/vhba/faulty/vhba_faulty.c
Normal file
@ -0,0 +1,349 @@
|
||||
/*-
|
||||
* 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$ */
|
||||
/*
|
||||
* "Faulty" Device. Victimize random commands with a Selection Timeout.
|
||||
*/
|
||||
#include "vhba.h"
|
||||
|
||||
#define MAX_TGT VHBA_MAXTGT
|
||||
#define MAX_LUN 4
|
||||
|
||||
#define DISK_SIZE 32
|
||||
#define DISK_SHIFT 9
|
||||
#define DISK_NBLKS ((DISK_SIZE << 20) >> DISK_SHIFT)
|
||||
#define PSEUDO_SPT 64
|
||||
#define PSEUDO_HDS 64
|
||||
#define PSEUDO_SPC (PSEUDO_SPT * PSEUDO_HDS)
|
||||
|
||||
typedef struct {
|
||||
vhba_softc_t * vhba;
|
||||
uint8_t * disk;
|
||||
size_t disk_size;
|
||||
uint32_t ctr;
|
||||
uint32_t dead;
|
||||
struct task qt;
|
||||
} faulty_t;
|
||||
|
||||
static void vhba_task(void *, int);
|
||||
static void faulty_act(faulty_t *, struct ccb_scsiio *);
|
||||
|
||||
void
|
||||
vhba_init(vhba_softc_t *vhba)
|
||||
{
|
||||
static faulty_t vhbastatic;
|
||||
vhbastatic.vhba = vhba;
|
||||
vhbastatic.disk_size = DISK_SIZE << 20;
|
||||
vhbastatic.disk = malloc(vhbastatic.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
|
||||
vhba->private = &vhbastatic;
|
||||
vhbastatic.ctr = (arc4random() & 0xffff) + 1;
|
||||
TASK_INIT(&vhbastatic.qt, 0, vhba_task, &vhbastatic);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
vhba_fini(vhba_softc_t *vhba)
|
||||
{
|
||||
faulty_t *vhbas = vhba->private;
|
||||
vhba->private = NULL;
|
||||
free(vhbas->disk, M_DEVBUF);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_kick(vhba_softc_t *vhba)
|
||||
{
|
||||
faulty_t *vhbas = vhba->private;
|
||||
taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
|
||||
}
|
||||
|
||||
static void
|
||||
vhba_task(void *arg, int pending)
|
||||
{
|
||||
faulty_t *vhbas = arg;
|
||||
struct ccb_hdr *ccbh;
|
||||
|
||||
mtx_lock(&vhbas->vhba->lock);
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
|
||||
faulty_act(vhbas, (struct ccb_scsiio *)ccbh);
|
||||
if (--vhbas->ctr == 0) {
|
||||
vhbas->dead = 1;
|
||||
vhbas->ctr = (arc4random() & 0xff) + 1;
|
||||
}
|
||||
}
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
|
||||
xpt_done((union ccb *)ccbh);
|
||||
}
|
||||
mtx_unlock(&vhbas->vhba->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
faulty_act(faulty_t *vhbas, struct ccb_scsiio *csio)
|
||||
{
|
||||
char junk[128];
|
||||
cam_status camstatus;
|
||||
uint8_t *cdb, *ptr, status;
|
||||
uint32_t data_len;
|
||||
uint64_t off;
|
||||
|
||||
data_len = 0;
|
||||
status = SCSI_STATUS_OK;
|
||||
|
||||
memset(&csio->sense_data, 0, sizeof (csio->sense_data));
|
||||
cdb = csio->cdb_io.cdb_bytes;
|
||||
|
||||
if (csio->ccb_h.target_id >= MAX_TGT) {
|
||||
vhba_set_status(&csio->ccb_h, CAM_SEL_TIMEOUT);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
if (vhbas->dead) {
|
||||
vhbas->dead = 0;
|
||||
vhba_set_status(&csio->ccb_h, CAM_SEL_TIMEOUT);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cdb[0]) {
|
||||
case MODE_SENSE:
|
||||
case MODE_SENSE_10:
|
||||
{
|
||||
unsigned int nbyte;
|
||||
uint8_t page = cdb[2] & SMS_PAGE_CODE;
|
||||
uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
|
||||
|
||||
switch (page) {
|
||||
case SMS_FORMAT_DEVICE_PAGE:
|
||||
case SMS_GEOMETRY_PAGE:
|
||||
case SMS_CACHE_PAGE:
|
||||
case SMS_CONTROL_MODE_PAGE:
|
||||
case SMS_ALL_PAGES_PAGE:
|
||||
break;
|
||||
default:
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
memset(junk, 0, sizeof (junk));
|
||||
if (cdb[1] & SMS_DBD) {
|
||||
ptr = &junk[4];
|
||||
} else {
|
||||
ptr = junk;
|
||||
ptr[3] = 8;
|
||||
ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
|
||||
ptr[8] = (DISK_NBLKS >> 24) & 0xff;
|
||||
ptr[9] = (DISK_NBLKS >> 16) & 0xff;
|
||||
ptr[10] = (DISK_NBLKS >> 8) & 0xff;
|
||||
ptr[11] = DISK_NBLKS & 0xff;
|
||||
ptr += 12;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
|
||||
ptr[0] = SMS_FORMAT_DEVICE_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
/* tracks per zone */
|
||||
/* ptr[2] = 0; */
|
||||
/* ptr[3] = 0; */
|
||||
/* alternate sectors per zone */
|
||||
/* ptr[4] = 0; */
|
||||
/* ptr[5] = 0; */
|
||||
/* alternate tracks per zone */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* alternate tracks per logical unit */
|
||||
/* ptr[8] = 0; */
|
||||
/* ptr[9] = 0; */
|
||||
/* sectors per track */
|
||||
ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
|
||||
ptr[11] = PSEUDO_SPT & 0xff;
|
||||
/* data bytes per physical sector */
|
||||
ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[13] = (1 << DISK_SHIFT) & 0xff;
|
||||
/* interleave */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 1; */
|
||||
/* track skew factor */
|
||||
/* ptr[16] = 0; */
|
||||
/* ptr[17] = 0; */
|
||||
/* cylinder skew factor */
|
||||
/* ptr[18] = 0; */
|
||||
/* ptr[19] = 0; */
|
||||
/* SSRC, HSEC, RMB, SURF */
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
|
||||
ptr[0] = SMS_GEOMETRY_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
|
||||
/* number of cylinders */
|
||||
ptr[2] = (cyl >> 24) & 0xff;
|
||||
ptr[3] = (cyl >> 16) & 0xff;
|
||||
ptr[4] = cyl & 0xff;
|
||||
/* number of heads */
|
||||
ptr[5] = PSEUDO_HDS;
|
||||
/* starting cylinder- write precompensation */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* ptr[8] = 0; */
|
||||
/* starting cylinder- reduced write current */
|
||||
/* ptr[9] = 0; */
|
||||
/* ptr[10] = 0; */
|
||||
/* ptr[11] = 0; */
|
||||
/* drive step rate */
|
||||
/* ptr[12] = 0; */
|
||||
/* ptr[13] = 0; */
|
||||
/* landing zone cylinder */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 0; */
|
||||
/* ptr[16] = 0; */
|
||||
/* RPL */
|
||||
/* ptr[17] = 0; */
|
||||
/* rotational offset */
|
||||
/* ptr[18] = 0; */
|
||||
/* medium rotation rate - 7200 RPM */
|
||||
ptr[20] = 0x1c;
|
||||
ptr[21] = 0x20;
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
|
||||
ptr[0] = SMS_CACHE_PAGE;
|
||||
ptr[1] = 18;
|
||||
ptr[2] = 1 << 2;
|
||||
ptr += 20;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
|
||||
ptr[0] = SMS_CONTROL_MODE_PAGE;
|
||||
ptr[1] = 10;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
ptr[3] = 1 << 4; /* unrestricted reordering allowed */
|
||||
ptr[8] = 0x75; /* 30000 ms */
|
||||
ptr[9] = 0x30;
|
||||
}
|
||||
ptr += 12;
|
||||
}
|
||||
nbyte = (char *)ptr - &junk[0];
|
||||
ptr[0] = nbyte - 4;
|
||||
|
||||
if (cdb[0] == MODE_SENSE) {
|
||||
data_len = min(cdb[4], csio->dxfer_len);
|
||||
} else {
|
||||
uint16_t tw = (cdb[7] << 8) | cdb[8];
|
||||
data_len = min(tw, csio->dxfer_len);
|
||||
}
|
||||
data_len = min(data_len, nbyte);
|
||||
if (data_len) {
|
||||
memcpy(csio->data_ptr, junk, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
break;
|
||||
}
|
||||
case READ_6:
|
||||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
case WRITE_6:
|
||||
case WRITE_10:
|
||||
case WRITE_12:
|
||||
case WRITE_16:
|
||||
if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (data_len) {
|
||||
if ((cdb[0] & 0xf) == 8) {
|
||||
memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
|
||||
} else {
|
||||
memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
} else {
|
||||
csio->resid = csio->dxfer_len;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_CAPACITY:
|
||||
if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
|
||||
vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (cdb[8] & 0x1) { /* PMI */
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
} else {
|
||||
uint64_t last_blk = DISK_NBLKS - 1;
|
||||
if (last_blk < 0xffffffffULL) {
|
||||
csio->data_ptr[0] = (last_blk >> 24) & 0xff;
|
||||
csio->data_ptr[1] = (last_blk >> 16) & 0xff;
|
||||
csio->data_ptr[2] = (last_blk >> 8) & 0xff;
|
||||
csio->data_ptr[3] = (last_blk) & 0xff;
|
||||
} else {
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
}
|
||||
}
|
||||
csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
csio->data_ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
break;
|
||||
default:
|
||||
vhba_default_cmd(csio, MAX_LUN, NULL);
|
||||
break;
|
||||
}
|
||||
if (csio->scsi_status != SCSI_STATUS_OK) {
|
||||
camstatus = CAM_SCSI_STATUS_ERROR;
|
||||
if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
|
||||
camstatus |= CAM_AUTOSNS_VALID;
|
||||
}
|
||||
} else {
|
||||
csio->scsi_status = SCSI_STATUS_OK;
|
||||
camstatus = CAM_REQ_CMP;
|
||||
}
|
||||
vhba_set_status(&csio->ccb_h, camstatus);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
}
|
||||
DEV_MODULE(vhba_faulty, vhba_modprobe, NULL);
|
7
tools/tools/vhba/lots/Makefile
Normal file
7
tools/tools/vhba/lots/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# $FreeBSD$
|
||||
KMOD= vlots
|
||||
SRCS= vhba_lots.c vhba.c
|
||||
CFLAGS += -I${.CURDIR}/.. -DVHBA_MOD=\"vlots\"
|
||||
VPATH= ${.CURDIR}/..
|
||||
|
||||
.include <bsd.kmod.mk>
|
335
tools/tools/vhba/lots/vhba_lots.c
Normal file
335
tools/tools/vhba/lots/vhba_lots.c
Normal file
@ -0,0 +1,335 @@
|
||||
/*-
|
||||
* 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$ */
|
||||
/*
|
||||
* VHBA device that just reate boatloads of devices.
|
||||
*/
|
||||
#include "vhba.h"
|
||||
|
||||
#define MAX_TGT VHBA_MAXTGT
|
||||
#define MAX_LUN 32
|
||||
|
||||
#define DISK_SIZE 32
|
||||
#define DISK_SHIFT 9
|
||||
#define DISK_NBLKS ((DISK_SIZE << 20) >> DISK_SHIFT)
|
||||
#define PSEUDO_SPT 64
|
||||
#define PSEUDO_HDS 64
|
||||
#define PSEUDO_SPC (PSEUDO_SPT * PSEUDO_HDS)
|
||||
|
||||
typedef struct {
|
||||
vhba_softc_t * vhba;
|
||||
uint8_t * disk;
|
||||
size_t disk_size;
|
||||
struct task qt;
|
||||
} vhbalots_t;
|
||||
|
||||
static void vhba_task(void *, int);
|
||||
static void vhbalots_act(vhbalots_t *, struct ccb_scsiio *);
|
||||
|
||||
void
|
||||
vhba_init(vhba_softc_t *vhba)
|
||||
{
|
||||
static vhbalots_t vhbas;
|
||||
vhbas.vhba = vhba;
|
||||
vhbas.disk_size = DISK_SIZE << 20;
|
||||
vhbas.disk = malloc(vhbas.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
|
||||
vhba->private = &vhbas;
|
||||
TASK_INIT(&vhbas.qt, 0, vhba_task, &vhbas);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
vhba_fini(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbalots_t *vhbas = vhba->private;
|
||||
vhba->private = NULL;
|
||||
free(vhbas->disk, M_DEVBUF);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_kick(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbalots_t *vhbas = vhba->private;
|
||||
taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
|
||||
}
|
||||
|
||||
static void
|
||||
vhba_task(void *arg, int pending)
|
||||
{
|
||||
vhbalots_t *vhbas = arg;
|
||||
struct ccb_hdr *ccbh;
|
||||
|
||||
mtx_lock(&vhbas->vhba->lock);
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
|
||||
vhbalots_act(vhbas, (struct ccb_scsiio *)ccbh);
|
||||
}
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
|
||||
xpt_done((union ccb *)ccbh);
|
||||
}
|
||||
mtx_unlock(&vhbas->vhba->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
vhbalots_act(vhbalots_t *vhbas, struct ccb_scsiio *csio)
|
||||
{
|
||||
char junk[128];
|
||||
uint8_t *cdb, *ptr, status;
|
||||
uint32_t data_len;
|
||||
uint64_t off;
|
||||
|
||||
data_len = 0;
|
||||
status = SCSI_STATUS_OK;
|
||||
|
||||
memset(&csio->sense_data, 0, sizeof (csio->sense_data));
|
||||
cdb = csio->cdb_io.cdb_bytes;
|
||||
|
||||
if (csio->ccb_h.target_id >= MAX_TGT) {
|
||||
csio->ccb_h.status = CAM_SEL_TIMEOUT;
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cdb[0]) {
|
||||
case MODE_SENSE:
|
||||
case MODE_SENSE_10:
|
||||
{
|
||||
unsigned int nbyte;
|
||||
uint8_t page = cdb[2] & SMS_PAGE_CODE;
|
||||
uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
|
||||
|
||||
switch (page) {
|
||||
case SMS_FORMAT_DEVICE_PAGE:
|
||||
case SMS_GEOMETRY_PAGE:
|
||||
case SMS_CACHE_PAGE:
|
||||
case SMS_CONTROL_MODE_PAGE:
|
||||
case SMS_ALL_PAGES_PAGE:
|
||||
break;
|
||||
default:
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
memset(junk, 0, sizeof (junk));
|
||||
if (cdb[1] & SMS_DBD) {
|
||||
ptr = &junk[4];
|
||||
} else {
|
||||
ptr = junk;
|
||||
ptr[3] = 8;
|
||||
ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
|
||||
ptr[8] = (DISK_NBLKS >> 24) & 0xff;
|
||||
ptr[9] = (DISK_NBLKS >> 16) & 0xff;
|
||||
ptr[10] = (DISK_NBLKS >> 8) & 0xff;
|
||||
ptr[11] = DISK_NBLKS & 0xff;
|
||||
ptr += 12;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
|
||||
ptr[0] = SMS_FORMAT_DEVICE_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
/* tracks per zone */
|
||||
/* ptr[2] = 0; */
|
||||
/* ptr[3] = 0; */
|
||||
/* alternate sectors per zone */
|
||||
/* ptr[4] = 0; */
|
||||
/* ptr[5] = 0; */
|
||||
/* alternate tracks per zone */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* alternate tracks per logical unit */
|
||||
/* ptr[8] = 0; */
|
||||
/* ptr[9] = 0; */
|
||||
/* sectors per track */
|
||||
ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
|
||||
ptr[11] = PSEUDO_SPT & 0xff;
|
||||
/* data bytes per physical sector */
|
||||
ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[13] = (1 << DISK_SHIFT) & 0xff;
|
||||
/* interleave */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 1; */
|
||||
/* track skew factor */
|
||||
/* ptr[16] = 0; */
|
||||
/* ptr[17] = 0; */
|
||||
/* cylinder skew factor */
|
||||
/* ptr[18] = 0; */
|
||||
/* ptr[19] = 0; */
|
||||
/* SSRC, HSEC, RMB, SURF */
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
|
||||
ptr[0] = SMS_GEOMETRY_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
|
||||
/* number of cylinders */
|
||||
ptr[2] = (cyl >> 24) & 0xff;
|
||||
ptr[3] = (cyl >> 16) & 0xff;
|
||||
ptr[4] = cyl & 0xff;
|
||||
/* number of heads */
|
||||
ptr[5] = PSEUDO_HDS;
|
||||
/* starting cylinder- write precompensation */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* ptr[8] = 0; */
|
||||
/* starting cylinder- reduced write current */
|
||||
/* ptr[9] = 0; */
|
||||
/* ptr[10] = 0; */
|
||||
/* ptr[11] = 0; */
|
||||
/* drive step rate */
|
||||
/* ptr[12] = 0; */
|
||||
/* ptr[13] = 0; */
|
||||
/* landing zone cylinder */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 0; */
|
||||
/* ptr[16] = 0; */
|
||||
/* RPL */
|
||||
/* ptr[17] = 0; */
|
||||
/* rotational offset */
|
||||
/* ptr[18] = 0; */
|
||||
/* medium rotation rate - 7200 RPM */
|
||||
ptr[20] = 0x1c;
|
||||
ptr[21] = 0x20;
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
|
||||
ptr[0] = SMS_CACHE_PAGE;
|
||||
ptr[1] = 18;
|
||||
ptr[2] = 1 << 2;
|
||||
ptr += 20;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
|
||||
ptr[0] = SMS_CONTROL_MODE_PAGE;
|
||||
ptr[1] = 10;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
ptr[3] = 1 << 4; /* unrestricted reordering allowed */
|
||||
ptr[8] = 0x75; /* 30000 ms */
|
||||
ptr[9] = 0x30;
|
||||
}
|
||||
ptr += 12;
|
||||
}
|
||||
nbyte = (char *)ptr - &junk[0];
|
||||
ptr[0] = nbyte - 4;
|
||||
|
||||
if (cdb[0] == MODE_SENSE) {
|
||||
data_len = min(cdb[4], csio->dxfer_len);
|
||||
} else {
|
||||
uint16_t tw = (cdb[7] << 8) | cdb[8];
|
||||
data_len = min(tw, csio->dxfer_len);
|
||||
}
|
||||
data_len = min(data_len, nbyte);
|
||||
if (data_len) {
|
||||
memcpy(csio->data_ptr, junk, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
break;
|
||||
}
|
||||
case READ_6:
|
||||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
case WRITE_6:
|
||||
case WRITE_10:
|
||||
case WRITE_12:
|
||||
case WRITE_16:
|
||||
if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (data_len) {
|
||||
if ((cdb[0] & 0xf) == 8) {
|
||||
memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
|
||||
} else {
|
||||
memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
} else {
|
||||
csio->resid = csio->dxfer_len;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_CAPACITY:
|
||||
if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
|
||||
vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (cdb[8] & 0x1) { /* PMI */
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
} else {
|
||||
uint64_t last_blk = DISK_NBLKS - 1;
|
||||
if (last_blk < 0xffffffffULL) {
|
||||
csio->data_ptr[0] = (last_blk >> 24) & 0xff;
|
||||
csio->data_ptr[1] = (last_blk >> 16) & 0xff;
|
||||
csio->data_ptr[2] = (last_blk >> 8) & 0xff;
|
||||
csio->data_ptr[3] = (last_blk) & 0xff;
|
||||
} else {
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
}
|
||||
}
|
||||
csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
csio->data_ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
break;
|
||||
default:
|
||||
vhba_default_cmd(csio, MAX_LUN, NULL);
|
||||
break;
|
||||
}
|
||||
csio->ccb_h.status &= ~CAM_STATUS_MASK;
|
||||
if (csio->scsi_status != SCSI_STATUS_OK) {
|
||||
csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
|
||||
if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
|
||||
csio->ccb_h.status |= CAM_AUTOSNS_VALID;
|
||||
}
|
||||
} else {
|
||||
csio->scsi_status = SCSI_STATUS_OK;
|
||||
csio->ccb_h.status |= CAM_REQ_CMP;
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
}
|
||||
DEV_MODULE(vhba_lots, vhba_modprobe, NULL);
|
7
tools/tools/vhba/medium/Makefile
Normal file
7
tools/tools/vhba/medium/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# $FreeBSD$
|
||||
KMOD= vmedium
|
||||
SRCS= vhba_medium.c vhba.c
|
||||
CFLAGS += -I${.CURDIR}/.. -DVHBA_MOD=\"vmedium\"
|
||||
VPATH= ${.CURDIR}/..
|
||||
|
||||
.include <bsd.kmod.mk>
|
337
tools/tools/vhba/medium/vhba_medium.c
Normal file
337
tools/tools/vhba/medium/vhba_medium.c
Normal file
@ -0,0 +1,337 @@
|
||||
/*-
|
||||
* 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$ */
|
||||
/*
|
||||
* A VHBA device that has a medium number of device.
|
||||
*/
|
||||
#include "vhba.h"
|
||||
|
||||
#define MAX_TGT 4
|
||||
#define MAX_LUN 10
|
||||
|
||||
#define DISK_SIZE 32
|
||||
#define DISK_SHIFT 9
|
||||
#define DISK_NBLKS ((DISK_SIZE << 20) >> DISK_SHIFT)
|
||||
#define PSEUDO_SPT 64
|
||||
#define PSEUDO_HDS 64
|
||||
#define PSEUDO_SPC (PSEUDO_SPT * PSEUDO_HDS)
|
||||
|
||||
typedef struct {
|
||||
vhba_softc_t * vhba;
|
||||
uint8_t * disk;
|
||||
size_t disk_size;
|
||||
struct task qt;
|
||||
} vhbamedium_t;
|
||||
|
||||
static void vhba_task(void *, int);
|
||||
static void vhbamedium_act(vhbamedium_t *, struct ccb_scsiio *);
|
||||
|
||||
void
|
||||
vhba_init(vhba_softc_t *vhba)
|
||||
{
|
||||
static vhbamedium_t vhbas;
|
||||
vhbas.vhba = vhba;
|
||||
vhbas.disk_size = DISK_SIZE << 20;
|
||||
vhbas.disk = malloc(vhbas.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
|
||||
vhba->private = &vhbas;
|
||||
TASK_INIT(&vhbas.qt, 0, vhba_task, &vhbas);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
vhba_fini(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbamedium_t *vhbas = vhba->private;
|
||||
vhba->private = NULL;
|
||||
free(vhbas->disk, M_DEVBUF);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_kick(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbamedium_t *vhbas = vhba->private;
|
||||
taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
|
||||
}
|
||||
|
||||
static void
|
||||
vhba_task(void *arg, int pending)
|
||||
{
|
||||
vhbamedium_t *vhbas = arg;
|
||||
struct ccb_hdr *ccbh;
|
||||
|
||||
mtx_lock(&vhbas->vhba->lock);
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
|
||||
vhbamedium_act(vhbas, (struct ccb_scsiio *)ccbh);
|
||||
}
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
|
||||
xpt_done((union ccb *)ccbh);
|
||||
}
|
||||
mtx_unlock(&vhbas->vhba->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
vhbamedium_act(vhbamedium_t *vhbas, struct ccb_scsiio *csio)
|
||||
{
|
||||
char junk[128];
|
||||
uint8_t *cdb, *ptr, status;
|
||||
uint32_t data_len;
|
||||
uint64_t off;
|
||||
|
||||
data_len = 0;
|
||||
status = SCSI_STATUS_OK;
|
||||
|
||||
memset(&csio->sense_data, 0, sizeof (csio->sense_data));
|
||||
cdb = csio->cdb_io.cdb_bytes;
|
||||
|
||||
if (csio->ccb_h.target_id >= MAX_TGT) {
|
||||
csio->ccb_h.status = CAM_SEL_TIMEOUT;
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cdb[0]) {
|
||||
case MODE_SENSE:
|
||||
case MODE_SENSE_10:
|
||||
{
|
||||
unsigned int nbyte;
|
||||
uint8_t page = cdb[2] & SMS_PAGE_CODE;
|
||||
uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
|
||||
|
||||
switch (page) {
|
||||
case SMS_FORMAT_DEVICE_PAGE:
|
||||
case SMS_GEOMETRY_PAGE:
|
||||
case SMS_CACHE_PAGE:
|
||||
case SMS_CONTROL_MODE_PAGE:
|
||||
case SMS_ALL_PAGES_PAGE:
|
||||
break;
|
||||
default:
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
memset(junk, 0, sizeof (junk));
|
||||
if (cdb[1] & SMS_DBD) {
|
||||
ptr = &junk[4];
|
||||
} else {
|
||||
ptr = junk;
|
||||
ptr[3] = 8;
|
||||
ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
|
||||
ptr[8] = (DISK_NBLKS >> 24) & 0xff;
|
||||
ptr[9] = (DISK_NBLKS >> 16) & 0xff;
|
||||
ptr[10] = (DISK_NBLKS >> 8) & 0xff;
|
||||
ptr[11] = DISK_NBLKS & 0xff;
|
||||
ptr += 12;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
|
||||
ptr[0] = SMS_FORMAT_DEVICE_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
/* tracks per zone */
|
||||
/* ptr[2] = 0; */
|
||||
/* ptr[3] = 0; */
|
||||
/* alternate sectors per zone */
|
||||
/* ptr[4] = 0; */
|
||||
/* ptr[5] = 0; */
|
||||
/* alternate tracks per zone */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* alternate tracks per logical unit */
|
||||
/* ptr[8] = 0; */
|
||||
/* ptr[9] = 0; */
|
||||
/* sectors per track */
|
||||
ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
|
||||
ptr[11] = PSEUDO_SPT & 0xff;
|
||||
/* data bytes per physical sector */
|
||||
ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[13] = (1 << DISK_SHIFT) & 0xff;
|
||||
/* interleave */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 1; */
|
||||
/* track skew factor */
|
||||
/* ptr[16] = 0; */
|
||||
/* ptr[17] = 0; */
|
||||
/* cylinder skew factor */
|
||||
/* ptr[18] = 0; */
|
||||
/* ptr[19] = 0; */
|
||||
/* SSRC, HSEC, RMB, SURF */
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
|
||||
ptr[0] = SMS_GEOMETRY_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
|
||||
/* number of cylinders */
|
||||
ptr[2] = (cyl >> 24) & 0xff;
|
||||
ptr[3] = (cyl >> 16) & 0xff;
|
||||
ptr[4] = cyl & 0xff;
|
||||
/* number of heads */
|
||||
ptr[5] = PSEUDO_HDS;
|
||||
/* starting cylinder- write precompensation */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* ptr[8] = 0; */
|
||||
/* starting cylinder- reduced write current */
|
||||
/* ptr[9] = 0; */
|
||||
/* ptr[10] = 0; */
|
||||
/* ptr[11] = 0; */
|
||||
/* drive step rate */
|
||||
/* ptr[12] = 0; */
|
||||
/* ptr[13] = 0; */
|
||||
/* landing zone cylinder */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 0; */
|
||||
/* ptr[16] = 0; */
|
||||
/* RPL */
|
||||
/* ptr[17] = 0; */
|
||||
/* rotational offset */
|
||||
/* ptr[18] = 0; */
|
||||
/* medium rotation rate - 7200 RPM */
|
||||
ptr[20] = 0x1c;
|
||||
ptr[21] = 0x20;
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
|
||||
ptr[0] = SMS_CACHE_PAGE;
|
||||
ptr[1] = 18;
|
||||
ptr[2] = 1 << 2;
|
||||
ptr += 20;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
|
||||
ptr[0] = SMS_CONTROL_MODE_PAGE;
|
||||
ptr[1] = 10;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
ptr[3] = 1 << 4; /* unrestricted reordering allowed */
|
||||
ptr[8] = 0x75; /* 30000 ms */
|
||||
ptr[9] = 0x30;
|
||||
}
|
||||
ptr += 12;
|
||||
}
|
||||
nbyte = (char *)ptr - &junk[0];
|
||||
ptr[0] = nbyte - 4;
|
||||
|
||||
if (cdb[0] == MODE_SENSE) {
|
||||
data_len = min(cdb[4], csio->dxfer_len);
|
||||
} else {
|
||||
uint16_t tw = (cdb[7] << 8) | cdb[8];
|
||||
data_len = min(tw, csio->dxfer_len);
|
||||
}
|
||||
data_len = min(data_len, nbyte);
|
||||
if (data_len) {
|
||||
memcpy(csio->data_ptr, junk, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
break;
|
||||
}
|
||||
case READ_6:
|
||||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
case WRITE_6:
|
||||
case WRITE_10:
|
||||
case WRITE_12:
|
||||
case WRITE_16:
|
||||
if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (data_len) {
|
||||
if ((cdb[0] & 0xf) == 8) {
|
||||
memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
|
||||
} else {
|
||||
memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
} else {
|
||||
csio->resid = csio->dxfer_len;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case READ_CAPACITY:
|
||||
if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
|
||||
vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (cdb[8] & 0x1) { /* PMI */
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
} else {
|
||||
uint64_t last_blk = DISK_NBLKS - 1;
|
||||
if (last_blk < 0xffffffffULL) {
|
||||
csio->data_ptr[0] = (last_blk >> 24) & 0xff;
|
||||
csio->data_ptr[1] = (last_blk >> 16) & 0xff;
|
||||
csio->data_ptr[2] = (last_blk >> 8) & 0xff;
|
||||
csio->data_ptr[3] = (last_blk) & 0xff;
|
||||
} else {
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
}
|
||||
}
|
||||
csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
csio->data_ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
break;
|
||||
|
||||
default:
|
||||
vhba_default_cmd(csio, MAX_LUN, NULL);
|
||||
break;
|
||||
}
|
||||
csio->ccb_h.status &= ~CAM_STATUS_MASK;
|
||||
if (csio->scsi_status != SCSI_STATUS_OK) {
|
||||
csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
|
||||
if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
|
||||
csio->ccb_h.status |= CAM_AUTOSNS_VALID;
|
||||
}
|
||||
} else {
|
||||
csio->scsi_status = SCSI_STATUS_OK;
|
||||
csio->ccb_h.status |= CAM_REQ_CMP;
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
}
|
||||
DEV_MODULE(vhba_mediaum, vhba_modprobe, NULL);
|
1
tools/tools/vhba/opt_cam.h
Normal file
1
tools/tools/vhba/opt_cam.h
Normal file
@ -0,0 +1 @@
|
||||
/* $FreeBSD$ */
|
7
tools/tools/vhba/rptluns/Makefile
Normal file
7
tools/tools/vhba/rptluns/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# $FreeBSD$
|
||||
KMOD= vrptluns
|
||||
SRCS= vhba_rptluns.c vhba.c
|
||||
CFLAGS += -I${.CURDIR}/.. -DVHBA_MOD=\"vrptluns\"
|
||||
VPATH= ${.CURDIR}/..
|
||||
|
||||
.include <bsd.kmod.mk>
|
366
tools/tools/vhba/rptluns/vhba_rptluns.c
Normal file
366
tools/tools/vhba/rptluns/vhba_rptluns.c
Normal file
@ -0,0 +1,366 @@
|
||||
/*-
|
||||
* 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$ */
|
||||
/*
|
||||
* A VHBA device to test REPORT LUN functionality.
|
||||
*/
|
||||
#include "vhba.h"
|
||||
|
||||
#define MAX_TGT 1
|
||||
#define MAX_LUN 1024
|
||||
|
||||
#define DISK_SIZE 32
|
||||
#define DISK_SHIFT 9
|
||||
#define DISK_NBLKS ((DISK_SIZE << 20) >> DISK_SHIFT)
|
||||
#define PSEUDO_SPT 64
|
||||
#define PSEUDO_HDS 64
|
||||
#define PSEUDO_SPC (PSEUDO_SPT * PSEUDO_HDS)
|
||||
|
||||
typedef struct {
|
||||
vhba_softc_t * vhba;
|
||||
uint8_t * disk;
|
||||
size_t disk_size;
|
||||
struct task qt;
|
||||
uint8_t rpbitmap[MAX_LUN >> 3];
|
||||
} vhbarptluns_t;
|
||||
|
||||
static void vhba_task(void *, int);
|
||||
static void vhbarptluns_act(vhbarptluns_t *, struct ccb_scsiio *);
|
||||
|
||||
void
|
||||
vhba_init(vhba_softc_t *vhba)
|
||||
{
|
||||
static vhbarptluns_t vhbas;
|
||||
struct timeval now;
|
||||
int i;
|
||||
|
||||
vhbas.vhba = vhba;
|
||||
vhbas.disk_size = DISK_SIZE << 20;
|
||||
vhbas.disk = malloc(vhbas.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
|
||||
vhba->private = &vhbas;
|
||||
printf("setting luns");
|
||||
getmicrotime(&now);
|
||||
if (now.tv_usec & 0x1) {
|
||||
vhbas.rpbitmap[0] |= 1;
|
||||
}
|
||||
for (i = 1; i < 8; i++) {
|
||||
if (arc4random() & 1) {
|
||||
printf(" %d", i);
|
||||
vhbas.rpbitmap[0] |= (1 << i);
|
||||
}
|
||||
}
|
||||
for (i = 8; i < MAX_LUN; i++) {
|
||||
if ((arc4random() % i) == 0) {
|
||||
vhbas.rpbitmap[i >> 3] |= (1 << (i & 0x7));
|
||||
printf(" %d", i);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
TASK_INIT(&vhbas.qt, 0, vhba_task, &vhbas);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_fini(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbarptluns_t *vhbas = vhba->private;
|
||||
vhba->private = NULL;
|
||||
free(vhbas->disk, M_DEVBUF);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_kick(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbarptluns_t *vhbas = vhba->private;
|
||||
taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
|
||||
}
|
||||
|
||||
static void
|
||||
vhba_task(void *arg, int pending)
|
||||
{
|
||||
vhbarptluns_t *vhbas = arg;
|
||||
struct ccb_hdr *ccbh;
|
||||
|
||||
mtx_lock(&vhbas->vhba->lock);
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
|
||||
vhbarptluns_act(vhbas, (struct ccb_scsiio *)ccbh);
|
||||
}
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
|
||||
xpt_done((union ccb *)ccbh);
|
||||
}
|
||||
mtx_unlock(&vhbas->vhba->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
vhbarptluns_act(vhbarptluns_t *vhbas, struct ccb_scsiio *csio)
|
||||
{
|
||||
char junk[128];
|
||||
uint8_t *cdb, *ptr, status;
|
||||
uint32_t data_len;
|
||||
uint64_t off;
|
||||
int i, attached_lun = 0;
|
||||
|
||||
data_len = 0;
|
||||
status = SCSI_STATUS_OK;
|
||||
|
||||
memset(&csio->sense_data, 0, sizeof (csio->sense_data));
|
||||
cdb = csio->cdb_io.cdb_bytes;
|
||||
|
||||
if (csio->ccb_h.target_id >= MAX_TGT) {
|
||||
csio->ccb_h.status = CAM_SEL_TIMEOUT;
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
if (csio->ccb_h.target_lun < MAX_LUN) {
|
||||
i = csio->ccb_h.target_lun & 0x7;
|
||||
if (vhbas->rpbitmap[csio->ccb_h.target_lun >> 3] & (1 << i)) {
|
||||
attached_lun = 1;
|
||||
}
|
||||
}
|
||||
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);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cdb[0]) {
|
||||
case MODE_SENSE:
|
||||
case MODE_SENSE_10:
|
||||
{
|
||||
unsigned int nbyte;
|
||||
uint8_t page = cdb[2] & SMS_PAGE_CODE;
|
||||
uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
|
||||
|
||||
switch (page) {
|
||||
case SMS_FORMAT_DEVICE_PAGE:
|
||||
case SMS_GEOMETRY_PAGE:
|
||||
case SMS_CACHE_PAGE:
|
||||
case SMS_CONTROL_MODE_PAGE:
|
||||
case SMS_ALL_PAGES_PAGE:
|
||||
break;
|
||||
default:
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
memset(junk, 0, sizeof (junk));
|
||||
if (cdb[1] & SMS_DBD) {
|
||||
ptr = &junk[4];
|
||||
} else {
|
||||
ptr = junk;
|
||||
ptr[3] = 8;
|
||||
ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
|
||||
ptr[8] = (DISK_NBLKS >> 24) & 0xff;
|
||||
ptr[9] = (DISK_NBLKS >> 16) & 0xff;
|
||||
ptr[10] = (DISK_NBLKS >> 8) & 0xff;
|
||||
ptr[11] = DISK_NBLKS & 0xff;
|
||||
ptr += 12;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
|
||||
ptr[0] = SMS_FORMAT_DEVICE_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
/* tracks per zone */
|
||||
/* ptr[2] = 0; */
|
||||
/* ptr[3] = 0; */
|
||||
/* alternate sectors per zone */
|
||||
/* ptr[4] = 0; */
|
||||
/* ptr[5] = 0; */
|
||||
/* alternate tracks per zone */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* alternate tracks per logical unit */
|
||||
/* ptr[8] = 0; */
|
||||
/* ptr[9] = 0; */
|
||||
/* sectors per track */
|
||||
ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
|
||||
ptr[11] = PSEUDO_SPT & 0xff;
|
||||
/* data bytes per physical sector */
|
||||
ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[13] = (1 << DISK_SHIFT) & 0xff;
|
||||
/* interleave */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 1; */
|
||||
/* track skew factor */
|
||||
/* ptr[16] = 0; */
|
||||
/* ptr[17] = 0; */
|
||||
/* cylinder skew factor */
|
||||
/* ptr[18] = 0; */
|
||||
/* ptr[19] = 0; */
|
||||
/* SSRC, HSEC, RMB, SURF */
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
|
||||
ptr[0] = SMS_GEOMETRY_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
|
||||
/* number of cylinders */
|
||||
ptr[2] = (cyl >> 24) & 0xff;
|
||||
ptr[3] = (cyl >> 16) & 0xff;
|
||||
ptr[4] = cyl & 0xff;
|
||||
/* number of heads */
|
||||
ptr[5] = PSEUDO_HDS;
|
||||
/* starting cylinder- write precompensation */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* ptr[8] = 0; */
|
||||
/* starting cylinder- reduced write current */
|
||||
/* ptr[9] = 0; */
|
||||
/* ptr[10] = 0; */
|
||||
/* ptr[11] = 0; */
|
||||
/* drive step rate */
|
||||
/* ptr[12] = 0; */
|
||||
/* ptr[13] = 0; */
|
||||
/* landing zone cylinder */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 0; */
|
||||
/* ptr[16] = 0; */
|
||||
/* RPL */
|
||||
/* ptr[17] = 0; */
|
||||
/* rotational offset */
|
||||
/* ptr[18] = 0; */
|
||||
/* medium rotation rate - 7200 RPM */
|
||||
ptr[20] = 0x1c;
|
||||
ptr[21] = 0x20;
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
|
||||
ptr[0] = SMS_CACHE_PAGE;
|
||||
ptr[1] = 18;
|
||||
ptr[2] = 1 << 2;
|
||||
ptr += 20;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
|
||||
ptr[0] = SMS_CONTROL_MODE_PAGE;
|
||||
ptr[1] = 10;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
ptr[3] = 1 << 4; /* unrestricted reordering allowed */
|
||||
ptr[8] = 0x75; /* 30000 ms */
|
||||
ptr[9] = 0x30;
|
||||
}
|
||||
ptr += 12;
|
||||
}
|
||||
nbyte = (char *)ptr - &junk[0];
|
||||
ptr[0] = nbyte - 4;
|
||||
|
||||
if (cdb[0] == MODE_SENSE) {
|
||||
data_len = min(cdb[4], csio->dxfer_len);
|
||||
} else {
|
||||
uint16_t tw = (cdb[7] << 8) | cdb[8];
|
||||
data_len = min(tw, csio->dxfer_len);
|
||||
}
|
||||
data_len = min(data_len, nbyte);
|
||||
if (data_len) {
|
||||
memcpy(csio->data_ptr, junk, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
break;
|
||||
}
|
||||
case READ_6:
|
||||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
case WRITE_6:
|
||||
case WRITE_10:
|
||||
case WRITE_12:
|
||||
case WRITE_16:
|
||||
if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (data_len) {
|
||||
if ((cdb[0] & 0xf) == 8) {
|
||||
memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
|
||||
} else {
|
||||
memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
} else {
|
||||
csio->resid = csio->dxfer_len;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case READ_CAPACITY:
|
||||
if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
|
||||
vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (cdb[8] & 0x1) { /* PMI */
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
} else {
|
||||
uint64_t last_blk = DISK_NBLKS - 1;
|
||||
if (last_blk < 0xffffffffULL) {
|
||||
csio->data_ptr[0] = (last_blk >> 24) & 0xff;
|
||||
csio->data_ptr[1] = (last_blk >> 16) & 0xff;
|
||||
csio->data_ptr[2] = (last_blk >> 8) & 0xff;
|
||||
csio->data_ptr[3] = (last_blk) & 0xff;
|
||||
} else {
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
}
|
||||
}
|
||||
csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
csio->data_ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
break;
|
||||
|
||||
default:
|
||||
vhba_default_cmd(csio, MAX_LUN, vhbas->rpbitmap);
|
||||
break;
|
||||
}
|
||||
csio->ccb_h.status &= ~CAM_STATUS_MASK;
|
||||
if (csio->scsi_status != SCSI_STATUS_OK) {
|
||||
csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
|
||||
if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
|
||||
csio->ccb_h.status |= CAM_AUTOSNS_VALID;
|
||||
}
|
||||
} else {
|
||||
csio->scsi_status = SCSI_STATUS_OK;
|
||||
csio->ccb_h.status |= CAM_REQ_CMP;
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
}
|
||||
DEV_MODULE(vhba_rtpluns, vhba_modprobe, NULL);
|
7
tools/tools/vhba/simple/Makefile
Normal file
7
tools/tools/vhba/simple/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# $FreeBSD$
|
||||
KMOD= vsimple
|
||||
SRCS= vhba_simple.c vhba.c
|
||||
CFLAGS += -I${.CURDIR}/.. -DVHBA_MOD=\"vsimple\"
|
||||
VPATH= ${.CURDIR}/..
|
||||
|
||||
.include <bsd.kmod.mk>
|
337
tools/tools/vhba/simple/vhba_simple.c
Normal file
337
tools/tools/vhba/simple/vhba_simple.c
Normal file
@ -0,0 +1,337 @@
|
||||
/*-
|
||||
* 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$ */
|
||||
/*
|
||||
* "Simple" VHBA device
|
||||
*/
|
||||
#include "vhba.h"
|
||||
|
||||
#define MAX_TGT 1
|
||||
#define MAX_LUN 1
|
||||
|
||||
#define DISK_SIZE 32
|
||||
#define DISK_SHIFT 9
|
||||
#define DISK_NBLKS ((DISK_SIZE << 20) >> DISK_SHIFT)
|
||||
#define PSEUDO_SPT 64
|
||||
#define PSEUDO_HDS 64
|
||||
#define PSEUDO_SPC (PSEUDO_SPT * PSEUDO_HDS)
|
||||
|
||||
typedef struct {
|
||||
vhba_softc_t * vhba;
|
||||
uint8_t * disk;
|
||||
size_t disk_size;
|
||||
struct task qt;
|
||||
} vhbasimple_t;
|
||||
|
||||
static void vhba_task(void *, int);
|
||||
static void vhbasimple_act(vhbasimple_t *, struct ccb_scsiio *);
|
||||
|
||||
void
|
||||
vhba_init(vhba_softc_t *vhba)
|
||||
{
|
||||
static vhbasimple_t vhbas;
|
||||
vhbas.vhba = vhba;
|
||||
vhbas.disk_size = DISK_SIZE << 20;
|
||||
vhbas.disk = malloc(vhbas.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
|
||||
vhba->private = &vhbas;
|
||||
TASK_INIT(&vhbas.qt, 0, vhba_task, &vhbas);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
vhba_fini(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbasimple_t *vhbas = vhba->private;
|
||||
vhba->private = NULL;
|
||||
free(vhbas->disk, M_DEVBUF);
|
||||
}
|
||||
|
||||
void
|
||||
vhba_kick(vhba_softc_t *vhba)
|
||||
{
|
||||
vhbasimple_t *vhbas = vhba->private;
|
||||
taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
|
||||
}
|
||||
|
||||
static void
|
||||
vhba_task(void *arg, int pending)
|
||||
{
|
||||
vhbasimple_t *vhbas = arg;
|
||||
struct ccb_hdr *ccbh;
|
||||
|
||||
mtx_lock(&vhbas->vhba->lock);
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
|
||||
vhbasimple_act(vhbas, (struct ccb_scsiio *)ccbh);
|
||||
}
|
||||
while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
|
||||
TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
|
||||
xpt_done((union ccb *)ccbh);
|
||||
}
|
||||
mtx_unlock(&vhbas->vhba->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
vhbasimple_act(vhbasimple_t *vhbas, struct ccb_scsiio *csio)
|
||||
{
|
||||
char junk[128];
|
||||
uint8_t *cdb, *ptr, status;
|
||||
uint32_t data_len;
|
||||
uint64_t off;
|
||||
|
||||
data_len = 0;
|
||||
status = SCSI_STATUS_OK;
|
||||
|
||||
memset(&csio->sense_data, 0, sizeof (csio->sense_data));
|
||||
cdb = csio->cdb_io.cdb_bytes;
|
||||
|
||||
if (csio->ccb_h.target_id >= MAX_TGT) {
|
||||
csio->ccb_h.status = CAM_SEL_TIMEOUT;
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cdb[0]) {
|
||||
case MODE_SENSE:
|
||||
case MODE_SENSE_10:
|
||||
{
|
||||
unsigned int nbyte;
|
||||
uint8_t page = cdb[2] & SMS_PAGE_CODE;
|
||||
uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;
|
||||
|
||||
switch (page) {
|
||||
case SMS_FORMAT_DEVICE_PAGE:
|
||||
case SMS_GEOMETRY_PAGE:
|
||||
case SMS_CACHE_PAGE:
|
||||
case SMS_CONTROL_MODE_PAGE:
|
||||
case SMS_ALL_PAGES_PAGE:
|
||||
break;
|
||||
default:
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
return;
|
||||
}
|
||||
memset(junk, 0, sizeof (junk));
|
||||
if (cdb[1] & SMS_DBD) {
|
||||
ptr = &junk[4];
|
||||
} else {
|
||||
ptr = junk;
|
||||
ptr[3] = 8;
|
||||
ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
|
||||
ptr[8] = (DISK_NBLKS >> 24) & 0xff;
|
||||
ptr[9] = (DISK_NBLKS >> 16) & 0xff;
|
||||
ptr[10] = (DISK_NBLKS >> 8) & 0xff;
|
||||
ptr[11] = DISK_NBLKS & 0xff;
|
||||
ptr += 12;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
|
||||
ptr[0] = SMS_FORMAT_DEVICE_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
/* tracks per zone */
|
||||
/* ptr[2] = 0; */
|
||||
/* ptr[3] = 0; */
|
||||
/* alternate sectors per zone */
|
||||
/* ptr[4] = 0; */
|
||||
/* ptr[5] = 0; */
|
||||
/* alternate tracks per zone */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* alternate tracks per logical unit */
|
||||
/* ptr[8] = 0; */
|
||||
/* ptr[9] = 0; */
|
||||
/* sectors per track */
|
||||
ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
|
||||
ptr[11] = PSEUDO_SPT & 0xff;
|
||||
/* data bytes per physical sector */
|
||||
ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
ptr[13] = (1 << DISK_SHIFT) & 0xff;
|
||||
/* interleave */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 1; */
|
||||
/* track skew factor */
|
||||
/* ptr[16] = 0; */
|
||||
/* ptr[17] = 0; */
|
||||
/* cylinder skew factor */
|
||||
/* ptr[18] = 0; */
|
||||
/* ptr[19] = 0; */
|
||||
/* SSRC, HSEC, RMB, SURF */
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
|
||||
ptr[0] = SMS_GEOMETRY_PAGE;
|
||||
ptr[1] = 24;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
|
||||
/* number of cylinders */
|
||||
ptr[2] = (cyl >> 24) & 0xff;
|
||||
ptr[3] = (cyl >> 16) & 0xff;
|
||||
ptr[4] = cyl & 0xff;
|
||||
/* number of heads */
|
||||
ptr[5] = PSEUDO_HDS;
|
||||
/* starting cylinder- write precompensation */
|
||||
/* ptr[6] = 0; */
|
||||
/* ptr[7] = 0; */
|
||||
/* ptr[8] = 0; */
|
||||
/* starting cylinder- reduced write current */
|
||||
/* ptr[9] = 0; */
|
||||
/* ptr[10] = 0; */
|
||||
/* ptr[11] = 0; */
|
||||
/* drive step rate */
|
||||
/* ptr[12] = 0; */
|
||||
/* ptr[13] = 0; */
|
||||
/* landing zone cylinder */
|
||||
/* ptr[14] = 0; */
|
||||
/* ptr[15] = 0; */
|
||||
/* ptr[16] = 0; */
|
||||
/* RPL */
|
||||
/* ptr[17] = 0; */
|
||||
/* rotational offset */
|
||||
/* ptr[18] = 0; */
|
||||
/* medium rotation rate - 7200 RPM */
|
||||
ptr[20] = 0x1c;
|
||||
ptr[21] = 0x20;
|
||||
}
|
||||
ptr += 26;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
|
||||
ptr[0] = SMS_CACHE_PAGE;
|
||||
ptr[1] = 18;
|
||||
ptr[2] = 1 << 2;
|
||||
ptr += 20;
|
||||
}
|
||||
|
||||
if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
|
||||
ptr[0] = SMS_CONTROL_MODE_PAGE;
|
||||
ptr[1] = 10;
|
||||
if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
|
||||
ptr[3] = 1 << 4; /* unrestricted reordering allowed */
|
||||
ptr[8] = 0x75; /* 30000 ms */
|
||||
ptr[9] = 0x30;
|
||||
}
|
||||
ptr += 12;
|
||||
}
|
||||
nbyte = (char *)ptr - &junk[0];
|
||||
ptr[0] = nbyte - 4;
|
||||
|
||||
if (cdb[0] == MODE_SENSE) {
|
||||
data_len = min(cdb[4], csio->dxfer_len);
|
||||
} else {
|
||||
uint16_t tw = (cdb[7] << 8) | cdb[8];
|
||||
data_len = min(tw, csio->dxfer_len);
|
||||
}
|
||||
data_len = min(data_len, nbyte);
|
||||
if (data_len) {
|
||||
memcpy(csio->data_ptr, junk, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
break;
|
||||
}
|
||||
case READ_6:
|
||||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
case WRITE_6:
|
||||
case WRITE_10:
|
||||
case WRITE_12:
|
||||
case WRITE_16:
|
||||
if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
|
||||
vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (data_len) {
|
||||
if ((cdb[0] & 0xf) == 8) {
|
||||
memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
|
||||
} else {
|
||||
memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
|
||||
}
|
||||
csio->resid = csio->dxfer_len - data_len;
|
||||
} else {
|
||||
csio->resid = csio->dxfer_len;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case READ_CAPACITY:
|
||||
if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
|
||||
vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
|
||||
break;
|
||||
}
|
||||
if (cdb[8] & 0x1) { /* PMI */
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
} else {
|
||||
uint64_t last_blk = DISK_NBLKS - 1;
|
||||
if (last_blk < 0xffffffffULL) {
|
||||
csio->data_ptr[0] = (last_blk >> 24) & 0xff;
|
||||
csio->data_ptr[1] = (last_blk >> 16) & 0xff;
|
||||
csio->data_ptr[2] = (last_blk >> 8) & 0xff;
|
||||
csio->data_ptr[3] = (last_blk) & 0xff;
|
||||
} else {
|
||||
csio->data_ptr[0] = 0xff;
|
||||
csio->data_ptr[1] = 0xff;
|
||||
csio->data_ptr[2] = 0xff;
|
||||
csio->data_ptr[3] = 0xff;
|
||||
}
|
||||
}
|
||||
csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
|
||||
csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
|
||||
csio->data_ptr[6] = ((1 << DISK_SHIFT) >> 8) & 0xff;
|
||||
csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
|
||||
break;
|
||||
|
||||
default:
|
||||
vhba_default_cmd(csio, MAX_LUN, NULL);
|
||||
break;
|
||||
}
|
||||
csio->ccb_h.status &= ~CAM_STATUS_MASK;
|
||||
if (csio->scsi_status != SCSI_STATUS_OK) {
|
||||
csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
|
||||
if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
|
||||
csio->ccb_h.status |= CAM_AUTOSNS_VALID;
|
||||
}
|
||||
} else {
|
||||
csio->scsi_status = SCSI_STATUS_OK;
|
||||
csio->ccb_h.status |= CAM_REQ_CMP;
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
|
||||
}
|
||||
DEV_MODULE(vhba_simple, vhba_modprobe, NULL);
|
431
tools/tools/vhba/vhba.c
Normal file
431
tools/tools/vhba/vhba.c
Normal file
@ -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);
|
||||
}
|
116
tools/tools/vhba/vhba.h
Normal file
116
tools/tools/vhba/vhba.h
Normal file
@ -0,0 +1,116 @@
|
||||
/*-
|
||||
* 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 defines
|
||||
*/
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/taskqueue.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/condvar.h>
|
||||
|
||||
#include <sys/proc.h>
|
||||
|
||||
#include <machine/bus.h>
|
||||
#include <machine/cpu.h>
|
||||
#include <machine/stdarg.h>
|
||||
|
||||
#include <cam/cam.h>
|
||||
#include <cam/cam_debug.h>
|
||||
#include <cam/cam_ccb.h>
|
||||
#include <cam/cam_sim.h>
|
||||
#include <cam/cam_xpt.h>
|
||||
#include <cam/cam_xpt_sim.h>
|
||||
#include <cam/cam_debug.h>
|
||||
#include <cam/scsi/scsi_all.h>
|
||||
#include <cam/scsi/scsi_message.h>
|
||||
|
||||
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/kthread.h>
|
||||
#include <sys/conf.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/ioccom.h>
|
||||
#include <sys/devicestat.h>
|
||||
#include <cam/cam_periph.h>
|
||||
#include <cam/cam_xpt_periph.h>
|
||||
|
||||
#define VHBA_MAXTGT 64
|
||||
#define VHBA_MAXCMDS 256
|
||||
|
||||
typedef struct {
|
||||
struct mtx lock;
|
||||
struct cam_sim * sim;
|
||||
struct cam_devq * devq;
|
||||
TAILQ_HEAD(, ccb_hdr) actv;
|
||||
TAILQ_HEAD(, ccb_hdr) done;
|
||||
void * private;
|
||||
} vhba_softc_t;
|
||||
|
||||
/*
|
||||
* Each different instantiation of a fake HBA needs to
|
||||
* provide these as function entry points. It's responsible
|
||||
* for setting up some thread or regular timeout that will
|
||||
* dequeue things from the actv queue and put done items
|
||||
* on the done queue.
|
||||
*/
|
||||
void vhba_init(vhba_softc_t *);
|
||||
void vhba_fini(vhba_softc_t *);
|
||||
void vhba_kick(vhba_softc_t *);
|
||||
|
||||
/*
|
||||
* Support functions
|
||||
*/
|
||||
void vhba_fill_sense(struct ccb_scsiio *, uint8_t, uint8_t, uint8_t);
|
||||
int vhba_rwparm(uint8_t *, uint64_t *, uint32_t *, uint64_t, uint32_t);
|
||||
void vhba_default_cmd(struct ccb_scsiio *, lun_id_t, uint8_t *);
|
||||
void vhba_set_status(struct ccb_hdr *, cam_status);
|
||||
|
||||
/*
|
||||
* Common module loader function
|
||||
*/
|
||||
int vhba_modprobe(module_t, int, void *);
|
||||
|
||||
/*
|
||||
* retrofits
|
||||
*/
|
||||
#ifndef MODE_SENSE
|
||||
#define MODE_SENSE 0x1a
|
||||
#endif
|
||||
#ifndef SMS_FORMAT_DEVICE_PAGE
|
||||
#define SMS_FORMAT_DEVICE_PAGE 0x03
|
||||
#endif
|
||||
#ifndef SMS_GEOMETRY_PAGE
|
||||
#define SMS_GEOMETRY_PAGE 0x04
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user