blob: ef2a4a4c5c4df2cafbb5c5019bf74a72271c4763 [file] [log] [blame]
/*
* Copyright (c) 2024 Intel Corporation
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <zephyr/sys/__assert.h>
#include <zephyr/kernel.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/usb/usb_ch9.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/pmci/mctp/mctp_usb.h>
#include <zephyr/logging/log.h>
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
#define MCTP_USB_ENABLED 0
#define MCTP_USB_NUM_INSTANCES CONFIG_MCTP_USB_CLASS_INSTANCES_COUNT
UDC_BUF_POOL_DEFINE(mctp_usb_ep_pool, MCTP_USB_NUM_INSTANCES * 2, USBD_MAX_BULK_MPS,
sizeof(struct udc_buf_info), NULL);
struct mctp_usb_class_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 mctp_usb_class_ctx {
struct usbd_class_data *class_data;
struct mctp_usb_class_desc *const desc;
const struct usb_desc_header **const fs_desc;
const struct usb_desc_header **const hs_desc;
struct mctp_usb_class_inst *inst;
uint8_t inst_idx;
struct net_buf *out_net_buf;
uint8_t out_buf[USBD_MAX_BULK_MPS];
struct k_work out_work;
atomic_t state;
};
static struct net_buf *mctp_usb_class_buf_alloc(const uint8_t ep)
{
struct net_buf *buf = NULL;
struct udc_buf_info *bi;
buf = net_buf_alloc(&mctp_usb_ep_pool, K_NO_WAIT);
if (!buf) {
return NULL;
}
bi = udc_get_buf_info(buf);
bi->ep = ep;
return buf;
}
static uint8_t mctp_usb_class_get_bulk_in(struct usbd_class_data *const c_data)
{
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
#if USBD_SUPPORTS_HIGH_SPEED
if (usbd_bus_speed(usbd_class_get_ctx(ctx->class_data)) == USBD_SPEED_HS) {
return ctx->desc->if0_hs_in_ep.bEndpointAddress;
}
#endif
return ctx->desc->if0_fs_in_ep.bEndpointAddress;
}
static uint8_t mctp_usb_class_get_bulk_out(struct usbd_class_data *const c_data)
{
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
#if USBD_SUPPORTS_HIGH_SPEED
if (usbd_bus_speed(usbd_class_get_ctx(ctx->class_data)) == USBD_SPEED_HS) {
return ctx->desc->if0_hs_out_ep.bEndpointAddress;
}
#endif
return ctx->desc->if0_fs_out_ep.bEndpointAddress;
}
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;
}
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);
struct usbd_class_data *c_data = usb->usb_class_data;
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
size_t len = mctp_pktbuf_size(pkt);
struct net_buf *buf = NULL;
int err;
if (!atomic_test_bit(&ctx->state, MCTP_USB_ENABLED)) {
return -EPERM;
}
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 + MCTP_USB_HEADER_SIZE, "buf = ");
if (usb->usb_class_data == NULL) {
LOG_ERR("MCTP instance not found");
return -ENODEV;
}
buf = mctp_usb_class_buf_alloc(mctp_usb_class_get_bulk_in(c_data));
if (buf == NULL) {
k_sem_give(&usb->tx_lock);
LOG_ERR("Failed to allocate IN buffer");
return -ENOMEM;
}
net_buf_add_mem(buf, usb->tx_buf, len + MCTP_USB_HEADER_SIZE);
err = usbd_ep_enqueue(c_data, buf);
if (err) {
k_sem_give(&usb->tx_lock);
LOG_ERR("Failed to enqueue IN buffer");
net_buf_unref(buf);
return err;
}
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;
}
static void mctp_usb_class_out_work(struct k_work *work)
{
struct mctp_usb_class_ctx *ctx = CONTAINER_OF(work, struct mctp_usb_class_ctx, out_work);
struct net_buf *buf;
size_t buf_size = 0;
if (!atomic_test_bit(&ctx->state, MCTP_USB_ENABLED)) {
return;
}
/* Move data from net_buf to our ctx buffer so we can receive another USB packet */
if (ctx->out_net_buf != NULL) {
buf_size = ctx->out_net_buf->len;
memcpy(ctx->out_buf, ctx->out_net_buf->data, ctx->out_net_buf->len);
/* Free the current buffer and allocate another for OUT */
net_buf_unref(ctx->out_net_buf);
}
buf = mctp_usb_class_buf_alloc(mctp_usb_class_get_bulk_out(ctx->class_data));
if (buf == NULL) {
LOG_ERR("Failed to allocate OUT buffer");
return;
}
if (usbd_ep_enqueue(ctx->class_data, buf)) {
net_buf_unref(buf);
LOG_ERR("Failed to enqueue OUT buffer");
}
/* Process the MCTP data */
struct mctp_binding_usb *usb = (struct mctp_binding_usb *)ctx->inst->mctp_binding;
LOG_DBG("size=%d", ctx->out_net_buf->len);
LOG_HEXDUMP_DBG(buf, ctx->out_net_buf->len, "buf = ");
for (int i = 0; i < buf_size; i++) {
switch (usb->rx_state) {
case STATE_WAIT_HDR_DMTF0: {
if (ctx->out_buf[i] == MCTP_USB_DMTF_0) {
usb->rx_state = STATE_WAIT_HDR_DMTF1;
} else {
LOG_ERR("Invalid DMTF0 %02X", ctx->out_buf[i]);
return;
}
break;
}
case STATE_WAIT_HDR_DMTF1: {
if (ctx->out_buf[i] == MCTP_USB_DMTF_1) {
usb->rx_state = STATE_WAIT_HDR_RSVD0;
} else {
LOG_ERR("Invalid DMTF1 %02X", ctx->out_buf[i]);
usb->rx_state = STATE_WAIT_HDR_DMTF0;
return;
}
break;
}
case STATE_WAIT_HDR_RSVD0: {
if (ctx->out_buf[i] == 0) {
usb->rx_state = STATE_WAIT_HDR_LEN;
} else {
LOG_ERR("Invalid RSVD0 %02X", ctx->out_buf[i]);
usb->rx_state = STATE_WAIT_HDR_DMTF0;
return;
}
break;
}
case STATE_WAIT_HDR_LEN: {
if (ctx->out_buf[i] > MCTP_USB_MAX_PACKET_LENGTH || ctx->out_buf[i] == 0) {
LOG_ERR("Invalid LEN %02X", ctx->out_buf[i]);
usb->rx_state = STATE_WAIT_HDR_DMTF0;
return;
}
usb->rx_data_idx = 0;
usb->rx_pkt = mctp_pktbuf_alloc(&usb->binding, ctx->out_buf[i]);
if (usb->rx_pkt == NULL) {
LOG_ERR("Could not allocate PKT buffer");
mctp_usb_reset_rx_state(usb);
return;
}
usb->rx_state = STATE_DATA;
LOG_DBG("Expecting LEN=%d", (int)ctx->out_buf[i]);
break;
}
case STATE_DATA: {
usb->rx_pkt->data[usb->rx_data_idx++] = ctx->out_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;
}
}
}
}
static int mctp_usb_class_request(struct usbd_class_data *const c_data, struct net_buf *buf,
int err)
{
struct mctp_usb_class_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);
}
goto exit;
}
if (bi->ep == mctp_usb_class_get_bulk_out(c_data)) {
ctx->out_net_buf = buf;
k_work_submit(&ctx->out_work);
}
if (bi->ep == mctp_usb_class_get_bulk_in(c_data)) {
k_sem_give(&ctx->inst->mctp_binding->tx_lock);
net_buf_unref(buf);
}
exit:
return 0;
}
static void *mctp_usb_class_get_desc(struct usbd_class_data *const c_data,
const enum usbd_speed speed)
{
struct mctp_usb_class_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 mctp_usb_class_enable(struct usbd_class_data *const c_data)
{
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
if (!atomic_test_and_set_bit(&ctx->state, MCTP_USB_ENABLED)) {
k_work_submit(&ctx->out_work);
}
LOG_DBG("Enabled %s", c_data->name);
}
static void mctp_usb_class_disable(struct usbd_class_data *const c_data)
{
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
atomic_clear_bit(&ctx->state, MCTP_USB_ENABLED);
LOG_DBG("Disabled %s", c_data->name);
}
static int mctp_usb_class_init(struct usbd_class_data *const c_data)
{
struct mctp_usb_class_ctx *ctx = usbd_class_get_private(c_data);
size_t num_instances = 0;
STRUCT_SECTION_COUNT(mctp_usb_class_inst, &num_instances);
if (num_instances != MCTP_USB_NUM_INSTANCES) {
LOG_ERR("The number of application instances (%d) does not match the number "
"specified by CONFIG_MCTP_USB_CLASS_INSTANCES_COUNT (%d)",
num_instances, MCTP_USB_NUM_INSTANCES);
return -EINVAL;
}
STRUCT_SECTION_GET(mctp_usb_class_inst, ctx->inst_idx, &ctx->inst);
ctx->class_data = c_data;
ctx->state = 0;
/* Share USB class data with the binding so that the binding */
ctx->inst->mctp_binding->usb_class_data = c_data;
k_work_init(&ctx->out_work, mctp_usb_class_out_work);
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->mctp_binding->binding.name);
return 0;
}
struct usbd_class_api mctp_usb_class_api = {
.request = mctp_usb_class_request,
.enable = mctp_usb_class_enable,
.disable = mctp_usb_class_disable,
.init = mctp_usb_class_init,
.get_desc = mctp_usb_class_get_desc,
};
#define DEFINE_MCTP_USB_CLASS_DESCRIPTORS(n, _) \
static struct mctp_usb_class_desc mctp_usb_class_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 *mctp_usb_class_fs_desc_##n[] = { \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0_fs_in_ep, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0_fs_out_ep, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.nil_desc \
}; \
\
const static struct usb_desc_header *mctp_usb_class_hs_desc_##n[] = { \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0_hs_in_ep, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.if0_hs_out_ep, \
(struct usb_desc_header *)&mctp_usb_class_desc_##n.nil_desc \
};
#define DEFINE_MCTP_USB_CLASS_DATA(n, _) \
static struct mctp_usb_class_ctx mctp_usb_class_ctx_##n = { \
.desc = &mctp_usb_class_desc_##n, \
.fs_desc = mctp_usb_class_fs_desc_##n, \
.hs_desc = mctp_usb_class_hs_desc_##n, \
.inst_idx = n, \
.out_net_buf = NULL \
}; \
\
USBD_DEFINE_CLASS(mctp_##n, &mctp_usb_class_api, &mctp_usb_class_ctx_##n, NULL);
LISTIFY(MCTP_USB_NUM_INSTANCES, DEFINE_MCTP_USB_CLASS_DESCRIPTORS, ())
LISTIFY(MCTP_USB_NUM_INSTANCES, DEFINE_MCTP_USB_CLASS_DATA, ())