1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
19242: usbus/msc: add initial Mass Storage Class support r=benpicco a=dylad

### Contribution description

This PR adds the initial support for Mass Storage Class in USBUS. This PR relies on the RIOT MTD implementation to implement the Mass Storage Class support. With the provided test application, a MTD device will be accessible as a normal storage device on your host computer.
Read and Write operations are allowed.
Multiple LUNs are supported so several MTD devices can be exported through USB.
The MSC relies on SCSI protocol to operate.

Currently there are some limitations:
    Supported host : Linux & Windows (macOS is untested)
    MSC cannot be used if MTD page size > 4096
    MTD device must have at least 512 bytes of memory to be exported.

Please be aware that performance are not so great.

### Testing procedure
Flash `tests/usbus_msc` application on a board with at least one MTD device.
Once the shell has started, prepare one or several MTD devices to be exported using `add_lun` command.
Once ready, start the USB connection with `usb_attach`

All MTD exported should appear as` /dev/sdX` on Linux.

### Issues/PRs references
Supersede #15941 


Co-authored-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
This commit is contained in:
bors[bot] 2023-03-02 22:19:41 +00:00 committed by GitHub
commit 743ae3f095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1713 additions and 0 deletions

View File

@ -8698,3 +8698,29 @@ warning: unable to resolve reference to 'esp32s2_devkit_optional_hardware' for \
warning: unable to resolve reference to 'esp32s3_devkit_optional_hardware' for \ref command
warning: unable to resolve reference to 'esp32_ttgo_t_beam_optional_hardware' for \ref command
warning: unable to resolve reference to 'nrf52840dongle_flash' for \ref command
warning: Member USBUS_MSC_BUFFER_SIZE (macro definition) of file msc.h is not documented.
warning: Member USBUS_MSC_EXPORTED_DEV (macro definition) of file msc.h is not documented.
warning: Member usbus_msc_state_t (enumeration) of file msc.h is not documented.
warning: Member USBUS_MSC_VENDOR_ID (macro definition) of file scsi.h is not documented.
warning: Member USBUS_MSC_PRODUCT_ID (macro definition) of file scsi.h is not documented.
warning: Member USBUS_MSC_PRODUCT_REV (macro definition) of file scsi.h is not documented.
warning: Member SCSI_VERSION_NONE (macro definition) of file scsi.h is not documented.
warning: Member SCSI_VERSION_SCSI1 (macro definition) of file scsi.h is not documented.
warning: Member SCSI_VERSION_SCSI2 (macro definition) of file scsi.h is not documented.
warning: Member SCSI_READ_FMT_CAPA_TYPE_RESERVED (macro definition) of file scsi.h is not documented.
warning: Member SCSI_READ_FMT_CAPA_TYPE_UNFORMATTED (macro definition) of file scsi.h is not documented.
warning: Member SCSI_READ_FMT_CAPA_TYPE_FORMATTED (macro definition) of file scsi.h is not documented.
warning: Member SCSI_READ_FMT_CAPA_TYPE_NO_MEDIA (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_NO_SENSE (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_RECOVERED_ERROR (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_NOT_READY (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_MEDIUM_ERROR (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_HARDWARE_ERROR (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_ILLEGAL_REQUEST (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_UNIT_ATTENTION (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_DATA_PROTECT (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_BLANK_CHECK (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_VENDOR_SPECIFIC (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_ABORTED_COMMAND (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_VOLUME_OVERFLOW (macro definition) of file scsi.h is not documented.
warning: Member SCSI_SENSE_KEY_MISCOMPARE (macro definition) of file scsi.h is not documented.

View File

@ -857,6 +857,12 @@ ifneq (,$(filter ut_process,$(USEMODULE)))
USEMODULE += fmt
endif
ifneq (,$(filter usbus_msc,$(USEMODULE)))
USEMODULE += usbus
USEMODULE += mtd
USEMODULE += mtd_write_page
endif
ifneq (,$(filter uuid,$(USEMODULE)))
USEMODULE += hashes
USEMODULE += random

104
sys/include/usb/msc.h Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright (C) 2019-2021 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*
*
*/
/**
* @defgroup usbus_msc USBUS Mass Storage Class functions
* @ingroup usb
*
* @{
*
* @file
* @brief USB Mass Storage Class functions definitions
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*/
#ifndef USB_MSC_H
#define USB_MSC_H
#include <stdint.h>
#include "usb/usbus.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name USB Mass Storage Class subclass definitions
*
* @see Table 1 SubClass Codes Mapped to Command Block Specifications
* from Universal Serial Bus Mass Storage Class Specification Overview 1.4
* @{
*/
#define USB_MSC_SUBCLASS_SCSI 0x00 /**< SCSI command set not reported */
#define USB_MSC_SUBCLASS_RBC 0x01 /**< RBC allocated by USB-IF */
#define USB_MSC_SUBCLASS_MMC5 0x02 /**< MMC5 allocated by USB-IF */
#define USB_MSC_SUBCLASS_UFI 0x04 /**< Interface Floppy Disk Drives */
#define USB_MSC_SUBCLASS_SCSI_TCS 0x06 /**< SCSI transparent command set */
#define USB_MSC_SUBCLASS_LSDFS 0x07 /**< Early negotiation access */
#define USB_MSC_SUBCLASS_IEEE1667 0x08 /**< IEEE1677 allocated by USB-IF */
#define USB_MSC_SUBCLASS_VENDOR 0xFF /**< Vendor Specific */
/** @} */
/**
* @name USB Mass Storage Class protocol definitions
*
* @see Table 2 Mass Storage Transport Protocol
* from Universal Serial Bus Mass Storage Class Specification Overview 1.4
* @{
*/
#define USB_MSC_PROTOCOL_CBI_CCI 0x00 /**< CBI transport with command completion interrupt*/
#define USB_MSC_PROTOCOL_CBI_NO_CCI 0x01 /**< CBI transport without command completion \
interrupt */
#define USB_MSC_PROTOCOL_BBB 0x50 /**< Bulk only (BBB) transport */
#define USB_MSC_PROTOCOL_UAS 0x62 /**< UAS allocated by USB-IF */
#define USB_MSC_PROTOCOL_VENDOR 0xFF /**< Vendor Specific */
/** @} */
/**
* @brief Command Block Wrapper flags
*
* @see Chap 5.1 Command Block Wrapper (CBW)
* from Universal Serial Bus Mass Storage Class Bulk-Only Transport
*/
#define USB_MSC_CBW_FLAG_IN 0x80 /**< Indicate Device to Host direction */
/**
* @name USB Mass Storage Class request codes
*
* @see Table 3 Mass Storage Request Codes
* from Universal Serial Bus Mass Storage Class Specification Overview 1.4
* @{
*/
#define USB_MSC_SETUP_REQ_ADSC 0x01 /**< Accept Device-Specific Command request */
#define USB_MSC_SETUP_REQ_GET_REQ 0xFC /**< Get Request */
#define USB_MSC_SETUP_REQ_PUT_REQ 0xFD /**< Put Request */
#define USB_MSC_SETUP_REQ_GML 0xFE /**< Get Max LUN request */
#define USB_MSC_SETUP_REQ_BOMSR 0xFF /**< Bulk-Only Mass Storage Reset request */
/** @} */
/**
* @name USB Mass Storage Class CSW status code
*
* @see Table 5.3 Command Block Status Values
* from Universal Serial Bus Mass Storage Class Bulk-Only Transport
* @{
*/
#define USB_MSC_CSW_STATUS_COMMAND_PASSED 0x00 /**< CSW Status command successful */
#define USB_MSC_CSW_STATUS_COMMAND_FAILED 0x01 /**< CSW Status command failure */
#define USB_MSC_CSW_STATUS_COMMAND_PHASE_ERROR 0x02 /**< CSW Status command phase error */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* USB_MSC_H */
/** @} */

128
sys/include/usb/usbus/msc.h Normal file
View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2019-2021 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*
*
*/
/**
* @ingroup usbus_msc
*
* @{
*
* @file
* @brief USBUS Mass Storage Class functions definitions
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*/
#ifndef USB_USBUS_MSC_H
#define USB_USBUS_MSC_H
#include <stdint.h>
#include "usb/usbus.h"
#include "usb/usbus/msc/scsi.h"
#include "mtd.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USBUS MSC Number of exported MTD device through USB
*/
#ifndef USBUS_MSC_EXPORTED_NUMOF
#ifdef MTD_NUMOF
#define USBUS_MSC_EXPORTED_NUMOF MTD_NUMOF
#else
#define USBUS_MSC_EXPORTED_NUMOF 0
#endif
#endif /* USBUS_MSC_EXPORTED_NUMOF */
/**
* @brief USBUS MSC internal state machine enum
*/
typedef enum {
WAITING, /**< Initial state, wait for USB setup */
WAIT_FOR_TRANSFER, /**< Wait for a single packet transfer before sending CSW */
DATA_TRANSFER_IN, /**< Ongoing transfer on USB MSC IN endpoint */
DATA_TRANSFER_OUT, /**< Ongoing transfer on USB MSC OUT endpoint */
GEN_CSW /**< Generate CSW response to host w/ the current transfer status */
} usbus_msc_state_t;
/**
* @brief USBUS MSC Logical Unit descriptor
*/
typedef struct usbus_msc_lun {
mtd_dev_t *mtd; /**< Pointer to the current mtd device used */
uint32_t block_nb; /**< Reported USB block number */
uint32_t block_size; /**< Reported USB block size */
} usbus_msc_lun_t;
/**
* @brief USBUS MSC device interface context
*/
typedef struct usbus_msc_device {
usbus_handler_t handler_ctrl; /**< Control interface handler */
usbus_interface_t iface; /**< MSC interface */
usbus_endpoint_t *ep_in; /**< Data endpoint in */
usbus_endpoint_t *ep_out; /**< Data endpoint out */
uint8_t *out_buf; /**< Pointer to internal out endpoint buffer */
uint8_t *in_buf; /**< Pointer to internal in endpoint buffer */
usbus_descr_gen_t msc_descr; /**< MSC descriptor generator */
usbus_t *usbus; /**< Pointer to the USBUS context */
cbw_info_t cmd; /**< Command Block Wrapper information */
event_t rx_event; /**< Transmit ready event */
usbus_msc_state_t state; /**< Internal state machine for msc */
uint8_t *buffer; /**< Pointer to the current data transfer buffer */
uint32_t buffer_size; /**< Size of the internal buffer used for data transfer */
uint32_t block; /**< First block to transfer data from/to */
uint16_t block_nb; /**< Number of block to transfer for READ and
WRITE operations */
uint16_t block_offset; /**< Internal offset for endpoint size chunk transfer */
usbus_msc_lun_t lun_dev[USBUS_MSC_EXPORTED_NUMOF]; /**< Array holding exported logical
unit descriptor */
} usbus_msc_device_t;
/**
* @brief MSC initialization function
*
* @param usbus USBUS thread to use
* @param handler MSC device struct
*/
int usbus_msc_init(usbus_t *usbus, usbus_msc_device_t *handler);
/**
* @brief Register a MTD device as a MSC LUN (Logical Unit Number)
*
* @param[in] usbus USBUS context
* @param[in] dev pointer to the MTD device to export
*
* @return 0 on success
* -ENOMEM, if the selected MTD device doesn't have enough memory
* -EAGAIN, if no more MTD devices can be exported
* -EBUSY, if the MTD device is already exported
*/
int usbus_msc_add_lun(usbus_t *usbus, mtd_dev_t *dev);
/**
* @brief Unregister a MTD device as a MSC LUN
*
* @param[in] usbus USBUS context
* @param[in] dev pointer to the MTD device to export
*
* @return 0 on success
* -ENODEV, if no matching MTD device was found
*/
int usbus_msc_remove_lun(usbus_t *usbus, mtd_dev_t *dev);
#ifdef __cplusplus
}
#endif
#endif /* USB_USBUS_MSC_H */
/** @} */

View File

@ -0,0 +1,295 @@
/*
* Copyright (C) 2019-2021 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @ingroup usbus_msc
*
* @{
*
* @file
* @brief SCSI protocol definitions for USBUS
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*/
#ifndef USB_USBUS_MSC_SCSI_H
#define USB_USBUS_MSC_SCSI_H
#include "byteorder.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef USBUS_MSC_VENDOR_ID
#define USBUS_MSC_VENDOR_ID "RIOT-OS"
#endif /* USBUS_MSC_VENDOR_ID */
#ifndef USBUS_MSC_PRODUCT_ID
#define USBUS_MSC_PRODUCT_ID "RIOT_MSC_DISK "
#endif /* USBUS_MSC_PRODUCT_ID */
#ifndef USBUS_MSC_PRODUCT_REV
#define USBUS_MSC_PRODUCT_REV " 1.0"
#endif /* USBUS_MSC_PRODUCT_REV */
/**
* @name USB SCSI Commands
*
* @see Table 9 - Packet Commands Supported by ATAPI Block Devices
* from INF-8070i draft published by SFF
* @{
*/
#define SCSI_TEST_UNIT_READY 0x00 /**< SCSI Test Unit Ready */
#define SCSI_REQUEST_SENSE 0x03 /**< SCSI Request Sense */
#define SCSI_FORMAT_UNIT 0x04 /**< SCSI Format Unit */
#define SCSI_INQUIRY 0x12 /**< SCSI Inquiry */
#define SCSI_MODE_SELECT6 0x15 /**< SCSI Mode Select6 */
#define SCSI_MODE_SENSE6 0x1A /**< SCSI Mode Sense6 */
#define SCSI_START_STOP_UNIT 0x1B /**< SCSI Start Stop Unit */
#define SCSI_MEDIA_REMOVAL 0x1E /**< SCSI Media Removal */
#define SCSI_READ_FORMAT_CAPACITIES 0x23 /**< SCSI Read Format Capacities */
#define SCSI_READ_CAPACITY 0x25 /**< SCSI Read Capacity */
#define SCSI_READ10 0x28 /**< SCSI Read10 */
#define SCCI_READ12 0xA8 /**< SCSI Read12 */
#define SCSI_WRITE10 0x2A /**< SCSI Write10 */
#define SCSI_WRITE12 0xAA /**< SCSI Write12 */
#define SCSI_SEEK 0x2B /**< SCSI Seek */
#define SCSI_WRITE_AND_VERIFY 0x2E /**< SCSI Write and Verify */
#define SCSI_VERIFY10 0x2F /**< SCSI Verify10 */
#define SCSI_MODE_SELECT10 0x55 /**< SCSI Mode Select10 */
#define SCSI_MODE_SENSE10 0x5A /**< SCSI Mode Sense10 */
/** @} */
/**
* @brief Command Block Wrapper signature
*/
#define SCSI_CBW_SIGNATURE 0x43425355
/**
* @brief Command Status Wrapper signature
*/
#define SCSI_CSW_SIGNATURE 0x53425355
/**
* @name USB SCSI Version list
* @{
*/
#define SCSI_VERSION_NONE 0x0000
#define SCSI_VERSION_SCSI1 0x0001
#define SCSI_VERSION_SCSI2 0x0002
/** @} */
/**
* @name USB SCSI Read format capacities descriptor type
*
* @see ( @ref msc_read_fmt_capa_pkt_t )
* @{
*/
#define SCSI_READ_FMT_CAPA_TYPE_RESERVED 0x00
#define SCSI_READ_FMT_CAPA_TYPE_UNFORMATTED 0x01
#define SCSI_READ_FMT_CAPA_TYPE_FORMATTED 0x02
#define SCSI_READ_FMT_CAPA_TYPE_NO_MEDIA 0x03
/** @} */
/**
* @brief SCSI Request Sense error type
*/
#define SCSI_REQUEST_SENSE_ERROR 0x70
/**
* @name USB SCSI Request Sense Sense Key type
*
* @{
*/
#define SCSI_SENSE_KEY_NO_SENSE 0x00
#define SCSI_SENSE_KEY_RECOVERED_ERROR 0x01
#define SCSI_SENSE_KEY_NOT_READY 0x02
#define SCSI_SENSE_KEY_MEDIUM_ERROR 0x03
#define SCSI_SENSE_KEY_HARDWARE_ERROR 0x04
#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05
#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06
#define SCSI_SENSE_KEY_DATA_PROTECT 0x07
#define SCSI_SENSE_KEY_BLANK_CHECK 0x08
#define SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09
#define SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B
#define SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D
#define SCSI_SENSE_KEY_MISCOMPARE 0x0E
/** @} */
/**
* @brief Packet structure to answer (@ref SCSI_TEST_UNIT_READY) request
*
* @see Inquiry Command from SCSI Primary Command
*/
typedef struct __attribute__((packed)) {
uint8_t type; /**< type of device */
uint8_t logical_unit; /**< Selected LUN */
uint8_t reserved[10]; /**< Reserved */
} msc_test_unit_pkt_t;
/**
* @brief Packet structure to answer (@ref SCSI_MODE_SELECT6) and
* (@ref SCSI_MODE_SENSE6) requests
*
* @see Inquiry Command from SCSI Primary Command
*/
typedef struct __attribute__((packed)) {
uint8_t mode_data_len; /**< Size of the whole packet */
uint8_t medium_type; /**< Type of medium */
uint8_t flags; /**< Miscellaneous flags */
uint8_t block_desc_len; /**< Length of block descriptor */
} msc_mode_parameter_pkt_t;
/**
* @brief CBW Packet structure for (@ref SCSI_READ10) and
* (@ref SCSI_WRITE10) requests
*
* @see Read10 or Write10 Command from SCSI Primary Command
*
* @warning uint16_t and uint32_t will be BIG ENDIAN
*/
typedef struct __attribute__((packed)) {
uint8_t opcode; /**< Operation code */
uint8_t flags; /**< Miscellaneous flags */
be_uint32_t blk_addr; /**< Block address */
uint8_t group_number; /**< Group number */
be_uint16_t xfer_len; /**< Transfer length in bytes */
} msc_cbw_rw10_pkt_t;
/**
* @brief Packet structure to answer (@ref SCSI_INQUIRY) request
*
* @see Inquiry Command from SCSI Primary Command
*
* @note Vendor specific information (Byte 36 and above) and flags from bytes
* 5 to 7 are currently unsupported
*/
typedef struct __attribute__((packed)) {
uint8_t type; /**< Byte 0 Peripheral type */
uint8_t reserved1 : 7; /**< Byte 1 [B6..B0] Reserved */
uint8_t removable : 1; /**< Byte 1 [B7] Removable device flag */
uint8_t version; /**< Byte 2 SCSI Version */
uint8_t response_format : 4; /**< Byte 3 [B3..B0] Response Data Format */
uint8_t reserved3 : 4; /**< Byte 3 [B7..B4] Reserved */
uint8_t length; /**< Byte 4 Additional Length (n-4) */
uint8_t flags[3]; /**< Byte 7..5 Miscellaneous flags UNUSED BY USBUS ONLY */
uint8_t vendor_id[8]; /**< Byte 15..8 Vendor Identification */
uint8_t product_id[16]; /**< Byte 31..16 Product Identification */
uint8_t product_rev[4]; /**< Byte 35..32 Product Revision */
} msc_inquiry_pkt_t;
/**
* @brief Packet structure to answer (@ref SCSI_READ_FORMAT_CAPACITIES) request
*
* @see READ FORMAT CAPACITIES from SCSI Multimedia Commands 2 (MMC-2)
*
*/
typedef struct __attribute__((packed)) {
uint8_t reserved1[3]; /**< Byte 2..0 reserved */
uint8_t list_length; /**< Byte 3 Capacity list length */
be_uint32_t blk_nb; /**< Byte 7..4 Number of blocks */
uint8_t type; /**< Byte 8 Descriptor type */
uint8_t blk_len[3]; /**< Byte 11..9 Block length */
} msc_read_fmt_capa_pkt_t;
/**
* @brief Packet structure to answer (@ref SCSI_REQUEST_SENSE) request
*
*/
typedef struct __attribute__((packed)) {
uint8_t error_code; /**< Byte 0 Error code */
uint8_t reserved1; /**< Reserved */
uint8_t sense_key; /**< Byte 2 Sense key */
uint8_t reserved2[4]; /**< Byte 6..3 Information */
uint8_t add_len; /**< Byte 7 Additional sense length */
uint8_t reserved3[4]; /**< Byte 11..8 Vendor specific */
uint8_t asc; /**< Byte 12 Additional Sense Code */
uint8_t ascq; /**< Byte 13 Additional Sense Code Qualifier */
uint8_t fruc; /**< Byte 14 Field Replaceable Unit Code */
uint16_t sk_spec; /**< Byte 16..15 Sense Key Specific */
} msc_request_sense_pkt_t;
/**
* @brief Packet structure to answer (@ref SCSI_READ_CAPACITY) request
*
* @note Multiply the two values from this struct between them
* indicates the total size of your MTD device
*
* @see Read Capacities from SCSI Primary Command
*/
typedef struct __attribute__((packed)) {
be_uint32_t last_blk; /**< Indicate last block number */
be_uint32_t blk_len; /**< Total size of a block in bytes */
} msc_read_capa_pkt_t;
/**
* @brief Command Block Wrapper packet structure
*
* @see Table 5.1 Command Block Wrapper (CBW)
* from Universal Serial Bus Mass Storage Class Bulk-Only Transport
*/
typedef struct __attribute__((packed)) {
uint32_t signature; /**< CBW signature (@ref SCSI_CBW_SIGNATURE) */
uint32_t tag; /**< ID for the current command */
uint32_t data_len; /**< Number of bytes host expects to transfer from/to */
uint8_t flags; /**< Command block flags */
uint8_t lun; /**< Target Logical Unit Number */
uint8_t cb_len; /**< Length of the block in bytes (max: 16 bytes) */
uint8_t cb[16]; /**< Command block buffer */
} msc_cbw_buf_t;
/**
* @brief Command Status Wrapper packet structure
*
* @see Table 5.2 - Command Status Wrapper
* from Universal Serial Bus Mass Storage Class Bulk-Only Transport
*/
typedef struct __attribute__((packed)) {
uint32_t signature; /**< CSW signature (@ref SCSI_CSW_SIGNATURE) */
uint32_t tag; /**< ID for the answered CBW */
uint32_t data_left; /**< Indicate how many bytes from the CBW were not processed */
uint8_t status; /**< Status of the command */
} msc_csw_buf_t;
/**
* @brief USBUS Command Block Wrapper information
*
*/
typedef struct {
uint32_t tag; /**< Store tag information from CBW */
size_t len; /**< Remaining bytes to handle */
uint8_t status; /**< Status of the current operation */
uint8_t lun; /**< Store LUN from CBW */
} cbw_info_t;
/**
* @brief Process incoming Command Block Wrapper buffer
*
* @param usbus USBUS thread to use
* @param handler MSC device struct
* @param ep Endpoint pointer to read CBW from
* @param len Size of the received CBW buffer
*/
void usbus_msc_scsi_process_cmd(usbus_t *usbus, usbus_handler_t *handler, usbdev_ep_t *ep,
size_t len);
/**
* @brief Generate Command Status Wrapper and send it to the host
*
* @param handler MSC device struct
* @param cmd struct containing needed information to generate CBW response
*/
void scsi_gen_csw(usbus_handler_t *handler, cbw_info_t *cmd);
#ifdef __cplusplus
}
#endif
#endif /* USB_USBUS_MSC_SCSI_H */
/** @} */

View File

@ -68,3 +68,4 @@ endif # KCONFIG_USEMODULE_USBUS
rsource "cdc/Kconfig"
rsource "dfu/Kconfig"
rsource "hid/Kconfig"
rsource "msc/Kconfig"

View File

@ -13,4 +13,7 @@ endif
ifneq (,$(filter usbus_hid,$(USEMODULE)))
DIRS += hid
endif
ifneq (,$(filter usbus_msc,$(USEMODULE)))
DIRS += msc
endif
include $(RIOTBASE)/Makefile.base

31
sys/usb/usbus/msc/Kconfig Normal file
View File

@ -0,0 +1,31 @@
# Copyright (c) 2021 Mesotic SAS
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
menuconfig MODULE_USBUS_MSC
bool "USBUS Mass Storage Class"
depends on MODULE_USBUS
select MODULE_MTD
select MODULE_MTD_WRITE_PAGE
select USEMODULE_USBUS_MSC
help
Configure the USBUS MSC module via Kconfig.
if MODULE_USBUS_MSC
config USBUS_MSC_VENDOR_ID
string "MSC Vendor ID"
default "RIOT-OS"
config USBUS_MSC_PRODUCT_ID
string "MSC Product ID"
default "RIOT_MSC_DISK"
config USBUS_MSC_PRODUCT_REV
string "MSC Product Revision"
default "1.0"
endif # MODULE_USBUS_MSC

View File

@ -0,0 +1,3 @@
MODULE = usbus_msc
include $(RIOTBASE)/Makefile.base

458
sys/usb/usbus/msc/msc.c Normal file
View File

@ -0,0 +1,458 @@
/*
* Copyright (C) 2019-2021 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_msc
* @{
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
* @}
*/
#include "usb/descriptor.h"
#include "usb/usbus.h"
#include "usb/usbus/control.h"
#include "usb/msc.h"
#include "usb/usbus/msc.h"
#include "usb/usbus/msc/scsi.h"
#include "board.h"
#include <string.h>
#include <errno.h>
#define ENABLE_DEBUG 0
#include "debug.h"
#include "mtd.h"
static unsigned char _ep_out_buf[CONFIG_USBUS_EP0_SIZE];
static unsigned char _ep_in_buf[CONFIG_USBUS_EP0_SIZE];
/* Internal handler definitions */
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event);
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup);
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event);
static void _init(usbus_t *usbus, usbus_handler_t *handler);
static const usbus_handler_driver_t msc_driver = {
.init = _init,
.event_handler = _event_handler,
.control_handler = _control_handler,
.transfer_handler = _transfer_handler,
};
static const usbus_descr_gen_funcs_t _msc_descriptor = {
.fmt_post_descriptor = NULL,
.fmt_pre_descriptor = NULL,
.len = {
.fixed_len = 0,
},
.len_type = USBUS_DESCR_LEN_FIXED,
};
static void _write_xfer(usbus_msc_device_t *msc)
{
uint8_t lun = msc->cmd.lun;
uint16_t block_size = msc->lun_dev[lun].block_size;
uint32_t sector_count;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
size_t len;
int ret;
/* Check if we have a block to read and transfer */
if (!msc->block_nb) {
return;
}
/* Retrieve incoming data */
usbdev_ep_get(msc->ep_out->ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t));
/* Update offset for page buffer */
msc->block_offset += len;
/* Decrement whole len */
msc->cmd.len -= len;
/* buffer is full, write it and point to new block if any */
if (msc->block_offset >= block_size) {
/* Specific case for MTD devices with sector size < 512 bytes
Since USB report 512 bytes per sector, we need to erase
several sectors at the same time */
if (mtd->page_size * mtd->pages_per_sector < 512) {
sector_count = (512 / (mtd->page_size * mtd->pages_per_sector));
} else {
sector_count = 1;
}
/* Write one or multiple sectors */
ret = mtd_write_page(mtd, msc->buffer,
msc->block * sector_count * mtd->pages_per_sector,
0, block_size);
if (ret != 0) {
/* Failure occurs during write operation, stall the operation */
DEBUG("[msc]: Write fail with error: %d\n", ret);
/* Stall IN bulk endpoint and signal error to host through
* CSW command with command failed status */
static const usbopt_enable_t enable = USBOPT_ENABLE;
usbdev_ep_set(msc->ep_in->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
msc->state = GEN_CSW;
msc->cmd.status = USB_MSC_CSW_STATUS_COMMAND_FAILED;
}
msc->block_offset = 0;
msc->block++;
msc->block_nb--;
}
if (msc->cmd.len == 0) {
/* All blocks have been transferred, send CSW to host */
if (msc->state == DATA_TRANSFER_OUT) {
msc->state = GEN_CSW;
/* Data was processed, ready next transfer */
usbdev_ep_xmit(msc->ep_out->ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
return;
}
}
if (len > 0) {
/* Directly put data incoming on the endpoint to the flashpage buffer */
usbdev_ep_xmit(msc->ep_out->ep, &msc->buffer[msc->block_offset], len);
}
}
static void _read_xfer(usbus_msc_device_t *msc)
{
uint8_t lun = msc->cmd.lun;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
uint32_t block_size = msc->lun_dev[lun].block_size;
int ret;
/* Check if we have a block to read and transfer */
if (msc->block_nb) {
/* read buffer from mtd device */
if (msc->block_offset == 0) {
ret = mtd_read(mtd, msc->buffer, msc->block * block_size, block_size);
if (ret != 0) {
DEBUG("[msc]: Read operation failed with error:%d\n", ret);
/* Stall the current operation, and signal it to host */
static const usbopt_enable_t enable = USBOPT_ENABLE;
usbdev_ep_set(msc->ep_in->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
msc->state = GEN_CSW;
msc->cmd.status = USB_MSC_CSW_STATUS_COMMAND_FAILED;
}
}
/* Data prepared, signal ready to usbus */
usbdev_ep_xmit(msc->ep_in->ep, &msc->buffer[msc->block_offset],
msc->ep_in->ep->len);
/* Update offset for page buffer */
msc->block_offset += msc->ep_in->ep->len;
/* Decrement whole len */
msc->cmd.len -= msc->ep_in->ep->len;
/* whole buffer is empty, point to new block if any */
if (msc->block_offset >= block_size) {
msc->block_offset = 0;
msc->block++;
msc->block_nb--;
}
}
else {
/* All blocks have been transferred, send CSW to host */
if (msc->state == DATA_TRANSFER_IN) {
msc->state = GEN_CSW;
}
}
}
static void _handle_rx_event(event_t *ev)
{
usbus_msc_device_t *msc = container_of(ev, usbus_msc_device_t, rx_event);
_read_xfer(msc);
}
static usbus_msc_device_t *_get_msc_handler(usbus_t *usbus)
{
for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) {
if (iface->class == USB_CLASS_MASS_STORAGE) {
return (usbus_msc_device_t *)iface->handler;
}
}
return NULL;
}
static unsigned _get_lun(usbus_msc_device_t *msc)
{
unsigned count = 0;
/* Count only registered MTD devices as USB LUN, (using usbus_msc_add_lun)
* not every MTD devices available on board */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (msc->lun_dev[i].mtd != NULL) {
count++;
}
}
return count;
}
int usbus_msc_add_lun(usbus_t *usbus, mtd_dev_t *dev)
{
uint32_t block_size;
/* Loop through all interfaces to find the MSC one */
usbus_msc_device_t *msc = _get_msc_handler(usbus);
if (!dev) {
return -ENODEV;
}
/* Check if MTD isn't already registered */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (dev == msc->lun_dev[i].mtd) {
return -EBUSY;
}
}
/* Store new MTD device in first slot available
Also re alloc internal buffer if needed */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (msc->lun_dev[i].mtd == NULL) {
int ret = mtd_init(dev);
if (ret != 0) {
DEBUG_PUTS("[msc]: cannot initialize MTD device");
return -ENODEV;
}
block_size = dev->page_size * dev->pages_per_sector;
/* Ensure MTD device can be exported */
if (block_size * dev->sector_count < 512) {
DEBUG("page size:%" PRIu32 " pages per sector:%" PRIu32 " \
sector_count:%" PRIu32 "\n", dev->page_size,
dev->pages_per_sector, dev->sector_count);
return -ENOMEM;
}
if (msc->buffer != NULL) {
/* If new registered MTD device need more memory than the
previous, realloc a new buffer */
if (block_size > msc->buffer_size) {
msc->buffer = realloc(msc->buffer, block_size);
}
} else {
/* No buffer allocated yet, so allocate one */
msc->buffer = malloc(block_size);
}
if (!msc->buffer) {
DEBUG_PUTS("[msc]: Failed to realloc new buffer\n");
return -ENOMEM;
}
msc->buffer_size = block_size;
msc->lun_dev[i].mtd = dev;
return 0;
}
}
/* No more slots available */
return -ENFILE;
}
int usbus_msc_remove_lun(usbus_t *usbus, mtd_dev_t *dev)
{
/* Loop through all interfaces to find the MSC one */
usbus_msc_device_t *msc = _get_msc_handler(usbus);
/* Identify the LUN to unexport */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (dev == msc->lun_dev[i].mtd) {
/* Wait for any pending transaction to end */
/* Clear relevant information */
msc->lun_dev[i].mtd = NULL;
msc->lun_dev[i].block_size = 0;
msc->lun_dev[i].block_nb = 0;
return 0;
}
}
/* No more slots available */
free(msc->buffer);
msc->buffer = NULL;
return -ENODEV;
}
int usbus_msc_init(usbus_t *usbus, usbus_msc_device_t *handler)
{
assert(usbus);
assert(handler);
memset(handler, 0, sizeof(usbus_msc_device_t));
handler->usbus = usbus;
handler->handler_ctrl.driver = &msc_driver;
usbus_register_event_handler(usbus, (usbus_handler_t *)handler);
return 0;
}
static void _init(usbus_t *usbus, usbus_handler_t *handler)
{
DEBUG_PUTS("[msc]: initialization\n");
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc->msc_descr.next = NULL;
msc->msc_descr.funcs = &_msc_descriptor;
msc->msc_descr.arg = msc;
msc->block_offset = 0;
/* Buffer will be initialized later */
msc->buffer = NULL;
msc->buffer_size = 0;
msc->block_nb = 0;
msc->block = 0;
msc->state = WAITING;
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
msc->lun_dev[i].mtd = NULL;
msc->lun_dev[i].block_size = 0;
msc->lun_dev[i].block_nb = 0;
}
/* Assign internal endpoint buffers */
msc->out_buf = _ep_out_buf;
msc->in_buf = _ep_in_buf;
/* Add event handlers */
msc->rx_event.handler = _handle_rx_event;
/* Instantiate interfaces */
memset(&msc->iface, 0, sizeof(usbus_interface_t));
/* Configure Interface 0 as control interface */
msc->iface.class = USB_CLASS_MASS_STORAGE;
msc->iface.subclass = USB_MSC_SUBCLASS_SCSI_TCS;
msc->iface.protocol = USB_MSC_PROTOCOL_BBB;
msc->iface.descr_gen = &(msc->msc_descr);
msc->iface.handler = handler;
/* Create required endpoints */
msc->ep_in = usbus_add_endpoint(usbus, &msc->iface, USB_EP_TYPE_BULK,
USB_EP_DIR_IN, CONFIG_USBUS_EP0_SIZE);
msc->ep_in->interval = 0;
msc->ep_out = usbus_add_endpoint(usbus, &msc->iface, USB_EP_TYPE_BULK,
USB_EP_DIR_OUT, CONFIG_USBUS_EP0_SIZE);
msc->ep_out->interval = 0;
/* Add interfaces to the stack */
usbus_add_interface(usbus, &msc->iface);
usbus_enable_endpoint(msc->ep_in);
usbus_enable_endpoint(msc->ep_out);
usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET);
/* Prepare to receive first bytes from Host */
usbdev_ep_xmit(msc->ep_out->ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
}
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup)
{
(void)usbus;
(void)state;
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
uint8_t data;
switch (setup->request) {
case USB_MSC_SETUP_REQ_GML:
data = _get_lun(msc);
if (data > 0) {
data -= 1;
}
/* Return the number of MTD devices available on the board */
usbus_control_slicer_put_bytes(usbus, &data, sizeof(data));
usbus_control_slicer_ready(usbus);
break;
case USB_MSC_SETUP_REQ_BOMSR:
DEBUG_PUTS("[msc]: TODO: implement reset setup request");
break;
default:
DEBUG("[msc]: unsupported handle setup request:0x%x\n", setup->request);
return -1;
}
return 1;
}
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event)
{
(void)usbus;
(void)event;
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
if (ep->dir == USB_EP_DIR_OUT) {
size_t len = 0;
/* Previous transfer is sent, send the next one */
if (msc->state == DATA_TRANSFER_OUT) {
_write_xfer(msc);
}
else {
/* Retrieve incoming data */
usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t));
if (len > 0) {
/* Process incoming endpoint buffer */
usbus_msc_scsi_process_cmd(usbus, handler, ep, len);
}
/* Data was processed, ready next transfer */
if (msc->state == DATA_TRANSFER_OUT) {
/* If the next pkt is supposed to be a WRITE10 transfer,
directly redirect content to appropriate buffer */
usbdev_ep_xmit(msc->ep_out->ep, &msc->buffer[msc->block_offset], 64);
}
else {
usbdev_ep_xmit(ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
}
}
}
else {
/* Data Transfer pending */
if (msc->state == DATA_TRANSFER_IN) {
_read_xfer(msc);
}
/* CBW answer was sent, prepare CSW */
else if (msc->state == WAIT_FOR_TRANSFER) {
msc->state = GEN_CSW;
}
}
if (msc->cmd.tag && msc->cmd.len == 0 && msc->state == GEN_CSW) {
DEBUG_PUTS("[msc]: Generate CSW");
scsi_gen_csw(handler, &(msc->cmd));
/* Command has been handled, so clear tag and state machine */
msc->cmd.tag = 0;
msc->state = WAITING;
}
}
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event)
{
(void)usbus;
(void)handler;
switch (event) {
case USBUS_EVENT_USB_RESET:
DEBUG_PUTS("[msc]: host reset event");
break;
default:
DEBUG("[msc]: Unhandled event :0x%x\n", event);
break;
}
}

389
sys/usb/usbus/msc/scsi.c Normal file
View File

@ -0,0 +1,389 @@
/*
* Copyright (C) 2019-2022 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_msc
* @{
*
* @note SCSI is a big endian protocol and
* USB transfer data as little endian
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
* @}
*/
#include "usb/usbus.h"
#include "usb/msc.h"
#include "usb/usbus/msc.h"
#include "usb/usbus/msc/scsi.h"
#include "board.h"
#include <string.h>
#define ENABLE_DEBUG 0
#include "debug.h"
static void _rx_ready(usbus_msc_device_t *msc)
{
usbus_event_post(msc->usbus, &msc->rx_event);
}
static void _scsi_test_unit_ready(usbus_handler_t *handler, msc_cbw_buf_t *cbw)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
if (cbw->data_len != 0) {
static const usbopt_enable_t enable = USBOPT_ENABLE;
if ((cbw->flags & USB_MSC_CBW_FLAG_IN) != 0) {
usbdev_ep_set(msc->ep_in->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
}
else {
usbdev_ep_set(msc->ep_out->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
}
}
msc->state = GEN_CSW;
}
static void _scsi_write10(usbus_handler_t *handler, msc_cbw_buf_t *cbw, uint8_t lun)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_cbw_rw10_pkt_t *pkt = (msc_cbw_rw10_pkt_t *)cbw->cb;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
uint32_t mtd_sector_size = mtd->page_size * mtd->pages_per_sector;
/* Convert big endian data from SCSI to little endian */
/* Get first block number to read from */
if (mtd_sector_size < 512) {
msc->block = byteorder_ntohl(pkt->blk_addr) * (512 / mtd_sector_size);
}
else if (mtd_sector_size > 4096) {
/* TODO: add support */
assert(0);
}
else {
msc->block = byteorder_ntohl(pkt->blk_addr);
}
/* Get number of blocks to transfer */
msc->block_nb = byteorder_ntohs(pkt->xfer_len);
msc->cmd.len = cbw->data_len;
msc->state = DATA_TRANSFER_OUT;
}
static void _scsi_read10(usbus_handler_t *handler, msc_cbw_buf_t *cbw, uint8_t lun)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_cbw_rw10_pkt_t *pkt = (msc_cbw_rw10_pkt_t *)cbw->cb;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
uint32_t mtd_sector_size = mtd->page_size * mtd->pages_per_sector;
/* Convert big endian data from SCSI to little endian */
/* Get first block number to read from */
if (mtd_sector_size < 512) {
/* If MTD sector size is less than 512 (e.g for EEPROM),
* apply some logic and let host believe this is a 512
* sector size device and let RIOT write several sectors in
* a row to reach 512 bytes so everyone is happy */
msc->block = byteorder_ntohl(pkt->blk_addr) * (512 / mtd_sector_size);
}
else if (mtd_sector_size > 4096) {
/* If MTD sector size is above 4096 bytes (e.g some MCU internal flash)
* limits the reported block size to 4096 bytes and add some logic to
* write less than a sector (using read-modify-write method). This will
* reduce the overall performance !*/
msc->block = byteorder_ntohl(pkt->blk_addr) / (mtd_sector_size / 4096);
}
else {
msc->block = byteorder_ntohl(pkt->blk_addr);
}
/* Get number of blocks to transfer */
msc->block_nb = byteorder_ntohs(pkt->xfer_len);
msc->cmd.len = cbw->data_len;
msc->state = DATA_TRANSFER_IN;
if ((cbw->flags & USB_MSC_CBW_FLAG_IN) != 0) {
_rx_ready(msc);
}
}
static void _scsi_inquiry(usbus_handler_t *handler)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_inquiry_pkt_t *pkt = (msc_inquiry_pkt_t *)msc->in_buf;
size_t len = sizeof(msc_inquiry_pkt_t);
/* Clear the exact amount of bytes needed for this transfer */
memset(pkt, 0, len);
/* prepare pkt response */
pkt->type = 0;
pkt->removable = 1;
pkt->version = 0x01;
/* Report the size of the additional data */
pkt->length = len - 4;
memcpy(&pkt->vendor_id, USBUS_MSC_VENDOR_ID, sizeof(pkt->vendor_id));
memcpy(&pkt->product_id, USBUS_MSC_PRODUCT_ID, sizeof(pkt->product_id));
memcpy(&pkt->product_rev, USBUS_MSC_PRODUCT_REV, sizeof(pkt->product_rev));
/* copy into ep buffer */
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)pkt, len);
msc->state = WAIT_FOR_TRANSFER;
return;
}
/*TODO: ensure this function is called AFTER _read_capacity*/
static void _scsi_read_format_capacities(usbus_handler_t *handler, uint8_t lun)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_read_fmt_capa_pkt_t *pkt = (msc_read_fmt_capa_pkt_t *)msc->in_buf;
uint16_t block_size = msc->lun_dev[lun].block_size;
uint32_t blk_nb = msc->lun_dev[lun].block_nb;
size_t len = sizeof(msc_read_fmt_capa_pkt_t);
/* Clear the exact amount of bytes needed for this transfer */
memset(pkt, 0, len);
/* Only support one list, size of the whole struct minus the 4 first bytes */
pkt->list_length = len - 4;
pkt->blk_nb = byteorder_htonl(blk_nb);
pkt->type = SCSI_READ_FMT_CAPA_TYPE_FORMATTED;
/* Manage endianness, bytes 11..9 -> LSB..MSB */
pkt->blk_len[0] = (block_size >> 16) && 0xFF;
pkt->blk_len[1] = (block_size >> 8) && 0xFF;
pkt->blk_len[2] = block_size && 0xFF;
/* copy into ep buffer */
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)pkt, sizeof(msc_read_fmt_capa_pkt_t));
msc->state = WAIT_FOR_TRANSFER;
}
static void _scsi_read_capacity(usbus_handler_t *handler, uint8_t lun)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_read_capa_pkt_t *pkt = (msc_read_capa_pkt_t *)msc->in_buf;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
uint16_t block_size = 0;
uint32_t mtd_sector_size = 0;
size_t len = sizeof(msc_read_capa_pkt_t);
uint32_t blk_nb = mtd->sector_count;
/* Clear the exact amount of bytes needed for this transfer */
memset(pkt, 0, len);
/* Compute the MTD sector size */
mtd_sector_size = mtd->page_size * mtd->pages_per_sector;
/* Check mtd sector size boundaries */
/* Host side expects to have at least 512 bytes per block, otherwise it
* will reject the setup. Thus, add some logic here to be able to use
* smaller page size mtd device */
if (mtd_sector_size < 512) {
block_size = 512;
/* Compute the number of block that will be reported to the host
Since the MTD sector is less than the minimum allowed (512),
the number of block reported is divided by the number of
sector that will be written
Example: if the MTD sector size is 64 bytes and the USB reported
block is 512, we must use 8 (512 / 64) sectors per USB block */
blk_nb /= (512 / mtd_sector_size);
}
/* setup some logic to manage devices with more than 4096 bytes
* per page size */
else if (mtd_sector_size > 4096) {
/* Force it to the greatest value supported by host */
block_size = 4096;
/* This case is the reverse as above, since the size of a sector
is above the maximum allowed boundary, Read/Modify/Write
operation is needed to properly write to the MTD device */
blk_nb *= (mtd_sector_size / 4096);
}
/* Standard page size support by Linux host driver (see drivers/scsi/sd.c
* in Linux kernel source tree) */
else if (mtd_sector_size == 512 || mtd_sector_size == 1024 ||
mtd_sector_size == 2048 || mtd_sector_size == 4096) {
block_size = mtd_sector_size;
}
else {
DEBUG_PUTS("Unsupported pagesize");
assert(0);
}
/* Report the size of the block (mtd sector size) */
pkt->blk_len.u32 = byteorder_swapl(block_size);
/* Store the reported block size and number for internal use */
msc->lun_dev[lun].block_size = block_size;
msc->lun_dev[lun].block_nb = blk_nb;
/* Report the last block number */
pkt->last_blk.u32 = byteorder_swapl(blk_nb - 1);
/* copy into ep buffer */
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)pkt, len);
msc->state = WAIT_FOR_TRANSFER;
}
static void _scsi_request_sense(usbus_handler_t *handler)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_request_sense_pkt_t *pkt = (msc_request_sense_pkt_t *)msc->in_buf;
size_t len = sizeof(msc_request_sense_pkt_t);
/* Clear the exact amount of bytes needed for this transfer */
memset(pkt, 0, len);
pkt->error_code = SCSI_REQUEST_SENSE_ERROR;
pkt->sense_key = SCSI_SENSE_KEY_ILLEGAL_REQUEST;
/* Report the size of the struct minus the 7 first bytes */
pkt->add_len = len - 7;
/* TODO: prepare a proper mechanism to correctly report all kind of error
to the host through the sense_key, asc and ascq combination */
/* Report CANNOT READ MEDIUM - UNKNOWN FORMAT as default value for now */
pkt->asc = 0x30;
pkt->ascq = 0x01;
/* copy into ep buffer */
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)pkt, len);
/* Wait for the current pkt to transfer before sending CSW */
msc->state = WAIT_FOR_TRANSFER;
}
static void _scsi_sense6(usbus_handler_t *handler)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_mode_parameter_pkt_t *pkt = (msc_mode_parameter_pkt_t *)msc->in_buf;
size_t len = sizeof(msc_mode_parameter_pkt_t);
/* Clear the exact amount of bytes needed for this transfer */
memset(pkt, 0, len);
/* Length of the packet minus the first byte */
pkt->mode_data_len = len - 1;
/* copy into ep buffer */
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)pkt, len);
/* Wait for the current pkt to transfer before sending CSW */
msc->state = WAIT_FOR_TRANSFER;
}
void usbus_msc_scsi_process_cmd(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, size_t len)
{
(void)usbus;
(void)ep;
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
/* store data into specific struct */
msc_cbw_buf_t *cbw = (msc_cbw_buf_t *)msc->out_buf;
/* Check Command Block signature */
if (cbw->signature != SCSI_CBW_SIGNATURE) {
DEBUG("Invalid CBW signature:0x%" PRIx32 ", abort\n", cbw->signature);
msc->cmd.status = USB_MSC_CSW_STATUS_COMMAND_FAILED;
msc->state = GEN_CSW;
return;
}
/* Store command for CSW generation & some operations */
msc->cmd.tag = cbw->tag;
msc->cmd.status = 0;
msc->cmd.len = 0;
msc->cmd.lun = cbw->lun;
switch (cbw->cb[0]) {
case SCSI_TEST_UNIT_READY:
DEBUG("SCSI_TEST_UNIT_READY:%d\n", len);
_scsi_test_unit_ready(handler, cbw);
break;
case SCSI_REQUEST_SENSE:
DEBUG_PUTS("SCSI_REQUEST_SENSE");
_scsi_request_sense(handler);
break;
case SCSI_FORMAT_UNIT:
DEBUG_PUTS("TODO: SCSI_FORMAT_UNIT");
msc->state = GEN_CSW;
break;
case SCSI_INQUIRY:
DEBUG_PUTS("SCSI_INQUIRY");
_scsi_inquiry(handler);
break;
case SCSI_START_STOP_UNIT:
DEBUG_PUTS("TODO: SCSI_START_STOP_UNIT");
msc->state = GEN_CSW;
break;
case SCSI_MEDIA_REMOVAL:
msc->cmd.status = 1;
DEBUG_PUTS("SCSI_MEDIA_REMOVAL");
msc->state = GEN_CSW;
break;
case SCSI_MODE_SELECT6:
DEBUG_PUTS("TODO: SCSI_MODE_SELECT6");
msc->state = GEN_CSW;
break;
case SCSI_MODE_SENSE6:
DEBUG_PUTS("SCSI_MODE_SENSE6");
_scsi_sense6(handler);
break;
case SCSI_MODE_SELECT10:
DEBUG_PUTS("TODO: SCSI_MODE_SELECT10");
msc->state = GEN_CSW;
break;
case SCSI_MODE_SENSE10:
DEBUG_PUTS("TODO: SCSI_MODE_SENSE10");
msc->state = GEN_CSW;
break;
case SCSI_READ_FORMAT_CAPACITIES:
DEBUG_PUTS("SCSI_READ_FORMAT_CAPACITIES");
_scsi_read_format_capacities(handler, cbw->lun);
break;
case SCSI_READ_CAPACITY:
DEBUG_PUTS("SCSI_READ_CAPACITY");
_scsi_read_capacity(handler, cbw->lun);
break;
case SCSI_READ10:
DEBUG("SCSI_READ10:%d\n", len);
_scsi_read10(handler, cbw, cbw->lun);
break;
case SCSI_WRITE10:
DEBUG("SCSI_WRITE10:%d\n", len);
_scsi_write10(handler, cbw, cbw->lun);
break;
case SCSI_VERIFY10:
DEBUG_PUTS("TODO: SCSI_VERIFY10");
msc->state = GEN_CSW;
break;
default:
DEBUG("Unhandled SCSI command:0x%x", cbw->cb[0]);
msc->state = GEN_CSW;
}
}
void scsi_gen_csw(usbus_handler_t *handler, cbw_info_t *cmd)
{
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc_csw_buf_t *csw = (msc_csw_buf_t *)msc->in_buf;
memset(csw, 0, sizeof(msc_csw_buf_t));
csw->signature = SCSI_CSW_SIGNATURE;
csw->tag = cmd->tag;
csw->data_left = cmd->len;
csw->status = cmd->status;
usbdev_ep_xmit(msc->ep_in->ep, (uint8_t *)csw, sizeof(msc_csw_buf_t));
}

30
tests/usbus_msc/Makefile Normal file
View File

@ -0,0 +1,30 @@
BOARD ?= same54-xpro
include ../Makefile.tests_common
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
USEMODULE += auto_init_usbus
USEMODULE += mtd
USEMODULE += mtd_write_page
USEMODULE += ps
USEMODULE += shell
USEMODULE += usbus_msc
USEMODULE += ztimer_msec
# Purposely disable auto_attach for this application
CFLAGS += -DCONFIG_USBUS_AUTO_ATTACH=0
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
USB_VID ?= $(USB_VID_TESTING)
USB_PID ?= $(USB_PID_TESTING)
include $(RIOTBASE)/Makefile.include

53
tests/usbus_msc/README.md Normal file
View File

@ -0,0 +1,53 @@
# USBUS Mass Storage Class (MSC) test application
## Overview
This application uses RIOT USBUS device stack to associate a MTD device and
exports it as Mass Storage Class on your host computer.
**Please note:** RIOT doesn't own any USB vendor and product ID. The test
application therefore uses `USB_VID_TESTING=0x1209` as manufacturer ID and
`USB_PID_TESTING=0x7d01` as product ID. Do not use these IDs outside of
test environments! They MUST NOT be used on any device that is redistributed,
sold or manufactured, as they are not unique!
To compile this application with your own vendor and product ID, set the
variables `USB_VID` and `USB_PID` in the makefile or at the command line,
for example
```
USB_VID=1234 USB_PID=5678 BOARD=... make -C tests/pkg_tinyusb_cdc_msc
```
## Usage
Once the application is flashed, the device should start a shell.
By default, the USB device is not attached to your host computer.
From RIOT shell, you should be able to list your MTD devices available onboard
with the command:
`add_lun` without arguments
From there you can decide to export one or several MTD devices using the
command:
`add_lun`
Once all the wanted MTD devices are selected, user should start the USB device
by attaching it to the host with the command:
`usb_attach`
At this point, the USBUS stack will export the selected MTD devices.
Devices should appears under /dev/sdX entries.
**Notes:** Depending on the MTD device and the USB speed, this operation can take some times.
USB operation can be stopped at any time using:
`usb_detach`
## Limitations
There is a few limitations that should be taken into account:
- MTD with less that 512 bytes of memory cannot be used with MSC.
- MTD devices with flashpage size > 4096 need to be implemented and thus, won't work for now.
## TODO
- Add support for MTD devices with flashpage size > 4096

View File

@ -0,0 +1,5 @@
CONFIG_MODULE_PS=y
CONFIG_MODULE_SHELL=y
CONFIG_MODULE_USBUS=y
CONFIG_MODULE_USBUS_MSC=y
CONFIG_MODULE_ZTIMER_MSEC=y

181
tests/usbus_msc/main.c Normal file
View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2019-2022 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for demonstrating USBUS MSC implementation
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "mtd.h"
#include "shell.h"
#include "usb/usbus.h"
#include "usb/usbus/msc.h"
#include "ztimer.h"
#ifndef MTD_NUMOF
#define MTD_NUMOF 0
#endif
static char _stack[USBUS_STACKSIZE];
static usbus_t usbus;
static usbus_msc_device_t msc;
static mtd_dev_t *_get_mtd_dev(unsigned idx)
{
switch (idx) {
#ifdef MTD_0
case 0: return MTD_0;
#endif
#ifdef MTD_1
case 1: return MTD_1;
#endif
#ifdef MTD_2
case 2: return MTD_2;
#endif
#ifdef MTD_3
case 3: return MTD_3;
#endif
}
return NULL;
}
static int _cmd_add_lun(int argc, char **argv)
{
int dev, ret;
mtd_dev_t *mtd_dev;
if (argc < 2) {
printf("usage: %s <mtd dev>\n", argv[0]);
puts("\tMTD devices available:");
for (int i = 0; i < (int)MTD_NUMOF; i++) {
printf("\t\t%i: MTD_DEV(%i)\n", i, i);
}
return -1;
}
/* parse the given MTD device */
dev = atol(argv[1]);
if (dev < 0 || dev >= (int)MTD_NUMOF) {
puts("error: invalid MTD device specified");
return -2;
}
mtd_dev = _get_mtd_dev(dev);
ret = usbus_msc_add_lun(&usbus, mtd_dev);
if (ret != 0) {
printf("Cannot add LUN device (error:%d %s)\n", ret, strerror(-ret));
}
return 0;
}
static int _cmd_remove_lun(int argc, char **argv)
{
int dev, ret;
mtd_dev_t *mtd_dev;
if (argc < 2) {
printf("usage: %s <mtd dev>\n", argv[0]);
puts("\tMTD devices available:");
for (int i = 0; i < (int)MTD_NUMOF; i++) {
printf("\t\t%i: MTD_DEV(%i)\n", i, i);
}
return -1;
}
/* parse the given MTD device */
dev = atol(argv[1]);
if (dev < 0 || dev >= (int)MTD_NUMOF) {
puts("error: invalid MTD device specified");
return -2;
}
mtd_dev = _get_mtd_dev(dev);
ret = usbus_msc_remove_lun(&usbus, mtd_dev);
if (ret == -EAGAIN) {
printf("MTD device was not registered\n");
}
return 0;
}
static int _cmd_usb_attach(int argc, char **argv)
{
(void)argc;
(void)argv;
static const usbopt_enable_t _enable = USBOPT_ENABLE;
usbdev_set(usbus.dev, USBOPT_ATTACH, &_enable,
sizeof(usbopt_enable_t));
return 0;
}
static int _cmd_usb_detach(int argc, char **argv)
{
(void)argc;
(void)argv;
static const usbopt_enable_t _enable = USBOPT_DISABLE;
usbdev_set(usbus.dev, USBOPT_ATTACH, &_enable,
sizeof(usbopt_enable_t));
return 0;
}
static int _cmd_usb_reset(int argc, char **argv)
{
_cmd_usb_detach(argc, argv);
ztimer_sleep(ZTIMER_MSEC, 100);
_cmd_usb_attach(argc, argv);
return 0;
}
static const shell_command_t shell_commands[] = {
{ "add_lun", "Add a MTD device as new LUN", _cmd_add_lun },
{ "remove_lun", "Remove existing LUN", _cmd_remove_lun },
{ "usb_attach", "Attach USB to host", _cmd_usb_attach },
{ "usb_detach", "Detach USB from host", _cmd_usb_detach },
{ "usb_reset", "Combine Detach and Attach with a 100ms delay", _cmd_usb_reset },
{ NULL, NULL, NULL }
};
int main(void)
{
puts("RIOT USB MSC test application");
puts("Add one or more MTD devices as USB LUN");
puts("Then use the attach command to connect");
puts("your device and start USB operation");
/* Get driver context */
usbdev_t *usbdev = usbdev_get_ctx(0);
assert(usbdev);
/* Initialize basic usbus struct, don't start the thread yet */
usbus_init(&usbus, usbdev);
/* Initialize Mass Storage Class */
usbus_msc_init(&usbus, &msc);
/* Create USBUS thread */
usbus_create(_stack, USBUS_STACKSIZE, USBUS_PRIO, USBUS_TNAME, &usbus);
/* start shell */
puts("All up, running the shell now");
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
/* should be never reached */
return 0;
}