zephyr/subsys/usb/device_next/class/usbd_msc_scsi.c
Johann Fischer e6bfc7f868 usb: fix common misspellings in USB support
Fix common misspellings in USB device next and host.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2023-07-26 10:46:01 +02:00

940 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/storage/disk_access.h>
#include <zephyr/sys/byteorder.h>
#include "usbd_msc_scsi.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(usbd_msc, CONFIG_USBD_MSC_LOG_LEVEL);
#define INQUIRY_VERSION_SPC_2 0x04
#define INQUIRY_VERSION_SPC_3 0x05
#define INQUIRY_VERSION_SPC_4 0x06
#define INQUIRY_VERSION_SPC_5 0x07
/* Claim conformance to SPC-2 because this allows us to implement less commands
* and do not care about multiple reserved bits that became actual options
* later on. DO NOT change unless you make sure that all mandatory commands are
* implemented and all options (e.g. vpd pages) that are mandatory at given
* version are also implemented.
*/
#define CLAIMED_CONFORMANCE_VERSION INQUIRY_VERSION_SPC_2
#define T10_VENDOR_LENGTH 8
#define T10_PRODUCT_LENGTH 16
#define T10_REVISION_LENGTH 4
/* Optional, however Windows insists on reading Unit Serial Number.
* There doesn't seem to be requirement on minimum product serial number length,
* however when the number is not available the device shall return ASCII spaces
* in the field.
*/
#define UNIT_SERIAL_NUMBER " "
/* Every SCSI command has to abide to the general handling rules. Use macros
* to allow generating boilerplate handling code.
*/
#define SCSI_CMD_STRUCT(opcode) struct scsi_##opcode##_cmd
#define SCSI_CMD_HANDLER(opcode) \
static int scsi_##opcode(struct scsi_ctx *ctx, \
struct scsi_##opcode##_cmd *cmd, \
uint8_t data_in_buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
/* SAM-6 5.2 Command descriptor block (CDB)
* Table 43 CONTROL byte
*/
#define GET_CONTROL_NACA(cmd) (cmd->control & BIT(2))
/* SPC-5 4.3.3 Variable type data field requirements
* Table 25 — Code set enumeration
*/
enum code_set {
CODE_SET_BINARY = 0x1,
CODE_SET_ASCII = 0x2,
CODE_SET_UTF8 = 0x3,
};
/* SPC-5 F.3.1 Operation codes Table F.2 — Operation codes */
enum scsi_opcode {
TEST_UNIT_READY = 0x00,
REQUEST_SENSE = 0x03,
INQUIRY = 0x12,
MODE_SENSE_6 = 0x1A,
START_STOP_UNIT = 0x1B,
PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E,
READ_FORMAT_CAPACITIES = 0x23,
READ_CAPACITY_10 = 0x25,
READ_10 = 0x28,
WRITE_10 = 0x2A,
MODE_SENSE_10 = 0x5A,
};
SCSI_CMD_STRUCT(TEST_UNIT_READY) {
uint8_t opcode;
uint32_t reserved;
uint8_t control;
} __packed;
/* DESC bit was reserved in SPC-2 and is optional since SPC-3 */
#define GET_REQUEST_SENSE_DESC(cmd) (cmd->desc & BIT(0))
SCSI_CMD_STRUCT(REQUEST_SENSE) {
uint8_t opcode;
uint8_t desc;
uint8_t reserved2;
uint8_t reserved3;
uint8_t allocation_length;
uint8_t control;
} __packed;
#define SENSE_VALID BIT(7)
#define SENSE_CODE_CURRENT_ERRORS 0x70
#define SENSE_CODE_DEFERRED_ERRORS 0x71
#define SENSE_FILEMARK BIT(7)
#define SENSE_EOM BIT(6)
#define SENSE_ILI BIT(5)
#define SENSE_KEY_MASK BIT_MASK(4)
#define SENSE_SKSV BIT(7)
struct scsi_request_sense_response {
uint8_t valid_code;
uint8_t obsolete;
uint8_t filemark_eom_ili_sense_key;
uint32_t information;
uint8_t additional_sense_length;
uint32_t command_specific_information;
uint16_t additional_sense_with_qualifier;
uint8_t field_replaceable_unit_code;
uint8_t sksv;
uint16_t sense_key_specific;
} __packed;
#define INQUIRY_EVPD BIT(0)
/* CMDDT in SPC-2, but obsolete since SPC-3 */
#define INQUIRY_CMDDT_OBSOLETE BIT(1)
enum vpd_page_code {
VPD_SUPPORTED_VPD_PAGES = 0x00,
VPD_UNIT_SERIAL_NUMBER = 0x80,
VPD_DEVICE_IDENTIFICATION = 0x83,
};
/* SPC-5 Table 517 — DESIGNATOR TYPE field */
enum designator_type {
DESIGNATOR_VENDOR = 0x0,
DESIGNATOR_T10_VENDOR_ID_BASED = 0x1,
DESIGNATOR_EUI_64_BASED = 0x2,
DESIGNATOR_NAA = 0x3,
DESIGNATOR_RELATIVE_TARGET_PORT_IDENTIFIER = 0x4,
DESIGNATOR_TARGET_PORT_GROUP = 0x5,
DESIGNATOR_MD5_LOGICAL_UNIT_IDENTIFIER = 0x6,
DESIGNATOR_SCSI_NAME_STRING = 0x8,
DESIGNATOR_PROTOCOL_SPECIFIC_PORT_IDENTIFIER = 0x9,
DESIGNATOR_UUID_IDENTIFIER = 0xA,
};
SCSI_CMD_STRUCT(INQUIRY) {
uint8_t opcode;
uint8_t cmddt_evpd;
uint8_t page_code;
/* Allocation length was 8-bit (LSB only) in SPC-2. MSB was reserved
* and hence SPC-2 compliant initiators should set it to 0.
*/
uint16_t allocation_length;
uint8_t control;
} __packed;
struct scsi_inquiry_response {
uint8_t peripheral;
uint8_t rmb;
uint8_t version;
uint8_t format;
uint8_t additional_length;
uint8_t sccs;
uint8_t encserv;
uint8_t cmdque;
char scsi_vendor[T10_VENDOR_LENGTH];
char product[T10_PRODUCT_LENGTH];
char revision[T10_REVISION_LENGTH];
/* SPC-5 states the standard INQUIRY should contain at least 36 bytes.
* We do the minimum required (and thus end the inquiry response here),
* as there is no real use in Zephyr for vendor specific data and/or
* parameters and we don't claim conformance to specific versions.
*/
} __packed;
#define MODE_SENSE_PAGE_CODE_ALL_PAGES 0x3F
SCSI_CMD_STRUCT(MODE_SENSE_6) {
uint8_t opcode;
uint8_t dbd;
uint8_t page;
uint8_t subpage;
uint8_t allocation_length;
uint8_t control;
} __packed;
/* SPC-5 7.5.6 Mode parameter header formats
* Table 443 — Mode parameter header(6)
*/
struct scsi_mode_sense_6_response {
uint8_t mode_data_length;
uint8_t medium_type;
uint8_t device_specific_parameter;
uint8_t block_descriptor_length;
} __packed;
#define GET_IMMED(cmd) (cmd->immed & BIT(0))
#define GET_POWER_CONDITION_MODIFIER(cmd) (cmd->condition & BIT_MASK(4))
#define GET_POWER_CONDITION(cmd) ((cmd->start & 0xF0) >> 4)
#define GET_NO_FLUSH(cmd) (cmd->start & BIT(2))
#define GET_LOEJ(cmd) (cmd->start & BIT(1))
#define GET_START(cmd) (cmd->start & BIT(0))
/* SBC-4 Table 114 — POWER CONDITION and POWER CONDITION MODIFIER field */
enum power_condition {
POWER_COND_START_VALID = 0x0,
POWER_COND_ACTIVE = 0x1,
POWER_COND_IDLE = 0x2,
POWER_COND_STANDBY = 0x3,
POWER_COND_LU_CONTROL = 0x7,
POWER_COND_FORCE_IDLE_0 = 0xA,
POWER_COND_FORCE_STANDBY_0 = 0xB,
};
SCSI_CMD_STRUCT(START_STOP_UNIT) {
uint8_t opcode;
uint8_t immed;
uint8_t reserved;
uint8_t condition;
uint8_t start;
uint8_t control;
} __packed;
#define GET_PREVENT(cmd) (cmd->prevent & BIT_MASK(2))
/* SBC-4 Table 77 — PREVENT field */
enum prevent_field {
MEDIUM_REMOVAL_ALLOWED = 0,
MEDIUM_REMOVAL_SHALL_BE_PREVENTED = 1,
PREVENT_OBSOLETE_2 = 2,
PREVENT_OBSOLETE_3 = 3,
};
SCSI_CMD_STRUCT(PREVENT_ALLOW_MEDIUM_REMOVAL) {
uint8_t opcode;
uint8_t reserved1;
uint8_t reserved2;
uint8_t reserved3;
uint8_t prevent;
uint8_t control;
} __packed;
SCSI_CMD_STRUCT(READ_FORMAT_CAPACITIES) {
uint8_t opcode;
uint8_t reserved1;
uint8_t reserved2;
uint8_t reserved3;
uint8_t reserved4;
uint8_t reserved5;
uint8_t reserved6;
uint16_t allocation_length;
uint8_t control;
} __packed;
struct capacity_list_header {
uint8_t reserved1;
uint8_t reserved2;
uint8_t reserved3;
uint8_t capacity_list_length;
} __packed;
enum descriptor_types {
UNFORMATTED_OR_BLANK_MEDIA = 1,
FORMATTED_MEDIA = 2,
NO_MEDIA_PRESENT_OR_UNKNOWN_CAPACITY = 3,
};
struct current_maximum_capacity_descriptor {
uint32_t number_of_blocks;
uint8_t type;
uint32_t block_length : 24;
} __packed;
struct scsi_read_format_capacities_response {
struct capacity_list_header header;
struct current_maximum_capacity_descriptor desc;
} __packed;
SCSI_CMD_STRUCT(READ_CAPACITY_10) {
uint8_t opcode;
uint8_t reserved_or_obsolete[8];
uint8_t control;
} __packed;
struct scsi_read_capacity_10_response {
uint32_t last_lba;
uint32_t block_length;
} __packed;
SCSI_CMD_STRUCT(READ_10) {
uint8_t opcode;
uint8_t rdprotect;
uint32_t lba;
uint8_t group_number;
uint16_t transfer_length;
uint8_t control;
} __packed;
SCSI_CMD_STRUCT(WRITE_10) {
uint8_t opcode;
uint8_t wrprotect;
uint32_t lba;
uint8_t group_number;
uint16_t transfer_length;
uint8_t control;
} __packed;
SCSI_CMD_STRUCT(MODE_SENSE_10) {
uint8_t opcode;
uint8_t llbaa_dbd;
uint8_t page;
uint8_t subpage;
uint8_t reserved4;
uint8_t reserved5;
uint8_t reserved6;
uint16_t allocation_length;
uint8_t control;
} __packed;
/* SPC-5 7.5.6 Mode parameter header formats
* Table 444 — Mode parameter header(10)
*/
struct scsi_mode_sense_10_response {
uint16_t mode_data_length;
uint8_t medium_type;
uint8_t device_specific_parameter;
uint8_t longlba;
uint8_t reserved5;
uint16_t block_descriptor_length;
} __packed;
static int update_disk_info(struct scsi_ctx *const ctx)
{
int status = disk_access_status(ctx->disk);
if (disk_access_ioctl(ctx->disk, DISK_IOCTL_GET_SECTOR_COUNT, &ctx->sector_count) != 0) {
ctx->sector_count = 0;
status = -EIO;
}
if (disk_access_ioctl(ctx->disk, DISK_IOCTL_GET_SECTOR_SIZE, &ctx->sector_size) != 0) {
ctx->sector_size = 0;
status = -EIO;
}
if (ctx->sector_size > CONFIG_USBD_MSC_SCSI_BUFFER_SIZE) {
status = -ENOMEM;
}
return status;
}
static size_t good(struct scsi_ctx *ctx, size_t data_in_bytes)
{
ctx->status = GOOD;
ctx->sense_key = NO_SENSE;
ctx->asc = NO_ADDITIONAL_SENSE_INFORMATION;
return data_in_bytes;
}
static size_t illegal_request(struct scsi_ctx *ctx, enum scsi_additional_sense_code asc)
{
ctx->status = CHECK_CONDITION;
ctx->sense_key = ILLEGAL_REQUEST;
ctx->asc = asc;
return 0;
}
static size_t not_ready(struct scsi_ctx *ctx, enum scsi_additional_sense_code asc)
{
ctx->status = CHECK_CONDITION;
ctx->sense_key = NOT_READY;
ctx->asc = asc;
return 0;
}
static size_t medium_error(struct scsi_ctx *ctx, enum scsi_additional_sense_code asc)
{
ctx->status = CHECK_CONDITION;
ctx->sense_key = MEDIUM_ERROR;
ctx->asc = asc;
return 0;
}
void scsi_init(struct scsi_ctx *ctx, const char *disk, const char *vendor,
const char *product, const char *revision)
{
memset(ctx, 0, sizeof(struct scsi_ctx));
ctx->disk = disk;
ctx->vendor = vendor;
ctx->product = product;
ctx->revision = revision;
scsi_reset(ctx);
}
void scsi_reset(struct scsi_ctx *ctx)
{
ctx->prevent_removal = false;
ctx->medium_loaded = true;
}
/* SPC-5 TEST UNIT READY command */
SCSI_CMD_HANDLER(TEST_UNIT_READY)
{
if (!ctx->medium_loaded || update_disk_info(ctx) != DISK_STATUS_OK) {
return not_ready(ctx, MEDIUM_NOT_PRESENT);
} else {
return good(ctx, 0);
}
}
/* SPC-5 REQUEST SENSE command */
SCSI_CMD_HANDLER(REQUEST_SENSE)
{
struct scsi_request_sense_response r;
int length;
ctx->cmd_is_data_read = true;
/* SPC-2 should ignore DESC (it was reserved)
* SPC-3 can ignore DESC if not supported
* SPC-4 and later shall error out if DESC is not supported
*/
if ((CLAIMED_CONFORMANCE_VERSION >= INQUIRY_VERSION_SPC_4) &&
(GET_REQUEST_SENSE_DESC(cmd))) {
return illegal_request(ctx, INVALID_FIELD_IN_CDB);
}
r.valid_code = SENSE_CODE_CURRENT_ERRORS;
r.obsolete = 0;
r.filemark_eom_ili_sense_key = ctx->sense_key & SENSE_KEY_MASK;
r.information = sys_cpu_to_be32(0);
r.additional_sense_length = sizeof(struct scsi_request_sense_response) - 1 -
offsetof(struct scsi_request_sense_response, additional_sense_length);
r.command_specific_information = sys_cpu_to_be32(0);
r.additional_sense_with_qualifier = sys_cpu_to_be16(ctx->asc);
r.field_replaceable_unit_code = 0;
r.sksv = 0;
r.sense_key_specific = sys_cpu_to_be16(0);
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
length = MIN(cmd->allocation_length, sizeof(r));
memcpy(data_in_buf, &r, length);
/* REQUEST SENSE completed successfully, old sense information is
* cleared according to SPC-5.
*/
return good(ctx, length);
}
static int fill_inquiry(struct scsi_ctx *ctx,
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
{
/* For simplicity prepare whole response on stack and then copy
* requested length.
*/
struct scsi_inquiry_response r;
memset(&r, 0, sizeof(struct scsi_inquiry_response));
/* Accessible; Direct access block device (SBC) */
r.peripheral = 0x00;
/* Removable; not a part of conglomerate. Note that when device is
* accessible via USB Mass Storage, it should always be marked as
* removable to allow Safely Remove Hardware.
*/
r.rmb = 0x80;
r.version = CLAIMED_CONFORMANCE_VERSION;
/* ACA not supported; No SAM-5 LUNs; Complies to SPC */
r.format = 0x02;
r.additional_length = sizeof(struct scsi_inquiry_response) - 1 -
offsetof(struct scsi_inquiry_response, additional_length);
/* No embedded storage array controller available */
r.sccs = 0x00;
/* No embedded enclosure services */
r.encserv = 0x00;
/* Does not support SAM-5 command management model */
r.cmdque = 0x00;
strncpy(r.scsi_vendor, ctx->vendor, sizeof(r.scsi_vendor));
strncpy(r.product, ctx->product, sizeof(r.product));
strncpy(r.revision, ctx->revision, sizeof(r.revision));
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
memcpy(buf, &r, sizeof(r));
return sizeof(r);
}
static int fill_vpd_page(struct scsi_ctx *ctx, enum vpd_page_code page,
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
{
uint16_t offset = 0;
uint8_t *page_start = &buf[4];
switch (page) {
case VPD_SUPPORTED_VPD_PAGES:
/* Page Codes must appear in ascending order */
page_start[offset++] = VPD_SUPPORTED_VPD_PAGES;
page_start[offset++] = VPD_UNIT_SERIAL_NUMBER;
page_start[offset++] = VPD_DEVICE_IDENTIFICATION;
break;
case VPD_DEVICE_IDENTIFICATION:
/* Absolute minimum is one vendor based descriptor formed by
* concatenating Vendor ID and Unit Serial Number
*
* Other descriptors (EUI-64 or NAA) should be there but should
* is equivalent to "it is strongly recommended" and adding them
* is pretty much problematic because these descriptors involve
* (additional) unique identifiers.
*/
page_start[offset++] = CODE_SET_ASCII;
page_start[offset++] = DESIGNATOR_T10_VENDOR_ID_BASED;
page_start[offset++] = 0x00;
page_start[offset++] = T10_VENDOR_LENGTH + sizeof(UNIT_SERIAL_NUMBER) - 1;
strncpy(&page_start[offset], ctx->vendor, T10_VENDOR_LENGTH);
offset += T10_VENDOR_LENGTH;
memcpy(&page_start[offset], UNIT_SERIAL_NUMBER, sizeof(UNIT_SERIAL_NUMBER) - 1);
offset += sizeof(UNIT_SERIAL_NUMBER) - 1;
break;
case VPD_UNIT_SERIAL_NUMBER:
memcpy(page_start, UNIT_SERIAL_NUMBER, sizeof(UNIT_SERIAL_NUMBER) - 1);
offset += sizeof(UNIT_SERIAL_NUMBER) - 1;
break;
default:
return -ENOTSUP;
}
/* Accessible; Direct access block device (SBC) */
buf[0] = 0x00;
buf[1] = page;
sys_put_be16(offset, &buf[2]);
return offset + 4;
}
/* SPC-5 6.7 INQUIRY command */
SCSI_CMD_HANDLER(INQUIRY)
{
int ret;
ctx->cmd_is_data_read = true;
if (cmd->cmddt_evpd & INQUIRY_CMDDT_OBSOLETE) {
/* Optional in SPC-2 and later obsoleted, do not support it */
ret = -EINVAL;
} else if (cmd->cmddt_evpd & INQUIRY_EVPD) {
/* Linux won't ask for VPD unless enabled with
* echo "Zephyr:Disk:0x10000000" > /proc/scsi/device_info
*/
ret = MIN(sys_be16_to_cpu(cmd->allocation_length),
fill_vpd_page(ctx, cmd->page_code, data_in_buf));
} else if (cmd->page_code != 0) {
LOG_WRN("Page Code is %d but EVPD set", cmd->page_code);
ret = -EINVAL;
} else {
/* Standard inquiry */
ret = MIN(sys_be16_to_cpu(cmd->allocation_length),
fill_inquiry(ctx, data_in_buf));
}
if (ret < 0) {
return illegal_request(ctx, INVALID_FIELD_IN_CDB);
}
return good(ctx, ret);
}
/* SPC-5 6.14 MODE SENSE(6) command */
SCSI_CMD_HANDLER(MODE_SENSE_6)
{
struct scsi_mode_sense_6_response r;
int length;
ctx->cmd_is_data_read = true;
if (cmd->page != MODE_SENSE_PAGE_CODE_ALL_PAGES || cmd->subpage != 0) {
return illegal_request(ctx, INVALID_FIELD_IN_CDB);
}
r.mode_data_length = 3;
r.medium_type = 0x00;
r.device_specific_parameter = 0x00;
r.block_descriptor_length = 0x00;
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
length = MIN(cmd->allocation_length, sizeof(r));
memcpy(data_in_buf, &r, length);
return good(ctx, length);
}
/* SBC-4 5.31 START STOP UNIT command */
SCSI_CMD_HANDLER(START_STOP_UNIT)
{
bool medium_loaded = ctx->medium_loaded;
/* Safe Hardware Removal is essentially START STOP UNIT command that
* asks to eject the media. Disk is shown as safely removed when
* device (SCSI target) responds with NOT READY/MEDIUM NOT PRESENT to
* TEST UNIT READY command
*/
if (GET_POWER_CONDITION(cmd) == POWER_COND_START_VALID) {
if (GET_LOEJ(cmd)) {
if (GET_START(cmd)) {
medium_loaded = true;
} else {
medium_loaded = false;
}
}
}
if (!medium_loaded && ctx->medium_loaded && ctx->prevent_removal) {
return illegal_request(ctx, MEDIUM_REMOVAL_PREVENTED);
}
ctx->medium_loaded = medium_loaded;
return good(ctx, 0);
}
/* SBC-4 5.15 PREVENT ALLOW MEDIUM REMOVAL command */
SCSI_CMD_HANDLER(PREVENT_ALLOW_MEDIUM_REMOVAL)
{
switch (GET_PREVENT(cmd)) {
case MEDIUM_REMOVAL_ALLOWED:
ctx->prevent_removal = false;
break;
case MEDIUM_REMOVAL_SHALL_BE_PREVENTED:
ctx->prevent_removal = true;
break;
case PREVENT_OBSOLETE_2:
case PREVENT_OBSOLETE_3:
break;
}
return good(ctx, 0);
}
/* MMC-6 6.23 READ FORMAT CAPACITIES command
* Microsoft Windows issues this command for all USB drives (no idea why)
*/
SCSI_CMD_HANDLER(READ_FORMAT_CAPACITIES)
{
struct scsi_read_format_capacities_response r;
int length;
ctx->cmd_is_data_read = true;
memset(&r, 0, sizeof(r));
r.header.capacity_list_length = sizeof(r) - sizeof(r.header);
if (update_disk_info(ctx) < 0) {
r.desc.number_of_blocks = sys_cpu_to_be32(UINT32_MAX);
r.desc.type = NO_MEDIA_PRESENT_OR_UNKNOWN_CAPACITY;
} else {
r.desc.number_of_blocks = sys_cpu_to_be32(ctx->sector_count);
r.desc.type = FORMATTED_MEDIA;
}
r.desc.block_length = sys_cpu_to_be32(ctx->sector_size);
ctx->cmd_is_data_read = true;
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
length = MIN(sys_be16_to_cpu(cmd->allocation_length), sizeof(r));
memcpy(data_in_buf, &r, length);
return good(ctx, length);
}
/* SBC-4 5.20 READ CAPACITY (10) command */
SCSI_CMD_HANDLER(READ_CAPACITY_10)
{
struct scsi_read_capacity_10_response r;
ctx->cmd_is_data_read = true;
if (!ctx->medium_loaded || update_disk_info(ctx) != DISK_STATUS_OK) {
return not_ready(ctx, MEDIUM_NOT_PRESENT);
}
r.last_lba = sys_cpu_to_be32(ctx->sector_count ? ctx->sector_count - 1 : 0);
r.block_length = sys_cpu_to_be32(ctx->sector_size);
ctx->cmd_is_data_read = true;
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
memcpy(data_in_buf, &r, sizeof(r));
return good(ctx, sizeof(r));
}
static int
validate_transfer_length(struct scsi_ctx *ctx, uint32_t lba, uint16_t length)
{
uint32_t last_lba = lba + length - 1;
if (lba >= ctx->sector_count) {
LOG_WRN("LBA %d is out of range", lba);
return -EINVAL;
}
/* SBC-4 explicitly mentions that transfer length 0 is OK */
if (length == 0) {
return 0;
}
if ((last_lba >= ctx->sector_count) || (last_lba < lba)) {
LOG_WRN("%d blocks starting at %d go out of bounds", length, lba);
return -EINVAL;
}
return 0;
}
static size_t fill_read_10(struct scsi_ctx *ctx,
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
{
uint32_t sectors;
sectors = MIN(CONFIG_USBD_MSC_SCSI_BUFFER_SIZE, ctx->remaining_data) / ctx->sector_size;
if (disk_access_read(ctx->disk, buf, ctx->lba, sectors) != 0) {
/* Terminate transfer */
sectors = 0;
}
ctx->lba += sectors;
return sectors * ctx->sector_size;
}
SCSI_CMD_HANDLER(READ_10)
{
uint32_t lba = sys_be32_to_cpu(cmd->lba);
uint16_t transfer_length = sys_be16_to_cpu(cmd->transfer_length);
ctx->cmd_is_data_read = true;
if (!ctx->medium_loaded || update_disk_info(ctx) != DISK_STATUS_OK) {
return not_ready(ctx, MEDIUM_NOT_PRESENT);
}
if (validate_transfer_length(ctx, lba, transfer_length)) {
return illegal_request(ctx, LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
}
ctx->read_cb = fill_read_10;
ctx->lba = lba;
ctx->remaining_data = ctx->sector_size * transfer_length;
return good(ctx, 0);
}
static size_t store_write_10(struct scsi_ctx *ctx, const uint8_t *buf, size_t length)
{
uint32_t remaining_sectors;
uint32_t sectors;
bool error = false;
remaining_sectors = ctx->remaining_data / ctx->sector_size;
sectors = MIN(length, ctx->remaining_data) / ctx->sector_size;
if (disk_access_write(ctx->disk, buf, ctx->lba, sectors) != 0) {
/* Flush cache and terminate transfer */
sectors = 0;
remaining_sectors = 0;
error = true;
}
/* Flush cache if this is the last sector in transfer */
if (remaining_sectors - sectors == 0) {
if (disk_access_ioctl(ctx->disk, DISK_IOCTL_CTRL_SYNC, NULL)) {
LOG_ERR("Disk cache sync failed");
error = true;
}
}
ctx->lba += sectors;
if (error) {
return medium_error(ctx, WRITE_ERROR);
} else {
return sectors * ctx->sector_size;
}
}
SCSI_CMD_HANDLER(WRITE_10)
{
uint32_t lba = sys_be32_to_cpu(cmd->lba);
uint16_t transfer_length = sys_be16_to_cpu(cmd->transfer_length);
ctx->cmd_is_data_write = true;
if (!ctx->medium_loaded || update_disk_info(ctx) != DISK_STATUS_OK) {
return not_ready(ctx, MEDIUM_NOT_PRESENT);
}
if (validate_transfer_length(ctx, lba, transfer_length)) {
return illegal_request(ctx, LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
}
ctx->write_cb = store_write_10;
ctx->lba = lba;
ctx->remaining_data = ctx->sector_size * transfer_length;
return good(ctx, 0);
}
/* SPC-5 6.15 MODE SENSE(10) command */
SCSI_CMD_HANDLER(MODE_SENSE_10)
{
struct scsi_mode_sense_10_response r;
int length;
ctx->cmd_is_data_read = true;
if (cmd->page != MODE_SENSE_PAGE_CODE_ALL_PAGES || cmd->subpage != 0) {
return illegal_request(ctx, INVALID_FIELD_IN_CDB);
}
r.mode_data_length = sys_cpu_to_be16(6);
r.medium_type = 0x00;
r.device_specific_parameter = 0x00;
r.longlba = 0x00;
r.reserved5 = 0x00;
r.block_descriptor_length = sys_cpu_to_be16(0);
BUILD_ASSERT(sizeof(r) <= CONFIG_USBD_MSC_SCSI_BUFFER_SIZE);
length = MIN(sys_be16_to_cpu(cmd->allocation_length), sizeof(r));
memcpy(data_in_buf, &r, length);
return good(ctx, length);
}
int scsi_usb_boot_cmd_len(const uint8_t *cb, int len)
{
/* Universal Serial Bus Mass Storage Specification For Bootability
* requires device to accept CBW padded to 12 bytes for commands
* documented in Bootability specification. Windows 11 uses padding
* for REQUEST SENSE command.
*/
if (len != 12) {
return len;
}
switch (cb[0]) {
case TEST_UNIT_READY: return sizeof(SCSI_CMD_STRUCT(TEST_UNIT_READY));
case REQUEST_SENSE: return sizeof(SCSI_CMD_STRUCT(REQUEST_SENSE));
case INQUIRY: return sizeof(SCSI_CMD_STRUCT(INQUIRY));
case READ_CAPACITY_10: return sizeof(SCSI_CMD_STRUCT(READ_CAPACITY_10));
case READ_10: return sizeof(SCSI_CMD_STRUCT(READ_10));
case WRITE_10: return sizeof(SCSI_CMD_STRUCT(WRITE_10));
case MODE_SENSE_10: return sizeof(SCSI_CMD_STRUCT(MODE_SENSE_10));
default: return len;
}
}
size_t scsi_cmd(struct scsi_ctx *ctx, const uint8_t *cb, int len,
uint8_t data_in_buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
{
ctx->cmd_is_data_read = false;
ctx->cmd_is_data_write = false;
ctx->remaining_data = 0;
ctx->read_cb = NULL;
ctx->write_cb = NULL;
#define SCSI_CMD(opcode) do { \
if (len == sizeof(SCSI_CMD_STRUCT(opcode)) && cb[0] == opcode) { \
LOG_DBG("SCSI " #opcode); \
if (GET_CONTROL_NACA(((SCSI_CMD_STRUCT(opcode)*)cb))) { \
return illegal_request(ctx, INVALID_FIELD_IN_CDB); \
} \
return scsi_##opcode(ctx, (SCSI_CMD_STRUCT(opcode)*)cb, \
data_in_buf); \
} \
} while (0)
SCSI_CMD(TEST_UNIT_READY);
SCSI_CMD(REQUEST_SENSE);
SCSI_CMD(INQUIRY);
SCSI_CMD(MODE_SENSE_6);
SCSI_CMD(START_STOP_UNIT);
SCSI_CMD(PREVENT_ALLOW_MEDIUM_REMOVAL);
SCSI_CMD(READ_FORMAT_CAPACITIES);
SCSI_CMD(READ_CAPACITY_10);
SCSI_CMD(READ_10);
SCSI_CMD(WRITE_10);
SCSI_CMD(MODE_SENSE_10);
LOG_ERR("Unknown SCSI opcode 0x%02x", cb[0]);
return illegal_request(ctx, INVALID_FIELD_IN_CDB);
}
bool scsi_cmd_is_data_read(struct scsi_ctx *ctx)
{
return ctx->cmd_is_data_read;
}
bool scsi_cmd_is_data_write(struct scsi_ctx *ctx)
{
return ctx->cmd_is_data_write;
}
size_t scsi_cmd_remaining_data_len(struct scsi_ctx *ctx)
{
return ctx->remaining_data;
}
size_t scsi_read_data(struct scsi_ctx *ctx,
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
{
size_t retrieved = 0;
__ASSERT_NO_MSG(ctx->cmd_is_data_read);
if ((ctx->remaining_data > 0) && ctx->read_cb) {
retrieved = ctx->read_cb(ctx, buf);
}
ctx->remaining_data -= retrieved;
if (retrieved == 0) {
/* Terminate transfer. Host will notice data residue. */
ctx->remaining_data = 0;
}
return retrieved;
}
size_t scsi_write_data(struct scsi_ctx *ctx, const uint8_t *buf, size_t length)
{
size_t processed = 0;
__ASSERT_NO_MSG(ctx->cmd_is_data_write);
length = MIN(length, ctx->remaining_data);
if ((length > 0) && ctx->write_cb) {
processed = ctx->write_cb(ctx, buf, length);
}
ctx->remaining_data -= processed;
if (processed == 0) {
/* Terminate transfer. Host will notice data residue. */
ctx->remaining_data = 0;
}
return processed;
}
enum scsi_status_code scsi_cmd_get_status(struct scsi_ctx *ctx)
{
return ctx->status;
}