Skip to content

Commit eb47fa0

Browse files
committed
usbd: Bugfixes around data transfer, support using an AbstractBlockDev-based device
1 parent e8bd164 commit eb47fa0

File tree

1 file changed

+81
-26
lines changed

1 file changed

+81
-26
lines changed

micropython/usbd/msc.py

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import micropython
1111
import ustruct
1212
from machine import Timer
13+
import gc
1314

1415
_INTERFACE_CLASS_MSC = const(0x08)
1516
_INTERFACE_SUBCLASS_SCSI = const(0x06)
@@ -317,25 +318,21 @@ def handle_cbw(self):
317318
self.log(f"Error: {exc}")
318319
self.prepare_for_csw(status=exc.status)
319320
return micropython.schedule(self.send_csw, None)
320-
return self.send_csw()
321321

322322
if response is None:
323323
self.log("None response")
324324
self.prepare_for_csw()
325325
return micropython.schedule(self.send_csw, None)
326-
return self.send_csw()
327326

328327
if len(response) > self.cbw.dCBWDataTransferLength:
329328
self.log("Wrong size")
330329
self.prepare_for_csw(status=CSW.STATUS_FAILED)
331330
return micropython.schedule(self.send_csw, None)
332-
return self.send_csw()
333331

334332
if len(response) == 0:
335333
self.log("Empty response")
336334
self.prepare_for_csw()
337335
return micropython.schedule(self.send_csw, None)
338-
return self.send_csw()
339336

340337
try:
341338
self.data = bytearray(response)
@@ -355,28 +352,40 @@ def proc_transfer_data(self, args):
355352
"""Actual handler for transferring non-CSW data"""
356353
(ep_addr, result, xferred_bytes) = args
357354
self.log("proc_transfer_data")
355+
self.transferred_length += xferred_bytes
358356

359357
if self.stage != type(self).MSC_STAGE_DATA:
360358
self.log("Wrong stage")
361359
return False
362360

363-
self.data = self.data[xferred_bytes:]
364-
self.transferred_length += xferred_bytes
365-
if not self.data:
366-
self.log("We're done")
367-
self.prepare_for_csw()
368-
return micropython.schedule(self.send_csw, None)
369-
return self.send_csw()
370-
371-
residue = self.cbw.dCBWDataTransferLength - len(self.data)
372-
if residue:
373-
self.csw.dCSWDataResidue = len(self.data)
374-
self.data.extend("\0" * residue)
361+
if len(self.data) > xferred_bytes:
362+
self.data = self.data[xferred_bytes:]
363+
else:
364+
self.data = bytearray()
365+
366+
if not self.data and self.storage_device.long_operation:
367+
self.data = self.storage_device.long_operation["operation"]()
368+
369+
# The above call will have cleared this if it was the last bit of data to send
370+
if not self.storage_device.long_operation:
371+
# We don't have more data to fetch...
372+
if not self.data:
373+
# We've already sent our final actual data packet
374+
self.log("We're done")
375+
self.prepare_for_csw()
376+
return micropython.schedule(self.send_csw, None)
377+
378+
# This is the last data we're sending, pad it out
379+
residue = self.cbw.dCBWDataTransferLength - (
380+
self.transferred_length + len(self.data)
381+
)
382+
if residue:
383+
self.log(f"Adding {residue} bytes of padding for residue")
384+
self.csw.dCSWDataResidue = residue
385+
self.data.extend("\0" * residue)
375386

376387
self.log(f"Preparing to submit data transfer, {len(self.data)} bytes")
377-
self.submit_xfer(
378-
ep_addr, self.data[: self.cbw.dCBWDataTransferLength], self.transfer_data
379-
)
388+
self.submit_xfer(ep_addr, self.data, self.transfer_data)
380389

381390
def validate_cbw(self) -> bool:
382391
"""Perform Valid and Meaningful checks on a CBW"""
@@ -462,7 +471,8 @@ class StorageDevice:
462471
Properties:
463472
filesystem -- a bytes-like thing representing the data this device is handling. If set to None, then the
464473
object will behave as if there is no medium inserted. This can be changed at runtime.
465-
block_size -- what size the blocks are for SCSI commands. This should probably be left as-is, at 512.
474+
block_size -- what size the blocks are for SCSI commands. This should probably be left as-is, at 512. If
475+
the device provides its own block size, that will be used instead
466476
"""
467477

468478
class StorageError(OSError):
@@ -483,6 +493,7 @@ def __init__(self, filesystem):
483493
self.block_size = 512
484494
self.sense = None
485495
self.additional_sense_code = None
496+
self.long_operation = {}
486497

487498
# A dict of SCSI commands and their handlers; the key is the opcode for the command
488499
self.scsi_commands = {
@@ -529,6 +540,7 @@ def validate_cmd(self, cmd):
529540
if self.scsi_commands[cmd[0]]["name"] != "REQUEST_SENSE":
530541
self.sense = type(self).NO_SENSE
531542

543+
# Windows seems to possibly send oversized CBDs by these rules in some circumstances?
532544
return True
533545

534546
# 0x00 to 0x1F should have 6-byte CBDs
@@ -550,7 +562,8 @@ def handle_cmd(self, cmd):
550562
return self.scsi_commands[cmd[0]]["handler"](cmd)
551563
except Exception as exc:
552564
raise StorageDevice.StorageError(
553-
f"Error handling command: {str(exc)}", CSW.STATUS_FAILED
565+
f"Error handling command {self.scsi_commands[cmd[0]]['name']}: {str(exc)}",
566+
CSW.STATUS_FAILED,
554567
)
555568

556569
# Below here are the SCSI command handlers
@@ -587,18 +600,30 @@ def handle_read_capacity_10(self, cmd):
587600
if self.filesystem is None:
588601
self.sense = type(self).MEDIUM_NOT_PRESENT
589602
raise StorageDevice.StorageError("No filesystem", status=CSW.STATUS_FAILED)
603+
604+
# Do we have an AbstractBlockDev?
605+
if getattr(self.filesystem, "ioctl", False):
606+
max_lba = self.filesystem.ioctl(4, None) - 1
607+
block_size = self.filesystem.ioctl(5, None) or 512
590608
else:
591609
max_lba = int(len(bytes(self.filesystem)) / self.block_size) - 1
610+
block_size = self.block_size
592611

593-
return ustruct.pack(">LL", max_lba, self.block_size)
612+
return ustruct.pack(">LL", max_lba, block_size)
594613

595614
def handle_read_format_capacity(self, cmd):
596615
block_num = 0
597616
list_length = 8
598617
descriptor_type = 3 # 3 = no media present
618+
block_size = self.block_size
599619
if self.filesystem is not None:
600620
descriptor_type = 2 # 2 = formatted media
601-
block_num = int(len(bytes(self.filesystem)) / self.block_size)
621+
# Do we have an AbstractBlockDev?
622+
if getattr(self.filesystem, "ioctl", False):
623+
block_num = self.filesystem.ioctl(4, None)
624+
block_size = self.filesystem.ioctl(5, None) or 512
625+
else:
626+
block_num = int(len(bytes(self.filesystem)) / self.block_size)
602627

603628
return ustruct.pack(
604629
">BBBBLBBH",
@@ -609,11 +634,41 @@ def handle_read_format_capacity(self, cmd):
609634
block_num,
610635
descriptor_type,
611636
0x00, # Reserved
612-
self.block_size,
637+
block_size,
613638
)
614639

615-
def handle_read10(self, cmd):
616-
(read10, flags, lba, group, length, control) = ustruct.unpack(">BBLBHB", cmd)
640+
def handle_read10(self, cmd=None):
641+
if cmd is None:
642+
if not self.long_operation:
643+
raise StorageDevice.StorageError(
644+
"handle_read10 called with no cmd, but we are not in an existing command"
645+
)
646+
647+
length = self.long_operation["remaining_length"]
648+
lba = self.long_operation["current_lba"]
649+
else:
650+
(read10, flags, lba, group, length, control) = ustruct.unpack(
651+
">BBLBHB", cmd
652+
)
653+
654+
# Do we have an AbstractBlockDev?
655+
if getattr(self.filesystem, "readblocks", False):
656+
gc.collect()
657+
# Will we be able to comfortably fit this in RAM?
658+
block_size = self.filesystem.ioctl(5, None) or 512
659+
max_size = int((gc.mem_free() / block_size) / 10) or 1
660+
if length > max_size:
661+
self.long_operation["remaining_length"] = length - max_size
662+
length = max_size
663+
self.long_operation["current_lba"] = lba + max_size
664+
self.long_operation["operation"] = self.handle_read10
665+
else:
666+
self.long_operation = {}
667+
668+
read_data = bytearray(length * block_size)
669+
self.filesystem.readblocks(lba, read_data)
670+
return read_data
671+
617672
return self.filesystem[
618673
lba * self.block_size : lba * self.block_size + length * self.block_size
619674
]

0 commit comments

Comments
 (0)