/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * []------------------------------------------------------------------[] * | Implementation of SBC-2 emulation | * []------------------------------------------------------------------[] */ #include <sys/types.h> #include <aio.h> #include <sys/asynch.h> #include <sys/mman.h> #include <stddef.h> #include <strings.h> #include <unistd.h> #include <assert.h> #include <sys/scsi/generic/sense.h> #include <sys/scsi/generic/status.h> #include <sys/scsi/generic/inquiry.h> #include <sys/scsi/generic/commands.h> #include <sys/scsi/generic/mode.h> #include <sys/scsi/generic/dad_mode.h> #include "t10.h" #include "t10_spc.h" #include "t10_spc_pr.h" #include "t10_sbc.h" #include "utility.h" /* * External declarations */ void sbc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); void spc_cmd_reserve6(t10_cmd_t *, uint8_t *, size_t); void spc_cmd_release6(t10_cmd_t *, uint8_t *, size_t); void spc_cmd_pr_in(t10_cmd_t *, uint8_t *, size_t); void spc_cmd_pr_out(t10_cmd_t *, uint8_t *, size_t); void spc_cmd_pr_out_data(t10_cmd_t *, emul_handle_t, size_t, char *, size_t); void spc_pr_read(t10_cmd_t *); Boolean_t spc_pgr_check(t10_cmd_t *, uint8_t *); Boolean_t spc_npr_check(t10_cmd_t *, uint8_t *); /* * Forward declarations */ static int sbc_mmap_overlap(const void *v1, const void *v2); static void sbc_overlap_store(disk_io_t *io); static void sbc_overlap_free(disk_io_t *io); static void sbc_overlap_check(disk_io_t *io); static void sbc_overlap_flush(disk_params_t *d); static void sbc_data(t10_cmd_t *cmd, emul_handle_t e, size_t offset, char *data, size_t data_len); static disk_io_t *sbc_io_alloc(t10_cmd_t *c); static void sbc_io_free(emul_handle_t e); static void sbc_read_cmplt(emul_handle_t e); static void sbc_write_cmplt(emul_handle_t e); static void sbc_read_capacity16(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len); static char *sense_page3(disk_params_t *d, char *buf, uint8_t pc); static char *sense_page4(disk_params_t *d, char *buf, uint8_t pc); static char *sense_cache(disk_params_t *d, char *buf, uint8_t pc); static char *sense_mode_control(t10_lu_impl_t *lu, char *buf, uint8_t pc); static char *sense_info_ctrl(char *buf, uint8_t pc); static scsi_cmd_table_t lba_table[]; static long sbc_page_size; /* * []---- * | sbc_init_common -- Initialize LU data which is common to all I_T_Ls * []---- */ Boolean_t sbc_common_init(t10_lu_common_t *lu) { disk_params_t *d; tgt_node_t *node = lu->l_root; sbc_page_size = sysconf(_SC_PAGESIZE); if ((d = (disk_params_t *)calloc(1, sizeof (*d))) == NULL) return (False); (void) tgt_find_value_int(node, XML_ELEMENT_BPS, (int *)&d->d_bytes_sect); (void) tgt_find_value_int(node, XML_ELEMENT_HEADS, (int *)&d->d_heads); (void) tgt_find_value_int(node, XML_ELEMENT_SPT, (int *)&d->d_spt); (void) tgt_find_value_int(node, XML_ELEMENT_CYLINDERS, (int *)&d->d_cyl); (void) tgt_find_value_int(node, XML_ELEMENT_RPM, (int *)&d->d_rpm); (void) tgt_find_value_int(node, XML_ELEMENT_INTERLEAVE, (int *)&d->d_interleave); d->d_fast_write = lu->l_fast_write_ack; d->d_size = lu->l_size / (uint64_t)d->d_bytes_sect; d->d_state = lu->l_state; avl_create(&d->d_mmap_overlaps, sbc_mmap_overlap, sizeof (disk_io_t), offsetof(disk_io_t, da_mmap_overlap)); (void) pthread_mutex_init(&d->d_mutex, NULL); (void) pthread_cond_init(&d->d_mmap_cond, NULL); (void) pthread_cond_init(&d->d_io_cond, NULL); if ((d->d_io_reserved = (disk_io_t *)calloc(1, sizeof (disk_io_t))) == NULL) { free(d); return (False); } lu->l_dtype_params = (void *)d; return (True); } void sbc_common_fini(t10_lu_common_t *lu) { disk_params_t *d = lu->l_dtype_params; sbc_overlap_flush(d); avl_destroy(&d->d_mmap_overlaps); free(d->d_io_reserved); free(lu->l_dtype_params); } void sbc_task_mgmt(t10_lu_common_t *lu, TaskOp_t op) { disk_params_t *d = (disk_params_t *)lu->l_dtype_params; switch (op) { case CapacityChange: d->d_size = lu->l_size / (uint64_t)d->d_bytes_sect; break; case DeviceOnline: d->d_state = lu->l_state; break; } } /* * []---- * | sbc_init_per -- Initialize per I_T_L information * []---- */ void sbc_per_init(t10_lu_impl_t *itl) { disk_params_t *d = (disk_params_t *)itl->l_common->l_dtype_params; if (d->d_state == lu_online) { itl->l_cmd = sbc_cmd; itl->l_pgr_read = False; /* Look for PGR data */ } else itl->l_cmd = spc_cmd_offline; itl->l_data = sbc_data; itl->l_cmd_table = lba_table; } void sbc_per_fini(t10_lu_impl_t *itl) { } /* * []---- * | sbc_cmd -- start a SCSI command * | * | This routine is called from within the SAM-3 Task router. * []---- */ void sbc_cmd(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { scsi_cmd_table_t *e; /* * Determine if there is persistent data for this I_T_L Nexus */ if (cmd->c_lu->l_pgr_read == False) { spc_pr_read(cmd); cmd->c_lu->l_pgr_read = True; } e = &cmd->c_lu->l_cmd_table[cdb[0]]; #ifdef FULL_DEBUG queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d Cmd %s id=%p\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, cmd->c_trans_id); #endif (*e->cmd_start)(cmd, cdb, cdb_len); } /* * []---- * | sbc_cmd_reserve -- Run commands when another I_T_L has a reservation * []---- */ void sbc_cmd_reserved(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { disk_params_t *p = (disk_params_t *)T10_PARAMS_AREA(cmd); sbc_reserve_t *res = &p->d_sbc_reserve; Boolean_t conflict = False; /* * SPC-3, revision 23, Table 31 * SPC commands that are allowed in the presence of various reservations */ switch (cdb[0]) { case SCMD_INQUIRY: case SCMD_LOG_SENSE_G1: case SCMD_REPORT_LUNS: case SCMD_REQUEST_SENSE: case SCMD_MAINTENANCE_IN: /* REPORT TARGET PORT GROUPS (A3h/0Ah) */ case SCMD_SVC_ACTION_IN_G5: /* READ MEDIA SERIAL NUMBER (ABh) */ break; default: (void) pthread_rwlock_rdlock(&res->res_rwlock); switch (res->res_type) { case RT_NONE: /* conflict = False; */ break; case RT_PGR: conflict = spc_pgr_check(cmd, cdb); break; case RT_NPR: conflict = spc_npr_check(cmd, cdb); break; default: conflict = True; break; } (void) pthread_rwlock_unlock(&res->res_rwlock); } queue_prt(mgmtq, Q_PR_IO, "PGR%x LUN%d CDB:%s - sbc_cmd_reserved(%s:%s)\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, cmd->c_lu->l_cmd_table[cmd->c_cdb[0]].cmd_name == NULL ? "(no name)" : cmd->c_lu->l_cmd_table[cmd->c_cdb[0]].cmd_name, res->res_type == RT_PGR ? "PGR" : res->res_type == RT_NPR ? "NPR" : res->res_type == RT_NONE ? "" : "unknown", conflict ? "Conflict" : "Allowed"); /* * If no conflict at this point, allow command */ if (conflict == False) { sbc_cmd(cmd, cdb, cdb_len); } else { trans_send_complete(cmd, STATUS_RESERVATION_CONFLICT); } } /* * []---- * | sbc_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 sbc_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, "SBC%x LUN%d Data %s id=%p\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, e->cmd_name, cmd->c_trans_id); #endif if (*e->cmd_data != NULL) (*e->cmd_data)(cmd, id, offset, data, data_len); } /* * []------------------------------------------------------------------[] * | SCSI Block Commands - 2 | * | T10/1417-D | * | The following functions implement the emulation of SBC-2 type | * | commands. | * []------------------------------------------------------------------[] */ /* * []---- * | sbc_read -- emulation of SCSI READ command * []---- */ /*ARGSUSED*/ static void sbc_read(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { /*LINTED*/ union scsi_cdb *u = (union scsi_cdb *)cdb; diskaddr_t addr; off_t offset = 0; uint32_t cnt; uint32_t min; disk_io_t *io; void *mmap_data = T10_MMAP_AREA(cmd); uint64_t err_blkno; disk_params_t *d; uchar_t addl_sense_len; t10_cmd_t *c; iscsi_cmd_t *iscsi = (iscsi_cmd_t *)T10_TRANS_ID(cmd); if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } switch (u->scc_cmd) { case SCMD_READ: /* * SBC-2 Revision 16, section 5.5 * Reserve bit checks */ if ((cdb[1] & 0xe0) || 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; } addr = (diskaddr_t)(uint32_t)GETG0ADDR(u); cnt = GETG0COUNT(u); /* * SBC-2 Revision 16 * Section: 5.5 READ(6) command * A TRANSFER LENGTH field set to zero specifies * that 256 logical blocks shall be read. */ if (cnt == 0) cnt = 256; break; case SCMD_READ_G1: /* * SBC-2 Revision 16, section 5.6 * Reserve bit checks. */ if ((cdb[1] & 6) || cdb[6] || 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; } addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); cnt = GETG1COUNT(u); break; case SCMD_READ_G4: /* * SBC-2 Revision 16, section 5.8 * Reserve bit checks */ if ((cdb[1] & 0x6) || cdb[14] || SAM_CONTROL_BYTE_RESERVED(cdb[15])) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } addr = GETG4LONGADDR(u); cnt = GETG4COUNT(u); break; default: spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); trans_send_complete(cmd, STATUS_CHECK); return; } if ((addr + cnt) > d->d_size) { if (addr > d->d_size) err_blkno = addr; else err_blkno = d->d_size; if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) addl_sense_len = INFORMATION_SENSE_DESCR; else addl_sense_len = 0; spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); spc_sense_info(cmd, err_blkno); spc_sense_ascq(cmd, SPC_ASC_BLOCK_RANGE, SPC_ASCQ_BLOCK_RANGE); trans_send_complete(cmd, STATUS_CHECK); queue_prt(mgmtq, Q_STE_ERRS, "SBC%x LUN%d READ Illegal sector " "(0x%llx + 0x%x) > 0x%ullx\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr, cnt, d->d_size); return; } cmd->c_lu->l_cmds_read++; cmd->c_lu->l_sects_read += cnt; if (cnt == 0) { trans_send_complete(cmd, STATUS_GOOD); return; } do { min = MIN((cnt * 512) - offset, T10_MAX_OUT(cmd)); if ((offset + min) < (cnt * 512LL)) { c = trans_cmd_dup(cmd); /* dup failed, just finish the original command */ if (c == NULL) { c = cmd; offset = (cnt * 512); } } else { c = cmd; } (void) pthread_mutex_lock(&cmd->c_lu->l_cmd_mutex); /* dup count include the original cmd */ iscsi->c_t10_dup++; /* * Add the cmd to the list of t10 cmds for this iscsi cmd. * But don't add the original t10 cmd twice. */ if (c != cmd) { c->c_cmd_next = iscsi->c_t10_cmd; iscsi->c_t10_cmd = c; } (void) pthread_mutex_unlock(&cmd->c_lu->l_cmd_mutex); io = sbc_io_alloc(c); io->da_lba = addr; io->da_lba_cnt = cnt; io->da_offset = offset; io->da_data_len = min; #ifdef FULL_DEBUG queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d blk 0x%llx, cnt %d, offset 0x%llx, size %d\n", c->c_lu->l_targ->s_targ_num, c->c_lu->l_common->l_num, addr, cnt, io->da_offset, min); #endif if (mmap_data != MAP_FAILED) { io->da_clear_overlap = True; io->da_data_alloc = False; io->da_aio.a_aio.aio_return = min; io->da_data = (char *)mmap_data + (addr * 512LL) + io->da_offset; sbc_overlap_store(io); sbc_read_cmplt((emul_handle_t)io); } else { if ((io->da_data = (char *)malloc(min)) == NULL) { trans_send_complete(c, STATUS_BUSY); return; } io->da_clear_overlap = False; io->da_data_alloc = True; io->da_aio.a_aio_cmplt = sbc_read_cmplt; io->da_aio.a_id = io; trans_aioread(c, io->da_data, min, (addr * 512LL) + (off_t)io->da_offset, &io->da_aio); } offset += min; } while (offset < (off_t)(cnt * 512)); } /* * []---- * | sbc_read_cmplt -- Once we have the data, need to send it along. * []---- */ static void sbc_read_cmplt(emul_handle_t id) { disk_io_t *io = (disk_io_t *)id; int sense_len; uint64_t err_blkno; t10_cmd_t *cmd = io->da_cmd; Boolean_t last; last = (io->da_offset + io->da_data_len) < (io->da_lba_cnt * 512LL) ? False : True; if (io->da_aio.a_aio.aio_return != io->da_data_len) { err_blkno = io->da_lba + ((io->da_offset + 511) / 512); cmd->c_resid = (io->da_lba_cnt * 512) - io->da_offset; if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) sense_len = INFORMATION_SENSE_DESCR; else sense_len = 0; spc_sense_create(cmd, KEY_HARDWARE_ERROR, sense_len); spc_sense_info(cmd, err_blkno); if (last == True) { trans_send_complete(cmd, STATUS_CHECK); sbc_io_free(io); } else { t10_cmd_done(cmd); } return; } if (trans_send_datain(cmd, io->da_data, io->da_data_len, io->da_offset, sbc_io_free, last, io) == False) { trans_send_complete(cmd, STATUS_BUSY); } } /* * []---- * | sbc_write -- implement a SCSI write command. * []---- */ /*ARGSUSED*/ static void sbc_write(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { union scsi_cdb *u; diskaddr_t addr; uint64_t err_blkno; uint32_t cnt; uchar_t addl_sense_len; disk_params_t *d; disk_io_t *io; size_t max_out; void *mmap_area; if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } /*LINTED*/ u = (union scsi_cdb *)cdb; switch (u->scc_cmd) { case SCMD_WRITE: /* * SBC-2 revision 16, section 5.24 * Reserve bit checks. */ if ((cdb[1] & 0xe0) || 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; } addr = (diskaddr_t)(uint32_t)GETG0ADDR(u); cnt = GETG0COUNT(u); /* * SBC-2 Revision 16/Section 5.24 WRITE(6) * A TRANSFER LENGHT of 0 indicates that 256 logical blocks * shall be written. */ if (cnt == 0) cnt = 256; break; case SCMD_WRITE_G1: /* * SBC-2 revision 16, section 5.25 * Reserve bit checks. */ if ((cdb[1] & 0x6) || cdb[6] || 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; } addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); cnt = GETG1COUNT(u); break; case SCMD_WRITE_G4: /* * SBC-2 revision 16, section 5.27 * Reserve bit checks. */ if ((cdb[1] & 0x6) || cdb[14] || SAM_CONTROL_BYTE_RESERVED(cdb[15])) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } addr = (diskaddr_t)GETG4LONGADDR(u); cnt = GETG4COUNT(u); break; default: queue_prt(mgmtq, Q_STE_ERRS, "Unprocessed WRITE type\n"); 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 ((addr + cnt) > d->d_size) { if (addr > d->d_size) err_blkno = addr; else err_blkno = d->d_size; if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) addl_sense_len = INFORMATION_SENSE_DESCR; else addl_sense_len = 0; spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); spc_sense_info(cmd, err_blkno); spc_sense_ascq(cmd, SPC_ASC_BLOCK_RANGE, SPC_ASCQ_BLOCK_RANGE); trans_send_complete(cmd, STATUS_CHECK); queue_prt(mgmtq, Q_STE_ERRS, "SBC%x LUN%d WRITE Illegal sector " "(0x%llx + 0x%x) > 0x%ullx\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr, cnt, d->d_size); return; } if (cnt == 0) { queue_prt(mgmtq, Q_STE_NONIO, "SBC%x LUN%d WRITE zero block count for addr 0x%x\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr); trans_send_complete(cmd, STATUS_GOOD); return; } io = (disk_io_t *)cmd->c_emul_id; if (io == NULL) { io = sbc_io_alloc(cmd); io->da_lba = addr; io->da_lba_cnt = cnt; io->da_clear_overlap = False; io->da_aio.a_aio_cmplt = sbc_write_cmplt; io->da_aio.a_id = io; /* * Only update the statistics the first time through * for this particular command. If the requested transfer * is larger than the transport can handle this routine * will be called many times. */ cmd->c_lu->l_cmds_write++; cmd->c_lu->l_sects_write += cnt; #ifdef FULL_DEBUG queue_prt(mgmtq, Q_STE_IO, "SBC%x LUN%d blk 0x%llx, cnt 0x%x\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr, cnt); #endif } /* * If a transport sets the maximum output value to zero we'll * just request the entire amount. Otherwise, transfer no more * than the maximum output or the reminder, whichever is less. */ max_out = cmd->c_lu->l_targ->s_maxout; io->da_data_len = max_out ? MIN(max_out, (cnt * 512) - io->da_offset) : (cnt * 512); mmap_area = T10_MMAP_AREA(cmd); if (mmap_area != MAP_FAILED) { io->da_data_alloc = False; io->da_data = (char *)mmap_area + (addr * 512LL) + io->da_offset; sbc_overlap_check(io); } else if ((io->da_data = (char *)malloc(io->da_data_len)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } else { io->da_data_alloc = True; } if (trans_rqst_dataout(cmd, io->da_data, io->da_data_len, io->da_offset, io, sbc_io_free) == False) { trans_send_complete(cmd, STATUS_BUSY); } } /* * []---- * | sbc_write_data -- store a chunk of data from the transport * []---- */ /*ARGSUSED*/ void sbc_write_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, size_t data_len) { disk_io_t *io = (disk_io_t *)id; disk_params_t *d; if (cmd->c_lu->l_common->l_mmap == MAP_FAILED) { trans_aiowrite(cmd, data, data_len, (io->da_lba * 512) + (off_t)io->da_offset, &io->da_aio); } else { if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) return; if (d->d_fast_write == 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 & ~(sbc_page_size - 1); len = (((size_t)data & (sbc_page_size - 1)) + data_len + sbc_page_size - 1) & ~(sbc_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; } } /* * Since the data has already been transfered from the * transport to the mmap area we just need to call * the complete routine. */ sbc_write_cmplt(id); } } /* * []---- * | sbc_write_cmplt -- deal with end game of write * | * | See if all of the data for this write operation has been dealt * | with. If so, send a final acknowledgement back to the transport. * | If not, update the offset, calculate the next transfer size, and * | start the process again. * []--- */ static void sbc_write_cmplt(emul_handle_t e) { disk_io_t *io = (disk_io_t *)e; t10_cmd_t *cmd = io->da_cmd; int sense_len; uint64_t err_blkno; if ((cmd->c_lu->l_common->l_mmap == MAP_FAILED) && (io->da_aio.a_aio.aio_return != io->da_data_len)) { err_blkno = io->da_lba + ((io->da_offset + 511) / 512); cmd->c_resid = (io->da_lba_cnt * 512) - io->da_offset; if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) sense_len = INFORMATION_SENSE_DESCR; else sense_len = 0; spc_sense_create(cmd, KEY_HARDWARE_ERROR, sense_len); spc_sense_info(cmd, err_blkno); trans_send_complete(cmd, STATUS_CHECK); return; } if ((io->da_offset + io->da_data_len) < (io->da_lba_cnt * 512)) { if (io->da_data_alloc == True) { io->da_data_alloc = False; free(io->da_data); } io->da_offset += io->da_data_len; io->da_data_len = MIN(cmd->c_lu->l_targ->s_maxout, (io->da_lba_cnt * 512) - io->da_offset); sbc_write(cmd, cmd->c_cdb, cmd->c_cdb_len); return; } trans_send_complete(cmd, STATUS_GOOD); } /*ARGSUSED*/ void sbc_startstop(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { /* * SBC-2 revision 16, section 5.17 * Reserve bit checks */ if ((cdb[1] & 0xfe) || cdb[2] || cdb[3] || (cdb[4] & ~(SBC_PWR_MASK|SBC_PWR_LOEJ|SBC_PWR_START)) || 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; } /* * More reserve bit checks */ switch ((cdb[4] & SBC_PWR_MASK) >> SBC_PWR_SHFT) { case SBC_PWR_START_VALID: /* * It's an error to ask that the media be ejected. * * NOTE: Look for method to pass the START bit * along to underlying storage. If we're asked to * stop the drive there's not much that we can do * for the virtual storage, but maybe everything else * has been requested to stop as well. */ if (cdb[4] & SBC_PWR_LOEJ) { goto send_error; } break; case SBC_PWR_ACTIVE: case SBC_PWR_IDLE: case SBC_PWR_STANDBY: case SBC_PWR_OBSOLETE: break; case SBC_PWR_LU_CONTROL: case SBC_PWR_FORCE_IDLE_0: case SBC_PWR_FORCE_STANDBY_0: break; default: send_error: 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 ((cdb[1] & 1) == 0) { /* * Immediate bit is not set, so go ahead a flush things. */ if (cmd->c_lu->l_common->l_mmap == MAP_FAILED) { if (fsync(cmd->c_lu->l_common->l_fd) != 0) { spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); trans_send_complete(cmd, STATUS_CHECK); return; } } else { if (msync(cmd->c_lu->l_common->l_mmap, cmd->c_lu->l_common->l_size, MS_SYNC) == -1) { spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); trans_send_complete(cmd, STATUS_CHECK); return; } } } trans_send_complete(cmd, STATUS_GOOD); } /* * []---- * | sbc_recap -- read capacity of device being emulated. * []---- */ /*ARGSUSED*/ void sbc_recap(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { uint64_t capacity; int len; uint32_t lba; struct scsi_capacity *cap; disk_params_t *d; disk_io_t *io; if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) return; capacity = d->d_size; len = sizeof (struct scsi_capacity); /* * SBC-2 Revision 16, section 5.10.1 * Any of the following conditions will generate an error. * (1) PMI bit is zero and LOGICAL block address is non-zero * (2) Rserved bytes are not zero * (3) Reseved bits are not zero * (4) Reserved CONTROL bits are not zero */ if ((((cdb[8] & SBC_CAPACITY_PMI) == 0) && (cdb[2] || cdb[3] || cdb[4] || cdb[5])) || cdb[1] || cdb[6] || cdb[7] || (cdb[8] & ~SBC_CAPACITY_PMI) || 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; } /* * if the device capacity larger than 32 bits then set * the capacity of the device to all 0xf's. * a device that supports LBAs larger than 32 bits which * should be used read_capacity(16) comand to get the capacity. * NOTE: the adjustment to subject one from the capacity is * done below. */ if (capacity & 0xFFFFFFFF00000000ULL) capacity = 0xFFFFFFFF; io = sbc_io_alloc(cmd); if ((cap = (struct scsi_capacity *)calloc(1, len)) == NULL) { sbc_io_free(io); trans_send_complete(cmd, STATUS_BUSY); return; } io->da_data = (char *)cap; io->da_data_alloc = True; io->da_clear_overlap = False; io->da_data_len = len; if (capacity != 0xFFFFFFFF) { /* * Look at the PMI information */ if (cdb[8] & SBC_CAPACITY_PMI) { lba = cdb[2] << 24 | cdb[3] << 16 | cdb[4] << 8 | cdb[5]; if (lba >= capacity) cap->capacity = htonl(0xffffffff); else cap->capacity = (capacity - 1); } else { cap->capacity = htonl(capacity - 1); } } else { cap->capacity = htonl(capacity); } cap->lbasize = htonl(d->d_bytes_sect); if (trans_send_datain(cmd, io->da_data, io->da_data_len, 0, sbc_io_free, True, io) == False) { trans_send_complete(cmd, STATUS_BUSY); } } /*ARGSUSED*/ void sbc_msense(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { struct mode_header *mode_hdr; char *np; disk_params_t *d; disk_io_t *io; int rtn_len; uint8_t pc; struct block_descriptor bd; if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) return; /* * SPC-3 Revision 21c section 6.8 * Reserve bit checks */ if ((cdb[1] & ~SPC_MODE_SENSE_DBD) || 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; } io = sbc_io_alloc(cmd); /* * Make sure that we have enough room in the data buffer. We'll * only send back the amount requested though */ io->da_data_len = MAX(cdb[4], sizeof (struct mode_format) + sizeof (struct mode_geometry) + sizeof (struct mode_control_scsi3) + sizeof (struct mode_cache_scsi3) + sizeof (struct mode_info_ctrl) + (MODE_BLK_DESC_LENGTH * 5)); if ((io->da_data = (char *)calloc(1, io->da_data_len)) == NULL) { sbc_io_free(io); trans_send_complete(cmd, STATUS_BUSY); return; } io->da_clear_overlap = False; io->da_data_alloc = True; mode_hdr = (struct mode_header *)io->da_data; /* * If DBD flag is set, then we should not send back the block * descriptor details */ if (cdb[1] & SPC_MODE_SENSE_DBD) { mode_hdr->length = sizeof (struct mode_header) - 1; mode_hdr->bdesc_length = 0; } /* * If DBD flag is zero, then we should add block descriptor details */ else { /* * We subtract one from the length because this value is not * supposed to contain it's size. */ mode_hdr->length = sizeof (struct mode_header) - 1 + MODE_BLK_DESC_LENGTH; mode_hdr->bdesc_length = MODE_BLK_DESC_LENGTH; /* * Need to fill in the block size. Some initiators are starting * to use this value, which is correct, instead of looking at * the page3 data which is starting to become obsolete. * * We define the space for the structure on the stack and then * copy it into the return area to avoid structure alignment * issues. */ bzero(&bd, sizeof (bd)); bd.blksize_hi = lobyte(hiword(d->d_bytes_sect)); bd.blksize_mid = hibyte(loword(d->d_bytes_sect)); bd.blksize_lo = lobyte(loword(d->d_bytes_sect)); bcopy(&bd, io->da_data + sizeof (*mode_hdr), sizeof (bd)); } /* * cdb[2] contains page code, and page control field. So, we need * to mask page control field, while checking for the page code. * * Page control specifies whether to report the current, changeable * default or saved values. This parameter only applies to the * mode page data itself. From spc-3 section 6.9.1: * * "The PC field only affects the mode parameters within the mode * pages, however the PS, SPF, PAGE CODE field, SUBPAGE CODE field, * and PAGE LENGTH field should return current values (i.e. as if * PC is set to 00b). The mode parameter header and mode parameter * block descriptor should return current values." * * Also: "Some SCSI target devices may not distinguish between * current and saved mode parameters and report identical values * in response to a PC field of either 0 or 3." Our implementation * will treat 0 and 3 the same. */ pc = (cdb[2] & SPC_MODE_SENSE_PC_MASK) >> SPC_MODE_SENSE_PC_SHIFT; switch (cdb[2] & SPC_MODE_SENSE_PAGE_CODE_MASK) { case MODE_SENSE_PAGE3_CODE: if ((d->d_heads == 0) && (d->d_cyl == 0) && (d->d_spt == 0)) { sbc_io_free(io); spc_unsupported(cmd, cdb, cdb_len); return; } mode_hdr->length += sizeof (struct mode_format); (void) sense_page3(d, io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length, pc); break; case MODE_SENSE_PAGE4_CODE: if ((d->d_heads == 0) && (d->d_cyl == 0) && (d->d_spt == 0)) { sbc_io_free(io); spc_unsupported(cmd, cdb, cdb_len); return; } mode_hdr->length += sizeof (struct mode_geometry); (void) sense_page4(d, io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length, pc); break; case MODE_SENSE_CACHE: mode_hdr->length += sizeof (struct mode_cache_scsi3); (void) sense_cache(d, io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length, pc); break; case MODE_SENSE_CONTROL: mode_hdr->length += sizeof (struct mode_control_scsi3); (void) sense_mode_control(cmd->c_lu, io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length, pc); break; case MODE_SENSE_INFO_CTRL: mode_hdr->length += sizeof (struct mode_info_ctrl); (void) sense_info_ctrl(io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length, pc); break; case MODE_SENSE_SEND_ALL: /* * SPC-3 revision 21c * Section 6.9.1 Table 97 * "Return all subpage 00h mode pages in page_0 format" */ mode_hdr->length += sizeof (struct mode_cache_scsi3) + sizeof (struct mode_control_scsi3) + sizeof (struct mode_info_ctrl); if (d->d_heads && d->d_cyl && d->d_spt) mode_hdr->length += sizeof (struct mode_format) + sizeof (struct mode_geometry); np = io->da_data + sizeof (*mode_hdr) + mode_hdr->bdesc_length; if (io->da_data_len < (sizeof (struct mode_format) + sizeof (struct mode_geometry) + sizeof (struct mode_cache_scsi3) + sizeof (struct mode_control_scsi3) + sizeof (struct mode_info_ctrl))) { /* * Believe it or not, there's an initiator out * there which sends a mode sense request for all * of the pages, without always sending a data-in * size which is large enough. * NOTE: Need to check the error key returned * here and see if something else should be used. */ spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); trans_send_complete(cmd, STATUS_CHECK); } else { /* * If we don't have geometry then don't attempt * report that information. */ rtn_len = sizeof (*mode_hdr) + mode_hdr->bdesc_length; if (d->d_heads && d->d_cyl && d->d_spt) { np = sense_page3(d, np, pc); np = sense_page4(d, np, pc); } np = sense_cache(d, np, pc); np = sense_mode_control(cmd->c_lu, np, pc); (void) sense_info_ctrl(np, pc); } break; case 0x00: /* * SPC-3 Revision 21c, section 6.9.1 * Table 97 -- Mode page code usage for all devices * Page Code 00 == Vendor specific. We are going to return * zeros. */ break; default: queue_prt(mgmtq, Q_STE_ERRS, "SBC%x LUN%d Unsupported mode_sense request 0x%x\n", 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; break; } rtn_len = mode_hdr->length + 1; rtn_len = MIN(rtn_len, cdb[4]); if (trans_send_datain(cmd, io->da_data, rtn_len, 0, sbc_io_free, True, io) == False) { trans_send_complete(cmd, STATUS_BUSY); } } /*ARGSUSED*/ void sbc_synccache(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { /* * SBC-2 revision 16, section 5.18 * Reserve bit checks */ if ((cdb[1] & ~(SBC_SYNC_CACHE_IMMED|SBC_SYNC_CACHE_NV)) || 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); } else { /* * SBC-3, revision 16, section 5.18 * An IMMED bit set to one specifies that the device server * shall return status as soon as the CDB has been validated. */ if (cdb[1] & SBC_SYNC_CACHE_IMMED) { /* * Immediately return a status of GOOD. If an error * occurs with the fsync/msync the next command will * pick up an error. */ trans_send_complete(cmd, STATUS_GOOD); if (cmd->c_lu->l_common->l_mmap == MAP_FAILED) { if (fsync(cmd->c_lu->l_common->l_fd) == -1) { cmd->c_lu->l_status = KEY_HARDWARE_ERROR; cmd->c_lu->l_asc = 0x00; cmd->c_lu->l_ascq = 0x00; } } else { if (msync(cmd->c_lu->l_common->l_mmap, cmd->c_lu->l_common->l_size, MS_SYNC) == -1) { cmd->c_lu->l_status = KEY_HARDWARE_ERROR; cmd->c_lu->l_asc = 0x00; cmd->c_lu->l_ascq = 0x00; } } } else { if (cmd->c_lu->l_common->l_mmap == MAP_FAILED) { if (fsync(cmd->c_lu->l_common->l_fd) == -1) { spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); trans_send_complete(cmd, STATUS_CHECK); } else trans_send_complete(cmd, STATUS_GOOD); } else { if (msync(cmd->c_lu->l_common->l_mmap, cmd->c_lu->l_common->l_size, MS_SYNC) == -1) { spc_sense_create(cmd, KEY_HARDWARE_ERROR, 0); trans_send_complete(cmd, STATUS_CHECK); } else trans_send_complete(cmd, STATUS_GOOD); } } } } /*ARGSUSED*/ void sbc_service_actiong4(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { switch (cdb[1] & SPC_GROUP4_SERVICE_ACTION_MASK) { case SSVC_ACTION_READ_CAPACITY_G4: sbc_read_capacity16(cmd, cdb, cdb_len); break; default: spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, 0x20, 0x00); trans_send_complete(cmd, STATUS_CHECK); break; } } /*ARGSUSED*/ static void sbc_read_capacity16(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { uint64_t capacity; uint64_t lba; int rep_size; /* response data size */ struct scsi_capacity_16 *cap16; disk_params_t *d; disk_io_t *io; if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) return; capacity = d->d_size; /* * READ_CAPACITY(16) command */ rep_size = cdb[10] << 24 | cdb[11] << 16 | cdb[12] << 8 | cdb[13]; if (rep_size == 0) { /* * A zero length field means we're done. */ trans_send_complete(cmd, STATUS_GOOD); return; } rep_size = MIN(rep_size, sizeof (*cap16)); /* * Reserve bit checks. */ if ((cdb[1] & ~SPC_GROUP4_SERVICE_ACTION_MASK) || (cdb[14] & ~SBC_CAPACITY_PMI) || SAM_CONTROL_BYTE_RESERVED(cdb[15])) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } lba = (uint64_t)cdb[2] << 56 | (uint64_t)cdb[3] << 48 | (uint64_t)cdb[4] << 40 | (uint64_t)cdb[5] << 32 | (uint64_t)cdb[6] << 24 | (uint64_t)cdb[7] << 16 | (uint64_t)cdb[8] << 8 | (uint64_t)cdb[9]; io = sbc_io_alloc(cmd); /* * We'll malloc enough space for the structure so that we can * set the values as we place. However, we'll set the transfer * length to the minimum of the requested size and our structure. * This is per SBC-2 revision 16, section 5.11.1 regarding * ALLOCATION LENGTH. */ if ((cap16 = (struct scsi_capacity_16 *)calloc(1, sizeof (*cap16))) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } io->da_data = (char *)cap16; io->da_data_len = rep_size; io->da_data_alloc = True; io->da_clear_overlap = False; if (cdb[14] & SBC_CAPACITY_PMI) { if (lba >= capacity) cap16->sc_capacity = htonll(0xffffffffffffffffULL); else cap16->sc_capacity = htonll(capacity - 1); } else { cap16->sc_capacity = htonll(capacity - 1); } cap16->sc_lbasize = htonl(d->d_bytes_sect); if (trans_send_datain(cmd, io->da_data, io->da_data_len, 0, sbc_io_free, True, io) == False) { trans_send_complete(cmd, STATUS_BUSY); } } /*ARGSUSED*/ static void sbc_verify(t10_cmd_t *cmd, uint8_t *cdb, size_t cdb_len) { /*LINTED*/ union scsi_cdb *u = (union scsi_cdb *)cdb; diskaddr_t addr; uint32_t cnt; uint32_t chk_size; uint64_t sz; uint64_t err_blkno; Boolean_t bytchk; char *chk_block; disk_io_t *io; disk_params_t *d; uchar_t addl_sense_len; if ((d = (disk_params_t *)T10_PARAMS_AREA(cmd)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } /* * Check the common reserved bits here and check the CONTROL byte * in each specific section for the different CDB sizes. * NOTE: If the VRPROTECT is non-zero we're required by SBC-3 * to return an error since our emulation code doesn't have * any protection information stored on the media that we can * access. */ if ((cdb[1] & ~(SBC_VRPROTECT_MASK|SBC_DPO|SBC_BYTCHK)) || (cdb[1] & SBC_VRPROTECT_MASK)) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } bytchk = cdb[1] & SBC_BYTCHK ? True : False; switch (u->scc_cmd) { case SCMD_VERIFY: /* * BYTE 6 of the VERIFY(10) contains bits: * 0-4: Group number -- not supported must be zero * 5-6: Reserved * 7 : Restricted for MMC-4 */ if (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; } addr = (diskaddr_t)(uint32_t)GETG1ADDR(u); cnt = GETG1COUNT(u); break; case SCMD_VERIFY_G4: /* * See VERIFY(10) above for definitions of what byte 14 * contains. */ if (cdb[14] || SAM_CONTROL_BYTE_RESERVED(cdb[15])) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } addr = GETG4LONGADDR(u); cnt = GETG4COUNT(u); break; case SCMD_VERIFY_G5: /* * See VERIFY(10) above for definitions of what byte 10 * contains. */ if (cdb[10] || SAM_CONTROL_BYTE_RESERVED(cdb[11])) { spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, 0); spc_sense_ascq(cmd, SPC_ASC_INVALID_CDB, 0x00); trans_send_complete(cmd, STATUS_CHECK); return; } addr = (diskaddr_t)GETG5ADDR(u); cnt = GETG5COUNT(u); 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; } if ((addr + cnt) > d->d_size) { if (addr > d->d_size) err_blkno = addr; else err_blkno = d->d_size; if (err_blkno > FIXED_SENSE_ADDL_INFO_LEN) addl_sense_len = INFORMATION_SENSE_DESCR; else addl_sense_len = 0; spc_sense_create(cmd, KEY_ILLEGAL_REQUEST, addl_sense_len); spc_sense_info(cmd, err_blkno); spc_sense_ascq(cmd, SPC_ASC_BLOCK_RANGE, SPC_ASCQ_BLOCK_RANGE); trans_send_complete(cmd, STATUS_CHECK); queue_prt(mgmtq, Q_STE_ERRS, "SBC%x LUN%d WRITE Illegal sector " "(0x%llx + 0x%x) > 0x%ullx\n", cmd->c_lu->l_targ->s_targ_num, cmd->c_lu->l_common->l_num, addr, cnt, d->d_size); return; } if (bytchk == False) { /* * With Byte Check being false all we need to do * is make sure that we can read the data off of the * media. */ chk_size = 1024 * 1024; if ((chk_block = malloc(chk_size)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } while (cnt) { sz = MIN(chk_size, cnt * 512); /* * Even if the device is mmap'd in use pread. This * way we know directly if a read of the data has * failed. */ if (pread(cmd->c_lu->l_common->l_fd, chk_block, sz, addr * 512LL) != sz) { spc_sense_create(cmd, KEY_MEDIUM_ERROR, 0); spc_sense_ascq(cmd, SPC_ASC_DATA_PATH, SPC_ASCQ_DATA_PATH); trans_send_complete(cmd, STATUS_CHECK); free(chk_block); return; } addr += sz / 512LL; cnt -= sz / 512; } free(chk_block); trans_send_complete(cmd, STATUS_GOOD); } else { io = cmd->c_emul_id; if (io == NULL) { io = sbc_io_alloc(cmd); io->da_lba = addr; io->da_lba_cnt = cnt; io->da_clear_overlap = False; io->da_aio.a_aio_cmplt = sbc_write_cmplt; io->da_aio.a_id = io; } sz = cmd->c_lu->l_targ->s_maxout; io->da_data_alloc = True; io->da_data_len = sz ? MIN(sz, (cnt * 512) - io->da_offset) : (cnt * 512); /* * Since we're going to just check the data we don't wish * to possibly change the on disk data. Therefore, even if * the backing store is mmap'd in we allocate space for the * data out buffer. */ if ((io->da_data = malloc(io->da_data_len)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); return; } if (trans_rqst_dataout(cmd, io->da_data, io->da_data_len, io->da_offset, io, sbc_io_free) == False) trans_send_complete(cmd, STATUS_BUSY); } } /*ARGSUSED*/ static void sbc_verify_data(t10_cmd_t *cmd, emul_handle_t id, size_t offset, char *data, size_t data_len) { disk_io_t *io = (disk_io_t *)id; char *on_disk_buf; if ((on_disk_buf = malloc(io->da_data_len)) == NULL) { trans_send_complete(cmd, STATUS_BUSY); } if (pread(cmd->c_lu->l_common->l_fd, on_disk_buf, io->da_data_len, io->da_offset + (io->da_lba * 512LL)) != io->da_data_len) { spc_sense_create(cmd, KEY_MISCOMPARE, 0); spc_sense_ascq(cmd, SPC_ASC_DATA_PATH, SPC_ASCQ_DATA_PATH); trans_send_complete(cmd, STATUS_CHECK); free(on_disk_buf); sbc_io_free(io); return; } if (bcmp(on_disk_buf, io->da_data, io->da_data_len) != 0) { spc_sense_create(cmd, KEY_MISCOMPARE, 0); spc_sense_ascq(cmd, SPC_ASC_MISCOMPARE, SPC_ASCQ_MISCOMPARE); trans_send_complete(cmd, STATUS_CHECK); free(on_disk_buf); sbc_io_free(io); return; } free(on_disk_buf); io->da_offset += io->da_data_len; if (io->da_offset < (io->da_lba_cnt * 512)) { if (io->da_data_alloc == True) { io->da_data_alloc = False; free(io->da_data); } sbc_verify(cmd, cmd->c_cdb, cmd->c_cdb_len); return; } trans_send_complete(cmd, STATUS_GOOD); } /* * []------------------------------------------------------------------[] * | Support related functions for SBC-2 | * []------------------------------------------------------------------[] */ /* * []---- * | sense_page3 -- Create page3 sense code for Disk. * | * | This is a separate routine because this is called in two different * | locations. * []---- */ static char * sense_page3(disk_params_t *d, char *buf, uint8_t pc) { struct mode_format mode_fmt; bzero(&mode_fmt, sizeof (mode_fmt)); /* Page code and page length are always set */ mode_fmt.mode_page.code = MODE_SENSE_PAGE3_CODE; mode_fmt.mode_page.length = sizeof (struct mode_format) - sizeof (struct mode_page); /* * No modifiable values so we report all zeros for * SPC_PC_MODIFIABLE_VALUES and all other page control values * result in the same values as SPC_PC_CURRENT_VALUES. */ switch (pc) { case SPC_PC_MODIFIABLE_VALUES: /* Leave it blank */ break; case SPC_PC_CURRENT_VALUES: case SPC_PC_DEFAULT_VALUES: case SPC_PC_SAVED_VALUES: mode_fmt.data_bytes_sect = htons(d->d_bytes_sect); mode_fmt.sect_track = htons(d->d_spt); mode_fmt.interleave = htons(d->d_interleave); break; } bcopy(&mode_fmt, buf, sizeof (mode_fmt)); return (buf + sizeof (mode_fmt)); } /* * []---- * | sense_page4 -- Create page4 sense code for Disk. * | * | This is a separate routine because this is called in two different * | locations. * []---- */ static char * sense_page4(disk_params_t *d, char *buf, uint8_t pc) { struct mode_geometry mode_geom; bzero(&mode_geom, sizeof (mode_geom)); /* Page code and page length are always set */ mode_geom.mode_page.code = MODE_SENSE_PAGE4_CODE; mode_geom.mode_page.length = sizeof (struct mode_geometry) - sizeof (struct mode_page); /* * No modifiable values so we report all zeros for * SPC_PC_MODIFIABLE_VALUES and all other page control values * result in the same values as SPC_PC_CURRENT_VALUES. */ switch (pc) { case SPC_PC_MODIFIABLE_VALUES: /* Leave it blank */ break; case SPC_PC_CURRENT_VALUES: case SPC_PC_DEFAULT_VALUES: case SPC_PC_SAVED_VALUES: mode_geom.heads = d->d_heads; mode_geom.cyl_ub = d->d_cyl >> 16; mode_geom.cyl_mb = d->d_cyl >> 8; mode_geom.cyl_lb = d->d_cyl; mode_geom.rpm = htons(d->d_rpm); break; } bcopy(&mode_geom, buf, sizeof (mode_geom)); return (buf + sizeof (mode_geom)); } static char * sense_cache(disk_params_t *d, char *buf, uint8_t pc) { struct mode_cache_scsi3 mode_cache; bzero(&mode_cache, sizeof (mode_cache)); /* Page code and page length are always set */ mode_cache.mode_page.code = MODE_SENSE_CACHE; mode_cache.mode_page.length = sizeof (mode_cache) - sizeof (struct mode_page); /* * No modifiable values so we report all zeros for * SPC_PC_MODIFIABLE_VALUES and all other page control values * result in the same values as SPC_PC_CURRENT_VALUES. * * Technically wce is modifiable but not by using a mode * select command. */ switch (pc) { case SPC_PC_MODIFIABLE_VALUES: /* Leave it blank */ break; case SPC_PC_CURRENT_VALUES: case SPC_PC_DEFAULT_VALUES: case SPC_PC_SAVED_VALUES: mode_cache.wce = d->d_fast_write == True ? 1 : 0; break; } bcopy(&mode_cache, buf, sizeof (mode_cache)); return (buf + sizeof (mode_cache)); } /* * []---- * | sense_mode_control -- Create mode control page for disk * []---- */ static char * sense_mode_control(t10_lu_impl_t *lu, char *buf, uint8_t pc) { struct mode_control_scsi3 m; bzero(&m, sizeof (m)); /* Page code and page length are always set */ m.mode_page.code = MODE_SENSE_CONTROL; m.mode_page.length = sizeof (struct mode_control_scsi3) - sizeof (struct mode_page); /* * D_SENSE is modifiable so we need to reflect this in the * SPC_PC_MODIFIABLE_VALUES page as well as report the default * value of '0' in SPC_PC_DEFAULT_VALUES. */ switch (pc) { case SPC_PC_MODIFIABLE_VALUES: m.d_sense = 1; /* Modifiable */ break; case SPC_PC_DEFAULT_VALUES: m.d_sense = 0; /* Default is disabled */ m.que_mod = SPC_QUEUE_UNRESTRICTED; break; case SPC_PC_CURRENT_VALUES: case SPC_PC_SAVED_VALUES: m.d_sense = (lu->l_dsense_enabled == True) ? 1 : 0; m.que_mod = SPC_QUEUE_UNRESTRICTED; break; } bcopy(&m, buf, sizeof (m)); return (buf + sizeof (m)); } /* * []---- * | sense_info_ctrl -- Create mode information control page * []---- */ static char * sense_info_ctrl(char *buf, uint8_t pc) { struct mode_info_ctrl info; bzero(&info, sizeof (info)); /* Page code and page length are always set */ info.mode_page.code = MODE_SENSE_INFO_CTRL; info.mode_page.length = sizeof (struct mode_info_ctrl) - sizeof (struct mode_page); /* * No modifiable values so we report all zeros for * SPC_PC_MODIFIABLE_VALUES and all other page control values * result in the same values as SPC_PC_CURRENT_VALUES. */ switch (pc) { case SPC_PC_MODIFIABLE_VALUES: /* Leave it blank */ break; case SPC_PC_CURRENT_VALUES: case SPC_PC_DEFAULT_VALUES: case SPC_PC_SAVED_VALUES: break; } bcopy(&info, buf, sizeof (info)); return (buf + sizeof (info)); } /* * []---- * | sbc_io_alloc -- return a disk_io_t structure * | * | If the call to calloc fails we use the structure that was allocate * | during the initial common initialization call. This will allow the * | daemon to at least make progress. * []---- */ static disk_io_t * sbc_io_alloc(t10_cmd_t *c) { disk_io_t *io; disk_params_t *d = T10_PARAMS_AREA(c); if ((io = (disk_io_t *)calloc(1, sizeof (*io))) == NULL) { (void) pthread_mutex_lock(&d->d_mutex); if (d->d_io_used == True) { d->d_io_need = True; while (d->d_io_used == True) (void) pthread_cond_wait(&d->d_io_cond, &d->d_mutex); d->d_io_need = False; } d->d_io_used = True; io = d->d_io_reserved; (void) pthread_mutex_unlock(&d->d_mutex); } io->da_cmd = c; io->da_params = d; /* Enable detecting a change in state of the aio operation */ io->da_aio.a_aio.aio_return = AIO_INPROGRESS; /* * Save the io pointer in the t10 cmd now rather than later. It's * needed during session shutdown processing to find the aio results * for determining if libaio has canceled the cmd, in which case * the cmd needs to be freed. */ c->c_emul_id = io; return (io); } /* * []---- * | sbc_io_free -- free local i/o buffers when transport is finished * | * | If the io structure being free is the preallocated buffer see if * | anyone is waiting for the buffer. If so, wake them up. * []---- */ static void sbc_io_free(emul_handle_t e) { disk_io_t *io = (disk_io_t *)e; if (io->da_clear_overlap == True) sbc_overlap_free(io); if (io->da_data_alloc == True) free(io->da_data); if (io == io->da_params->d_io_reserved) { (void) pthread_mutex_lock(&io->da_params->d_mutex); io->da_params->d_io_used = False; if (io->da_params->d_io_need == True) (void) pthread_cond_signal(&io->da_params->d_io_cond); (void) pthread_mutex_unlock(&io->da_params->d_mutex); } else { free(io); } } static int sbc_mmap_overlap(const void *v1, const void *v2) { disk_io_t *d1 = (disk_io_t *)v1; disk_io_t *d2 = (disk_io_t *)v2; if ((d1->da_data + d1->da_data_len) < d2->da_data) return (-1); if (d1->da_data > (d2->da_data + d2->da_data_len)) return (1); return (0); } static void sbc_overlap_store(disk_io_t *io) { disk_params_t *d = io->da_params; avl_index_t where = 0; assert(d != NULL); (void) pthread_mutex_lock(&d->d_mutex); (void) avl_find(&d->d_mmap_overlaps, (void *)io, &where); avl_insert(&d->d_mmap_overlaps, (void *)io, where); (void) pthread_mutex_unlock(&d->d_mutex); } static void sbc_overlap_free(disk_io_t *io) { disk_params_t *d = io->da_params; assert(d != NULL); (void) pthread_mutex_lock(&d->d_mutex); avl_remove(&d->d_mmap_overlaps, (void *)io); if (d->d_mmap_paused == True) { d->d_mmap_paused = False; (void) pthread_cond_signal(&d->d_mmap_cond); } (void) pthread_mutex_unlock(&d->d_mutex); } static void sbc_overlap_check(disk_io_t *io) { disk_params_t *d = io->da_params; assert(d != NULL); recheck: (void) pthread_mutex_lock(&d->d_mutex); if (avl_find(&d->d_mmap_overlaps, (void *)io, NULL) != NULL) { d->d_mmap_paused = True; while (d->d_mmap_paused == True) (void) pthread_cond_wait(&d->d_mmap_cond, &d->d_mutex); /* * After waiting on the condition variable the link * list has changed because someone removed a command. * So, drop the lock and reexamine the list. */ (void) pthread_mutex_unlock(&d->d_mutex); goto recheck; } (void) pthread_mutex_unlock(&d->d_mutex); } /* * []---- * | sbc_overlap_flush -- wait until everyone has reported in * []---- */ static void sbc_overlap_flush(disk_params_t *d) { assert(d != NULL); recheck: (void) pthread_mutex_lock(&d->d_mutex); if (avl_numnodes(&d->d_mmap_overlaps) != 0) { d->d_mmap_paused = True; while (d->d_mmap_paused == True) (void) pthread_cond_wait(&d->d_mmap_cond, &d->d_mutex); /* * After waiting on the condition variable the link * list has changed because someone removed a command. * So, drop the lock and reexamine the list. */ (void) pthread_mutex_unlock(&d->d_mutex); goto recheck; } (void) pthread_mutex_unlock(&d->d_mutex); } /* * []---- * | Command table for LBA emulation. This is at the end of the file because * | it's big and ugly. ;-) To make for fast translation to the appropriate * | emulation routine we just have a big command table with all 256 possible * | entries. Most will report STATUS_CHECK, unsupport operation. By doing * | this we can avoid error checking for command range. * []---- */ static scsi_cmd_table_t lba_table[] = { /* 0x00 -- 0x0f */ { spc_tur, NULL, NULL, "TEST_UNIT_READY" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_request_sense, NULL, NULL, "REQUEST_SENSE" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_read, NULL, sbc_read_cmplt, "READ" }, { spc_unsupported, NULL, NULL, NULL }, { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, /* 0x10 -- 0x1f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_inquiry, NULL, NULL, "INQUIRY" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_mselect, spc_mselect_data, NULL, "MODE_SELECT" }, { spc_cmd_reserve6, NULL, NULL, "RESERVE(6)" }, { spc_cmd_release6, NULL, NULL, "RELEASE(6)" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_msense, NULL, NULL, "MODE_SENSE" }, { sbc_startstop, NULL, NULL, "START_STOP" }, { 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 }, { sbc_recap, NULL, NULL, "READ_CAPACITY" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_read, NULL, sbc_read_cmplt, "READ_G1" }, { spc_unsupported, NULL, NULL, NULL }, { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE_G1" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_verify, sbc_verify_data, NULL, "VERIFY_G1" }, /* 0x30 -- 0x3f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_synccache, NULL, NULL, "SYNC_CACHE" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, /* 0x40 -- 0x4f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, "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_cmd_pr_in, NULL, NULL, "PERSISTENT_RESERVE_IN" }, { spc_cmd_pr_out, spc_cmd_pr_out_data, NULL, "PERSISTENT_RESERVE_OUT" }, /* 0x60 -- 0x6f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, /* 0x70 -- 0x7f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, /* 0x80 -- 0x8f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_read, NULL, sbc_read_cmplt, "READ_G4" }, { spc_unsupported, NULL, NULL, NULL }, { sbc_write, sbc_write_data, sbc_write_cmplt, "WRITE_G4" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_verify, sbc_verify_data, NULL, "VERIFY_G4" }, /* 0x90 -- 0x9f */ { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { sbc_service_actiong4, NULL, NULL, "SVC_ACTION_G4" }, { spc_unsupported, NULL, NULL, NULL }, /* 0xa0 - 0xaf */ { spc_report_luns, NULL, NULL, "REPORT_LUNS" }, { spc_unsupported, NULL, NULL, NULL }, { spc_unsupported, NULL, NULL, NULL }, { 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 }, { sbc_verify, sbc_verify_data, NULL, "VERIFY_G5" }, /* 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 }, };