/* * 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; } /** @} */