[RFC PATCH qemu 5/5] usb/mctp/cxl: CXL FMAPI interface via MCTP over usb.

Jonathan Cameron via posted 5 patches 5 months, 1 week ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Peter Maydell <peter.maydell@linaro.org>, Jonathan Cameron <jonathan.cameron@huawei.com>, Fan Ni <fan.ni@samsung.com>, Klaus Jensen <k.jensen@samsung.com>, Corey Minyard <cminyard@mvista.com>, Jason Wang <jasowang@redhat.com>
[RFC PATCH qemu 5/5] usb/mctp/cxl: CXL FMAPI interface via MCTP over usb.
Posted by Jonathan Cameron via 5 months, 1 week ago
Given the challenges in testing systems with the previously
posted i2c based mctp support for x86 / arm64 machines (as only
emulated controller that is suitably capable is the aspeed one)
providing a USB interface greatly simplifies things.

This is a PoC of such an interface.

RFC reasons:
- Not all error conditions are fully checked / handled.
- MTU in the 'to host' direction is ignored.  Seems the Linux
  stack doesn't care about this today but that should be brought
  into compliance with the specification anyway.

Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
 include/hw/usb.h   |   1 +
 include/net/mctp.h |  77 +++++-
 hw/i2c/mctp.c      |  17 --
 hw/usb/dev-mctp.c  | 639 +++++++++++++++++++++++++++++++++++++++++++++
 hw/usb/Kconfig     |   5 +
 hw/usb/meson.build |   1 +
 6 files changed, 717 insertions(+), 23 deletions(-)

diff --git a/include/hw/usb.h b/include/hw/usb.h
index 26a9f3ecde..ae5f3f77cb 100644
--- a/include/hw/usb.h
+++ b/include/hw/usb.h
@@ -81,6 +81,7 @@
 #define USB_CLASS_CDC_DATA              0x0a
 #define USB_CLASS_CSCID                 0x0b
 #define USB_CLASS_CONTENT_SEC           0x0d
+#define USB_CLASS_MCTP                  0x14
 #define USB_CLASS_APP_SPEC              0xfe
 #define USB_CLASS_VENDOR_SPEC           0xff
 
diff --git a/include/net/mctp.h b/include/net/mctp.h
index 5d26d855db..f278b17ce2 100644
--- a/include/net/mctp.h
+++ b/include/net/mctp.h
@@ -3,21 +3,21 @@
 
 #include "hw/registerfields.h"
 
-/* DSP0236 1.3.0, Section 8.3.1 */
+/* DSP0236 1.3.3, Section 8.4.2 Baseline transmission unit */
 #define MCTP_BASELINE_MTU 64
 
-/* DSP0236 1.3.0, Table 1, Message body */
+/* DSP0236 1.3.3, Table 1, Message body */
 FIELD(MCTP_MESSAGE_H, TYPE, 0, 7)
 FIELD(MCTP_MESSAGE_H, IC,   7, 1)
 
-/* DSP0236 1.3.0, Table 1, MCTP transport header */
+/* DSP0236 1.3.3, Table 1, MCTP transport header */
 FIELD(MCTP_H_FLAGS, TAG,    0, 3);
 FIELD(MCTP_H_FLAGS, TO,     3, 1);
 FIELD(MCTP_H_FLAGS, PKTSEQ, 4, 2);
 FIELD(MCTP_H_FLAGS, EOM,    6, 1);
 FIELD(MCTP_H_FLAGS, SOM,    7, 1);
 
-/* DSP0236 1.3.0, Figure 4 */
+/* DSP0236 1.3.3, Figure 4 Generic message fields */
 typedef struct MCTPPacketHeader {
     uint8_t version;
     struct {
@@ -25,11 +25,76 @@ typedef struct MCTPPacketHeader {
         uint8_t source;
     } eid;
     uint8_t flags;
-} MCTPPacketHeader;
+} QEMU_PACKED MCTPPacketHeader;
 
 typedef struct MCTPPacket {
     MCTPPacketHeader hdr;
     uint8_t          payload[];
-} MCTPPacket;
+} QEMU_PACKED MCTPPacket;
+
+/* DSP0236 1.3.3, Figure 20 MCTP control message format */
+typedef struct MCTPControlMessage {
+#define MCTP_MESSAGE_TYPE_CONTROL 0x0
+    uint8_t type;
+#define MCTP_CONTROL_FLAGS_RQ               (1 << 7)
+#define MCTP_CONTROL_FLAGS_D                (1 << 6)
+    uint8_t flags;
+    uint8_t command_code;
+    uint8_t data[];
+} QEMU_PACKED MCTPControlMessage;
+
+enum MCTPControlCommandCodes {
+    MCTP_CONTROL_SET_EID                    = 0x01,
+    MCTP_CONTROL_GET_EID                    = 0x02,
+    MCTP_CONTROL_GET_UUID                   = 0x03,
+    MCTP_CONTROL_GET_VERSION                = 0x04,
+    MCTP_CONTROL_GET_MESSAGE_TYPE_SUPPORT   = 0x05,
+};
+
+/* DSP0236 1.3.3, Table 13 MCTP control message completion codes */
+#define MCTP_CONTROL_CC_SUCCESS                 0x0
+#define MCTP_CONTROL_CC_ERROR                   0x1
+#define MCTP_CONTROL_CC_ERROR_INVALID_DATA      0x2
+#define MCTP_CONTROL_CC_ERROR_INVALID_LENGTH    0x3
+#define MCTP_CONTROL_CC_ERROR_NOT_READY         0x4
+#define MCTP_CONTROL_CC_ERROR_UNSUP_COMMAND     0x5
+
+typedef struct MCTPControlErrRsp {
+    uint8_t completion_code;
+} MCTPControlErrRsp;
+
+/* DSP0236 1.3.3 Table 14 - Set Endpoint ID message */
+typedef struct MCTPControlSetEIDReq {
+    uint8_t operation;
+    uint8_t eid;
+} MCTPControlSetEIDReq;
+
+typedef struct MCTPControlSetEIDRsp {
+    uint8_t completion_code;
+    uint8_t operation_result; /* Not named in spec */
+    uint8_t eid_setting;
+    uint8_t eid_pool_size;
+} MCTPControlSetEIDRsp;
+
+/* DSP0236 1.3.3 Table 15 - Get Endpoint ID message */
+typedef struct MCTPControlGetEIDRsp {
+    uint8_t completion_code;
+    uint8_t endpoint_id;
+    uint8_t endpoint_type;
+    uint8_t medium_specific_info;
+} MCTPControlGetEIDRsp;
+
+/* DSP0236 1.3.3 Table 16 - Get Endpoint UUID message format */
+typedef struct MCTPControlGetUUIDRsp {
+    uint8_t completion_code;
+    uint8_t uuid[0x10];
+} MCTPControlGetUUIDRsp;
+
+/* DSP0236 1.3.3 Table 19 - Get Message Type Support message */
+typedef struct MCTPControlGetMessageTypeRsp {
+    uint8_t completion_code;
+    uint8_t message_type_count;
+    uint8_t types[];
+} MCTPControlGetMessageTypeRsp;
 
 #endif /* QEMU_MCTP_H */
diff --git a/hw/i2c/mctp.c b/hw/i2c/mctp.c
index cf45a46706..ac9bebb3c4 100644
--- a/hw/i2c/mctp.c
+++ b/hw/i2c/mctp.c
@@ -33,23 +33,6 @@ typedef struct MCTPI2CPacket {
 #define i2c_mctp_payload_offset offsetof(MCTPI2CPacket, mctp.payload)
 #define i2c_mctp_payload(buf) (buf + i2c_mctp_payload_offset)
 
-/* DSP0236 1.3.0, Figure 20 */
-typedef struct MCTPControlMessage {
-#define MCTP_MESSAGE_TYPE_CONTROL 0x0
-    uint8_t type;
-#define MCTP_CONTROL_FLAGS_RQ               (1 << 7)
-#define MCTP_CONTROL_FLAGS_D                (1 << 6)
-    uint8_t flags;
-    uint8_t command_code;
-    uint8_t data[];
-} MCTPControlMessage;
-
-enum MCTPControlCommandCodes {
-    MCTP_CONTROL_SET_EID                    = 0x01,
-    MCTP_CONTROL_GET_EID                    = 0x02,
-    MCTP_CONTROL_GET_VERSION                = 0x04,
-    MCTP_CONTROL_GET_MESSAGE_TYPE_SUPPORT   = 0x05,
-};
 
 #define MCTP_CONTROL_ERROR_UNSUPPORTED_CMD 0x5
 
diff --git a/hw/usb/dev-mctp.c b/hw/usb/dev-mctp.c
new file mode 100644
index 0000000000..aafb9e7e96
--- /dev/null
+++ b/hw/usb/dev-mctp.c
@@ -0,0 +1,639 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright Huawei 2025
+ *
+ * Author: Jonathan Cameron <jonathan.cameron@huawei.com>
+ *
+ * CXL MCTP device
+ *
+ * TODO:
+ * - Respect MTU on packets being sent to host. For now it work in Linux but
+ *   who knows longer term.
+ * - More complete sanity checking of command flags etc.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+
+#include "hw/pci/pci_device.h"
+#include "hw/qdev-properties.h"
+#include "hw/cxl/cxl.h"
+#include "hw/pci-bridge/cxl_upstream_port.h"
+#include "hw/pci/pcie.h"
+#include "hw/pci/pcie_port.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "net/mctp.h"
+
+/* TODO: Move to header */
+
+/* DMTF DSP0234 CXL Fabric Manager API over MCTP Binding Specification */
+#define MCTP_MT_CXL_FMAPI 0x7
+/*
+ * DMTF DSP0281 CXL Type 3 Device Component Command Interface over MCTP
+ * Binding Specification
+ */
+#define MCTP_MT_CXL_TYPE3 0x8
+typedef struct CXLMCTPMessage {
+    /*
+     * DSP0236 (MCTP Base) Integrity Check + Message Type
+     * DSP0234/DSP0281 (CXL bindings) state no Integrity Check
+     * so just the message type.
+     */
+    uint8_t message_type;
+    /* Remaining fields from CXL r3.0 Table 7-14 CCI Message Format */
+    uint8_t category;
+    uint8_t tag;
+    uint8_t rsvd;
+    /*
+     * CXL r3.0 - Table 8-36 Generic Component Command Opcodes:
+     * Command opcode is split into two sub fields
+     */
+    uint8_t command;
+    uint8_t command_set;
+    /* Only bits 4:0 of pl_length[2] are part of the length */
+    uint8_t pl_length[3];
+    uint16_t rc;
+    uint16_t vendor_status;
+    uint8_t payload[];
+} QEMU_PACKED CXLMCTPMessage;
+
+/* DSP0283 1.0.0 Figure 5 */
+typedef struct MCTPUSBPacket {
+    uint16_t dmtf_id;
+    uint8_t resv;
+    uint8_t length;
+    /* Do we see the header? */
+    MCTPPacket mctp;
+} MCTPUSBPacket;
+
+#define MCTP_CXL_MAILBOX_BYTES 128
+
+enum cxl_dev_type {
+    cxl_type3,
+    cxl_switch,
+};
+
+typedef struct USBCXLMCTPState {
+    USBDevice dev;
+    PCIDevice *target;
+    CXLCCI *cci;
+    enum cxl_dev_type type;
+    USBPacket *cached_tohost;
+    USBPacket *cached_fromhost;
+    uint8_t my_eid;
+    bool building_input;
+/* TODO, match size to what we report as acceptable */
+#define MCTPUSBCXL_MAX_SIZE 1024 * 1024
+    MCTPUSBPacket *pack0; /* Current incoming packet */
+    MCTPUSBPacket *pack; /* Aggregate packet - what we'd get with large MTU */
+} USBCLXMCTState;
+
+#define TYPE_USB_CXL_MCTP "usb-cxl-mctp"
+OBJECT_DECLARE_SIMPLE_TYPE(USBCXLMCTPState, USB_CXL_MCTP)
+
+enum {
+    STR_MANUFACTURER = 1,
+    STR_PRODUCT,
+    STR_SERIALNUMBER,
+    STR_MCTP,
+    STR_CONFIG_FULL,
+    STR_CONFIG_HIGH,
+    STR_CONFIG_SUPER,
+};
+
+static const USBDescStrings desc_strings = {
+    [STR_MANUFACTURER] = "QEMU",
+    [STR_PRODUCT] = "QEMU CXL MCTP",
+    [STR_SERIALNUMBER] = "34618",
+    [STR_MCTP] = "MCTP",
+    [STR_CONFIG_FULL] = "Full speed config (usb 1.1)",
+    [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+    [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)",
+};
+
+static const USBDescIface desc_iface_full = {
+    .bInterfaceNumber = 0,
+    .bNumEndpoints = 2,
+    .bInterfaceClass = USB_CLASS_MCTP,
+    .bInterfaceSubClass = 0x0,
+    .bInterfaceProtocol = 0x1,
+    .iInterface = STR_MCTP,
+    .eps = (USBDescEndpoint[]) {
+        {
+             /* DSP0283 6.1.4.2.1 Out Bulk endpoint descriptor */
+            .bEndpointAddress = USB_DIR_OUT | 0x1,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = 512,
+            .bInterval = 0x1,
+        }, {
+            /* DSP0283 6.1.4.2.2 In Bulk endpoint descriptor */
+            .bEndpointAddress = USB_DIR_IN | 0x1,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = 512,
+            .bInterval = 0x1,
+        },
+    },
+};
+
+static const USBDescDevice desc_device_full = {
+    .bcdUSB = 0x200,
+    .bMaxPacketSize0 = 8,
+    .bNumConfigurations = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces = 1,
+            .bConfigurationValue = 1,
+            .iConfiguration = STR_CONFIG_FULL,
+            .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP,
+            .bMaxPower = 2,
+            .nif = 1,
+            .ifs = &desc_iface_full,
+        },
+    },
+};
+
+static const USBDescMSOS desc_msos = {
+    .CompatibleID = "MCTP",
+    .SelectiveSuspendEnabled = true,
+};
+
+static const USBDesc desc = {
+    .id = {
+        .idVendor = 0x46f4, /* CRC16() of "QEMU" */
+        .idProduct = 0x0006,
+        .bcdDevice = 0,
+        .iManufacturer = STR_MANUFACTURER,
+        .iSerialNumber = STR_SERIALNUMBER,
+    },
+    .full = &desc_device_full,
+    .high = &desc_device_full,
+    .str = desc_strings,
+    .msos = &desc_msos,
+};
+
+static void usb_cxl_mctp_handle_reset(USBDevice *dev)
+{
+    USBCXLMCTPState *s = USB_CXL_MCTP(dev);
+
+    s->cached_tohost = NULL;
+    s->cached_fromhost = NULL;
+    s->building_input = 0;
+    s->my_eid = 0;
+}
+
+static void usb_cxl_mctp_handle_control(USBDevice *dev, USBPacket *p,
+                                        int request, int value, int index,
+                                        int length, uint8_t *data)
+{
+    usb_desc_handle_control(dev, p, request, value, index, length, data);
+}
+
+/* A lot of fields are the same for all control responses */
+static void usb_mctp_fill_common(MCTPUSBPacket *o_usb_pkt,
+                                 MCTPUSBPacket *i_usb_pkt,
+                                 uint8_t usb_pkt_len)
+{
+    MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+    uint8_t tag, flags;
+
+    o_usb_pkt->dmtf_id = cpu_to_be16(0x1AB4);
+    o_usb_pkt->length = usb_pkt_len;
+
+    o_mctp_pkt->hdr.version = 1;
+    o_mctp_pkt->hdr.eid.dest =  i_usb_pkt->mctp.hdr.eid.source;
+    o_mctp_pkt->hdr.eid.source = i_usb_pkt->mctp.hdr.eid.dest;
+
+    tag = FIELD_EX8(i_usb_pkt->mctp.hdr.flags, MCTP_H_FLAGS, TAG);
+
+    flags = FIELD_DP8(0, MCTP_H_FLAGS, PKTSEQ, 0);
+    flags = FIELD_DP8(flags, MCTP_H_FLAGS, TAG, tag);
+    flags = FIELD_DP8(flags, MCTP_H_FLAGS, SOM, 1);
+    flags = FIELD_DP8(flags, MCTP_H_FLAGS, EOM, 1);
+    o_mctp_pkt->hdr.flags = flags;
+}
+
+static MCTPUSBPacket *usb_mctp_handle_control(USBCXLMCTPState *s,
+                                              MCTPControlMessage *ctrlmsg,
+                                              USBPacket *p)
+{
+    switch (ctrlmsg->command_code) {
+    case MCTP_CONTROL_SET_EID: {
+        MCTPControlSetEIDReq *req = (MCTPControlSetEIDReq *)(ctrlmsg->data);
+        uint8_t usb_pkt_len = sizeof(MCTPUSBPacket) +
+            sizeof(MCTPControlMessage) +
+            sizeof(MCTPControlSetEIDRsp);
+        MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        MCTPControlMessage *o_mctp_ctrl =
+            (MCTPControlMessage *)(o_mctp_pkt->payload);
+        MCTPControlSetEIDRsp *rsp = (MCTPControlSetEIDRsp *)o_mctp_ctrl->data;
+
+        /* Todo - check flags in request */
+        s->my_eid = req->eid;
+
+        o_mctp_ctrl->type = 0;
+        o_mctp_ctrl->command_code = ctrlmsg->command_code;
+        o_mctp_ctrl->flags = ctrlmsg->flags &
+            ~(MCTP_CONTROL_FLAGS_RQ | MCTP_CONTROL_FLAGS_D);
+        *rsp = (MCTPControlSetEIDRsp) {
+            .completion_code = MCTP_CONTROL_CC_SUCCESS,
+            .operation_result = 0,
+            .eid_setting = s->my_eid,
+            .eid_pool_size = 0,
+        };
+
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+
+        return o_usb_pkt;
+    }
+    case MCTP_CONTROL_GET_EID: {
+        uint8_t usb_pkt_len = sizeof(MCTPUSBPacket) +
+            sizeof(MCTPControlMessage) +
+            sizeof(MCTPControlGetEIDRsp);
+        MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        MCTPControlMessage *o_mctp_ctrl =
+            (MCTPControlMessage *)(o_mctp_pkt->payload);
+        MCTPControlGetEIDRsp *rsp = (MCTPControlGetEIDRsp *)o_mctp_ctrl->data;
+
+        o_mctp_ctrl->type = 0;
+        o_mctp_ctrl->command_code = ctrlmsg->command_code;
+        o_mctp_ctrl->flags = ctrlmsg->flags &
+            ~(MCTP_CONTROL_FLAGS_RQ | MCTP_CONTROL_FLAGS_D);
+        *rsp = (MCTPControlGetEIDRsp) {
+            .completion_code = MCTP_CONTROL_CC_SUCCESS,
+            .endpoint_id = s->my_eid,
+            .endpoint_type = 0,
+            .medium_specific_info = 0,
+        };
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+
+        return o_usb_pkt;
+    }
+    case MCTP_CONTROL_GET_UUID: {
+        uint8_t usb_pkt_len = sizeof(MCTPUSBPacket) +
+            sizeof(MCTPControlMessage) +
+            sizeof(MCTPControlGetUUIDRsp);
+        MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        MCTPControlMessage *o_mctp_ctrl =
+            (MCTPControlMessage *)(o_mctp_pkt->payload);
+        MCTPControlGetUUIDRsp *rsp = (MCTPControlGetUUIDRsp *)o_mctp_ctrl->data;
+
+        o_mctp_ctrl->type = 0;
+        o_mctp_ctrl->command_code = ctrlmsg->command_code;
+        o_mctp_ctrl->flags = ctrlmsg->flags &
+            ~(MCTP_CONTROL_FLAGS_RQ | MCTP_CONTROL_FLAGS_D);
+        *rsp = (MCTPControlGetUUIDRsp) {
+            .completion_code = MCTP_CONTROL_CC_SUCCESS,
+            /* TODO: Fill in uuid */
+        };
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+
+        return o_usb_pkt;
+    }
+    case MCTP_CONTROL_GET_MESSAGE_TYPE_SUPPORT: {
+        const uint8_t types[] = { MCTP_MT_CXL_FMAPI, MCTP_MT_CXL_TYPE3, };
+        uint8_t usb_pkt_len = sizeof(MCTPUSBPacket) +
+            sizeof(MCTPControlMessage) +
+            sizeof(MCTPControlGetMessageTypeRsp) +
+            sizeof(types);
+        MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        MCTPControlMessage *o_mctp_ctrl =
+            (MCTPControlMessage *)(o_mctp_pkt->payload);
+        MCTPControlGetMessageTypeRsp *rsp =
+            (MCTPControlGetMessageTypeRsp *)o_mctp_ctrl->data;
+
+        o_mctp_ctrl->type = 0;
+        o_mctp_ctrl->command_code = ctrlmsg->command_code;
+        o_mctp_ctrl->flags = ctrlmsg->flags &
+            ~(MCTP_CONTROL_FLAGS_RQ | MCTP_CONTROL_FLAGS_D);
+        *rsp = (MCTPControlGetMessageTypeRsp) {
+            .completion_code = MCTP_CONTROL_CC_SUCCESS,
+            .message_type_count = sizeof(types),
+        };
+        memcpy(rsp->types, types, sizeof(types));
+
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+
+        return o_usb_pkt;
+    }
+    default: {
+        uint8_t usb_pkt_len = sizeof(MCTPUSBPacket) +
+            sizeof(MCTPControlMessage) +
+            sizeof(MCTPControlErrRsp);
+        MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        MCTPControlMessage *o_mctp_ctrl =
+            (MCTPControlMessage *)(o_mctp_pkt->payload);
+        MCTPControlErrRsp *rsp = (MCTPControlErrRsp *)o_mctp_ctrl->data;
+
+        o_mctp_ctrl->type = 0;
+        o_mctp_ctrl->command_code = ctrlmsg->command_code;
+        o_mctp_ctrl->flags = ctrlmsg->flags &
+            ~(MCTP_CONTROL_FLAGS_RQ | MCTP_CONTROL_FLAGS_D);
+        *rsp = (MCTPControlErrRsp) {
+            .completion_code = MCTP_CONTROL_CC_ERROR_UNSUP_COMMAND,
+        };
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+        return o_usb_pkt;
+    }
+    }
+}
+
+static void usb_cxl_mctp_handle_data(USBDevice *dev, USBPacket *p)
+{
+    USBCXLMCTPState *s = USB_CXL_MCTP(dev);
+    USBPacket *tohost, *fromhost;
+    uint8_t message_type;
+    bool som, eom;
+
+    /*
+     * In and out on EP 0x1: anything else is bug.
+     * Not sure what right answer is.
+     */
+    if (p->ep->nr != 1) {
+        p->status = USB_RET_STALL;
+        return;
+    }
+
+    /*
+     * Conservative approach - don't proceed until we have at least one packet
+     * in each direction. For fragmented messages cases we only need this to be
+     * true for the EOM packet (potential optimization).
+     */
+    if (p->pid == USB_TOKEN_IN) { /* Direction from point of view of host */
+        tohost = p;
+        if (s->cached_fromhost == NULL) {
+            s->cached_tohost = tohost;
+            tohost->status = USB_RET_ASYNC;
+            return;
+        }
+        fromhost = s->cached_fromhost;
+    } else {
+        fromhost = p;
+        if (s->cached_tohost == NULL) {
+            s->cached_fromhost = fromhost;
+            fromhost->status = USB_RET_ASYNC;
+            return;
+        }
+        tohost = s->cached_tohost;
+    }
+
+    /* DSP0236 1.3.3 section 8.7 Dropped packets, physical layer errors */
+    if (fromhost->iov.size < sizeof(s->pack->mctp)) {
+        goto err_drop;
+    }
+
+    usb_packet_copy(fromhost, s->pack0, fromhost->iov.size);
+    /* DSP0236 1.3.3 section 8.7 Dropped packets, physical layer errors */
+    if ((be16_to_cpu(s->pack0->dmtf_id) != 0x1AB4) ||
+        (fromhost->iov.size != s->pack0->length)) {
+        goto err_drop;
+    }
+
+    eom = FIELD_EX8(s->pack0->mctp.hdr.flags, MCTP_H_FLAGS, EOM);
+    som = FIELD_EX8(s->pack0->mctp.hdr.flags, MCTP_H_FLAGS, SOM);
+
+    /* DSP0236 1.3.3 section 8.7 Dropped packets, unexpected middle or end */
+    if (!som && !s->building_input) {
+        goto err_drop;
+    }
+
+    if (som) {
+        /* Note repeated SOM without EOM is not an error */
+        s->building_input = true;
+        /* Put first part of full message in place */
+        memcpy(s->pack, s->pack0, s->pack0->length);
+    } else {
+        size_t additional_len;
+
+        additional_len = s->pack0->length - 8;
+        memcpy((void *)(s->pack) + s->pack->length, s->pack0->mctp.payload,
+               additional_len);
+        s->pack->length += additional_len;
+    }
+
+    if (eom) {
+        s->building_input = false;
+    } else { /* More to come so complete message from host to let it flow */
+        if (p->pid == USB_TOKEN_IN) {
+            /* Hold the tohost packet */
+            p->status = USB_RET_ASYNC;
+            s->cached_tohost = p;
+            /* Release the fromhost packet */
+            fromhost->status = USB_RET_SUCCESS;
+            s->cached_fromhost = NULL;
+            usb_packet_complete(dev, fromhost);
+        } else {
+            /* From host - handled synchronously */
+            p->status = USB_RET_SUCCESS;
+        }
+        return;
+    }
+
+    /* DSP0236 1.3.3 section 8.7 Dropped packets, bad header version */
+    if (s->pack->mctp.hdr.version != 1) {
+        goto err_drop;
+    }
+
+    /* DSP0236 1.3.3 section 8.7 Dropped packets, unknown eid */
+    if ((s->pack->mctp.hdr.eid.dest != s->my_eid) &&
+        (s->pack->mctp.hdr.eid.dest != 0)) {
+        goto err_drop;
+    }
+
+    message_type = s->pack->mctp.payload[0];
+    switch (message_type) {
+    case MCTP_MESSAGE_TYPE_CONTROL: {
+        MCTPUSBPacket *o_usb_pkt;
+        MCTPControlMessage *ctrlmsg =
+            (MCTPControlMessage *)(s->pack->mctp.payload);
+
+        /* DSP0236 1.3.3 section 8.7 Dropped packets, physical layer errors */
+        if (fromhost->iov.size < sizeof(s->pack->mctp) + sizeof(*ctrlmsg)) {
+            goto err_drop;
+        }
+
+        o_usb_pkt = usb_mctp_handle_control(s, ctrlmsg, tohost);
+
+        usb_packet_copy(tohost, o_usb_pkt, o_usb_pkt->length);
+        g_free(o_usb_pkt);
+
+        break;
+    }
+    case MCTP_MT_CXL_TYPE3:
+    case MCTP_MT_CXL_FMAPI: {
+        size_t usb_pkt_len = MCTPUSBCXL_MAX_SIZE;
+        g_autofree MCTPUSBPacket *o_usb_pkt = g_malloc0(usb_pkt_len);
+        MCTPPacket *o_mctp_pkt = &o_usb_pkt->mctp;
+        CXLMCTPMessage *rsp = (CXLMCTPMessage *)(o_mctp_pkt->payload);
+        CXLMCTPMessage *req = (CXLMCTPMessage *)(s->pack->mctp.payload);
+        bool bg_started;
+        size_t len_out = 0;
+        size_t len_in;
+        int rc;
+
+        *rsp = (CXLMCTPMessage) {
+            .message_type = req->message_type,
+            .category = 1,
+            .tag = req->tag,
+            .command = req->command,
+            .command_set = req->command_set,
+        };
+
+        /*
+         * As it was not immediately obvious from the various specifications,
+         * clarification was sort for which binding applies for which command
+         * set. The outcome was:
+         *
+         * Any command forming part of the CXL FM-API command set
+         * e.g. Present in CXL r3.0 Table 8-132: CXL FM API Command Opcodes
+         * (and equivalent in later CXL specifications) is valid only with
+         * the CXL Fabric Manager API over MCTP binding (DSP0234).
+         *
+         * Any other CXL command currently should be sent using the
+         * CXL Type 3 Device Component Command interface over MCTP binding,
+         * even if it is being sent to a switch.
+         *
+         * If tunneling is used, the component creating the PCIe VDMs must
+         * use the appropriate binding for sending the tunnel contents
+         * onwards.
+         */
+        if (!(req->message_type == MCTP_MT_CXL_TYPE3 &&
+              req->command_set < 0x51) &&
+            !(req->message_type == MCTP_MT_CXL_FMAPI &&
+              req->command_set >= 0x51 && req->command_set < 0x56)) {
+            len_out = 0;
+            usb_pkt_len = sizeof(MCTPUSBPacket) + sizeof(CXLMCTPMessage) +
+                len_out;
+            rsp->rc = CXL_MBOX_UNSUPPORTED;
+            st24_le_p(rsp->pl_length, len_out);
+
+            usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+            usb_packet_copy(tohost, o_usb_pkt, usb_pkt_len);
+            break;
+        }
+        /* TODO: add ld24_le_p */
+        len_in = req->pl_length[2] << 16 | req->pl_length[1] << 8 |
+            req->pl_length[0];
+        rc = cxl_process_cci_message(s->cci, req->command_set,
+                                     req->command,
+                                     len_in, req->payload,
+                                     &len_out,
+                                     (uint8_t *)(rsp + 1),
+                                     &bg_started);
+        rsp->rc = rc;
+        st24_le_p(rsp->pl_length, len_out);
+        usb_pkt_len = sizeof(MCTPUSBPacket) + sizeof(CXLMCTPMessage) +
+            len_out;
+
+        usb_mctp_fill_common(o_usb_pkt, s->pack, usb_pkt_len);
+        usb_packet_copy(tohost, o_usb_pkt, usb_pkt_len);
+        break;
+    }
+    default:
+        /*
+         * 8.9 Dropped messages - message type unsuported.
+         * Dropping after assembly
+         */
+        goto err_drop;
+    }
+    /* Something to send */
+    tohost->status = USB_RET_SUCCESS;
+    fromhost->status = USB_RET_SUCCESS;
+    if (p->pid == USB_TOKEN_IN) {
+        s->cached_fromhost = NULL;
+        usb_packet_complete(dev, fromhost);
+    } else {
+        s->cached_tohost = NULL;
+        usb_packet_complete(dev, tohost);
+    }
+    return;
+
+err_drop:
+    /* Reply with 'nothing' as dropping packet */
+    fromhost->status = USB_RET_SUCCESS;
+    if (p->pid == USB_TOKEN_IN) {
+        /* Hold the tohost packet */
+        tohost->status = USB_RET_ASYNC;
+        s->cached_tohost = p;
+        s->cached_fromhost = NULL;
+        usb_packet_complete(dev, fromhost);
+    }
+}
+
+static void usb_cxl_mctp_realize(USBDevice *dev, Error **errp)
+{
+    USBCXLMCTPState *s = USB_CXL_MCTP(dev);
+
+    s->pack = g_malloc0(MCTPUSBCXL_MAX_SIZE);
+    s->pack0 = g_malloc0(MCTPUSBCXL_MAX_SIZE);
+    usb_desc_create_serial(dev);
+    usb_desc_init(dev);
+
+    /* Check this is a type we support */
+    if (object_dynamic_cast(OBJECT(s->target), TYPE_CXL_USP)) {
+        CXLUpstreamPort *usp = CXL_USP(s->target);
+
+        s->type = cxl_switch;
+        s->cci = &usp->mctpcci;
+
+        cxl_initialize_usp_mctpcci(s->cci, DEVICE(s->target), DEVICE(dev),
+                                   MCTP_CXL_MAILBOX_BYTES);
+
+        return;
+    }
+
+    if (object_dynamic_cast(OBJECT(s->target), TYPE_CXL_TYPE3)) {
+        CXLType3Dev *ct3d = CXL_TYPE3(s->target);
+
+        s->type = cxl_type3;
+        s->cci = &ct3d->oob_mctp_cci;
+
+        cxl_initialize_t3_fm_owned_ld_mctpcci(s->cci, DEVICE(s->target),
+                                              DEVICE(dev),
+                                              MCTP_CXL_MAILBOX_BYTES);
+        return;
+    }
+
+    error_setg(errp, "Unhandled target type for CXL MCTP EP");
+}
+
+static const Property usb_cxl_mctp_properties[] = {
+    DEFINE_PROP_LINK("target", USBCXLMCTPState, target, TYPE_PCI_DEVICE,
+                     PCIDevice *),
+};
+
+static void usb_cxl_mctp_class_initfn(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+    uc->realize = usb_cxl_mctp_realize;
+    uc->product_desc = "QEMU USB CXL MCTP";
+    uc->usb_desc = &desc;
+    uc->handle_attach = usb_desc_attach;
+    uc->handle_reset = usb_cxl_mctp_handle_reset;
+    uc->handle_control = usb_cxl_mctp_handle_control;
+    uc->handle_data = usb_cxl_mctp_handle_data;
+    dc->desc = "USB CXL MCTP device";
+    dc->fw_name = "mctp";
+    device_class_set_props(dc, usb_cxl_mctp_properties);
+}
+
+static const TypeInfo usb_cxl_mctp_info = {
+    .name = TYPE_USB_CXL_MCTP,
+    .parent = TYPE_USB_DEVICE,
+    .instance_size = sizeof(USBCXLMCTPState),
+    .class_init = usb_cxl_mctp_class_initfn,
+};
+
+static void usb_cxl_mctp_register_types(void)
+{
+    type_register_static(&usb_cxl_mctp_info);
+}
+type_init(usb_cxl_mctp_register_types)
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig
index 69c663be52..4340475708 100644
--- a/hw/usb/Kconfig
+++ b/hw/usb/Kconfig
@@ -105,6 +105,11 @@ config USB_SERIAL
     default y
     depends on USB
 
+config USB_MCTP
+    bool
+    default y if CXL
+    depends on USB
+
 config USB_NETWORK
     bool
     default y
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index 17360a5b5a..c2910f9818 100644
--- a/hw/usb/meson.build
+++ b/hw/usb/meson.build
@@ -43,6 +43,7 @@ system_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: files('dev-uas.c'))
 system_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c'))
 system_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c'))
 system_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c'))
+system_ss.add(when: 'CONFIG_USB_MCTP', if_true: files('dev-mctp.c'))
 if host_os != 'windows'
   system_ss.add(when: 'CONFIG_USB_STORAGE_MTP', if_true: files('dev-mtp.c'))
 endif
-- 
2.48.1