mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
usbus/msc: add initial support for Mass Storage Class
Signed-off-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
This commit is contained in:
parent
4ccf0af0b1
commit
d49ed218ab
@ -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
104
sys/include/usb/msc.h
Normal 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
128
sys/include/usb/usbus/msc.h
Normal 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 */
|
||||
/** @} */
|
295
sys/include/usb/usbus/msc/scsi.h
Normal file
295
sys/include/usb/usbus/msc/scsi.h
Normal 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 */
|
||||
/** @} */
|
@ -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
|
||||
|
3
sys/usb/usbus/msc/Makefile
Normal file
3
sys/usb/usbus/msc/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE = usbus_msc
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
458
sys/usb/usbus/msc/msc.c
Normal file
458
sys/usb/usbus/msc/msc.c
Normal 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
389
sys/usb/usbus/msc/scsi.c
Normal 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));
|
||||
}
|
Loading…
Reference in New Issue
Block a user