From d5e62b2d548b3ec37c70d7b8e1909e1331f695ea Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sat, 1 Aug 2026 12:41:31 -0700 Subject: [PATCH 1/9] Start working on RP2xxx USB test --- drivers/usb/source/USBDevice.cpp | 11 +- .../usb/tests/TESTS/host_tests/pyusb_basic.py | 2560 +++++++++-------- .../TESTS/usb_device/basic/USBTester.cpp | 20 +- .../TARGET_MCU_RP2/USBPhyHw.h | 1 + .../TARGET_MCU_RP2/USBPhy_RP2.cpp | 11 + targets/targets.json5 | 1 - 6 files changed, 1316 insertions(+), 1288 deletions(-) diff --git a/drivers/usb/source/USBDevice.cpp b/drivers/usb/source/USBDevice.cpp index 07493e7985..e6dd930caf 100644 --- a/drivers/usb/source/USBDevice.cpp +++ b/drivers/usb/source/USBDevice.cpp @@ -1434,12 +1434,13 @@ bool USBDevice::read_start(usb_ep_t endpoint, uint8_t *buffer, uint32_t max_size if (!EP_INDEXABLE(endpoint)) { #if MBED_TRAP_ERRORS_ENABLED - MBED_ERROR( + MBED_ERROR1( MBED_MAKE_ERROR( MBED_MODULE_DRIVER_USB, MBED_ERROR_CODE_INVALID_INDEX ), - "The endpoint is not indexable." + "The endpoint is not indexable.", + endpoint ); #else unlock(); @@ -1449,8 +1450,10 @@ bool USBDevice::read_start(usb_ep_t endpoint, uint8_t *buffer, uint32_t max_size endpoint_info_t *info = &_endpoint_info[EP_TO_INDEX(endpoint)]; if (!(info->flags & ENDPOINT_ENABLED)) { - // Assert that only valid endpoints are used when in the configured state - MBED_ASSERT(!configured()); + // Only valid endpoints may be used when in the configured state + if(configured()) { + MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_USB, MBED_ERROR_CODE_INVALID_OPERATION), "Endpoint read before being enabled!", endpoint); + } unlock(); return false; } diff --git a/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py b/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py index e3675fd55c..2dd2fbe7ec 100644 --- a/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py +++ b/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. """ -from __future__ import print_function - from mbed_host_tests import BaseHostTest from argparse import ArgumentParser import time @@ -48,7 +46,7 @@ # Use a default backend on other platforms. USB_BACKEND = None -def get_interface(dev, interface, alternate=0): +def get_interface(dev: usb.core.Device, interface, alternate=0): intf = None for active_if in dev.get_active_configuration(): if active_if.bInterfaceNumber == interface and active_if.bAlternateSetting == alternate: @@ -118,6 +116,10 @@ def get_interface(dev, interface, alternate=0): MSG_KEY_TEST_CASE_PASSED = 'pass' MSG_VALUE_DUMMY = '0' +USB_ERROR_FMT = str('Got {0!r} while testing endpoints ' + '{1.bEndpointAddress:#04x}({1.wMaxPacketSize:02}) and ' + '{2.bEndpointAddress:#04x}({2.wMaxPacketSize:02}) with ' + 'a random payload of {3} B.') def format_local_error_msg(fmt): """Return an error message formatted with the last traceback entry from this file. @@ -176,8 +178,8 @@ def _callback_control_basic_test(self, key, value, timestamp): serial_number, vendor_id, product_id = value.split(' ') self.test_usb_device( usb_dev_serial_number=serial_number, - test_fun=control_basic_test, - log=print, + test_fun=self.control_basic_test, + log=self.log, vendor_id=int(vendor_id), product_id=int(product_id) ) @@ -185,22 +187,22 @@ def _callback_control_basic_test(self, key, value, timestamp): def _callback_control_stall_test(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=control_stall_test, - log=print + test_fun=self.control_stall_test, + log=self.log ) def _callback_control_sizes_test(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=control_sizes_test, - log=print + test_fun=self.control_sizes_test, + log=self.log ) def _callback_control_stress_test(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=control_stress_test, - log=print + test_fun=self.control_stress_test, + log=self.log ) def _callback_device_reset_test(self, key, value, timestamp): @@ -238,43 +240,43 @@ def _callback_repeated_construction_destruction_test(self, key, value, timestamp def _callback_ep_test_data_correctness(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_data_correctness, - log=print + test_fun=self.ep_test_data_correctness, + log=self.log ) def _callback_ep_test_halt(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_halt, - log=print + test_fun=self.ep_test_halt, + log=self.log ) def _callback_ep_test_parallel_transfers(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_parallel_transfers, - log=print + test_fun=self.ep_test_parallel_transfers, + log=self.log ) def _callback_ep_test_parallel_transfers_ctrl(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_parallel_transfers_ctrl, - log=print + test_fun=self.ep_test_parallel_transfers_ctrl, + log=self.log ) def _callback_ep_test_abort(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_abort, - log=print + test_fun=self.ep_test_abort, + log=self.log ) def _callback_ep_test_data_toggle(self, key, value, timestamp): self.test_usb_device( usb_dev_serial_number=value, - test_fun=ep_test_data_toggle, - log=print + test_fun=self.ep_test_data_toggle, + log=self.log ) def _callback_reset_support(self, key, value, timestamp): @@ -282,10 +284,10 @@ def _callback_reset_support(self, key, value, timestamp): self.log("Reset supported: %s" % status) self.send_kv("placeholder", status) - def find_device(self, serial_number): + def find_device(self, serial_number) -> usb.Device: # to make it more reliable, 20 retries in 2[s] for _ in range(20): - dev = usb.core.find(custom_match=TestMatch(serial_number), backend=USB_BACKEND) + dev = usb.core.find(custom_match=self.TestMatch(serial_number), backend=USB_BACKEND) if dev is not None: break time.sleep(0.1) @@ -312,16 +314,16 @@ def notify_error(self, msg): def setup(self): self.__result = False - self.device_reset_test = device_reset_test(log=print) + self.device_reset_test = self.device_reset_test(log=self.log) self.device_reset_test.send(None) - self.device_soft_reconnection_test = device_soft_reconnection_test(log=print) + self.device_soft_reconnection_test = self.device_soft_reconnection_test(log=self.log) self.device_soft_reconnection_test.send(None) - self.device_suspend_resume_test = device_suspend_resume_test(log=print) + self.device_suspend_resume_test = self.device_suspend_resume_test(log=self.log) self.device_suspend_resume_test.send(None) - self.repeated_construction_destruction_test = repeated_construction_destruction_test(log=print) + self.repeated_construction_destruction_test = self.repeated_construction_destruction_test(log=self.log) self.repeated_construction_destruction_test.send(None) self.register_callback('control_basic_test', self._callback_control_basic_test) @@ -349,1287 +351,1289 @@ def teardown(self): pass -class TestMatch(object): - - def __init__(self, serial): - self.serial = serial - - def __call__(self, dev): + class TestMatch(object): + + def __init__(self, serial): + self.serial = serial + + def __call__(self, dev): + try: + return dev.serial_number == self.serial + except ValueError: + return False + + + @staticmethod + def lineno(): + """Returns the current line number in our program.""" + return inspect.currentframe().f_back.f_lineno + + + @staticmethod + def raise_if_different(expected, actual, line, text=''): + """Raise a RuntimeError if actual is different than expected.""" + if expected != actual: + raise RuntimeError('[{}]:{}, {} Got {!r}, expected {!r}'.format(__file__, line, text, actual, expected)) + + + @staticmethod + def raise_unconditionally(line, text=''): + """Raise a RuntimeError unconditionally.""" + raise RuntimeError('[{}]:{}, {}'.format(__file__, line, text)) + + + def control_basic_test(self, dev: usb.core.Device, vendor_id, product_id, log): + + self.log(f"Testing with device (bus = {dev.bus}, address = {dev.address})") + + self.get_set_configuration_test(dev, log) + self.get_set_interface_test(dev, log) + self.get_status_test(dev, log) + self.set_clear_feature_test(dev, log) + self.get_descriptor_test(dev, vendor_id, product_id, log) + self.set_descriptor_test(dev, log) + + + def get_set_configuration_test(self, dev: usb.core.Device, log): + """ + Test device configuration/deconfiguration + + Given an initialized USB (HOST <---> DUT connection established) + When device configuration is checked just after initialization + Then get_configuration returns 1 (default configuration is set) + When device is deconfigured + Then get_configuration returns 0 (no configuration is set) + When each from supported configurations is set + Then the configuration is set correctly + """ + + self.log("<<< get_set_configuration_test >>>") + # check if dafault(1) configuration set try: - return dev.serial_number == self.serial - except ValueError: - return False - - -def lineno(): - """Returns the current line number in our program.""" - return inspect.currentframe().f_back.f_lineno - - -def raise_if_different(expected, actual, line, text=''): - """Raise a RuntimeError if actual is different than expected.""" - if expected != actual: - raise RuntimeError('[{}]:{}, {} Got {!r}, expected {!r}'.format(__file__, line, text, actual, expected)) - - -def raise_unconditionally(line, text=''): - """Raise a RuntimeError unconditionally.""" - raise RuntimeError('[{}]:{}, {}'.format(__file__, line, text)) - - -def control_basic_test(dev, vendor_id, product_id, log): - get_set_configuration_test(dev, log) - get_set_interface_test(dev, log) - get_status_test(dev, log) - set_clear_feature_test(dev, log) - get_descriptor_test(dev, vendor_id, product_id, log) - set_descriptor_test(dev, log) - - -def get_set_configuration_test(dev, log): - """ - Test device configuration/deconfiguration - - Given an initialized USB (HOST <---> DUT connection established) - When device configuration is checked just after initialization - Then get_configuration returns 1 (default configuration is set) - When device is deconfigured - Then get_configuration returns 0 (no configuration is set) - When each from supported configurations is set - Then the configuration is set correctly - """ - - print("<<< get_set_configuration_test >>>") - # check if dafault(1) configuration set - try: - ret = usb.control.get_configuration(dev) - raise_if_different(1, ret, lineno(), 'Invalid configuration.') - except usb.core.USBError as error: - raise_unconditionally(lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) - - cfg = dev.get_active_configuration() - for intf in cfg: - usb.util.release_interface(dev, intf) - - # deconfigure the device - try: - ret = dev.set_configuration(0) - except usb.core.USBError as error: - raise_unconditionally(lineno(), 'set_configuration request (deconfigure) failed ({}).'.format(str(error).strip())) - - # check if deconfigured - try: - ret = usb.control.get_configuration(dev) - raise_if_different(0, ret, lineno(), 'Invalid configuration.') - print("device deconfigured - OK") - except usb.core.USBError as error: - raise_unconditionally(lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) - - # for every configuration - for cfg in dev: + ret = usb.control.get_configuration(dev) + self.raise_if_different(1, ret, self.lineno(), 'Invalid configuration.') + except usb.core.USBError as error: + self.raise_unconditionally(self.lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) + + cfg = dev.get_active_configuration() + for intf in cfg: + usb.util.release_interface(dev, intf) + + # deconfigure the device try: - # set configuration - ret = cfg.set() + ret = dev.set_configuration(0) except usb.core.USBError as error: - raise_unconditionally(lineno(), 'set_configuration request failed ({}).'.format(str(error).strip())) - - # check if configured + self.raise_unconditionally(self.lineno(), 'set_configuration request (deconfigure) failed ({}).'.format(str(error).strip())) + + # check if deconfigured try: ret = usb.control.get_configuration(dev) - raise_if_different(cfg.bConfigurationValue, ret, lineno(), 'Invalid configuration.') - print("configuration {} set - OK ".format(cfg.bConfigurationValue)) + self.raise_if_different(0, ret, self.lineno(), 'Invalid configuration.') + self.log("device deconfigured - OK") except usb.core.USBError as error: - raise_unconditionally(lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) - # test control data transfer after configuration set - control_data_test(dev, [64, 256], log) - print("") # new line - - -def get_set_interface_test(dev, log): - """ - Test device interface setting - - Given an initialized USB (HOST <---> DUT connection established) - When each altsetting from every supported configuration is set - Then the interface altsetting is set correctly - """ - - print("<<< get_set_interface_test >>>") - # for every configuration - for cfg in dev: - cfg.set() - # for every interface - for intf in cfg: - intf.set_altsetting() - altsett = usb.control.get_interface(dev, intf.bInterfaceNumber) - raise_if_different(intf.bAlternateSetting, altsett, lineno(), text='Wrong alternate setting for interface {}'.format(intf.bInterfaceNumber)) - print("cfg({}) inteface {}.{} set - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting)) - control_data_test(dev, [64, 256], log) - - release_interfaces(dev) - - restore_default_configuration(dev) - - # test control data transfer after default interface restoring - control_data_test(dev, [64, 256], log) - print("") # new line - - -def get_status_test(dev, log): - """ - Test device/interface/endpoint status - - Given an initialized USB (HOST <---> DUT connection established) - When device status is checked - Then status is within allowed values (see status bits description below) - When control endpoint status is checked - Then control endpoint status is 0 - When status of each interface from every supported configuration is checked - Then interface status is 0 - When status of each endpoint in every allowed device interface/configuration combination is checked - Then endpoint status is 0 (not halted) - """ - - print("<<< get_status_test >>>") - # check device status - ret = get_status(dev, CTRL_RECIPIENT_DEVICE) - # Status bits - # ret == 0b01 (D0)Self Powered - # ret == 0b10 (D1)Remote Wakeup - # (D2 - D15 reserved) Must be set to 0 - if(ret < 0 or ret > 3): - raise_unconditionally(lineno(), "GET_STATUS on DEVICE failed") - - # check endpoint 0 status - ret = get_status(dev, CTRL_RECIPIENT_ENDPOINT, 0) - # Status bits - # ret == 0b1 (D0)endpoint Halt - # (D1 - D15 reserved) Must be set to 0 - # endpoint 0 can't be halted ret == 0 - raise_if_different(0, ret, lineno(), "GET_STATUS on ENDPOINT 0 should return 0") - - # for every configuration - for cfg in dev: - cfg.set() - raise_if_different(cfg.bConfigurationValue, usb.control.get_configuration(dev), lineno(), "Configuration {} set failed".format(cfg.bConfigurationValue)) - - for intf in cfg: - intf.set_altsetting() - # check interface status - ret = get_status(dev, CTRL_RECIPIENT_INTERFACE, intf.bInterfaceNumber) - # Status bits - # ret == 0b0 - # (D0 - D15 reserved) Must be set to 0 - if(ret != 0): - raise_unconditionally(lineno(), "GET_STATUS on INTERFACE ({},{}) failed".format(intf.bInterfaceNumber, intf.bAlternateSetting)) - print("cfg({}) interface {}.{} status - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting)) - - # on every ENDPOINT in this altsetting - for ep in intf: - ret = usb.control.get_status(dev, ep) + self.raise_unconditionally(self.lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) + + # for every configuration + for cfg in dev: + try: + # set configuration + ret = cfg.set() + except usb.core.USBError as error: + self.raise_unconditionally(self.lineno(), 'set_configuration request failed ({}).'.format(str(error).strip())) + + # check if configured + try: + ret = usb.control.get_configuration(dev) + self.raise_if_different(cfg.bConfigurationValue, ret, self.lineno(), 'Invalid configuration.') + self.log("configuration {} set - OK ".format(cfg.bConfigurationValue)) + except usb.core.USBError as error: + self.raise_unconditionally(self.lineno(), 'get_configuration request failed ({}).'.format(str(error).strip())) + # test control data transfer after configuration set + self.control_data_test(dev, [64, 256], log) + self.log("") # new line + + + def get_set_interface_test(self, dev: usb.core.Device, log): + """ + Test device interface setting + + Given an initialized USB (HOST <---> DUT connection established) + When each altsetting from every supported configuration is set + Then the interface altsetting is set correctly + """ + + self.log("<<< get_set_interface_test >>>") + # for every configuration + for cfg in dev: + cfg.set() + # for every interface + for intf in cfg: + intf.set_altsetting() + altsett = usb.control.get_interface(dev, intf.bInterfaceNumber) + self.raise_if_different(intf.bAlternateSetting, altsett, self.lineno(), text='Wrong alternate setting for interface {}'.format(intf.bInterfaceNumber)) + self.log("cfg({}) interface {}.{} set - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting)) + self.control_data_test(dev, [64, 256], log) + + self.release_interfaces(dev) + + self.restore_default_configuration(dev) + + # test control data transfer after default interface restoring + self.control_data_test(dev, [64, 256], log) + self.log("") # new line + + + def get_status_test(self, dev: usb.core.Device, log): + """ + Test device/interface/endpoint status + + Given an initialized USB (HOST <---> DUT connection established) + When device status is checked + Then status is within allowed values (see status bits description below) + When control endpoint status is checked + Then control endpoint status is 0 + When status of each interface from every supported configuration is checked + Then interface status is 0 + When status of each endpoint in every allowed device interface/configuration combination is checked + Then endpoint status is 0 (not halted) + """ + + self.log("<<< get_status_test >>>") + # check device status + ret = self.get_status(dev, CTRL_RECIPIENT_DEVICE) + # Status bits + # ret == 0b01 (D0)Self Powered + # ret == 0b10 (D1)Remote Wakeup + # (D2 - D15 reserved) Must be set to 0 + if(ret < 0 or ret > 3): + self.raise_unconditionally(self.lineno(), "GET_STATUS on DEVICE failed") + + # check endpoint 0 status + ret = self.get_status(dev, CTRL_RECIPIENT_ENDPOINT, 0) + # Status bits + # ret == 0b1 (D0)endpoint Halt + # (D1 - D15 reserved) Must be set to 0 + # endpoint 0 can't be halted ret == 0 + self.raise_if_different(0, ret, self.lineno(), "GET_STATUS on ENDPOINT 0 should return 0") + + # for every configuration + for cfg in dev: + cfg.set() + self.raise_if_different(cfg.bConfigurationValue, usb.control.get_configuration(dev), self.lineno(), "Configuration {} set failed".format(cfg.bConfigurationValue)) + + for intf in cfg: + intf.set_altsetting() + # check interface status + ret = self.get_status(dev, CTRL_RECIPIENT_INTERFACE, intf.bInterfaceNumber) # Status bits - # ret == 0b1 (D0)endpoint Halt - # (D1 - D15 reserved) Must be set to 0 - if(ret >= 1): - raise_unconditionally(lineno(), "GET_STATUS on ENDPOINT {} failed - endpoint halted".format(ep.bEndpointAddress)) - print("cfg({}) intf({}.{}) endpoint {} status - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) - - release_interfaces(dev) - restore_default_configuration(dev) - print("") # new line - - -def set_clear_feature_test(dev, log): - """ - Test set/clear feature on device/interface/endpoint - - Given an initialized USB (HOST <---> DUT connection established) - When for each endpoint in every allowed interface/configuration combination the feature is set and then cleared - Then selected feature is set/cleared accordingly - """ - - print("<<< set_clear_feature_test >>>") - # TODO: - # test set_feature on device (Remote wakeup feature not supported on DUT side) - # test set_feature on interface (not supported at all) - - # for every configuration - for cfg in dev: - cfg.set() - raise_if_different(cfg.bConfigurationValue, usb.control.get_configuration(dev), lineno(), "Configuration {} set failed".format(cfg.bConfigurationValue)) - - for intf in cfg: - intf.set_altsetting() - # on every ENDPOINT - for ep in intf: - # halt endpoint - try: - usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, ep) - except usb.core.USBError as err: - raise_unconditionally(lineno(), 'set_feature request (halt) failed for endpoint {} ({}).'.format(ep.bEndpointAddress, str(err).strip())) - - # check if endpoint was halted - try: - ret = usb.control.get_status(dev, ep) - except usb.core.USBError as err: - raise_unconditionally(lineno(), 'get_status request failed for endpoint {} ({}).'.format(ep.bEndpointAddress, str(err).strip())) - if(ret != 1): - raise_unconditionally(lineno(), "endpoint {} was not halted".format(ep.bEndpointAddress)) - print("cfg({}) intf({}.{}) ep {} halted - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) - - # Control OUT CLEAR_FEATURE on endpoint - unhalt - try: - usb.control.clear_feature(dev, FEATURE_ENDPOINT_HALT, ep) - except usb.core.USBError as err: - raise_unconditionally(lineno(), "clear_feature request (unhalt) failed for endpoint {} ({})".format(ep.bEndpointAddress, str(err).strip())) - - # check if endpoint was unhalted - ret = usb.control.get_status(dev, ep) + # ret == 0b0 + # (D0 - D15 reserved) Must be set to 0 if(ret != 0): - raise_unconditionally(lineno(), "endpoint {} was not unhalted".format(ep.bEndpointAddress)) - print("cfg({}) intf({}.{}) ep {} unhalted - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) - - release_interfaces(dev) - - restore_default_configuration(dev) - print("") # new line - - -def get_descriptor_test(dev, vendor_id, product_id, log): - """ - Test device/configuration/interface/endpoint descriptors - - Given an initialized USB (HOST <---> DUT connection established) - When device descriptor is read - Then the descriptor content is valid - When configuration descriptor is read - Then the descriptor content is valid - When interface descriptor is read - Then the error is thrown since it is not directly accessible - When endpoint descriptor is read - Then the error is thrown since it is not directly accessible - """ - - print("<<< get_descriptor_test >>>") - # device descriptor - try: - ret = get_descriptor(dev, (DESC_TYPE_DEVICE << 8) | (0 << 0), 0, DEVICE_DESC_SIZE) - dev_desc = dict(zip(device_descriptor_keys, device_descriptor_parser.unpack(ret))) - raise_if_different(DEVICE_DESC_SIZE, dev_desc['bLength'], lineno(), text='Wrong device descriptor size.') - raise_if_different(vendor_id, dev_desc['idVendor'], lineno(), text='Wrong vendor ID.') - raise_if_different(product_id, dev_desc['idProduct'], lineno(), text='Wrong product ID.') - except usb.core.USBError: - raise_unconditionally(lineno(), "Requesting device descriptor failed") - - # configuration descriptor - try: - ret = get_descriptor(dev, (DESC_TYPE_CONFIG << 8) | (0 << 0), 0, CONFIGURATION_DESC_SIZE) - conf_desc = dict(zip(configuration_descriptor_keys, configuration_descriptor_parser.unpack(ret))) - raise_if_different(CONFIGURATION_DESC_SIZE, conf_desc['bLength'], lineno(), text='Wrong configuration descriptor size.') - except usb.core.USBError: - raise_unconditionally(lineno(), "Requesting configuration descriptor failed") - - # interface descriptor - try: - ret = get_descriptor(dev, (DESC_TYPE_INTERFACE << 8) | (0 << 0), 0, INTERFACE_DESC_SIZE) - raise_unconditionally(lineno(), "Requesting interface descriptor should fail since it is not directly accessible.") - except usb.core.USBError: - log("interface descriptor is not directly accessible - OK") - - # endpoint descriptor - try: - ret = get_descriptor(dev, (DESC_TYPE_ENDPOINT << 8) | (0 << 0), 0, ENDPOINT_DESC_SIZE) - raise_unconditionally(lineno(), "Requesting endpoint descriptor should fail since it is not directly accessible.") - except usb.core.USBError: - log("endpoint descriptor is not directly accessible - OK") - print("") # new line - - -def set_descriptor_test(dev, log): - """ - Test descriptor setting - - Given an initialized USB (HOST <---> DUT connection established) - When device descriptor is to be set - Then error is thrown since descriptor setting command is not supported by Mbed - """ - - print("<<< set_descriptor_test >>>") - # SET_DESCRIPTOR is optional and not implemented in Mbed - # command should fail with no action on device side - - # Control OUT SET_DESCRIPTOR - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_STANDARD, - CTRL_RECIPIENT_DEVICE) - request = REQUEST_SET_DESCRIPTOR - value = (DESC_TYPE_DEVICE << 8) | (0 << 0) # Descriptor Type (H) and Descriptor Index (L) - index = 0 # 0 or Language ID for this request - data = bytearray(DEVICE_DESC_SIZE) # Descriptor data - try: - dev.ctrl_transfer(request_type, request, value, index, data) - raise_unconditionally(lineno(), "set_descriptor request should fail since it is not implemented") - except usb.core.USBError: - log("SET_DESCRIPTOR is unsupported - OK") - print("") # new line - - -def synch_frame_test(dev, log): - """ - Test sync frame request - - Given an initialized USB (HOST <---> DUT connection established) - When ... - Then ... - """ - - print("<<< synch_frame_test >>>") - # only for isochronous endpoints - request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, - CTRL_RECIPIENT_ENDPOINT) - request = REQUEST_SYNCH_FRAME - value = 0 # Always 0 for this request - index = 1 # Endpoint index - length = 2 # Always 2 for this request (size of return data) - try: - ret = dev.ctrl_transfer(request_type, request, value, index, length) - ret = ret[0] | (ret[1] << 8) - log("synch frame ret: %d" % (ret)) - except usb.core.USBError: - raise_unconditionally(lineno(), "SYNCH_FRAME request failed") - print("") # new line - - -def control_stall_test(dev, log): - """ - Test control endpoint stall on invalid request - - Given an initialized USB (HOST <---> DUT connection established) - When unsupported request to control endpoint is to be sent - Then the endpoint is stalled and error is thrown - """ - - print("<<< control_stall_test >>>") - # Control OUT stall - try: - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_UNSUPPORTED_REQUEST - value = 0 # Always 0 for this request - index = 0 # Communication interface - data = bytearray(64) # Dummy data - dev.ctrl_transfer(request_type, request, value, index, data, 5000) - raise_unconditionally(lineno(), "Invalid request not stalled") - except usb.core.USBError: - log("Invalid request stalled - OK") - - # Control request with no data stage (Device-to-host) - try: - request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_UNSUPPORTED_REQUEST - value = 0 # Always 0 for this request - index = 0 # Communication interface - length = 0 - dev.ctrl_transfer(request_type, request, value, index, length, 5000) - raise_unconditionally(lineno(), "Invalid request not stalled") - except usb.core.USBError: - log("Invalid request stalled - OK") - - # Control request with no data stage (Host-to-device) - try: - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_UNSUPPORTED_REQUEST - value = 0 # Always 0 for this request - index = 0 # Communication interface - length = 0 - dev.ctrl_transfer(request_type, request, value, index, length, 5000) - raise_unconditionally(lineno(), "Invalid request not stalled") - except usb.core.USBError: - log("Invalid request stalled - OK") - - # Control IN stall - try: - request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_UNSUPPORTED_REQUEST - value = 0 # Always 0 for this request - index = 0 # Communication interface - length = 255 - dev.ctrl_transfer(request_type, request, value, index, length, 5000) - raise_unconditionally(lineno(), "Invalid request not stalled") - except usb.core.USBError: - log("Invalid request stalled - OK") - - for i in (3, 4, 5): + self.raise_unconditionally(self.lineno(), "GET_STATUS on INTERFACE ({},{}) failed".format(intf.bInterfaceNumber, intf.bAlternateSetting)) + self.log("cfg({}) interface {}.{} status - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting)) + + # on every ENDPOINT in this altsetting + for ep in intf: + ret = usb.control.get_status(dev, ep) + # Status bits + # ret == 0b1 (D0)endpoint Halt + # (D1 - D15 reserved) Must be set to 0 + if(ret >= 1): + self.raise_unconditionally(self.lineno(), "GET_STATUS on ENDPOINT {} failed - endpoint halted".format(ep.bEndpointAddress)) + self.log("cfg({}) intf({}.{}) endpoint {} status - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) + + self.release_interfaces(dev) + self.restore_default_configuration(dev) + self.log("") # new line + + + def set_clear_feature_test(self, dev: usb.core.Device, log): + """ + Test set/clear feature on device/interface/endpoint + + Given an initialized USB (HOST <---> DUT connection established) + When for each endpoint in every allowed interface/configuration combination the feature is set and then cleared + Then selected feature is set/cleared accordingly + """ + + self.log("<<< set_clear_feature_test >>>") + # TODO: + # test set_feature on device (Remote wakeup feature not supported on DUT side) + # test set_feature on interface (not supported at all) + + # for every configuration + for cfg in dev: + cfg.set() + self.raise_if_different(cfg.bConfigurationValue, usb.control.get_configuration(dev), self.lineno(), "Configuration {} set failed".format(cfg.bConfigurationValue)) + + for intf in cfg: + intf.set_altsetting() + # on every ENDPOINT + for ep in intf: + # halt endpoint + try: + usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, ep) + except usb.core.USBError as err: + self.raise_unconditionally(self.lineno(), 'set_feature request (halt) failed for endpoint {} ({}).'.format(ep.bEndpointAddress, str(err).strip())) + + # check if endpoint was halted + try: + ret = usb.control.get_status(dev, ep) + except usb.core.USBError as err: + self.raise_unconditionally(self.lineno(), 'get_status request failed for endpoint {} ({}).'.format(ep.bEndpointAddress, str(err).strip())) + if(ret != 1): + self.raise_unconditionally(self.lineno(), "endpoint {} was not halted".format(ep.bEndpointAddress)) + self.log("cfg({}) intf({}.{}) ep {} halted - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) + + # Control OUT CLEAR_FEATURE on endpoint - unhalt + try: + usb.control.clear_feature(dev, FEATURE_ENDPOINT_HALT, ep) + except usb.core.USBError as err: + self.raise_unconditionally(self.lineno(), "clear_feature request (unhalt) failed for endpoint {} ({})".format(ep.bEndpointAddress, str(err).strip())) + + # check if endpoint was unhalted + ret = usb.control.get_status(dev, ep) + if(ret != 0): + self.raise_unconditionally(self.lineno(), "endpoint {} was not unhalted".format(ep.bEndpointAddress)) + self.log("cfg({}) intf({}.{}) ep {} unhalted - OK".format(cfg.bConfigurationValue, intf.bInterfaceNumber, intf.bAlternateSetting, ep.bEndpointAddress)) + + self.release_interfaces(dev) + + self.restore_default_configuration(dev) + self.log("") # new line + + + def get_descriptor_test(self, dev: usb.core.Device, vendor_id, product_id, log): + """ + Test device/configuration/interface/endpoint descriptors + + Given an initialized USB (HOST <---> DUT connection established) + When device descriptor is read + Then the descriptor content is valid + When configuration descriptor is read + Then the descriptor content is valid + When interface descriptor is read + Then the error is thrown since it is not directly accessible + When endpoint descriptor is read + Then the error is thrown since it is not directly accessible + """ + + self.log("<<< get_descriptor_test >>>") + # device descriptor try: - request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, - CTRL_RECIPIENT_DEVICE) - request = 0x6 # GET_DESCRIPTOR - value = (0x03 << 8) | (i << 0) # String descriptor index - index = 0 # Communication interface - length = 255 - resp = dev.ctrl_transfer(request_type, request, value, index, length, 5000) + ret = self.get_descriptor(dev, (DESC_TYPE_DEVICE << 8) | (0 << 0), 0, DEVICE_DESC_SIZE) + dev_desc = dict(zip(device_descriptor_keys, device_descriptor_parser.unpack(ret))) + self.raise_if_different(DEVICE_DESC_SIZE, dev_desc['bLength'], self.lineno(), text='Wrong device descriptor size.') + self.raise_if_different(vendor_id, dev_desc['idVendor'], self.lineno(), text='Wrong vendor ID.') + self.raise_if_different(product_id, dev_desc['idProduct'], self.lineno(), text='Wrong product ID.') except usb.core.USBError: - raise_unconditionally(lineno(), "Requesting string failed i: " + str(i)) - - for i in (6, 7): + self.raise_unconditionally(self.lineno(), "Requesting device descriptor failed") + + # configuration descriptor try: - request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, - CTRL_RECIPIENT_DEVICE) - request = 0x6 # GET_DESCRIPTOR - value = (0x03 << 8) | (i << 0) # String descriptor index - index = 0 # Communication interface - length = 255 - resp = dev.ctrl_transfer(request_type, request, value, index, length, 5000) - raise_unconditionally(lineno(), "Requesting string passed i: " + str(i)) + ret = self.get_descriptor(dev, (DESC_TYPE_CONFIG << 8) | (0 << 0), 0, CONFIGURATION_DESC_SIZE) + conf_desc = dict(zip(configuration_descriptor_keys, configuration_descriptor_parser.unpack(ret))) + self.raise_if_different(CONFIGURATION_DESC_SIZE, conf_desc['bLength'], self.lineno(), text='Wrong configuration descriptor size.') except usb.core.USBError: - log("Requesting string %s failed - OK" % i) - print("") # new line - - -def control_sizes_test(dev, log): - """ - Test various data sizes in control transfer - - Given an initialized USB (HOST <---> DUT connection established) - When control data in each tested size is sent - Then read data should match sent data - """ - - list = [1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 2047, 2048] - control_data_test(dev, list, log) - - -def control_data_test(dev, sizes_list, log): - # Test control requests of various data stage sizes (1,8,16,32,64,255,256,...) - count = 1 - for i in sizes_list: - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_OUT_SIZES - value = i # Size of data the device should actually read - index = 0 # Unused - set for debugging only - data = bytearray(os.urandom(i)) # Dummy data - + self.raise_unconditionally(self.lineno(), "Requesting configuration descriptor failed") + + # interface descriptor try: - dev.ctrl_transfer(request_type, request, value, index, data, 5000) + ret = self.get_descriptor(dev, (DESC_TYPE_INTERFACE << 8) | (0 << 0), 0, INTERFACE_DESC_SIZE) + self.raise_unconditionally(self.lineno(), "Requesting interface descriptor should fail since it is not directly accessible.") except usb.core.USBError: - raise_unconditionally(lineno(), "VENDOR_TEST_CTRL_OUT_SIZES failed ") - - request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_IN_SIZES - value = 0 # Size of data the device should actually send - index = 0 # Unused - set for debugging only - length = i + log("interface descriptor is not directly accessible - OK") + + # endpoint descriptor try: - ret = dev.ctrl_transfer(request_type, request, value, index, length, 5000) - raise_if_different(i, len(ret), lineno(), "send/receive data is the wrong size") - for j in range(0, i): - raise_if_different(data[j], ret[j], lineno(), "send/receive data mismatch") + ret = self.get_descriptor(dev, (DESC_TYPE_ENDPOINT << 8) | (0 << 0), 0, ENDPOINT_DESC_SIZE) + self.raise_unconditionally(self.lineno(), "Requesting endpoint descriptor should fail since it is not directly accessible.") except usb.core.USBError: - raise_unconditionally(lineno(), "VENDOR_TEST_CTRL_IN_SIZES failed") - count += 1 - - -def control_stress_test(dev, log): - """ - Test various patterns of control transfers - - Given an initialized USB (HOST <---> DUT connection established) - When stress control transfer with a data in stage is performed - Then transfer ends with success - When stress control transfer with a data out stage followed by a control transfer with a data in stage is performed - Then transfer ends with success - When stress control transfer with a data out stage is performed - Then transfer ends with success - """ - - # Some devices have had problems with back-to-back - # control transfers. Intentionally send these sequences - # to make sure they are properly handled. - count = 0 - for _ in range(100): - # Control transfer with a data in stage - request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_IN - value = 8 # Size of data the device should actually send - index = count # Unused - set for debugging only - length = 255 - dev.ctrl_transfer(request_type, request, value, index, length, 5000) - count += 1 - - for _ in range(100): - # Control transfer with a data out stage followed - # by a control transfer with a data in stage - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_OUT - value = 8 # Size of data the device should actually read - index = count # Unused - set for debugging only - data = bytearray(8) # Dummy data - dev.ctrl_transfer(request_type, request, value, index, data, 5000) - count += 1 - - request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, - CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_IN - value = 8 # Size of data the device should actually send - index = count # Unused - set for debugging only - length = 255 - dev.ctrl_transfer(request_type, request, value, index, length, 5000) - count += 1 - - for _ in range(100): - # Control transfer with a data out stage - request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + log("endpoint descriptor is not directly accessible - OK") + self.log("") # new line + + + def set_descriptor_test(self, dev: usb.core.Device, log): + """ + Test descriptor setting + + Given an initialized USB (HOST <---> DUT connection established) + When device descriptor is to be set + Then error is thrown since descriptor setting command is not supported by Mbed + """ + + self.log("<<< set_descriptor_test >>>") + # SET_DESCRIPTOR is optional and not implemented in Mbed + # command should fail with no action on device side + + # Control OUT SET_DESCRIPTOR + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_STANDARD, CTRL_RECIPIENT_DEVICE) - request = VENDOR_TEST_CTRL_OUT - value = 8 # Size of data the device should actually read - index = count # Unused - set for debugging only - data = bytearray(8) # Dummy data - dev.ctrl_transfer(request_type, request, value, index, data, 5000) - count += 1 - - -def find_ep_pair(intf, endpoint_type): - """Find an OUT and IN endpoint pair. - - Raise a RuntimeError if any endpoint could not be found - or wMaxPacketSize is not equal for both endpoints. - """ - ep_out = usb.util.find_descriptor( - intf, custom_match=lambda e: - usb.util.endpoint_type(e.bmAttributes) == endpoint_type and - usb.util.endpoint_direction(e.bEndpointAddress) == usb.ENDPOINT_OUT) - ep_in = usb.util.find_descriptor( - intf, custom_match=lambda e: - usb.util.endpoint_type(e.bmAttributes) == endpoint_type and - usb.util.endpoint_direction(e.bEndpointAddress) == usb.ENDPOINT_IN) - if not all((ep_out, ep_in)): - raise_unconditionally(lineno(), 'Unable to find {} endpoint pair.' - .format(ENDPOINT_TYPE_NAMES[endpoint_type])) - raise_if_different(ep_out.wMaxPacketSize, ep_in.wMaxPacketSize, lineno(), - 'wMaxPacketSize not equal for OUT and IN {} endpoints.' - .format(ENDPOINT_TYPE_NAMES[endpoint_type])) - return ep_out, ep_in - - -def loopback_ep_test(ep_out, ep_in, payload_size): - """Send and receive random data using OUT/IN endpoint pair. - - Verify that data received from IN endpoint is equal to - data sent to OUT endpoint. - Raise a RuntimeError if data does not match. - """ - payload_out = array.array('B', (random.randint(0x00, 0xff) for _ in range(payload_size))) - ep_out.write(payload_out) - payload_in = ep_in.read(ep_in.wMaxPacketSize) - raise_if_different(payload_out, payload_in, lineno(), 'Payloads mismatch.') - - -def random_size_loopback_ep_test(ep_out, ep_in, failure, error, seconds, log, min_payload_size=1): - """Repeat data transfer test for OUT/IN endpoint pair for a given time. - - Set a failure Event if OUT/IN data verification fails. - Set an error Event if unexpected USB error occurs. - """ - end_ts = time.time() + seconds - while time.time() < end_ts and not failure.is_set() and not error.is_set(): - payload_size = random.randint(min_payload_size, ep_out.wMaxPacketSize) + request = REQUEST_SET_DESCRIPTOR + value = (DESC_TYPE_DEVICE << 8) | (0 << 0) # Descriptor Type (H) and Descriptor Index (L) + index = 0 # 0 or Language ID for this request + data = bytearray(DEVICE_DESC_SIZE) # Descriptor data try: - loopback_ep_test(ep_out, ep_in, payload_size) - except RuntimeError as err: - log(err) - failure.set() - return - except usb.USBError as err: - log(USB_ERROR_FMT.format(err, ep_out, ep_in, payload_size)) - error.set() - return - time.sleep(0.01) - - -def halt_ep_test(dev, ep_out, ep_in, log): - """OUT/IN endpoint halt test. - - Verify that halting an endpoint at a random point of OUT or IN transfer - raises a USBError. - Raise a RuntimeError if halt fails or any unexpected error occurs. - """ - MIN_HALT_DELAY = 0.01 - MAX_HALT_DELAY = 0.1 - POST_HALT_DELAY = 0.1 - ctrl_error = Event() - - for ep in (ep_out, ep_in): + dev.ctrl_transfer(request_type, request, value, index, data) + self.raise_unconditionally(self.lineno(), "set_descriptor request should fail since it is not implemented") + except usb.core.USBError: + log("SET_DESCRIPTOR is unsupported - OK") + self.log("") # new line + + + def synch_frame_test(self, dev: usb.core.Device, log): + """ + Test sync frame request + + Given an initialized USB (HOST <---> DUT connection established) + When ... + Then ... + """ + + self.log("<<< synch_frame_test >>>") + # only for isochronous endpoints + request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, + CTRL_RECIPIENT_ENDPOINT) + request = REQUEST_SYNCH_FRAME + value = 0 # Always 0 for this request + index = 1 # Endpoint index + length = 2 # Always 2 for this request (size of return data) try: - if (usb.control.get_status(dev, ep) == 1): - raise_unconditionally(lineno(), 'Endpoints must NOT be halted at the start of this test') - except usb.core.USBError as err: - raise_unconditionally(lineno(), 'Unable to get endpoint status ({!r}).'.format(err)) - - ep_to_halt = random.choice([ep_out, ep_in]) - - def timer_handler(): - """Halt an endpoint using a USB control request.""" + ret = dev.ctrl_transfer(request_type, request, value, index, length) + ret = ret[0] | (ret[1] << 8) + log("synch frame ret: %d" % (ret)) + except usb.core.USBError: + self.raise_unconditionally(self.lineno(), "SYNCH_FRAME request failed") + self.log("") # new line + + + def control_stall_test(self, dev: usb.core.Device, log): + """ + Test control endpoint stall on invalid request + + Given an initialized USB (HOST <---> DUT connection established) + When unsupported request to control endpoint is to be sent + Then the endpoint is stalled and error is thrown + """ + + self.log("<<< control_stall_test >>>") + # Control OUT stall try: - usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, ep_to_halt) - if (usb.control.get_status(dev, ep_to_halt) != 1): - raise RuntimeError('Invalid endpoint status after halt operation') - except Exception as err: - ctrl_error.set() - log('Endpoint {:#04x} halt failed ({!r}).'.format(ep_to_halt.bEndpointAddress, err)) - # Whether the halt operation was successful or not, - # wait a bit so the main thread has a chance to run into a USBError - # or report the failure of halt operation. - time.sleep(POST_HALT_DELAY) - - delay = random.uniform(MIN_HALT_DELAY, MAX_HALT_DELAY) - delayed_halt = Timer(delay, timer_handler) - delayed_halt.start() - # Keep transferring data to and from the device until one of the endpoints - # is halted. - try: - while delayed_halt.is_alive(): - if ctrl_error.is_set(): - break + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_UNSUPPORTED_REQUEST + value = 0 # Always 0 for this request + index = 0 # Communication interface + data = bytearray(64) # Dummy data + dev.ctrl_transfer(request_type, request, value, index, data, 5000) + self.raise_unconditionally(self.lineno(), "Invalid request not stalled") + except usb.core.USBError: + log("Invalid request stalled - OK") + + # Control request with no data stage (Device-to-host) + try: + request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_UNSUPPORTED_REQUEST + value = 0 # Always 0 for this request + index = 0 # Communication interface + length = 0 + dev.ctrl_transfer(request_type, request, value, index, length, 5000) + self.raise_unconditionally(self.lineno(), "Invalid request not stalled") + except usb.core.USBError: + log("Invalid request stalled - OK") + + # Control request with no data stage (Host-to-device) + try: + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_UNSUPPORTED_REQUEST + value = 0 # Always 0 for this request + index = 0 # Communication interface + length = 0 + dev.ctrl_transfer(request_type, request, value, index, length, 5000) + self.raise_unconditionally(self.lineno(), "Invalid request not stalled") + except usb.core.USBError: + log("Invalid request stalled - OK") + + # Control IN stall + try: + request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_UNSUPPORTED_REQUEST + value = 0 # Always 0 for this request + index = 0 # Communication interface + length = 255 + dev.ctrl_transfer(request_type, request, value, index, length, 5000) + self.raise_unconditionally(self.lineno(), "Invalid request not stalled") + except usb.core.USBError: + log("Invalid request stalled - OK") + + for i in (3, 4, 5): try: - loopback_ep_test(ep_out, ep_in, ep_out.wMaxPacketSize) + request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, + CTRL_RECIPIENT_DEVICE) + request = 0x6 # GET_DESCRIPTOR + value = (0x03 << 8) | (i << 0) # String descriptor index + index = 0 # Communication interface + length = 255 + resp = dev.ctrl_transfer(request_type, request, value, index, length, 5000) + except usb.core.USBError: + self.raise_unconditionally(self.lineno(), "Requesting string failed i: " + str(i)) + + for i in (6, 7): + try: + request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, + CTRL_RECIPIENT_DEVICE) + request = 0x6 # GET_DESCRIPTOR + value = (0x03 << 8) | (i << 0) # String descriptor index + index = 0 # Communication interface + length = 255 + resp = dev.ctrl_transfer(request_type, request, value, index, length, 5000) + self.raise_unconditionally(self.lineno(), "Requesting string passed i: " + str(i)) + except usb.core.USBError: + log("Requesting string %s failed - OK" % i) + self.log("") # new line + + + def control_sizes_test(self, dev: usb.core.Device, log): + """ + Test various data sizes in control transfer + + Given an initialized USB (HOST <---> DUT connection established) + When control data in each tested size is sent + Then read data should match sent data + """ + + list = [1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 2047, 2048] + self.control_data_test(dev, list, log) + + + def control_data_test(self, dev: usb.core.Device, sizes_list, log): + # Test control requests of various data stage sizes (1,8,16,32,64,255,256,...) + count = 1 + for i in sizes_list: + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_OUT_SIZES + value = i # Size of data the device should actually read + index = 0 # Unused - set for debugging only + data = bytearray(os.urandom(i)) # Dummy data + + try: + dev.ctrl_transfer(request_type, request, value, index, data, 5000) + except usb.core.USBError: + self.raise_unconditionally(self.lineno(), "VENDOR_TEST_CTRL_OUT_SIZES failed ") + + request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_IN_SIZES + value = 0 # Size of data the device should actually send + index = 0 # Unused - set for debugging only + length = i + try: + ret = dev.ctrl_transfer(request_type, request, value, index, length, 5000) + self.raise_if_different(i, len(ret), self.lineno(), "send/receive data is the wrong size") + for j in range(0, i): + self.raise_if_different(data[j], ret[j], self.lineno(), "send/receive data mismatch") + except usb.core.USBError: + self.raise_unconditionally(self.lineno(), "VENDOR_TEST_CTRL_IN_SIZES failed") + count += 1 + + + def control_stress_test(self, dev: usb.core.Device, log): + """ + Test various patterns of control transfers + + Given an initialized USB (HOST <---> DUT connection established) + When stress control transfer with a data in stage is performed + Then transfer ends with success + When stress control transfer with a data out stage followed by a control transfer with a data in stage is performed + Then transfer ends with success + When stress control transfer with a data out stage is performed + Then transfer ends with success + """ + + # Some devices have had problems with back-to-back + # control transfers. Intentionally send these sequences + # to make sure they are properly handled. + count = 0 + for _ in range(100): + # Control transfer with a data in stage + request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_IN + value = 8 # Size of data the device should actually send + index = count # Unused - set for debugging only + length = 255 + dev.ctrl_transfer(request_type, request, value, index, length, 5000) + count += 1 + + for _ in range(100): + # Control transfer with a data out stage followed + # by a control transfer with a data in stage + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_OUT + value = 8 # Size of data the device should actually read + index = count # Unused - set for debugging only + data = bytearray(8) # Dummy data + dev.ctrl_transfer(request_type, request, value, index, data, 5000) + count += 1 + + request_type = build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_IN + value = 8 # Size of data the device should actually send + index = count # Unused - set for debugging only + length = 255 + dev.ctrl_transfer(request_type, request, value, index, length, 5000) + count += 1 + + for _ in range(100): + # Control transfer with a data out stage + request_type = build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, + CTRL_RECIPIENT_DEVICE) + request = VENDOR_TEST_CTRL_OUT + value = 8 # Size of data the device should actually read + index = count # Unused - set for debugging only + data = bytearray(8) # Dummy data + dev.ctrl_transfer(request_type, request, value, index, data, 5000) + count += 1 + + + def find_ep_pair(self, intf, endpoint_type): + """Find an OUT and IN endpoint pair. + + Raise a RuntimeError if any endpoint could not be found + or wMaxPacketSize is not equal for both endpoints. + """ + ep_out = usb.util.find_descriptor( + intf, custom_match=lambda e: + usb.util.endpoint_type(e.bmAttributes) == endpoint_type and + usb.util.endpoint_direction(e.bEndpointAddress) == usb.ENDPOINT_OUT) + ep_in = usb.util.find_descriptor( + intf, custom_match=lambda e: + usb.util.endpoint_type(e.bmAttributes) == endpoint_type and + usb.util.endpoint_direction(e.bEndpointAddress) == usb.ENDPOINT_IN) + if not all((ep_out, ep_in)): + self.raise_unconditionally(self.lineno(), 'Unable to find {} endpoint pair.' + .format(ENDPOINT_TYPE_NAMES[endpoint_type])) + self.raise_if_different(ep_out.wMaxPacketSize, ep_in.wMaxPacketSize, self.lineno(), + 'wMaxPacketSize not equal for OUT and IN {} endpoints.' + .format(ENDPOINT_TYPE_NAMES[endpoint_type])) + return ep_out, ep_in + + + def loopback_ep_test(self, ep_out, ep_in, payload_size): + """Send and receive random data using OUT/IN endpoint pair. + + Verify that data received from IN endpoint is equal to + data sent to OUT endpoint. + Raise a RuntimeError if data does not match. + """ + payload_out = array.array('B', (random.randint(0x00, 0xff) for _ in range(payload_size))) + ep_out.write(payload_out) + payload_in = ep_in.read(ep_in.wMaxPacketSize) + self.raise_if_different(payload_out, payload_in, self.lineno(), 'Payloads mismatch.') + + + def random_size_loopback_ep_test(self, ep_out, ep_in, failure, error, seconds, log, min_payload_size=1): + """Repeat data transfer test for OUT/IN endpoint pair for a given time. + + Set a failure Event if OUT/IN data verification fails. + Set an error Event if unexpected USB error occurs. + """ + end_ts = time.time() + seconds + while time.time() < end_ts and not failure.is_set() and not error.is_set(): + payload_size = random.randint(min_payload_size, ep_out.wMaxPacketSize) + try: + self.loopback_ep_test(ep_out, ep_in, payload_size) + except RuntimeError as err: + log(err) + failure.set() + return + except usb.USBError as err: + log(USB_ERROR_FMT.format(err, ep_out, ep_in, payload_size)) + error.set() + return + time.sleep(0.01) + + + def halt_ep_test(self, dev: usb.core.Device, ep_out, ep_in, log): + """OUT/IN endpoint halt test. + + Verify that halting an endpoint at a random point of OUT or IN transfer + raises a USBError. + Raise a RuntimeError if halt fails or any unexpected error occurs. + """ + MIN_HALT_DELAY = 0.01 + MAX_HALT_DELAY = 0.1 + POST_HALT_DELAY = 0.1 + ctrl_error = Event() + + for ep in (ep_out, ep_in): + try: + if (usb.control.get_status(dev, ep) == 1): + self.raise_unconditionally(self.lineno(), 'Endpoints must NOT be halted at the start of this test') except usb.core.USBError as err: + self.raise_unconditionally(self.lineno(), 'Unable to get endpoint status ({!r}).'.format(err)) + + ep_to_halt = random.choice([ep_out, ep_in]) + + def timer_handler(): + """Halt an endpoint using a USB control request.""" + try: + usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, ep_to_halt) + if (usb.control.get_status(dev, ep_to_halt) != 1): + raise RuntimeError('Invalid endpoint status after halt operation') + except Exception as err: + ctrl_error.set() + log('Endpoint {:#04x} halt failed ({!r}).'.format(ep_to_halt.bEndpointAddress, err)) + # Whether the halt operation was successful or not, + # wait a bit so the main thread has a chance to run into a USBError + # or report the failure of halt operation. + time.sleep(POST_HALT_DELAY) + + delay = random.uniform(MIN_HALT_DELAY, MAX_HALT_DELAY) + delayed_halt = Timer(delay, timer_handler) + delayed_halt.start() + # Keep transferring data to and from the device until one of the endpoints + # is halted. + try: + while delayed_halt.is_alive(): if ctrl_error.is_set(): break try: - ep_status = usb.control.get_status(dev, ep_to_halt) + self.loopback_ep_test(ep_out, ep_in, ep_out.wMaxPacketSize) except usb.core.USBError as err: if ctrl_error.is_set(): break - raise_unconditionally(lineno(), 'Unable to get endpoint status ({!r}).'.format(err)) - if ep_status == 1: - # OK, got USBError because of endpoint halt - return - else: - raise_unconditionally(lineno(), 'Unexpected error ({!r}).'.format(err)) - if ctrl_error.is_set(): - raise_unconditionally(lineno(), 'Halting endpoint {0.bEndpointAddress:#04x} failed' - .format(ep_to_halt)) - finally: - # Always wait for the Timer thread created above. - delayed_halt.join() - if not ctrl_error.is_set(): - ep_out.clear_halt() - ep_in.clear_halt() - raise_unconditionally(lineno(), 'Halting endpoint {0.bEndpointAddress:#04x}' - ' during transmission did not raise USBError.' - .format(ep_to_halt)) - - -def request_endpoint_loops_restart(dev): - ctrl_kwargs = { - 'bmRequestType': build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE), - 'bRequest': VENDOR_TEST_RW_RESTART, - 'wValue': 0, - 'wIndex': 0} - dev.ctrl_transfer(**ctrl_kwargs) - - -def request_abort_buff_check(dev, ep): - ctrl_kwargs = { - 'bmRequestType': build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, CTRL_RECIPIENT_ENDPOINT), - 'bRequest': VENDOR_TEST_ABORT_BUFF_CHECK, - 'wValue': 0, - 'wIndex': ep.bEndpointAddress, - 'data_or_wLength': 1} - return bool(dev.ctrl_transfer(**ctrl_kwargs)[0]) - - -USB_ERROR_FMT = str('Got {0!r} while testing endpoints ' - '{1.bEndpointAddress:#04x}({1.wMaxPacketSize:02}) and ' - '{2.bEndpointAddress:#04x}({2.wMaxPacketSize:02}) with ' - 'a random payload of {3} B.') - - -def ep_test_data_correctness(dev, log, verbose=False): - """Test data correctness for every OUT/IN endpoint pair. - - Given a USB device with multiple OUT/IN endpoint pairs - When the host sends random payloads up to wMaxPacketSize in size - to an OUT endpoint of the device, - and then the device sends data back to host using an IN endpoint - Then data sent and received by host is equal for every endpoint pair - """ - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - intf.set_altsetting() - - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - interrupt_out, interrupt_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) - iso_out, iso_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) - - if verbose: - log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) - log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) - log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) - log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) - log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) - log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) - - if verbose: - log('Testing OUT/IN data correctness for bulk endpoint pair.') - for payload_size in range(bulk_out.wMaxPacketSize + 1): - try: - loopback_ep_test(bulk_out, bulk_in, payload_size) - except usb.USBError as err: - raise_unconditionally(lineno(), USB_ERROR_FMT.format(err, bulk_out, bulk_in, payload_size)) - - if verbose: - log('Testing OUT/IN data correctness for interrupt endpoint pair.') - for payload_size in range(interrupt_out.wMaxPacketSize + 1): - try: - loopback_ep_test(interrupt_out, interrupt_in, payload_size) - except usb.USBError as err: - raise_unconditionally(lineno(), USB_ERROR_FMT.format(err, interrupt_out, interrupt_in, payload_size)) - -# if verbose: -# log('Testing OUT/IN data correctness for isochronous endnpoint pair.') -# payload_size = 128 # range(1, iso_out.wMaxPacketSize + 1): -# try: -# loopback_ep_test(iso_out, iso_in, payload_size) -# except usb.USBError as err: -# log(err) -# raise_unconditionally(lineno(), USB_ERROR_FMT.format(err, iso_out, iso_in, payload_size)) - - -def ep_test_halt(dev, log, verbose=False): - """Test endpoint halt for every OUT/IN endpoint pair. - - Given a USB device with multiple OUT/IN endpoint pairs - When the host issues an endpoint halt control request at a random point - of OUT or IN transfer - Then the endpoint is stalled and all further transfers fail - """ - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - intf.set_altsetting() - - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - interrupt_out, interrupt_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) - - if verbose: - log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) - log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) - log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) - log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) - - if verbose: - log('Testing endpoint halt at a random point of bulk transmission.') - end_ts = time.time() + 1.0 - while time.time() < end_ts: - halt_ep_test(dev, bulk_out, bulk_in, log) - request_endpoint_loops_restart(dev) - - if verbose: - log('Testing endpoint halt at a random point of interrupt transmission.') - end_ts = time.time() + 1.0 - while time.time() < end_ts: - halt_ep_test(dev, interrupt_out, interrupt_in, log) - request_endpoint_loops_restart(dev) - - -def ep_test_parallel_transfers(dev, log, verbose=False): - """Test simultaneous data transfers for multiple OUT/IN endpoint pairs. - - Given a USB device with multiple OUT/IN endpoint pairs - When multiple OUT and IN endpoints are used to transfer random test data - Then all transfers succeed - and data received equals data sent for every endpoint pair - """ - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - intf.set_altsetting() - - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - interrupt_out, interrupt_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) - iso_out, iso_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) - - if verbose: - log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) - log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) - log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) - log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) - log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) - log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) - - if verbose: - log('Testing simultaneous transfers through bulk and interrupt endpoint pairs.') - test_error = Event() - test_failure = Event() - test_kwargs_bulk_ep = { - 'ep_out': bulk_out, - 'ep_in': bulk_in, - 'failure': test_failure, - 'error': test_error, - 'seconds': 1.0, - 'log': log} - test_kwargs_interrupt_ep = { - 'ep_out': interrupt_out, - 'ep_in': interrupt_in, - 'failure': test_failure, - 'error': test_error, - 'seconds': 1.0, - 'log': log} - ep_test_threads = [] - for kwargs in (test_kwargs_bulk_ep, test_kwargs_interrupt_ep): - ep_test_threads.append(Thread(target=random_size_loopback_ep_test, kwargs=kwargs)) - for t in ep_test_threads: - t.start() - for t in ep_test_threads: - t.join() - if test_failure.is_set(): - raise_unconditionally(lineno(), 'Payload mismatch') - if test_error.is_set(): - raise_unconditionally(lineno(), 'USBError') - - -def ep_test_parallel_transfers_ctrl(dev, log, verbose=False): - """Test simultaneous data transfers in parallel with control transfers. - - Given a USB device with multiple OUT/IN endpoint pairs - When multiple OUT and IN endpoints are used to transfer random data - and control requests are processed in parallel - Then all transfers succeed - and for every endpoint pair, data received by host equals data sent by host - """ - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - intf.set_altsetting() - - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - interrupt_out, interrupt_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) - iso_out, iso_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) - - if verbose: - log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) - log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) - log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) - log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) - log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) - log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) - - if verbose: - log('Testing parallel data transfers through bulk, interrupt & control endpoint pairs.') - test_error = Event() - test_failure = Event() - test_kwargs_bulk_ep = { - 'ep_out': bulk_out, - 'ep_in': bulk_in, - 'failure': test_failure, - 'error': test_error, - 'seconds': 1.0, - 'log': log} - test_kwargs_interrupt_ep = { - 'ep_out': interrupt_out, - 'ep_in': interrupt_in, - 'failure': test_failure, - 'error': test_error, - 'seconds': 1.0, - 'log': log} - ep_test_threads = [] - for kwargs in (test_kwargs_bulk_ep, test_kwargs_interrupt_ep): - ep_test_threads.append(Thread(target=random_size_loopback_ep_test, kwargs=kwargs)) - for t in ep_test_threads: - t.start() - while any(t.is_alive() for t in ep_test_threads): - control_stress_test(dev, log) - control_sizes_test(dev, log) - for t in ep_test_threads: - t.join() - if test_failure.is_set(): - raise_unconditionally(lineno(), 'Payload mismatch') - if test_error.is_set(): - raise_unconditionally(lineno(), 'USBError') - - -def ep_test_abort(dev, log, verbose=False): - """Test aborting data transfer for every OUT/IN endpoint pair. - - Given a USB device with multiple OUT/IN endpoint pairs - When a device aborts an in progress data transfer - Then no more data is transmitted - and endpoint buffer is correctly released on the device end - """ - NUM_PACKETS_UNTIL_ABORT = 2 - NUM_PACKETS_AFTER_ABORT = 8 - - # If the host ever receives a payload with any byte set to this value, - # the device does not handle abort operation correctly. The buffer - # passed to aborted operation must not be used after call to abort(). - FORBIDDEN_PAYLOAD_VALUE = NUM_PACKETS_AFTER_ABORT + 1 - - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - intf.set_altsetting() - - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - interrupt_out, interrupt_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) - - if verbose: - log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) - log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) - log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) - log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) - - if verbose: - log('Testing aborting an in progress transfer for IN endpoints.') - for ep_in in (bulk_in, interrupt_in): - payload_size = (NUM_PACKETS_UNTIL_ABORT + NUM_PACKETS_AFTER_ABORT) * ep_in.wMaxPacketSize - payload_in = array.array('B') - while len(payload_in) < payload_size: + try: + ep_status = usb.control.get_status(dev, ep_to_halt) + except usb.core.USBError as err: + if ctrl_error.is_set(): + break + self.raise_unconditionally(self.lineno(), 'Unable to get endpoint status ({!r}).'.format(err)) + if ep_status == 1: + # OK, got USBError because of endpoint halt + return + else: + self.raise_unconditionally(self.lineno(), 'Unexpected error ({!r}).'.format(err)) + if ctrl_error.is_set(): + self.raise_unconditionally(self.lineno(), 'Halting endpoint {0.bEndpointAddress:#04x} failed' + .format(ep_to_halt)) + finally: + # Always wait for the Timer thread created above. + delayed_halt.join() + if not ctrl_error.is_set(): + ep_out.clear_halt() + ep_in.clear_halt() + self.raise_unconditionally(self.lineno(), 'Halting endpoint {0.bEndpointAddress:#04x}' + ' during transmission did not raise USBError.' + .format(ep_to_halt)) + + @staticmethod + def request_endpoint_loops_restart(dev): + ctrl_kwargs = { + 'bmRequestType': build_request_type(CTRL_OUT, CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE), + 'bRequest': VENDOR_TEST_RW_RESTART, + 'wValue': 0, + 'wIndex': 0} + dev.ctrl_transfer(**ctrl_kwargs) + + + @staticmethod + def request_abort_buff_check(dev: usb.core.Device, ep): + ctrl_kwargs = { + 'bmRequestType': build_request_type(CTRL_IN, CTRL_TYPE_VENDOR, CTRL_RECIPIENT_ENDPOINT), + 'bRequest': VENDOR_TEST_ABORT_BUFF_CHECK, + 'wValue': 0, + 'wIndex': ep.bEndpointAddress, + 'data_or_wLength': 1} + return bool(dev.ctrl_transfer(**ctrl_kwargs)[0]) + + + def ep_test_data_correctness(self, dev: usb.core.Device, log, verbose=False): + """Test data correctness for every OUT/IN endpoint pair. + + Given a USB device with multiple OUT/IN endpoint pairs + When the host sends random payloads up to wMaxPacketSize in size + to an OUT endpoint of the device, + and then the device sends data back to host using an IN endpoint + Then data sent and received by host is equal for every endpoint pair + """ + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + intf.set_altsetting() + + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + interrupt_out, interrupt_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) + iso_out, iso_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) + + if verbose: + log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) + log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) + log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) + log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) + log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) + log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) + + if verbose: + log('Testing OUT/IN data correctness for bulk endpoint pair.') + for payload_size in range(bulk_out.wMaxPacketSize + 1): try: - packet = ep_in.read(ep_in.wMaxPacketSize) - payload_in.extend(packet) - except usb.core.USBError as err: - break - if FORBIDDEN_PAYLOAD_VALUE in payload_in: - raise_unconditionally( - lineno(), 'Endpoint buffer not released when aborting the ' - 'write operation on endpoint {0.bEndpointAddress:#04x}.' - .format(ep_in)) + self.loopback_ep_test(bulk_out, bulk_in, payload_size) + except usb.USBError as err: + self.raise_unconditionally(self.lineno(), USB_ERROR_FMT.format(err, bulk_out, bulk_in, payload_size)) + if verbose: - log('The size of data successfully received from endpoint {0.bEndpointAddress:#04x}: {1} B.' - .format(ep_in, len(payload_in))) - too_little = bool(len(payload_in) < (NUM_PACKETS_UNTIL_ABORT * ep_in.wMaxPacketSize)) - too_much = bool(len(payload_in) >= payload_size) - if too_little or too_much: - raise_unconditionally( - lineno(), 'Invalid size of data successfully received from endpoint ' - '{0.bEndpointAddress:#04x} before aborting the transfer. ' - 'Value {1} B out of range [{2}, {3}).' - .format(ep_in, len(payload_in), - NUM_PACKETS_UNTIL_ABORT * ep_in.wMaxPacketSize, payload_size)) - - if verbose: - log('Testing aborting an in progress transfer for OUT endpoints.') - for ep_out in (bulk_out, interrupt_out): - payload_size = (NUM_PACKETS_UNTIL_ABORT + NUM_PACKETS_AFTER_ABORT) * ep_out.wMaxPacketSize - num_bytes_written = 0 - while num_bytes_written < payload_size: - payload_out = array.array('B', (num_bytes_written//ep_out.wMaxPacketSize - for _ in range(ep_out.wMaxPacketSize))) + log('Testing OUT/IN data correctness for interrupt endpoint pair.') + for payload_size in range(interrupt_out.wMaxPacketSize + 1): try: - num_bytes_written += ep_out.write(payload_out) - except usb.core.USBError: - break - try: - ep_buff_correct = request_abort_buff_check(dev, ep_out) - except (usb.core.USBError, IndexError, TypeError) as err: - raise_unconditionally( - lineno(), 'Unable to verify endpoint buffer content ({!r}).'.format(err)) - if not ep_buff_correct: - raise_unconditionally( - lineno(), 'Endpoint buffer not released when aborting the ' - 'read operation on endpoint {0.bEndpointAddress:#04x}.' - .format(ep_out)) + self.loopback_ep_test(interrupt_out, interrupt_in, payload_size) + except usb.USBError as err: + self.raise_unconditionally(self.lineno(), USB_ERROR_FMT.format(err, interrupt_out, interrupt_in, payload_size)) + + # if verbose: + # log('Testing OUT/IN data correctness for isochronous endnpoint pair.') + # payload_size = 128 # range(1, iso_out.wMaxPacketSize + 1): + # try: + # self.loopback_ep_test(iso_out, iso_in, payload_size) + # except usb.USBError as err: + # log(err) + # self.raise_unconditionally(self.lineno(), USB_ERROR_FMT.format(err, iso_out, iso_in, payload_size)) + + + def ep_test_halt(self, dev: usb.core.Device, log, verbose=False): + """Test endpoint halt for every OUT/IN endpoint pair. + + Given a USB device with multiple OUT/IN endpoint pairs + When the host issues an endpoint halt control request at a random point + of OUT or IN transfer + Then the endpoint is stalled and all further transfers fail + """ + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + intf.set_altsetting() + + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + interrupt_out, interrupt_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) + if verbose: - log('The size of data successfully sent to endpoint {0.bEndpointAddress:#04x}: {1} B.' - .format(ep_out, num_bytes_written)) - too_little = bool(num_bytes_written < (NUM_PACKETS_UNTIL_ABORT * ep_out.wMaxPacketSize)) - too_much = bool(num_bytes_written >= payload_size) - if too_little or too_much: - raise_unconditionally( - lineno(), 'Invalid size of data successfully sent to endpoint ' - '{0.bEndpointAddress:#04x} before aborting the transfer. ' - 'Value {1} B out of range [{2}, {3}).' - .format(ep_out, num_bytes_written, - NUM_PACKETS_UNTIL_ABORT * ep_out.wMaxPacketSize, payload_size)) - - -def ep_test_data_toggle(dev, log, verbose=False): - """Test data toggle reset for bulk OUT/IN endpoint pairs. - - Given a USB device - When an interface is set - Then the data toggle bits for all endpoints are reset to DATA0 - When clear feature is called for an endpoint that *IS NOT* stalled - Then the data toggle is reset to DATA0 for that endpoint - When clear halt is called for an endpoint that *IS* stalled - Then the data toggle is reset to DATA0 for that endpoint - """ - cfg = dev.get_active_configuration() - for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') - if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') - continue - log('running tests') - - if verbose: - log('Testing data toggle reset for bulk endpoint pair.') - - # 1.1 reset OUT and IN data toggle to DATA0 - intf.set_altsetting() - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - - # 1.2 send and receive a single data packet, - # so both OUT and IN endpoints switch to DATA1 - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - - # 1.3 reset OUT and IN data toggle to DATA0 - # USB spec, section 9.1.1.5 - # " - # Configuring a device or changing an alternate setting causes all of the status and - # configuration values associated with endpoints in the affected interfaces to be set to their default values. - # This includes setting the data toggle of any endpoint using data toggles to the value DATA0. - # " - intf.set_altsetting() - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - - # 1.4 verify that host and USB device are still in sync with respect to data toggle - try: - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - except usb.USBError as err: + log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) + log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) + log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) + log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) + if verbose: - log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) - raise_unconditionally(lineno(), 'Data toggle not reset when setting interface.') - - # 2.1 reset OUT and IN data toggle to DATA0 - intf.set_altsetting() - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - - # 2.2 send and receive a single data packet, - # so both OUT and IN endpoints switch to DATA1 - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - - # 2.3 reset OUT data toggle to DATA0 - # USB spec, section 9.4.5 - # " - # For endpoints using data toggle, regardless of whether an endpoint has the Halt feature set, a - # ClearFeature(ENDPOINT_HALT) request always results in the data toggle being reinitialized to DATA0. - # " - bulk_out.clear_halt() - # The ClearFeature(ENDPOINT_HALT) terminates a pending read operation on the device end. - # Use a custom vendor request to restart reading on the OUT endpoint. - # This does not impact the state of the data toggle bit. - request_endpoint_loops_restart(dev) - - # 2.4 verify that host and USB device are still in sync with respect to data toggle - try: - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - except usb.USBError as err: + log('Testing endpoint halt at a random point of bulk transmission.') + end_ts = time.time() + 1.0 + while time.time() < end_ts: + self.halt_ep_test(dev, bulk_out, bulk_in, log) + self.request_endpoint_loops_restart(dev) + if verbose: - log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) - raise_unconditionally(lineno(), 'Data toggle not reset when calling ClearFeature(ENDPOINT_HALT) ' - 'on an endpoint that has not been halted.') - - # 3.1 reset OUT and IN data toggle to DATA0 - intf.set_altsetting() - bulk_out, bulk_in = find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) - - # 3.2 send and receive a single data packet, - # so both OUT and IN endpoints switch to DATA1 - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - - # 3.3 reset IN data toggle to DATA0 - # USB spec, section 9.4.5 - # " - # For endpoints using data toggle, regardless of whether an endpoint has the Halt feature set, a - # ClearFeature(ENDPOINT_HALT) request always results in the data toggle being reinitialized to DATA0. - # " - usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, bulk_in) - bulk_in.clear_halt() - - # 3.4 verify that host and USB device are still in sync with respect to data toggle - try: - loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) - except usb.USBError as err: + log('Testing endpoint halt at a random point of interrupt transmission.') + end_ts = time.time() + 1.0 + while time.time() < end_ts: + self.halt_ep_test(dev, interrupt_out, interrupt_in, log) + self.request_endpoint_loops_restart(dev) + + + def ep_test_parallel_transfers(self, dev: usb.core.Device, log, verbose=False): + """Test simultaneous data transfers for multiple OUT/IN endpoint pairs. + + Given a USB device with multiple OUT/IN endpoint pairs + When multiple OUT and IN endpoints are used to transfer random test data + Then all transfers succeed + and data received equals data sent for every endpoint pair + """ + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + intf.set_altsetting() + + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + interrupt_out, interrupt_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) + iso_out, iso_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) + if verbose: - log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) - raise_unconditionally(lineno(), 'Data toggle not reset when clearing endpoint halt.') - - -def device_reset_test(log): - """ - Test USB implementation against repeated reset - - Given an initialized USB (HOST <---> DUT connection established) - When USB device is reset repeatedly - Then the USB is operational with no errors - """ - - dev = yield - dev.reset(); - dev = yield - # run other test to check if USB works fine after reset - control_data_test(dev, [64, 256], log) - dev.reset(); - dev = yield - # run other test to check if USB works fine after reset - control_data_test(dev, [64, 256], log) - dev.reset(); - dev = yield - # run other test to check if USB works fine after reset - control_data_test(dev, [64, 256], log) - yield - - -def device_soft_reconnection_test(log): - """ - Test USB implementation against repeated reconnection - - Given an initialized USB (HOST <---> DUT connection established) - When USB device is disconnected and then connected repeatedly - Then the USB is operational with no errors - """ - - list = [64, 256] - dev = yield - # run other test to check if USB works fine before reconnection - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after reconnection - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after reconnection - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after reconnection - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after reconnection - control_data_test(dev, list, log) - yield - - -def device_suspend_resume_test(log): - """ - Test USB implementation against repeated suspend and resume - - Given an initialized USB (HOST <---> DUT connection established) - When USB device is suspended and then resumed repeatedly - Then the USB is operational with no errors - """ - - dev = yield - control_data_test(dev, [64, 256], log) - # suspend code goes here - # ... - # resume code here - # ... - # run other test to check if USB works fine after resume - control_data_test(dev, [64, 256], log) - # suspend code here - # ... - # resume code here - # ... - # run other test to check if USB works fine after resume - control_data_test(dev, [64, 256], log) - # suspend code here - # ... - # resume code here - # ... - # run other test to check if USB works fine after resume - control_data_test(dev, [64, 256], log) - yield - - -def repeated_construction_destruction_test(log): - """ - Test USB implementation against repeated initialization and deinitialization - - Given an initialized USB (HOST <---> DUT connection established) - When USB device is deinitialized and then initialized repeatedly - Then the USB is operational with no errors - """ - - list = [64, 256] - dev = yield - # run other test to check if USB works fine after repeated construction/destruction - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after repeated construction/destruction - control_data_test(dev, list, log) - dev = yield - # run other test to check if USB works fine after repeated construction/destruction - control_data_test(dev, list, log) - yield - - -def release_interfaces(dev): - """ Releases interfaces to allow configuration switch - - Fixes error while configuration change(on Windows machines): - USBError: [Errno None] libusb0-dll:err [set_configuration] can't change configuration, an interface is still in use (claimed) - """ - cfg = dev.get_active_configuration() - for i in range(0, cfg.bNumInterfaces): - usb.util.release_interface(dev, i) - - -def restore_default_configuration(dev): - """ Set default configuration """ - - cfg = dev[1] - cfg.set() - - -def get_status(dev, recipient, index = 0): - """ Get status of the recipient - - Args: - dev - pyusb device - recipient - CTRL_RECIPIENT_DEVICE/CTRL_RECIPIENT_INTERFACE/CTRL_RECIPIENT_ENDPOINT - index - 0 if recipient is device, interface index if recipient is interface, endpoint index if recipient is endpoint - - Returns: - status flag 32b int - """ - request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, - recipient) - request = REQUEST_GET_STATUS - value = 0 # Always 0 for this request - index = index # recipient index - length = 2 # Always 2 for this request (size of return data) - ret = dev.ctrl_transfer(request_type, request, value, index, length) - ret = ret[0] | (ret[1] << 8) - - return ret - - -def get_descriptor(dev, type_index, lang_id, length): - # Control IN GET_DESCRIPTOR - device - request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, - CTRL_RECIPIENT_DEVICE) - request = REQUEST_GET_DESCRIPTOR - value = type_index # Descriptor Type (H) and Descriptor Index (L) - index = lang_id # 0 or Language ID for this request - length = length # Descriptor Length - ret = dev.ctrl_transfer(request_type, request, value, index, length) - - return ret + log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) + log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) + log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) + log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) + log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) + log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) + + if verbose: + log('Testing simultaneous transfers through bulk and interrupt endpoint pairs.') + test_error = Event() + test_failure = Event() + test_kwargs_bulk_ep = { + 'ep_out': bulk_out, + 'ep_in': bulk_in, + 'failure': test_failure, + 'error': test_error, + 'seconds': 1.0, + 'log': log} + test_kwargs_interrupt_ep = { + 'ep_out': interrupt_out, + 'ep_in': interrupt_in, + 'failure': test_failure, + 'error': test_error, + 'seconds': 1.0, + 'log': log} + ep_test_threads = [] + for kwargs in (test_kwargs_bulk_ep, test_kwargs_interrupt_ep): + ep_test_threads.append(Thread(target=self.random_size_loopback_ep_test, kwargs=kwargs)) + for t in ep_test_threads: + t.start() + for t in ep_test_threads: + t.join() + if test_failure.is_set(): + self.raise_unconditionally(self.lineno(), 'Payload mismatch') + if test_error.is_set(): + self.raise_unconditionally(self.lineno(), 'USBError') + + + def ep_test_parallel_transfers_ctrl(self, dev: usb.core.Device, log, verbose=False): + """Test simultaneous data transfers in parallel with control transfers. + + Given a USB device with multiple OUT/IN endpoint pairs + When multiple OUT and IN endpoints are used to transfer random data + and control requests are processed in parallel + Then all transfers succeed + and for every endpoint pair, data received by host equals data sent by host + """ + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + intf.set_altsetting() + + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + interrupt_out, interrupt_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) + iso_out, iso_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_ISOCHRONOUS) + + if verbose: + log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) + log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) + log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) + log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) + log('\tiso_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_out)) + log('\tiso_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(iso_in)) + + if verbose: + log('Testing parallel data transfers through bulk, interrupt & control endpoint pairs.') + test_error = Event() + test_failure = Event() + test_kwargs_bulk_ep = { + 'ep_out': bulk_out, + 'ep_in': bulk_in, + 'failure': test_failure, + 'error': test_error, + 'seconds': 1.0, + 'log': log} + test_kwargs_interrupt_ep = { + 'ep_out': interrupt_out, + 'ep_in': interrupt_in, + 'failure': test_failure, + 'error': test_error, + 'seconds': 1.0, + 'log': log} + ep_test_threads = [] + for kwargs in (test_kwargs_bulk_ep, test_kwargs_interrupt_ep): + ep_test_threads.append(Thread(target=self.random_size_loopback_ep_test, kwargs=kwargs)) + for t in ep_test_threads: + t.start() + while any(t.is_alive() for t in ep_test_threads): + self.control_stress_test(dev, log) + self.control_sizes_test(dev, log) + for t in ep_test_threads: + t.join() + if test_failure.is_set(): + self.raise_unconditionally(self.lineno(), 'Payload mismatch') + if test_error.is_set(): + self.raise_unconditionally(self.lineno(), 'USBError') + + + def ep_test_abort(self, dev: usb.core.Device, log, verbose=False): + """Test aborting data transfer for every OUT/IN endpoint pair. + + Given a USB device with multiple OUT/IN endpoint pairs + When a device aborts an in progress data transfer + Then no more data is transmitted + and endpoint buffer is correctly released on the device end + """ + NUM_PACKETS_UNTIL_ABORT = 2 + NUM_PACKETS_AFTER_ABORT = 8 + + # If the host ever receives a payload with any byte set to this value, + # the device does not handle abort operation correctly. The buffer + # passed to aborted operation must not be used after call to abort(). + FORBIDDEN_PAYLOAD_VALUE = NUM_PACKETS_AFTER_ABORT + 1 + + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + intf.set_altsetting() + + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + interrupt_out, interrupt_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_INTERRUPT) + + if verbose: + log('\tbulk_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_out)) + log('\tbulk_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(bulk_in)) + log('\tinterrupt_out {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_out)) + log('\tinterrupt_in {0.bEndpointAddress:#04x}, {0.wMaxPacketSize:02} B'.format(interrupt_in)) + + if verbose: + log('Testing aborting an in progress transfer for IN endpoints.') + for ep_in in (bulk_in, interrupt_in): + payload_size = (NUM_PACKETS_UNTIL_ABORT + NUM_PACKETS_AFTER_ABORT) * ep_in.wMaxPacketSize + payload_in = array.array('B') + while len(payload_in) < payload_size: + try: + packet = ep_in.read(ep_in.wMaxPacketSize) + payload_in.extend(packet) + except usb.core.USBError as err: + break + if FORBIDDEN_PAYLOAD_VALUE in payload_in: + self.raise_unconditionally( + self.lineno(), 'Endpoint buffer not released when aborting the ' + 'write operation on endpoint {0.bEndpointAddress:#04x}.' + .format(ep_in)) + if verbose: + log('The size of data successfully received from endpoint {0.bEndpointAddress:#04x}: {1} B.' + .format(ep_in, len(payload_in))) + too_little = bool(len(payload_in) < (NUM_PACKETS_UNTIL_ABORT * ep_in.wMaxPacketSize)) + too_much = bool(len(payload_in) >= payload_size) + if too_little or too_much: + self.raise_unconditionally( + self.lineno(), 'Invalid size of data successfully received from endpoint ' + '{0.bEndpointAddress:#04x} before aborting the transfer. ' + 'Value {1} B out of range [{2}, {3}).' + .format(ep_in, len(payload_in), + NUM_PACKETS_UNTIL_ABORT * ep_in.wMaxPacketSize, payload_size)) + + if verbose: + log('Testing aborting an in progress transfer for OUT endpoints.') + for ep_out in (bulk_out, interrupt_out): + payload_size = (NUM_PACKETS_UNTIL_ABORT + NUM_PACKETS_AFTER_ABORT) * ep_out.wMaxPacketSize + num_bytes_written = 0 + while num_bytes_written < payload_size: + payload_out = array.array('B', (num_bytes_written//ep_out.wMaxPacketSize + for _ in range(ep_out.wMaxPacketSize))) + try: + num_bytes_written += ep_out.write(payload_out) + except usb.core.USBError: + break + try: + ep_buff_correct = self.request_abort_buff_check(dev, ep_out) + except (usb.core.USBError, IndexError, TypeError) as err: + self.raise_unconditionally( + self.lineno(), 'Unable to verify endpoint buffer content ({!r}).'.format(err)) + if not ep_buff_correct: + self.raise_unconditionally( + self.lineno(), 'Endpoint buffer not released when aborting the ' + 'read operation on endpoint {0.bEndpointAddress:#04x}.' + .format(ep_out)) + if verbose: + log('The size of data successfully sent to endpoint {0.bEndpointAddress:#04x}: {1} B.' + .format(ep_out, num_bytes_written)) + too_little = bool(num_bytes_written < (NUM_PACKETS_UNTIL_ABORT * ep_out.wMaxPacketSize)) + too_much = bool(num_bytes_written >= payload_size) + if too_little or too_much: + self.raise_unconditionally( + self.lineno(), 'Invalid size of data successfully sent to endpoint ' + '{0.bEndpointAddress:#04x} before aborting the transfer. ' + 'Value {1} B out of range [{2}, {3}).' + .format(ep_out, num_bytes_written, + NUM_PACKETS_UNTIL_ABORT * ep_out.wMaxPacketSize, payload_size)) + + + def ep_test_data_toggle(self, dev: usb.core.Device, log, verbose=False): + """Test data toggle reset for bulk OUT/IN endpoint pairs. + + Given a USB device + When an interface is set + Then the data toggle bits for all endpoints are reset to DATA0 + When clear feature is called for an endpoint that *IS NOT* stalled + Then the data toggle is reset to DATA0 for that endpoint + When clear halt is called for an endpoint that *IS* stalled + Then the data toggle is reset to DATA0 for that endpoint + """ + cfg = dev.get_active_configuration() + for intf in cfg: + log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + if intf.bAlternateSetting == 0: + log('skipping the default AlternateSetting') + continue + log('running tests') + + if verbose: + log('Testing data toggle reset for bulk endpoint pair.') + + # 1.1 reset OUT and IN data toggle to DATA0 + intf.set_altsetting() + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + + # 1.2 send and receive a single data packet, + # so both OUT and IN endpoints switch to DATA1 + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + + # 1.3 reset OUT and IN data toggle to DATA0 + # USB spec, section 9.1.1.5 + # " + # Configuring a device or changing an alternate setting causes all of the status and + # configuration values associated with endpoints in the affected interfaces to be set to their default values. + # This includes setting the data toggle of any endpoint using data toggles to the value DATA0. + # " + intf.set_altsetting() + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + + # 1.4 verify that host and USB device are still in sync with respect to data toggle + try: + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + except usb.USBError as err: + if verbose: + log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) + self.raise_unconditionally(self.lineno(), 'Data toggle not reset when setting interface.') + + # 2.1 reset OUT and IN data toggle to DATA0 + intf.set_altsetting() + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + + # 2.2 send and receive a single data packet, + # so both OUT and IN endpoints switch to DATA1 + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + + # 2.3 reset OUT data toggle to DATA0 + # USB spec, section 9.4.5 + # " + # For endpoints using data toggle, regardless of whether an endpoint has the Halt feature set, a + # ClearFeature(ENDPOINT_HALT) request always results in the data toggle being reinitialized to DATA0. + # " + bulk_out.clear_halt() + # The ClearFeature(ENDPOINT_HALT) terminates a pending read operation on the device end. + # Use a custom vendor request to restart reading on the OUT endpoint. + # This does not impact the state of the data toggle bit. + self.request_endpoint_loops_restart(dev) + + # 2.4 verify that host and USB device are still in sync with respect to data toggle + try: + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + except usb.USBError as err: + if verbose: + log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) + self.raise_unconditionally(self.lineno(), 'Data toggle not reset when calling ClearFeature(ENDPOINT_HALT) ' + 'on an endpoint that has not been halted.') + + # 3.1 reset OUT and IN data toggle to DATA0 + intf.set_altsetting() + bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) + + # 3.2 send and receive a single data packet, + # so both OUT and IN endpoints switch to DATA1 + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + + # 3.3 reset IN data toggle to DATA0 + # USB spec, section 9.4.5 + # " + # For endpoints using data toggle, regardless of whether an endpoint has the Halt feature set, a + # ClearFeature(ENDPOINT_HALT) request always results in the data toggle being reinitialized to DATA0. + # " + usb.control.set_feature(dev, FEATURE_ENDPOINT_HALT, bulk_in) + bulk_in.clear_halt() + + # 3.4 verify that host and USB device are still in sync with respect to data toggle + try: + self.loopback_ep_test(bulk_out, bulk_in, bulk_out.wMaxPacketSize) + except usb.USBError as err: + if verbose: + log(USB_ERROR_FMT.format(err, bulk_out, bulk_in, bulk_out.wMaxPacketSize)) + self.raise_unconditionally(self.lineno(), 'Data toggle not reset when clearing endpoint halt.') + + + def device_reset_test(self, log): + """ + Test USB implementation against repeated reset + + Given an initialized USB (HOST <---> DUT connection established) + When USB device is reset repeatedly + Then the USB is operational with no errors + """ + + dev = yield + dev.reset(); + dev = yield + # run other test to check if USB works fine after reset + self.control_data_test(dev, [64, 256], log) + dev.reset(); + dev = yield + # run other test to check if USB works fine after reset + self.control_data_test(dev, [64, 256], log) + dev.reset(); + dev = yield + # run other test to check if USB works fine after reset + self.control_data_test(dev, [64, 256], log) + yield + + + def device_soft_reconnection_test(self, log): + """ + Test USB implementation against repeated reconnection + + Given an initialized USB (HOST <---> DUT connection established) + When USB device is disconnected and then connected repeatedly + Then the USB is operational with no errors + """ + + list = [64, 256] + dev = yield + # run other test to check if USB works fine before reconnection + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after reconnection + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after reconnection + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after reconnection + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after reconnection + self.control_data_test(dev, list, log) + yield + + + def device_suspend_resume_test(self, log): + """ + Test USB implementation against repeated suspend and resume + + Given an initialized USB (HOST <---> DUT connection established) + When USB device is suspended and then resumed repeatedly + Then the USB is operational with no errors + """ + + dev = yield + self.control_data_test(dev, [64, 256], log) + # suspend code goes here + # ... + # resume code here + # ... + # run other test to check if USB works fine after resume + self.control_data_test(dev, [64, 256], log) + # suspend code here + # ... + # resume code here + # ... + # run other test to check if USB works fine after resume + self.control_data_test(dev, [64, 256], log) + # suspend code here + # ... + # resume code here + # ... + # run other test to check if USB works fine after resume + self.control_data_test(dev, [64, 256], log) + yield + + + def repeated_construction_destruction_test(self, log): + """ + Test USB implementation against repeated initialization and deinitialization + + Given an initialized USB (HOST <---> DUT connection established) + When USB device is deinitialized and then initialized repeatedly + Then the USB is operational with no errors + """ + + list = [64, 256] + dev = yield + # run other test to check if USB works fine after repeated construction/destruction + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after repeated construction/destruction + self.control_data_test(dev, list, log) + dev = yield + # run other test to check if USB works fine after repeated construction/destruction + self.control_data_test(dev, list, log) + yield + + + def release_interfaces(self, dev): + """ Releases interfaces to allow configuration switch + + Fixes error while configuration change(on Windows machines): + USBError: [Errno None] libusb0-dll:err [set_configuration] can't change configuration, an interface is still in use (claimed) + """ + cfg = dev.get_active_configuration() + for i in range(0, cfg.bNumInterfaces): + usb.util.release_interface(dev, i) + + + @staticmethod + def restore_default_configuration(dev): + """ Set default configuration """ + + cfg = dev[1] + cfg.set() + + + def get_status(self, dev: usb.core.Device, recipient, index = 0): + """ Get status of the recipient + + Args: + dev - pyusb device + recipient - CTRL_RECIPIENT_DEVICE/CTRL_RECIPIENT_INTERFACE/CTRL_RECIPIENT_ENDPOINT + index - 0 if recipient is device, interface index if recipient is interface, endpoint index if recipient is endpoint + + Returns: + status flag 32b int + """ + request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, + recipient) + request = REQUEST_GET_STATUS + value = 0 # Always 0 for this request + index = index # recipient index + length = 2 # Always 2 for this request (size of return data) + ret = dev.ctrl_transfer(request_type, request, value, index, length) + ret = ret[0] | (ret[1] << 8) + + return ret + + + def get_descriptor(self, dev: usb.core.Device, type_index, lang_id, length): + # Control IN GET_DESCRIPTOR - device + request_type = build_request_type(CTRL_IN, CTRL_TYPE_STANDARD, + CTRL_RECIPIENT_DEVICE) + request = REQUEST_GET_DESCRIPTOR + value = type_index # Descriptor Type (H) and Descriptor Index (L) + index = lang_id # 0 or Language ID for this request + length = length # Descriptor Length + ret = dev.ctrl_transfer(request_type, request, value, index, length) + + return ret diff --git a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp index 14dcfdb7f9..57f10fe6ac 100644 --- a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp +++ b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp @@ -17,10 +17,13 @@ #if DEVICE_USBDEVICE +#include + #include "stdint.h" #include "USBTester.h" #include "events/mbed_shared_queues.h" #include "EndpointResolver.h" +#include "mbed_interface.h" #define DEFAULT_CONFIGURATION (1) #define LAST_CONFIGURATION (2) @@ -207,12 +210,17 @@ void USBTester::callback_set_configuration(uint8_t configuration) bool USBTester::setup_iterface(uint8_t ep_in, uint8_t ep_out, uint32_t ep_size, usb_ep_type_t ep_type, uint8_t *buf, uint32_t buf_size, void (USBTester::*callback)()) { - bool success = false; + if(!endpoint_add(ep_in, ep_size, ep_type)) { + mbed_error_printf("Warning: endpoint_add(%" PRIu8 ", %" PRIu32 ", %d) failed.\n", ep_in, ep_size, ep_type); + return false; + } - success = endpoint_add(ep_in, ep_size, ep_type); - success &= endpoint_add(ep_out, ep_size, ep_type, callback); - success &= read_start(ep_out, buf, buf_size); - return success; + if(!endpoint_add(ep_out, ep_size, ep_type, callback)) { + mbed_error_printf("Warning: endpoint_add(%" PRIu8 ", %" PRIu32 ", %d, callback) failed.\n", ep_out, ep_size, ep_type); + return false; + } + + return read_start(ep_out, buf, buf_size); } void USBTester::remove_iterface(uint16_t interface) @@ -276,6 +284,8 @@ void USBTester::callback_set_interface(uint16_t interface, uint8_t alternate) bool USBTester::set_interface(uint16_t interface, uint16_t alternate) { + mbed_error_printf("Interface set: interface=%" PRIu16 ", alternate = %" PRIu16 "\n", interface, alternate); + bool success = false; if (interface == 0) { diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h index d0f938851f..389979d0a5 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h @@ -67,6 +67,7 @@ class USBPhyHw : public USBPhy { private: USBPhyEvents *events; int new_addr; // Indicates a new device address has been chosen + size_t open_endpoints; uint32_t dpram_buffer_free_ptr; endpoint_info_t ep_info_in [USB_NUM_ENDPOINTS]; endpoint_info_t ep_info_out[USB_NUM_ENDPOINTS]; diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp index cb10224ec9..8a7b0bf0ab 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp @@ -68,6 +68,7 @@ void USBPhyHw::init(USBPhyEvents *events) { this->events = events; this->new_addr = 0; + this->open_endpoints = 0; instance = this; // Disable IRQ @@ -290,9 +291,12 @@ bool USBPhyHw::endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_ (this->dpram_buffer_free_ptr + 0x180); this->dpram_buffer_free_ptr += (max_packet + 63) & ~63; + ++open_endpoints; + mbed_error_printf("EP added: %hhu\n", endpoint); } else { + mbed_error_printf("RP2xxx USB PHY: Out of DPRAM memory! Too many opened and closed endpoints.\n"); return false; } @@ -301,7 +305,13 @@ bool USBPhyHw::endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_ void USBPhyHw::endpoint_remove(usb_ep_t endpoint) { + mbed_error_printf("EP removed: %hhu\n", endpoint); + --open_endpoints; + if(open_endpoints == 0) { + mbed_error_printf("All EPs closed, freeing DPRAM\n"); + dpram_buffer_free_ptr = 0; + } } void USBPhyHw::endpoint_stall(usb_ep_t endpoint) @@ -402,6 +412,7 @@ void USBPhyHw::process() // Reset all endpoint buffers and controls (leave SETUP packet) memset(&usb_dpram->ep_ctrl[0], 0, sizeof(*usb_dpram) - sizeof(usb_dpram->setup_packet)); this->dpram_buffer_free_ptr = 0; + open_endpoints = 0; // Clear the bus reset usb_hw->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; diff --git a/targets/targets.json5 b/targets/targets.json5 index cbcaca569c..6a8b9c1d1c 100644 --- a/targets/targets.json5 +++ b/targets/targets.json5 @@ -8486,7 +8486,6 @@ mode is recommended for target MCUs with small amounts of flash and RAM.", "device_has_remove": [ "SERIAL_FC", - "USBDEVICE", ], "config": { "lpticker-use-lposc": { From 72056adff84f2d5be030553858248ccca8ded0b8 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sat, 1 Aug 2026 21:04:48 -0700 Subject: [PATCH 2/9] RP2xxx USB improvements --- .gitmodules | 3 + CHANGELOG.md | 2 + drivers/usb/source/USBDevice.cpp | 13 +- .../usb/tests/TESTS/host_tests/pyusb_basic.py | 42 +++--- .../usb/tests/TESTS/usb_device/basic/main.cpp | 4 +- platform/include/platform/mbed_math_helpers.h | 15 ++ targets/TARGET_RASPBERRYPI/CMakeLists.txt | 9 ++ .../TARGET_MCU_RP2/USBPhyHw.h | 4 +- .../TARGET_MCU_RP2/USBPhy_RP2.cpp | 133 +++++++++++++----- targets/TARGET_RASPBERRYPI/tinyalloc | 1 + .../host_tests_runner/host_test_default.py | 7 +- 11 files changed, 170 insertions(+), 63 deletions(-) create mode 160000 targets/TARGET_RASPBERRYPI/tinyalloc diff --git a/.gitmodules b/.gitmodules index 335be34dd1..097a07e411 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "features/frameworks/rtt/RTT"] path = features/frameworks/rtt/RTT url = https://github.com/SEGGERMicro/RTT.git +[submodule "targets/TARGET_RASPBERRYPI/tinyalloc"] + path = targets/TARGET_RASPBERRYPI/tinyalloc + url = https://github.com/thi-ng/tinyalloc.git diff --git a/CHANGELOG.md b/CHANGELOG.md index f2688a5503..285f852384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,8 @@ A message that notes the main changes in the update. - Fixed issue where reading from an I2C master in slave mode could hang forever if the master ends the transaction early - Fixed issue where writing to an I2C master in slave mode would always return success regardless of success/failure - Fixed assert failure when calling SPI::write() with a zero-length Tx or Rx buffer + - Implemented missing USB endpoint abort function, so `USBDevice::endpoint_abort()` is no longer a no-op + - Implemented memory manager for USB DPRAM and proper deallocation of endpoint buffers, so USB will no longer die once a certain number of endpoints are created and destroyed over the life of the application. - RP2040: - Fixed RTC not counting until the time was first set (just calling `RealTimeClock::init()` was not enough) - Fixed issue where if the same setting was overridden in multiple different `target_override` blocks in mbed_app.json, only one of the overrides would be processed diff --git a/drivers/usb/source/USBDevice.cpp b/drivers/usb/source/USBDevice.cpp index e6dd930caf..5deb8ce7d5 100644 --- a/drivers/usb/source/USBDevice.cpp +++ b/drivers/usb/source/USBDevice.cpp @@ -22,6 +22,7 @@ #include "USBDescriptor.h" #include "usb_phy_api.h" #include "mbed_assert.h" +#include "mbed_interface.h" #include "platform/mbed_error.h" //#define DEBUG @@ -1007,7 +1008,17 @@ void USBDevice::in(usb_ep_t endpoint) endpoint_info_t *info = &_endpoint_info[EP_TO_INDEX(endpoint)]; - MBED_ASSERT(info->pending >= 1); + if(info->pending == 0) { + MBED_ERROR1( + MBED_MAKE_ERROR( + MBED_MODULE_DRIVER_USB, + MBED_ERROR_CODE_INVALID_OPERATION + ), + "IN transfer started to endpoint with no preceding write_start() call.", + endpoint + ); + } + info->pending -= 1; if (info->callback) { info->callback(); diff --git a/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py b/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py index 2dd2fbe7ec..c9a47e567e 100644 --- a/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py +++ b/drivers/usb/tests/TESTS/host_tests/pyusb_basic.py @@ -1054,7 +1054,7 @@ def request_abort_buff_check(dev: usb.core.Device, ep): return bool(dev.ctrl_transfer(**ctrl_kwargs)[0]) - def ep_test_data_correctness(self, dev: usb.core.Device, log, verbose=False): + def ep_test_data_correctness(self, dev: usb.core.Device, log, verbose=True): """Test data correctness for every OUT/IN endpoint pair. Given a USB device with multiple OUT/IN endpoint pairs @@ -1065,11 +1065,11 @@ def ep_test_data_correctness(self, dev: usb.core.Device, log, verbose=False): """ cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') intf.set_altsetting() bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) @@ -1110,7 +1110,7 @@ def ep_test_data_correctness(self, dev: usb.core.Device, log, verbose=False): # self.raise_unconditionally(self.lineno(), USB_ERROR_FMT.format(err, iso_out, iso_in, payload_size)) - def ep_test_halt(self, dev: usb.core.Device, log, verbose=False): + def ep_test_halt(self, dev: usb.core.Device, log, verbose=True): """Test endpoint halt for every OUT/IN endpoint pair. Given a USB device with multiple OUT/IN endpoint pairs @@ -1118,13 +1118,15 @@ def ep_test_halt(self, dev: usb.core.Device, log, verbose=False): of OUT or IN transfer Then the endpoint is stalled and all further transfers fail """ + self.log(f"Testing with device (bus = {dev.bus}, address = {dev.address})") + cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') intf.set_altsetting() bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) @@ -1161,11 +1163,11 @@ def ep_test_parallel_transfers(self, dev: usb.core.Device, log, verbose=False): """ cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') intf.set_altsetting() bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) @@ -1222,11 +1224,11 @@ def ep_test_parallel_transfers_ctrl(self, dev: usb.core.Device, log, verbose=Fal """ cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') intf.set_altsetting() bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) @@ -1293,11 +1295,11 @@ def ep_test_abort(self, dev: usb.core.Device, log, verbose=False): cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') intf.set_altsetting() bulk_out, bulk_in = self.find_ep_pair(intf, usb.ENDPOINT_TYPE_BULK) @@ -1387,11 +1389,11 @@ def ep_test_data_toggle(self, dev: usb.core.Device, log, verbose=False): """ cfg = dev.get_active_configuration() for intf in cfg: - log('interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting), end='') + message = 'interface {}, alt {} -- '.format(intf.bInterfaceNumber, intf.bAlternateSetting) if intf.bAlternateSetting == 0: - log('skipping the default AlternateSetting') + log(message + 'skipping the default AlternateSetting') continue - log('running tests') + log(message + 'running tests') if verbose: log('Testing data toggle reset for bulk endpoint pair.') diff --git a/drivers/usb/tests/TESTS/usb_device/basic/main.cpp b/drivers/usb/tests/TESTS/usb_device/basic/main.cpp index 33a81698b0..d1772d9b9c 100644 --- a/drivers/usb/tests/TESTS/usb_device/basic/main.cpp +++ b/drivers/usb/tests/TESTS/usb_device/basic/main.cpp @@ -505,7 +505,7 @@ void device_soft_reconnection_test() greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); - for (int i = 0; i < reconnect_try_count; i++) { + for (uint32_t i = 0; i < reconnect_try_count; i++) { serial.disconnect(); wait_us(MIN_DISCONNECT_TIME_US); serial.connect(); @@ -653,7 +653,7 @@ utest::v1::status_t greentea_test_setup(const size_t number_of_cases) return greentea_test_setup_handler(number_of_cases); } -Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); +Specification specification(greentea_test_setup, cases, greentea_continue_handlers); int main() { diff --git a/platform/include/platform/mbed_math_helpers.h b/platform/include/platform/mbed_math_helpers.h index c09396c04c..d0b88a0494 100644 --- a/platform/include/platform/mbed_math_helpers.h +++ b/platform/include/platform/mbed_math_helpers.h @@ -37,3 +37,18 @@ static inline uint32_t mbed_integer_log_2(uint32_t x) { return sizeof(uint32_t) * 8 - 1 - __builtin_clz(x); } + +/** + * @brief Align an address (\c addr) to a given \c alignment by adding between 0 and \c alignment-1 bytes to it. + */ +static inline void* mbed_align_up_to(void* addr, size_t alignment) +{ + // Use integer division to divide the address down to the alignment size, which + // rounds to the block before the given address. + // So that we always go one cache line back even if the given address is on the start of a block, + // subtract 1. + ptrdiff_t prev_block_start = ((ptrdiff_t)(addr) - 1) / alignment; + + // Now we just have to multiply up again to get an address (adding 1 to go forward by 1 block) + return (void *)((prev_block_start + 1) * alignment); +} \ No newline at end of file diff --git a/targets/TARGET_RASPBERRYPI/CMakeLists.txt b/targets/TARGET_RASPBERRYPI/CMakeLists.txt index 96e32b21d0..b9fa54f98e 100644 --- a/targets/TARGET_RASPBERRYPI/CMakeLists.txt +++ b/targets/TARGET_RASPBERRYPI/CMakeLists.txt @@ -186,6 +186,15 @@ elseif(MCU_RP235X IN_LIST MBED_TARGET_LABELS) ) endif() +# tinyalloc, used for managing USB ram +mbed_setup_submodule(tinyalloc CHECK_FILE tinyalloc.c IF_LABEL TARGET_MCU_RP2) +target_sources(mbed-mcu-rp2 + INTERFACE + tinyalloc/tinyalloc.c) +target_include_directories(mbed-mcu-rp2 + INTERFACE + tinyalloc) + # RP2040 SDK ----------------------------------------------------------------------------------------------------------- target_include_directories(mbed-mcu-rp2040 diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h index 389979d0a5..61aac5e50f 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h @@ -67,11 +67,13 @@ class USBPhyHw : public USBPhy { private: USBPhyEvents *events; int new_addr; // Indicates a new device address has been chosen - size_t open_endpoints; uint32_t dpram_buffer_free_ptr; endpoint_info_t ep_info_in [USB_NUM_ENDPOINTS]; endpoint_info_t ep_info_out[USB_NUM_ENDPOINTS]; + /// (Re)initialize the heap structure in DPRAM for storing USB buffers + void reinit_dpram_heap(); + static void _usbisr(void); }; diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp index 8a7b0bf0ab..7b2e69ce3b 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp @@ -18,6 +18,8 @@ #if DEVICE_USBDEVICE #include "USBPhyHw.h" +#include "tinyalloc.h" +#include "mbed_math_helpers.h" #ifdef __cplusplus extern "C" { @@ -48,6 +50,9 @@ extern "C" { static USBPhyHw *instance; +// Per RP2350 manual section 12.7.3.7.3, DPRAM buffers must be 64 byte aligned. +constexpr size_t USB_BUFFER_ALIGN = 64; + USBPhy *get_usb_phy() { static USBPhyHw usbphy; @@ -68,7 +73,6 @@ void USBPhyHw::init(USBPhyEvents *events) { this->events = events; this->new_addr = 0; - this->open_endpoints = 0; instance = this; // Disable IRQ @@ -80,6 +84,7 @@ void USBPhyHw::init(USBPhyEvents *events) // Clear any previous state in dpram just in case memset(usb_dpram, 0, sizeof(*usb_dpram)); + reinit_dpram_heap(); // Mux the controller to the onboard usb phy usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; @@ -130,6 +135,7 @@ void USBPhyHw::disconnect() { // Clear all endpoint interrupts and disable interrupts memset(&usb_dpram->ep_ctrl[0], 0, sizeof(*usb_dpram) - sizeof(usb_dpram->setup_packet)); + reinit_dpram_heap(); // TODO - Disable pullup on D+ usb_hw_clear->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; @@ -172,25 +178,29 @@ void USBPhyHw::remote_wakeup() const usb_ep_table_t *USBPhyHw::endpoint_table() { + // Struct sizes for tinyalloc structures. These should be constant for any 32 bit machines + constexpr size_t TA_HEAP_SIZE = 16; // Base heap structure + constexpr size_t TA_BLOCK_SIZE = 12; + static const usb_ep_table_t template_table = { - sizeof(usb_dpram->epx_data), + sizeof(usb_dpram->epx_data) - TA_HEAP_SIZE, { - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, - {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, 0}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, + {USB_EP_ATTR_ALLOW_ALL | USB_EP_ATTR_DIR_IN_AND_OUT, 1, TA_BLOCK_SIZE + USB_BUFFER_ALIGN}, } }; return &template_table; @@ -277,22 +287,23 @@ bool USBPhyHw::endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_ int in = endpoint >> 7; io_rw_32 * ep_ctrl = in ? &usb_dpram->ep_ctrl[ep_num - 1].in : &usb_dpram->ep_ctrl[ep_num - 1].out; - if(this->dpram_buffer_free_ptr + max_packet < sizeof(usb_dpram->epx_data) ) + + auto * const dpram_buffer_base = reinterpret_cast(ta_alloc(max_packet)); + + if(dpram_buffer_base != nullptr) { endpoint_info_t * ep = in ? &this->ep_info_in[ep_num] : &this->ep_info_out[ep_num]; ep->next_pid = 0; - ep->dpram = &usb_dpram->epx_data[this->dpram_buffer_free_ptr]; + ep->dpram = static_cast(dpram_buffer_base); *ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | type << EP_CTRL_BUFFER_TYPE_LSB | EP_CTRL_INTERRUPT_ON_STALL | - (this->dpram_buffer_free_ptr + 0x180); + reinterpret_cast(ep->dpram - USBCTRL_DPRAM_BASE); - this->dpram_buffer_free_ptr += (max_packet + 63) & ~63; - ++open_endpoints; - mbed_error_printf("EP added: %hhu\n", endpoint); + // mbed_error_printf("EP added: %hhu, using buffer %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); } else { @@ -303,27 +314,41 @@ bool USBPhyHw::endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_ return true; } -void USBPhyHw::endpoint_remove(usb_ep_t endpoint) -{ - mbed_error_printf("EP removed: %hhu\n", endpoint); - --open_endpoints; +void USBPhyHw::endpoint_remove(usb_ep_t endpoint) { + int ep_num = endpoint & 0x7f; + int in = endpoint >> 7; + io_rw_32 * const ep_ctrl = in ? &usb_dpram->ep_ctrl[ep_num - 1].in : + &usb_dpram->ep_ctrl[ep_num - 1].out; + endpoint_info_t * const ep = in ? &this->ep_info_in[ep_num] : &this->ep_info_out[ep_num]; - if(open_endpoints == 0) { - mbed_error_printf("All EPs closed, freeing DPRAM\n"); - dpram_buffer_free_ptr = 0; - } + // Disable endpoint + *ep_ctrl = 0; + + // mbed_error_printf("EP removed: %hhu, buffer: %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); + + MBED_ASSERT(ta_free(ep->dpram)); } void USBPhyHw::endpoint_stall(usb_ep_t endpoint) { + mbed_error_printf("EP stalled: %hhu\n", endpoint); + + // Implemented per TinyUSB: https://github.com/hathach/tinyusb/blob/f3021b337fcea154b898489c417d428c92f88e92/src/portable/raspberrypi/rp2040/dcd_rp2040.c#L532 + endpoint_abort(endpoint); + int ep_num = endpoint & 0x7f; - usb_dpram->ep_buf_ctrl[ep_num].out |= USB_BUF_CTRL_STALL; + usb_dpram->ep_buf_ctrl[ep_num].out = USB_BUF_CTRL_STALL; } void USBPhyHw::endpoint_unstall(usb_ep_t endpoint) { + mbed_error_printf("EP unstalled: %hhu\n", endpoint); + int ep_num = endpoint & 0x7f; usb_dpram->ep_buf_ctrl[ep_num].out &= ~USB_BUF_CTRL_STALL; + + // reset data toggle + this->ep_info_out[ep_num].next_pid = 0; } bool USBPhyHw::endpoint_read(usb_ep_t endpoint, uint8_t *data, uint32_t size) @@ -336,9 +361,11 @@ bool USBPhyHw::endpoint_read(usb_ep_t endpoint, uint8_t *data, uint32_t size) usb_dpram->ep_buf_ctrl[ep_num].out = (size & USB_BUF_CTRL_LEN_MASK) | - USB_BUF_CTRL_AVAIL | (ep->next_pid ? USB_BUF_CTRL_DATA1_PID : 0); + // Per RP2350 datasheet section 12.7.3.7.4, must set AVAIL after assigning other fields in the buf ctrl register. + usb_dpram->ep_buf_ctrl[ep_num].out |= USB_BUF_CTRL_AVAIL; + ep->next_pid = !ep->next_pid; return true; @@ -387,9 +414,11 @@ bool USBPhyHw::endpoint_write(usb_ep_t endpoint, uint8_t *data, uint32_t size) usb_dpram->ep_buf_ctrl[ep_num].in = size | USB_BUF_CTRL_FULL | - USB_BUF_CTRL_AVAIL | (ep->next_pid ? USB_BUF_CTRL_DATA1_PID : 0); + // Per RP2350 datasheet section 12.7.3.7.4, must set AVAIL after assigning other fields in the buf ctrl register. + usb_dpram->ep_buf_ctrl[ep_num].in |= USB_BUF_CTRL_AVAIL; + ep->next_pid = !ep->next_pid; return true; @@ -397,7 +426,31 @@ bool USBPhyHw::endpoint_write(usb_ep_t endpoint, uint8_t *data, uint32_t size) void USBPhyHw::endpoint_abort(usb_ep_t endpoint) { + // Based on TinyUSB: https://github.com/hathach/tinyusb/blob/f3021b337fcea154b898489c417d428c92f88e92/src/portable/raspberrypi/rp2040/dcd_rp2040.c#L119 + // Abort any pending transfer + int ep_num = endpoint & 0x7f; + int in = endpoint >> 7; + const uint32_t abort_mask = 1 << (ep_num * 2 + in); + + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). + // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. + if (rp2040_chip_version() >= 2) { + usb_hw_set->abort = abort_mask; + while ((usb_hw->abort_done & abort_mask) != abort_mask) {} + } + // clear buffer control + if(in) { + usb_dpram->ep_buf_ctrl[ep_num].in = 0; + } + else { + usb_dpram->ep_buf_ctrl[ep_num].out = 0; + } + + if (rp2040_chip_version() >= 2) { + usb_hw_clear->abort_done = abort_mask; + usb_hw_clear->abort = abort_mask; + } } void USBPhyHw::process() @@ -411,8 +464,8 @@ void USBPhyHw::process() usb_hw->dev_addr_ctrl = 0; // Reset all endpoint buffers and controls (leave SETUP packet) memset(&usb_dpram->ep_ctrl[0], 0, sizeof(*usb_dpram) - sizeof(usb_dpram->setup_packet)); - this->dpram_buffer_free_ptr = 0; - open_endpoints = 0; + reinit_dpram_heap(); + // Clear the bus reset usb_hw->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; @@ -528,6 +581,12 @@ void USBPhyHw::process() NVIC_EnableIRQ(USBCTRL_IRQ_IRQn); } +void USBPhyHw::reinit_dpram_heap() { + // Initialize the heap to cover all of the DPRAM block after the EP0 data (fixed layout). + // There are 15 user-controlled endpoints, and for each endpoint we can have In and Out buffers, so there can be 15*2=30 total blocks. + ta_init(usb_dpram->epx_data, usb_dpram->epx_data + sizeof(usb_dpram->epx_data), USB_HOST_INTERRUPT_ENDPOINTS * 2, 64, USB_BUFFER_ALIGN); +} + void USBPhyHw::_usbisr(void) { NVIC_DisableIRQ(USBCTRL_IRQ_IRQn); diff --git a/targets/TARGET_RASPBERRYPI/tinyalloc b/targets/TARGET_RASPBERRYPI/tinyalloc new file mode 160000 index 0000000000..9bc8baf973 --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/tinyalloc @@ -0,0 +1 @@ +Subproject commit 9bc8baf973e24bcd6e1b8ffb09ebc84165ac2d8e diff --git a/tools/python/mbed_os_tools/test/host_tests_runner/host_test_default.py b/tools/python/mbed_os_tools/test/host_tests_runner/host_test_default.py index 1143a2a5be..b1f006e480 100644 --- a/tools/python/mbed_os_tools/test/host_tests_runner/host_test_default.py +++ b/tools/python/mbed_os_tools/test/host_tests_runner/host_test_default.py @@ -129,8 +129,11 @@ def run_test(self): def callback__notify_prn(key, value, timestamp): """! Handles __norify_prn. Prints all lines in separate log line""" - for line in value.splitlines(): - self.logger.prn_inf(line) + if isinstance(value, Exception): + self.logger.prn_err(traceback.format_exception(value)) + else: + for line in value.splitlines(): + self.logger.prn_inf(line) callbacks = {"__notify_prn": callback__notify_prn} From 03c6e604cb9c7c4e93816287880278a5d3977d30 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sat, 1 Aug 2026 21:10:44 -0700 Subject: [PATCH 3/9] Reformat --- drivers/usb/source/USBDevice.cpp | 4 ++-- drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp | 4 ++-- platform/include/platform/mbed_math_helpers.h | 2 +- targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/usb/source/USBDevice.cpp b/drivers/usb/source/USBDevice.cpp index 5deb8ce7d5..7b5372f633 100644 --- a/drivers/usb/source/USBDevice.cpp +++ b/drivers/usb/source/USBDevice.cpp @@ -1008,7 +1008,7 @@ void USBDevice::in(usb_ep_t endpoint) endpoint_info_t *info = &_endpoint_info[EP_TO_INDEX(endpoint)]; - if(info->pending == 0) { + if (info->pending == 0) { MBED_ERROR1( MBED_MAKE_ERROR( MBED_MODULE_DRIVER_USB, @@ -1462,7 +1462,7 @@ bool USBDevice::read_start(usb_ep_t endpoint, uint8_t *buffer, uint32_t max_size endpoint_info_t *info = &_endpoint_info[EP_TO_INDEX(endpoint)]; if (!(info->flags & ENDPOINT_ENABLED)) { // Only valid endpoints may be used when in the configured state - if(configured()) { + if (configured()) { MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_USB, MBED_ERROR_CODE_INVALID_OPERATION), "Endpoint read before being enabled!", endpoint); } unlock(); diff --git a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp index 57f10fe6ac..2933af9cc3 100644 --- a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp +++ b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp @@ -210,12 +210,12 @@ void USBTester::callback_set_configuration(uint8_t configuration) bool USBTester::setup_iterface(uint8_t ep_in, uint8_t ep_out, uint32_t ep_size, usb_ep_type_t ep_type, uint8_t *buf, uint32_t buf_size, void (USBTester::*callback)()) { - if(!endpoint_add(ep_in, ep_size, ep_type)) { + if (!endpoint_add(ep_in, ep_size, ep_type)) { mbed_error_printf("Warning: endpoint_add(%" PRIu8 ", %" PRIu32 ", %d) failed.\n", ep_in, ep_size, ep_type); return false; } - if(!endpoint_add(ep_out, ep_size, ep_type, callback)) { + if (!endpoint_add(ep_out, ep_size, ep_type, callback)) { mbed_error_printf("Warning: endpoint_add(%" PRIu8 ", %" PRIu32 ", %d, callback) failed.\n", ep_out, ep_size, ep_type); return false; } diff --git a/platform/include/platform/mbed_math_helpers.h b/platform/include/platform/mbed_math_helpers.h index d0b88a0494..df2b803451 100644 --- a/platform/include/platform/mbed_math_helpers.h +++ b/platform/include/platform/mbed_math_helpers.h @@ -41,7 +41,7 @@ static inline uint32_t mbed_integer_log_2(uint32_t x) /** * @brief Align an address (\c addr) to a given \c alignment by adding between 0 and \c alignment-1 bytes to it. */ -static inline void* mbed_align_up_to(void* addr, size_t alignment) +static inline void *mbed_align_up_to(void *addr, size_t alignment) { // Use integer division to divide the address down to the alignment size, which // rounds to the block before the given address. diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp index 7b2e69ce3b..a17a586b96 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp @@ -331,7 +331,7 @@ void USBPhyHw::endpoint_remove(usb_ep_t endpoint) { void USBPhyHw::endpoint_stall(usb_ep_t endpoint) { - mbed_error_printf("EP stalled: %hhu\n", endpoint); + // mbed_error_printf("EP stalled: %hhu\n", endpoint); // Implemented per TinyUSB: https://github.com/hathach/tinyusb/blob/f3021b337fcea154b898489c417d428c92f88e92/src/portable/raspberrypi/rp2040/dcd_rp2040.c#L532 endpoint_abort(endpoint); @@ -342,7 +342,7 @@ void USBPhyHw::endpoint_stall(usb_ep_t endpoint) void USBPhyHw::endpoint_unstall(usb_ep_t endpoint) { - mbed_error_printf("EP unstalled: %hhu\n", endpoint); + // mbed_error_printf("EP unstalled: %hhu\n", endpoint); int ep_num = endpoint & 0x7f; usb_dpram->ep_buf_ctrl[ep_num].out &= ~USB_BUF_CTRL_STALL; From 2ddea58543c59028b1d9ed466c8b9da76e304085 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sat, 1 Aug 2026 21:25:09 -0700 Subject: [PATCH 4/9] Fix broken grep expression --- .github/workflows/basic_checks.yml | 4 ++-- .gitmodules | 2 +- targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/basic_checks.yml b/.github/workflows/basic_checks.yml index ba285dfafa..f8821df0b6 100644 --- a/.github/workflows/basic_checks.yml +++ b/.github/workflows/basic_checks.yml @@ -40,10 +40,10 @@ jobs: mkdir -p SCANCODE git config --global --add safe.directory "$GITHUB_WORKSPACE" git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF} \ - | ( grep '.\(c\|cpp\|h\|hpp\|py\)$' || true ) + | ( grep '\.\(c\|cpp\|h\|hpp\|py\)$' || true ) echo $? git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF} \ - | ( grep '.\(c\|cpp\|h\|hpp\|py\)$' || true ) \ + | ( grep '\.\(c\|cpp\|h\|hpp\|py\)$' || true ) \ | while read file; do cp --parents "${file}" SCANCODE; done ls SCANCODE scancode -l --json-pp scancode.json SCANCODE diff --git a/.gitmodules b/.gitmodules index 097a07e411..e12d9b4ff1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,4 +30,4 @@ url = https://github.com/SEGGERMicro/RTT.git [submodule "targets/TARGET_RASPBERRYPI/tinyalloc"] path = targets/TARGET_RASPBERRYPI/tinyalloc - url = https://github.com/thi-ng/tinyalloc.git + url = https://github.com/dzyong/tinyalloc.git diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h index 61aac5e50f..5a66f29530 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhyHw.h @@ -67,7 +67,6 @@ class USBPhyHw : public USBPhy { private: USBPhyEvents *events; int new_addr; // Indicates a new device address has been chosen - uint32_t dpram_buffer_free_ptr; endpoint_info_t ep_info_in [USB_NUM_ENDPOINTS]; endpoint_info_t ep_info_out[USB_NUM_ENDPOINTS]; From 4d693b241d42a78421d1ced4888dd13829dbff99 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 2 Aug 2026 11:07:53 -0700 Subject: [PATCH 5/9] Fix target label --- .github/workflows/greentea_cmake.yml | 2 ++ targets/TARGET_RASPBERRYPI/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/greentea_cmake.yml b/.github/workflows/greentea_cmake.yml index 2455dbb582..deb4427c72 100644 --- a/.github/workflows/greentea_cmake.yml +++ b/.github/workflows/greentea_cmake.yml @@ -123,6 +123,8 @@ jobs: # Raspberry Pi MCUs - target: RASPBERRY_PI_PICO profile: full + - target: RASPBERRY_PI_PICO_2 + profile: full steps: - name: Checkout diff --git a/targets/TARGET_RASPBERRYPI/CMakeLists.txt b/targets/TARGET_RASPBERRYPI/CMakeLists.txt index b9fa54f98e..f7c7714156 100644 --- a/targets/TARGET_RASPBERRYPI/CMakeLists.txt +++ b/targets/TARGET_RASPBERRYPI/CMakeLists.txt @@ -187,7 +187,7 @@ elseif(MCU_RP235X IN_LIST MBED_TARGET_LABELS) endif() # tinyalloc, used for managing USB ram -mbed_setup_submodule(tinyalloc CHECK_FILE tinyalloc.c IF_LABEL TARGET_MCU_RP2) +mbed_setup_submodule(tinyalloc CHECK_FILE tinyalloc.c IF_LABEL MCU_RP2) target_sources(mbed-mcu-rp2 INTERFACE tinyalloc/tinyalloc.c) From 298cce9e811b2ae4dd6197c0c92e4c78148e4181 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 2 Aug 2026 12:51:23 -0700 Subject: [PATCH 6/9] README updates --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37a6185c79..3a4ae46052 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,12 @@ Additionally, the [discussions page](https://github.com/mbed-ce/mbed-os/discussi ## Documentation ### Mbed CE Docs -For the Mbed CE code-level documentation (Doxygen), see [here](https://mbed-ce.github.io/mbed-os/group__mbed-os-public.html). For an auto-generated listing of target boards supported by Mbed, and the drivers & features each target supports, see the [targets index here](https://mbed-ce.github.io/mbed-ce-test-tools/targets/) and the [drivers index here](https://mbed-ce.github.io/mbed-ce-test-tools/drivers/). +Our website has several [how-to-use guides](https://mbed-ce.dev/how-to-use/io-basics/) that will get you started using core Mbed OS concepts, and we are working on more over time. For the Mbed CE code-level documentation (Doxygen), see [here](https://mbed-ce.github.io/mbed-os/group__mbed-os-public.html). For an auto-generated listing of target boards supported by Mbed, and the drivers & features each target supports, see the [targets index here](https://mbed-ce.github.io/mbed-ce-test-tools/targets/) and the [drivers index here](https://mbed-ce.github.io/mbed-ce-test-tools/drivers/). Mbed CE is still working on more complete documentation infrastructure. Eventually, we hope to migrate all of the docs and examples as well. However, for now, see below to access those on the original Mbed OS docs site. ### Original ARM Mbed OS Docs -For more information about Mbed OS, please see [the published Mbed OS documentation](https://os.mbed.com/docs/latest). It includes general overview information, step-by-step tutorials, porting information and background reference materials about our architecture and tools. +For more information about Mbed OS, please see [the archived Mbed OS documentation](https://web.archive.org/web/20250622054924/https://os.mbed.com/docs/mbed-os/v6.16/introduction/index.html). It includes general overview information, step-by-step tutorials, porting information and background reference materials about our architecture and tools. From a1dd9e72064b209f8f01023ec492e25a20a764cf Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 2 Aug 2026 13:40:44 -0700 Subject: [PATCH 7/9] More README updates --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3a4ae46052..1f1103c47f 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,7 @@ Folders containing files under different permissive license than Apache 2.0 are To start a new project that uses Mbed CE, see the setup guide [here](https://github.com/mbed-ce/mbed-os/wiki/New-Project-Setup-Guide). -We have a [developer website](https://os.mbed.com) for asking questions, engaging with others, finding information on boards and components, using an online IDE and compiler, reading the documentation and learning about what's new and what's coming next in Mbed OS. - -Additionally, the [discussions page](https://github.com/mbed-ce/mbed-os/discussions) on this repo can be used for proposing and discussing specific code changes. +If you'd like to talk to the Mbed CE devs, the [discussions page](https://github.com/mbed-ce/mbed-os/discussions) on this repo can be used to ask for help or to propose and discuss code changes. ## Documentation From 101a4c94b6928c6d44c9e1b394a49f76b7b7de5a Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 3 Aug 2026 23:39:31 -0700 Subject: [PATCH 8/9] Respond to comments --- .../TESTS/usb_device/basic/USBTester.cpp | 6 ++- .../TARGET_MCU_RP2/USBPhy_RP2.cpp | 37 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp index 2933af9cc3..382ffe5ad8 100644 --- a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp +++ b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp @@ -40,6 +40,8 @@ #define EVENT_READY (1 << 0) +using namespace std::chrono_literals; + USBTester::USBTester(USBPhy *phy, uint16_t vendor_id, uint16_t product_id, uint16_t product_release): USBDevice(phy, vendor_id, product_id, product_release), interface_0_alt_set(NONE), interface_1_alt_set(NONE), configuration_set(NONE), reset_count(0), @@ -58,8 +60,10 @@ USBTester::USBTester(USBPhy *phy, uint16_t vendor_id, uint16_t product_id, uint1 ctrl_buf = new uint8_t[CTRL_BUF_SIZE]; init(); USBDevice::connect(); - flags.wait_any(EVENT_READY, osWaitForever, false); + if(flags.wait_any_for(EVENT_READY, 5s, false) & osFlagsError) { + printf("WARNING: USB device never went into CONFIGURED state!\n"); + } } USBTester::~USBTester() diff --git a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp index a17a586b96..a3e1ed3e54 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp +++ b/targets/TARGET_RASPBERRYPI/TARGET_MCU_RP2/USBPhy_RP2.cpp @@ -48,6 +48,9 @@ extern "C" { #define usb_hw_set hw_set_alias(usb_hw) #define usb_hw_clear hw_clear_alias(usb_hw) +// Change to 1 to print additional debug information +#define RP2_USB_DEBUG 0 + static USBPhyHw *instance; // Per RP2350 manual section 12.7.3.7.3, DPRAM buffers must be 64 byte aligned. @@ -303,7 +306,9 @@ bool USBPhyHw::endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_ EP_CTRL_INTERRUPT_ON_STALL | reinterpret_cast(ep->dpram - USBCTRL_DPRAM_BASE); - // mbed_error_printf("EP added: %hhu, using buffer %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); +#if RP2_USB_DEBUG + mbed_error_printf("EP added: %hhu, using buffer %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); +#endif } else { @@ -324,31 +329,47 @@ void USBPhyHw::endpoint_remove(usb_ep_t endpoint) { // Disable endpoint *ep_ctrl = 0; - // mbed_error_printf("EP removed: %hhu, buffer: %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); +#if RP2_USB_DEBUG + mbed_error_printf("EP removed: %hhu, buffer: %p\n", endpoint, ep->dpram - USBCTRL_DPRAM_BASE); +#endif - MBED_ASSERT(ta_free(ep->dpram)); + const bool free_result = ta_free(ep->dpram); + MBED_ASSERT(free_result); } void USBPhyHw::endpoint_stall(usb_ep_t endpoint) { - // mbed_error_printf("EP stalled: %hhu\n", endpoint); +#if RP2_USB_DEBUG + mbed_error_printf("EP stalled: %hhu\n", endpoint); +#endif // Implemented per TinyUSB: https://github.com/hathach/tinyusb/blob/f3021b337fcea154b898489c417d428c92f88e92/src/portable/raspberrypi/rp2040/dcd_rp2040.c#L532 endpoint_abort(endpoint); int ep_num = endpoint & 0x7f; - usb_dpram->ep_buf_ctrl[ep_num].out = USB_BUF_CTRL_STALL; + int in = endpoint >> 7; + io_rw_32 * const ep_buf_ctrl = in ? &usb_dpram->ep_buf_ctrl[ep_num - 1].in : + &usb_dpram->ep_buf_ctrl[ep_num - 1].out; + + *ep_buf_ctrl = USB_BUF_CTRL_STALL; } void USBPhyHw::endpoint_unstall(usb_ep_t endpoint) { - // mbed_error_printf("EP unstalled: %hhu\n", endpoint); +#if RP2_USB_DEBUG + mbed_error_printf("EP unstalled: %hhu\n", endpoint); +#endif int ep_num = endpoint & 0x7f; - usb_dpram->ep_buf_ctrl[ep_num].out &= ~USB_BUF_CTRL_STALL; + int in = endpoint >> 7; + io_rw_32 * const ep_buf_ctrl = in ? &usb_dpram->ep_buf_ctrl[ep_num - 1].in : + &usb_dpram->ep_buf_ctrl[ep_num - 1].out; + + *ep_buf_ctrl &= ~USB_BUF_CTRL_STALL; // reset data toggle - this->ep_info_out[ep_num].next_pid = 0; + endpoint_info_t * const ep = in ? &this->ep_info_in[ep_num] : &this->ep_info_out[ep_num]; + ep->next_pid = 0; } bool USBPhyHw::endpoint_read(usb_ep_t endpoint, uint8_t *data, uint32_t size) From ac714dd745a4b034d05b793a8f0f1fef8cc53f27 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Wed, 5 Aug 2026 09:00:20 -0700 Subject: [PATCH 9/9] Reformat --- drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp | 2 +- run-formatter.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp index 382ffe5ad8..64340858c5 100644 --- a/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp +++ b/drivers/usb/tests/TESTS/usb_device/basic/USBTester.cpp @@ -61,7 +61,7 @@ USBTester::USBTester(USBPhy *phy, uint16_t vendor_id, uint16_t product_id, uint1 init(); USBDevice::connect(); - if(flags.wait_any_for(EVENT_READY, 5s, false) & osFlagsError) { + if (flags.wait_any_for(EVENT_READY, 5s, false) & osFlagsError) { printf("WARNING: USB device never went into CONFIGURED state!\n"); } } diff --git a/run-formatter.sh b/run-formatter.sh index 14882fadb2..2cc13e2ea2 100755 --- a/run-formatter.sh +++ b/run-formatter.sh @@ -6,7 +6,7 @@ # Script to run the Astyle formatter on the Mbed OS code base. # Run before submitting code changes! -git diff --name-only --diff-filter=d origin/master \ +git diff --name-only --diff-filter=d origin/main \ | ( grep '.*\.\(c\|cpp\|h\|hpp\)$' || true ) \ | ( grep -v -f .codecheckignore || true ) \ | while read file; do astyle -n --options=.astylerc "${file}"; done \ No newline at end of file