Mercurial > illumos > onarm
view 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 source
/* * 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 }, };