/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "usbd_msc_scsi.h" #include 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; }