From c5f1cdfb643e12363aa8d40de2a97f9d7663b58d Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Tue, 20 Aug 2024 13:40:19 +0000 Subject: [PATCH] tests: add gs_usb test suite Add initial test suite covering the control transfers of the gs_usb USB device class implementation. Signed-off-by: Henrik Brix Andersen --- 99-cannectivity.rules | 3 + scripts/requirements-run-test.txt | 1 + scripts/requirements.txt | 1 + tests/subsys/usb/gs_usb/host/CMakeLists.txt | 9 + tests/subsys/usb/gs_usb/host/Kconfig | 59 +++ tests/subsys/usb/gs_usb/host/README.rst | 27 ++ tests/subsys/usb/gs_usb/host/app.overlay | 33 ++ tests/subsys/usb/gs_usb/host/prj.conf | 21 + .../subsys/usb/gs_usb/host/prj_usbd_next.conf | 21 + .../subsys/usb/gs_usb/host/pytest/conftest.py | 80 ++++ tests/subsys/usb/gs_usb/host/pytest/gs_usb.py | 413 ++++++++++++++++++ .../usb/gs_usb/host/pytest/test_gs_usb.py | 195 +++++++++ tests/subsys/usb/gs_usb/host/src/main.c | 198 +++++++++ tests/subsys/usb/gs_usb/host/src/shell.c | 41 ++ tests/subsys/usb/gs_usb/host/src/test.h | 12 + tests/subsys/usb/gs_usb/host/src/usb.c | 161 +++++++ tests/subsys/usb/gs_usb/host/testcase.yaml | 22 + 17 files changed, 1297 insertions(+) create mode 100644 scripts/requirements-run-test.txt create mode 100644 scripts/requirements.txt create mode 100644 tests/subsys/usb/gs_usb/host/CMakeLists.txt create mode 100644 tests/subsys/usb/gs_usb/host/Kconfig create mode 100644 tests/subsys/usb/gs_usb/host/README.rst create mode 100644 tests/subsys/usb/gs_usb/host/app.overlay create mode 100644 tests/subsys/usb/gs_usb/host/prj.conf create mode 100644 tests/subsys/usb/gs_usb/host/prj_usbd_next.conf create mode 100644 tests/subsys/usb/gs_usb/host/pytest/conftest.py create mode 100644 tests/subsys/usb/gs_usb/host/pytest/gs_usb.py create mode 100644 tests/subsys/usb/gs_usb/host/pytest/test_gs_usb.py create mode 100644 tests/subsys/usb/gs_usb/host/src/main.c create mode 100644 tests/subsys/usb/gs_usb/host/src/shell.c create mode 100644 tests/subsys/usb/gs_usb/host/src/test.h create mode 100644 tests/subsys/usb/gs_usb/host/src/usb.c create mode 100644 tests/subsys/usb/gs_usb/host/testcase.yaml diff --git a/99-cannectivity.rules b/99-cannectivity.rules index a3c885b..742a6c2 100644 --- a/99-cannectivity.rules +++ b/99-cannectivity.rules @@ -13,4 +13,7 @@ ACTION!="add", SUBSYSTEM!="usb_device", GOTO="cannectivity_rules_end" ATTR{idVendor}=="1209", ATTR{idProduct}=="ca01", RUN+="/sbin/modprobe -b gs_usb" MODE="660", GROUP="plugdev", TAG+="uaccess" SUBSYSTEM=="drivers", ENV{DEVPATH}=="/bus/usb/drivers/gs_usb", ATTR{new_id}="1209 ca01" +# Used for pytest suites +ATTR{idVendor}=="1209", ATTR{idProduct}=="0001", MODE="660", GROUP="plugdev", TAG+="uaccess" + LABEL="cannectivity_rules_end" diff --git a/scripts/requirements-run-test.txt b/scripts/requirements-run-test.txt new file mode 100644 index 0000000..6513d5e --- /dev/null +++ b/scripts/requirements-run-test.txt @@ -0,0 +1 @@ +pyusb diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..920dd0e --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1 @@ +-r requirements-run-test.txt diff --git a/tests/subsys/usb/gs_usb/host/CMakeLists.txt b/tests/subsys/usb/gs_usb/host/CMakeLists.txt new file mode 100644 index 0000000..8897cbe --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(host) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/usb/gs_usb/host/Kconfig b/tests/subsys/usb/gs_usb/host/Kconfig new file mode 100644 index 0000000..f6d6c78 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/Kconfig @@ -0,0 +1,59 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +menu "Test suite for gs_usb" + +config TEST_USB_MANUFACTURER + string "USB device manufacturer string" + default "CANnectivity" + help + USB device manufacturer string. + +config TEST_USB_PRODUCT + string "USB device product string" + default "CANnectivity gs_usb Test Suite" + help + USB product string. + +config TEST_USB_VID + hex "USB device Vendor ID (VID)" + default 0x1209 + help + USB device Vendor ID (VID). + +config TEST_USB_PID + hex "USB device Product ID (PID)" + default 0x0001 + help + USB device Product ID (PID). + +config TEST_USB_MAX_POWER + int "USB device maximum power" + default 125 + range 0 250 + help + USB maximum current draw in milliampere (mA) divided by 2. + A value of 125 results in a maximum current draw value of 250 mA. + +if USB_DEVICE_STACK + +configdefault USB_DEVICE_MANUFACTURER + default TEST_USB_MANUFACTURER + +configdefault USB_DEVICE_PRODUCT + default TEST_USB_PRODUCT + +configdefault USB_DEVICE_VID + default TEST_USB_VID + +configdefault USB_DEVICE_PID + default TEST_USB_PID + +configdefault USB_MAX_POWER + default TEST_USB_MAX_POWER + +endif # USB_DEVICE_STACK + +endmenu + +source "Kconfig.zephyr" diff --git a/tests/subsys/usb/gs_usb/host/README.rst b/tests/subsys/usb/gs_usb/host/README.rst new file mode 100644 index 0000000..052a12c --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/README.rst @@ -0,0 +1,27 @@ +Geschwister Schneider USB/CAN Protocol Tests +############################################ + +Overview +******** + +This test suite uses `PyUSB`_ for testing the Geschwister Schneider USB/CAN protocol implementation. + +Prerequisites +************* + +The test suite has the following prerequisites: + +* The PyUSB library must be installed on the host PC. +* The DUT must be connected to the host PC via USB. + +Building and Running +******************** + +Below is an example for running the test suite on the ``frdm_k64f`` board: + +.. code-block:: shell + + west twister -v -p frdm_k64f/mk64f12 --device-testing --device-serial /dev/ttyACM0 -X usb -T tests/subsys/usb/gs_usb/host/ + +.. _PyUSB: + https://pyusb.github.io/pyusb/ diff --git a/tests/subsys/usb/gs_usb/host/app.overlay b/tests/subsys/usb/gs_usb/host/app.overlay new file mode 100644 index 0000000..2cc3c82 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/app.overlay @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-2024 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + fake_can0: fake_can0 { + status = "okay"; + compatible = "zephyr,fake-can"; + }; + + fake_can1: fake_can1 { + status = "okay"; + compatible = "zephyr,fake-can"; + }; + + can_loopback0: can_loopback0 { + status = "okay"; + compatible = "zephyr,can-loopback"; + }; + + can_loopback1: can_loopback1 { + status = "okay"; + compatible = "zephyr,can-loopback"; + }; +}; + +&zephyr_udc0 { + gs_usb0: gs_usb0 { + compatible = "gs_usb"; + }; +}; diff --git a/tests/subsys/usb/gs_usb/host/prj.conf b/tests/subsys/usb/gs_usb/host/prj.conf new file mode 100644 index 0000000..13f778e --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/prj.conf @@ -0,0 +1,21 @@ +CONFIG_TEST=y + +CONFIG_LOG=y +CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_GS_USB_LOG_LEVEL_DBG=y + +CONFIG_SHELL=y + +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_BOS=y +CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n +CONFIG_USB_COMPOSITE_DEVICE=y + +CONFIG_CAN=y +CONFIG_CAN_FD_MODE=y + +CONFIG_USB_DEVICE_GS_USB_MAX_CHANNELS=4 +CONFIG_USB_DEVICE_GS_USB_IDENTIFICATION=y +CONFIG_USB_DEVICE_GS_USB_TIMESTAMP=y +CONFIG_USB_DEVICE_GS_USB_TERMINATION=y diff --git a/tests/subsys/usb/gs_usb/host/prj_usbd_next.conf b/tests/subsys/usb/gs_usb/host/prj_usbd_next.conf new file mode 100644 index 0000000..adeb109 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/prj_usbd_next.conf @@ -0,0 +1,21 @@ +CONFIG_TEST=y +# Some usbd_next drivers suffer from calling mutexes in ISR context. Disable asserts until fixed +# upstream. +#CONFIG_ASSERT=n + +CONFIG_LOG=y +CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y +CONFIG_USBD_LOG_LEVEL_WRN=y +CONFIG_USBD_GS_USB_LOG_LEVEL_DBG=y + +CONFIG_SHELL=y + +CONFIG_USB_DEVICE_STACK_NEXT=y + +CONFIG_CAN=y +CONFIG_CAN_FD_MODE=y + +CONFIG_USBD_GS_USB_MAX_CHANNELS=4 +CONFIG_USBD_GS_USB_IDENTIFICATION=y +CONFIG_USBD_GS_USB_TIMESTAMP=y +CONFIG_USBD_GS_USB_TERMINATION=y diff --git a/tests/subsys/usb/gs_usb/host/pytest/conftest.py b/tests/subsys/usb/gs_usb/host/pytest/conftest.py new file mode 100644 index 0000000..57cec02 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/pytest/conftest.py @@ -0,0 +1,80 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +""" +Configuration of gs_usb test suite. +""" + +import re +import logging +import time +import pytest + +from twister_harness import DeviceAdapter, Shell +from gs_usb import GsUSB + +logger = logging.getLogger(__name__) + +def pytest_addoption(parser) -> None: + """Add local parser options to pytest.""" + parser.addoption('--usb-delay', default=5, + help='Delay to wait for USB enumeration after flashing (default: 5 seconds)') + parser.addoption('--usb-sn', default=None, + help='USB serial number of the DUT (default: None)') + +@pytest.fixture(name='usb_vid', scope='module') +def fixture_usb_vid(shell: Shell) -> int: + """Return the USB VID used by the DUT.""" + regex = re.compile(r'USB VID:\s+(\S+)') + lines = shell.get_filtered_output(shell.exec_command('gs_usb vid')) + + for line in lines: + m = regex.match(line) + if m: + vid = int(m.groups()[0], 16) + return vid + + pytest.fail('USB VID not found') + return 0x0000 + +@pytest.fixture(name='usb_pid', scope='module') +def fixture_usb_pid(shell: Shell) -> int: + """Return the USB PID used by the DUT.""" + regex = re.compile(r'USB PID:\s+(\S+)') + lines = shell.get_filtered_output(shell.exec_command('gs_usb pid')) + + for line in lines: + m = regex.match(line) + if m: + pid = int(m.groups()[0], 16) + return pid + + pytest.fail('USB PID not found') + return 0x0000 + +@pytest.fixture(name='usb_sn', scope='module') +def fixture_usb_sn(request, dut: DeviceAdapter) -> str: + """Return the USB serial number used by the DUT.""" + + sn = request.config.getoption('--usb-sn') + + if sn is None: + for fixture in dut.device_config.fixtures: + if fixture.startswith('usb:'): + sn = fixture.split(sep=':', maxsplit=1)[1] + break + + if sn is not None: + logger.info('using USB S/N: "%s"', sn) + + return sn + +@pytest.fixture(name='dev', scope='module') +def fixture_gs_usb(request, usb_vid: int, usb_pid: int, usb_sn: str) -> GsUSB: + """Return gs_usb instance for testing""" + + delay = request.config.getoption('--usb-delay') + logger.info('Waiting %d seconds for USB enumeration...', delay) + time.sleep(delay) + + return GsUSB(usb_vid, usb_pid, usb_sn) diff --git a/tests/subsys/usb/gs_usb/host/pytest/gs_usb.py b/tests/subsys/usb/gs_usb/host/pytest/gs_usb.py new file mode 100644 index 0000000..3af460b --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/pytest/gs_usb.py @@ -0,0 +1,413 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +""" +Utility class implementing the gs_usb protocol. +""" + +import logging +import struct + +from dataclasses import dataclass, astuple +from enum import IntEnum, IntFlag + +import usb.core +import usb.util + +logger = logging.getLogger(__name__) + +class GsUSBDeviceNotFound(Exception): + """ + Geschwister Schneider USB/CAN device not found. + """ + +class GsUSBbRequest(IntEnum): + """ + Geschwister Schneider USB/CAN protocol bRequest types. + """ + # Host format (little endian vs. big endian) + HOST_FORMAT = 0 + # Set CAN channel bit timing (CAN classic) + BITTIMING = 1 + # Set CAN channel operational mode + MODE = 2 + # CAN channel bus error (unsupported) + BERR = 3 + # Get CAN channel bit timing limits (CAN classic) + BT_CONST = 4 + # Get device configuration + DEVICE_CONFIG = 5 + # Get device hardware timestamp + TIMESTAMP = 6 + # Set CAN channel identify + IDENTIFY = 7 + # Get device user ID (unsupported) + GET_USER_ID = 8 + # Set device user ID (unsupported) + SET_USER_ID = 9 + # Set CAN channel bit timing (CAN FD data phase) + DATA_BITTIMING = 10 + # Get CAN channel bit timing limits (CAN FD) + BT_CONST_EXT = 11 + # Set CAN channel bus termination + SET_TERMINATION = 12 + # Get CAN channel bus termination + GET_TERMINATION = 13 + # Get CAN channel bus state + GET_STATE = 14 + +class GsUSBCANChannelFeature(IntFlag): + """ + Geschwister Schneider USB/CAN protocol CAN channel features. + """ + # CAN channel supports listen-onlu mode, in which it is not allowed to send dominant bits. + LISTEN_ONLY = 2**0 + # CAN channel supports in loopback mode, which it receives own frames. + LOOP_BACK = 2**1 + # CAN channel supports triple sampling mode + TRIPLE_SAMPLE = 2**2 + # CAN channel supports not retransmitting in case of lost arbitration or missing ACK. + ONE_SHOT = 2**3 + # CAN channel supports hardware timestamping of CAN frames. + HW_TIMESTAMP = 2**4 + # CAN channel supports visual identification. + IDENTIFY = 2**5 + # CAN channel supports user IDs (unsupported). + USER_ID = 2**6 + # CAN channel supports padding of host frames (unsupported). + PAD_PKTS_TO_MAX_PKT_SIZE = 2**7 + # CAN channel supports transmitting/receiving CAN FD frames. + FD = 2**8 + # CAN channel support LCP546xx specific quirks (Unused) + REQ_USB_QUIRK_LPC546XX = 2**9 + # CAN channel supports extended bit timing limits. + BT_CONST_EXT = 2**10 + # CAN channel supports configurable bus termination. + TERMINATION = 2**11 + # CAN channel supports bus error reporting (Unsupported, always enabled) + BERR_REPORTING = 2**12 + # CAN channel supports reporting of bus state. + GET_STATE = 2**13 + +class GsUSBCANChannelMode(IntEnum): + """ + Geschwister Schneider USB/CAN protocol CAN channel mode. + """ + # Reset CAN channel + RESET = 0 + # Start CAN channel + START = 1 + +class GsUSBCANChannelFlag(IntFlag): + """ + Geschwister Schneider USB/CAN protocol CAN channel flags. + """ + # CAN channel is in normal mode. + NORMAL = 0 + # CAN channel is not allowed to send dominant bits. + LISTEN_ONLY = 2**0 + # CAN channel is in loopback mode (receives own frames). + LOOP_BACK = 2**1 + # CAN channel uses triple sampling mode. + TRIPLE_SAMPLE = 2**2 + # CAN channel does not retransmit in case of lost arbitration or missing ACK + ONE_SHOT = 2**3 + # CAN channel frames are timestamped. + HW_TIMESTAMP = 2**4 + # CAN channel host frames are padded (unsupported). + PAD_PKTS_TO_MAX_PKT_SIZE = 2**7 + # CAN channel allows transmitting/receiving CAN FD frames. + FD = 2**8 + # CAN channel uses bus error reporting (unsupported, always enabled). + BERR_REPORTING = 2**12 + +class GsUSBCANChannelState(IntEnum): + """ + Geschwister Schneider USB/CAN protocol CAN channel state. + """ + # Error-active state (RX/TX error count < 96). + ERROR_ACTIVE = 0 + # Error-warning state (RX/TX error count < 128). + ERROR_WARNING = 1 + # Error-passive state (RX/TX error count < 256). + ERROR_PASSIVE = 2 + # Bus-off state (RX/TX error count >= 256). + BUS_OFF = 3 + # CAN controller is stopped and does not participate in CAN communication. + STOPPED = 4 + # CAN controller is sleeping (unused) + SLEEPING = 5 + +@dataclass +class GsUSBDeviceBTConst: # pylint: disable=too-many-instance-attributes + """ + Geschwister Schneider USB/CAN protocol CAN classic timing limits. + """ + # Supported CAN channel features. + feature: GsUSBCANChannelFeature + # CAN core clock frequency. + fclk_can: int + # Time segment 1 minimum value (tq). + tseg1_min: int + # Time segment 1 maximum value (tq). + tseg1_max: int + # Time segment 2 minimum value (tq). + tseg2_min: int + # Time segment 2 maximum value (tq). + tseg2_max: int + # Synchronisation jump width (SJW) maximum value (tq). + sjw_max: int + # Bitrate prescaler minimum value. + brp_min: int + # Bitrate prescaler maximum value. + brp_max: int + # Bitrate prescaler increment. + brp_inc: int + +@dataclass +class GsUSBDeviceBTConstExt: # pylint: disable=too-many-instance-attributes + """ + Geschwister Schneider USB/CAN protocol CAN classic extended timing limits. + """ + # Supported CAN channel features. + feature: GsUSBCANChannelFeature + # CAN core clock frequency. + fclk_can: int + # Time segment 1 minimum value (tq). + tseg1_min: int + # Time segment 1 maximum value (tq). + tseg1_max: int + # Time segment 2 minimum value (tq). + tseg2_min: int + # Time segment 2 maximum value (tq). + tseg2_max: int + # Synchronisation jump width (SJW) maximum value (tq). + sjw_max: int + # Bitrate prescaler minimum value. + brp_min: int + # Bitrate prescaler maximum value. + brp_max: int + # Bitrate prescaler increment. + brp_inc: int + # Data phase time segment 1 minimum value (tq). + dtseg1_min: int + # Data phase time segment 1 maximum value (tq). + dtseg1_max: int + # Data phase time segment 2 minimum value (tq). + dtseg2_min: int + # Data phase time segment 2 maximum value (tq). + dtseg2_max: int + # Data phase synchronisation jump width (SJW) maximum value (tq). + dsjw_max: int + # Data phase bitrate prescaler minimum value. + dbrp_min: int + # Data phase bitrate prescaler maximum value. + dbrp_max: int + # Data phase bitrate prescaler increment. + dbrp_inc: int + +@dataclass +class GsUSBDeviceBittiming: + """ + Geschwister Schneider USB/CAN protocol device bittiming. + """ + # Propagation segment (tq) */ + prop_seg: int + # Phase segment 1 (tq) */ + phase_seg1: int + # Phase segment 1 (tq) */ + phase_seg2: int + # Synchronisation jump width (tq) */ + sjw: int + # Bitrate prescaler */ + brp: int + +@dataclass +class GsUSBDeviceConfig: + """ + Geschwister Schneider USB/CAN protocol device configuration. + """ + # Number of CAN channels on the device minus 1 (a value of zero means one channel) + nchannels: int + # Device software version + sw_version: int + # Device hardware version + hw_version: int + +@dataclass +class GsUSBDeviceMode: + """ + Geschwister Schneider USB/CAN protocol CAN device mode. + """ + # CAN channel mode. + mode: int + # CAN channel flags. + flags: GsUSBCANChannelFlag + +@dataclass +class GsUSBDeviceState: + """ + Geschwister Schneider USB/CAN protocol CAN device state. + """ + # CAN channel state. + state: GsUSBCANChannelState + # CAN channel RX bus error count. + rxerr: int + # CAN channel TX bus error count. + txerr: int + +class GsUSB(): + """ + Utility class implementing the gs_usb protocol. + """ + + def __init__(self, usb_vid: int, usb_pid: int, usb_sn: str) -> None: + device = None + + if usb_sn is None: + device = usb.core.find(idVendor=usb_vid, idProduct=usb_pid) + else: + devices = usb.core.find(find_all=True, idVendor=usb_vid, idProduct=usb_pid) + for d in devices: + if d.serial_number == usb_sn: + device = d + break + + if device is None: + logger.error('USB device %04x:%04x S/N %s not found', usb_vid, usb_pid, usb_sn) + raise GsUSBDeviceNotFound + + device.set_configuration() + self.device = device + + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + # This class assumes little endian transfer format, let the device know + data = struct.pack(' None: + """Send a bittiming request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = struct.pack('<5I', *astuple(bittiming)) + length = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.BITTIMING, + wValue = ch, data_or_wLength = data) + assert length == len(data) + + def mode(self, ch: int, mode: GsUSBDeviceMode) -> None: + """Send a mode request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = struct.pack('<2I', *astuple(mode)) + length = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.MODE, + wValue = ch, data_or_wLength = data) + assert length == len(data) + + def bt_const(self, ch: int) -> GsUSBDeviceBTConst: + """Send a bt_const request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.BT_CONST, + wValue = ch, data_or_wLength = struct.calcsize('<10I')) + + return GsUSBDeviceBTConst(*struct.unpack('<10I', data)) + + def device_config(self) -> GsUSBDeviceConfig: + """Send a device_config request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.DEVICE_CONFIG, + data_or_wLength = struct.calcsize(' int: + """Send a timestamp request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.TIMESTAMP, + data_or_wLength = struct.calcsize(' None: + """Send an identify on/off request for the given CAN channel.""" + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = struct.pack(' None: + """Send a data_bittiming request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = struct.pack('<5I', *astuple(bittiming)) + length = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.DATA_BITTIMING, + wValue = ch, data_or_wLength = data) + assert length == len(data) + + def bt_const_ext(self, ch: int) -> GsUSBDeviceBTConstExt: + """Send a bt_const_ext request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.BT_CONST_EXT, + wValue = ch, data_or_wLength = struct.calcsize('<18I')) + + return GsUSBDeviceBTConstExt(*struct.unpack('<18I', data)) + + def set_termination(self, ch: int, terminate: bool) -> None: + """Send a CAN bus termination on/off request for the given CAN channel.""" + rtype = usb.util.build_request_type(usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = struct.pack(' bool: + """Send a CAN bus termination get request for the given CAN channel.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.GET_TERMINATION, + wValue = ch, data_or_wLength = struct.calcsize(' GsUSBDeviceState: + """Send a get_state request.""" + rtype = usb.util.build_request_type(usb.util.CTRL_IN, + usb.util.CTRL_TYPE_VENDOR, + usb.util.CTRL_RECIPIENT_INTERFACE) + data = self.device.ctrl_transfer(bmRequestType = rtype, + bRequest = GsUSBbRequest.GET_STATE, + wValue = ch, data_or_wLength = struct.calcsize('<3I')) + + return GsUSBDeviceState(*struct.unpack('<3I', data)) diff --git a/tests/subsys/usb/gs_usb/host/pytest/test_gs_usb.py b/tests/subsys/usb/gs_usb/host/pytest/test_gs_usb.py new file mode 100644 index 0000000..6bc923e --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/pytest/test_gs_usb.py @@ -0,0 +1,195 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +""" +Test suites for testing gs_usb +""" + +import logging +import pytest + +from gs_usb import GsUSBCANChannelFeature, GsUSBCANChannelFlag, GsUSBCANChannelMode, \ + GsUSBCANChannelState, GsUSBDeviceBittiming, GsUSBDeviceMode + +logger = logging.getLogger(__name__) + +TIMEOUT = 1.0 + +DEV_NAME = 'gs_usb0' +USER_DATA = '0x12345678' + +FAKE_CHANNELS = [ 0, 1 ] +LOOPBACK_CHANNELS = [ 2, 3 ] +NUM_CHANNELS = len(FAKE_CHANNELS + LOOPBACK_CHANNELS) + +@pytest.mark.usefixtures('dut', 'dev') +class TestGsUsbRequests(): + """ + Class for testing gs_usb requests. + """ + + def test_bittiming(self, dut, dev) -> None: + """Test the bittiming request""" + timing = GsUSBDeviceBittiming(0, 139, 20, 10, 1) + + for ch in FAKE_CHANNELS: + dev.bittiming(ch, timing) + regex = fr'fake_can{ch}: sjw = 10, prop_seg = 0, phase_seg1 = 139, phase_seg2 = 20, ' \ + 'prescaler = 1' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_mode(self, dut, dev) -> None: + """Test the mode request""" + mode = GsUSBDeviceMode(GsUSBCANChannelMode.RESET, GsUSBCANChannelFlag.NORMAL) + + for ch in FAKE_CHANNELS: + mode = GsUSBDeviceMode(GsUSBCANChannelMode.START, GsUSBCANChannelFlag.NORMAL) + dev.mode(ch, mode) + + regex = fr'fake_can{ch}: mode = 0' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + regex = fr'fake_can{ch}: start' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + regex = fr'dev = {DEV_NAME}, ch = {ch}, started = 1, user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + mode = GsUSBDeviceMode(GsUSBCANChannelMode.RESET, GsUSBCANChannelFlag.NORMAL) + dev.mode(ch, mode) + + regex = fr'fake_can{ch}: stop' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + regex = fr'dev = {DEV_NAME}, ch = {ch}, started = 0, user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_bt_const(self, dev) -> None: + """Test the bt_const request""" + for ch in range(NUM_CHANNELS): + features = GsUSBCANChannelFeature.HW_TIMESTAMP | \ + GsUSBCANChannelFeature.IDENTIFY | \ + GsUSBCANChannelFeature.FD | \ + GsUSBCANChannelFeature.BT_CONST_EXT | \ + GsUSBCANChannelFeature.TERMINATION | \ + GsUSBCANChannelFeature.GET_STATE + + btc = dev.bt_const(ch) + logger.debug('ch%d = %s', ch, btc) + + assert btc.fclk_can == 80000000 + + assert btc.tseg1_min == 2 + assert btc.tseg1_max == 256 + assert btc.tseg2_min == 2 + assert btc.tseg2_max == 128 + assert btc.sjw_max == 128 + assert btc.brp_min == 1 + assert btc.brp_max == 32 + assert btc.brp_inc == 1 + + if ch in LOOPBACK_CHANNELS: + features |= GsUSBCANChannelFeature.LOOP_BACK + + assert btc.feature == features + + def test_device_config(self, dev) -> None: + """Test the device_config request""" + cfg = dev.device_config() + assert cfg.nchannels == NUM_CHANNELS - 1 + assert cfg.sw_version == 2 + assert cfg.hw_version == 1 + + def test_timestamp(self, dut, dev) -> None: + """test the timestamp request""" + timestamp = dev.timestamp() + regex = fr'dev = {DEV_NAME}, timestamp = 0x{timestamp:08x}, user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_identify(self, dut, dev) -> None: + """Test the identify request""" + for ident in (False, True): + for ch in range(NUM_CHANNELS): + dev.identify(ch, ident) + regex = fr'dev = {DEV_NAME}, ch = {ch}, identify = {int(ident)}, ' \ + fr'user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_data_bittiming(self, dut, dev) -> None: + """Test the data_bittiming request""" + timing = GsUSBDeviceBittiming(0, 14, 5, 2, 1) + + for ch in FAKE_CHANNELS: + dev.data_bittiming(ch, timing) + regex = fr'fake_can{ch}: sjw = 2, prop_seg = 0, phase_seg1 = 14, phase_seg2 = 5, ' \ + 'prescaler = 1' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_bt_const_ext(self, dev) -> None: + """Test the bt_const_ext request""" + for ch in range(NUM_CHANNELS): + features = GsUSBCANChannelFeature.HW_TIMESTAMP | \ + GsUSBCANChannelFeature.IDENTIFY | \ + GsUSBCANChannelFeature.FD | \ + GsUSBCANChannelFeature.BT_CONST_EXT | \ + GsUSBCANChannelFeature.TERMINATION | \ + GsUSBCANChannelFeature.GET_STATE + + btce = dev.bt_const_ext(ch) + logger.debug('ch%d = %s', ch, btce) + + assert btce.fclk_can == 80000000 + + assert btce.tseg1_min == 2 + assert btce.tseg1_max == 256 + assert btce.tseg2_min == 2 + assert btce.tseg2_max == 128 + assert btce.sjw_max == 128 + assert btce.brp_min == 1 + assert btce.brp_max == 32 + assert btce.brp_inc == 1 + + assert btce.dtseg1_min == 1 + assert btce.dtseg1_max == 32 + assert btce.dtseg2_min == 1 + assert btce.dtseg2_max == 16 + assert btce.dsjw_max == 16 + assert btce.dbrp_min == 1 + assert btce.dbrp_max == 32 + assert btce.dbrp_inc == 1 + + if ch in LOOPBACK_CHANNELS: + features |= GsUSBCANChannelFeature.LOOP_BACK + + assert btce.feature == features + + def test_set_termination(self, dut, dev) -> None: + """Test the set_termination request""" + for term in (False, True): + for ch in range(NUM_CHANNELS): + dev.set_termination(ch, term) + regex = fr'dev = {DEV_NAME}, ch = {ch}, terminate = {int(term)}, ' \ + fr'user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_get_termination(self, dut, dev) -> None: + """Test the get_termination request""" + for ch in range(NUM_CHANNELS): + term = int(dev.get_termination(ch)) + regex = fr'dev = {DEV_NAME}, ch = {ch}, terminated = {term}, user_data = {USER_DATA}' + dut.readlines_until(regex=regex, timeout=TIMEOUT) + + def test_get_state(self, dev) -> None: + """Test the get_state request""" + for ch in range(NUM_CHANNELS): + state = dev.get_state(ch) + logger.debug('ch%d = %s', ch, state) + + if ch in FAKE_CHANNELS: + assert state.state == GsUSBCANChannelState.ERROR_PASSIVE + assert state.rxerr == 96 + assert state.txerr == 128 + else: + assert state.state == GsUSBCANChannelState.STOPPED + assert state.rxerr == 0 + assert state.txerr == 0 diff --git a/tests/subsys/usb/gs_usb/host/src/main.c b/tests/subsys/usb/gs_usb/host/src/main.c new file mode 100644 index 0000000..5027769 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/src/main.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2024 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "test.h" + +LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); + +DEFINE_FFF_GLOBALS; + +#define USER_DATA 0x12345678 +#define TIMESTAMP 0xdeadbeef + +static int fake_can_get_capabilities_delegate(const struct device *dev, can_mode_t *cap) +{ + *cap = CAN_MODE_NORMAL | CAN_MODE_FD; + + return 0; +} + +static int fake_can_get_state_delegate(const struct device *dev, enum can_state *state, + struct can_bus_err_cnt *err_cnt) +{ + *state = CAN_STATE_ERROR_PASSIVE; + + err_cnt->tx_err_cnt = 128; + err_cnt->rx_err_cnt = 96; + + return 0; +} + +static int fake_can_set_timing_delegate(const struct device *dev, const struct can_timing *timing) +{ + LOG_DBG("%s: sjw = %u, prop_seg = %u, phase_seg1 = %u, phase_seg2 = %u, " + "prescaler = %u", dev->name, timing->sjw, timing->prop_seg, timing->phase_seg1, + timing->phase_seg2, timing->prescaler); + + return 0; +} + +static int fake_can_set_timing_data_delegate(const struct device *dev, + const struct can_timing *timing_data) +{ + LOG_DBG("%s: sjw = %u, prop_seg = %u, phase_seg1 = %u, phase_seg2 = %u, " + "prescaler = %u", dev->name, timing_data->sjw, timing_data->prop_seg, + timing_data->phase_seg1, timing_data->phase_seg2, timing_data->prescaler); + + return 0; +} + +static int fake_can_start_delegate(const struct device *dev) +{ + LOG_DBG("%s: start", dev->name); + + return 0; +} + +static int fake_can_stop_delegate(const struct device *dev) +{ + LOG_DBG("%s: stop", dev->name); + + return 0; +} + +static int fake_can_set_mode_delegate(const struct device *dev, can_mode_t mode) +{ + LOG_DBG("%s: mode = 0x%08x", dev->name, mode); + + return 0; +} + +static int identify_cb(const struct device *dev, uint16_t ch, bool identify, void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + LOG_DBG("dev = %s, ch = %u, identify = %u, user_data = 0x%08x", dev->name, ch, identify, + ud); + + return 0; +} + +static int state_cb(const struct device *dev, uint16_t ch, bool started, void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + LOG_DBG("dev = %s, ch = %u, started = %u, user_data = 0x%08x", dev->name, ch, started, ud); + + return 0; +} + +static int activity_cb(const struct device *dev, uint16_t ch, void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + LOG_DBG("dev = %s, ch = %u, user_data = 0x%08x", dev->name, ch, ud); + + return 0; +} + +static int set_termination_cb(const struct device *dev, uint16_t ch, bool terminate, + void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + LOG_DBG("dev = %s, ch = %u, terminate = %u, user_data = 0x%08x", dev->name, ch, terminate, + ud); + + return 0; +} + +static int get_termination_cb(const struct device *dev, uint16_t ch, bool *terminated, + void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + *terminated = (ch % 2 == 0) ? true : false; + LOG_DBG("dev = %s, ch = %u, terminated = %u, user_data = 0x%08x", dev->name, ch, + *terminated, ud); + + return 0; +} + +static int timestamp_get_cb(const struct device *dev, uint32_t *timestamp, void *user_data) +{ + uint32_t ud = POINTER_TO_UINT(user_data); + + *timestamp = TIMESTAMP; + LOG_DBG("dev = %s, timestamp = 0x%08x, user_data = 0x%08x", dev->name, *timestamp, ud); + + return 0; +} + +static const struct gs_usb_ops gs_usb_ops = { + .timestamp = timestamp_get_cb, + .identify = identify_cb, + .state = state_cb, + .activity = activity_cb, + .set_termination = set_termination_cb, + .get_termination = get_termination_cb, +}; + +int main(void) +{ + const struct device *gs_usb = DEVICE_DT_GET(DT_NODELABEL(gs_usb0)); + const struct device *channels[] = { + DEVICE_DT_GET(DT_NODELABEL(fake_can0)), + DEVICE_DT_GET(DT_NODELABEL(fake_can1)), + DEVICE_DT_GET(DT_NODELABEL(can_loopback0)), + DEVICE_DT_GET(DT_NODELABEL(can_loopback1)), + }; + int err; + int i; + + fake_can_get_capabilities_fake.custom_fake = fake_can_get_capabilities_delegate; + fake_can_get_state_fake.custom_fake = fake_can_get_state_delegate; + fake_can_set_timing_fake.custom_fake = fake_can_set_timing_delegate; + fake_can_set_timing_data_fake.custom_fake = fake_can_set_timing_data_delegate; + fake_can_start_fake.custom_fake = fake_can_start_delegate; + fake_can_stop_fake.custom_fake = fake_can_stop_delegate; + fake_can_set_mode_fake.custom_fake = fake_can_set_mode_delegate; + + if (!device_is_ready(gs_usb)) { + LOG_ERR("gs_usb USB device not ready"); + return 0; + } + + for (i = 0; i < ARRAY_SIZE(channels); i++) { + if (!device_is_ready(channels[i])) { + LOG_ERR("CAN controller channel %d not ready", i); + return 0; + } + } + + err = gs_usb_register(gs_usb, channels, ARRAY_SIZE(channels), &gs_usb_ops, + UINT_TO_POINTER(USER_DATA)); + if (err != 0U) { + LOG_ERR("failed to register gs_usb (err %d)", err); + return 0; + } + + err = test_usb_init(); + if (err != 0) { + LOG_ERR("failed to initialize USB (err %d)", err); + return 0; + } +} diff --git a/tests/subsys/usb/gs_usb/host/src/shell.c b/tests/subsys/usb/gs_usb/host/src/shell.c new file mode 100644 index 0000000..f366f9c --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/src/shell.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +static int cmd_gs_usb_vid(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "USB VID: 0x%04x", CONFIG_TEST_USB_VID); + + return 0; +} + +static int cmd_gs_usb_pid(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "USB PID: 0x%04x", CONFIG_TEST_USB_PID); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_gs_usb_cmds, + SHELL_CMD(vid, NULL, + "Get USB VID\n" + "Usage: gs_usb vid", + cmd_gs_usb_vid), + SHELL_CMD(pid, NULL, + "Get USB PID\n" + "Usage: gs_usb pid", + cmd_gs_usb_pid), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_REGISTER(gs_usb, &sub_gs_usb_cmds, "gs_usb test commands", NULL); diff --git a/tests/subsys/usb/gs_usb/host/src/test.h b/tests/subsys/usb/gs_usb/host/src/test.h new file mode 100644 index 0000000..dd02b45 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/src/test.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ +#define CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ + +int test_usb_init(void); + +#endif /* CANNECTIVITY_TEST_SUBSYS_USB_GS_USB_TEST_H_ */ diff --git a/tests/subsys/usb/gs_usb/host/src/usb.c b/tests/subsys/usb/gs_usb/host/src/usb.c new file mode 100644 index 0000000..41f4e9d --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/src/usb.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2024 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#ifdef CONFIG_USB_DEVICE_STACK_NEXT +#include +#else /* CONFIG_USB_DEVICE_STACK_NEXT */ +#include +#include +#endif /* !CONFIG_USB_DEVICE_STACK_NEXT*/ + +#include "test.h" + +LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG); + +#ifdef CONFIG_USB_DEVICE_STACK_NEXT +#define TEST_BOS_DESC_DEFINE_CAP static +#else /* CONFIG_USB_DEVICE_STACK_NEXT */ +#define TEST_BOS_DESC_DEFINE_CAP USB_DEVICE_BOS_DESC_DEFINE_CAP +#endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ + +TEST_BOS_DESC_DEFINE_CAP const struct usb_bos_capability_lpm bos_cap_lpm = { + .bLength = sizeof(struct usb_bos_capability_lpm), + .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_BOS_CAPABILITY_EXTENSION, + .bmAttributes = 0UL, +}; + +#ifdef CONFIG_USB_DEVICE_STACK_NEXT + +USBD_DEVICE_DEFINE(usbd, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), CONFIG_TEST_USB_VID, + CONFIG_TEST_USB_PID); + +USBD_DESC_LANG_DEFINE(lang); +USBD_DESC_MANUFACTURER_DEFINE(mfr, CONFIG_TEST_USB_MANUFACTURER); +USBD_DESC_PRODUCT_DEFINE(product, CONFIG_TEST_USB_PRODUCT); +USBD_DESC_SERIAL_NUMBER_DEFINE(sn); +USBD_DESC_CONFIG_DEFINE(fs_config_desc, "Full-Speed Configuration"); +USBD_DESC_CONFIG_DEFINE(hs_config_desc, "High-Speed Configuration"); + +USBD_CONFIGURATION_DEFINE(fs_config, 0U, CONFIG_TEST_USB_MAX_POWER, &fs_config_desc); +USBD_CONFIGURATION_DEFINE(hs_config, 0U, CONFIG_TEST_USB_MAX_POWER, &hs_config_desc); + +USBD_DESC_BOS_DEFINE(usbext, sizeof(bos_cap_lpm), &bos_cap_lpm); + +static int test_usb_init_usbd(void) +{ + int err; + + err = usbd_add_descriptor(&usbd, &lang); + if (err != 0) { + LOG_ERR("failed to add language descriptor (err %d)", err); + return err; + } + + err = usbd_add_descriptor(&usbd, &mfr); + if (err != 0) { + LOG_ERR("failed to add manufacturer descriptor (err %d)", err); + return err; + } + + err = usbd_add_descriptor(&usbd, &product); + if (err != 0) { + LOG_ERR("failed to add product descriptor (%d)", err); + return err; + } + + err = usbd_add_descriptor(&usbd, &sn); + if (err != 0) { + LOG_ERR("failed to add S/N descriptor (err %d)", err); + return err; + } + + if (usbd_caps_speed(&usbd) == USBD_SPEED_HS) { + err = usbd_add_configuration(&usbd, USBD_SPEED_HS, &hs_config); + if (err != 0) { + LOG_ERR("failed to add high-speed configuration (err %d)", err); + return err; + } + + err = usbd_register_all_classes(&usbd, USBD_SPEED_HS, 1); + if (err != 0) { + LOG_ERR("failed to register high-speed classes (err %d)", err); + return err; + } + + err = usbd_device_set_code_triple(&usbd, USBD_SPEED_HS, USB_BCC_MISCELLANEOUS, 0x02, + 0x01); + if (err != 0) { + LOG_ERR("failed to set high-speed code triple (err %d)", err); + return err; + } + + err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_HS, USB_SRN_2_0_1); + if (err != 0) { + LOG_ERR("failed to set high-speed bcdUSB (err %d)", err); + return err; + } + } + + err = usbd_add_configuration(&usbd, USBD_SPEED_FS, &fs_config); + if (err != 0) { + LOG_ERR("failed to add full-speed configuration (err %d)", err); + return err; + } + + err = usbd_register_all_classes(&usbd, USBD_SPEED_FS, 1); + if (err != 0) { + LOG_ERR("failed to register full-speed classes (err %d)", err); + return err; + } + + err = usbd_device_set_code_triple(&usbd, USBD_SPEED_FS, USB_BCC_MISCELLANEOUS, 0x02, 0x01); + if (err != 0) { + LOG_ERR("failed to set full-speed code triple (err %d)", err); + return err; + } + + err = usbd_device_set_bcd_usb(&usbd, USBD_SPEED_FS, USB_SRN_2_0_1); + if (err != 0) { + LOG_ERR("failed to set full-speed bcdUSB (err %d)", err); + return err; + } + + err = usbd_add_descriptor(&usbd, &usbext); + if (err != 0) { + LOG_ERR("failed to add USB 2.0 extension descriptor (err %d)", err); + return err; + } + + err = usbd_init(&usbd); + if (err != 0) { + LOG_ERR("failed to initialize USB device support (err %d)", err); + return err; + } + + err = usbd_enable(&usbd); + if (err != 0) { + LOG_ERR("failed to enable USB device"); + return err; + } + + return 0; +} +#endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ + +int test_usb_init(void) +{ +#ifdef CONFIG_USB_DEVICE_STACK_NEXT + return test_usb_init_usbd(); +#else /* CONFIG_USB_DEVICE_STACK_NEXT */ + usb_bos_register_cap((void *)&bos_cap_lpm); + + return usb_enable(NULL); +#endif /* !CONFIG_USB_DEVICE_STACK_NEXT */ +} diff --git a/tests/subsys/usb/gs_usb/host/testcase.yaml b/tests/subsys/usb/gs_usb/host/testcase.yaml new file mode 100644 index 0000000..5852652 --- /dev/null +++ b/tests/subsys/usb/gs_usb/host/testcase.yaml @@ -0,0 +1,22 @@ +# Copyright (c) 2024 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +common: + tags: usb can + harness: pytest + harness_config: + pytest_dut_scope: session + fixture: usb + platform_exclude: + - native_sim +tests: + usb.gs_usb.host: + depends_on: usb_device can + integration_platforms: + - frdm_k64f + usb.gs_usb.host.usbd_next: + depends_on: usbd can + integration_platforms: + - frdm_k64f + extra_args: + - FILE_SUFFIX=usbd_next