diff usr/src/cmd/iscsi/iscsitgtd/t10_ssc.c @ 0:c9caec207d52 b86

Initial porting based on b86
author Koji Uno <koji.uno@sun.com>
date Tue, 02 Jun 2009 18:56:50 +0900
parents
children 1a15d5aaf794
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/iscsi/iscsitgtd/t10_ssc.c	Tue Jun 02 18:56:50 2009 +0900
@@ -0,0 +1,1646 @@
+/*
+ * 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	"@(#)t10_ssc.c	1.6	06/12/16 SMI"
+
+/*
+ * Implementation of SSC-2 emulation
+ */
+
+#include <strings.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <aio.h>
+#include <sys/asynch.h>
+#include <sys/scsi/generic/sense.h>
+#include <sys/scsi/generic/status.h>
+#include <sys/scsi/targets/stdef.h>
+#include <netinet/in.h>
+
+#include "target.h"
+#include "utility.h"
+#include "t10.h"
+#include "t10_spc.h"
+#include "t10_ssc.h"
+
+/*
+ * []----
+ * | Forward declarations
+ * []----
+ */
+static scsi_cmd_table_t ssc_table[];
+static void ssc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len);
+static void ssc_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset,
+    char *data, size_t data_len);
+static void ssc_free(emul_handle_t e);
+static void ssc_write_cmplt(emul_handle_t e);
+static void ssc_read_cmplt(emul_handle_t id);
+static void ssc_setup_tape(ssc_params_t *s, t10_lu_common_t *lu);
+static uint32_t find_last_obj_id(char *file_mark, off_t eod);
+static char *sense_dev_config(ssc_params_t *s, char *data);
+static char *sense_compression(ssc_params_t *s, char *data);
+
+static long ssc_page_size;
+
+/*
+ * []----
+ * | ssc_init_common -- initialize common information that all ITLs will use
+ * []----
+ */
+Boolean_t
+ssc_common_init(t10_lu_common_t *lu)
+{
+	ssc_params_t	*s;
+	ssc_obj_mark_t	mark;
+
+	ssc_page_size = sysconf(_SC_PAGESIZE);
+
+	if (lu->l_mmap == MAP_FAILED)
+		return (False);
+
+	if ((s = (ssc_params_t *)calloc(1, sizeof (*s))) == NULL)
+		return (False);
+
+	s->s_size		= lu->l_size;
+	s->s_fast_write_ack = lu->l_fast_write_ack;
+
+	bcopy(lu->l_mmap, &mark, sizeof (mark));
+	if (mark.som_sig != SSC_OBJ_SIG) {
+		ssc_setup_tape(s, lu);
+	}
+	s->s_cur_fm	= 0;
+	s->s_cur_rec	= sizeof (ssc_obj_mark_t);
+	s->s_prev_rec	= s->s_cur_rec;
+	s->s_state	= lu->l_state;
+
+	lu->l_dtype_params = (void *)s;
+	return (True);
+}
+
+/*
+ * []----
+ * | ssc_fini_common -- free any resources
+ * []----
+ */
+void
+ssc_common_fini(t10_lu_common_t *lu)
+{
+	free(lu->l_dtype_params);
+}
+
+void
+ssc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op)
+{
+	ssc_params_t	*s = (ssc_params_t *)lu->l_dtype_params;
+
+	switch (op) {
+	case CapacityChange:
+		s->s_size = lu->l_size;
+		break;
+
+	case DeviceOnline:
+		s->s_state = lu->l_state;
+	}
+}
+
+/*
+ * []----
+ * | ssc_init_per -- initialize per ITL information
+ * []----
+ */
+void
+ssc_per_init(t10_lu_impl_t *itl)
+{
+	ssc_params_t	*s = (ssc_params_t *)itl->l_common->l_dtype_params;
+
+	if (s->s_state == lu_online)
+		itl->l_cmd	= ssc_cmd;
+	else
+		itl->l_cmd	= spc_cmd_offline;
+	itl->l_data		= ssc_data;
+	itl->l_cmd_table	= ssc_table;
+}
+
+/*
+ * []----
+ * | ssc_fini_per -- release or free any ITL resources
+ * []----
+ */
+/*ARGSUSED*/
+void
+ssc_per_fini(t10_lu_impl_t *itl)
+{
+}
+
+/*
+ * []----
+ * | ssc_cmd -- start a SCSI command
+ * |
+ * | This routine is called from within the SAM-3 Task router.
+ * []----
+ */
+static void
+ssc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	scsi_cmd_table_t	*e;
+
+	e = &cmd->c_lu->l_cmd_table[cdb[0]];
+#ifdef FULL_DEBUG
+	queue_prt(mgmtq, Q_STE_IO, "SSC%x  LUN%d Cmd %s\n",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num,
+	    e->cmd_name == NULL ? "(no name)" : e->cmd_name);
+#endif
+	(*e->cmd_start)(cmd, cdb, cdb_len);
+}
+
+/*
+ * []----
+ * | ssc_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
+ssc_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
+    size_t data_len)
+{
+	scsi_cmd_table_t	*e;
+
+	e = &cmd->c_lu->l_cmd_table[cmd->c_cdb[0]];
+#ifdef FULL_DEBUG
+	queue_prt(mgmtq, Q_STE_IO, "SSC%x  LUN%d Data %s\n",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num,
+	    e->cmd_name);
+#endif
+	(*e->cmd_data)(cmd, id, offset, data, data_len);
+}
+
+/*
+ * []------------------------------------------------------------------[]
+ * | SCSI Streaming Commands - 3					|
+ * | T10/1611-D Revision 01c						|
+ * | The following functions implement the emulation of SSC-3 type	|
+ * | commands.								|
+ * []------------------------------------------------------------------[]
+ */
+
+/*ARGSUSED*/
+static void
+ssc_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_io_t	*io;
+	ssc_params_t	*s		= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+	ssc_obj_mark_t	fm,
+			rm;
+	int		fixed,
+			sili;
+	off_t		offset		= 0;
+	size_t		xfer,
+			req_len;
+	void		*mmap		= cmd->c_lu->l_common->l_mmap;
+	t10_cmd_t	*c;
+
+	fixed	= cdb[1] & 0x01;
+	sili	= cdb[1] & 0x02;
+
+	if (s == NULL)
+		return;
+
+	/*
+	 * Standard error checking.
+	 */
+	if ((sili && fixed) || (cdb[1] & 0xfc) ||
+	    SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	req_len	= (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
+	req_len	*= fixed ? 512 : 1;
+
+	if (req_len == 0) {
+		trans_send_complete(cmd, STATUS_GOOD);
+		return;
+	}
+
+#ifdef FULL_DEBUG
+	queue_prt(mgmtq, Q_STE_IO,
+	    "SSC%x  LUN%d read 0x%x bytes",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, req_len);
+#endif
+
+	bcopy((char *)mmap + s->s_cur_fm, &fm, sizeof (fm));
+	bcopy((char *)mmap + s->s_cur_fm + s->s_cur_rec, &rm, sizeof (rm));
+
+	if (rm.som_sig != SSC_OBJ_SIG) {
+		queue_prt(mgmtq, Q_STE_ERRS,
+		    "SSC%x  LUN%d bad RECORD-MARK",
+		    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num);
+		spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	} else if (rm.som_type != SSC_OBJ_TYPE_RM) {
+		s->s_cur_fm	+= fm.o_fm.size;
+		s->s_cur_rec	= sizeof (ssc_obj_mark_t);
+		s->s_prev_rec	= s->s_cur_rec;
+
+		spc_sense_create(cmd, KEY_NO_SENSE, 0);
+		spc_sense_ascq(cmd, SPC_ASC_FM_DETECTED, SPC_ASCQ_FM_DETECTED);
+		spc_sense_info(cmd, req_len);
+		spc_sense_flags(cmd, SPC_SENSE_FM);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	} else if ((sili == 0) &&
+	    ((rm.o_rm.size - sizeof (ssc_obj_mark_t)) != req_len)) {
+		queue_prt(mgmtq, Q_STE_ERRS,
+		    "SSC%x  LUN%d Wrong size read",
+		    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num);
+
+		s->s_prev_rec	= s->s_cur_rec;
+		s->s_cur_rec	+= rm.o_rm.size;
+
+		spc_sense_create(cmd, KEY_NO_SENSE, 0);
+		spc_sense_flags(cmd, SPC_SENSE_ILI);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	do {
+		xfer = MIN((req_len - offset), T10_MAX_OUT(cmd));
+		if ((offset + xfer) < req_len)
+			c = trans_cmd_dup(cmd);
+		else
+			c = cmd;
+		if ((io = (ssc_io_t *)calloc(1, sizeof (*io))) == NULL) {
+			trans_send_complete(c, STATUS_BUSY);
+			return;
+		}
+
+		io->sio_cmd		= c;
+		io->sio_offset		= offset;
+		io->sio_total		= req_len;
+		io->sio_data_len	= xfer;
+		io->sio_data		= (char *)mmap + s->s_cur_fm +
+		    s->s_cur_rec + sizeof (ssc_obj_mark_t) + offset;
+		io->sio_aio.a_aio.aio_return = xfer;
+
+		ssc_read_cmplt((emul_handle_t)io);
+		offset += xfer;
+	} while (offset < req_len);
+
+	s->s_prev_rec = s->s_cur_rec;
+	s->s_cur_rec += req_len + sizeof (ssc_obj_mark_t);
+}
+
+static void
+ssc_read_cmplt(emul_handle_t id)
+{
+	ssc_io_t	*io	= (ssc_io_t *)id;
+	t10_cmd_t	*cmd	= io->sio_cmd;
+
+	if (io->sio_aio.a_aio.aio_return != io->sio_data_len) {
+		spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	if ((io->sio_offset + io->sio_data_len) < io->sio_total) {
+		if (trans_send_datain(cmd, io->sio_data, io->sio_data_len,
+		    io->sio_offset, ssc_free, False, io) == False) {
+			trans_send_complete(cmd, STATUS_BUSY);
+		}
+	} else {
+		if (trans_send_datain(cmd, io->sio_data, io->sio_data_len,
+		    io->sio_offset, ssc_free, True, io) == False) {
+			trans_send_complete(cmd, STATUS_BUSY);
+		}
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_obj_mark_t	mark;
+	size_t		request_len,
+			max_xfer;
+	int		fixed,
+			prev_id;
+	ssc_io_t	*io;
+	ssc_params_t	*s		= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+
+	if (s == NULL)
+		return;
+
+	if ((cdb[1] & 0xfe) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+	fixed		= cdb[1];
+	request_len	= (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
+	request_len	*= fixed ? 512 : 1;
+
+#ifdef FULL_DEBUG
+	queue_prt(mgmtq, Q_STE_IO,
+	    "SSC%x  LUN%d write %d, fixed %d",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num,
+	    request_len, fixed);
+#endif
+	io = cmd->c_emul_id;
+	if (io == NULL) {
+		if ((io = calloc(1, sizeof (*io))) == NULL) {
+			trans_send_complete(cmd, STATUS_BUSY);
+			return;
+		}
+		io->sio_total	= request_len;
+		io->sio_cmd	= cmd;
+		io->sio_offset	= 0;
+
+		/*
+		 * Writing looses all information after the current
+		 * file-mark. So, check to see if the current file-mark
+		 * size doesn't reflect the end-of-media. If not, update
+		 * it.
+		 */
+		bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm,
+		    &mark, sizeof (mark));
+		if (mark.o_fm.size !=
+		    (s->s_size - sizeof (ssc_obj_mark_t) - s->s_cur_fm)) {
+			mark.o_fm.size = s->s_size - sizeof (ssc_obj_mark_t) -
+			    s->s_cur_fm;
+			bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap +
+			    s->s_cur_fm, sizeof (mark));
+		}
+
+		/*
+		 * End-of-Partition detection
+		 */
+		if ((s->s_cur_rec + request_len) > (mark.o_fm.size)) {
+			spc_sense_create(cmd, KEY_VOLUME_OVERFLOW, 0);
+			spc_sense_ascq(cmd, SPC_ASC_EOP, SPC_ASCQ_EOP);
+			spc_sense_flags(cmd, SPC_SENSE_EOM);
+			trans_send_complete(cmd, STATUS_CHECK);
+			return;
+		}
+
+		if ((s->s_cur_fm == 0) &&
+		    (s->s_cur_rec == sizeof (ssc_obj_mark_t))) {
+
+			/*
+			 * The current position is a BOM. By setting
+			 * the prev_id value to -1 the code below will
+			 * create the first ID with a value of zero
+			 * Which is what the specification requires.
+			 */
+			prev_id = -1;
+
+		} else if (s->s_cur_rec == sizeof (ssc_obj_mark_t)) {
+
+			/*
+			 * If the current position is at the beginning of
+			 * this file-mark use the object ID found in
+			 * the file-mark header. It will have been updated
+			 * from the last object ID in the previous file-mark.
+			 *
+			 * NOTE: We're counting on 'mark' still referring
+			 * to the current file mark here.
+			 */
+			prev_id = mark.o_fm.last_obj_id;
+		} else {
+			bcopy((char *)cmd->c_lu->l_common->l_mmap +
+			    s->s_cur_fm + s->s_prev_rec, &mark, sizeof (mark));
+			prev_id	= mark.o_rm.obj_id;
+		}
+
+		bzero(&mark, sizeof (mark));
+		mark.som_sig		= SSC_OBJ_SIG;
+		mark.som_type		= SSC_OBJ_TYPE_RM;
+		mark.o_rm.size		= request_len +
+		    sizeof (ssc_obj_mark_t);
+		mark.o_rm.obj_id	= prev_id + 1;
+		bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap +
+		    s->s_cur_fm + s->s_cur_rec, sizeof (mark));
+	}
+
+	max_xfer = min(io->sio_total - io->sio_offset,
+	    cmd->c_lu->l_targ->s_maxout);
+	io->sio_aio.a_aio.aio_return = max_xfer;
+	io->sio_data_len = max_xfer;
+	io->sio_data = (char *)cmd->c_lu->l_common->l_mmap +
+	    s->s_cur_fm +  s->s_cur_rec + sizeof (mark) + io->sio_offset;
+
+	if (trans_rqst_dataout(cmd, io->sio_data, io->sio_data_len,
+	    io->sio_offset, io, ssc_free) == False) {
+		trans_send_complete(cmd, STATUS_BUSY);
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data,
+    size_t data_len)
+{
+	ssc_io_t	*io	= (ssc_io_t *)id;
+	ssc_params_t	*s	= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+
+	if (s == NULL)
+		return;
+
+	if (s->s_fast_write_ack == False) {
+		uint64_t	sa;
+		size_t		len;
+
+		/*
+		 * msync requires the address to be page aligned.
+		 * That means we need to account for any alignment
+		 * loss in the len field and access the full page.
+		 */
+		sa = (uint64_t)(intptr_t)data & ~(ssc_page_size - 1);
+		len = (((size_t)data & (ssc_page_size - 1)) +
+		    data_len + ssc_page_size - 1) &
+		    ~(ssc_page_size -1);
+
+		/*
+		 * We only need to worry about sync'ing the blocks
+		 * in the mmap case because if the fast cache isn't
+		 * enabled for AIO the file will be opened with F_SYNC
+		 * which performs the correct action.
+		 */
+		if (msync((char *)(intptr_t)sa, len, MS_SYNC) == -1) {
+			perror("msync");
+			spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+			trans_send_complete(cmd, STATUS_CHECK);
+			return;
+		}
+	}
+	ssc_write_cmplt((emul_handle_t)io);
+}
+
+static void
+ssc_write_cmplt(emul_handle_t e)
+{
+	ssc_io_t	*io	= (ssc_io_t *)e;
+	t10_cmd_t	*cmd	= io->sio_cmd;
+	ssc_params_t	*s	= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+
+	if (s == NULL)
+		return;
+
+	if ((io->sio_offset + io->sio_data_len) < io->sio_total) {
+		io->sio_offset	+= io->sio_data_len;
+		ssc_write(cmd, cmd->c_cdb, cmd->c_cdb_len);
+		return;
+	}
+
+	s->s_prev_rec	= s->s_cur_rec;
+	s->s_cur_rec	+= io->sio_total + sizeof (ssc_obj_mark_t);
+	free(io);
+	trans_send_complete(cmd, STATUS_GOOD);
+}
+
+/*ARGSUSED*/
+static void
+ssc_rewind(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_params_t	*s = (ssc_params_t *)T10_PARAMS_AREA(cmd);
+
+	if (s == NULL)
+		return;
+
+	if ((cdb[1] & ~SSC_REWIND_IMMED) || cdb[2] || cdb[3] || cdb[4] ||
+	    SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	s->s_cur_fm	= 0;
+	s->s_cur_rec	= sizeof (ssc_obj_mark_t);
+	trans_send_complete(cmd, STATUS_GOOD);
+}
+
+/*ARGSUSED*/
+static void
+ssc_read_limits(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	struct read_blklim	*rb;
+	int			min_size	= 512;
+
+	if (cdb[1] || cdb[2] || cdb[3] || cdb[4] ||
+	    SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	if ((rb = (struct read_blklim *)calloc(1, sizeof (*rb))) == NULL) {
+		trans_send_complete(cmd, STATUS_BUSY);
+		return;
+	}
+
+	/*
+	 * maximum block size is set to zero to indicate no maximum block
+	 * limit is specified.
+	 */
+	rb->granularity = 9;	/* 512 block sizes */
+	rb->min_hi	= hibyte(min_size);
+	rb->min_lo	= lobyte(min_size);
+
+	if (trans_send_datain(cmd, (char *)rb, sizeof (*rb), 0, ssc_free,
+	    True, (emul_handle_t)rb) == False) {
+		trans_send_complete(cmd, STATUS_BUSY);
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_space(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	int		code,
+			count;
+	ssc_params_t	*s		= T10_PARAMS_AREA(cmd);
+	ssc_obj_mark_t	mark;
+	t10_lu_common_t	*lu		= cmd->c_lu->l_common;
+
+	if (s == NULL)
+		return;
+
+	if ((cdb[1] & 0xf0) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	code = cdb[1] & 0x0f;
+	count = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
+
+	if ((count == 0) && (code != SSC_SPACE_CODE_END_OF_DATA)) {
+		trans_send_complete(cmd, STATUS_GOOD);
+		return;
+	}
+
+	switch (code) {
+	case SSC_SPACE_CODE_BLOCKS:
+		if (count < 0) {
+			bcopy((char *)lu->l_mmap + s->s_cur_fm + s->s_cur_rec,
+			    &mark, sizeof (mark));
+			if ((mark.som_sig == SSC_OBJ_SIG) &&
+			    (mark.som_type == SSC_OBJ_TYPE_RM)) {
+				count = mark.o_rm.obj_id + count;
+
+				/*
+				 * If the count is still negative it means
+				 * the request is still attempting to go
+				 * beyond the beginning of the file mark.
+				 */
+				if (count < 0) {
+					count *= -1;
+					spc_sense_create(cmd, KEY_NO_SENSE, 0);
+					spc_sense_ascq(cmd,
+					    SPC_ASC_FM_DETECTED,
+					    SPC_ASCQ_FM_DETECTED);
+					spc_sense_info(cmd, count);
+					trans_send_complete(cmd, STATUS_CHECK);
+					return;
+				}
+				s->s_cur_rec = s->s_cur_fm + sizeof (mark);
+			} else {
+				/*
+				 * Something is not right. We'll let the
+				 * processing below determine exactly what
+				 * is wrong. So don't update the record
+				 * mark and sent the count to 1.
+				 */
+				count = 1;
+			}
+		}
+
+		while (count) {
+			bcopy((char *)lu->l_mmap + s->s_cur_fm + s->s_cur_rec,
+			    &mark, sizeof (mark));
+
+			/*
+			 * Something internally bad has happened with
+			 * the marks in the file.
+			 */
+			if (mark.som_sig != SSC_OBJ_SIG) {
+				queue_prt(mgmtq, Q_STE_ERRS,
+				    "SSC%x  LUN%d, bad sig mark: "
+				    "expected=0x%x, got=0x%x",
+				    cmd->c_lu->l_targ->s_targ_num,
+				    cmd->c_lu->l_common->l_num,
+				    SSC_OBJ_SIG, mark.som_sig);
+				spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+				trans_send_complete(cmd, STATUS_CHECK);
+				return;
+			}
+
+			/*
+			 * Hit a filemark. Update the current record if
+			 * we're not at the End-Of-Medium.
+			 */
+			if (mark.som_type == SSC_OBJ_TYPE_FM) {
+				if (mark.o_fm.eom == True) {
+					spc_sense_create(cmd, KEY_MEDIUM_ERROR,
+					    0);
+					spc_sense_ascq(cmd, SPC_ASC_EOP,
+					    SPC_ASCQ_EOP);
+					spc_sense_info(cmd, count);
+					spc_sense_flags(cmd, SPC_SENSE_EOM);
+					trans_send_complete(cmd, STATUS_CHECK);
+					return;
+				}
+				s->s_cur_fm += s->s_cur_rec;
+				s->s_cur_rec += sizeof (mark);
+				spc_sense_create(cmd, KEY_NO_SENSE, 0);
+				spc_sense_ascq(cmd, SPC_ASC_FM_DETECTED,
+				    SPC_ASCQ_FM_DETECTED);
+				spc_sense_info(cmd, count);
+				trans_send_complete(cmd, STATUS_CHECK);
+				return;
+			}
+			s->s_cur_rec += mark.o_rm.size;
+			count--;
+		}
+		trans_send_complete(cmd, STATUS_CHECK);
+		break;
+
+	case SSC_SPACE_CODE_FILEMARKS:
+		if (count < 0) {
+			bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark,
+			    sizeof (mark));
+			if ((mark.som_sig == SSC_OBJ_SIG) &&
+			    (mark.som_type == SSC_OBJ_TYPE_FM)) {
+				count = mark.o_fm.num + count;
+
+				/*
+				 * If the count is still negative it means
+				 * the request is still attempting to go
+				 * beyond the beginning of the file mark.
+				 */
+				if (count < 0) {
+					count *= -1;
+					spc_sense_create(cmd, KEY_NO_SENSE, 0);
+					spc_sense_ascq(cmd,
+					    SPC_ASC_FM_DETECTED,
+					    SPC_ASCQ_FM_DETECTED);
+					spc_sense_info(cmd, count);
+					trans_send_complete(cmd, STATUS_CHECK);
+					return;
+				}
+				s->s_cur_fm = 0;
+				s->s_cur_rec = sizeof (ssc_obj_mark_t);
+			} else {
+				/*
+				 * Something is not right. We'll let the
+				 * processing below determine exactly what
+				 * is wrong. So don't update the record
+				 * mark and sent the count to 1.
+				 */
+				count = 1;
+			}
+		}
+
+		while (count--) {
+			bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark,
+			    sizeof (mark));
+			if (mark.som_sig != SSC_OBJ_SIG) {
+				queue_prt(mgmtq, Q_STE_ERRS,
+				    "SSC%x  LUN%d, bad sig mark: "
+				    "expected=0x%x, got=0x%x",
+				    cmd->c_lu->l_targ->s_targ_num,
+				    cmd->c_lu->l_common->l_num,
+				    SSC_OBJ_SIG, mark.som_sig);
+				spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+				trans_send_complete(cmd, STATUS_CHECK);
+				return;
+			}
+			if (mark.som_type != SSC_OBJ_TYPE_FM) {
+				queue_prt(mgmtq, Q_STE_ERRS,
+				    "SSC%x  LUN%d, bad mark type: "
+				    "expected=0x%x, got=0x%x",
+				    cmd->c_lu->l_targ->s_targ_num,
+				    cmd->c_lu->l_common->l_num,
+				    SSC_OBJ_TYPE_FM, mark.som_type);
+				spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+				trans_send_complete(cmd, STATUS_CHECK);
+				return;
+			}
+			if (mark.o_fm.eom == True) {
+				spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0);
+				spc_sense_ascq(cmd, SPC_ASC_EOP, SPC_ASCQ_EOP);
+				spc_sense_info(cmd, count);
+				spc_sense_flags(cmd, SPC_SENSE_EOM);
+				trans_send_complete(cmd, STATUS_CHECK);
+				return;
+			}
+			s->s_cur_fm += mark.o_fm.size;
+		}
+		trans_send_complete(cmd, STATUS_GOOD);
+		break;
+
+	default:
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+}
+
+/*
+ * []----
+ * | ssc_msense -- MODE SENSE command
+ * |
+ * | This command is part of the SPC set, but is device specific enough
+ * | that it must be emulated in each device type.
+ * []----
+ */
+/*ARGSUSED*/
+static void
+ssc_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_params_t		*s		= T10_PARAMS_AREA(cmd);
+	struct mode_header	*mode_hdr;
+	int			request_len,
+				alloc_len;
+	char			*data,
+				*np;
+
+	/*
+	 * SPC-3 Revision 21c section 6.8
+	 * Reserve bit checks
+	 */
+	if ((cdb[1] & ~8) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	/*
+	 * Zero length causes a simple ack to occur.
+	 */
+	if (cdb[4] == 0) {
+		trans_send_complete(cmd, STATUS_GOOD);
+		return;
+	} else {
+		request_len = cdb[4];
+		alloc_len = max(request_len,
+		    sizeof (*mode_hdr) + MODE_BLK_DESC_LENGTH +
+		    sizeof (ssc_data_compression_t) + sizeof (*mode_hdr) +
+		    MODE_BLK_DESC_LENGTH + sizeof (ssc_device_config_t));
+	}
+
+	queue_prt(mgmtq, Q_STE_NONIO, "SSC%x  LUN%d: MODE_SENSE(0x%x)",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[2]);
+
+	if ((data = memalign(sizeof (void *), alloc_len)) == NULL) {
+		trans_send_complete(cmd, STATUS_BUSY);
+		return;
+	}
+
+	mode_hdr = (struct mode_header *)data;
+
+	switch (cdb[2]) {
+	case MODE_SENSE_COMPRESSION:
+		mode_hdr->length	= sizeof (ssc_data_compression_t);
+		mode_hdr->bdesc_length	= MODE_BLK_DESC_LENGTH;
+		(void) sense_compression(s, data + sizeof (*mode_hdr) +
+		    mode_hdr->bdesc_length);
+		break;
+
+	case MODE_SENSE_DEV_CONFIG:
+		mode_hdr->length	= sizeof (ssc_device_config_t);
+		mode_hdr->bdesc_length	= MODE_BLK_DESC_LENGTH;
+		(void) sense_dev_config(s, data + sizeof (*mode_hdr) +
+		    mode_hdr->bdesc_length);
+		break;
+
+	case MODE_SENSE_SEND_ALL:
+		np = sense_compression(s, data);
+		(void) sense_dev_config(s, np);
+		break;
+
+	case 0x00:
+		bzero(data, alloc_len);
+		break;
+
+	default:
+		queue_prt(mgmtq, Q_STE_ERRS,
+		    "SSC%x  LUN%d: MODE SENSE(0x%x) not handled",
+		    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num,
+		    cdb[2]);
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free,
+	    True, data) == False) {
+		trans_send_complete(cmd, STATUS_BUSY);
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_read_pos(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	int			service_action,
+				request_len,
+				alloc_len;
+	pos_short_form_t	*sf;
+	void			*data;
+	ssc_params_t		*s = (ssc_params_t *)T10_PARAMS_AREA(cmd);
+	ssc_obj_mark_t		mark;
+
+	if (s == NULL)
+		return;
+
+	/*
+	 * Standard reserve bit check
+	 */
+	if ((cdb[1] & 0xc0) || cdb[2] || cdb[3] || cdb[4] || cdb[5] ||
+	    cdb[6] || SAM_CONTROL_BYTE_RESERVED(cdb[9])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	service_action	= cdb[1] & 0x1f;
+	request_len	= (cdb[7] << 8) | cdb[8];
+	switch (service_action) {
+	case SSC_READ_POS_SHORT_FORM:
+		alloc_len = max(request_len, sizeof (*sf));
+		if ((data = memalign(sizeof (void *), alloc_len)) == NULL) {
+			trans_send_complete(cmd, STATUS_BUSY);
+			return;
+		}
+		sf = (pos_short_form_t *)data;
+		bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm,
+		    &mark, sizeof (mark));
+		if ((mark.o_fm.bom == True) &&
+		    (s->s_cur_rec == sizeof (ssc_obj_mark_t)))
+			sf->bop = 1;
+		bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm +
+		    s->s_cur_rec, &mark, sizeof (mark));
+		sf->first_obj[0] = hibyte(hiword(mark.o_rm.obj_id));
+		sf->first_obj[1] = lobyte(hiword(mark.o_rm.obj_id));
+		sf->first_obj[2] = hibyte(loword(mark.o_rm.obj_id));
+		sf->first_obj[3] = lobyte(loword(mark.o_rm.obj_id));
+
+		/*
+		 * We mark the last object to be the same as the first
+		 * object which indicates that nothing is currently
+		 * buffered.
+		 */
+		sf->last_obj[0] = sf->first_obj[0];
+		sf->last_obj[1] = sf->first_obj[1];
+		sf->last_obj[2] = sf->first_obj[2];
+		sf->last_obj[3] = sf->first_obj[3];
+
+		break;
+
+	case SSC_READ_POS_LONG_FORM:
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+
+	default:
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free,
+	    True, data) == False) {
+		trans_send_complete(cmd, STATUS_BUSY);
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_rpt_density(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_density_t		*d;
+	ssc_density_media_t	*dm;
+	ssc_params_t		*s	= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+	size_t			cap	= s->s_size / (1024 * 1024);
+	int			medium_type,
+				request_len,
+				alloc_len;
+	void			*data;
+
+	if (s == NULL)
+		return;
+
+	if ((cdb[1] & 0xfc) || cdb[2] || cdb[3] || cdb[4] || cdb[5] ||
+	    cdb[6] || SAM_CONTROL_BYTE_RESERVED(cdb[9])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	medium_type = cdb[1] & 0x2;
+	request_len = (cdb[7] << 8) | cdb[8];
+	alloc_len = max(request_len, max(sizeof (*d), sizeof (*dm)));
+	if ((data = memalign(sizeof (void *), alloc_len)) == NULL) {
+		trans_send_complete(cmd, STATUS_BUSY);
+		return;
+	}
+	if (medium_type == 0) {
+		d		= (ssc_density_t *)data;
+		d->d_hdr.len	= htons(sizeof (*d) -
+		    sizeof (ssc_density_header_t));
+		d->d_prim_code	= 1;
+		d->d_wrtok	= 1;
+		d->d_deflt	= 1;
+		d->d_tracks[1]	= 1;
+		d->d_capacity[0]	= hibyte(hiword(cap));
+		d->d_capacity[1]	= lobyte(hiword(cap));
+		d->d_capacity[2]	= hibyte(loword(cap));
+		d->d_capacity[3]	= lobyte(loword(cap));
+		bcopy(cmd->c_lu->l_common->l_vid, d->d_organization,
+		    min(sizeof (d->d_organization),
+		    strlen(cmd->c_lu->l_common->l_vid)));
+		bcopy(cmd->c_lu->l_common->l_pid, d->d_description,
+		    min(sizeof (d->d_description),
+		    strlen(cmd->c_lu->l_common->l_pid)));
+	} else {
+		dm		= (ssc_density_media_t *)data;
+		dm->d_hdr.len	= htons(sizeof (*d) -
+		    sizeof (ssc_density_header_t));
+		bcopy(cmd->c_lu->l_common->l_vid, d->d_organization,
+		    min(sizeof (d->d_organization),
+		    strlen(cmd->c_lu->l_common->l_vid)));
+		bcopy(cmd->c_lu->l_common->l_pid, dm->d_description,
+		    min(sizeof (dm->d_description),
+		    strlen(cmd->c_lu->l_common->l_pid)));
+	}
+
+	if (trans_send_datain(cmd, (char *)data, request_len, 0, ssc_free,
+	    True, (emul_handle_t)data) == False) {
+		trans_send_complete(cmd, STATUS_BUSY);
+	}
+}
+
+/*ARGSUSED*/
+static void
+ssc_write_fm(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	int		marks_requested;
+	off_t		next_size;
+	ssc_params_t	*s		= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+	ssc_obj_mark_t	mark_fm;
+
+	if (s == NULL)
+		return;
+
+	if ((cdb[1] & 0xfc) || SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	marks_requested = (cdb[2] << 16) | (cdb[3] << 8) | cdb[4];
+	while (marks_requested--) {
+		/*
+		 * Get the last file-mark and update it's size.
+		 */
+		bcopy((char *)cmd->c_lu->l_common->l_mmap + s->s_cur_fm,
+		    &mark_fm, sizeof (mark_fm));
+		if (mark_fm.som_sig != SSC_OBJ_SIG) {
+			spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0);
+			trans_send_complete(cmd, STATUS_CHECK);
+			return;
+		}
+		next_size		= mark_fm.o_fm.size - s->s_cur_rec;
+		mark_fm.o_fm.size	= s->s_cur_rec;
+		bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap +
+		    s->s_cur_fm, sizeof (mark_fm));
+
+		/*
+		 * Write new mark and update internal location of mark.
+		 */
+		mark_fm.o_fm.last_obj_id =
+		    find_last_obj_id((char *)cmd->c_lu->l_common->l_mmap +
+		    s->s_cur_fm, s->s_cur_rec);
+		mark_fm.o_fm.bom	= False;
+		mark_fm.o_fm.size	= next_size;
+		s->s_cur_fm		+= s->s_cur_rec;
+		s->s_cur_rec		= sizeof (ssc_obj_mark_t);
+		s->s_prev_rec		= s->s_cur_rec;
+		bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap +
+		    s->s_cur_fm, sizeof (mark_fm));
+		mark_fm.o_fm.size	= 0;
+		bcopy(&mark_fm, (char *)cmd->c_lu->l_common->l_mmap +
+		    s->s_cur_fm + s->s_cur_rec, sizeof (mark_fm));
+	}
+	trans_send_complete(cmd, STATUS_GOOD);
+}
+
+/*ARGSUSED*/
+static void
+ssc_locate(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+}
+
+/*ARGSUSED*/
+static void
+ssc_erase(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_obj_mark_t	mark;
+	ssc_params_t	*s	= (ssc_params_t *)T10_PARAMS_AREA(cmd);
+
+	if (s == NULL)
+		return;
+
+	bzero(&mark, sizeof (mark));
+	mark.som_sig	= SSC_OBJ_SIG;
+	mark.som_type	= SSC_OBJ_TYPE_FM;
+	mark.o_fm.bom	= True;
+	mark.o_fm.eom	= False;
+	mark.o_fm.size	= s->s_size - sizeof (mark);
+
+	bcopy(&mark, (char *)cmd->c_lu->l_common->l_mmap, sizeof (mark));
+}
+
+/*ARGSUSED*/
+static void
+ssc_load(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	ssc_params_t	*s	= T10_PARAMS_AREA(cmd);
+	t10_lu_common_t	*lu	= cmd->c_lu->l_common;
+	ssc_obj_mark_t	mark;
+
+	/*
+	 * SSC-3, revision 02, section 7.2 LOAD/UNLOAD command
+	 * Check for various reserve bits.
+	 */
+	if ((cdb[1] & ~SSC_LOAD_CMD_IMMED) || cdb[2] || cdb[3] ||
+	    (cdb[4] & ~(SSC_LOAD_CMD_LOAD | SSC_LOAD_CMD_RETEN |
+	    SSC_LOAD_CMD_EOT | SSC_LOAD_CMD_HOLD)) ||
+	    SAM_CONTROL_BYTE_RESERVED(cdb[5])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	queue_prt(mgmtq, Q_STE_NONIO, "SSC%x  LUN%d load bits 0x%x",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[4]);
+
+	/*
+	 * There are four possible actions based on the LOAD and HOLD
+	 * bits.
+	 */
+	switch (cdb[4] & (SSC_LOAD_CMD_LOAD|SSC_LOAD_CMD_HOLD)) {
+	case SSC_LOAD_CMD_LOAD|SSC_LOAD_CMD_HOLD:
+		/*
+		 * Load the media into the system if not already done
+		 * so, but do not position tape. The EOT and RETEN should
+		 * be zero. Since this emulation currently is always available
+		 * we're good to go.
+		 */
+		break;
+
+	case SSC_LOAD_CMD_LOAD:
+		/*
+		 * Without the HOLD bit the tape should be positioned at
+		 * the beginning of partition 0.
+		 */
+		s->s_cur_fm	= 0;
+		s->s_cur_rec	= sizeof (ssc_obj_mark_t);
+		break;
+
+	case SSC_LOAD_CMD_HOLD:
+		/*
+		 * Without the LOAD bit we leave the tape online, but look
+		 * at the RETEN and EOT bits. The RETEN doesn't mean anything
+		 * for this virtual tape, but we can reposition to EOT.
+		 */
+		if (cdb[4] & SSC_LOAD_CMD_EOT) {
+			/*CONSTANTCONDITION*/
+			while (1) {
+				bcopy((char *)lu->l_mmap + s->s_cur_fm, &mark,
+				    sizeof (mark));
+				if (mark.som_sig != SSC_OBJ_SIG) {
+					queue_prt(mgmtq, Q_STE_ERRS,
+					    "SSC%x  LUN%d, bad sig mark: "
+					    "expected=0x%x, got=0x%x",
+					    cmd->c_lu->l_targ->s_targ_num,
+					    cmd->c_lu->l_common->l_num,
+					    SSC_OBJ_SIG, mark.som_sig);
+					spc_sense_create(cmd,
+					    KEY_MEDIUM_ERROR, 0);
+					trans_send_complete(cmd, STATUS_CHECK);
+					return;
+				}
+				if (mark.som_type != SSC_OBJ_TYPE_FM) {
+					queue_prt(mgmtq, Q_STE_ERRS,
+					    "SSC%x  LUN%d, bad mark type: "
+					    "expected=0x%x, got=0x%x",
+					    cmd->c_lu->l_targ->s_targ_num,
+					    cmd->c_lu->l_common->l_num,
+					    SSC_OBJ_TYPE_FM, mark.som_type);
+					spc_sense_create(cmd,
+					    KEY_MEDIUM_ERROR, 0);
+					trans_send_complete(cmd, STATUS_CHECK);
+					return;
+				}
+				if (mark.o_fm.eom == True)
+					break;
+				s->s_cur_fm += mark.o_fm.size;
+			}
+		}
+		break;
+
+	case 0:
+		/*
+		 * Unload the current tape.
+		 */
+
+		break;
+	}
+	trans_send_complete(cmd, STATUS_GOOD);
+}
+
+/*ARGSUSED*/
+static void
+ssc_logsense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len)
+{
+	spc_log_supported_pages_t	p;
+	void				*v;
+
+	/*
+	 * Reserve bit checks
+	 */
+	if ((cdb[1] & ~(SSC_LOG_SP|SSC_LOG_PPC)) || cdb[3] || cdb[4] ||
+	    SAM_CONTROL_BYTE_RESERVED(cdb[9])) {
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0);
+		trans_send_complete(cmd, STATUS_CHECK);
+		return;
+	}
+
+	queue_prt(mgmtq, Q_STE_ERRS, "SSC%x  LUN%d page code 0x%x",
+	    cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cdb[2]);
+
+	switch (cdb[2] & SPC_LOG_PAGE_MASK) {
+	case 0:
+		if ((v = malloc(sizeof (p))) == NULL) {
+			trans_send_complete(cmd, STATUS_BUSY);
+			return;
+		}
+		bzero(&p, sizeof (p));
+		p.length[0] = 1;
+		bcopy(&p, v, sizeof (p));
+		if (trans_send_datain(cmd, (char *)v, sizeof (p), 0, free,
+		    True, (emul_handle_t)v) == False) {
+			trans_send_complete(cmd, STATUS_BUSY);
+		}
+		break;
+
+	default:
+		spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0);
+		spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0);
+		trans_send_complete(cmd, STATUS_CHECK);
+		break;
+	}
+}
+
+/*
+ * []------------------------------------------------------------------[]
+ * | Support functions for the SSC command set				|
+ * []------------------------------------------------------------------[]
+ */
+static uint32_t
+find_last_obj_id(char *file_mark, off_t eod)
+{
+	ssc_obj_mark_t	rm;
+	off_t		offset	= sizeof (ssc_obj_mark_t);
+	uint32_t	obj_id	= 0xffffffff;
+
+	bcopy(file_mark + offset, &rm, sizeof (rm));
+	while (rm.som_type == SSC_OBJ_TYPE_RM) {
+		obj_id = rm.o_rm.obj_id;
+		offset += rm.o_rm.size;
+		if (offset >= eod)
+			break;
+		bcopy(file_mark + offset, &rm, sizeof (rm));
+	}
+	return (obj_id);
+}
+
+static void
+ssc_setup_tape(ssc_params_t *s, t10_lu_common_t *lu)
+{
+	ssc_obj_mark_t	mark;
+
+	/*
+	 * Add Begin-of-Partition marker
+	 */
+	bzero(&mark, sizeof (mark));
+	mark.som_sig	= SSC_OBJ_SIG;
+	mark.som_type	= SSC_OBJ_TYPE_FM;
+	mark.o_fm.bom	= True;
+	mark.o_fm.eom	= False;
+	mark.o_fm.size	= s->s_size - sizeof (mark);
+	bcopy(&mark, lu->l_mmap, sizeof (mark));
+
+	/*
+	 * Add first file-record with a zero size.
+	 */
+	bzero(&mark, sizeof (mark));
+	mark.som_sig		= SSC_OBJ_SIG;
+	mark.som_type		= SSC_OBJ_TYPE_RM;
+	mark.o_rm.size		= 0;
+	mark.o_rm.obj_id	= 0xffffffff;
+	bcopy(&mark, (char *)lu->l_mmap + sizeof (ssc_obj_mark_t),
+	    sizeof (mark));
+
+	/*
+	 * Add End-of-Partiton marker
+	 */
+	bzero(&mark, sizeof (mark));
+	mark.som_sig	= SSC_OBJ_SIG;
+	mark.som_type	= SSC_OBJ_TYPE_FM;
+	mark.o_fm.bom	= False;
+	mark.o_fm.eom	= True;
+	mark.o_fm.size	= 0;
+	bcopy(&mark, (char *)lu->l_mmap + s->s_size - sizeof (mark),
+	    sizeof (mark));
+}
+
+static char *
+sense_compression(ssc_params_t *s, char *data)
+{
+	ssc_data_compression_t	d;
+
+	bzero(&d, sizeof (d));
+	d.mode_page.code	= MODE_SENSE_COMPRESSION;
+	d.mode_page.length	= sizeof (d) - sizeof (struct mode_page);
+	bcopy(&d, data, sizeof (d));
+
+	return (data + sizeof (d));
+}
+
+static char *
+sense_dev_config(ssc_params_t *s, char *data)
+{
+	ssc_device_config_t	d;
+
+	bzero(&d, sizeof (d));
+	d.mode_page.code	= MODE_SENSE_DEV_CONFIG;
+	d.mode_page.length	= sizeof (d) - sizeof (struct mode_page);
+	d.lois			= 1;
+	d.socf			= 1;
+	d.rewind_on_reset	= 1;
+	bcopy(&d, data, sizeof (d));
+
+	return (data + sizeof (d));
+}
+
+static void
+ssc_free(emul_handle_t e)
+{
+	free(e);
+}
+
+/*
+ * []----
+ * | Command table for SSC 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 ssc_table[] = {
+	/* 0x00 -- 0x0f */
+	{ spc_tur,		NULL,	NULL,		"TEST_UNIT_READY" },
+	{ ssc_rewind,	NULL,	NULL,			"REWIND"},
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_request_sense,	NULL,	NULL,		"REQUEST_SENSE" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_read_limits,	NULL,	NULL,		"READ BLOCK LIMITS"},
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_read, NULL, ssc_read_cmplt,		"READ(6)" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_write, ssc_write_data, ssc_write_cmplt,	"WRITE(6)" },
+	{ 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 */
+	{ ssc_write_fm,	NULL,	NULL,			"WRITE_FILEMARKS(6)"},
+	{ ssc_space,	NULL,	NULL,			"SPACE(6)"},
+	{ spc_inquiry, NULL, NULL,			"INQUIRY" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_mselect, spc_mselect_data, NULL,		"MODE_SELECT(6)" },
+	{ spc_request_sense,		NULL,	NULL,	"RESERVE" },
+	{ spc_request_sense,		NULL,	NULL,	"RELEASE" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_erase,	NULL,	NULL,			"ERASE(6)"},
+	{ ssc_msense,		NULL,	NULL,		"MODE_SENSE(6)" },
+	{ ssc_load,	NULL,	NULL,		"LOAD_UNLOAD" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_send_diag,	NULL,	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 },
+	{ spc_unsupported,		NULL,	NULL,	"READ_CAPACITY" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_read, NULL, ssc_read_cmplt,		"READ_G1" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_write, ssc_write_data, ssc_write_cmplt,	"WRITE_G1" },
+	{ ssc_locate,	NULL,	NULL,			"LOCATE(10)"},
+	{ 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 },
+	{ ssc_read_pos,	NULL,	NULL,			"READ POSITION"},
+	{ spc_unsupported,	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 },
+	{ ssc_rpt_density,	NULL,	NULL,	"REPORT DENSITY SUPPORT"},
+	{ 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 },
+	{ ssc_logsense,		NULL,	NULL,		"LOG SENSE" },
+	{ 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 },
+	{ 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 },
+
+	/* 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 */
+	{ ssc_write_fm,	NULL,	NULL,			"WRITE_FILEMARKS(16)"},
+	{ 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 },
+	{ ssc_read, NULL, ssc_read_cmplt,		"READ_G4" },
+	{ spc_unsupported,	NULL,	NULL,	NULL },
+	{ ssc_write, ssc_write_data, ssc_write_cmplt,	"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 },
+	{ ssc_locate,	NULL,	NULL,			"LOCATE(16)"},
+	{ ssc_erase,	NULL,	NULL,			"ERASE" },
+	{ 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,		"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 },
+	{ spc_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 },
+};