From 806ebb18faf3285bb4dfa3c9c2caeac77dca7f34 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 27 Jun 2013 01:03:50 +0200 Subject: [PATCH] add MSC (Mass Storage Class) support --- include/libopencm3/usb/msc.h | 93 ++++ lib/stm32/f1/Makefile | 2 +- lib/stm32/f2/Makefile | 2 +- lib/stm32/f4/Makefile | 2 +- lib/usb/usb_msc.c | 814 +++++++++++++++++++++++++++++++++++ 5 files changed, 910 insertions(+), 3 deletions(-) create mode 100644 include/libopencm3/usb/msc.h create mode 100644 lib/usb/usb_msc.c diff --git a/include/libopencm3/usb/msc.h b/include/libopencm3/usb/msc.h new file mode 100644 index 00000000..cf9c54a6 --- /dev/null +++ b/include/libopencm3/usb/msc.h @@ -0,0 +1,93 @@ +/** @defgroup usb_msc_defines USB MSC Type Definitions + +@brief Defined Constants and Types for the USB MSC Type Definitions + +@ingroup USB_defines + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2013 +Weston Schmidt +Pavol Rusnak + +@date 27 June 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2013 Weston Schmidt + * Copyright (C) 2013 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#ifndef __MSC_H +#define __MSC_H + +typedef struct _usbd_mass_storage usbd_mass_storage; + +/* Definitions of Mass Storage Class from: + * + * (A) "Universal Serial Bus Mass Storage Class Bulk-Only Transport + * Revision 1.0" + * + * (B) "Universal Serial Bus Mass Storage Class Specification Overview + * Revision 1.0" + */ + +/* (A) Table 4.5: Mass Storage Device Class Code */ +#define USB_CLASS_MSC 0x08 + +/* (B) Table 2.1: Class Subclass Code */ +#define USB_MSC_SUBCLASS_RBC 0x01 +#define USB_MSC_SUBCLASS_ATAPI 0x02 +#define USB_MSC_SUBCLASS_UFI 0x04 +#define USB_MSC_SUBCLASS_SCSI 0x06 +#define USB_MSC_SUBCLASS_LOCKABLE 0x07 +#define USB_MSC_SUBCLASS_IEEE1667 0x08 + +/* (B) Table 3.1 Mass Storage Interface Class Control Protocol Codes */ +#define USB_MSC_PROTOCOL_CBI 0x00 +#define USB_MSC_PROTOCOL_CBI_ALT 0x01 +#define USB_MSC_PROTOCOL_BBB 0x50 + +/* (B) Table 4.1 Mass Storage Request Codes */ +#define USB_MSC_REQ_CODES_ADSC 0x00 +#define USB_MSC_REQ_CODES_GET 0xFC +#define USB_MSC_REQ_CODES_PUT 0xFD +#define USB_MSC_REQ_CODES_GML 0xFE +#define USB_MSC_REQ_CODES_BOMSR 0xFF + +/* (A) Table 3.1/3.2 Class-Specific Request Codes */ +#define USB_MSC_REQ_BULK_ONLY_RESET 0xFF +#define USB_MSC_REQ_GET_MAX_LUN 0xFE + +usbd_mass_storage *usb_msc_init(usbd_device *usbd_dev, + uint8_t ep_in, uint8_t ep_in_size, + uint8_t ep_out, uint8_t ep_out_size, + const char *vendor_id, + const char *product_id, + const char *product_revision_level, + const uint32_t block_count, + int (*read_block)(uint32_t lba, uint8_t *copy_to), + int (*write_block)(uint32_t lba, const uint8_t *copy_from)); + +#endif + +/**@}*/ diff --git a/lib/stm32/f1/Makefile b/lib/stm32/f1/Makefile index ea0b49dd..a4ee7a12 100644 --- a/lib/stm32/f1/Makefile +++ b/lib/stm32/f1/Makefile @@ -45,7 +45,7 @@ OBJS += crc_common_all.o dac_common_all.o dma_common_l1f013.o \ flash_common_f01.o OBJS += usb.o usb_control.o usb_standard.o usb_f103.o usb_f107.o \ - usb_fx07_common.o + usb_fx07_common.o usb_msc.o VPATH += ../../usb:../:../../cm3:../common diff --git a/lib/stm32/f2/Makefile b/lib/stm32/f2/Makefile index 90fe2edf..92916686 100644 --- a/lib/stm32/f2/Makefile +++ b/lib/stm32/f2/Makefile @@ -45,7 +45,7 @@ OBJS += crc_common_all.o dac_common_all.o dma_common_f24.o \ crypto_common_f24.o exti_common_all.o rcc_common_all.o OBJS += usb.o usb_standard.o usb_control.o usb_fx07_common.o \ - usb_f107.o usb_f207.o + usb_f107.o usb_f207.o usb_msc.o VPATH += ../../usb:../:../../cm3:../common diff --git a/lib/stm32/f4/Makefile b/lib/stm32/f4/Makefile index 9964e29b..475d9178 100644 --- a/lib/stm32/f4/Makefile +++ b/lib/stm32/f4/Makefile @@ -49,7 +49,7 @@ OBJS += crc_common_all.o dac_common_all.o dma_common_f24.o \ rcc_common_all.o OBJS += usb.o usb_standard.o usb_control.o usb_fx07_common.o \ - usb_f107.o usb_f207.o + usb_f107.o usb_f207.o usb_msc.o OBJS += mac.o phy.o mac_stm32fxx7.o phy_ksz8051mll.o fmc.o diff --git a/lib/usb/usb_msc.c b/lib/usb/usb_msc.c new file mode 100644 index 00000000..573590dc --- /dev/null +++ b/lib/usb/usb_msc.c @@ -0,0 +1,814 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2013 Weston Schmidt + * Copyright (C) 2013 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "usb_private.h" + +/* Definitions of Mass Storage Class from: + * + * (A) "Universal Serial Bus Mass Storage Class Bulk-Only Transport + * Revision 1.0" + * + * (B) "Universal Serial Bus Mass Storage Class Specification Overview + * Revision 1.0" + */ + +/* Command Block Wrapper */ +#define CBW_SIGNATURE 0x43425355 +#define CBW_STATUS_SUCCESS 0 +#define CBW_STATUS_FAILED 1 +#define CBW_STATUS_PHASE_ERROR 2 + +/* Command Status Wrapper */ +#define CSW_SIGNATURE 0x53425355 +#define CSW_STATUS_SUCCESS 0 +#define CSW_STATUS_FAILED 1 +#define CSW_STATUS_PHASE_ERROR 2 + +/* Implemented SCSI Commands */ +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_REQUEST_SENSE 0x03 +#define SCSI_FORMAT_UNIT 0x04 +#define SCSI_READ_6 0x08 +#define SCSI_WRITE_6 0x0A +#define SCSI_INQUIRY 0x12 +#define SCSI_MODE_SENSE_6 0x1A +#define SCSI_SEND_DIAGNOSTIC 0x1D +#define SCSI_READ_CAPACITY 0x25 +#define SCSI_READ_10 0x28 + + +/* Required SCSI Commands */ + +/* Optional SCSI Commands */ +#define SCSI_REPORT_LUNS 0xA0 +#define SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SCSI_MODE_SELECT_6 0x15 +#define SCSI_MODE_SELECT_10 0x55 +#define SCSI_MODE_SENSE_10 0x5A +#define SCSI_READ_12 0xA8 +#define SCSI_READ_FORMAT_CAPACITIES 0x23 +#define SCSI_READ_TOC_PMA_ATIP 0x43 +#define SCSI_START_STOP_UNIT 0x1B +#define SCSI_SYNCHRONIZE_CACHE 0x35 +#define SCSI_VERIFY 0x2F +#define SCSI_WRITE_10 0x2A +#define SCSI_WRITE_12 0xAA + +/* The sense codes */ +enum sbc_sense_key { + SBC_SENSE_KEY_NO_SENSE = 0x00, + SBC_SENSE_KEY_RECOVERED_ERROR = 0x01, + SBC_SENSE_KEY_NOT_READY = 0x02, + SBC_SENSE_KEY_MEDIUM_ERROR = 0x03, + SBC_SENSE_KEY_HARDWARE_ERROR = 0x04, + SBC_SENSE_KEY_ILLEGAL_REQUEST = 0x05, + SBC_SENSE_KEY_UNIT_ATTENTION = 0x06, + SBC_SENSE_KEY_DATA_PROTECT = 0x07, + SBC_SENSE_KEY_BLANK_CHECK = 0x08, + SBC_SENSE_KEY_VENDOR_SPECIFIC = 0x09, + SBC_SENSE_KEY_COPY_ABORTED = 0x0A, + SBC_SENSE_KEY_ABORTED_COMMAND = 0x0B, + SBC_SENSE_KEY_VOLUME_OVERFLOW = 0x0D, + SBC_SENSE_KEY_MISCOMPARE = 0x0E +}; + +enum sbc_asc { + SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION = 0x00, + SBC_ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03, + SBC_ASC_LOGICAL_UNIT_NOT_READY = 0x04, + SBC_ASC_UNRECOVERED_READ_ERROR = 0x11, + SBC_ASC_INVALID_COMMAND_OPERATION_CODE = 0x20, + SBC_ASC_LBA_OUT_OF_RANGE = 0x21, + SBC_ASC_INVALID_FIELD_IN_CDB = 0x24, + SBC_ASC_WRITE_PROTECTED = 0x27, + SBC_ASC_NOT_READY_TO_READY_CHANGE = 0x28, + SBC_ASC_FORMAT_ERROR = 0x31, + SBC_ASC_MEDIUM_NOT_PRESENT = 0x3A +}; + +enum sbc_ascq { + SBC_ASCQ_NA = 0x00, + SBC_ASCQ_FORMAT_COMMAND_FAILED = 0x01, + SBC_ASCQ_INITIALIZING_COMMAND_REQUIRED = 0x02, + SBC_ASCQ_OPERATION_IN_PROGRESS = 0x07 +}; + +enum trans_event { + EVENT_CBW_VALID, + EVENT_NEED_STATUS +}; + +struct usb_msc_cbw { + uint32_t dCBWSignature; + uint32_t dCBWTag; + uint32_t dCBWDataTransferLength; + uint8_t bmCBWFlags; + uint8_t bCBWLUN; + uint8_t bCBWCBLength; + uint8_t CBWCB[16]; +} __attribute__((packed)); + +struct usb_msc_csw { + uint32_t dCSWSignature; + uint32_t dCSWTag; + uint32_t dCSWDataResidue; + uint8_t bCSWStatus; +} __attribute__((packed)); + +struct sbc_sense_info { + uint8_t key; + uint8_t asc; + uint8_t ascq; +}; + +struct usb_msc_trans { + uint8_t cbw_cnt; /* Read until 31 bytes */ + union { + struct usb_msc_cbw cbw; + uint8_t buf[1]; + } cbw; + + uint32_t bytes_to_read; + uint32_t bytes_to_write; + uint32_t byte_count; /* Either read until equal to bytes_to_read or + write until equal to bytes_to_write. */ + uint32_t lba_start; + uint32_t block_count; + uint32_t current_block; + + uint8_t msd_buf[512]; + + bool csw_valid; + uint8_t csw_sent; /* Write until 13 bytes */ + union { + struct usb_msc_csw csw; + uint8_t buf[1]; + } csw; +}; + +struct _usbd_mass_storage { + usbd_device *usbd_dev; + uint8_t ep_in; + uint8_t ep_in_size; + uint8_t ep_out; + uint8_t ep_out_size; + + const char *vendor_id; + const char *product_id; + const char *product_revision_level; + uint32_t block_count; + + int (*read_block)(uint32_t lba, uint8_t *copy_to); + int (*write_block)(uint32_t lba, const uint8_t *copy_from); + + void (*lock)(void); + void (*unlock)(void); + + struct usb_msc_trans trans; + struct sbc_sense_info sense; +}; + +static usbd_mass_storage _mass_storage; + +/*-- SCSI Base Responses -----------------------------------------------------*/ + +static const uint8_t _spc3_inquiry_response[36] = { + 0x00, /* Byte 0: Peripheral Qualifier = 0, Peripheral Device Type = 0 */ + 0x80, /* Byte 1: RMB = 1, Reserved = 0 */ + 0x04, /* Byte 2: Version = 0 */ + 0x02, /* Byte 3: Obsolete = 0, NormACA = 0, HiSup = 0, Response Data Format = 2 */ + 0x20, /* Byte 4: Additional Length (n-4) = 31 + 4 */ + 0x00, /* Byte 5: SCCS = 0, ACC = 0, TPGS = 0, 3PC = 0, Reserved = 0, Protect = 0 */ + 0x00, /* Byte 6: BQue = 0, EncServ = 0, VS = 0, MultiP = 0, MChngr = 0, Obsolete = 0, Addr16 = 0 */ + 0x00, /* Byte 7: Obsolete = 0, Wbus16 = 0, Sync = 0, Linked = 0, CmdQue = 0, VS = 0 */ + /* Byte 8 - Byte 15: Vendor Identification */ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + /* Byte 16 - Byte 31: Product Identification */ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + /* Byte 32 - Byte 35: Product Revision Level */ + 0x20, 0x20, 0x20, 0x20 +}; + +static const uint8_t _spc3_request_sense[18] = { + 0x70, /* Byte 0: VALID = 0, Response Code = 112 */ + 0x00, /* Byte 1: Obsolete = 0 */ + 0x00, /* Byte 2: Filemark = 0, EOM = 0, ILI = 0, Reserved = 0, Sense Key = 0 */ + /* Byte 3 - Byte 6: Information = 0 */ + 0, 0, 0, 0, + 0x0a, /* Byte 7: Additional Sense Length = 10 */ + /* Byte 8 - Byte 11: Command Specific Info = 0 */ + 0, 0, 0, 0, + 0x00, /* Byte 12: Additional Sense Code (ASC) = 0 */ + 0x00, /* Byte 13: Additional Sense Code Qualifier (ASCQ) = 0 */ + 0x00, /* Byte 14: Field Replaceable Unit Code (FRUC) = 0 */ + 0x00, /* Byte 15: SKSV = 0, SenseKeySpecific[0] = 0 */ + 0x00, /* Byte 16: SenseKeySpecific[0] = 0 */ + 0x00 /* Byte 17: SenseKeySpecific[0] = 0 */ +}; + +/*-- SCSI Layer --------------------------------------------------------------*/ + +static void set_sbc_status(usbd_mass_storage *ms, + enum sbc_sense_key key, + enum sbc_asc asc, + enum sbc_ascq ascq) +{ + ms->sense.key = (uint8_t) key; + ms->sense.asc = (uint8_t) asc; + ms->sense.ascq = (uint8_t) ascq; +} + +static void set_sbc_status_good(usbd_mass_storage *ms) +{ + set_sbc_status(ms, + SBC_SENSE_KEY_NO_SENSE, + SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION, + SBC_ASCQ_NA); +} + +static uint8_t *get_cbw_buf(struct usb_msc_trans *trans) +{ + return &trans->cbw.cbw.CBWCB[0]; +} + +static void scsi_read_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 8) | buf[3]; + trans->block_count = buf[4]; + trans->current_block = 0; + + /* TODO: Check the lba & block_count for range. */ + + /* both are in terms of 512 byte blocks, so shift by 9 */ + trans->bytes_to_write = trans->block_count << 9; + + set_sbc_status_good(ms); + } +} + +static void scsi_write_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = ((0x1f & buf[1]) << 16) | (buf[2] << 8) | buf[3]; + trans->block_count = buf[4]; + trans->current_block = 0; + + trans->bytes_to_read = trans->block_count << 9; + } +} + +static void scsi_write_10(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 24) | (buf[3] << 16) | + (buf[4] << 8) | buf[5]; + trans->block_count = (buf[7] << 8) | buf[8]; + trans->current_block = 0; + + trans->bytes_to_read = trans->block_count << 9; + } +} + +static void scsi_read_10(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 24) | (buf[3] << 16) | (buf[4] << 8) | buf[5]; + trans->block_count = (buf[7] << 8) | buf[8]; + + /* TODO: Check the lba & block_count for range. */ + + /* both are in terms of 512 byte blocks, so shift by 9 */ + trans->bytes_to_write = trans->block_count << 9; + + set_sbc_status_good(ms); + } +} + +static void scsi_read_capacity(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + trans->msd_buf[0] = ms->block_count >> 24; + trans->msd_buf[1] = 0xff & (ms->block_count >> 16); + trans->msd_buf[2] = 0xff & (ms->block_count >> 8); + trans->msd_buf[3] = 0xff & ms->block_count; + + /* Block size: 512 */ + trans->msd_buf[4] = 0; + trans->msd_buf[5] = 0; + trans->msd_buf[6] = 2; + trans->msd_buf[7] = 0; + trans->bytes_to_write = 8; + set_sbc_status_good(ms); + } +} + +static void scsi_format_unit(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint32_t i; + + memset(trans->msd_buf, 0, 512); + + for (i = 0; i < ms->block_count; i++) { + (*ms->write_block)(i, trans->msd_buf); + } + + set_sbc_status_good(ms); + } +} + +static void scsi_request_sense(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = &trans->cbw.cbw.CBWCB[0]; + + trans->bytes_to_write = buf[4]; /* allocation length */ + memcpy(trans->msd_buf, _spc3_request_sense, sizeof(_spc3_request_sense)); + + trans->msd_buf[2] = ms->sense.key; + trans->msd_buf[12] = ms->sense.asc; + trans->msd_buf[13] = ms->sense.ascq; + } +} + +static void scsi_mode_sense_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { +#if 0 + uint8_t *buf; + uint8_t page_code; + uint8_t allocation_length; + + buf = &trans->cbw.cbw.CBWCB[0]; + page_code = buf[2]; + allocation_length = buf[4]; + + if (0x1C == page_code) { /* Informational Exceptions */ +#endif + trans->bytes_to_write = 4; + + trans->msd_buf[0] = 3; /* Num bytes that follow */ + trans->msd_buf[1] = 0; /* Medium Type */ + trans->msd_buf[2] = 0; /* Device specific param */ + trans->csw.csw.dCSWDataResidue = 4; +#if 0 + } else if (0x01 == page_code) { /* Error recovery */ + } else if (0x3F == page_code) { /* All */ + } else { + /* Error */ + trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED; + set_sbc_status(ms, + SBC_SENSE_KEY_ILLEGAL_REQUEST, + SBC_ASC_INVALID_FIELD_IN_CDB, + SBC_ASCQ_NA); + } +#endif + } +} + +static void scsi_inquiry(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t evpd; + uint8_t *buf; + + buf = get_cbw_buf(trans); + evpd = 1 & buf[1]; + + if (0 == evpd) { + size_t len; + trans->bytes_to_write = sizeof(_spc3_inquiry_response); + memcpy(trans->msd_buf, _spc3_inquiry_response, sizeof(_spc3_inquiry_response)); + + len = strlen(ms->vendor_id); + len = MIN(len, 8); + memcpy(&trans->msd_buf[8], ms->vendor_id, len); + + len = strlen(ms->product_id); + len = MIN(len, 16); + memcpy(&trans->msd_buf[16], ms->product_id, len); + + len = strlen(ms->product_revision_level); + len = MIN(len, 4); + memcpy(&trans->msd_buf[32], ms->product_revision_level, len); + + trans->csw.csw.dCSWDataResidue = sizeof(_spc3_inquiry_response); + + set_sbc_status_good(ms); + } else { + /* TODO: Add VPD 0x83 support */ + /* TODO: Add VPD 0x00 support */ + } + } +} + +static void scsi_command(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + /* Setup the default success */ + trans->csw_sent = 0; + trans->csw.csw.dCSWSignature = CSW_SIGNATURE; + trans->csw.csw.dCSWTag = trans->cbw.cbw.dCBWTag; + trans->csw.csw.dCSWDataResidue = 0; + trans->csw.csw.bCSWStatus = CSW_STATUS_SUCCESS; + + trans->bytes_to_write = 0; + trans->bytes_to_read = 0; + trans->byte_count = 0; + } + + switch (trans->cbw.cbw.CBWCB[0]) { + case SCSI_TEST_UNIT_READY: + case SCSI_SEND_DIAGNOSTIC: + /* Do nothing, just send the success. */ + set_sbc_status_good(ms); + break; + case SCSI_FORMAT_UNIT: + scsi_format_unit(ms, trans, event); + break; + case SCSI_REQUEST_SENSE: + scsi_request_sense(ms, trans, event); + break; + case SCSI_MODE_SENSE_6: + scsi_mode_sense_6(ms, trans, event); + break; + case SCSI_READ_6: + scsi_read_6(ms, trans, event); + break; + case SCSI_INQUIRY: + scsi_inquiry(ms, trans, event); + break; + case SCSI_READ_CAPACITY: + scsi_read_capacity(ms, trans, event); + break; + case SCSI_READ_10: + scsi_read_10(ms, trans, event); + break; + case SCSI_WRITE_6: + scsi_write_6(ms, trans, event); + break; + case SCSI_WRITE_10: + scsi_write_10(ms, trans, event); + break; + default: + set_sbc_status(ms, SBC_SENSE_KEY_ILLEGAL_REQUEST, + SBC_ASC_INVALID_COMMAND_OPERATION_CODE, + SBC_ASCQ_NA); + + trans->bytes_to_write = 0; + trans->bytes_to_read = 0; + trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED; + break; + } +} + +/*-- USB Mass Storage Layer --------------------------------------------------*/ + +/** @brief Handle the USB 'OUT' requests. */ +static void msc_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + usbd_mass_storage *ms; + struct usb_msc_trans *trans; + int len, max_len, left; + void *p; + + ms = &_mass_storage; + trans = &ms->trans; + + /* RX only */ + left = sizeof(struct usb_msc_cbw) - trans->cbw_cnt; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->cbw.buf[0x1ff & trans->cbw_cnt]; + len = usbd_ep_read_packet(usbd_dev, ep, p, max_len); + trans->cbw_cnt += len; + + if (sizeof(struct usb_msc_cbw) == trans->cbw_cnt) { + scsi_command(ms, trans, EVENT_CBW_VALID); + if (trans->byte_count < trans->bytes_to_read) { + /* We must wait until there is something to + * read again. */ + return; + } + } + } + + if (trans->byte_count < trans->bytes_to_read) { + if (0 < trans->block_count) { + if ((0 == trans->byte_count) && (NULL != ms->lock)){ + (*ms->lock)(); + } + } + + left = trans->bytes_to_read - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_read_packet(usbd_dev, ep, p, max_len); + trans->byte_count += len; + + if (0 < trans->block_count) { + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->write_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + } else if (trans->byte_count < trans->bytes_to_write) { + if (0 < trans->block_count) { + if ((0 == trans->byte_count) && (NULL != ms->lock)) { + (*ms->lock)(); + } + + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->read_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + + left = trans->bytes_to_write - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len); + trans->byte_count += len; + } else { + if (0 < trans->block_count) { + if (trans->current_block == trans->block_count) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->write_block)(lba, trans->msd_buf)) { + /* Error */ + } + + trans->current_block = 0; + if (NULL != ms->unlock){ + (*ms->unlock)(); + } + } + } + if (false == trans->csw_valid) { + scsi_command(ms, trans, EVENT_NEED_STATUS); + trans->csw_valid = true; + } + + left = sizeof(struct usb_msc_csw) - trans->csw_sent; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->csw.buf[trans->csw_sent]; + len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len); + trans->csw_sent += len; + } + } +} + +/** @brief Handle the USB 'IN' requests. */ +static void msc_data_tx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + usbd_mass_storage *ms; + struct usb_msc_trans *trans; + int len, max_len, left; + void *p; + + ms = &_mass_storage; + trans = &ms->trans; + + if (trans->byte_count < trans->bytes_to_write) { + if (0 < trans->block_count) { + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->read_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + + left = trans->bytes_to_write - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_write_packet(usbd_dev, ep, p, max_len); + trans->byte_count += len; + } else { + if (0 < trans->block_count) { + if (trans->current_block == trans->block_count) { + trans->current_block = 0; + if (NULL != ms->unlock){ + (*ms->unlock)(); + } + } + } + if (false == trans->csw_valid) { + scsi_command(ms, trans, EVENT_NEED_STATUS); + trans->csw_valid = true; + } + + left = sizeof(struct usb_msc_csw) - trans->csw_sent; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->csw.buf[trans->csw_sent]; + len = usbd_ep_write_packet(usbd_dev, ep, p, max_len); + trans->csw_sent += len; + } else if (sizeof(struct usb_msc_csw) == trans->csw_sent) { + /* End of transaction */ + trans->lba_start = 0xffffffff; + trans->block_count = 0; + trans->current_block = 0; + trans->cbw_cnt = 0; + trans->bytes_to_read = 0; + trans->bytes_to_write = 0; + trans->byte_count = 0; + trans->csw_sent = 0; + trans->csw_valid = false; + } + } +} + +/** @brief Handle various control requests related to the msc storage + * interface. + */ +static int msc_control_request(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) +{ + (void)complete; + (void)usbd_dev; + + switch (req->bRequest) { + case USB_MSC_REQ_BULK_ONLY_RESET: + /* Do any special reset code here. */ + return USBD_REQ_HANDLED; + case USB_MSC_REQ_GET_MAX_LUN: + /* Return the number of LUNs. We use 0. */ + *buf[0] = 0; + *len = 1; + return USBD_REQ_HANDLED; + } + + return USBD_REQ_NOTSUPP; +} + +/** @brief Setup the endpoints to be bulk & register the callbacks. */ +static void msc_set_config(usbd_device *usbd_dev, uint16_t wValue) +{ + usbd_mass_storage *ms = &_mass_storage; + + (void)wValue; + + usbd_ep_setup(usbd_dev, ms->ep_in, USB_ENDPOINT_ATTR_BULK, + ms->ep_in_size, msc_data_tx_cb); + usbd_ep_setup(usbd_dev, ms->ep_out, USB_ENDPOINT_ATTR_BULK, + ms->ep_out_size, msc_data_rx_cb); + + usbd_register_control_callback( + usbd_dev, + USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + msc_control_request); +} + +/** @addtogroup usb_msc */ +/** @{ */ + +/** @brief Initializes the USB Mass Storage subsystem. + +@note Currently you can only have this profile active. + +@param[in] usbd_dev The USB device to associate the Mass Storage with. +@param[in] ep_in The USB 'IN' endpoint. +@param[in] ep_in_size The maximum endpoint size. Valid values: 8, 16, 32 or 64 +@param[in] ep_out The USB 'OUT' endpoint. +@param[in] ep_out_size The maximum endpoint size. Valid values: 8, 16, 32 or 64 +@param[in] vendor_id The SCSI vendor ID to return. Maximum used length is 8. +@param[in] product_id The SCSI product ID to return. Maximum used length is 16. +@param[in] product_revision_level The SCSI product revision level to return. + Maximum used length is 4. +@param[in] block_count The number of 512-byte blocks available. +@param[in] read_block The function called when the host requests to read a LBA + block. Must _NOT_ be NULL. +@param[in] write_block The function called when the host requests to write a + LBA block. Must _NOT_ be NULL. + +@return Pointer to the usbd_mass_storage struct. +*/ +usbd_mass_storage *usb_msc_init(usbd_device *usbd_dev, + uint8_t ep_in, uint8_t ep_in_size, + uint8_t ep_out, uint8_t ep_out_size, + const char *vendor_id, + const char *product_id, + const char *product_revision_level, + const uint32_t block_count, + int (*read_block)(uint32_t lba, uint8_t *copy_to), + int (*write_block)(uint32_t lba, const uint8_t *copy_from)) +{ + _mass_storage.usbd_dev = usbd_dev; + _mass_storage.ep_in = ep_in; + _mass_storage.ep_in_size = ep_in_size; + _mass_storage.ep_out = ep_out; + _mass_storage.ep_out_size = ep_out_size; + _mass_storage.vendor_id = vendor_id; + _mass_storage.product_id = product_id; + _mass_storage.product_revision_level = product_revision_level; + _mass_storage.block_count = block_count - 1; + _mass_storage.read_block = read_block; + _mass_storage.write_block = write_block; + _mass_storage.lock = NULL; + _mass_storage.unlock = NULL; + + _mass_storage.trans.lba_start = 0xffffffff; + _mass_storage.trans.block_count = 0; + _mass_storage.trans.current_block = 0; + _mass_storage.trans.cbw_cnt = 0; + _mass_storage.trans.bytes_to_read = 0; + _mass_storage.trans.bytes_to_write = 0; + _mass_storage.trans.byte_count = 0; + _mass_storage.trans.csw_valid = false; + _mass_storage.trans.csw_sent = 0; + + set_sbc_status_good(&_mass_storage); + + usbd_register_set_config_callback(usbd_dev, msc_set_config); + + return &_mass_storage; +} + +/** @} */