diff --git a/doc/connectivity/usb/device_next/api/index.rst b/doc/connectivity/usb/device_next/api/index.rst index 34a6e068a3199..084084729c187 100644 --- a/doc/connectivity/usb/device_next/api/index.rst +++ b/doc/connectivity/usb/device_next/api/index.rst @@ -13,3 +13,4 @@ USB device support APIs usbd_msc_device.rst usbd_midi2.rst usbd_dfu.rst + usbd_mctp.rst diff --git a/doc/connectivity/usb/device_next/api/usbd_mctp.rst b/doc/connectivity/usb/device_next/api/usbd_mctp.rst new file mode 100644 index 0000000000000..c825e34b7e386 --- /dev/null +++ b/doc/connectivity/usb/device_next/api/usbd_mctp.rst @@ -0,0 +1,14 @@ +.. _usbd_mctp: + +MCTP (Management Component Transport Protocol) USB device API +############################################################# + +MCTP USB device specific API defined in :zephyr_file:`include/zephyr/usb/class/usbd_mctp.h`. + +The implementation of this device class is specified by DMTF and the spec is found `here +`__. + +API Reference +************* + +.. doxygengroup:: usbd_mctp diff --git a/doc/connectivity/usb/device_next/usb_device.rst b/doc/connectivity/usb/device_next/usb_device.rst index 3e81252602dbd..2e76bd92f2151 100644 --- a/doc/connectivity/usb/device_next/usb_device.rst +++ b/doc/connectivity/usb/device_next/usb_device.rst @@ -41,6 +41,8 @@ Samples * :zephyr:code-sample:`usb-hid-mouse` +* :zephyr:code-sample:`mctp-usb-endpoint` + * :zephyr:code-sample:`zperf` To build the sample for the device support, set the configuration overlay file ``-DDEXTRA_CONF_FILE=overlay-usbd_next_ecm.conf`` and devicetree overlay file @@ -217,6 +219,8 @@ instance (``n``) and is used as an argument to the :c:func:`usbd_register_class` +-----------------------------------+-------------------------+-------------------------+ | USB Video Class (UVC) | Video device | :samp:`uvc_{n}` | +-----------------------------------+-------------------------+-------------------------+ +| MCTP over USB Endpoint class | :ref:`usbd_mctp` | :samp:`mctp_{n}` | ++-----------------------------------+-------------------------+-------------------------+ CDC ACM UART ============ diff --git a/dts/bindings/pmci/mctp/usb.yaml b/dts/bindings/pmci/mctp/usb.yaml new file mode 100644 index 0000000000000..c706af125574e --- /dev/null +++ b/dts/bindings/pmci/mctp/usb.yaml @@ -0,0 +1,13 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + A configuration for MCTP binding to a USB target. + +compatible: "zephyr,mctp-usb" + +properties: + endpoint-id: + type: int + description: | + MCTP Endpoint ID diff --git a/include/zephyr/pmci/mctp/mctp_usb.h b/include/zephyr/pmci/mctp/mctp_usb.h new file mode 100644 index 0000000000000..a795d5e76b2c3 --- /dev/null +++ b/include/zephyr/pmci/mctp/mctp_usb.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 Intel Corporation + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef ZEPHYR_MCTP_USB_H_ +#define ZEPHYR_MCTP_USB_H_ + +#include +#include +#include +#include +#include + +#define MCTP_USB_HEADER_SIZE 4 +#define MCTP_USB_MAX_PACKET_LENGTH 255 + +/** + * @brief An MCTP binding for Zephyr's USB device stack + */ +struct mctp_binding_usb { + /** @cond INTERNAL_HIDDEN */ + struct mctp_binding binding; + const struct usbd_mctp_inst *usb_inst; + uint8_t endpoint_id; + uint8_t tx_buf[MCTP_USB_HEADER_SIZE + MCTP_USB_MAX_PACKET_LENGTH]; + struct k_sem tx_lock; + struct mctp_pktbuf *rx_pkt; + uint8_t rx_data_idx; + enum { + STATE_WAIT_HDR_DMTF0, + STATE_WAIT_HDR_DMTF1, + STATE_WAIT_HDR_RSVD0, + STATE_WAIT_HDR_LEN, + STATE_DATA + } rx_state; + /** @endcond INTERNAL_HIDDEN */ +}; + +/** @cond INTERNAL_HIDDEN */ +void mctp_usb_data_recv_cb(const void *const buf, const uint16_t size, const void *const priv); +void mctp_usb_error_cb(const int err, const uint8_t ep, const void *const priv); +int mctp_usb_start(struct mctp_binding *binding); +int mctp_usb_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt); +/** @endcond INTERNAL_HIDDEN */ + +/** + * @brief Define a MCTP bus binding for USB + * + * @param _name Symbolic name of the bus binding variable + * @param _dev DeviceTree Node containing the configuration of this MCTP binding + */ +#define MCTP_USB_DEFINE(_name, _dev, _subclass, _protocol) \ + extern const struct usbd_mctp_inst usbd_mctp_inst_##_name; \ + \ + struct mctp_binding_usb _name = { \ + .binding = { \ + .name = STRINGIFY(_name), \ + .version = 1, \ + .pkt_size = MCTP_PACKET_SIZE(MCTP_USB_MAX_PACKET_LENGTH), \ + .pkt_header = 0, \ + .pkt_trailer = 0, \ + .start = mctp_usb_start, \ + .tx = mctp_usb_tx \ + }, \ + .usb_inst = &usbd_mctp_inst_##_name, \ + .endpoint_id = DT_PROP(_dev, endpoint_id), \ + .rx_pkt = NULL, \ + .rx_data_idx = 0, \ + .rx_state = STATE_WAIT_HDR_DMTF0 \ + }; \ + \ + USBD_DEFINE_MCTP_INSTANCE(_name, _subclass, _protocol, &_name, mctp_usb_data_recv_cb, \ + mctp_usb_error_cb); + +#endif /* ZEPHYR_MCTP_USB_H_ */ diff --git a/include/zephyr/usb/class/usbd_mctp.h b/include/zephyr/usb/class/usbd_mctp.h new file mode 100644 index 0000000000000..b37a24c63f3bc --- /dev/null +++ b/include/zephyr/usb/class/usbd_mctp.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief MCTP over USB Protocol Endpoint Device Class public header + * + * This API is currently considered experimental. + */ + +#include + +#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_MCTP_H_ +#define ZEPHYR_INCLUDE_USB_CLASS_USBD_MCTP_H_ + +/** + * @brief USB MCTP device API + * @defgroup usbd_mctp USB MCTP device API + * @ingroup usb + * @since 4.2 + * @version 0.1.0 + * @{ + */ + +/* MCTP class subclass options */ +#define USBD_MCTP_SUBCLASS_MANAGEMENT_CONTROLLER 0 +#define USBD_MCTP_SUBCLASS_MANAGED_DEVICE_ENDPOINT 0 +#define USBD_MCTP_SUBCLASS_HOST_INTERFACE_ENDPOINT 1 + +/* MCTP class protocol options */ +#define USBD_MCTP_PROTOCOL_1_X 1 +#define USBD_MCTP_PROTOCOL_2_X 2 + +struct usbd_mctp_inst { + const char *name; + uint8_t sublcass; + uint8_t mctp_protocol; + void *priv; + void (*data_recv_cb)(const void *const buf, + const uint16_t size, + const void *const priv); + void (*error_cb)(const int err, + const uint8_t ep, + const void *const priv); +}; + +/** + * @brief Define USB MCTP instance + * + * Use this macro to create set the parameters of an MCTP instance. + * + * @param id Identifier by which the linker sorts registered instances + * @param subclass MCTP subclass used in the USB interfce descriptor + * @param protocol MCTP protocol used in the USB interface descriptor + * @param private Opaque private/user data + * @param recv_cb Data received callback + * @param err_cb Error callback + */ +#define USBD_DEFINE_MCTP_INSTANCE(id, subclass, protocol, private, recv_cb, err_cb) \ + const STRUCT_SECTION_ITERABLE(usbd_mctp_inst, usbd_mctp_inst_##id) = { \ + .name = STRINGIFY(id), \ + .sublcass = subclass, \ + .mctp_protocol = protocol, \ + .priv = private, \ + .data_recv_cb = recv_cb, \ + .error_cb = err_cb \ + } + +/** + * @brief Send data to the MCTP bus owner. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param inst USBD MCTP instance to send data through + * @param buf Buffer containing outgoing data + * @param size Number of bytes to send + * + * @return 0 on success, negative value on error + */ +int usbd_mctp_send(const struct usbd_mctp_inst *const inst, + const void *const buf, + const uint16_t size); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_USB_CLASS_USBD_MCTP_H_ */ diff --git a/include/zephyr/usb/usb_ch9.h b/include/zephyr/usb/usb_ch9.h index 0c4a3ac06086a..67af0047cc11c 100644 --- a/include/zephyr/usb/usb_ch9.h +++ b/include/zephyr/usb/usb_ch9.h @@ -267,6 +267,7 @@ struct usb_association_descriptor { #define USB_BCC_MASS_STORAGE 0x08 #define USB_BCC_CDC_DATA 0x0A #define USB_BCC_VIDEO 0x0E +#define USB_BCC_MCTP 0x14 #define USB_BCC_WIRELESS_CONTROLLER 0xE0 #define USB_BCC_MISCELLANEOUS 0xEF #define USB_BCC_APPLICATION 0xFE diff --git a/samples/modules/pmci/mctp/usb_endpoint/CMakeLists.txt b/samples/modules/pmci/mctp/usb_endpoint/CMakeLists.txt new file mode 100644 index 0000000000000..efbc4cb50f8c6 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mctp_usb_endpoint) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/modules/pmci/mctp/usb_endpoint/Kconfig b/samples/modules/pmci/mctp/usb_endpoint/Kconfig new file mode 100644 index 0000000000000..96c5455894806 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/modules/pmci/mctp/usb_endpoint/README.rst b/samples/modules/pmci/mctp/usb_endpoint/README.rst new file mode 100644 index 0000000000000..fa8ca6099295a --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/README.rst @@ -0,0 +1,56 @@ +.. zephyr:code-sample:: mctp-usb-endpoint + :name: MCTP USB Endpoint Node Sample + :relevant-api: usbd_api + + Create an MCTP endpoint node using the USB device interface. + +Overview +******** +Sets up an MCTP endpoint node that listens for messages from the MCTP bus owner with EID 20. +Responds with "Hello, bus owner" message. + +Requirements +************ +A board and SoC that provide support for USB device capability (either FS or HS). Testing requires +a USB host that has the ability to enumerate the MCTP interface and interact with the board using +the MCTP protocol. An easy way to do this is a Python script. + +An example script to test this interface is provided with this sample application. To run the +script, you must first install pyusb. + +.. code-block:: console + + pip install requirements.txt + python usb_host_tester.py + +If the test is successful, you will see the following output from the script: + +.. code-block:: console + + Sending message with size smaller than USB FS MPS (<64b) + Received: b'\x1a\xb4\x00\x15\x01\x14\n\xc0Hello, bus owner\x00' + Sending message spanning two USB FS packets (>64b) + Received: b'\x1a\xb4\x00\x15\x01\x14\n\xc0Hello, bus owner\x00' + Sending message with two MCTP messages in a single USB FS packet + Received: b'\x1a\xb4\x00\x15\x01\x14\n\xc0Hello, bus owner\x00' + Received: b'\x1a\xb4\x00\x15\x01\x14\n\xc0Hello, bus owner\x00' + +Wiring +****** +Connect a USB cable between the host (likely a PC) and the board. The type of cable (mini, micro, +type C) depends on the host and board connectors. + +Building and Running +******************** + +.. zephyr-app-commands:: + :zephyr-app: samples/modules/pmci/mctp/usb_endpoint + :host-os: unix + :board: frdm_mcxn947_mcxn947_cpu0 + :goals: run + :compact: + +References +********** + +`MCTP Base Specification 2019 `_ diff --git a/samples/modules/pmci/mctp/usb_endpoint/app.overlay b/samples/modules/pmci/mctp/usb_endpoint/app.overlay new file mode 100644 index 0000000000000..5cca7e255447b --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/app.overlay @@ -0,0 +1,12 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + mctp_usb0: mctp_usb0 { + compatible = "zephyr,mctp-usb"; + endpoint-id = <10>; + }; +}; diff --git a/samples/modules/pmci/mctp/usb_endpoint/prj.conf b/samples/modules/pmci/mctp/usb_endpoint/prj.conf new file mode 100644 index 0000000000000..3a0e9c7dc2d69 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/prj.conf @@ -0,0 +1,11 @@ +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USBD_MCTP_CLASS=y + +CONFIG_MCTP=y +CONFIG_MCTP_USB=y + +CONFIG_LOG=y +CONFIG_MCTP_LOG_LEVEL_ERR=y +CONFIG_USBD_MCTP_LOG_LEVEL_DBG=y +CONFIG_USBD_LOG_LEVEL_ERR=y +CONFIG_UDC_DRIVER_LOG_LEVEL_ERR=y diff --git a/samples/modules/pmci/mctp/usb_endpoint/requirements.txt b/samples/modules/pmci/mctp/usb_endpoint/requirements.txt new file mode 100644 index 0000000000000..feec2b980afdb --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/requirements.txt @@ -0,0 +1,2 @@ +# Needed to run USB host test script. +pyusb diff --git a/samples/modules/pmci/mctp/usb_endpoint/src/main.c b/samples/modules/pmci/mctp/usb_endpoint/src/main.c new file mode 100644 index 0000000000000..271c1a1f4f5e9 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/src/main.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Intel Corporation + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(mctp_endpoint); + +#define BUS_OWNER_ID 20 + +K_SEM_DEFINE(mctp_rx, 0, 1); +MCTP_USB_DEFINE(mctp0, DT_NODELABEL(mctp_usb0), USBD_MCTP_SUBCLASS_MANAGED_DEVICE_ENDPOINT, + USBD_MCTP_PROTOCOL_1_X); + +static struct mctp *mctp_ctx; +static bool usb_configured; + +static void sample_msg_cb(struct usbd_context *const ctx, const struct usbd_msg *msg) +{ + LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type)); + + if (msg->type == USBD_MSG_CONFIGURATION) { + usb_configured = true; + } +} + +static int enable_usb_device_next(void) +{ + static struct usbd_context *mctp_poc_usbd; + int err; + + mctp_poc_usbd = sample_usbd_init_device(sample_msg_cb); + if (mctp_poc_usbd == NULL) { + LOG_ERR("Failed to initialize USB device"); + return -ENODEV; + } + + if (!usbd_can_detect_vbus(mctp_poc_usbd)) { + err = usbd_enable(mctp_poc_usbd); + if (err) { + LOG_ERR("Failed to enable device support"); + return err; + } + } + + LOG_INF("USB device support enabled"); + + return 0; +} + +static void rx_message(uint8_t eid, bool tag_owner, uint8_t msg_tag, void *data, void *msg, + size_t len) +{ + switch (eid) { + case BUS_OWNER_ID: { + LOG_INF("Received MCTP message \"%s\" from EID %d", (char *)msg, eid); + k_sem_give(&mctp_rx); + break; + } + default: { + LOG_INF("Unknown endpoint %d", eid); + break; + } + } +} + +int main(void) +{ + LOG_INF("MCTP Endpoint EID: %d on %s", mctp0.endpoint_id, CONFIG_BOARD_TARGET); + + int ret = enable_usb_device_next(); + + if (ret != 0) { + LOG_ERR("Failed to enable USB device support"); + return 0; + } + + while (!usb_configured) { + k_msleep(5); + } + + mctp_set_alloc_ops(malloc, free, realloc); + mctp_ctx = mctp_init(); + __ASSERT_NO_MSG(mctp_ctx != NULL); + + mctp_register_bus(mctp_ctx, &mctp0.binding, mctp0.endpoint_id); + mctp_set_rx_all(mctp_ctx, rx_message, NULL); + + while (1) { + k_sem_take(&mctp_rx, K_FOREVER); + mctp_message_tx(mctp_ctx, BUS_OWNER_ID, false, 0, "Hello, bus owner", + sizeof("Hello, bus owner")); + } + + return 0; +} diff --git a/samples/modules/pmci/mctp/usb_endpoint/usb_host_tester.py b/samples/modules/pmci/mctp/usb_endpoint/usb_host_tester.py new file mode 100644 index 0000000000000..c8acb4c5659b4 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/usb_host_tester.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +import usb.core +import usb.util + + +class USBDevice: + def __init__(self, vid, pid): + self.vid = vid + self.pid = pid + self.device = None + self.endpoint_in = None + self.endpoint_out = None + self.interface = None + + def connect(self): + # Find the device + self.device = usb.core.find(idVendor=self.vid, idProduct=self.pid) + if self.device is None: + raise ValueError( + "Device not found. Make sure the board running the MCTP USB endpoint \ + sample is connected." + ) + + # Set the active configuration + self.device.set_configuration() + cfg = self.device.get_active_configuration() + self.interface = cfg[(0, 0)] + + # Claim the interface + usb.util.claim_interface(self.device, self.interface.bInterfaceNumber) + + # Find bulk IN and OUT endpoints + for ep in self.interface: + if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN: + self.endpoint_in = ep + elif usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_OUT: + self.endpoint_out = ep + + if not self.endpoint_in or not self.endpoint_out: + raise ValueError("Bulk IN/OUT endpoints not found") + + # Print descriptor information (for debug purposes) + for cfg in self.device: + print(cfg) + + def send_data(self, data): + if not self.endpoint_out: + raise RuntimeError("OUT endpoint not initialized") + self.endpoint_out.write(data) + + def receive_data(self, size=64, timeout=1000): + if not self.endpoint_in: + raise RuntimeError("IN endpoint not initialized") + return self.endpoint_in.read(size, timeout) + + def disconnect(self): + usb.util.release_interface(self.device, self.interface.bInterfaceNumber) + usb.util.dispose_resources(self.device) + + +if __name__ == "__main__": + usb_dev = USBDevice(0x2FE3, 0x0001) + try: + usb_dev.connect() + + print("Sending message with size smaller than USB FS MPS (<64b)") + usb_dev.send_data( + b'\x1a\xb4\x00\x0f\x01\x0a\x14\xc0\x48\x65\x6c\x6c\x6f\x2c\x20\x4d\x43\x58\x00' + ) + response = usb_dev.receive_data() + print("Received:", bytes(response)) + + print("Sending message spanning two USB FS packets (>64b)") + usb_dev.send_data( + b'\x1a\xb4\x00\x4c\x01\x0a\x14\xc0\x48\x65\x6c\x6c\x6f\x2c\x20\x4d\x43\x58\x2e\x20' + b'\x4c\x65\x74\x27\x73\x20\x74\x65\x73\x74\x20\x61\x20\x6d\x75\x6c\x74\x69\x2d\x70' + b'\x61\x63\x6b\x65\x74\x20\x6d\x65\x73\x73\x61\x67\x65\x20\x74\x6f\x20\x73\x65\x65' + b'\x20\x68\x6f\x77\x20\x79\x6f\x75\x20\x68\x61\x6e\x64\x6c\x65\x20\x69\x74\x2e\x00' + ) + response = usb_dev.receive_data() + print("Received:", bytes(response)) + + print("Sending message with two MCTP messages in a single USB FS packet") + usb_dev.send_data( + b'\x1a\xb4\x00\x12\x01\x0a\x14\xc0\x48\x65\x6c\x6c\x6f\x2c\x20\x4d\x43\x58\x2c\x20' + b'\x31\x00\x1a\xb4\x00\x12\x01\x0a\x14\xc0\x48\x65\x6c\x6c\x6f\x2c\x20\x4d\x43\x58' + b'\x2c\x20\x32\x00' + ) + response = usb_dev.receive_data() + print("Received:", bytes(response)) + print("Received:", bytes(response)) + finally: + usb_dev.disconnect() diff --git a/subsys/pmci/mctp/CMakeLists.txt b/subsys/pmci/mctp/CMakeLists.txt index aa32c208f00c9..9d9fc9cbb3d3a 100644 --- a/subsys/pmci/mctp/CMakeLists.txt +++ b/subsys/pmci/mctp/CMakeLists.txt @@ -3,3 +3,4 @@ zephyr_library_sources(mctp_memory.c) zephyr_library_sources_ifdef(CONFIG_MCTP_UART mctp_uart.c) zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_CONTROLLER mctp_i2c_gpio_controller.c) zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_TARGET mctp_i2c_gpio_target.c) +zephyr_library_sources_ifdef(CONFIG_MCTP_USB mctp_usb.c) diff --git a/subsys/pmci/mctp/Kconfig b/subsys/pmci/mctp/Kconfig index 1d70ccc7b240d..7f2f9dc11d2c8 100644 --- a/subsys/pmci/mctp/Kconfig +++ b/subsys/pmci/mctp/Kconfig @@ -1,4 +1,5 @@ # Copyright (c) 2024 Intel Corporation +# Copyright 2025 NXP # SPDX-License-Identifier: Apache-2.0 menuconfig MCTP @@ -42,9 +43,16 @@ config MCTP_I2C_GPIO_TARGET Build the MCTP I2C+GPIO target binding to use MCTP over Zephyr's I2C target interface and GPIO to signal writes to the bus controller. +config MCTP_USB + bool "MCTP USB Binding" + depends on USBD_MCTP_CLASS + help + Build the MCTP USB binding to use MCTP over Zephyr's USBD "next" interface. module = MCTP module-str = MCTP source "subsys/logging/Kconfig.template.log_config" +rsource "Kconfig.usb" + endif diff --git a/subsys/pmci/mctp/Kconfig.usb b/subsys/pmci/mctp/Kconfig.usb new file mode 100644 index 0000000000000..9fba1affae5bc --- /dev/null +++ b/subsys/pmci/mctp/Kconfig.usb @@ -0,0 +1,12 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +if MCTP_USB + +config MCTP_USB_TX_TIMEOUT + int "MCTP USB transmit timeout (ms)" + default 1000 + help + Set the MCTP USB transmit timeout, in milliseconds. + +endif diff --git a/subsys/pmci/mctp/mctp_usb.c b/subsys/pmci/mctp/mctp_usb.c new file mode 100644 index 0000000000000..65ca1f06022b5 --- /dev/null +++ b/subsys/pmci/mctp/mctp_usb.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 Intel Corporation + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(mctp_usb, CONFIG_MCTP_LOG_LEVEL); + +#include "libmctp-alloc.h" + +#define MCTP_USB_DMTF_0 0x1A +#define MCTP_USB_DMTF_1 0xB4 + +static void mctp_usb_reset_rx_state(struct mctp_binding_usb *usb) +{ + if (usb->rx_pkt != NULL) { + mctp_pktbuf_free(usb->rx_pkt); + } + + usb->rx_data_idx = 0; + usb->rx_state = STATE_WAIT_HDR_DMTF0; +} + +void mctp_usb_data_recv_cb(const void *const buf, const uint16_t size, const void *const priv) +{ + struct mctp_binding_usb *usb = (struct mctp_binding_usb *)priv; + uint8_t *rx_buf = (uint8_t *)buf; + + LOG_DBG("size=%d", size); + LOG_HEXDUMP_DBG(buf, size, "buf = "); + + for (int i = 0; i < size; i++) { + switch (usb->rx_state) { + case STATE_WAIT_HDR_DMTF0: { + if (rx_buf[i] == MCTP_USB_DMTF_0) { + usb->rx_state = STATE_WAIT_HDR_DMTF1; + } else { + LOG_ERR("Invalid DMTF0 %02X", rx_buf[i]); + goto recv_exit; + } + break; + } + case STATE_WAIT_HDR_DMTF1: { + if (rx_buf[i] == MCTP_USB_DMTF_1) { + usb->rx_state = STATE_WAIT_HDR_RSVD0; + } else { + LOG_ERR("Invalid DMTF1 %02X", rx_buf[i]); + usb->rx_state = STATE_WAIT_HDR_DMTF0; + goto recv_exit; + } + break; + } + case STATE_WAIT_HDR_RSVD0: { + if (rx_buf[i] == 0) { + usb->rx_state = STATE_WAIT_HDR_LEN; + } else { + LOG_ERR("Invalid RSVD0 %02X", rx_buf[i]); + usb->rx_state = STATE_WAIT_HDR_DMTF0; + goto recv_exit; + } + break; + } + case STATE_WAIT_HDR_LEN: { + if (rx_buf[i] > MCTP_USB_MAX_PACKET_LENGTH || rx_buf[i] == 0) { + LOG_ERR("Invalid LEN %02X", rx_buf[i]); + usb->rx_state = STATE_WAIT_HDR_DMTF0; + goto recv_exit; + } + + usb->rx_data_idx = 0; + usb->rx_pkt = mctp_pktbuf_alloc(&usb->binding, rx_buf[i]); + if (usb->rx_pkt == NULL) { + LOG_ERR("Could not allocate PKT buffer"); + mctp_usb_reset_rx_state(usb); + goto recv_exit; + } else { + usb->rx_state = STATE_DATA; + } + + LOG_DBG("Expecting LEN=%d", (int)rx_buf[i]); + + break; + } + case STATE_DATA: { + usb->rx_pkt->data[usb->rx_data_idx++] = rx_buf[i]; + + if (usb->rx_data_idx == usb->rx_pkt->end) { + LOG_DBG("Packet complete"); + mctp_bus_rx(&usb->binding, usb->rx_pkt); + mctp_usb_reset_rx_state(usb); + } + + break; + } + } + } + +recv_exit: +} + +void mctp_usb_error_cb(const int err, const uint8_t ep, const void *const priv) +{ + struct mctp_binding_usb *usb = (struct mctp_binding_usb *)priv; + + LOG_ERR("Encountered USB error %d, endpoint %d, on %s", err, ep, usb->binding.name); +} + +int mctp_usb_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt) +{ + struct mctp_binding_usb *usb = CONTAINER_OF(binding, struct mctp_binding_usb, binding); + size_t len = mctp_pktbuf_size(pkt); + int err; + + if (len > MCTP_USB_MAX_PACKET_LENGTH) { + return -E2BIG; + } + + err = k_sem_take(&usb->tx_lock, K_MSEC(CONFIG_MCTP_USB_TX_TIMEOUT)); + if (err != 0) { + LOG_ERR("Semaphore could not be obtained"); + return err; + } + + usb->tx_buf[0] = MCTP_USB_DMTF_0; + usb->tx_buf[1] = MCTP_USB_DMTF_1; + usb->tx_buf[2] = 0; + usb->tx_buf[3] = len; + + memcpy((void *)&usb->tx_buf[MCTP_USB_HEADER_SIZE], pkt->data, len); + + LOG_HEXDUMP_DBG(usb->tx_buf, len + 4, "buf = "); + + err = usbd_mctp_send(usb->usb_inst, (void *)usb->tx_buf, len + MCTP_USB_HEADER_SIZE); + if (err != 0) { + LOG_ERR("Failed to send MCTP data over USB"); + return err; + } + + k_sem_give(&usb->tx_lock); + + return 0; +} + +int mctp_usb_start(struct mctp_binding *binding) +{ + struct mctp_binding_usb *usb = CONTAINER_OF(binding, struct mctp_binding_usb, binding); + + k_sem_init(&usb->tx_lock, 1, 1); + mctp_binding_set_tx_enabled(binding, true); + + return 0; +} diff --git a/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt index 717cdb9da37b0..2c9f5d62c700d 100644 --- a/subsys/usb/device_next/CMakeLists.txt +++ b/subsys/usb/device_next/CMakeLists.txt @@ -104,4 +104,14 @@ zephyr_linker_sources_ifdef( SECTIONS class/usbd_dfu.ld ) +zephyr_library_sources_ifdef( + CONFIG_USBD_MCTP_CLASS + class/usbd_mctp.c +) + +zephyr_linker_sources_ifdef( + CONFIG_USBD_MCTP_CLASS + SECTIONS class/usbd_mctp.ld +) + zephyr_linker_sources(DATA_SECTIONS usbd_data.ld) diff --git a/subsys/usb/device_next/class/Kconfig b/subsys/usb/device_next/class/Kconfig index d3d9a946488e3..84eb20473fd6c 100644 --- a/subsys/usb/device_next/class/Kconfig +++ b/subsys/usb/device_next/class/Kconfig @@ -13,3 +13,4 @@ rsource "Kconfig.hid" rsource "Kconfig.midi2" rsource "Kconfig.dfu" rsource "Kconfig.uvc" +rsource "Kconfig.mctp" diff --git a/subsys/usb/device_next/class/Kconfig.mctp b/subsys/usb/device_next/class/Kconfig.mctp new file mode 100644 index 0000000000000..1aaed70deeaab --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.mctp @@ -0,0 +1,19 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +config USBD_MCTP_CLASS + bool "USB MCTP implementation" + default y + help + USB device MCTP class implementation. + +if USBD_MCTP_CLASS + +module = USBD_MCTP +module-str = usbd mctp +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" + +rsource "Kconfig.template.instances_count" +endif diff --git a/subsys/usb/device_next/class/usbd_mctp.c b/subsys/usb/device_next/class/usbd_mctp.c new file mode 100644 index 0000000000000..0695e277d4596 --- /dev/null +++ b/subsys/usb/device_next/class/usbd_mctp.c @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(usbd_mctp, CONFIG_USBD_MCTP_LOG_LEVEL); + +#define MCTP_ENABLED 0 +#define MCTP_NUM_INSTANCES CONFIG_USBD_MCTP_INSTANCES_COUNT + +UDC_BUF_POOL_DEFINE(mctp_ep_pool, MCTP_NUM_INSTANCES * 2, USBD_MAX_BULK_MPS, + sizeof(struct udc_buf_info), NULL); + +struct usbd_mctp_desc { + struct usb_if_descriptor if0; + struct usb_ep_descriptor if0_fs_out_ep; + struct usb_ep_descriptor if0_fs_in_ep; + struct usb_ep_descriptor if0_hs_out_ep; + struct usb_ep_descriptor if0_hs_in_ep; + struct usb_desc_header nil_desc; +}; + +struct usbd_mctp_ctx { + struct usbd_class_data *class_node; + struct usbd_mctp_desc *const desc; + const struct usb_desc_header **const fs_desc; + const struct usb_desc_header **const hs_desc; + uint8_t inst_idx; + struct usbd_mctp_inst *inst; + struct k_sem in_sem; + struct k_work out_work; + atomic_t state; +}; + +static struct { + const struct usbd_mctp_inst *inst; + struct usbd_class_data *c_data; +} usbd_mctp_inst_to_c_data[MCTP_NUM_INSTANCES]; + +static struct net_buf *mctp_buf_alloc(const uint8_t ep) +{ + struct net_buf *buf = NULL; + struct udc_buf_info *bi; + + buf = net_buf_alloc(&mctp_ep_pool, K_NO_WAIT); + if (!buf) { + return NULL; + } + + bi = udc_get_buf_info(buf); + bi->ep = ep; + + return buf; +} + +static uint8_t mctp_get_bulk_in(struct usbd_class_data *const c_data) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + +#if USBD_SUPPORTS_HIGH_SPEED + if (usbd_bus_speed(usbd_class_get_ctx(ctx->class_node)) == USBD_SPEED_HS) { + return ctx->desc->if0_hs_in_ep.bEndpointAddress; + } +#endif + + return ctx->desc->if0_fs_in_ep.bEndpointAddress; +} + +static uint8_t mctp_get_bulk_out(struct usbd_class_data *const c_data) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + +#if USBD_SUPPORTS_HIGH_SPEED + if (usbd_bus_speed(usbd_class_get_ctx(ctx->class_node)) == USBD_SPEED_HS) { + return ctx->desc->if0_hs_out_ep.bEndpointAddress; + } +#endif + + return ctx->desc->if0_fs_out_ep.bEndpointAddress; +} + +static void mctp_out_work(struct k_work *work) +{ + struct usbd_mctp_ctx *ctx = CONTAINER_OF(work, struct usbd_mctp_ctx, out_work); + struct net_buf *buf; + + if (!atomic_test_bit(&ctx->state, MCTP_ENABLED)) { + return; + } + + buf = mctp_buf_alloc(mctp_get_bulk_out(ctx->class_node)); + if (buf == NULL) { + if (ctx->inst->error_cb != NULL) { + ctx->inst->error_cb(-ENOBUFS, mctp_get_bulk_in(ctx->class_node), + ctx->inst->priv); + } + LOG_ERR("Failed to allocate OUT buffer"); + return; + } + + if (usbd_ep_enqueue(ctx->class_node, buf)) { + net_buf_unref(buf); + if (ctx->inst->error_cb != NULL) { + ctx->inst->error_cb(-EIO, mctp_get_bulk_in(ctx->class_node), + ctx->inst->priv); + } + LOG_ERR("Failed to enqueue OUT buffer"); + } +} + +static int usbd_mctp_request(struct usbd_class_data *const c_data, struct net_buf *buf, int err) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + struct udc_buf_info *bi = udc_get_buf_info(buf); + + LOG_DBG("request for EP 0x%x", bi->ep); + + if (err) { + if (err == -ECONNABORTED) { + LOG_WRN("request ep 0x%02x, len %u cancelled", bi->ep, buf->len); + } else { + LOG_ERR("request ep 0x%02x, len %u failed", bi->ep, buf->len); + } + + if (ctx->inst->error_cb != NULL) { + ctx->inst->error_cb(err, bi->ep, ctx->inst->priv); + } + + goto ep_request_exit; + } + + if (bi->ep == mctp_get_bulk_out(c_data)) { + if (ctx->inst->data_recv_cb != NULL) { + ctx->inst->data_recv_cb(buf->data, buf->len, ctx->inst->priv); + } + + k_work_submit(&ctx->out_work); + } + + if (bi->ep == mctp_get_bulk_in(c_data)) { + k_sem_give(&ctx->in_sem); + } + +ep_request_exit: + net_buf_unref(buf); + return 0; +} + +static void *usbd_mctp_get_desc(struct usbd_class_data *const c_data, const enum usbd_speed speed) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + + if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) { + return ctx->hs_desc; + } + + return ctx->fs_desc; +} + +static void usbd_mctp_enable(struct usbd_class_data *const c_data) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + + if (!atomic_test_and_set_bit(&ctx->state, MCTP_ENABLED)) { + k_work_submit(&ctx->out_work); + } + + LOG_DBG("Enabled %s", c_data->name); +} + +static void usbd_mctp_disable(struct usbd_class_data *const c_data) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + + atomic_clear_bit(&ctx->state, MCTP_ENABLED); + + LOG_DBG("Disabled %s", c_data->name); +} + +static int usbd_mctp_init(struct usbd_class_data *const c_data) +{ + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + size_t num_instances = 0; + + STRUCT_SECTION_COUNT(usbd_mctp_inst, &num_instances); + + if (num_instances != MCTP_NUM_INSTANCES) { + LOG_ERR("The number of application instances (%d) does not match the number " + "specified by CONFIG_USBD_MCTP_INSTANCES_COUNT (%d)", + num_instances, MCTP_NUM_INSTANCES); + return -EINVAL; + } + + ctx->class_node = c_data; + ctx->state = 0; + + k_sem_init(&ctx->in_sem, 1, 1); + k_work_init(&ctx->out_work, mctp_out_work); + + STRUCT_SECTION_GET(usbd_mctp_inst, ctx->inst_idx, &ctx->inst); + usbd_mctp_inst_to_c_data[ctx->inst_idx].inst = ctx->inst; + usbd_mctp_inst_to_c_data[ctx->inst_idx].c_data = c_data; + + if (ctx->inst->sublcass == USBD_MCTP_SUBCLASS_MANAGEMENT_CONTROLLER || + ctx->inst->sublcass == USBD_MCTP_SUBCLASS_MANAGED_DEVICE_ENDPOINT || + ctx->inst->sublcass == USBD_MCTP_SUBCLASS_HOST_INTERFACE_ENDPOINT) { + ctx->desc->if0.bInterfaceSubClass = ctx->inst->sublcass; + } else { + LOG_ERR("Invalid USB MCTP sublcass"); + return -EINVAL; + } + + if (ctx->inst->mctp_protocol == USBD_MCTP_PROTOCOL_1_X || + ctx->inst->mctp_protocol == USBD_MCTP_PROTOCOL_2_X) { + ctx->desc->if0.bInterfaceProtocol = ctx->inst->mctp_protocol; + } else { + LOG_ERR("Invalid MCTP protocol"); + return -EINVAL; + } + + LOG_DBG("MCTP device %s initialized", ctx->inst->name); + + return 0; +} + +struct usbd_class_api usbd_mctp_api = { + .request = usbd_mctp_request, + .enable = usbd_mctp_enable, + .disable = usbd_mctp_disable, + .init = usbd_mctp_init, + .get_desc = usbd_mctp_get_desc, +}; + +int usbd_mctp_send(const struct usbd_mctp_inst *const inst, const void *const data, + const uint16_t size) +{ + struct usbd_class_data *c_data = NULL; + struct net_buf *buf = NULL; + int err; + + /* Get the class data associated with the MCTP instance */ + for (int i = 0; i < MCTP_NUM_INSTANCES; i++) { + if (usbd_mctp_inst_to_c_data[i].inst == inst) { + c_data = usbd_mctp_inst_to_c_data[i].c_data; + break; + } + } + + if (c_data == NULL) { + LOG_ERR("MCTP instance not found"); + return -ENODEV; + } + + struct usbd_mctp_ctx *ctx = usbd_class_get_private(c_data); + + if (!atomic_test_bit(&ctx->state, MCTP_ENABLED)) { + return -EPERM; + } + + k_sem_take(&ctx->in_sem, K_FOREVER); + + buf = mctp_buf_alloc(mctp_get_bulk_in(c_data)); + if (buf == NULL) { + k_sem_give(&ctx->in_sem); + LOG_ERR("Failed to allocate IN buffer"); + return -ENOMEM; + } + + net_buf_add_mem(buf, data, size); + + err = usbd_ep_enqueue(c_data, buf); + if (err) { + k_sem_give(&ctx->in_sem); + LOG_ERR("Failed to enqueue IN buffer"); + net_buf_unref(buf); + return err; + } + + return 0; +} + +#define DEFINE_MCTP_DESCRIPTORS(n, _) \ + static struct usbd_mctp_desc usbd_mctp_desc_##n = { \ + .if0 = { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_BCC_MCTP, \ + .bInterfaceSubClass = 0, \ + .bInterfaceProtocol = 1, \ + .iInterface = 0 \ + }, \ + .if0_fs_out_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64), \ + .bInterval = 1 \ + }, \ + .if0_fs_in_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64), \ + .bInterval = 1 \ + }, \ + .if0_hs_out_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512), \ + .bInterval = 1 \ + }, \ + .if0_hs_in_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512), \ + .bInterval = 1 \ + }, \ + .nil_desc = { \ + .bLength = 0, \ + .bDescriptorType = 0 \ + } \ + }; \ + \ + const static struct usb_desc_header *usbd_mctp_fs_desc_##n[] = { \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0_fs_in_ep, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0_fs_out_ep, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.nil_desc \ + }; \ + \ + const static struct usb_desc_header *usbd_mctp_hs_desc_##n[] = { \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0_hs_in_ep, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.if0_hs_out_ep, \ + (struct usb_desc_header *)&usbd_mctp_desc_##n.nil_desc \ + }; + +#define DEFINE_MCTP_CLASS_DATA(n, _) \ + static struct usbd_mctp_ctx usbd_mctp_ctx_##n = { \ + .desc = &usbd_mctp_desc_##n, \ + .fs_desc = usbd_mctp_fs_desc_##n, \ + .hs_desc = usbd_mctp_hs_desc_##n, \ + .inst_idx = n \ + }; \ + \ + USBD_DEFINE_CLASS(mctp_##n, &usbd_mctp_api, &usbd_mctp_ctx_##n, NULL); + +LISTIFY(MCTP_NUM_INSTANCES, DEFINE_MCTP_DESCRIPTORS, ()) +LISTIFY(MCTP_NUM_INSTANCES, DEFINE_MCTP_CLASS_DATA, ()) diff --git a/subsys/usb/device_next/class/usbd_mctp.ld b/subsys/usb/device_next/class/usbd_mctp.ld new file mode 100644 index 0000000000000..e0641324364ac --- /dev/null +++ b/subsys/usb/device_next/class/usbd_mctp.ld @@ -0,0 +1,3 @@ +#include + +ITERABLE_SECTION_ROM(usbd_mctp_inst, Z_LINK_ITERABLE_SUBALIGN)