summaryrefslogtreecommitdiff
path: root/src/class/msc
diff options
context:
space:
mode:
authorhathach <[email protected]>2025-09-15 16:45:41 +0700
committerhathach <[email protected]>2025-09-15 16:45:41 +0700
commit8e34ba9cf6bdcabe2c9eb73ae1aafec38d0ec46d (patch)
tree58c2426146aa5d413706a62521ab6e2cb740b170 /src/class/msc
parent802819d271314298ea5a786a37ec6a1e65ef31d6 (diff)
parentfeef534921446e1a3ded54a0c46191fe1709bdb4 (diff)
Merge branch 'master' into fork/ennebi/mtp
Diffstat (limited to 'src/class/msc')
-rw-r--r--src/class/msc/msc.h27
-rw-r--r--src/class/msc/msc_device.c307
-rw-r--r--src/class/msc/msc_device.h60
3 files changed, 245 insertions, 149 deletions
diff --git a/src/class/msc/msc.h b/src/class/msc/msc.h
index bbfd35a43..b2b44eac4 100644
--- a/src/class/msc/msc.h
+++ b/src/class/msc/msc.h
@@ -108,8 +108,7 @@ TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct");
//--------------------------------------------------------------------+
/// SCSI Command Operation Code
-typedef enum
-{
+typedef enum {
SCSI_CMD_TEST_UNIT_READY = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation.
SCSI_CMD_INQUIRY = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device.
SCSI_CMD_MODE_SELECT_6 = 0x15, ///< provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters.
@@ -124,8 +123,7 @@ typedef enum
}scsi_cmd_type_t;
/// SCSI Sense Key
-typedef enum
-{
+typedef enum {
SCSI_SENSE_NONE = 0x00, ///< no specific Sense Key. This would be the case for a successful command
SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< Indicates the last command completed successfully with some recovery action performed by the disc drive.
SCSI_SENSE_NOT_READY = 0x02, ///< Indicates the logical unit addressed cannot be accessed.
@@ -141,6 +139,27 @@ typedef enum
SCSI_SENSE_MISCOMPARE = 0x0e ///< Indicates that the source data did not match the data read from the medium.
}scsi_sense_key_type_t;
+
+typedef enum {
+ SCSI_PDT_DIRECT_ACCESS = 0x0,
+ SCSI_PDT_SEQUENTIAL_ACCESS = 0x1,
+ SCSI_PDT_PRINTER = 0x2,
+ SCSI_PDT_PROCESSOR = 0x3,
+ SCSI_PDT_WRITE_ONCE = 0x4,
+ SCSI_PDT_CD_DVD = 0x5,
+ SCSI_PDT_SCANNER = 0x6,
+ SCSI_PDT_OPTICAL_DEVICE = 0x7,
+ SCSI_PDT_MEDIUM_CHANGER = 0x8,
+ SCSI_PDT_COMMUNICATIONS = 0x9, // obsolete
+ SCSI_PDT_RAID = 0x0c,
+ SCSI_PDT_ENCLOSURE_SERVICES = 0x0d,
+ SCSI_PDT_SIMPLIFIED_DIRECT_ACCESS = 0x0e,
+ SCSI_PDT_OPTICAL_CARD_READER = 0x0f,
+ SCSI_PDT_BRIDGE = 0x10, ///< Bridge device, e.g. USB to SCSI bridge
+ SCSI_PDT_OBJECT_BASED_STORAGE = 0x11, ///< Object-based storage device
+ SCSI_PDT_AUTOMATION_DRIVE_INTERFACE = 0x12, ///< Automation/Drive Interface (ADI) device
+} scsi_peripheral_device_type_t;
+
//--------------------------------------------------------------------+
// SCSI Primary Command (SPC-4)
//--------------------------------------------------------------------+
diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c
index 6670045aa..a32014c2d 100644
--- a/src/class/msc/msc_device.c
+++ b/src/class/msc/msc_device.c
@@ -42,6 +42,17 @@
#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MSC_LOG_LEVEL, __VA_ARGS__)
//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
+ (void) lun; (void) vendor_id; (void) product_id; (void) product_rev;
+}
+TU_ATTR_WEAK uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize) {
+ (void) lun; (void) inquiry_resp; (void) bufsize;
+ return 0;
+}
+
+//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF
//--------------------------------------------------------------------+
enum {
@@ -53,23 +64,26 @@ enum {
};
typedef struct {
- TU_ATTR_ALIGNED(4) msc_cbw_t cbw;
- TU_ATTR_ALIGNED(4) msc_csw_t csw;
+ TU_ATTR_ALIGNED(4) msc_cbw_t cbw; // 31 bytes
+ uint8_t rhport;
+ TU_ATTR_ALIGNED(4) msc_csw_t csw; // 13 bytes
uint8_t itf_num;
uint8_t ep_in;
uint8_t ep_out;
- // Bulk Only Transfer (BOT) Protocol
- uint8_t stage;
-
uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw
uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage
- // Sense Response Data
+ // Bulk Only Transfer (BOT) Protocol
+ uint8_t stage;
+
+ // SCSI Sense Response Data
uint8_t sense_key;
uint8_t add_sense_code;
uint8_t add_sense_qualifier;
+
+ uint8_t pending_io; // pending async IO
}mscd_interface_t;
static mscd_interface_t _mscd_itf;
@@ -82,31 +96,36 @@ CFG_TUD_MEM_SECTION static struct {
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize);
-static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc);
-
-static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc);
-static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes);
+static void proc_read10_cmd(mscd_interface_t* p_msc);
+static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes);
+static void proc_write10_cmd(mscd_interface_t* p_msc);
+static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes);
+static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes);
+static bool proc_stage_status(mscd_interface_t* p_msc);
TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir) {
return tu_bit_test(dir, 7);
}
-static inline bool send_csw(uint8_t rhport, mscd_interface_t* p_msc) {
+static inline bool send_csw(mscd_interface_t* p_msc) {
// Data residue is always = host expect - actual transferred
+ uint8_t rhport = p_msc->rhport;
p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len;
p_msc->stage = MSC_STAGE_STATUS_SENT;
memcpy(_mscd_epbuf.buf, &p_msc->csw, sizeof(msc_csw_t));
return usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t));
}
-static inline bool prepare_cbw(uint8_t rhport, mscd_interface_t* p_msc) {
+static inline bool prepare_cbw(mscd_interface_t* p_msc) {
+ uint8_t rhport = p_msc->rhport;
p_msc->stage = MSC_STAGE_CMD;
return usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, sizeof(msc_cbw_t));
}
-static void fail_scsi_op(uint8_t rhport, mscd_interface_t* p_msc, uint8_t status) {
+static void fail_scsi_op(mscd_interface_t* p_msc, uint8_t status) {
msc_cbw_t const * p_cbw = &p_msc->cbw;
msc_csw_t * p_csw = &p_msc->csw;
+ uint8_t rhport = p_msc->rhport;
p_csw->status = status;
p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len;
@@ -177,6 +196,33 @@ static uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) {
return status;
}
+static bool proc_stage_status(mscd_interface_t *p_msc) {
+ uint8_t rhport = p_msc->rhport;
+ msc_cbw_t const *p_cbw = &p_msc->cbw;
+
+ // skip status if epin is currently stalled, will do it when received Clear Stall request
+ if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) {
+ if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) {
+ // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status
+ // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len);
+ usbd_edpt_stall(rhport, p_msc->ep_in);
+ } else {
+ TU_ASSERT(send_csw(p_msc));
+ }
+ }
+
+ #if TU_CHECK_MCU(OPT_MCU_CXD56)
+ // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD.
+ // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and
+ // hope everything will work
+ if (usbd_edpt_stalled(rhport, p_msc->ep_in)) {
+ usbd_edpt_clear_stall(rhport, p_msc->ep_in);
+ send_csw(p_msc);
+ }
+ #endif
+ return true;
+}
+
//--------------------------------------------------------------------+
// Debug
//--------------------------------------------------------------------+
@@ -214,15 +260,51 @@ bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, u
return true;
}
-static inline void set_sense_medium_not_present(uint8_t lun) {
+TU_ATTR_ALWAYS_INLINE static inline void set_sense_medium_not_present(uint8_t lun) {
// default sense is NOT READY, MEDIUM NOT PRESENT
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00);
}
+static void proc_async_io_done(void *bytes_io) {
+ mscd_interface_t *p_msc = &_mscd_itf;
+ TU_VERIFY(p_msc->pending_io, );
+ const int32_t nbytes = (int32_t) (intptr_t) bytes_io;
+ const uint8_t cmd = p_msc->cbw.command[0];
+
+ p_msc->pending_io = 0;
+ switch (cmd) {
+ case SCSI_CMD_READ_10:
+ proc_read_io_data(p_msc, nbytes);
+ break;
+
+ case SCSI_CMD_WRITE_10:
+ proc_write_io_data(p_msc, (uint32_t) nbytes, nbytes);
+ break;
+
+ default: break;
+ }
+
+ // send status if stage is transitioned to STATUS
+ if (p_msc->stage == MSC_STAGE_STATUS) {
+ proc_stage_status(p_msc);
+ }
+}
+
+bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr) {
+ // Precheck to avoid queueing multiple RW done callback
+ TU_VERIFY(_mscd_itf.pending_io);
+ if (bytes_io == 0) {
+ bytes_io = TUD_MSC_RET_ERROR; // 0 is treated as error, no reason to call this with BUSY here
+ }
+ usbd_defer_func(proc_async_io_done, (void *) (intptr_t) bytes_io, in_isr);
+ return true;
+}
+
//--------------------------------------------------------------------+
// USBD Driver API
//--------------------------------------------------------------------+
void mscd_init(void) {
+ TU_LOG_INT(CFG_TUD_MSC_LOG_LEVEL, sizeof(mscd_interface_t));
tu_memclr(&_mscd_itf, sizeof(mscd_interface_t));
}
@@ -245,12 +327,13 @@ uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
mscd_interface_t * p_msc = &_mscd_itf;
p_msc->itf_num = itf_desc->bInterfaceNumber;
+ p_msc->rhport = rhport;
// Open endpoint pair
TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0);
// Prepare for Command Block Wrapper
- TU_ASSERT(prepare_cbw(rhport, p_msc), drv_len);
+ TU_ASSERT(prepare_cbw(p_msc), drv_len);
return drv_len;
}
@@ -289,14 +372,14 @@ bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
if (ep_addr == p_msc->ep_in) {
if (p_msc->stage == MSC_STAGE_STATUS) {
// resume sending SCSI status if we are in this stage previously before stalled
- TU_ASSERT(send_csw(rhport, p_msc));
+ TU_ASSERT(send_csw(p_msc));
}
} else if (ep_addr == p_msc->ep_out) {
if (p_msc->stage == MSC_STAGE_CMD) {
// part of reset recovery (probably due to invalid CBW) -> prepare for new command
// Note: skip if already queued previously
if (usbd_edpt_ready(rhport, p_msc->ep_out)) {
- TU_ASSERT(prepare_cbw(rhport, p_msc));
+ TU_ASSERT(prepare_cbw(p_msc));
}
}
}
@@ -344,7 +427,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
msc_csw_t * p_csw = &p_msc->csw;
switch (p_msc->stage) {
- case MSC_STAGE_CMD:
+ case MSC_STAGE_CMD: {
//------------- new CBW received -------------//
// Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it
if (ep_addr != p_msc->ep_out) {
@@ -382,12 +465,12 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
uint8_t const status = rdwr10_validate_cmd(p_cbw);
if (status != MSC_CSW_STATUS_PASSED) {
- fail_scsi_op(rhport, p_msc, status);
+ fail_scsi_op(p_msc, status);
} else if (p_cbw->total_bytes) {
if (SCSI_CMD_READ_10 == p_cbw->command[0]) {
- proc_read10_cmd(rhport, p_msc);
+ proc_read10_cmd(p_msc);
} else {
- proc_write10_cmd(rhport, p_msc);
+ proc_write10_cmd(p_msc);
}
} else {
// no data transfer, only exist in complaint test suite
@@ -400,7 +483,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
if ((p_cbw->total_bytes > 0) && !is_data_in(p_cbw->dir)) {
if (p_cbw->total_bytes > CFG_TUD_MSC_EP_BUFSIZE) {
TU_LOG_DRV(" SCSI reject non READ10/WRITE10 with large data\r\n");
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
} else {
// Didn't check for case 9 (Ho > Dn), which requires examining scsi command first
// but it is OK to just receive data then responded with failed status
@@ -418,12 +501,12 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
if (resplen < 0) {
// unsupported command
TU_LOG_DRV(" SCSI unsupported or failed command\r\n");
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
} else if (resplen == 0) {
if (p_cbw->total_bytes) {
// 6.7 The 13 Cases: case 4 (Hi > Dn)
// TU_LOG_DRV(" SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes);
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
} else {
// case 1 Hn = Dn: all good
p_msc->stage = MSC_STAGE_STATUS;
@@ -432,7 +515,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
if (p_cbw->total_bytes == 0) {
// 6.7 The 13 Cases: case 2 (Hn < Di)
// TU_LOG_DRV(" SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes);
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
} else {
// cannot return more than host expect
p_msc->total_len = tu_min32((uint32_t)resplen, p_cbw->total_bytes);
@@ -441,7 +524,8 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
}
}
}
- break;
+ break;
+ }
case MSC_STAGE_DATA:
TU_LOG_DRV(" SCSI Data [Lun%u]\r\n", p_cbw->lun);
@@ -455,10 +539,10 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
// Data Stage is complete
p_msc->stage = MSC_STAGE_STATUS;
}else {
- proc_read10_cmd(rhport, p_msc);
+ proc_read10_cmd(p_msc);
}
} else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) {
- proc_write10_new_data(rhport, p_msc, xferred_bytes);
+ proc_write10_host_data(p_msc, xferred_bytes);
} else {
p_msc->xferred_len += xferred_bytes;
@@ -469,7 +553,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
if ( cb_result < 0 ) {
// unsupported command
TU_LOG_DRV(" SCSI unsupported command\r\n");
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
}else {
// TODO haven't implement this scenario any further yet
}
@@ -490,7 +574,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
break;
case MSC_STAGE_STATUS_SENT:
- // Wait for the Status phase to complete
+ // Status phase is complete
if ((ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t))) {
TU_LOG_DRV(" SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status);
// TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_csw, xferred_bytes, 2);
@@ -518,9 +602,9 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
break;
}
- TU_ASSERT(prepare_cbw(rhport, p_msc));
+ TU_ASSERT(prepare_cbw(p_msc));
} else {
- // Any xfer ended here is consider unknown error, ignore it
+ // Any xfer ended here is considered unknown error, ignore it
TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n");
}
break;
@@ -529,26 +613,7 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
}
if (p_msc->stage == MSC_STAGE_STATUS) {
- // skip status if epin is currently stalled, will do it when received Clear Stall request
- if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) {
- if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) {
- // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status
- // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len);
- usbd_edpt_stall(rhport, p_msc->ep_in);
- } else {
- TU_ASSERT(send_csw(rhport, p_msc));
- }
- }
-
- #if TU_CHECK_MCU(OPT_MCU_CXD56)
- // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD.
- // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and
- // hope everything will work
- if ( usbd_edpt_stalled(rhport, p_msc->ep_in) ) {
- usbd_edpt_clear_stall(rhport, p_msc->ep_in);
- send_csw(rhport, p_msc);
- }
- #endif
+ TU_ASSERT(proc_stage_status(p_msc));
}
return true;
@@ -645,8 +710,7 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_
break;
case SCSI_CMD_READ_FORMAT_CAPACITY: {
- scsi_read_format_capacity_data_t read_fmt_capa =
- {
+ scsi_read_format_capacity_data_t read_fmt_capa = {
.list_length = 8,
.block_num = 0,
.descriptor_type = 2, // formatted media
@@ -678,29 +742,24 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_
break;
case SCSI_CMD_INQUIRY: {
- scsi_inquiry_resp_t inquiry_rsp =
- {
- .is_removable = 1,
- .version = 2,
- .response_data_format = 2,
- .additional_length = sizeof(scsi_inquiry_resp_t) - 5,
- };
-
- // vendor_id, product_id, product_rev is space padded string
- memset(inquiry_rsp.vendor_id , ' ', sizeof(inquiry_rsp.vendor_id));
- memset(inquiry_rsp.product_id , ' ', sizeof(inquiry_rsp.product_id));
- memset(inquiry_rsp.product_rev, ' ', sizeof(inquiry_rsp.product_rev));
-
- tud_msc_inquiry_cb(lun, inquiry_rsp.vendor_id, inquiry_rsp.product_id, inquiry_rsp.product_rev);
+ scsi_inquiry_resp_t *inquiry_rsp = (scsi_inquiry_resp_t *) buffer;
+ tu_memclr(inquiry_rsp, sizeof(scsi_inquiry_resp_t));
+ inquiry_rsp->is_removable = 1;
+ inquiry_rsp->version = 2;
+ inquiry_rsp->response_data_format = 2;
+ inquiry_rsp->additional_length = sizeof(scsi_inquiry_resp_t) - 5;
- resplen = sizeof(inquiry_rsp);
- TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &inquiry_rsp, (size_t) resplen));
+ resplen = (int32_t) tud_msc_inquiry2_cb(lun, inquiry_rsp, bufsize);
+ if (resplen == 0) {
+ // stub callback with no response, use v1 callback
+ tud_msc_inquiry_cb(lun, inquiry_rsp->vendor_id, inquiry_rsp->product_id, inquiry_rsp->product_rev);
+ resplen = sizeof(scsi_inquiry_resp_t);
+ }
}
break;
case SCSI_CMD_MODE_SENSE_6: {
- scsi_mode_sense6_resp_t mode_resp =
- {
+ scsi_mode_sense6_resp_t mode_resp = {
.data_len = 3,
.medium_type = 0,
.write_protected = false,
@@ -721,8 +780,7 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_
break;
case SCSI_CMD_REQUEST_SENSE: {
- scsi_sense_fixed_resp_t sense_rsp =
- {
+ scsi_sense_fixed_resp_t sense_rsp = {
.response_code = 0x70, // current, fixed format
.valid = 1
};
@@ -752,39 +810,49 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_
return resplen;
}
-static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc) {
+static void proc_read10_cmd(mscd_interface_t* p_msc) {
msc_cbw_t const* p_cbw = &p_msc->cbw;
-
- // block size already verified not zero
- uint16_t const block_sz = rdwr10_get_blocksize(p_cbw);
-
- // Adjust lba with transferred bytes
+ uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero
+ // Adjust lba & offset with transferred bytes
uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz);
+ uint32_t const offset = p_msc->xferred_len % block_sz;
// remaining bytes capped at class buffer
int32_t nbytes = (int32_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len);
- // Application can consume smaller bytes
- uint32_t const offset = p_msc->xferred_len % block_sz;
+ p_msc->pending_io = 1;
nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, (uint32_t)nbytes);
+ if (nbytes != TUD_MSC_RET_ASYNC) {
+ p_msc->pending_io = 0;
+ proc_read_io_data(p_msc, nbytes);
+ }
+}
- if (nbytes < 0) {
- // negative means error -> endpoint is stalled & status in CSW set to failed
- TU_LOG_DRV(" tud_msc_read10_cb() return -1\r\n");
+static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes) {
+ const uint8_t rhport = p_msc->rhport;
+ if (nbytes > 0) {
+ TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes),);
+ } else {
+ // nbytes is status
+ switch (nbytes) {
+ case TUD_MSC_RET_ERROR:
+ // error -> endpoint is stalled & status in CSW set to failed
+ TU_LOG_DRV(" IO read() failed\r\n");
+ set_sense_medium_not_present(p_msc->cbw.lun);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
+ break;
- // set sense
- set_sense_medium_not_present(p_cbw->lun);
+ case TUD_MSC_RET_BUSY:
+ // not ready yet -> fake a transfer complete so that this driver callback will fire again
+ dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false);
+ break;
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
- } else if (nbytes == 0) {
- // zero means not ready -> simulate an transfer complete so that this driver callback will fired again
- dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false);
- } else {
- TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes),);
+ default: break;
+ }
}
}
-static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) {
+static void proc_write10_cmd(mscd_interface_t* p_msc) {
msc_cbw_t const* p_cbw = &p_msc->cbw;
bool writable = true;
@@ -796,51 +864,56 @@ static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) {
// Not writable, complete this SCSI op with error
// Sense = Write protected
tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00);
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
return;
}
// remaining bytes capped at class buffer
uint16_t nbytes = (uint16_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len);
-
// Write10 callback will be called later when usb transfer complete
- TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes),);
+ TU_ASSERT(usbd_edpt_xfer(p_msc->rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes),);
}
// process new data arrived from WRITE10
-static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes) {
+static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes) {
msc_cbw_t const* p_cbw = &p_msc->cbw;
+ uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero
- // block size already verified not zero
- uint16_t const block_sz = rdwr10_get_blocksize(p_cbw);
-
- // Adjust lba with transferred bytes
+ // Adjust lba & offset with transferred bytes
uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz);
-
- // Invoke callback to consume new data
uint32_t const offset = p_msc->xferred_len % block_sz;
- int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes);
- if (nbytes < 0) {
- // negative means error -> failed this scsi op
- TU_LOG_DRV(" tud_msc_write10_cb() return -1\r\n");
+ p_msc->pending_io = 1;
+ int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes);
+ if (nbytes != TUD_MSC_RET_ASYNC) {
+ p_msc->pending_io = 0;
+ proc_write_io_data(p_msc, xferred_bytes, nbytes);
+ }
+}
- // update actual byte before failed
- p_msc->xferred_len += xferred_bytes;
+static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes) {
+ if (nbytes < 0) {
+ // nbytes is status
+ switch (nbytes) {
+ case TUD_MSC_RET_ERROR:
+ // IO error -> failed this scsi op
+ TU_LOG_DRV(" IO write() failed\r\n");
+ set_sense_medium_not_present(p_msc->cbw.lun);
+ fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED);
+ break;
- set_sense_medium_not_present(p_cbw->lun);
- fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED);
+ default: break;
+ }
} else {
if ((uint32_t)nbytes < xferred_bytes) {
- // Application consume less than what we got (including zero)
+ // Application consume less than what we got including TUD_MSC_RET_BUSY (0)
const uint32_t left_over = xferred_bytes - (uint32_t)nbytes;
if (nbytes > 0) {
- p_msc->xferred_len += (uint16_t)nbytes;
memmove(_mscd_epbuf.buf, _mscd_epbuf.buf + nbytes, left_over);
}
- // simulate a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameter
- dcd_event_xfer_complete(rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false);
+ // fake a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameters
+ dcd_event_xfer_complete(p_msc->rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false);
} else {
// Application consume all bytes in our buffer
p_msc->xferred_len += xferred_bytes;
@@ -850,7 +923,7 @@ static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint3
p_msc->stage = MSC_STAGE_STATUS;
} else {
// prepare to receive more data from host
- proc_write10_cmd(rhport, p_msc);
+ proc_write10_cmd(p_msc);
}
}
}
diff --git a/src/class/msc/msc_device.h b/src/class/msc/msc_device.h
index 29acd280a..144b74f71 100644
--- a/src/class/msc/msc_device.h
+++ b/src/class/msc/msc_device.h
@@ -48,6 +48,13 @@
#error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better
#endif
+// Return value of callback functions
+enum {
+ TUD_MSC_RET_BUSY = 0, // Busy, e.g disk I/O is not ready
+ TUD_MSC_RET_ERROR = -1,
+ TUD_MSC_RET_ASYNC = -2, // Asynchronous IO
+};
+
TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct");
//--------------------------------------------------------------------+
@@ -57,44 +64,41 @@ TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct");
// Set SCSI sense response
bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier);
+// Called by Application once asynchronous I/O operation is done
+// bytes_io is number of bytes in I/O op, typically the bufsize in read/write_cb() or
+// TUD_MSC_RET_ERROR (-1) for error. Note TUD_MSC_RET_BUSY (0) will be treated as error as well.
+bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr);
+
//--------------------------------------------------------------------+
// Application Callbacks (WEAK is optional)
//--------------------------------------------------------------------+
-// Invoked when received SCSI READ10 command
-// - Address = lba * BLOCK_SIZE + offset
-// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE.
-//
-// - Application fill the buffer (up to bufsize) with address contents and return number of read byte. If
-// - read < bufsize : These bytes are transferred first and callback invoked again for remaining data.
-//
-// - read == 0 : Indicate application is not ready yet e.g disk I/O busy.
-// Callback invoked again with the same parameters later on.
-//
-// - read < 0 : Indicate application error e.g invalid address. This request will be STALLed
-// and return failed status in command status wrapper phase.
+/*
+ Invoked when received SCSI READ10/WRITE10 command
+ - Address = lba * BLOCK_SIZE + offset
+ - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE.
+ - Application fill the buffer (up to bufsize) with address contents and return number of bytes read or status.
+ - 0 < ret < bufsize: These bytes are transferred first and callback will be invoked again for remaining data.
+ - TUD_MSC_RET_BUSY
+ Application is buys e.g disk I/O not ready. Callback will be invoked again with the same parameters later on.
+ - TUD_MSC_RET_ERROR
+ error such as invalid address. This request will be STALLed and scsi command will be failed
+ - TUD_MSC_RET_ASYNC
+ Data I/O will be done asynchronously in a background task. Application should return immediately.
+ tud_msc_async_io_done() must be called once IO/ is done to signal completion.
+*/
int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
-
-// Invoked when received SCSI WRITE10 command
-// - Address = lba * BLOCK_SIZE + offset
-// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE.
-//
-// - Application write data from buffer to address contents (up to bufsize) and return number of written byte. If
-// - write < bufsize : callback invoked again with remaining data later on.
-//
-// - write == 0 : Indicate application is not ready yet e.g disk I/O busy.
-// Callback invoked again with the same parameters later on.
-//
-// - write < 0 : Indicate application error e.g invalid address. This request will be STALLed
-// and return failed status in command status wrapper phase.
-//
-// TODO change buffer to const uint8_t*
int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
-// Invoked when received SCSI_CMD_INQUIRY
+// Invoked when received SCSI_CMD_INQUIRY, v1, application should use v2 if possible
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]);
+// Invoked when received SCSI_CMD_INQUIRY, v2 with full inquiry response
+// Some inquiry_resp's fields are already filled with default values, application can update them
+// Return length of inquiry response, typically sizeof(scsi_inquiry_resp_t) (36 bytes), can be longer if included vendor data.
+uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize);
+
// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun);