/*
* SCSI helpers
*
* Copyright 2017 Red Hat, Inc.
*
* Authors:
* Fam Zheng <famz@redhat.com>
* Paolo Bonzini <pbonzini@redhat.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*/
#include "qemu/osdep.h"
#include "scsi/constants.h"
#include "scsi/utils.h"
#include "qemu/bswap.h"
uint32_t scsi_data_cdb_xfer(uint8_t *buf)
{
if ((buf[0] >> 5) == 0 && buf[4] == 0) {
return 256;
} else {
return scsi_cdb_xfer(buf);
}
}
uint32_t scsi_cdb_xfer(uint8_t *buf)
{
switch (buf[0] >> 5) {
case 0:
return buf[4];
break;
case 1:
case 2:
return lduw_be_p(&buf[7]);
break;
case 4:
return ldl_be_p(&buf[10]) & 0xffffffffULL;
break;
case 5:
return ldl_be_p(&buf[6]) & 0xffffffffULL;
break;
default:
return -1;
}
}
uint64_t scsi_cmd_lba(SCSICommand *cmd)
{
uint8_t *buf = cmd->buf;
uint64_t lba;
switch (buf[0] >> 5) {
case 0:
lba = ldl_be_p(&buf[0]) & 0x1fffff;
break;
case 1:
case 2:
case 5:
lba = ldl_be_p(&buf[2]) & 0xffffffffULL;
break;
case 4:
lba = ldq_be_p(&buf[2]);
break;
default:
lba = -1;
}
return lba;
}
int scsi_cdb_length(uint8_t *buf)
{
int cdb_len;
switch (buf[0] >> 5) {
case 0:
cdb_len = 6;
break;
case 1:
case 2:
cdb_len = 10;
break;
case 4:
cdb_len = 16;
break;
case 5:
cdb_len = 12;
break;
default:
cdb_len = -1;
}
return cdb_len;
}
SCSISense scsi_parse_sense_buf(const uint8_t *in_buf, int in_len)
{
bool fixed_in;
SCSISense sense;
assert(in_len > 0);
fixed_in = (in_buf[0] & 2) == 0;
if (fixed_in) {
if (in_len < 14) {
return SENSE_CODE(IO_ERROR);
}
sense.key = in_buf[2];
sense.asc = in_buf[12];
sense.ascq = in_buf[13];
} else {
if (in_len < 4) {
return SENSE_CODE(IO_ERROR);
}
sense.key = in_buf[1];
sense.asc = in_buf[2];
sense.ascq = in_buf[3];
}
return sense;
}
int scsi_build_sense_buf(uint8_t *out_buf, size_t size, SCSISense sense,
bool fixed_sense)
{
int len;
uint8_t buf[SCSI_SENSE_LEN] = { 0 };
if (fixed_sense) {
buf[0] = 0x70;
buf[2] = sense.key;
buf[7] = 10;
buf[12] = sense.asc;
buf[13] = sense.ascq;
len = 18;
} else {
buf[0] = 0x72;
buf[1] = sense.key;
buf[2] = sense.asc;
buf[3] = sense.ascq;
len = 8;
}
len = MIN(len, size);
memcpy(out_buf, buf, len);
return len;
}
int scsi_build_sense(uint8_t *buf, SCSISense sense)
{
return scsi_build_sense_buf(buf, SCSI_SENSE_LEN, sense, true);
}
/*
* Predefined sense codes
*/
/* No sense data available */
const struct SCSISense sense_code_NO_SENSE = {
.key = NO_SENSE , .asc = 0x00 , .ascq = 0x00
};
/* LUN not ready, Manual intervention required */
const struct SCSISense sense_code_LUN_NOT_READY = {
.key = NOT_READY, .asc = 0x04, .ascq = 0x03
};
/* LUN not ready, Medium not present */
const struct SCSISense sense_code_NO_MEDIUM = {
.key = NOT_READY, .asc = 0x3a, .ascq = 0x00
};
/* LUN not ready, medium removal prevented */
const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED = {
.key = NOT_READY, .asc = 0x53, .ascq = 0x02
};
/* Hardware error, internal target failure */
const struct SCSISense sense_code_TARGET_FAILURE = {
.key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00
};
/* Illegal request, invalid command operation code */
const struct SCSISense sense_code_INVALID_OPCODE = {
.key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00
};
/* Illegal request, LBA out of range */
const struct SCSISense sense_code_LBA_OUT_OF_RANGE = {
.key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00
};
/* Illegal request, Invalid field in CDB */
const struct SCSISense sense_code_INVALID_FIELD = {
.key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00
};
/* Illegal request, Invalid field in parameter list */
const struct SCSISense sense_code_INVALID_PARAM = {
.key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00
};
/* Illegal request, Parameter list length error */
const struct SCSISense sense_code_INVALID_PARAM_LEN = {
.key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00
};
/* Illegal request, LUN not supported */
const struct SCSISense sense_code_LUN_NOT_SUPPORTED = {
.key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00
};
/* Illegal request, Saving parameters not supported */
const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED = {
.key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00
};
/* Illegal request, Incompatible medium installed */
const struct SCSISense sense_code_INCOMPATIBLE_FORMAT = {
.key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00
};
/* Illegal request, medium removal prevented */
const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED = {
.key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02
};
/* Illegal request, Invalid Transfer Tag */
const struct SCSISense sense_code_INVALID_TAG = {
.key = ILLEGAL_REQUEST, .asc = 0x4b, .ascq = 0x01
};
/* Command aborted, I/O process terminated */
const struct SCSISense sense_code_IO_ERROR = {
.key = ABORTED_COMMAND, .asc = 0x00, .ascq = 0x06
};
/* Command aborted, I_T Nexus loss occurred */
const struct SCSISense sense_code_I_T_NEXUS_LOSS = {
.key = ABORTED_COMMAND, .asc = 0x29, .ascq = 0x07
};
/* Command aborted, Logical Unit failure */
const struct SCSISense sense_code_LUN_FAILURE = {
.key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01
};
/* Command aborted, Overlapped Commands Attempted */
const struct SCSISense sense_code_OVERLAPPED_COMMANDS = {
.key = ABORTED_COMMAND, .asc = 0x4e, .ascq = 0x00
};
/* Command aborted, LUN Communication Failure */
const struct SCSISense sense_code_LUN_COMM_FAILURE = {
.key = ABORTED_COMMAND, .asc = 0x08, .ascq = 0x00
};
/* Medium Error, Unrecovered read error */
const struct SCSISense sense_code_READ_ERROR = {
.key = MEDIUM_ERROR, .asc = 0x11, .ascq = 0x00
};
/* Not ready, Cause not reportable */
const struct SCSISense sense_code_NOT_READY = {
.key = NOT_READY, .asc = 0x04, .ascq = 0x00
};
/* Unit attention, Capacity data has changed */
const struct SCSISense sense_code_CAPACITY_CHANGED = {
.key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09
};
/* Unit attention, Power on, reset or bus device reset occurred */
const struct SCSISense sense_code_RESET = {
.key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00
};
/* Unit attention, SCSI bus reset */
const struct SCSISense sense_code_SCSI_BUS_RESET = {
.key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x02
};
/* Unit attention, No medium */
const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM = {
.key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00
};
/* Unit attention, Medium may have changed */
const struct SCSISense sense_code_MEDIUM_CHANGED = {
.key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00
};
/* Unit attention, Reported LUNs data has changed */
const struct SCSISense sense_code_REPORTED_LUNS_CHANGED = {
.key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e
};
/* Unit attention, Device internal reset */
const struct SCSISense sense_code_DEVICE_INTERNAL_RESET = {
.key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04
};
/* Data Protection, Write Protected */
const struct SCSISense sense_code_WRITE_PROTECTED = {
.key = DATA_PROTECT, .asc = 0x27, .ascq = 0x00
};
/* Data Protection, Space Allocation Failed Write Protect */
const struct SCSISense sense_code_SPACE_ALLOC_FAILED = {
.key = DATA_PROTECT, .asc = 0x27, .ascq = 0x07
};
/*
* scsi_convert_sense
*
* Convert between fixed and descriptor sense buffers
*/
int scsi_convert_sense(uint8_t *in_buf, int in_len,
uint8_t *buf, int len, bool fixed)
{
SCSISense sense;
bool fixed_in;
if (in_len == 0) {
return scsi_build_sense_buf(buf, len, SENSE_CODE(NO_SENSE), fixed);
}
fixed_in = (in_buf[0] & 2) == 0;
if (fixed == fixed_in) {
memcpy(buf, in_buf, MIN(len, in_len));
return MIN(len, in_len);
} else {
sense = scsi_parse_sense_buf(in_buf, in_len);
return scsi_build_sense_buf(buf, len, sense, fixed);
}
}
static bool scsi_sense_is_guest_recoverable(int key, int asc, int ascq)
{
switch (key) {
case NO_SENSE:
case RECOVERED_ERROR:
case UNIT_ATTENTION:
case ABORTED_COMMAND:
return true;
case NOT_READY:
case ILLEGAL_REQUEST:
case DATA_PROTECT:
/* Parse ASCQ */
break;
default:
return false;
}
switch ((asc << 8) | ascq) {
case 0x1a00: /* PARAMETER LIST LENGTH ERROR */
case 0x2000: /* INVALID OPERATION CODE */
case 0x2400: /* INVALID FIELD IN CDB */
case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */
case 0x2600: /* INVALID FIELD IN PARAMETER LIST */
case 0x2104: /* UNALIGNED WRITE COMMAND */
case 0x2105: /* WRITE BOUNDARY VIOLATION */
case 0x2106: /* ATTEMPT TO READ INVALID DATA */
case 0x550e: /* INSUFFICIENT ZONE RESOURCES */
case 0x0401: /* NOT READY, IN PROGRESS OF BECOMING READY */
case 0x0402: /* NOT READY, INITIALIZING COMMAND REQUIRED */
return true;
default:
return false;
}
}
int scsi_sense_to_errno(int key, int asc, int ascq)
{
switch (key) {
case NO_SENSE:
case RECOVERED_ERROR:
case UNIT_ATTENTION:
return EAGAIN;
case ABORTED_COMMAND: /* COMMAND ABORTED */
return ECANCELED;
case NOT_READY:
case ILLEGAL_REQUEST:
case DATA_PROTECT:
/* Parse ASCQ */
break;
default:
return EIO;
}
switch ((asc << 8) | ascq) {
case 0x1a00: /* PARAMETER LIST LENGTH ERROR */
case 0x2000: /* INVALID OPERATION CODE */
case 0x2400: /* INVALID FIELD IN CDB */
case 0x2600: /* INVALID FIELD IN PARAMETER LIST */
return EINVAL;
case 0x2100: /* LBA OUT OF RANGE */
case 0x2707: /* SPACE ALLOC FAILED */
return ENOSPC;
case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */
return ENOTSUP;
case 0x3a00: /* MEDIUM NOT PRESENT */
case 0x3a01: /* MEDIUM NOT PRESENT TRAY CLOSED */
case 0x3a02: /* MEDIUM NOT PRESENT TRAY OPEN */
return ENOMEDIUM;
case 0x2700: /* WRITE PROTECTED */
return EACCES;
case 0x0401: /* NOT READY, IN PROGRESS OF BECOMING READY */
return EINPROGRESS;
case 0x0402: /* NOT READY, INITIALIZING COMMAND REQUIRED */
return ENOTCONN;
default:
return EIO;
}
}
int scsi_sense_buf_to_errno(const uint8_t *in_buf, size_t in_len)
{
SCSISense sense;
if (in_len < 1) {
return EIO;
}
sense = scsi_parse_sense_buf(in_buf, in_len);
return scsi_sense_to_errno(sense.key, sense.asc, sense.ascq);
}
bool scsi_sense_buf_is_guest_recoverable(const uint8_t *in_buf, size_t in_len)
{
SCSISense sense;
if (in_len < 1) {
return false;
}
sense = scsi_parse_sense_buf(in_buf, in_len);
return scsi_sense_is_guest_recoverable(sense.key, sense.asc, sense.ascq);
}
const char *scsi_command_name(uint8_t cmd)
{
static const char *names[] = {
[ TEST_UNIT_READY ] = "TEST_UNIT_READY",
[ REWIND ] = "REWIND",
[ REQUEST_SENSE ] = "REQUEST_SENSE",
[ FORMAT_UNIT ] = "FORMAT_UNIT",
[ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS",
[ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS/INITIALIZE ELEMENT STATUS",
/* LOAD_UNLOAD and INITIALIZE_ELEMENT_STATUS use the same operation code */
[ READ_6 ] = "READ_6",
[ WRITE_6 ] = "WRITE_6",
[ SET_CAPACITY ] = "SET_CAPACITY",
[ READ_REVERSE ] = "READ_REVERSE",
[ WRITE_FILEMARKS ] = "WRITE_FILEMARKS",
[ SPACE ] = "SPACE",
[ INQUIRY ] = "INQUIRY",
[ RECOVER_BUFFERED_DATA ] = "RECOVER_BUFFERED_DATA",
[ MAINTENANCE_IN ] = "MAINTENANCE_IN",
[ MAINTENANCE_OUT ] = "MAINTENANCE_OUT",
[ MODE_SELECT ] = "MODE_SELECT",
[ RESERVE ] = "RESERVE",
[ RELEASE ] = "RELEASE",
[ COPY ] = "COPY",
[ ERASE ] = "ERASE",
[ MODE_SENSE ] = "MODE_SENSE",
[ START_STOP ] = "START_STOP/LOAD_UNLOAD",
/* LOAD_UNLOAD and START_STOP use the same operation code */
[ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC",
[ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC",
[ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL",
[ READ_CAPACITY_10 ] = "READ_CAPACITY_10",
[ READ_10 ] = "READ_10",
[ WRITE_10 ] = "WRITE_10",
[ SEEK_10 ] = "SEEK_10/POSITION_TO_ELEMENT",
/* SEEK_10 and POSITION_TO_ELEMENT use the same operation code */
[ WRITE_VERIFY_10 ] = "WRITE_VERIFY_10",
[ VERIFY_10 ] = "VERIFY_10",
[ SEARCH_HIGH ] = "SEARCH_HIGH",
[ SEARCH_EQUAL ] = "SEARCH_EQUAL",
[ SEARCH_LOW ] = "SEARCH_LOW",
[ SET_LIMITS ] = "SET_LIMITS",
[ PRE_FETCH ] = "PRE_FETCH/READ_POSITION",
/* READ_POSITION and PRE_FETCH use the same operation code */
[ SYNCHRONIZE_CACHE ] = "SYNCHRONIZE_CACHE",
[ LOCK_UNLOCK_CACHE ] = "LOCK_UNLOCK_CACHE",
[ READ_DEFECT_DATA ] = "READ_DEFECT_DATA/INITIALIZE_ELEMENT_STATUS_WITH_RANGE",
/* READ_DEFECT_DATA and INITIALIZE_ELEMENT_STATUS_WITH_RANGE use the same operation code */
[ MEDIUM_SCAN ] = "MEDIUM_SCAN",
[ COMPARE ] = "COMPARE",
[ COPY_VERIFY ] = "COPY_VERIFY",
[ WRITE_BUFFER ] = "WRITE_BUFFER",
[ READ_BUFFER ] = "READ_BUFFER",
[ UPDATE_BLOCK ] = "UPDATE_BLOCK",
[ READ_LONG_10 ] = "READ_LONG_10",
[ WRITE_LONG_10 ] = "WRITE_LONG_10",
[ CHANGE_DEFINITION ] = "CHANGE_DEFINITION",
[ WRITE_SAME_10 ] = "WRITE_SAME_10",
[ UNMAP ] = "UNMAP",
[ READ_TOC ] = "READ_TOC",
[ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT",
[ SANITIZE ] = "SANITIZE",
[ GET_CONFIGURATION ] = "GET_CONFIGURATION",
[ LOG_SELECT ] = "LOG_SELECT",
[ LOG_SENSE ] = "LOG_SENSE",
[ MODE_SELECT_10 ] = "MODE_SELECT_10",
[ RESERVE_10 ] = "RESERVE_10",
[ RELEASE_10 ] = "RELEASE_10",
[ MODE_SENSE_10 ] = "MODE_SENSE_10",
[ PERSISTENT_RESERVE_IN ] = "PERSISTENT_RESERVE_IN",
[ PERSISTENT_RESERVE_OUT ] = "PERSISTENT_RESERVE_OUT",
[ WRITE_FILEMARKS_16 ] = "WRITE_FILEMARKS_16",
[ EXTENDED_COPY ] = "EXTENDED_COPY",
[ ATA_PASSTHROUGH_16 ] = "ATA_PASSTHROUGH_16",
[ ACCESS_CONTROL_IN ] = "ACCESS_CONTROL_IN",
[ ACCESS_CONTROL_OUT ] = "ACCESS_CONTROL_OUT",
[ READ_16 ] = "READ_16",
[ COMPARE_AND_WRITE ] = "COMPARE_AND_WRITE",
[ WRITE_16 ] = "WRITE_16",
[ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16",
[ VERIFY_16 ] = "VERIFY_16",
[ PRE_FETCH_16 ] = "PRE_FETCH_16",
[ SYNCHRONIZE_CACHE_16 ] = "SPACE_16/SYNCHRONIZE_CACHE_16",
/* SPACE_16 and SYNCHRONIZE_CACHE_16 use the same operation code */
[ LOCATE_16 ] = "LOCATE_16",
[ WRITE_SAME_16 ] = "ERASE_16/WRITE_SAME_16",
/* ERASE_16 and WRITE_SAME_16 use the same operation code */
[ SERVICE_ACTION_IN_16 ] = "SERVICE_ACTION_IN_16",
[ WRITE_LONG_16 ] = "WRITE_LONG_16",
[ REPORT_LUNS ] = "REPORT_LUNS",
[ ATA_PASSTHROUGH_12 ] = "BLANK/ATA_PASSTHROUGH_12",
[ MOVE_MEDIUM ] = "MOVE_MEDIUM",
[ EXCHANGE_MEDIUM ] = "EXCHANGE MEDIUM",
[ READ_12 ] = "READ_12",
[ WRITE_12 ] = "WRITE_12",
[ ERASE_12 ] = "ERASE_12/GET_PERFORMANCE",
/* ERASE_12 and GET_PERFORMANCE use the same operation code */
[ SERVICE_ACTION_IN_12 ] = "SERVICE_ACTION_IN_12",
[ WRITE_VERIFY_12 ] = "WRITE_VERIFY_12",
[ VERIFY_12 ] = "VERIFY_12",
[ SEARCH_HIGH_12 ] = "SEARCH_HIGH_12",
[ SEARCH_EQUAL_12 ] = "SEARCH_EQUAL_12",
[ SEARCH_LOW_12 ] = "SEARCH_LOW_12",
[ READ_ELEMENT_STATUS ] = "READ_ELEMENT_STATUS",
[ SEND_VOLUME_TAG ] = "SEND_VOLUME_TAG/SET_STREAMING",
/* SEND_VOLUME_TAG and SET_STREAMING use the same operation code */
[ READ_CD ] = "READ_CD",
[ READ_DEFECT_DATA_12 ] = "READ_DEFECT_DATA_12",
[ READ_DVD_STRUCTURE ] = "READ_DVD_STRUCTURE",
[ RESERVE_TRACK ] = "RESERVE_TRACK",
[ SEND_CUE_SHEET ] = "SEND_CUE_SHEET",
[ SEND_DVD_STRUCTURE ] = "SEND_DVD_STRUCTURE",
[ SET_CD_SPEED ] = "SET_CD_SPEED",
[ SET_READ_AHEAD ] = "SET_READ_AHEAD",
[ ALLOW_OVERWRITE ] = "ALLOW_OVERWRITE",
[ MECHANISM_STATUS ] = "MECHANISM_STATUS",
[ GET_EVENT_STATUS_NOTIFICATION ] = "GET_EVENT_STATUS_NOTIFICATION",
[ READ_DISC_INFORMATION ] = "READ_DISC_INFORMATION",
};
if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL) {
return "*UNKNOWN*";
}
return names[cmd];
}
#ifdef CONFIG_LINUX
int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
SCSISense *sense)
{
if (errno_value != 0) {
switch (errno_value) {
case EDOM:
return TASK_SET_FULL;
case ENOMEM:
*sense = SENSE_CODE(TARGET_FAILURE);
return CHECK_CONDITION;
default:
*sense = SENSE_CODE(IO_ERROR);
return CHECK_CONDITION;
}
} else {
if (io_hdr->host_status == SG_ERR_DID_NO_CONNECT ||
io_hdr->host_status == SG_ERR_DID_BUS_BUSY ||
io_hdr->host_status == SG_ERR_DID_TIME_OUT ||
(io_hdr->driver_status & SG_ERR_DRIVER_TIMEOUT)) {
return BUSY;
} else if (io_hdr->host_status) {
*sense = SENSE_CODE(I_T_NEXUS_LOSS);
return CHECK_CONDITION;
} else if (io_hdr->status) {
return io_hdr->status;
} else if (io_hdr->driver_status & SG_ERR_DRIVER_SENSE) {
return CHECK_CONDITION;
} else {
return GOOD;
}
}
}
#endif