From bc8e11a6639122130cf30a3996941e30ba4d3a8b Mon Sep 17 00:00:00 2001 From: Eric Ocasio Date: Fri, 29 Aug 2025 09:32:10 -0500 Subject: [PATCH 1/3] usb: Add USB device class for base class MCTP Endpoint. In order to support the USB binding for libMCTP, we must first add a device class for MCTP. MCTP is one of the base classes in the USB specification. Signed-off-by: Eric Ocasio --- .../usb/device_next/api/index.rst | 1 + .../usb/device_next/api/usbd_mctp.rst | 11 + .../usb/device_next/usb_device.rst | 4 + include/zephyr/usb/class/usbd_mctp.h | 88 ++++++ include/zephyr/usb/usb_ch9.h | 1 + subsys/usb/device_next/CMakeLists.txt | 5 + subsys/usb/device_next/class/Kconfig | 1 + subsys/usb/device_next/class/Kconfig.mctp | 42 +++ subsys/usb/device_next/class/usbd_mctp.c | 282 ++++++++++++++++++ 9 files changed, 435 insertions(+) create mode 100644 doc/connectivity/usb/device_next/api/usbd_mctp.rst create mode 100644 include/zephyr/usb/class/usbd_mctp.h create mode 100644 subsys/usb/device_next/class/Kconfig.mctp create mode 100644 subsys/usb/device_next/class/usbd_mctp.c 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..fd7c876d5d5e0 --- /dev/null +++ b/doc/connectivity/usb/device_next/api/usbd_mctp.rst @@ -0,0 +1,11 @@ +.. _usbd_mctp: + +MCTP USB device API +######################## + +MCTP USB device specific API defined in :zephyr_file:`include/zephyr/usb/class/usbd_mctp.h`. + +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..ab953aeeaeaba 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}` | +-----------------------------------+-------------------------+-------------------------+ +| USB MCTP over USB Endpoint | :ref:`usbd_mctp` | :samp:`mctp_{n}` | ++-----------------------------------+-------------------------+-------------------------+ CDC ACM UART ============ diff --git a/include/zephyr/usb/class/usbd_mctp.h b/include/zephyr/usb/class/usbd_mctp.h new file mode 100644 index 0000000000000..efb9439e7e848 --- /dev/null +++ b/include/zephyr/usb/class/usbd_mctp.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB MCTP over USB Protocol Endpoint Device Class (MCTP) public header + * + * This header describes MCTP class interactions desgined to be used by libMCTP. + * The MCTP device itself is modelled with devicetree zephyr,mctp-usb compatible. + * + * This API is currently considered experimental. + */ + +#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 + * @{ + */ + +/** + * @brief USB MCTP application event handlers + */ +struct mctp_ops { + /** + * @brief Data received + * + * This function is called after the USB has received a packet of data, up to max packet + * size. The MCTP USB binding for libMCTP is responsible for copying the data from the USB + * buffer and packetizing the data for the MCTP core. This callback is mandatory. + * + * @param dev MCTP USB device instance + * @param buf Buffer containing USB packet data + * @param size Number of bytes in the buffer + * @param user_data Opaque user data pointer + */ + void (*data_recv_cb)(const struct device *dev, void *buf, uint16_t size, void *user_data); + /** + * @brief TX complete + * + * This function is called after the USB has completed sending data to the MCTP bus owner. + * This function is optional in general, but required if using the libMCTP USB binding. + * + * @param dev MCTP USB device instance + * @param buf Buffer containing USB packet data + * @param size Number of bytes in the buffer + * @param user_data Opaque user data pointer + */ + void (*tx_complete_cb)(const struct device *dev, void *user_data); +}; + +/** + * @brief Register MCTP USB application callbacks. + * + * @param dev MCTP USB device instance + * @param ops MCTP callback structure + * @param user_data Opaque user data to pass to ops callbacks + */ +void usbd_mctp_set_ops(const struct device *dev, const struct mctp_ops *ops, void *user_data); + +/** + * @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 dev MCTP USB device instance + * @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 device *dev, void *buf, 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/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt index 717cdb9da37b0..df9d6f4a2a1a7 100644 --- a/subsys/usb/device_next/CMakeLists.txt +++ b/subsys/usb/device_next/CMakeLists.txt @@ -104,4 +104,9 @@ zephyr_linker_sources_ifdef( SECTIONS class/usbd_dfu.ld ) +zephyr_library_sources_ifdef( + CONFIG_USBD_MCTP_CLASS + class/usbd_mctp.c +) + 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..4f4b23245ac91 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.mctp @@ -0,0 +1,42 @@ +# 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 + +config USBD_MCTP_BULK_IN_EP_ADDR + hex "Address for the Bulk IN endpoint" + default 0x81 + help + Address for the Bulk IN endpoint. + +config USBD_MCTP_BULK_OUT_EP_ADDR + hex "Address for the Bulk OUT endpoint" + default 0x01 + help + Address for the Bulk OUT endpoint. + +config USBD_MCTP_INTERFACE_SUBCLASS + int "Sub-class field of the interface descriptor" + default 0 + help + Sub-class field of the interface descriptor. + +config USBD_MCTP_INTERFACE_PROTOCOL + int "Protocol field for interface descriptor" + default 1 + help + Protocol field for interface descriptor. + +module = USBD_MCTP +module-str = usbd mctp +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" + +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..03436e18fa45e --- /dev/null +++ b/subsys/usb/device_next/class/usbd_mctp.c @@ -0,0 +1,282 @@ +/* + * 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 DT_DRV_COMPAT zephyr_mctp_usb + +#define MCTP_ENABLED 0 + +struct usbd_mctp_desc { + struct usb_association_descriptor iad; + 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_config { + struct usbd_class_data *const c_data; + struct usbd_mctp_desc *const desc; + const struct usb_desc_header **const fs_desc; + const struct usb_desc_header **const hs_desc; +}; + +struct usbd_mctp_data { + const struct device *dev; + const struct mctp_ops *ops; + struct k_sem in_sem; + struct k_work out_work; + void *user_data; + atomic_t state; +}; + +static void usbd_mctp_out_work(struct k_work *work) +{ + struct usbd_mctp_data *data = CONTAINER_OF(work, struct usbd_mctp_data, out_work); + const struct usbd_mctp_config *config = data->dev->config; + struct usbd_class_data *c_data = config->c_data; + struct net_buf *buf; + + if (!atomic_test_bit(&data->state, MCTP_ENABLED)) { + return; + } + + buf = usbd_ep_buf_alloc(c_data, CONFIG_USBD_MCTP_BULK_OUT_EP_ADDR, USBD_MAX_BULK_MPS); + if (buf == NULL) { + LOG_ERR("Failed to allocate OUT buffer"); + return; + } + + if (usbd_ep_enqueue(c_data, buf)) { + net_buf_unref(buf); + LOG_ERR("Failed to enqueu OUT buffer"); + } +} + +static int usbd_mctp_request(struct usbd_class_data *const c_data, struct net_buf *buf, int err) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + const struct device *dev = usbd_class_get_private(c_data); + struct usbd_mctp_data *data = dev->data; + const struct mctp_ops *ops = data->ops; + + struct udc_buf_info *bi = udc_get_buf_info(buf); + + if (bi->ep == CONFIG_USBD_MCTP_BULK_OUT_EP_ADDR) { + if (ops->data_recv_cb != NULL) { + ops->data_recv_cb(dev, buf->data, buf->len, data->user_data); + } + + k_work_submit(&data->out_work); + } + + if (bi->ep == CONFIG_USBD_MCTP_BULK_IN_EP_ADDR) { + if (ops->tx_complete_cb != NULL) { + ops->tx_complete_cb(dev, data->user_data); + } else { + k_sem_give(&data->in_sem); + } + } + + return usbd_ep_buf_free(uds_ctx, buf); +} + +static void *usbd_mctp_get_desc(struct usbd_class_data *const c_data, const enum usbd_speed speed) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct usbd_mctp_config *config = dev->config; + +#if USBD_SUPPORTS_HIGH_SPEED + if (speed == USBD_SPEED_HS) { + return config->hs_desc; + } +#endif + + return config->fs_desc; +} + +static void usbd_mctp_enable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct usbd_mctp_data *data = dev->data; + + if (!atomic_test_and_set_bit(&data->state, MCTP_ENABLED)) { + k_work_submit(&data->out_work); + } + + LOG_DBG("Enabled %s", c_data->name); +} + +static void usbd_mctp_disable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct usbd_mctp_data *data = dev->data; + + atomic_clear_bit(&data->state, MCTP_ENABLED); + + LOG_DBG("Disabled %s", c_data->name); +} + +static int usbd_mctp_init(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct usbd_mctp_data *data = dev->data; + + k_sem_init(&data->in_sem, 1, 1); + k_work_init(&data->out_work, usbd_mctp_out_work); + + LOG_DBG("MCTP device %s initialized", dev->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}; + +void usbd_mctp_set_ops(const struct device *dev, const struct mctp_ops *ops, void *user_data) +{ + struct usbd_mctp_data *data = dev->data; + + __ASSERT(ops->data_recv_cb, "data_recv_cb is mandatory"); + + data->ops = ops; + data->user_data = user_data; +} + +int usbd_mctp_send(const struct device *dev, void *data, uint16_t size) +{ + struct usbd_mctp_data *ddata = dev->data; + const struct usbd_mctp_config *config = dev->config; + struct usbd_class_data *c_data = config->c_data; + struct net_buf *buf = NULL; + int err; + + if (!atomic_test_bit(&ddata->state, MCTP_ENABLED)) { + return -EPERM; + } + + buf = usbd_ep_buf_alloc(c_data, CONFIG_USBD_MCTP_BULK_IN_EP_ADDR, size); + if (buf == NULL) { + 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) { + LOG_ERR("Failed to enqueue IN buffer"); + net_buf_unref(buf); + return err; + } + + /* If there is no TX complete defined, use the semaphore. */ + if (ddata->ops->tx_complete_cb == NULL) { + k_sem_take(&ddata->in_sem, K_FOREVER); + } + + return 0; +} + +#define USBD_MCTP_DEFINE_DESCRIPTORS(n) \ + static struct usbd_mctp_desc usbd_mctp_desc_##n = { \ + .iad = \ + { \ + .bLength = sizeof(struct usb_association_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE_ASSOC, \ + .bFirstInterface = 0, \ + .bInterfaceCount = 1, \ + .bFunctionClass = USB_BCC_MCTP, \ + .bFunctionSubClass = CONFIG_USBD_MCTP_INTERFACE_SUBCLASS, \ + .bFunctionProtocol = CONFIG_USBD_MCTP_INTERFACE_PROTOCOL, \ + .iFunction = 0, \ + }, \ + .if0 = {.bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_BCC_MCTP, \ + .bInterfaceSubClass = CONFIG_USBD_MCTP_INTERFACE_SUBCLASS, \ + .bInterfaceProtocol = CONFIG_USBD_MCTP_INTERFACE_PROTOCOL, \ + .iInterface = 0}, \ + .if0_fs_out_ep = {.bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = CONFIG_USBD_MCTP_BULK_OUT_EP_ADDR, \ + .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 = CONFIG_USBD_MCTP_BULK_IN_EP_ADDR, \ + .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 = CONFIG_USBD_MCTP_BULK_OUT_EP_ADDR, \ + .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 = CONFIG_USBD_MCTP_BULK_IN_EP_ADDR, \ + .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.iad, \ + (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.iad, \ + (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 USBD_MCTP_DT_DEVICE_DEFINE(n) \ + BUILD_ASSERT(DT_INST_ON_BUS(n, usb), \ + "node " DT_NODE_PATH( \ + DT_DRV_INST(n)) " is not assigned to a USB device controller"); \ + \ + USBD_MCTP_DEFINE_DESCRIPTORS(n); \ + \ + USBD_DEFINE_CLASS(mctp_##n, &usbd_mctp_api, (void *)DEVICE_DT_GET(DT_DRV_INST(n)), NULL); \ + \ + static const struct usbd_mctp_config mctp_config_##n = {.c_data = &mctp_##n, \ + .desc = &usbd_mctp_desc_##n, \ + .fs_desc = usbd_mctp_fs_desc_##n, \ + .hs_desc = usbd_mctp_hs_desc_##n}; \ + \ + static struct usbd_mctp_data mctp_data_##n = { \ + .dev = DEVICE_DT_GET(DT_DRV_INST(n)), .ops = NULL, .user_data = NULL, .state = 0}; \ + \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, &mctp_data_##n, &mctp_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL); + +DT_INST_FOREACH_STATUS_OKAY(USBD_MCTP_DT_DEVICE_DEFINE); From ee96a806185f3780a143d094bf78e2a9b0c457a8 Mon Sep 17 00:00:00 2001 From: Eric Ocasio Date: Fri, 29 Aug 2025 09:33:43 -0500 Subject: [PATCH 2/3] pcmi: mctp: USB endpoint binding Adds support for the USB device binding for MCTP. Binding created based on the DMTF specification for USB Transport Binding. The overall design of this binding follows the existing UART and I2C+GPIO bindings that already exist in Zephyr. This binding interacts with the new USBD_MCTP device class that is part of this PR. Signed-off-by: Eric Ocasio --- dts/bindings/pmci/mctp/usb.yaml | 13 ++ include/zephyr/pmci/mctp/mctp_usb.h | 68 +++++++++++ subsys/pmci/mctp/CMakeLists.txt | 1 + subsys/pmci/mctp/Kconfig | 9 ++ subsys/pmci/mctp/Kconfig.usb | 12 ++ subsys/pmci/mctp/mctp_usb.c | 176 ++++++++++++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 dts/bindings/pmci/mctp/usb.yaml create mode 100644 include/zephyr/pmci/mctp/mctp_usb.h create mode 100644 subsys/pmci/mctp/Kconfig.usb create mode 100644 subsys/pmci/mctp/mctp_usb.c 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..9bd8a4023fc61 --- /dev/null +++ b/include/zephyr/pmci/mctp/mctp_usb.h @@ -0,0 +1,68 @@ +/* + * 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 + +#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 device *usb_dev; + uint8_t endpoint_id; + uint8_t tx_buf[MCTP_USB_HEADER_SIZE + MCTP_USB_MAX_PACKET_LENGTH]; + struct k_sem tx_sem; + 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 */ +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_DT_DEFINE(_name, _dev) \ + 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_dev = DEVICE_DT_GET(_dev), \ + .endpoint_id = DT_PROP_OR(_dev, endpoint_id, 0), \ + .rx_pkt = NULL, \ + .rx_data_idx = 0, \ + .rx_state = STATE_WAIT_HDR_DMTF0}; + +#endif /* ZEPHYR_MCTP_USB_H_ */ 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..db9755088a1aa 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,17 @@ 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 USB_DEVICE_STACK_NEXT + 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..073ab939452e8 --- /dev/null +++ b/subsys/pmci/mctp/mctp_usb.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024 Intel Corporation + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#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 + +void (*rx_timeout_handler)(struct k_work *work); + +struct mctp_usb_header { + uint8_t dmtf0; +}; + +static inline struct mctp_binding_usb *binding_to_usb(struct mctp_binding *binding) +{ + return (struct mctp_binding_usb *)binding; +} + +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; +} + +static void mctp_usb_transport_data_recv_cb(const struct device *dev, void *buf, uint16_t size, + void *user_data) +{ + struct mctp_binding_usb *usb = (struct mctp_binding_usb *)user_data; + 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: +} + +static void mctp_usb_transport_tx_complete_cb(const struct device *dev, void *user_data) +{ + struct mctp_binding_usb *usb = (struct mctp_binding_usb *)user_data; + + k_sem_give(&usb->tx_sem); +} + +static struct mctp_ops mctp_usb_ops = {.data_recv_cb = mctp_usb_transport_data_recv_cb, + .tx_complete_cb = mctp_usb_transport_tx_complete_cb}; + +int mctp_usb_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt) +{ + struct mctp_binding_usb *usb = binding_to_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_sem, 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_dev, (void *)usb->tx_buf, len + MCTP_USB_HEADER_SIZE); + if (err != 0) { + LOG_ERR("Failed to send MCTP data over USB"); + return err; + } + + return 0; +} + +int mctp_usb_start(struct mctp_binding *binding) +{ + struct mctp_binding_usb *usb = binding_to_usb(binding); + + k_sem_init(&usb->tx_sem, 1, 1); + + usbd_mctp_set_ops(usb->usb_dev, &mctp_usb_ops, binding); + + mctp_binding_set_tx_enabled(binding, true); + + return 0; +} From 8c07b955bccf979ab2107f322c0133c007a6215c Mon Sep 17 00:00:00 2001 From: Eric Ocasio Date: Fri, 29 Aug 2025 09:34:04 -0500 Subject: [PATCH 3/3] samples: Create MCTP over USB sample Based on the existing sample applications for MCTP, this new sample demonstrates how to use MCTP using the USB device binding. Signed-off-by: Eric Ocasio --- .../pmci/mctp/usb_endpoint/CMakeLists.txt | 9 ++ .../modules/pmci/mctp/usb_endpoint/Kconfig | 9 ++ .../modules/pmci/mctp/usb_endpoint/README.rst | 113 ++++++++++++++++++ .../pmci/mctp/usb_endpoint/app.overlay | 12 ++ .../modules/pmci/mctp/usb_endpoint/prj.conf | 11 ++ .../modules/pmci/mctp/usb_endpoint/src/main.c | 109 +++++++++++++++++ 6 files changed, 263 insertions(+) create mode 100644 samples/modules/pmci/mctp/usb_endpoint/CMakeLists.txt create mode 100644 samples/modules/pmci/mctp/usb_endpoint/Kconfig create mode 100644 samples/modules/pmci/mctp/usb_endpoint/README.rst create mode 100644 samples/modules/pmci/mctp/usb_endpoint/app.overlay create mode 100644 samples/modules/pmci/mctp/usb_endpoint/prj.conf create mode 100644 samples/modules/pmci/mctp/usb_endpoint/src/main.c 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..2c0f2a302fdcf --- /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_endpoint) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) 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..532ea4023541e --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/README.rst @@ -0,0 +1,113 @@ +.. 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 below. + +.. code-block:: python + + 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") + + # 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") + + 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\x4c\x65\x74\x27\x73\x20\x74\x65\x73\x74\x20\x61\x20\x6d\x75\x6c\x74\x69\x2d\x70\x61\x63\x6b\x65\x74\x20\x6d\x65\x73\x73\x61\x67\x65\x20\x74\x6f\x20\x73\x65\x65\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\x31\x00\x1a\xb4\x00\x12\x01\x0a\x14\xc0\x48\x65\x6c\x6c\x6f\x2c\x20\x4d\x43\x58\x2c\x20\x32\x00') + response = usb_dev.receive_data() + print("Received:", bytes(response)) + print("Received:", bytes(response)) + finally: + usb_dev.disconnect() + +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..d1522d9574fd8 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/app.overlay @@ -0,0 +1,12 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&zephyr_udc0 { + mctp_usb: mctp_usb { + 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..8915c02bbd1da --- /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_ERR=y +CONFIG_USBD_LOG_LEVEL_ERR=y +CONFIG_UDC_DRIVER_LOG_LEVEL_ERR=y 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..89007c3e151e8 --- /dev/null +++ b/samples/modules/pmci/mctp/usb_endpoint/src/main.c @@ -0,0 +1,109 @@ +/* + * 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_DT_DEFINE(mctp_endpoint, DT_NODELABEL(mctp_usb)); + +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", mctp_endpoint.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, &mctp_endpoint.binding, mctp_endpoint.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; +}