t10_raw_if.c revision 36c5fee33fa8b822175d410202aebcf592c8d342
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* []------------------------------------------------------------------[]
* | Implementation of SBC-2 emulation |
* []------------------------------------------------------------------[]
*/
#include <sys/types.h>
#include <sys/asynch.h>
#include <sys/mman.h>
#include <stddef.h>
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include <sys/scsi/generic/sense.h>
#include <sys/scsi/generic/status.h>
#include <sys/scsi/generic/inquiry.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/generic/mode.h>
#include <sys/scsi/generic/dad_mode.h>
#include <sys/scsi/impl/uscsi.h>
#include "t10.h"
#include "t10_spc.h"
#include "utility.h"
#include "target.h"
typedef struct raw_io {
t10_aio_t r_aio;
t10_cmd_t *r_cmd;
uint8_t *r_cdb;
char *r_data;
size_t r_cdb_len,
r_data_len;
uint64_t r_offset,
r_lba;
size_t r_lba_cnt;
uint32_t r_status;
} raw_io_t;
typedef struct raw_params {
uint64_t r_size;
int r_dtype;
} raw_params_t;
typedef enum { RawDataToDevice, RawDataFromDevice, NoData } raw_direction_t;
/*
* Forward declarations
*/
static scsi_cmd_table_t raw_table[];
static void raw_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len);
static void raw_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset,
char *data, size_t data_len);
static void raw_free_io(emul_handle_t id);
static void do_dataout(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len,
size_t opt_data_len);
static raw_io_t *do_datain(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len,
size_t data_len);
static int do_uscsi(t10_cmd_t *cmd, raw_io_t *io, raw_direction_t dir);
static void raw_read_cmplt(emul_handle_t id);
static void raw_write_cmplt(emul_handle_t e);
/*
* []----
* | raw_init_common -- Initialize LU data which is common to all I_T_Ls
* []----
*/
Boolean_t
raw_init_common(t10_lu_common_t *lu)
{
xml_node_t *node = lu->l_root;
char *str;
raw_params_t *r;
if ((r = (raw_params_t *)calloc(1, sizeof (*r))) == NULL)
return (False);
if (xml_find_value_str(node, XML_ELEMENT_SIZE, &str) == True) {
r->r_size = strtoll(str, NULL, 0);
free(str);
}
lu->l_dtype_params = (void *)r;
return (True);
}
void
raw_fini_common(t10_lu_common_t *lu)
{
free(lu->l_dtype_params);
}
/*
* []----
* | raw_init_per -- Initialize per I_T_L information
* []----
*/
void
raw_init_per(t10_lu_impl_t *itl)
{
itl->l_cmd = raw_cmd;
itl->l_data = raw_data;
itl->l_cmd_table = raw_table;
/*
* The first time an I_T nexus connects to a LU it is supposed
* to receive an unit attention upon the first command sent.
*/
itl->l_status = KEY_UNIT_ATTENTION;
itl->l_asc = 0x29;
itl->l_ascq = 0x01;
}
/*ARGSUSED*/
void
raw_fini_per(t10_lu_impl_t *itl)
{
}
/*
* []----
* | raw_cmd -- start a SCSI command
* |
* | This routine is called from within the SAM-3 Task router.
* []----
*/
static void
raw_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
scsi_cmd_table_t *e;
#ifdef FULL_DEBUG
char debug[80];
#endif
e = &cmd->c_lu->l_cmd_table[cdb[0]];
#ifdef FULL_DEBUG
(void) snprintf(debug, sizeof (debug), "RAW%d Cmd %s\n",
cmd->c_lu->l_common->l_num,
e->cmd_name == NULL ? "(no name)" : e->cmd_name);
queue_str(mgmtq, Q_STE_IO, msg_log, debug);
#endif
(*e->cmd_start)(cmd, cdb, cdb_len);
}
/*
* []----
* | raw_data -- Data phase for command.
* |
* | Normally this is only called for the WRITE command. Other commands
* | that have a data in phase will probably be short circuited when
* | we call trans_rqst_dataout() and the data is already available.
* | At least this is true for iSCSI. FC however will need a DataIn phase
* | for commands like MODE SELECT and PGROUT.
* []----
*/
static void
raw_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
size_t data_len)
{
scsi_cmd_table_t *e;
#ifdef FULL_DEBUG
char debug[80];
#endif
e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]];
#ifdef FULL_DEBUG
(void) snprintf(debug, sizeof (debug), "RAW%d Data %s\n",
cmd->c_lu->l_common->l_num, e->cmd_name);
queue_str(mgmtq, Q_STE_IO, msg_log, debug);
#endif
(*e->cmd_data)(cmd, id, offset, data, data_len);
}
/*
* []------------------------------------------------------------------[]
* | The following methods handle special case requirements for the |
* | raw devices. |
* []------------------------------------------------------------------[]
*/
/*
* []----
* | raw_read_tape -- handle SCSI reads from raw tape
* |
* | Need to handle reads from SCSI tape differently than LBA devices
* | for two reasons.
* | (1) The command block for tape reads is different than for
* | LBA devices. There's only a count field.
* | (2) Since tapes have records it's not possible to break up
* | the read operations in the same manner as LBA devices.
* | All of the data must first be read in from the device
* | and then broken up to fit the transport. This is a slower
* | approach, but nobody expects tapes to be quick. If speed
* | is needed a better approach would be to create a virtual
* | tape device and then stage out the data to the device later.
* []----
*/
/*ARGSUSED*/
static void
raw_read_tape(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
size_t req_len,
xfer;
off_t offset = 0;
raw_io_t *io;
Boolean_t last;
req_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
if (cdb[1] & 0x1)
req_len *= 512;
if (((io = do_datain(cmd, cdb, CDB_GROUP0, req_len)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
while (offset < io->r_data_len) {
xfer = min(T10_MAX_OUT(cmd), io->r_data_len - offset);
last = ((offset + xfer) >= io->r_data_len) ? True : False;
if (trans_send_datain(cmd, io->r_data + offset,
xfer, offset, raw_free_io, last, io) == False) {
raw_free_io(io);
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
offset += xfer;
}
}
/*
* []----
* | raw_read -- emulation of SCSI READ command
* []----
*/
/*ARGSUSED*/
static void
raw_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
/*LINTED*/
union scsi_cdb *u = (union scsi_cdb *)cdb;
diskaddr_t addr;
off_t offset = 0;
uint32_t cnt,
min;
raw_io_t *io;
uint64_t err_blkno;
int sense_len;
char debug[80];
raw_params_t *r;
uchar_t addl_sense_len;
if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL)
return;
if (r->r_dtype == DTYPE_SEQUENTIAL) {
raw_read_tape(cmd, cdb, cdb_len);
return;
}
switch (u->scc_cmd) {
case SCMD_READ:
/*
* SBC-2 Revision 16, section 5.5
* Reserve bit checks
*/
if ((cdb[1] & 0xe0) || (cdb[5] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = (diskaddr_t)(uint32_t)GETG0ADDR(u);
cnt = GETG0COUNT(u);
/*
* SBC-2 Revision 16
* Section: 5.5 READ(6) command
* A TRANSFER LENGTH field set to zero specifies
* that 256 logical blocks shall be read.
*/
if (cnt == 0)
cnt = 256;
break;
case SCMD_READ_G1:
/*
* SBC-2 Revision 16, section 5.6
* Reserve bit checks.
*/
if ((cdb[1] & 6) || cdb[6] || (cdb[9] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = (diskaddr_t)(uint32_t)GETG1ADDR(u);
cnt = GETG1COUNT(u);
break;
case SCMD_READ_G4:
/*
* SBC-2 Revision 16, section 5.8
* Reserve bit checks
*/
if ((cdb[1] & 0x6) || (cdb[10] & 6) || cdb[14] ||
(cdb[15] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = GETG4LONGADDR(u);
cnt = GETG4COUNT(u);
break;
default:
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if ((addr + cnt) > r->r_size) {
/*
* request exceed the capacity of disk
* set error block number to capacity + 1
*/
err_blkno = r->r_size + 1;
/*
* XXX: What's SBC-2 say about ASC/ASCQ here. Solaris
* doesn't care about these values when key is set
* to KEY_ILLEGAL_REQUEST.
*/
if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN)
addl_sense_len = INFORMATION_SENSE_DESCR;
else
addl_sense_len = 0;
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len);
spc_sense_info(cmd, err_blkno);
spc_sense_ascq(cmd, 0x21, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
(void) snprintf(debug, sizeof (debug),
"RAW%d READ Illegal sector (0x%llx + 0x%x) > 0x%llx",
cmd->c_lu->l_common->l_num, addr, cnt, r->r_size);
queue_str(mgmtq, Q_STE_ERRS, msg_log, debug);
return;
}
cmd->c_lu->l_cmds_read++;
cmd->c_lu->l_sects_read += cnt;
if (cnt == 0) {
trans_send_complete(cmd, STATUS_GOOD);
return;
}
do {
if ((io = (raw_io_t *)calloc(1, sizeof (*io))) == NULL) {
/*
* We're pretty much dead in the water. If we can't
* allocate memory. It's unlikey we'll be able to
* allocate a sense buffer or queue the command
* up to be sent back to the transport for delivery.
*/
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
min = MIN((cnt * 512) - offset, T10_MAX_OUT(cmd));
io->r_cmd = cmd;
io->r_lba = addr;
io->r_lba_cnt = cnt;
io->r_offset = offset;
io->r_data_len = min;
io->r_aio.a_aio_cmplt = raw_read_cmplt;
io->r_aio.a_id = io;
#ifdef FULL_DEBUG
(void) snprintf(debug, sizeof (debug),
"RAW%d blk 0x%llx, cnt %d, offset 0x%llx, size %d",
cmd->c_lu->l_common->l_num, addr, cnt, io->r_offset, min);
queue_str(mgmtq, Q_STE_IO, msg_log, debug);
#endif
if ((io->r_data = (char *)malloc(min)) == NULL) {
err_blkno = addr + ((offset + 511) / 512);
if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN)
sense_len = INFORMATION_SENSE_DESCR;
else
sense_len = 0;
spc_sense_create(cmd, KEY_HARDWARE_ERROR,
sense_len);
spc_sense_info(cmd, err_blkno);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
trans_aioread(cmd, io->r_data, min, (addr * 512LL) +
(off_t)io->r_offset, (aio_result_t *)io);
offset += min;
} while (offset < (off_t)(cnt * 512));
}
/*
* []----
* | raw_read_cmplt -- Once we have the data, need to send it along.
* []----
*/
static void
raw_read_cmplt(emul_handle_t id)
{
raw_io_t *io = (raw_io_t *)id;
int sense_len;
uint64_t err_blkno;
t10_cmd_t *cmd = io->r_cmd;
Boolean_t last;
if (io->r_aio.a_aio.aio_return != io->r_data_len) {
err_blkno = io->r_lba + ((io->r_offset + 511) / 512);
cmd->c_resid = (io->r_lba_cnt * 512) - io->r_offset;
if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN)
sense_len = INFORMATION_SENSE_DESCR;
else
sense_len = 0;
spc_sense_create(cmd, KEY_HARDWARE_ERROR, sense_len);
spc_sense_info(cmd, err_blkno);
trans_send_complete(cmd, STATUS_CHECK);
raw_free_io(io);
return;
}
last = ((io->r_offset + io->r_data_len) < (io->r_lba_cnt * 512LL)) ?
False : True;
if (trans_send_datain(cmd, io->r_data, io->r_data_len,
io->r_offset, raw_free_io, last, io) == False) {
raw_free_io(io);
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
/*ARGSUSED*/
static void
raw_write_tape(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
size_t request_len,
xfer;
raw_io_t *io;
request_len = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
request_len *= (cdb[1] & 0x1) ? 512 : 1;
if ((io = calloc(1, sizeof (*io))) == NULL) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if ((io->r_data = malloc(request_len)) == NULL) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
io->r_data_len = request_len;
io->r_cmd = cmd;
xfer = min(T10_MAX_OUT(cmd), request_len);
(void) trans_rqst_dataout(cmd, io->r_data, xfer, io->r_offset, io);
}
/*ARGSUSED*/
void
raw_write_tape_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
size_t data_len)
{
raw_io_t *io = (raw_io_t *)id;
size_t xfer;
if ((io->r_offset + data_len) < io->r_data_len) {
io->r_offset += data_len;
xfer = min(T10_MAX_OUT(cmd), io->r_data_len - io->r_offset);
(void) trans_rqst_dataout(cmd, io->r_data + io->r_offset, xfer,
io->r_offset, io);
return;
} else {
trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice));
}
}
/*
* []----
* | raw_write -- implement a SCSI write command.
* []----
*/
/*ARGSUSED*/
static void
raw_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
/*LINTED*/
union scsi_cdb *cdbp = (union scsi_cdb *)cdb;
off_t addr;
uint64_t err_blkno;
uint32_t cnt;
uchar_t addl_sense_len;
char debug[80]; /* debug */
raw_params_t *r;
raw_io_t *io;
size_t max_out;
if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL)
return;
if (r->r_dtype == DTYPE_SEQUENTIAL) {
raw_write_tape(cmd, cdb, cdb_len);
return;
}
switch (cdb[0]) {
case SCMD_WRITE:
/*
* SBC-2 revision 16, section 5.24
* Reserve bit checks.
*/
if ((cdb[1] & 0xe0) || (cdb[5] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = (off_t)cdbp->g0_addr2 << 16 |
(off_t)cdbp->g0_addr1 << 8 | (off_t)cdbp->g0_addr0;
cnt = cdbp->g0_count0;
/*
* SBC-2 Revision 16/Section 5.24 WRITE(6)
* A TRANSFER LENGHT of 0 indicates that 256 logical blocks
* shall be written.
*/
if (cnt == 0)
cnt = 256;
break;
case SCMD_WRITE_G1:
/*
* SBC-2 revision 16, section 5.25
* Reserve bit checks.
*/
if ((cdb[1] & 0x6) || cdb[6] || (cdb[9] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = (off_t)cdbp->g1_addr3 << 24 |
(off_t)cdbp->g1_addr2 << 16 |
(off_t)cdbp->g1_addr1 << 8 |
(off_t)cdbp->g1_addr0;
cnt = cdbp->g1_count1 << 8 | cdbp->g1_count0;
break;
case SCMD_WRITE_G4:
/*
* SBC-2 revision 16, section 5.27
* Reserve bit checks.
*/
if ((cdb[1] & 0x6) || cdb[14] || (cdb[15] & 0x38)) {
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
addr = (off_t)(cdbp->g4_addr3 & 0xff) << 56 |
(off_t)(cdbp->g4_addr2 & 0xff) << 48 |
(off_t)(cdbp->g4_addr1 & 0xff) << 40 |
(off_t)(cdbp->g4_addr0 & 0xff) << 32 |
(off_t)(cdbp->g4_addtl_cdb_data3 & 0xff) << 24 |
(off_t)(cdbp->g4_addtl_cdb_data2 & 0xff) << 16 |
(off_t)(cdbp->g4_addtl_cdb_data1 & 0xff) << 8 |
(off_t)(cdbp->g4_addtl_cdb_data0 & 0xff);
cnt = cdbp->g4_count3 << 24 | cdbp->g4_count2 << 16 |
cdbp->g4_count1 << 8 | cdbp->g4_count0;
break;
default:
queue_str(mgmtq, Q_STE_ERRS, msg_log,
"Unprocessed WRITE type");
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
spc_sense_ascq(cmd, 0x24, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if ((addr < 0) || ((addr + cnt) > r->r_size)) {
/*
* request exceed the capacity of disk
* set error block number to capacity + 1
*/
err_blkno = r->r_size + 1;
/*
* XXX: What's SBC-2 say about ASC/ASCQ here. Solaris
* doesn't care about these values when key is set
* to KEY_ILLEGAL_REQUEST.
*/
if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN)
addl_sense_len = INFORMATION_SENSE_DESCR;
else
addl_sense_len = 0;
spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len);
spc_sense_info(cmd, err_blkno);
spc_sense_ascq(cmd, 0x21, 0x00);
trans_send_complete(cmd, STATUS_CHECK);
(void) snprintf(debug, sizeof (debug),
"RAW%d WRITE Illegal sector (0x%llx + 0x%x) > 0x%llx",
cmd->c_lu->l_common->l_num, addr, cnt, r->r_size);
queue_str(mgmtq, Q_STE_ERRS, msg_log, debug);
return;
}
if (cnt == 0) {
trans_send_complete(cmd, STATUS_GOOD);
return;
}
io = (raw_io_t *)cmd->c_emul_id;
if (io == NULL) {
if ((io = (raw_io_t *)calloc(1, sizeof (*io))) == NULL) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
io->r_lba = addr;
io->r_lba_cnt = cnt;
io->r_cmd = cmd;
io->r_aio.a_aio_cmplt = raw_write_cmplt;
io->r_aio.a_id = io;
/*
* Only update the statistics the first time through
* for this particular command. If the requested transfer
* is larger than the transport can handle this routine
* will be called many times.
*/
cmd->c_lu->l_cmds_write++;
cmd->c_lu->l_sects_write += cnt;
}
/*
* If a transport sets the maximum output value to zero we'll
* just request the entire amount. Otherwise, transfer no more
* than the maximum output or the reminder, whichever is less.
*/
max_out = cmd->c_lu->l_targ->s_maxout;
io->r_data_len = max_out ? MIN(max_out,
(cnt * 512) - io->r_offset) : (cnt * 512);
#ifdef FULL_DEBUG
(void) snprintf(debug, sizeof (debug),
"RAW%d blk 0x%llx, cnt %d, offset 0x%llx, size %d",
cmd->c_lu->l_common->l_num, addr, cnt, io->r_offset,
io->r_data_len);
queue_str(mgmtq, Q_STE_IO, msg_log, debug);
#endif
if ((io->r_data = (char *)malloc(io->r_data_len)) == NULL) {
/*
* NOTE: May need a different ASC code
*/
err_blkno = addr + ((io->r_offset + 511) / 512);
if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN)
addl_sense_len = INFORMATION_SENSE_DESCR;
else
addl_sense_len = 0;
spc_sense_create(cmd, KEY_HARDWARE_ERROR, addl_sense_len);
spc_sense_info(cmd, err_blkno);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if (trans_rqst_dataout(cmd, io->r_data, io->r_data_len, io->r_offset,
io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
/*
* []----
* | raw_write_data -- store a chunk of data from the transport
* []----
*/
/*ARGSUSED*/
void
raw_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
size_t data_len)
{
raw_io_t *io = (raw_io_t *)id;
raw_params_t *r = T10_PARAMS_AREA(cmd);
if (r == NULL)
return;
if (r->r_dtype == DTYPE_SEQUENTIAL) {
raw_write_tape_data(cmd, id, offset, data, data_len);
return;
}
trans_aiowrite(cmd, data, data_len, (io->r_lba * 512) +
(off_t)io->r_offset, (aio_result_t *)io);
}
/*
* []----
* | raw_write_cmplt -- deal with end game of write
* |
* | See if all of the data for this write operation has been dealt
* | with. If so, send a final acknowledgement back to the transport.
* | If not, update the offset, calculate the next transfer size, and
* | start the process again.
* []---
*/
static void
raw_write_cmplt(emul_handle_t e)
{
raw_io_t *io = (raw_io_t *)e;
t10_cmd_t *cmd = io->r_cmd;
if ((io->r_offset + io->r_data_len) < (io->r_lba_cnt * 512)) {
free(io->r_data);
io->r_offset += io->r_data_len;
io->r_data_len = MIN(cmd->c_lu->l_targ->s_maxout,
(io->r_lba_cnt * 512) - io->r_offset);
raw_write(cmd, cmd->c_cdb, cmd->c_cdb_len);
return;
}
raw_free_io(io);
trans_send_complete(cmd, STATUS_GOOD);
}
static void
raw_reserve(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_release(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
int len;
switch (cdb[0]) {
case SCMD_MODE_SENSE:
len = cdb[4];
break;
case SCMD_MODE_SENSE_G1:
len = (cdb[7] << 8) | cdb[8];
break;
}
if (((io = do_datain(cmd, cdb, CDB_GROUP0, len)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static void
raw_tur(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_request_sense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if (((io = do_datain(cmd, cdb, CDB_GROUP0, cdb[4])) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
} else {
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
}
static void
raw_inquiry(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
uint32_t len;
struct scsi_inquiry inq;
raw_params_t *r;
if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL)
return;
len = (cdb[3] << 8) | cdb[4];
if (((io = do_datain(cmd, cdb, CDB_GROUP0, len)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if ((cdb[1] & 1) == 0) {
bcopy(io->r_data, &inq, sizeof (inq));
r->r_dtype = inq.inq_dtype;
}
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static void
raw_mselect(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
int len;
switch (cdb[0]) {
case SCMD_MODE_SELECT:
len = cdb[4];
cdb_len = CDB_GROUP0;
break;
case SCMD_MODE_SELECT_G1:
len = (cdb[7] << 8) | cdb[8];
cdb_len = CDB_GROUP1;
break;
}
do_dataout(cmd, cdb, cdb_len, len);
}
/*ARGSUSED*/
static void
raw_mselect_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
size_t data_len)
{
raw_io_t *io = (raw_io_t *)id;
trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice));
}
static void
raw_startstop(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_rewind(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_send_diag(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
int len;
len = (cdb[3] << 8) | cdb[4];
do_dataout(cmd, cdb, CDB_GROUP0, len);
}
/*ARGSUSED*/
static void
raw_send_diag_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset,
char *data, size_t data_len)
{
raw_io_t *io = (raw_io_t *)id;
trans_send_complete(cmd, do_uscsi(cmd, io, RawDataToDevice));
}
static void
raw_recap(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
struct scsi_capacity cap;
raw_io_t *io;
raw_params_t *r;
if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL)
return;
if (((io = do_datain(cmd, cdb, CDB_GROUP1, sizeof (cap))) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
bcopy(io->r_data, &cap, sizeof (cap));
/*
* Currently there's a bug in ZFS which doesn't report a capacity
* for any of the volumes. This means that when using ZFS the
* administrator must supply the device size.
*/
if (cap.capacity != 0)
r->r_size = cap.capacity;
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static void
raw_service_actiong4(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
uint32_t len;
struct scsi_capacity_16 cap16;
raw_params_t *r;
if ((r = (raw_params_t *)T10_PARAMS_AREA(cmd)) == NULL)
return;
len = (cdb[10] << 24) | (cdb[11] << 16) | (cdb[12] << 8) | cdb[13];
if (((io = do_datain(cmd, cdb, CDB_GROUP4, len)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
bcopy(io->r_data, &cap16, sizeof (cap16));
/*
* Currently there's a bug in ZFS which doesn't report a capacity
* for any of the volumes. This means that when using ZFS the
* administrator must supply the device size.
*/
if (cap16.sc_capacity != 0)
r->r_size = cap16.sc_capacity;
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static void
raw_synccache(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP1, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_write_fm(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
if ((io = do_datain(cmd, cdb, CDB_GROUP0, 0)) == NULL) {
trans_send_complete(cmd, STATUS_CHECK);
} else {
trans_send_complete(cmd, io->r_status);
raw_free_io(io);
}
}
static void
raw_report_tpgs(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
uint32_t len;
len = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9];
if (((io = do_datain(cmd, cdb, CDB_GROUP5, len)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static void
raw_read_limits(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
{
raw_io_t *io;
/*
* spec defines this command to return 6 bytes of data
*/
if (((io = do_datain(cmd, cdb, CDB_GROUP0, 6)) == NULL) ||
(io->r_status != STATUS_GOOD)) {
if (io != NULL)
raw_free_io(io);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if (trans_send_datain(cmd, io->r_data, io->r_data_len, 0,
raw_free_io, True, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
/*
* []------------------------------------------------------------------[]
* | Support related functions for raw devices |
* []------------------------------------------------------------------[]
*/
static void
do_dataout(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, size_t opt_data_len)
{
char *opt_data = NULL;
raw_io_t *io;
if ((io = calloc(1, sizeof (*io))) == NULL) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
if ((opt_data_len != 0) &&
((opt_data = malloc(opt_data_len)) == NULL)) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
return;
}
io->r_cdb = cdb;
io->r_cdb_len = cdb_len;
io->r_data = opt_data;
io->r_data_len = opt_data_len;
if (trans_rqst_dataout(cmd, opt_data, opt_data_len, 0, io) == False) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
trans_send_complete(cmd, STATUS_CHECK);
}
}
static raw_io_t *
do_datain(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len, size_t data_len)
{
raw_io_t *io;
if ((io = calloc(1, sizeof (*io))) == NULL) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
return (NULL);
}
io->r_cdb = cdb;
io->r_cdb_len = cdb_len;
io->r_data_len = data_len;
if ((data_len != 0) && ((io->r_data = malloc(data_len)) == NULL)) {
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
free(io);
return (NULL);
}
(void) do_uscsi(cmd, io, data_len == 0 ? NoData : RawDataFromDevice);
return (io);
}
static int
do_uscsi(t10_cmd_t *cmd, raw_io_t *io, raw_direction_t dir)
{
struct uscsi_cmd u;
uchar_t sense_buf[128];
bzero(&u, sizeof (u));
u.uscsi_cdb = (caddr_t)io->r_cdb;
u.uscsi_cdblen = io->r_cdb_len;
u.uscsi_bufaddr = io->r_data;
u.uscsi_buflen = io->r_data_len;
u.uscsi_flags = ((dir == RawDataToDevice) ? USCSI_WRITE :
(dir == RawDataFromDevice) ? USCSI_READ : 0) | USCSI_RQENABLE;
u.uscsi_rqbuf = (char *)sense_buf;
u.uscsi_rqlen = sizeof (sense_buf);
if ((ioctl(cmd->c_lu->l_common->l_fd, USCSICMD, &u) == 0) &&
(u.uscsi_status == 0)) {
io->r_status = 0;
return (0);
}
queue_prt(mgmtq, Q_STE_ERRS,
"RAW%d LUN%d USCSICMD errno %d, cmd_status %d, rqstatus %d, "
"rqresid %d",
cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, errno,
u.uscsi_status, u.uscsi_rqstatus, u.uscsi_rqresid);
if ((u.uscsi_rqlen - u.uscsi_rqresid) <
sizeof (struct scsi_extended_sense)) {
queue_prt(mgmtq, Q_STE_ERRS,
"RAW%x LUN%d -- No sense data, got=%d, needed=%d",
cmd->c_lu->l_targ->s_targ_num,
cmd->c_lu->l_common->l_num,
u.uscsi_rqlen - u.uscsi_rqresid,
sizeof (struct scsi_extended_sense));
spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
io->r_status = STATUS_CHECK;
return (STATUS_CHECK);
} else {
spc_sense_raw(cmd, sense_buf, u.uscsi_rqlen - u.uscsi_rqresid);
io->r_status = u.uscsi_status;
return (u.uscsi_status);
}
}
static void
raw_free_io(emul_handle_t id)
{
raw_io_t *io = (raw_io_t *)id;
if (io->r_data_len)
free(io->r_data);
free(io);
}
/*
* []----
* | Command table for LBA emulation. This is at the end of the file because
* | it's big and ugly. ;-) To make for fast translation to the appropriate
* | emulation routine we just have a big command table with all 256 possible
* | entries. Most will report STATUS_CHECK, unsupport operation. By doing
* | this we can avoid error checking for command range.
* []----
*/
static scsi_cmd_table_t raw_table[] = {
/* 0x00 -- 0x0f */
{ raw_tur, NULL, NULL, "TEST_UNIT_READY" },
{ raw_rewind, NULL, NULL, "REWIND" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_request_sense, NULL, NULL, "REQUEST_SENSE" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_read_limits, NULL, NULL, "READ_LIMITS" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_read, NULL, NULL, "READ" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_write, raw_write_data, NULL, "WRITE" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x10 -- 0x1f */
{ raw_write_fm, NULL, NULL, "WRITE_FILEMARKS" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_inquiry, NULL, NULL, "INQUIRY" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_mselect, raw_mselect_data, NULL, "MODE_SELECT" },
{ raw_reserve, NULL, NULL, "RESERVE" },
{ raw_release, NULL, NULL, "RELEASE" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_msense, NULL, NULL, "MODE_SENSE" },
{ raw_startstop, NULL, NULL, "START_STOP" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_send_diag, raw_send_diag_data, NULL, "SEND_DIAG" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x20 -- 0x2f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_recap, NULL, NULL, "READ_CAPACITY" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_read, NULL, NULL, "READ_G1" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_write, raw_write_data, NULL, "WRITE_G1" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x30 -- 0x3f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_synccache, NULL, NULL, "SYNC_CACHE" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x40 -- 0x4f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x50 -- 0x5f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_mselect, raw_mselect_data, NULL, "MODE_SELECT" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_msense, NULL, NULL, "MODE_SENSE" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x60 -- 0x6f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x70 -- 0x7f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x80 -- 0x8f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_read, NULL, NULL, "READ_G4" },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_write, raw_write_data, NULL, "WRITE_G4" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0x90 -- 0x9f */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_service_actiong4, NULL, NULL, "SVC_ACTION_G4" },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xa0 - 0xaf */
{ spc_report_luns, NULL, NULL, "REPORT_LUNS" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ raw_report_tpgs, NULL, NULL, "REPORT_TPGS" },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xb0 -- 0xbf */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xc0 -- 0xcf */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xd0 -- 0xdf */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xe0 -- 0xef */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
/* 0xf0 -- 0xff */
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
{ spc_unsupported, NULL, NULL, NULL },
};