Skip to content

Commit b10c46a

Browse files
[nrf fromtree] usb: device_next: msc: do not copy SCSI data
Support only power-of-two disk sector sizes to enable significant throughput improvements: * SCSI data zero-copy * allow queuing multi-packet transfers Previously large SCSI buffers did not improve performance. With this change, larger SCSI buffer allows scheduling bigger USB transfers which reduces overhead. Co-authored-by: Tomasz Moń <tomasz.mon@nordicsemi.no> Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no> Signed-off-by: Mark Wang <yichang.wang@nxp.com> (cherry picked from commit 3c4b4e1)
1 parent a753d6a commit b10c46a

File tree

3 files changed

+121
-94
lines changed

3 files changed

+121
-94
lines changed

subsys/usb/device_next/class/usbd_msc.c

Lines changed: 96 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,8 @@ struct CSW {
6060
/* Single instance is likely enough because it can support multiple LUNs */
6161
#define MSC_NUM_INSTANCES CONFIG_USBD_MSC_INSTANCES_COUNT
6262

63-
/* Can be 64 if device is not High-Speed capable */
64-
#define MSC_BUF_SIZE USBD_MAX_BULK_MPS
65-
6663
UDC_BUF_POOL_DEFINE(msc_ep_pool,
67-
MSC_NUM_INSTANCES * 2, MSC_BUF_SIZE,
64+
MSC_NUM_INSTANCES * 2, USBD_MAX_BULK_MPS,
6865
sizeof(struct udc_buf_info), NULL);
6966

7067
struct msc_event {
@@ -121,9 +118,8 @@ struct msc_bot_ctx {
121118
struct scsi_ctx luns[CONFIG_USBD_MSC_LUNS_PER_INSTANCE];
122119
struct CBW cbw;
123120
struct CSW csw;
124-
uint8_t scsi_buf[CONFIG_USBD_MSC_SCSI_BUFFER_SIZE];
121+
uint8_t *scsi_buf;
125122
uint32_t transferred_data;
126-
size_t scsi_offset;
127123
size_t scsi_bytes;
128124
};
129125

@@ -143,6 +139,53 @@ static struct net_buf *msc_buf_alloc(const uint8_t ep)
143139
return buf;
144140
}
145141

142+
static struct net_buf *msc_buf_alloc_data(const uint8_t ep, uint8_t *data, size_t len)
143+
{
144+
struct net_buf *buf = NULL;
145+
struct udc_buf_info *bi;
146+
147+
buf = net_buf_alloc_with_data(&msc_ep_pool, data, len, K_NO_WAIT);
148+
if (buf == NULL) {
149+
return NULL;
150+
}
151+
152+
if (USB_EP_DIR_IS_OUT(ep)) {
153+
/* Buffer is empty, USB stack will write data from host */
154+
buf->len = 0;
155+
}
156+
157+
bi = udc_get_buf_info(buf);
158+
bi->ep = ep;
159+
160+
return buf;
161+
}
162+
163+
static size_t msc_next_transfer_length(struct usbd_class_data *const c_data)
164+
{
165+
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
166+
struct msc_bot_ctx *ctx = usbd_class_get_private(c_data);
167+
struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN];
168+
size_t len = scsi_cmd_remaining_data_len(lun);
169+
170+
len = MIN(CONFIG_USBD_MSC_SCSI_BUFFER_SIZE, len);
171+
172+
/* Limit transfer to bulk endpoint wMaxPacketSize multiple */
173+
if (USBD_SUPPORTS_HIGH_SPEED &&
174+
usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
175+
len = ROUND_DOWN(len, 512);
176+
} else {
177+
/* Full-Speed */
178+
len = ROUND_DOWN(len, 64);
179+
}
180+
181+
/* Round down to sector size multiple */
182+
if (lun->sector_size) {
183+
len = ROUND_DOWN(len, lun->sector_size);
184+
}
185+
186+
return len;
187+
}
188+
146189
static uint8_t msc_get_bulk_in(struct usbd_class_data *const c_data)
147190
{
148191
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
@@ -171,10 +214,11 @@ static uint8_t msc_get_bulk_out(struct usbd_class_data *const c_data)
171214
return desc->if0_out_ep.bEndpointAddress;
172215
}
173216

174-
static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data)
217+
static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data, bool data)
175218
{
176219
struct msc_bot_ctx *ctx = usbd_class_get_private(c_data);
177220
struct net_buf *buf;
221+
uint8_t *scsi_buf = ctx->scsi_buf;
178222
uint8_t ep;
179223
int ret;
180224

@@ -185,7 +229,13 @@ static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data)
185229

186230
LOG_DBG("Queuing OUT");
187231
ep = msc_get_bulk_out(c_data);
188-
buf = msc_buf_alloc(ep);
232+
233+
if (data) {
234+
buf = msc_buf_alloc_data(ep, scsi_buf, msc_next_transfer_length(c_data));
235+
} else {
236+
buf = msc_buf_alloc(ep);
237+
}
238+
189239
/* The pool is large enough to support all allocations. Failing alloc
190240
* indicates either a memory leak or logic error.
191241
*/
@@ -255,50 +305,37 @@ static void msc_process_read(struct msc_bot_ctx *ctx)
255305
struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN];
256306
int bytes_queued = 0;
257307
struct net_buf *buf;
308+
uint8_t *scsi_buf = ctx->scsi_buf;
258309
uint8_t ep;
259-
size_t len;
260310
int ret;
261311

262312
/* Fill SCSI Data IN buffer if there is no data available */
263313
if (ctx->scsi_bytes == 0) {
264-
ctx->scsi_bytes = scsi_read_data(lun, ctx->scsi_buf);
265-
ctx->scsi_offset = 0;
314+
size_t len = msc_next_transfer_length(ctx->class_node);
315+
316+
bytes_queued = scsi_read_data(lun, scsi_buf, len);
317+
} else {
318+
bytes_queued = ctx->scsi_bytes;
266319
}
267320

321+
/* All data is submitted in one go. Any potential new data will
322+
* have to be retrieved using scsi_read_data() on next call.
323+
*/
324+
ctx->scsi_bytes = 0;
325+
268326
if (atomic_test_and_set_bit(&ctx->bits, MSC_BULK_IN_QUEUED)) {
269327
__ASSERT_NO_MSG(false);
270328
LOG_ERR("IN already queued");
271329
return;
272330
}
273331

274332
ep = msc_get_bulk_in(ctx->class_node);
275-
buf = msc_buf_alloc(ep);
333+
buf = msc_buf_alloc_data(ep, scsi_buf, bytes_queued);
276334
/* The pool is large enough to support all allocations. Failing alloc
277335
* indicates either a memory leak or logic error.
278336
*/
279337
__ASSERT_NO_MSG(buf);
280338

281-
while (ctx->scsi_bytes - ctx->scsi_offset > 0) {
282-
len = MIN(ctx->scsi_bytes - ctx->scsi_offset,
283-
MSC_BUF_SIZE - bytes_queued);
284-
if (len == 0) {
285-
/* Either queued as much as possible or there is no more
286-
* SCSI IN data available
287-
*/
288-
break;
289-
}
290-
291-
net_buf_add_mem(buf, &ctx->scsi_buf[ctx->scsi_offset], len);
292-
bytes_queued += len;
293-
ctx->scsi_offset += len;
294-
295-
if (ctx->scsi_bytes == ctx->scsi_offset) {
296-
/* SCSI buffer can be reused now */
297-
ctx->scsi_bytes = scsi_read_data(lun, ctx->scsi_buf);
298-
ctx->scsi_offset = 0;
299-
}
300-
}
301-
302339
/* Either the net buf is full or there is no more SCSI data */
303340
ctx->csw.dCSWDataResidue -= bytes_queued;
304341
ret = usbd_ep_enqueue(ctx->class_node, buf);
@@ -319,7 +356,6 @@ static void msc_process_cbw(struct msc_bot_ctx *ctx)
319356
cb_len = scsi_usb_boot_cmd_len(ctx->cbw.CBWCB, ctx->cbw.bCBWCBLength);
320357
data_len = scsi_cmd(lun, ctx->cbw.CBWCB, cb_len, ctx->scsi_buf);
321358
ctx->scsi_bytes = data_len;
322-
ctx->scsi_offset = 0;
323359
cmd_is_data_read = scsi_cmd_is_data_read(lun);
324360
cmd_is_data_write = scsi_cmd_is_data_write(lun);
325361
data_len += scsi_cmd_remaining_data_len(lun);
@@ -399,46 +435,17 @@ static void msc_process_write(struct msc_bot_ctx *ctx,
399435

400436
ctx->transferred_data += len;
401437

402-
while ((len > 0) && (scsi_cmd_remaining_data_len(lun) > 0)) {
403-
/* Copy received data to the end of SCSI buffer */
404-
tmp = MIN(len, sizeof(ctx->scsi_buf) - ctx->scsi_bytes);
405-
memcpy(&ctx->scsi_buf[ctx->scsi_bytes], buf, tmp);
406-
ctx->scsi_bytes += tmp;
407-
buf += tmp;
408-
len -= tmp;
409-
410-
/* Pass data to SCSI layer when either all transfer data bytes
411-
* have been received or SCSI buffer is full.
412-
*/
413-
while ((ctx->scsi_bytes >= scsi_cmd_remaining_data_len(lun)) ||
414-
(ctx->scsi_bytes == sizeof(ctx->scsi_buf))) {
415-
tmp = scsi_write_data(lun, ctx->scsi_buf, ctx->scsi_bytes);
416-
__ASSERT(tmp <= ctx->scsi_bytes,
417-
"Processed more data than requested");
418-
if (tmp == 0) {
419-
LOG_WRN("SCSI handler didn't process %d bytes",
420-
ctx->scsi_bytes);
421-
ctx->scsi_bytes = 0;
422-
} else {
423-
LOG_DBG("SCSI processed %d out of %d bytes",
424-
tmp, ctx->scsi_bytes);
425-
}
426-
427-
ctx->csw.dCSWDataResidue -= tmp;
428-
if (scsi_cmd_remaining_data_len(lun) == 0) {
429-
/* Abandon any leftover data */
430-
ctx->scsi_bytes = 0;
431-
break;
432-
}
433-
434-
/* Move remaining data at the start of SCSI buffer. Note
435-
* that the copied length here is zero (and thus no copy
436-
* happens) when underlying sector size is equal to SCSI
437-
* buffer size.
438-
*/
439-
memmove(ctx->scsi_buf, &ctx->scsi_buf[tmp], ctx->scsi_bytes - tmp);
440-
ctx->scsi_bytes -= tmp;
438+
if ((len > 0) && (scsi_cmd_remaining_data_len(lun) > 0)) {
439+
/* Pass data to SCSI layer. */
440+
tmp = scsi_write_data(lun, buf, len);
441+
__ASSERT(tmp <= len, "Processed more data than requested");
442+
if (tmp == 0) {
443+
LOG_WRN("SCSI handler didn't process %d bytes", len);
444+
} else {
445+
LOG_DBG("SCSI processed %d out of %d bytes", tmp, len);
441446
}
447+
448+
ctx->csw.dCSWDataResidue -= tmp;
442449
}
443450

444451
if ((ctx->transferred_data >= ctx->cbw.dCBWDataTransferLength) ||
@@ -514,7 +521,7 @@ static void msc_handle_bulk_in(struct msc_bot_ctx *ctx,
514521
struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN];
515522

516523
ctx->transferred_data += len;
517-
if (ctx->scsi_bytes == 0) {
524+
if (msc_next_transfer_length(ctx->class_node) == 0) {
518525
if (ctx->csw.dCSWDataResidue > 0) {
519526
/* Case (5) Hi > Di
520527
* While we may have sent short packet, device
@@ -623,9 +630,11 @@ static void usbd_msc_thread(void *arg1, void *arg2, void *arg3)
623630

624631
switch (ctx->state) {
625632
case MSC_BBB_EXPECT_CBW:
633+
msc_queue_bulk_out_ep(evt.c_data, false);
634+
break;
626635
case MSC_BBB_PROCESS_WRITE:
627636
/* Ensure we can accept next OUT packet */
628-
msc_queue_bulk_out_ep(evt.c_data);
637+
msc_queue_bulk_out_ep(evt.c_data, true);
629638
break;
630639
default:
631640
break;
@@ -645,7 +654,7 @@ static void usbd_msc_thread(void *arg1, void *arg2, void *arg3)
645654
if (ctx->state == MSC_BBB_PROCESS_READ) {
646655
msc_process_read(ctx);
647656
} else if (ctx->state == MSC_BBB_PROCESS_WRITE) {
648-
msc_queue_bulk_out_ep(evt.c_data);
657+
msc_queue_bulk_out_ep(evt.c_data, true);
649658
} else if (ctx->state == MSC_BBB_SEND_CSW) {
650659
msc_send_csw(ctx);
651660
}
@@ -864,14 +873,17 @@ struct usbd_class_api msc_bot_api = {
864873
.init = msc_bot_init,
865874
};
866875

867-
#define DEFINE_MSC_BOT_CLASS_DATA(x, _) \
868-
static struct msc_bot_ctx msc_bot_ctx_##x = { \
869-
.desc = &msc_bot_desc_##x, \
870-
.fs_desc = msc_bot_fs_desc_##x, \
871-
.hs_desc = msc_bot_hs_desc_##x, \
872-
}; \
873-
\
874-
USBD_DEFINE_CLASS(msc_##x, &msc_bot_api, &msc_bot_ctx_##x, \
876+
#define DEFINE_MSC_BOT_CLASS_DATA(x, _) \
877+
UDC_STATIC_BUF_DEFINE(scsi_buf_##x, CONFIG_USBD_MSC_SCSI_BUFFER_SIZE); \
878+
\
879+
static struct msc_bot_ctx msc_bot_ctx_##x = { \
880+
.desc = &msc_bot_desc_##x, \
881+
.fs_desc = msc_bot_fs_desc_##x, \
882+
.hs_desc = msc_bot_hs_desc_##x, \
883+
.scsi_buf = scsi_buf_##x \
884+
}; \
885+
\
886+
USBD_DEFINE_CLASS(msc_##x, &msc_bot_api, &msc_bot_ctx_##x, \
875887
&msc_bot_vregs);
876888

877889
LISTIFY(MSC_NUM_INSTANCES, DEFINE_MSC_BOT_DESCRIPTOR, ())

subsys/usb/device_next/class/usbd_msc_scsi.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <zephyr/kernel.h>
88
#include <zephyr/storage/disk_access.h>
99
#include <zephyr/sys/byteorder.h>
10+
#include <zephyr/usb/usbd.h>
1011

1112
#include "usbd_msc_scsi.h"
1213

@@ -339,6 +340,24 @@ static int update_disk_info(struct scsi_ctx *const ctx)
339340
status = -EIO;
340341
}
341342

343+
if (!ctx->sector_size) {
344+
status = -EIO;
345+
} else if ((ctx->sector_size % USBD_MAX_BULK_MPS) &&
346+
(USBD_MAX_BULK_MPS % ctx->sector_size)) {
347+
/* Zephyr MSC class implementation initially allowed any sector
348+
* size, however doing so requires bouncing which significantly
349+
* impedes throughput. To enable zero-copy and scheduling larger
350+
* transfers, the implementation is now restricted to work only
351+
* with power of two disk sizes.
352+
*
353+
* USB bulk wMaxPacketSize is 64 (Full-Speed), 512 (High-Speed)
354+
* or 1024 (Super-Speed) and most common disk sector sizes are
355+
* either 512 or 4096. Therefore the power of two limitation
356+
* shouldn't have effect on any actual application.
357+
*/
358+
status = -EIO;
359+
}
360+
342361
if (ctx->sector_size > CONFIG_USBD_MSC_SCSI_BUFFER_SIZE) {
343362
status = -ENOMEM;
344363
}
@@ -707,12 +726,11 @@ validate_transfer_length(struct scsi_ctx *ctx, uint32_t lba, uint16_t length)
707726
return 0;
708727
}
709728

710-
static size_t fill_read_10(struct scsi_ctx *ctx,
711-
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
729+
static size_t fill_read_10(struct scsi_ctx *ctx, uint8_t *buf, size_t length)
712730
{
713731
uint32_t sectors;
714732

715-
sectors = MIN(CONFIG_USBD_MSC_SCSI_BUFFER_SIZE, ctx->remaining_data) / ctx->sector_size;
733+
sectors = MIN(length, ctx->remaining_data) / ctx->sector_size;
716734
if (disk_access_read(ctx->disk, buf, ctx->lba, sectors) != 0) {
717735
/* Terminate transfer */
718736
sectors = 0;
@@ -897,15 +915,14 @@ size_t scsi_cmd_remaining_data_len(struct scsi_ctx *ctx)
897915
return ctx->remaining_data;
898916
}
899917

900-
size_t scsi_read_data(struct scsi_ctx *ctx,
901-
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE])
918+
size_t scsi_read_data(struct scsi_ctx *ctx, uint8_t *buf, size_t length)
902919
{
903920
size_t retrieved = 0;
904921

905922
__ASSERT_NO_MSG(ctx->cmd_is_data_read);
906923

907924
if ((ctx->remaining_data > 0) && ctx->read_cb) {
908-
retrieved = ctx->read_cb(ctx, buf);
925+
retrieved = ctx->read_cb(ctx, buf, length);
909926
}
910927
ctx->remaining_data -= retrieved;
911928
if (retrieved == 0) {

subsys/usb/device_next/class/usbd_msc_scsi.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ struct scsi_ctx {
6666
const char *vendor;
6767
const char *product;
6868
const char *revision;
69-
size_t (*read_cb)(struct scsi_ctx *ctx,
70-
uint8_t buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE]);
69+
size_t (*read_cb)(struct scsi_ctx *ctx, uint8_t *buf, size_t length);
7170
size_t (*write_cb)(struct scsi_ctx *ctx, const uint8_t *buf, size_t length);
7271
size_t remaining_data;
7372
uint32_t lba;
@@ -92,8 +91,7 @@ size_t scsi_cmd(struct scsi_ctx *ctx, const uint8_t *cb, int len,
9291
bool scsi_cmd_is_data_read(struct scsi_ctx *ctx);
9392
bool scsi_cmd_is_data_write(struct scsi_ctx *ctx);
9493
size_t scsi_cmd_remaining_data_len(struct scsi_ctx *ctx);
95-
size_t scsi_read_data(struct scsi_ctx *ctx,
96-
uint8_t data_in_buf[static CONFIG_USBD_MSC_SCSI_BUFFER_SIZE]);
94+
size_t scsi_read_data(struct scsi_ctx *ctx, uint8_t *data_in_buf, size_t length);
9795
size_t scsi_write_data(struct scsi_ctx *ctx, const uint8_t *buf, size_t length);
9896

9997
enum scsi_status_code scsi_cmd_get_status(struct scsi_ctx *ctx);

0 commit comments

Comments
 (0)