diff --git a/README.rst b/README.rst index d366aa3..8355a37 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ Dependencies ============= This driver depends on: -* `Adafruit CircuitPython `_ +* `Adafruit CircuitPython 9.0.0 and later `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading @@ -37,18 +37,8 @@ or individual libraries can be installed using `circup `_. - -.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the -image from the assets folder in the PCB's GitHub repo. - -`Purchase one from the Adafruit shop `_ - Installing from PyPI ===================== -.. note:: This library is not available on PyPI yet. Install documentation is included - as a standard element. Stay tuned for PyPI availability! - -.. todo:: Remove the above note if PyPI version is/will be available at time of release. On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. @@ -99,8 +89,51 @@ Or the following command to update an existing version: Usage Example ============= -.. todo:: Add a quick, simple example. It and other examples should live in the -examples folder and be included in docs/examples.rst. +Print basic information about a device and its first (and usually only) configuration. + +.. code-block:: python + + import usb.core + import os + import storage + import time + + from adafruit_usb_host_descriptors import * + + DIR_IN = 0x80 + + while True: + print("searching for devices") + for device in usb.core.find(find_all=True): + print("pid", hex(device.idProduct)) + print("vid", hex(device.idVendor)) + print("man", device.manufacturer) + print("product", device.product) + print("serial", device.serial_number) + print("config[0]:") + config_descriptor = get_configuration_descriptor(device, 0) + + i = 0 + while i < len(config_descriptor): + descriptor_len = config_descriptor[i] + descriptor_type = config_descriptor[i + 1] + if descriptor_type == DESC_CONFIGURATION: + config_value = config_descriptor[i + 5] + print(f" value {config_value:d}") + elif descriptor_type == DESC_INTERFACE: + interface_number = config_descriptor[i + 2] + interface_class = config_descriptor[i + 5] + interface_subclass = config_descriptor[i + 6] + print(f" interface[{interface_number:d}] class {interface_class:02x} subclass {interface_subclass:02x}") + elif descriptor_type == DESC_ENDPOINT: + endpoint_address = config_descriptor[i + 2] + if endpoint_address & DIR_IN: + print(f" IN {endpoint_address:02x}") + else: + print(f" OUT {endpoint_address:02x}") + i += descriptor_len + print() + time.sleep(5) Documentation ============= diff --git a/adafruit_usb_host_mass_storage.py b/adafruit_usb_host_mass_storage.py index 6c0ea8d..762e515 100644 --- a/adafruit_usb_host_mass_storage.py +++ b/adafruit_usb_host_mass_storage.py @@ -14,12 +14,15 @@ import struct import time +from typing import Optional import usb.core from micropython import const -from adafruit_usb_host_descriptors import * +import adafruit_usb_host_descriptors __version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_USB_Host_Mass_Storage.git" +__repo__ = ( + "https://github.com/adafruit/Adafruit_CircuitPython_USB_Host_Mass_Storage.git" +) # USB defines _DIR_OUT = const(0x00) @@ -32,27 +35,39 @@ _MSC_REQ_GET_GET_MAX_LUN = const(254) # SCSI commands -_SCSI_CMD_TEST_UNIT_READY = const(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 = const(0x12) +_SCSI_CMD_TEST_UNIT_READY = const(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 = const(0x12) """The SCSI Inquiry command is used to obtain basic information from a target device.""" -_SCSI_CMD_READ_CAPACITY_10 = const(0x25) -"""The SCSI Read Capacity command is used to obtain data capacity information from a target device.""" -_SCSI_CMD_REQUEST_SENSE = const(0x03) -"""The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is used to obtain sense data -- status/error information -- from a target device.""" -_SCSI_CMD_READ_10 = const(0x28) -"""The READ (10) command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer.""" -_SCSI_CMD_WRITE_10 = const(0x2A) -"""The WRITE (10) command requests that the device server transfer the specified logical block(s) from the data-out buffer and write them.""" +_SCSI_CMD_READ_CAPACITY_10 = const(0x25) +"""The SCSI Read Capacity command is used to obtain data capacity information from a target +device.""" +_SCSI_CMD_REQUEST_SENSE = const(0x03) +"""The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is +used to obtain sense data -- status/error information -- from a target device.""" +_SCSI_CMD_READ_10 = const(0x28) +"""The READ (10) command requests that the device server read the specified logical block(s) and +transfer them to the data-in buffer.""" +_SCSI_CMD_WRITE_10 = const(0x2A) +"""The WRITE (10) command requests that the device server transfer the specified logical block(s) +from the data-out buffer and write them.""" + class USBMassStorage: + """CircuitPython BlockDevice backed by a USB mass storage device (aka thumb drive).""" + def __init__(self, device: usb.core.Device, lun=0): - config_descriptor = get_configuration_descriptor(device, 0) + config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor( + device, 0 + ) self.in_ep = 0 self.out_ep = 0 self.sector_count = None + self.block_size = None # Look over each descriptor for mass storage interface and then the two # endpoints. in_msc_interface = False @@ -62,16 +77,19 @@ def __init__(self, device: usb.core.Device, lun=0): while i < len(config_descriptor): descriptor_len = config_descriptor[i] descriptor_type = config_descriptor[i + 1] - if descriptor_type == DESC_CONFIGURATION: + if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION: config_value = config_descriptor[i + 5] - elif descriptor_type == DESC_INTERFACE: + elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE: interface_number = config_descriptor[i + 2] interface_class = config_descriptor[i + 5] interface_subclass = config_descriptor[i + 6] in_msc_interface = interface_class == 8 and interface_subclass == 6 if in_msc_interface: msc_interface = interface_number - elif descriptor_type == DESC_ENDPOINT and in_msc_interface: + elif ( + descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT + and in_msc_interface + ): endpoint_address = config_descriptor[i + 2] if endpoint_address & _DIR_IN: self.in_ep = endpoint_address @@ -89,7 +107,13 @@ def __init__(self, device: usb.core.Device, lun=0): # Get the max lun. max_lun = bytearray(1) try: - device.ctrl_transfer(_REQ_RCPT_INTERFACE | _REQ_TYPE_CLASS | _DIR_IN, _MSC_REQ_GET_GET_MAX_LUN, 0, msc_interface, max_lun) + device.ctrl_transfer( + _REQ_RCPT_INTERFACE | _REQ_TYPE_CLASS | _DIR_IN, + _MSC_REQ_GET_GET_MAX_LUN, + 0, + msc_interface, + max_lun, + ) max_lun = max_lun[0] + 1 except usb.core.USBError: # Stall means 0. @@ -108,8 +132,9 @@ def __init__(self, device: usb.core.Device, lun=0): self._wait_for_ready() def _scsi_command(self, direction, command, data) -> None: + """Do a SCSI command over USB. Reads or writes to data depending on direction.""" struct.pack_into(" None: self.device.read(self.in_ep, self.csw) def _wait_for_ready(self, tries=100): + """Waits for the device to be ready.""" status = 12 self.csw[status] = 1 test_ready = bytearray(6) @@ -145,6 +171,7 @@ def _wait_for_ready(self, tries=100): raise RuntimeError("Out of tries") def _inquire(self) -> None: + """Run inquiry command""" response = bytearray(36) command = bytearray(6) command[0] = _SCSI_CMD_INQUIRY @@ -152,6 +179,7 @@ def _inquire(self) -> None: self._scsi_command(_DIR_IN, command, response) def _read_capacity(self) -> None: + """Read the device's capacity and store it in the object""" command = bytearray(10) command[0] = _SCSI_CMD_READ_CAPACITY_10 response = bytearray(8) @@ -159,22 +187,46 @@ def _read_capacity(self) -> None: self._scsi_command(_DIR_IN, command, response) self.sector_count, self.block_size = struct.unpack(">II", response) - self.sector_count += 1 # Response has the last valid number. Count is one greater. + self.sector_count += ( + 1 # Response has the last valid number. Count is one greater. + ) def readblocks(self, block_num: int, buf: bytearray) -> None: + """Read data from block_num into buf""" command = bytearray(10) - struct.pack_into(">BBIxH", command, 0, _SCSI_CMD_READ_10, self.lun, block_num, len(buf) // 512) + struct.pack_into( + ">BBIxH", + command, + 0, + _SCSI_CMD_READ_10, + self.lun, + block_num, + len(buf) // 512, + ) self._scsi_command(_DIR_IN, command, buf) def writeblocks(self, block_num: int, buf: bytearray) -> None: + """Write data to block_num from buf""" command = bytearray(10) - struct.pack_into(">BBIxH", command, 0, _SCSI_CMD_WRITE_10, self.lun, block_num, len(buf) // 512) + struct.pack_into( + ">BBIxH", + command, + 0, + _SCSI_CMD_WRITE_10, + self.lun, + block_num, + len(buf) // 512, + ) self._scsi_command(_DIR_OUT, command, buf) def ioctl(self, operation: int, arg: Optional[int] = None) -> Optional[int]: + """Perform an IOCTL operation""" + # This is a standard interface so we need to take arg even though we ignore it. + # pylint: disable=unused-argument if operation == _MP_BLOCKDEV_IOCTL_BLOCK_COUNT: if not self.sector_count: self._read_capacity() return self.sector_count + return None diff --git a/docs/examples.rst b/docs/examples.rst index 2060f5c..d5cfc11 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,7 +1,7 @@ Simple test ------------ -Ensure your device works with this simple test. +Mount a USB mass storage device to ``/usb_device`` in CircuitPython. .. literalinclude:: ../examples/usb_host_mass_storage_simpletest.py :caption: examples/usb_host_mass_storage_simpletest.py diff --git a/docs/index.rst b/docs/index.rst index 41cc9c8..ae879b1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,14 +24,10 @@ Table of Contents .. toctree:: :caption: Tutorials -.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Related Products -.. todo:: Add any product links here. If there are none, then simply delete this todo and leave - the toctree above for use later. + Adafruit Feather RP2040 USB Host .. toctree:: :caption: Other Links diff --git a/examples/usb_host_mass_storage_simpletest.py b/examples/usb_host_mass_storage_simpletest.py index f46d6c0..9b22546 100644 --- a/examples/usb_host_mass_storage_simpletest.py +++ b/examples/usb_host_mass_storage_simpletest.py @@ -3,10 +3,10 @@ # # SPDX-License-Identifier: Unlicense -import usb.core +import time import os import storage -import time +import usb.core import adafruit_usb_host_mass_storage diff --git a/requirements.txt b/requirements.txt index 3b4e055..b14ab39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ # SPDX-License-Identifier: MIT Adafruit-Blinka +adafruit-circuitpython-usb-host-descriptors