| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/08 23:29 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"MCTP",
"MCTP_TRANSPORT_USB",
"MCTP_TRANSPORT_USBLIB"
],
"FocusSymbols": [
"mctp_usblib_rx_prepare",
"mctp_usblib_rx_complete",
"mctp_usblib_tx_push",
"mctp_usblib_tx_send_complete",
"mctp_usb_in_complete",
"mctp_usb_out_complete",
"mctp_usb_tx_send",
"mctp_usb_start_xmit",
"mctp_usb_rx_queue"
],
"Reasoning": "The patch introduces a new common library for MCTP-over-USB (`mctp-usblib`) and modifies the MCTP USB transport driver (`mctp-usb`) to use it. The changes are functional and reachable via USB gadget emulation (e.g., raw-gadget).",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/08 23:29 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d6da7744f259a2362ba413449d66e16101358e81\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jul 8 23:29:47 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig\nindex cf325ab0b1ef55..c40ac9c665b758 100644\n--- a/drivers/net/mctp/Kconfig\n+++ b/drivers/net/mctp/Kconfig\n@@ -47,9 +47,25 @@ config MCTP_TRANSPORT_I3C\n \t A MCTP protocol network device is created for each I3C bus\n \t having a \"mctp-controller\" devicetree property.\n \n+config MCTP_TRANSPORT_USBLIB\n+\ttristate \"MCTP over USB common library\"\n+\tdepends on USB\n+\thelp\n+\t Common protocol handling functions for MCTP-over-USB transport\n+\t implementations, suitable for use in either host- or gadget-side\n+\t transport driver\n+\n+\t This will be automatically enabled by the transport driver.\n+\n+config MCTP_TRANSPORT_USBLIB_TEST\n+ bool \"MCTP usblib tests\" if !KUNIT_ALL_TESTS\n+ depends on MCTP_TRANSPORT_USBLIB=y \u0026\u0026 KUNIT=y\n+ default KUNIT_ALL_TESTS\n+\n config MCTP_TRANSPORT_USB\n \ttristate \"MCTP USB transport\"\n \tdepends on USB\n+\tselect MCTP_TRANSPORT_USBLIB\n \thelp\n \t Provides a driver to access MCTP devices over USB transport,\n \t defined by DMTF specification DSP0283.\ndiff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile\nindex c36006849a1e7d..c870b62d3f1c95 100644\n--- a/drivers/net/mctp/Makefile\n+++ b/drivers/net/mctp/Makefile\n@@ -2,3 +2,4 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o\n obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o\n obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o\n obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o\n+obj-$(CONFIG_MCTP_TRANSPORT_USBLIB) += mctp-usblib.o\ndiff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c\nindex fade65f2f26995..2d31075374b45b 100644\n--- a/drivers/net/mctp/mctp-usb.c\n+++ b/drivers/net/mctp/mctp-usb.c\n@@ -3,9 +3,9 @@\n * mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver.\n *\n * DSP0283 is available at:\n- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf\n+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf\n *\n- * Copyright (C) 2024-2025 Code Construct Pty Ltd\n+ * Copyright (C) 2024-2026 Code Construct Pty Ltd\n */\n \n #include \u003clinux/module.h\u003e\n@@ -22,95 +22,115 @@\n struct mctp_usb {\n \tstruct usb_device *usbdev;\n \tstruct usb_interface *intf;\n+\tbool span;\n \n \tstruct net_device *netdev;\n \n \tu8 ep_in;\n \tu8 ep_out;\n \n-\tstruct urb *tx_urb;\n+\tstruct mctp_usblib_rx rx;\n \tstruct urb *rx_urb;\n+\tint in_err_count;\n+\tint in_err_orig;\n+\tbool clear_halt;\n \n \t/* enforces atomic access to rx_stopped and requeuing the retry work */\n \tspinlock_t rx_lock;\n \tbool rx_stopped;\n \tstruct delayed_work rx_retry_work;\n+\n+\tstruct mctp_usblib_tx tx;\n+\tstruct usb_anchor tx_anchor;\n+\t/* serialises tx_qmem updates to netdev queue states */\n+\tspinlock_t tx_qmem_lock;\n+\tint tx_qmem;\n+};\n+\n+enum {\n+\tMCTP_USB_SUBCLASS_BASE = 0x00,\n+\tMCTP_USB_SUBCLASS_SPAN = 0x02,\n };\n \n+/* We use a total-size limit for outstanding URBs, as the transfer counts\n+ * may vary a lot between spanning- and non-spanning modes. In spanning mode,\n+ * this will allow for a couple of max-sized transfers to be in flight. In\n+ * non-spanning mode, 32.\n+ *\n+ * We want to avoid disabling the tx queue if possible; doing so will end up\n+ * requeueing to gso_skb, and we only dequeue from that one skb at a time,\n+ * so can no longer perform transfer packing.\n+ */\n+static const unsigned int TX_QMEM_MAX = 16384;\n+\n static void mctp_usb_out_complete(struct urb *urb)\n {\n-\tstruct sk_buff *skb = urb-\u003econtext;\n-\tstruct net_device *netdev = skb-\u003edev;\n-\tint status;\n+\tstruct mctp_usblib_tx_ctx *tx_ctx = urb-\u003econtext;\n+\tstruct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);\n+\tunsigned int len = urb-\u003etransfer_buffer_length;\n+\tstruct net_device *netdev = mctp_usb-\u003enetdev;\n+\tunsigned long flags;\n \n-\tstatus = urb-\u003estatus;\n+\tmctp_usblib_tx_send_complete(tx_ctx, netdev, urb-\u003estatus == 0);\n \n-\tswitch (status) {\n-\tcase -ENOENT:\n-\tcase -ECONNRESET:\n-\tcase -ESHUTDOWN:\n-\tcase -EPROTO:\n-\t\tdev_dstats_tx_dropped(netdev);\n-\t\tbreak;\n-\tcase 0:\n-\t\tdev_dstats_tx_add(netdev, skb-\u003elen);\n-\t\tnetif_wake_queue(netdev);\n-\t\tconsume_skb(skb);\n-\t\treturn;\n-\tdefault:\n-\t\tnetdev_dbg(netdev, \"unexpected tx urb status: %d\\n\", status);\n-\t\tdev_dstats_tx_dropped(netdev);\n-\t}\n+\tusb_free_urb(urb);\n \n-\tkfree_skb(skb);\n+\tspin_lock_irqsave(\u0026mctp_usb-\u003etx_qmem_lock, flags);\n+\tmctp_usb-\u003etx_qmem -= len;\n+\tif (mctp_usb-\u003etx_qmem \u003c TX_QMEM_MAX \u0026\u0026 netif_running(netdev))\n+\t\tnetif_wake_queue(netdev);\n+\tspin_unlock_irqrestore(\u0026mctp_usb-\u003etx_qmem_lock, flags);\n }\n \n-static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,\n-\t\t\t\t struct net_device *dev)\n+static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,\n+\t\t\t void *data, size_t len)\n {\n-\tstruct mctp_usb *mctp_usb = netdev_priv(dev);\n-\tstruct mctp_usb_hdr *hdr;\n-\tunsigned int plen;\n+\tstruct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);\n+\tunsigned long flags;\n \tstruct urb *urb;\n \tint rc;\n \n-\tplen = skb-\u003elen;\n-\n-\tif (plen + sizeof(*hdr) \u003e MCTP_USB_XFER_SIZE)\n-\t\tgoto err_drop;\n-\n-\trc = skb_cow_head(skb, sizeof(*hdr));\n-\tif (rc)\n-\t\tgoto err_drop;\n-\n-\thdr = skb_push(skb, sizeof(*hdr));\n-\tif (!hdr)\n-\t\tgoto err_drop;\n-\n-\thdr-\u003eid = cpu_to_be16(MCTP_USB_DMTF_ID);\n-\thdr-\u003ersvd = 0;\n-\thdr-\u003elen = plen + sizeof(*hdr);\n-\n-\turb = mctp_usb-\u003etx_urb;\n+\turb = usb_alloc_urb(0, GFP_ATOMIC);\n+\tif (!urb)\n+\t\treturn -ENOMEM;\n \n \tusb_fill_bulk_urb(urb, mctp_usb-\u003eusbdev,\n \t\t\t usb_sndbulkpipe(mctp_usb-\u003eusbdev, mctp_usb-\u003eep_out),\n-\t\t\t skb-\u003edata, skb-\u003elen,\n-\t\t\t mctp_usb_out_complete, skb);\n+\t\t\t data, len, mctp_usb_out_complete, tx_ctx);\n+\n+\tif (mctp_usb-\u003espan)\n+\t\turb-\u003etransfer_flags |= URB_ZERO_PACKET;\n+\n+\tusb_anchor_urb(urb, \u0026mctp_usb-\u003etx_anchor);\n \n-\t/* Stops TX queue first to prevent race condition with URB complete */\n-\tnetif_stop_queue(dev);\n \trc = usb_submit_urb(urb, GFP_ATOMIC);\n \tif (rc) {\n-\t\tnetif_wake_queue(dev);\n-\t\tgoto err_drop;\n+\t\tnetdev_dbg(mctp_usb-\u003enetdev, \"TX urb submit failed, %d\\n\", rc);\n+\t\tusb_unanchor_urb(urb);\n+\t\tusb_free_urb(urb);\n+\t} else {\n+\t\tspin_lock_irqsave(\u0026mctp_usb-\u003etx_qmem_lock, flags);\n+\t\tmctp_usb-\u003etx_qmem += len;\n+\t\tif (mctp_usb-\u003etx_qmem \u003e= TX_QMEM_MAX)\n+\t\t\tnetif_stop_queue(mctp_usb-\u003enetdev);\n+\t\tspin_unlock_irqrestore(\u0026mctp_usb-\u003etx_qmem_lock, flags);\n \t}\n \n-\treturn NETDEV_TX_OK;\n+\treturn rc;\n+}\n+\n+static const struct mctp_usblib_tx_ops tx_ops = {\n+\t.send = mctp_usb_tx_send,\n+};\n+\n+static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,\n+\t\t\t\t struct net_device *dev)\n+{\n+\tstruct mctp_usb *mctp_usb = netdev_priv(dev);\n+\tbool more = netdev_xmit_more();\n+\n+\tmctp_usblib_tx_push(dev, \u0026mctp_usb-\u003etx, skb, more);\n \n-err_drop:\n-\tdev_dstats_tx_dropped(dev);\n-\tkfree_skb(skb);\n \treturn NETDEV_TX_OK;\n }\n \n@@ -125,24 +145,23 @@ static const unsigned long RX_RETRY_DELAY = HZ / 4;\n static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)\n {\n \tunsigned long flags;\n-\tstruct sk_buff *skb;\n+\tsize_t len;\n+\tvoid *buf;\n \tint rc;\n \n-\tskb = __netdev_alloc_skb(mctp_usb-\u003enetdev, MCTP_USB_XFER_SIZE, gfp);\n-\tif (!skb) {\n-\t\trc = -ENOMEM;\n+\trc = mctp_usblib_rx_prepare(mctp_usb-\u003enetdev, \u0026mctp_usb-\u003erx,\n+\t\t\t\t \u0026buf, \u0026len, gfp);\n+\tif (rc)\n \t\tgoto err_retry;\n-\t}\n \n \tusb_fill_bulk_urb(mctp_usb-\u003erx_urb, mctp_usb-\u003eusbdev,\n \t\t\t usb_rcvbulkpipe(mctp_usb-\u003eusbdev, mctp_usb-\u003eep_in),\n-\t\t\t skb-\u003edata, MCTP_USB_XFER_SIZE,\n-\t\t\t mctp_usb_in_complete, skb);\n+\t\t\t buf, len, mctp_usb_in_complete, mctp_usb);\n \n \trc = usb_submit_urb(mctp_usb-\u003erx_urb, gfp);\n \tif (rc) {\n \t\tnetdev_dbg(mctp_usb-\u003enetdev, \"rx urb submit failure: %d\\n\", rc);\n-\t\tkfree_skb(skb);\n+\t\tmctp_usblib_rx_cancel(\u0026mctp_usb-\u003erx);\n \t\tif (rc == -ENOMEM)\n \t\t\tgoto err_retry;\n \t}\n@@ -157,13 +176,13 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)\n \treturn 0;\n }\n \n+static const unsigned int rx_err_max = 10;\n+\n static void mctp_usb_in_complete(struct urb *urb)\n {\n-\tstruct sk_buff *skb = urb-\u003econtext;\n-\tstruct net_device *netdev = skb-\u003edev;\n-\tstruct mctp_usb *mctp_usb = netdev_priv(netdev);\n-\tstruct mctp_skb_cb *cb;\n-\tunsigned int len;\n+\tstruct mctp_usb *mctp_usb = urb-\u003econtext;\n+\tstruct net_device *netdev = mctp_usb-\u003enetdev;\n+\tunsigned long flags;\n \tint status;\n \n \tstatus = urb-\u003estatus;\n@@ -172,80 +191,54 @@ static void mctp_usb_in_complete(struct urb *urb)\n \tcase -ENOENT:\n \tcase -ECONNRESET:\n \tcase -ESHUTDOWN:\n-\tcase -EPROTO:\n-\t\tkfree_skb(skb);\n+\t\t/* device shutdown, don't resubmit */\n+\t\tmctp_usblib_rx_cancel(\u0026mctp_usb-\u003erx);\n \t\treturn;\n-\tcase 0:\n-\t\tbreak;\n-\tdefault:\n-\t\tnetdev_dbg(netdev, \"unexpected rx urb status: %d\\n\", status);\n-\t\tkfree_skb(skb);\n-\t\treturn;\n-\t}\n-\n-\tlen = urb-\u003eactual_length;\n-\t__skb_put(skb, len);\n-\n-\twhile (skb) {\n-\t\tstruct sk_buff *skb2 = NULL;\n-\t\tstruct mctp_usb_hdr *hdr;\n-\t\tu8 pkt_len; /* length of MCTP packet, no USB header */\n \n-\t\tskb_reset_mac_header(skb);\n-\t\thdr = skb_pull_data(skb, sizeof(*hdr));\n-\t\tif (!hdr)\n-\t\t\tbreak;\n-\n-\t\tif (be16_to_cpu(hdr-\u003eid) != MCTP_USB_DMTF_ID) {\n-\t\t\tnetdev_dbg(netdev, \"rx: invalid id %04x\\n\",\n-\t\t\t\t be16_to_cpu(hdr-\u003eid));\n-\t\t\tbreak;\n-\t\t}\n+\tcase -EPIPE:\n+\t\t/* endpoint stall: clear halt, which will cause a resubmit */\n \n-\t\tif (hdr-\u003elen \u003c\n-\t\t sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) {\n-\t\t\tnetdev_dbg(netdev, \"rx: short packet (hdr) %d\\n\",\n-\t\t\t\t hdr-\u003elen);\n-\t\t\tbreak;\n+\t\tif (!mctp_usb-\u003ein_err_count++)\n+\t\t\tmctp_usb-\u003ein_err_orig = status;\n+\t\tif (mctp_usb-\u003ein_err_count \u003e= rx_err_max) {\n+\t\t\tnetdev_err(netdev, \"excessive stalls from IN EP\\n\");\n+\t\t\treturn;\n \t\t}\n \n-\t\t/* we know we have at least sizeof(struct mctp_usb_hdr) here */\n-\t\tpkt_len = hdr-\u003elen - sizeof(struct mctp_usb_hdr);\n-\t\tif (pkt_len \u003e skb-\u003elen) {\n-\t\t\tnetdev_dbg(netdev,\n-\t\t\t\t \"rx: short packet (xfer) %d, actual %d\\n\",\n-\t\t\t\t hdr-\u003elen, skb-\u003elen);\n-\t\t\tbreak;\n-\t\t}\n+\t\tmctp_usb-\u003eclear_halt = true;\n+\t\tspin_lock_irqsave(\u0026mctp_usb-\u003erx_lock, flags);\n+\t\tif (!mctp_usb-\u003erx_stopped)\n+\t\t\tschedule_delayed_work(\u0026mctp_usb-\u003erx_retry_work,\n+\t\t\t\t\t RX_RETRY_DELAY);\n+\t\tspin_unlock_irqrestore(\u0026mctp_usb-\u003erx_lock, flags);\n+\t\tmctp_usblib_rx_cancel(\u0026mctp_usb-\u003erx);\n+\t\treturn;\n \n-\t\tif (pkt_len \u003c skb-\u003elen) {\n-\t\t\t/* more packets may follow - clone to a new\n-\t\t\t * skb to use on the next iteration\n-\t\t\t */\n-\t\t\tskb2 = skb_clone(skb, GFP_ATOMIC);\n-\t\t\tif (skb2) {\n-\t\t\t\tif (!skb_pull(skb2, pkt_len)) {\n-\t\t\t\t\tkfree_skb(skb2);\n-\t\t\t\t\tskb2 = NULL;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tskb_trim(skb, pkt_len);\n+\tdefault:\n+\t\tnetdev_dbg(netdev, \"unexpected rx urb status: %d\\n\", status);\n+\t\tfallthrough;\n+\tcase -ETIME:\n+\tcase -EPROTO:\n+\tcase -EILSEQ:\n+\tcase -EOVERFLOW:\n+\t\t/* possibly transient; record first failure, resubmit */\n+\t\tmctp_usblib_rx_cancel(\u0026mctp_usb-\u003erx);\n+\t\tif (!mctp_usb-\u003ein_err_count++)\n+\t\t\tmctp_usb-\u003ein_err_orig = status;\n+\t\tif (mctp_usb-\u003ein_err_count \u003e= rx_err_max) {\n+\t\t\tnetdev_err(netdev,\n+\t\t\t\t \"excessive errors from IN EP, first: %d\\n\",\n+\t\t\t\t mctp_usb-\u003ein_err_orig);\n+\t\t\treturn;\n \t\t}\n+\t\tbreak;\n \n-\t\tdev_dstats_rx_add(netdev, skb-\u003elen);\n-\n-\t\tskb-\u003eprotocol = htons(ETH_P_MCTP);\n-\t\tskb_reset_network_header(skb);\n-\t\tcb = __mctp_cb(skb);\n-\t\tcb-\u003ehalen = 0;\n-\t\tnetif_rx(skb);\n-\n-\t\tskb = skb2;\n+\tcase 0:\n+\t\tmctp_usblib_rx_complete(netdev, \u0026mctp_usb-\u003erx, urb-\u003eactual_length);\n+\t\tmctp_usb-\u003ein_err_count = 0;\n+\t\tbreak;\n \t}\n \n-\tif (skb)\n-\t\tkfree_skb(skb);\n-\n \tmctp_usb_rx_queue(mctp_usb, GFP_ATOMIC);\n }\n \n@@ -253,6 +246,20 @@ static void mctp_usb_rx_retry_work(struct work_struct *work)\n {\n \tstruct mctp_usb *mctp_usb = container_of(work, struct mctp_usb,\n \t\t\t\t\t\t rx_retry_work.work);\n+\tint rc;\n+\n+\t/* We are only called when rx completions are suspended */\n+\tif (mctp_usb-\u003eclear_halt) {\n+\t\tint pipe = usb_rcvbulkpipe(mctp_usb-\u003eusbdev, mctp_usb-\u003eep_in);\n+\n+\t\trc = usb_clear_halt(mctp_usb-\u003eusbdev, pipe);\n+\t\tif (rc) {\n+\t\t\tnetdev_err(mctp_usb-\u003enetdev,\n+\t\t\t\t \"can't clear IN EP halt: %d\\n\", rc);\n+\t\t\treturn;\n+\t\t}\n+\t\tmctp_usb-\u003eclear_halt = false;\n+\t}\n \n \tmctp_usb_rx_queue(mctp_usb, GFP_KERNEL);\n }\n@@ -262,6 +269,8 @@ static int mctp_usb_open(struct net_device *dev)\n \tstruct mctp_usb *mctp_usb = netdev_priv(dev);\n \n \tWRITE_ONCE(mctp_usb-\u003erx_stopped, false);\n+\tmctp_usb-\u003eclear_halt = false;\n+\tmctp_usb-\u003ein_err_count = 0;\n \n \tnetif_start_queue(dev);\n \n@@ -284,7 +293,10 @@ static int mctp_usb_stop(struct net_device *dev)\n \tflush_delayed_work(\u0026mctp_usb-\u003erx_retry_work);\n \n \tusb_kill_urb(mctp_usb-\u003erx_urb);\n-\tusb_kill_urb(mctp_usb-\u003etx_urb);\n+\n+\tusb_kill_anchored_urbs(\u0026mctp_usb-\u003etx_anchor);\n+\n+\tmctp_usblib_tx_cancel(\u0026mctp_usb-\u003etx, dev, SKB_DROP_REASON_DEV_READY);\n \n \treturn 0;\n }\n@@ -301,7 +313,7 @@ static void mctp_usb_netdev_setup(struct net_device *dev)\n \n \tdev-\u003emtu = MCTP_USB_MTU_MIN;\n \tdev-\u003emin_mtu = MCTP_USB_MTU_MIN;\n-\tdev-\u003emax_mtu = MCTP_USB_MTU_MAX;\n+\tdev-\u003emax_mtu = MCTP_USB_1_0_MTU_MAX;\n \n \tdev-\u003ehard_header_len = sizeof(struct mctp_usb_hdr);\n \tdev-\u003etx_queue_len = DEFAULT_TX_QUEUE_LEN;\n@@ -317,6 +329,7 @@ static int mctp_usb_probe(struct usb_interface *intf,\n \tstruct usb_host_interface *iface_desc;\n \tstruct net_device *netdev;\n \tstruct mctp_usb *dev;\n+\tbool span;\n \tint rc;\n \n \t/* only one alternate */\n@@ -328,6 +341,8 @@ static int mctp_usb_probe(struct usb_interface *intf,\n \t\treturn rc;\n \t}\n \n+\tspan = iface_desc-\u003edesc.bInterfaceSubClass == MCTP_USB_SUBCLASS_SPAN;\n+\n \tnetdev = alloc_netdev(sizeof(*dev), \"mctpusb%d\", NET_NAME_ENUM,\n \t\t\t mctp_usb_netdev_setup);\n \tif (!netdev)\n@@ -335,33 +350,43 @@ static int mctp_usb_probe(struct usb_interface *intf,\n \n \tSET_NETDEV_DEV(netdev, \u0026intf-\u003edev);\n \tdev = netdev_priv(netdev);\n+\tdev-\u003espan = span;\n \tdev-\u003enetdev = netdev;\n \tdev-\u003eusbdev = interface_to_usbdev(intf);\n \tdev-\u003eintf = intf;\n \tspin_lock_init(\u0026dev-\u003erx_lock);\n+\tif (dev-\u003espan)\n+\t\tnetdev-\u003emax_mtu = MCTP_USB_1_1_MTU_MAX;\n+\tspin_lock_init(\u0026dev-\u003etx_qmem_lock);\n \tusb_set_intfdata(intf, dev);\n \n+\tmctp_usblib_rx_init(\u0026dev-\u003erx, le16_to_cpu(ep_in-\u003ewMaxPacketSize),\n+\t\t\t dev-\u003espan);\n+\tmctp_usblib_tx_init(\u0026dev-\u003etx, \u0026tx_ops, dev, dev-\u003espan);\n+\tinit_usb_anchor(\u0026dev-\u003etx_anchor);\n+\n \tdev-\u003eep_in = ep_in-\u003ebEndpointAddress;\n \tdev-\u003eep_out = ep_out-\u003ebEndpointAddress;\n \n-\tdev-\u003etx_urb = usb_alloc_urb(0, GFP_KERNEL);\n \tdev-\u003erx_urb = usb_alloc_urb(0, GFP_KERNEL);\n-\tif (!dev-\u003etx_urb || !dev-\u003erx_urb) {\n+\tif (!dev-\u003erx_urb) {\n \t\trc = -ENOMEM;\n-\t\tgoto err_free_urbs;\n+\t\tgoto err_fini_rxtx;\n \t}\n \n \tINIT_DELAYED_WORK(\u0026dev-\u003erx_retry_work, mctp_usb_rx_retry_work);\n \n \trc = mctp_register_netdev(netdev, NULL, MCTP_PHYS_BINDING_USB);\n \tif (rc)\n-\t\tgoto err_free_urbs;\n+\t\tgoto err_free_urb;\n \n \treturn 0;\n \n-err_free_urbs:\n-\tusb_free_urb(dev-\u003etx_urb);\n+err_free_urb:\n \tusb_free_urb(dev-\u003erx_urb);\n+err_fini_rxtx:\n+\tmctp_usblib_tx_fini(\u0026dev-\u003etx);\n+\tmctp_usblib_rx_fini(\u0026dev-\u003erx);\n \tfree_netdev(netdev);\n \treturn rc;\n }\n@@ -371,13 +396,15 @@ static void mctp_usb_disconnect(struct usb_interface *intf)\n \tstruct mctp_usb *dev = usb_get_intfdata(intf);\n \n \tmctp_unregister_netdev(dev-\u003enetdev);\n-\tusb_free_urb(dev-\u003etx_urb);\n+\tmctp_usblib_rx_fini(\u0026dev-\u003erx);\n+\tmctp_usblib_tx_fini(\u0026dev-\u003etx);\n \tusb_free_urb(dev-\u003erx_urb);\n \tfree_netdev(dev-\u003enetdev);\n }\n \n static const struct usb_device_id mctp_usb_devices[] = {\n-\t{ USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) },\n+\t{ USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_BASE, 0x1) },\n+\t{ USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_SPAN, 0x1) },\n \t{ 0 },\n };\n \ndiff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c\nnew file mode 100644\nindex 00000000000000..2a22be999fa0a1\n--- /dev/null\n+++ b/drivers/net/mctp/mctp-usblib-test.c\n@@ -0,0 +1,410 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library,\n+ * unit test definitions.\n+ *\n+ * Copyright (C) 2026 Code Construct Pty Ltd\n+ */\n+\n+#include \u003cuapi/linux/netdevice.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003ckunit/test.h\u003e\n+#include \u003clinux/if_arp.h\u003e\n+#include \u003cnet/mctp.h\u003e\n+#include \u003cnet/mctpdevice.h\u003e\n+#include \u003clinux/usb/mctp-usb.h\u003e\n+\n+struct mctp_usblib_test_dev {\n+\tstruct net_device *ndev;\n+\tstruct mctp_dev *mdev;\n+\tstruct sk_buff_head rx_pkts;\n+};\n+\n+struct mctp_usblib_test_ctx {\n+\tstruct mctp_usblib_test_dev *dev;\n+\tstruct mctp_route rt;\n+};\n+\n+static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb,\n+\t\t\t\t struct net_device *ndev)\n+{\n+\t/* we don't track any TXed packets at present */\n+\tkfree_skb(skb);\n+\treturn NETDEV_TX_OK;\n+}\n+\n+static const struct net_device_ops mctp_test_netdev_ops = {\n+\t.ndo_start_xmit = mctp_usblib_dev_tx,\n+};\n+\n+static const u16 ep_maxpacket = 512;\n+static const mctp_eid_t local_eid = 8;\n+\n+static void mctp_usblib_dev_setup(struct net_device *ndev)\n+{\n+\tndev-\u003etype = ARPHRD_MCTP;\n+\tndev-\u003emtu = 8192;\n+\tndev-\u003eflags = IFF_NOARP;\n+\tndev-\u003enetdev_ops = \u0026mctp_test_netdev_ops;\n+\tndev-\u003eneeds_free_netdev = true;\n+\tndev-\u003epcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;\n+}\n+\n+static void mctp_usblib_test_dev_action(void *data)\n+{\n+\tstruct mctp_usblib_test_dev *dev = data;\n+\n+\tskb_queue_purge(\u0026dev-\u003erx_pkts);\n+\tif (dev-\u003emdev)\n+\t\tmctp_dev_put(dev-\u003emdev);\n+\tunregister_netdev(dev-\u003endev);\n+}\n+\n+static struct mctp_usblib_test_dev *\n+mctp_usblib_test_create_dev(struct kunit *test)\n+{\n+\tstruct mctp_usblib_test_dev *dev;\n+\tstruct net_device *ndev;\n+\tint rc;\n+\n+\tndev = alloc_netdev(sizeof(*dev), \"mctptest%d\", NET_NAME_ENUM,\n+\t\t\t mctp_usblib_dev_setup);\n+\tif (!ndev)\n+\t\treturn NULL;\n+\n+\tdev = netdev_priv(ndev);\n+\tdev-\u003endev = ndev;\n+\tskb_queue_head_init(\u0026dev-\u003erx_pkts);\n+\n+\trc = register_netdev(ndev);\n+\tif (rc) {\n+\t\tfree_netdev(ndev);\n+\t\treturn NULL;\n+\t}\n+\n+\trc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev);\n+\tif (rc)\n+\t\treturn NULL;\n+\n+\trcu_read_lock();\n+\tdev-\u003emdev = __mctp_dev_get(ndev);\n+\tif (dev-\u003emdev)\n+\t\tdev-\u003emdev-\u003enet = mctp_default_net(dev_net(ndev));\n+\trcu_read_unlock();\n+\n+\tif (!dev-\u003emdev)\n+\t\treturn NULL;\n+\n+\trtnl_lock();\n+\trc = dev_open(ndev, NULL);\n+\trtnl_unlock();\n+\tif (rc)\n+\t\treturn NULL;\n+\n+\treturn dev;\n+}\n+\n+static int mctp_usblib_test_dst_output(struct mctp_dst *dst,\n+\t\t\t\t struct sk_buff *skb)\n+{\n+\tstruct mctp_usblib_test_dev *dev = netdev_priv(skb-\u003edev);\n+\n+\tskb_queue_tail(\u0026dev-\u003erx_pkts, skb);\n+\n+\treturn 0;\n+}\n+\n+static void mctp_usblib_test_fini_action(void *data)\n+{\n+\tstruct mctp_usblib_test_ctx *ctx = data;\n+\n+\t/* The device will have been destroyed, so -\u003ert will be unlinked.\n+\t * Just ensure that the refcount is as expected.\n+\t */\n+\tKUNIT_ASSERT_TRUE(current-\u003ekunit_test,\n+\t\t\t refcount_dec_and_test(\u0026ctx-\u003ert.refs));\n+\n+\tkfree(ctx);\n+}\n+\n+static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test)\n+{\n+\tstruct mctp_usblib_test_ctx *ctx;\n+\tstruct mctp_route *rt;\n+\tint rc;\n+\n+\tctx = kzalloc_obj(*ctx);\n+\tKUNIT_ASSERT_NOT_NULL(test, ctx);\n+\n+\tINIT_LIST_HEAD(\u0026ctx-\u003ert.list);\n+\trt = \u0026ctx-\u003ert;\n+\trefcount_set(\u0026rt-\u003erefs, 1);\n+\n+\trc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx);\n+\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\n+\tctx-\u003edev = mctp_usblib_test_create_dev(test);\n+\tKUNIT_ASSERT_NOT_NULL(test, ctx-\u003edev);\n+\n+\trt-\u003emin = local_eid;\n+\trt-\u003emax = local_eid;\n+\trt-\u003edst_type = MCTP_ROUTE_DIRECT;\n+\trt-\u003etype = RTN_LOCAL;\n+\trt-\u003edev = ctx-\u003edev-\u003emdev;\n+\trt-\u003eoutput = mctp_usblib_test_dst_output;\n+\n+\trtnl_lock();\n+\tlist_add_rcu(\u0026ctx-\u003ert.list, \u0026init_net.mctp.routes);\n+\trefcount_inc(\u0026rt-\u003erefs);\n+\trtnl_unlock();\n+\n+\treturn ctx;\n+}\n+\n+/* Init a MCTP-over-USB packet within a buffer. @len is the length of the\n+ * buffer to write, @payload_len is the reported size of the MCTP-over-USB\n+ * packet.\n+ */\n+static void mctp_usblib_test_init_pkt(void *data, size_t len,\n+\t\t\t\t size_t payload_len)\n+{\n+\tstruct {\n+\t\tstruct mctp_usb_hdr usb;\n+\t\tstruct mctp_hdr mctp;\n+\t} hdr;\n+\n+\thdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID);\n+\thdr.usb.len = cpu_to_be16(payload_len);\n+\thdr.mctp.ver = 1;\n+\thdr.mctp.dest = local_eid;\n+\thdr.mctp.src = 0;\n+\thdr.mctp.flags_seq_tag = 0;\n+\n+\tmemcpy(data, \u0026hdr, min(len, sizeof(hdr)));\n+\tif (len \u003e sizeof(hdr))\n+\t\tmemset(data + sizeof(hdr), 0, len - sizeof(hdr));\n+}\n+\n+static void action_rx_fini(void *data)\n+{\n+\tstruct mctp_usblib_rx *rx = data;\n+\n+\tmctp_usblib_rx_fini(rx);\n+\tkfree(rx);\n+}\n+\n+static struct mctp_usblib_rx *\n+mctp_usblib_test_rx_init(struct kunit *test, bool span)\n+{\n+\tstruct mctp_usblib_rx *rx;\n+\tint rc;\n+\n+\trx = kzalloc_obj(*rx);\n+\tif (rx) {\n+\t\trc = kunit_add_action_or_reset(test, action_rx_fini, rx);\n+\t\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\t}\n+\tKUNIT_ASSERT_NOT_NULL(test, rx);\n+\tmctp_usblib_rx_init(rx, ep_maxpacket, span);\n+\n+\treturn rx;\n+}\n+\n+/* Wrappers for usblib's rx_complete callback, which is intended to be called\n+ * from atomic context\n+ */\n+static int mctp_usblib_test_rx_complete(struct net_device *netdev,\n+\t\t\t\t\tstruct mctp_usblib_rx *rx, size_t len)\n+{\n+\tint rc;\n+\n+\tlocal_bh_disable();\n+\trc = mctp_usblib_rx_complete(netdev, rx, len);\n+\tlocal_bh_enable();\n+\n+\treturn rc;\n+}\n+\n+/* Single packet, starting on a transfer boundary, contained entirely within\n+ * the transfer\n+ */\n+static void mctp_usblib_test_rx_single(struct kunit *test)\n+{\n+\tstruct mctp_usblib_test_dev *dev;\n+\tstruct mctp_usblib_test_ctx *ctx;\n+\tstruct mctp_usblib_rx *rx;\n+\tstruct sk_buff *skb;\n+\tsize_t len;\n+\tvoid *buf;\n+\tint rc;\n+\n+\tctx = mctp_usblib_test_init(test);\n+\tdev = ctx-\u003edev;\n+\n+\trx = mctp_usblib_test_rx_init(test, true);\n+\n+\trc = mctp_usblib_rx_prepare(dev-\u003endev, rx,\n+\t\t\t\t \u0026buf, \u0026len, GFP_KERNEL);\n+\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\n+\t/* we should always have a maxpacket of transfer available */\n+\tKUNIT_ASSERT_GE(test, len, ep_maxpacket);\n+\n+\tmctp_usblib_test_init_pkt(buf, 8, 8);\n+\n+\trc = mctp_usblib_test_rx_complete(dev-\u003endev, rx, 8);\n+\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\n+\tskb = __skb_dequeue(\u0026dev-\u003erx_pkts);\n+\tKUNIT_EXPECT_NOT_NULL(test, skb);\n+\tif (skb)\n+\t\tKUNIT_EXPECT_EQ(test, skb-\u003elen, 4);\n+\tkfree_skb(skb);\n+}\n+\n+struct mctp_usblib_test_pkt_span {\n+\tconst char *name;\n+\tsize_t n_pkts;\n+\tsize_t pkts[6];\n+\tsize_t n_xfers;\n+\tsize_t xfers[6];\n+};\n+\n+static void\n+mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t,\n+\t\t\t\t char *desc)\n+{\n+\tstrscpy(desc, t-\u003ename, KUNIT_PARAM_DESC_SIZE);\n+}\n+\n+static void\n+mctp_usblib_test_pkt_span_validate(struct kunit *test,\n+\t\t\t\t const struct mctp_usblib_test_pkt_span *span,\n+\t\t\t\t size_t *len)\n+{\n+\tsize_t pkt_len = 0, xfer_len = 0;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c span-\u003en_pkts; i++) {\n+\t\tKUNIT_ASSERT_GE_MSG(test, span-\u003epkts[i], 8,\n+\t\t\t\t \"pkt[%d] len too small (%zd) for %s\",\n+\t\t\t\t i, span-\u003epkts[i], span-\u003ename);\n+\t\tpkt_len += span-\u003epkts[i];\n+\t}\n+\n+\tfor (i = 0; i \u003c span-\u003en_xfers; i++)\n+\t\txfer_len += span-\u003exfers[i];\n+\n+\tKUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len,\n+\t\t\t \"invalid pkt_len (%zd) != xfer_len (%zd) for %s\",\n+\t\t\t pkt_len, xfer_len, span-\u003ename);\n+\n+\t*len = pkt_len;\n+}\n+\n+static void mctp_usblib_test_rx_pkt_span(struct kunit *test)\n+{\n+\tconst struct mctp_usblib_test_pkt_span *pkt_span = test-\u003eparam_value;\n+\tsize_t len, xfer_len, off, xfer_off;\n+\tstruct mctp_usblib_test_dev *dev;\n+\tstruct mctp_usblib_test_ctx *ctx;\n+\tstruct mctp_usblib_rx *rx;\n+\tunsigned int i;\n+\tu8 *pktbuf;\n+\tvoid *buf;\n+\tint rc;\n+\n+\tmctp_usblib_test_pkt_span_validate(test, pkt_span, \u0026len);\n+\tpktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, pktbuf);\n+\n+\t/* lay out packets */\n+\tfor (off = 0, i = 0; i \u003c pkt_span-\u003en_pkts; i++) {\n+\t\tlen = pkt_span-\u003epkts[i];\n+\t\tmctp_usblib_test_init_pkt(pktbuf + off, len, len);\n+\t\toff += len;\n+\t}\n+\n+\tctx = mctp_usblib_test_init(test);\n+\tdev = ctx-\u003edev;\n+\n+\trx = mctp_usblib_test_rx_init(test, true);\n+\n+\t/* feed transfers */\n+\tfor (off = 0, xfer_off = 0, i = 0; i \u003c pkt_span-\u003en_xfers;) {\n+\t\txfer_len = pkt_span-\u003exfers[i] - xfer_off;\n+\t\trc = mctp_usblib_rx_prepare(dev-\u003endev, rx,\n+\t\t\t\t\t \u0026buf, \u0026len, GFP_KERNEL);\n+\t\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\n+\t\tKUNIT_ASSERT_GE(test, len, ep_maxpacket);\n+\n+\t\tlen = min(len, xfer_len);\n+\t\tmemcpy(buf, pktbuf + off, len);\n+\n+\t\tif (len == xfer_len) {\n+\t\t\t/* whole/end xfer, proceed to next */\n+\t\t\txfer_off = 0;\n+\t\t\ti++;\n+\t\t} else {\n+\t\t\t/* partial */\n+\t\t\txfer_off += len;\n+\t\t}\n+\n+\t\trc = mctp_usblib_test_rx_complete(dev-\u003endev, rx, len);\n+\t\tKUNIT_ASSERT_EQ(test, rc, 0);\n+\t\toff += len;\n+\t}\n+\n+\t/* check received packets */\n+\tKUNIT_EXPECT_EQ(test, dev-\u003erx_pkts.qlen, pkt_span-\u003en_pkts);\n+\tfor (i = 0; ; i++) {\n+\t\tstruct sk_buff *skb = __skb_dequeue(\u0026dev-\u003erx_pkts);\n+\n+\t\tif (!skb)\n+\t\t\tbreak;\n+\n+\t\tif (i \u003c pkt_span-\u003en_pkts)\n+\t\t\tKUNIT_EXPECT_EQ(test, skb-\u003elen, pkt_span-\u003epkts[i] - 4);\n+\n+\t\tkfree_skb(skb);\n+\t}\n+}\n+\n+static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = {\n+\t/* One packet completely within a transfer */\n+\t{ \"1p1x-complete\", 1, { 8 }, 1, { 8 } },\n+\t/* Two small packets combined within one transfer */\n+\t{ \"2p1x-combined\", 2, { 8, 8 }, 1, { 16 } },\n+\t/* A packet split over two transfers, at the MCTP payload */\n+\t{ \"1p2x-split-payload\", 1, { 16 }, 2, { 8, 8 } },\n+\t/* A packet split over two transfers, at the USB transport header */\n+\t{ \"1p2x-split-usbhdr\", 1, { 16 }, 2, { 2, 14 } },\n+\t/* A packet split over two transfers, at the MCTP header */\n+\t{ \"1p2x-split-mctphdr\", 1, { 16 }, 2, { 6, 10 } },\n+\t/* Single packet split over 3 transfers, middle entirely continuation */\n+\t{ \"1p3x-split\", 1, { 12 }, 3, { 4, 4, 4 } },\n+\t/* Max-sized single transfer */\n+\t{ \"1p1x-large\", 1, { 8191 }, 1, { 8191 } },\n+\t/* Two large packets, split at the worst-case for allocation, with a\n+\t * single byte continuing the span\n+\t */\n+\t{ \"2p2x-large-split\", 2, { 8190, 8190 }, 2, { 8191, 8189 } },\n+};\n+\n+KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans,\n+\t\t mctp_usblib_test_pkt_span_to_desc);\n+\n+static struct kunit_case mctp_usblib_test_cases[] = {\n+\tKUNIT_CASE(mctp_usblib_test_rx_single),\n+\tKUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span,\n+\t\t\t mctp_usblib_test_rx_pkt_span_gen_params),\n+\t{}\n+};\n+\n+static struct kunit_suite mctp_usblib_test_suite = {\n+\t.name = \"mctp-usblib\",\n+\t.test_cases = mctp_usblib_test_cases,\n+};\n+\n+kunit_test_suite(mctp_usblib_test_suite);\ndiff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c\nnew file mode 100644\nindex 00000000000000..fc0282152e6efb\n--- /dev/null\n+++ b/drivers/net/mctp/mctp-usblib.c\n@@ -0,0 +1,616 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library\n+ *\n+ * DSP0283 is available at:\n+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf\n+ *\n+ * Copyright (C) 2024-2026 Code Construct Pty Ltd\n+ */\n+\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/usb/mctp-usb.h\u003e\n+#include \u003cnet/mctp.h\u003e\n+\n+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span)\n+{\n+\tmemset(rx, 0, sizeof(*rx));\n+\trx-\u003espan = span;\n+\trx-\u003eep_pktlen = ep_pktlen;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_rx_init);\n+\n+void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx)\n+{\n+\tkfree_skb(rx-\u003eskb);\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_rx_fini);\n+\n+/*\n+ * Prepare a transfer buffer for future completion; *bufp and *lenp will\n+ * be populated on success.\n+ */\n+int mctp_usblib_rx_prepare(struct net_device *netdev,\n+\t\t\t struct mctp_usblib_rx *rx,\n+\t\t\t void **bufp, size_t *lenp, gfp_t gfp)\n+{\n+\tstruct sk_buff *skb = rx-\u003eskb;\n+\tunsigned int len = 0;\n+\n+\tif (skb \u0026\u0026 skb-\u003elen \u003e= MCTP_USB_1_1_PKTLEN_MAX) {\n+\t\t/* something must have gone terribly wrong. clear and restart */\n+\t\tmctp_usblib_rx_cancel(rx);\n+\t\tskb = NULL;\n+\t}\n+\n+\tlen = rx-\u003espan ? ALIGN(MCTP_USB_1_1_PKTLEN_MAX, rx-\u003eep_pktlen)\n+\t\t: MCTP_USB_1_0_XFER_SIZE;\n+\n+\tif (!skb) {\n+\t\tskb = __netdev_alloc_skb(netdev, len, gfp);\n+\t\tif (!skb)\n+\t\t\treturn -ENOMEM;\n+\n+\t} else if (skb-\u003ecloned || skb_tailroom(skb) \u003c rx-\u003eep_pktlen) {\n+\t\t/* We always need to realloc if -\u003ecloned, as we cannot\n+\t\t * resubmit the (now-shared) skb buffer for possible DMA.\n+\t\t *\n+\t\t * Otherwise (if we have an un-cloned SKB): just ensure we\n+\t\t * have sufficient space to prevent babble. Since we allocated\n+\t\t * for max size in the last prepare (and have not consumed any\n+\t\t * of that space for a prior MCTP packet, because !cloned), we\n+\t\t * have sufficient data to finish the current MCTP packet.\n+\t\t */\n+\t\tstruct sk_buff *skb2;\n+\n+\t\tskb2 = skb_copy_expand(skb, 0, len, gfp);\n+\t\tif (!skb2)\n+\t\t\treturn -ENOMEM;\n+\t\tdev_kfree_skb_any(skb);\n+\t\tskb = skb2;\n+\t}\n+\n+\trx-\u003eskb = skb;\n+\n+\t/* Spanning mode allows ZLPs, so we don't require exactly one\n+\t * transfer packet. If we have extra tailroom, may as well use it,\n+\t * and we have ensured that the tailroom \u003e= ep_pktlen.\n+\t */\n+\tif (rx-\u003espan)\n+\t\tlen = ALIGN_DOWN(skb_tailroom(skb), rx-\u003eep_pktlen);\n+\n+\t*bufp = skb_tail_pointer(skb);\n+\t*lenp = len;\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_rx_prepare);\n+\n+static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb)\n+{\n+\tstruct pcpu_dstats *dstats = this_cpu_ptr(netdev-\u003edstats);\n+\tstruct mctp_skb_cb *cb;\n+\tunsigned long flags;\n+\n+\tskb_reset_mac_header(skb);\n+\tskb_pull(skb, sizeof(struct mctp_usb_hdr));\n+\n+\t/* we're called from an URB completion handler, and cannot assume local\n+\t * irqs are always disabled\n+\t */\n+\tflags = u64_stats_update_begin_irqsave(\u0026dstats-\u003esyncp);\n+\tu64_stats_inc(\u0026dstats-\u003erx_packets);\n+\tu64_stats_add(\u0026dstats-\u003erx_bytes, skb-\u003elen);\n+\tu64_stats_update_end_irqrestore(\u0026dstats-\u003esyncp, flags);\n+\n+\tskb-\u003eprotocol = htons(ETH_P_MCTP);\n+\tskb_reset_network_header(skb);\n+\tcb = __mctp_cb(skb);\n+\tcb-\u003ehalen = 0;\n+\tnetif_rx(skb);\n+}\n+\n+static void mctp_usblib_rx_stats_single_drop(struct net_device *dev)\n+{\n+\tstruct pcpu_dstats *dstats = this_cpu_ptr(dev-\u003edstats);\n+\tunsigned long flags;\n+\n+\tflags = u64_stats_update_begin_irqsave(\u0026dstats-\u003esyncp);\n+\tu64_stats_inc(\u0026dstats-\u003erx_drops);\n+\tu64_stats_update_end_irqrestore(\u0026dstats-\u003esyncp, flags);\n+}\n+\n+/*\n+ * Receive a USB completion of @len bytes of incoming data. We will then split\n+ * this into packets and netif_rx() each. Intended to be called in atomic\n+ * contexts - ie., URB completion.\n+ *\n+ * Assumes @netdev uses dstats.\n+ */\n+int mctp_usblib_rx_complete(struct net_device *netdev,\n+\t\t\t struct mctp_usblib_rx *rx, size_t len)\n+{\n+\tstruct sk_buff *skb = rx-\u003eskb;\n+\tint rc = 0;\n+\n+\t__skb_put(skb, len);\n+\n+\tfor (;;) {\n+\t\tstruct mctp_usb_hdr *hdr;\n+\t\tstruct sk_buff *skb2;\n+\t\t/* length of MCTP packet, including USB header */\n+\t\tu16 pkt_len;\n+\n+\t\t/* no header yet, resubmit for the rest of the packet */\n+\t\tif (skb-\u003elen \u003c sizeof(*hdr)) {\n+\t\t\tif (!rx-\u003espan) {\n+\t\t\t\tnetdev_dbg(netdev,\n+\t\t\t\t\t \"rx: tiny xfer (%d) in non-span mode\",\n+\t\t\t\t\t skb-\u003elen);\n+\t\t\t\trc = -ENOMSG;\n+\t\t\t\tgoto err_reset;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\thdr = (struct mctp_usb_hdr *)skb-\u003edata;\n+\n+\t\tif (be16_to_cpu(hdr-\u003eid) != MCTP_USB_DMTF_ID) {\n+\t\t\t/* By resetting here, will start the next IN transfer\n+\t\t\t * at the beginning of the new skb. This will mean\n+\t\t\t * we re-sync when we next see a spanned packet aligned\n+\t\t\t * with the start of a transfer.\n+\t\t\t *\n+\t\t\t * In non-spanning mode, this just means we'll drop\n+\t\t\t * the current transfer only\n+\t\t\t */\n+\t\t\tnetdev_dbg(netdev, \"rx: invalid id %04x\\n\",\n+\t\t\t\t be16_to_cpu(hdr-\u003eid));\n+\t\t\trc = -EPROTO;\n+\t\t\tgoto err_reset;\n+\t\t}\n+\n+\t\tpkt_len = be16_to_cpu(hdr-\u003elen);\n+\t\t/* v1.1, with span enabled, has a 13-bit length */\n+\t\tpkt_len \u0026= rx-\u003espan ?\n+\t\t\tMCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;\n+\t\tif (pkt_len \u003c sizeof(*hdr) + sizeof(struct mctp_hdr)) {\n+\t\t\tnetdev_dbg(netdev, \"rx: invalid len %d\\n\", pkt_len);\n+\t\t\trc = -EPROTO;\n+\t\t\tgoto err_reset;\n+\t\t}\n+\n+\t\t/* span continues to the next transfer, resubmit */\n+\t\tif (pkt_len \u003e skb-\u003elen) {\n+\t\t\tif (!rx-\u003espan) {\n+\t\t\t\tnetdev_dbg(netdev,\n+\t\t\t\t\t \"rx: short xfer (%d vs %d) in non-span mode\",\n+\t\t\t\t\t pkt_len, skb-\u003elen);\n+\t\t\t\trc = -ENOMSG;\n+\t\t\t\tgoto err_reset;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* we have (exactly) a complete packet, RX it directly */\n+\t\tif (pkt_len == skb-\u003elen) {\n+\t\t\tmctp_usblib_rx(netdev, skb);\n+\t\t\trx-\u003eskb = NULL;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* more packets follow - RX a clone so that we can continue\n+\t\t * processing the current SKB, which may be the start of a\n+\t\t * span.\n+\t\t */\n+\t\tskb2 = skb_clone(skb, GFP_ATOMIC);\n+\t\tif (skb2) {\n+\t\t\tskb_trim(skb2, pkt_len);\n+\t\t\tmctp_usblib_rx(netdev, skb2);\n+\t\t} else {\n+\t\t\tmctp_usblib_rx_stats_single_drop(netdev);\n+\t\t}\n+\t\tskb_pull(skb, pkt_len);\n+\t}\n+\n+\treturn 0;\n+\n+err_reset:\n+\tdev_kfree_skb_any(rx-\u003eskb);\n+\trx-\u003eskb = NULL;\n+\treturn rc;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete);\n+\n+/*\n+ * Cancel a rx context; subsequent prepare/complete calls will not be a\n+ * continuation of any data already received.\n+ */\n+void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx)\n+{\n+\tdev_kfree_skb_any(rx-\u003eskb);\n+\trx-\u003eskb = NULL;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel);\n+\n+/* transmit context: encapsulates one transfer */\n+struct mctp_usblib_tx_ctx {\n+\tstruct mctp_usblib_tx *tx;\n+\tstruct sk_buff_head skbs;\n+\tunsigned int buf_len, len;\n+\tenum mctp_usblib_tx_buf_type {\n+\t\tTX_SINGLE,\n+\t\tTX_FLAT,\n+\t} buf_type;\n+\tu8 buf[] ____cacheline_aligned;\n+};\n+\n+void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,\n+\t\t\t const struct mctp_usblib_tx_ops *ops,\n+\t\t\t void *priv, bool span)\n+{\n+\tmemset(tx, 0, sizeof(*tx));\n+\ttx-\u003eops = *ops;\n+\ttx-\u003epriv = priv;\n+\ttx-\u003espan = span;\n+\tspin_lock_init(\u0026tx-\u003elock);\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_init);\n+\n+static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx)\n+{\n+\treturn ctx-\u003ebuf_type == TX_SINGLE ? 0 : ctx-\u003ebuf_len - ctx-\u003elen;\n+}\n+\n+static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx)\n+{\n+\t/* Use the baseline length (ie, BTU) as an approximate\n+\t * \"reasonably-sized\" packet we could expect. If there is\n+\t * insufficient capacity for that, then send.\n+\t */\n+\tconst size_t pkt_len = MCTP_USB_BTU + sizeof(struct mctp_usb_hdr);\n+\n+\treturn mctp_usblib_tx_avail(ctx) \u003c pkt_len;\n+}\n+\n+/*\n+ * Returns zero on success, non-zero on failure - indicating that the new skb\n+ * could not be appended. So, errors reported here to the TX path will result\n+ * in the TX being transmitted.\n+ */\n+static int mctp_usblib_tx_append(struct mctp_usblib_tx_ctx *ctx,\n+\t\t\t\t struct sk_buff *skb)\n+{\n+\tif (ctx-\u003ebuf_type == TX_SINGLE)\n+\t\treturn -EINVAL;\n+\n+\tif (mctp_usblib_tx_avail(ctx) \u003c skb-\u003elen)\n+\t\treturn -ENOBUFS;\n+\n+\t__skb_queue_tail(\u0026ctx-\u003eskbs, skb);\n+\n+\tctx-\u003elen += skb-\u003elen;\n+\n+\treturn 0;\n+}\n+\n+static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx)\n+{\n+\tvoid *buf;\n+\n+\t/* If we have a qlen of 1, we only ended up packing a single skb,\n+\t * despite allocating for multiple. Skip the copy and send directly\n+\t * from the skb data.\n+\t */\n+\tif (ctx-\u003ebuf_type == TX_SINGLE || ctx-\u003eskbs.qlen == 1) {\n+\t\tbuf = ctx-\u003eskbs.next-\u003edata;\n+\n+\t} else if (ctx-\u003ebuf_type == TX_FLAT) {\n+\t\tstruct sk_buff *skb;\n+\t\tsize_t pos = 0;\n+\n+\t\tskb_queue_walk(\u0026ctx-\u003eskbs, skb) {\n+\t\t\tskb_copy_bits(skb, 0, ctx-\u003ebuf + pos, skb-\u003elen);\n+\t\t\tpos += skb-\u003elen;\n+\t\t}\n+\n+\t\tbuf = ctx-\u003ebuf;\n+\t} else {\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn ctx-\u003etx-\u003eops.send(ctx, buf, ctx-\u003elen);\n+}\n+\n+static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx,\n+\t\t\t\t enum skb_drop_reason reason)\n+{\n+\tstruct sk_buff *skb;\n+\n+\tif (!ctx)\n+\t\treturn;\n+\n+\twhile ((skb = __skb_dequeue(\u0026ctx-\u003eskbs)) != NULL)\n+\t\tdev_kfree_skb_any_reason(skb, reason);\n+\tkfree(ctx);\n+}\n+\n+void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx)\n+{\n+\treturn tx_ctx-\u003etx-\u003epriv;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_ctx_priv);\n+\n+/* caller must ensure the tx \u0026 completion path is quiesced */\n+void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx)\n+{\n+\tmctp_usblib_tx_ctx_free(tx-\u003ecur_ctx, SKB_DROP_REASON_NOT_SPECIFIED);\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini);\n+\n+/* Max size of a spanned TX. Since we allocate a separate span buffer, limit\n+ * the tx-time allocations to 4k. Larger packets will be sent as single\n+ * transfers.\n+ */\n+static const unsigned int TX_SPAN_MAX = 4096 - sizeof(struct mctp_usblib_tx_ctx);\n+\n+static struct mctp_usblib_tx_ctx *\n+mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb,\n+\t\t\t bool single)\n+{\n+\tenum mctp_usblib_tx_buf_type type;\n+\tstruct mctp_usblib_tx_ctx *ctx;\n+\tsize_t sz = 0;\n+\n+\tif (single || skb-\u003elen \u003e TX_SPAN_MAX) {\n+\t\ttype = TX_SINGLE;\n+\t} else {\n+\t\ttype = TX_FLAT;\n+\t\tsz = tx-\u003espan ? TX_SPAN_MAX : MCTP_USB_1_0_XFER_SIZE;\n+\t}\n+\n+\tctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC);\n+\tif (!ctx)\n+\t\treturn NULL;\n+\n+\tctx-\u003etx = tx;\n+\tctx-\u003ebuf_type = type;\n+\tctx-\u003ebuf_len = sz;\n+\tctx-\u003elen = skb-\u003elen;\n+\tskb_queue_head_init(\u0026ctx-\u003eskbs);\n+\t__skb_queue_tail(\u0026ctx-\u003eskbs, skb);\n+\n+\treturn ctx;\n+}\n+\n+static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx,\n+\t\t\t\t\tstruct net_device *dev,\n+\t\t\t\t\tbool ok)\n+{\n+\tstruct pcpu_dstats *dstats = get_cpu_ptr(dev-\u003edstats);\n+\tunsigned long flags;\n+\n+\tflags = u64_stats_update_begin_irqsave(\u0026dstats-\u003esyncp);\n+\tif (ok) {\n+\t\t/* Only include the network-layer data in tx stats; we know\n+\t\t * that there is a 4-byte header pushed to all skbs in\n+\t\t * tx_skb_prepare()\n+\t\t */\n+\t\tu64 n = ctx-\u003eskbs.qlen;\n+\t\ts64 len = ctx-\u003elen - (n * sizeof(struct mctp_usb_hdr));\n+\n+\t\tu64_stats_add(\u0026dstats-\u003etx_packets, n);\n+\t\tu64_stats_add(\u0026dstats-\u003etx_bytes, len);\n+\t} else {\n+\t\tu64_stats_add(\u0026dstats-\u003etx_drops, ctx-\u003eskbs.qlen);\n+\t}\n+\tu64_stats_update_end_irqrestore(\u0026dstats-\u003esyncp, flags);\n+\tput_cpu_ptr(dev-\u003edstats);\n+}\n+\n+static void mctp_usblib_tx_stats_single_drop(struct net_device *dev)\n+{\n+\tstruct pcpu_dstats *dstats = get_cpu_ptr(dev-\u003edstats);\n+\tunsigned long flags;\n+\n+\tflags = u64_stats_update_begin_irqsave(\u0026dstats-\u003esyncp);\n+\tu64_stats_inc(\u0026dstats-\u003etx_drops);\n+\tu64_stats_update_end_irqrestore(\u0026dstats-\u003esyncp, flags);\n+\tput_cpu_ptr(dev-\u003edstats);\n+}\n+\n+/*\n+ * Completion for the -\u003esend() op. This will update netdev stats and\n+ * free the tx context.\n+ *\n+ * Likely called from (atomic) URB completion context.\n+ */\n+void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx,\n+\t\t\t\t struct net_device *dev, bool ok)\n+{\n+\tenum skb_drop_reason reason =\n+\t\tok ? SKB_CONSUMED : SKB_DROP_REASON_NOT_SPECIFIED;\n+\n+\tmctp_usblib_tx_stats_update(tx_ctx, dev, ok);\n+\tmctp_usblib_tx_ctx_free(tx_ctx, reason);\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete);\n+\n+/* Prepare a skb for push()\n+ *\n+ * On error, populates @reason.\n+ */\n+static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span,\n+\t\t\t\t enum skb_drop_reason *reason)\n+{\n+\tunsigned long plen, max_len;\n+\tstruct mctp_usb_hdr *hdr;\n+\tint rc;\n+\n+\tmax_len = span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;\n+\n+\tplen = skb-\u003elen;\n+\tif (plen + sizeof(*hdr) \u003e max_len) {\n+\t\t*reason = SKB_DROP_REASON_PKT_TOO_BIG;\n+\t\treturn -EMSGSIZE;\n+\t}\n+\n+\trc = skb_cow_head(skb, sizeof(*hdr));\n+\tif (rc) {\n+\t\t*reason = SKB_DROP_REASON_NOMEM;\n+\t\treturn rc;\n+\t}\n+\n+\thdr = skb_push(skb, sizeof(*hdr));\n+\tif (!hdr) {\n+\t\t*reason = SKB_DROP_REASON_NOMEM;\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\thdr-\u003eid = cpu_to_be16(MCTP_USB_DMTF_ID);\n+\thdr-\u003elen = cpu_to_be16(plen + sizeof(*hdr));\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Push a new skb to the transfer. May result in zero or more calls to\n+ * ops-\u003esend().\n+ *\n+ * Takes ownership of @skb, including on error.\n+ */\n+int mctp_usblib_tx_push(struct net_device *dev,\n+\t\t\tstruct mctp_usblib_tx *tx,\n+\t\t\tstruct sk_buff *skb, bool more)\n+{\n+\tstruct mctp_usblib_tx_ctx *ctx, *send_ctx = NULL;\n+\tenum skb_drop_reason reason;\n+\tconst int max_tries = 3;\n+\tunsigned long flags;\n+\tint try = 1, rc;\n+\n+\trc = mctp_usblib_tx_skb_prepare(skb, tx-\u003espan, \u0026reason);\n+\tif (rc) {\n+\t\tmctp_usblib_tx_stats_single_drop(dev);\n+\t\tkfree_skb_reason(skb, reason);\n+\t\t/* we may still need to proceed, in case an existing ctx\n+\t\t * is now sendable (ie.: !more).\n+\t\t */\n+\t\tskb = NULL;\n+\t}\n+\n+\treason = SKB_DROP_REASON_NOT_SPECIFIED;\n+retry:\n+\t/* Try and queue to the current context. We exit this critical section\n+\t * with a few bits of state:\n+\t * - send_ctx: indicating a prior context that needs to be sent\n+\t * - skb: indicating that a skb still needs to be queued/sent\n+\t */\n+\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\tctx = tx-\u003ecur_ctx;\n+\tif (ctx) {\n+\t\tif (skb) {\n+\t\t\trc = mctp_usblib_tx_append(ctx, skb);\n+\t\t\tif (rc) {\n+\t\t\t\t/* can't append to the pending tx - detach for\n+\t\t\t\t * sending, and we'll create a new tx below.\n+\t\t\t\t */\n+\t\t\t\tswap(tx-\u003ecur_ctx, send_ctx);\n+\t\t\t} else {\n+\t\t\t\t/* we have queued */\n+\t\t\t\tskb = NULL;\n+\t\t\t\tif (!more || mctp_usblib_tx_should_send(ctx))\n+\t\t\t\t\tswap(tx-\u003ecur_ctx, send_ctx);\n+\t\t\t}\n+\t\t} else if (!more) {\n+\t\t\tswap(tx-\u003ecur_ctx, send_ctx);\n+\t\t}\n+\t}\n+\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n+\n+\tif (send_ctx) {\n+\t\trc = mctp_usblib_tx_send(send_ctx);\n+\t\tif (rc) {\n+\t\t\tmctp_usblib_tx_stats_update(send_ctx, dev, false);\n+\t\t\tmctp_usblib_tx_ctx_free(send_ctx, reason);\n+\t\t}\n+\t\tsend_ctx = NULL;\n+\t}\n+\n+\t/* we have either queued, or the prepare failed; nothing more to do */\n+\tif (!skb)\n+\t\treturn 0;\n+\n+\tctx = mctp_usblib_tx_ctx_create(tx, skb, !more);\n+\tif (!ctx) {\n+\t\tnetdev_dbg(dev, \"TX context create failed\\n\");\n+\t\tmctp_usblib_tx_stats_single_drop(dev);\n+\t\tkfree_skb(skb);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\t/* if we're ready to send now, no need to enqueue */\n+\tif (!more || mctp_usblib_tx_should_send(ctx)) {\n+\t\trc = mctp_usblib_tx_send(ctx);\n+\t\tif (rc) {\n+\t\t\tmctp_usblib_tx_stats_update(ctx, dev, false);\n+\t\t\tmctp_usblib_tx_ctx_free(ctx, reason);\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\tif (!tx-\u003ecur_ctx) {\n+\t\ttx-\u003ecur_ctx = ctx;\n+\t\tctx = NULL;\n+\t}\n+\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n+\n+\t/* we may have lost the race with a concurrent tx; shouldn't happen, as\n+\t * ndo_start_xmit should be serialised over one queue, but try again\n+\t * from the top, as we may be able to queue the skb to that context.\n+\t */\n+\tif (ctx) {\n+\t\t/* unlink the new (sole) skb, we don't want it freed with ctx */\n+\t\t__skb_queue_head_init(\u0026ctx-\u003eskbs);\n+\t\tmctp_usblib_tx_ctx_free(ctx, reason);\n+\t\tif (++try \u003e max_tries) {\n+\t\t\tkfree_skb(skb);\n+\t\t\tmctp_usblib_tx_stats_single_drop(dev);\n+\t\t\treturn -EBUSY;\n+\t\t}\n+\t\tgoto retry;\n+\t}\n+\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_push);\n+\n+/* Cancel a tx: any un-sent context is released. */\n+void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev,\n+\t\t\t enum skb_drop_reason reason)\n+{\n+\tstruct mctp_usblib_tx_ctx *ctx = NULL;\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026tx-\u003elock, flags);\n+\tswap(tx-\u003ecur_ctx, ctx);\n+\tspin_unlock_irqrestore(\u0026tx-\u003elock, flags);\n+\n+\tif (!ctx)\n+\t\treturn;\n+\n+\tmctp_usblib_tx_stats_update(ctx, dev, false);\n+\tmctp_usblib_tx_ctx_free(ctx, reason);\n+}\n+EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel);\n+\n+MODULE_LICENSE(\"GPL\");\n+MODULE_AUTHOR(\"Jeremy Kerr \u003cjk@codeconstruct.com.au\u003e\");\n+MODULE_DESCRIPTION(\"MCTP USB transport library\");\n+\n+#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST)\n+#include \"mctp-usblib-test.c\"\n+#endif\ndiff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h\nindex a2f6f1e04efbd0..35cc2cc15f9a04 100644\n--- a/include/linux/usb/mctp-usb.h\n+++ b/include/linux/usb/mctp-usb.h\n@@ -2,7 +2,7 @@\n /*\n * mctp-usb.h - MCTP USB transport binding: common definitions,\n * based on DMTF0283 specification:\n- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf\n+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf\n *\n * These are protocol-level definitions, that may be shared between host\n * and gadget drivers.\n@@ -13,18 +13,96 @@\n #ifndef __LINUX_USB_MCTP_USB_H\n #define __LINUX_USB_MCTP_USB_H\n \n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n #include \u003clinux/types.h\u003e\n \n+/*\n+ * MCTP-over-USB transport header. DSP0283 v1.0 has an 8-bit length field\n+ * (preceded by 8 reserved bits), v1.1 has a 13-bit length field (preceded by\n+ * 3 reserved bits). We use a be16 for our length to handle the larger v1.1\n+ * representation, and mask as appropriate.\n+ */\n struct mctp_usb_hdr {\n \t__be16\tid;\n-\tu8\trsvd;\n-\tu8\tlen;\n+\t__be16\tlen;\n } __packed;\n \n-#define MCTP_USB_XFER_SIZE\t512\n+/* max transfer size for DSP0283 v1.0 */\n+#define MCTP_USB_1_0_XFER_SIZE\t512\n #define MCTP_USB_BTU\t\t68\n #define MCTP_USB_MTU_MIN\tMCTP_USB_BTU\n-#define MCTP_USB_MTU_MAX\t(U8_MAX - sizeof(struct mctp_usb_hdr))\n+#define MCTP_USB_1_0_PKTLEN_MAX\tU8_MAX\n+#define MCTP_USB_1_0_MTU_MAX\t(MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))\n+#define MCTP_USB_1_1_PKTLEN_MAX\tGENMASK(12, 0)\n+#define MCTP_USB_1_1_MTU_MAX\t(MCTP_USB_1_1_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))\n #define MCTP_USB_DMTF_ID\t0x1ab4\n \n+/* mctp-usblib */\n+\n+/*\n+ * RX handle: drivers will typically create one on init, which persists for\n+ * the life of the driver. The same handle is used for progressive\n+ * prepare -\u003e complete operations (for each incoming USB transfer), which\n+ * result in netif_rx()-ing the MCTP packets received\n+ */\n+struct mctp_usblib_rx {\n+\tstruct sk_buff *skb;\n+\tu16 ep_pktlen;\n+\tbool span;\n+};\n+\n+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span);\n+void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx);\n+\n+int mctp_usblib_rx_prepare(struct net_device *netdev,\n+\t\t\t struct mctp_usblib_rx *rx,\n+\t\t\t void **bufp, size_t *lenp, gfp_t gfp);\n+\n+int mctp_usblib_rx_complete(struct net_device *netdev,\n+\t\t\t struct mctp_usblib_rx *rx, size_t len);\n+\n+void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx);\n+\n+/*\n+ * TX handle: created by mctp_usblib_tx_push() during the tx path, and\n+ * may persist across multiple packet transmits.\n+ */\n+struct mctp_usblib_tx_ctx;\n+\n+struct mctp_usblib_tx_ops {\n+\t/* Start a USB TX for @data. On returning success, the implementation\n+\t * must arrange for mctp_usblib_tx_send_complete() to be called at some\n+\t * later point (eg., on urb completion).\n+\t */\n+\tint (*send)(struct mctp_usblib_tx_ctx *tx_ctx, void *data, size_t len);\n+};\n+\n+struct mctp_usblib_tx {\n+\tstruct mctp_usblib_tx_ops ops;\n+\tvoid *priv;\n+\tbool span;\n+\t/* protects access to cur_ctx */\n+\tspinlock_t lock;\n+\t/* context to which we are adding packets, cleared on send */\n+\tstruct mctp_usblib_tx_ctx *cur_ctx;\n+};\n+\n+void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,\n+\t\t\t const struct mctp_usblib_tx_ops *ops, void *priv,\n+\t\t\t bool span);\n+void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx);\n+\n+void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx);\n+\n+int mctp_usblib_tx_push(struct net_device *dev,\n+\t\t\tstruct mctp_usblib_tx *tx,\n+\t\t\tstruct sk_buff *skb, bool more);\n+\n+void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx,\n+\t\t\t\t struct net_device *dev, bool ok);\n+\n+void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev,\n+\t\t\t enum skb_drop_reason reason);\n+\n #endif /* __LINUX_USB_MCTP_USB_H */\n"
} |
| 2/1 | 2026/07/08 23:29 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"MCTP",
"MCTP_TRANSPORT_USB",
"MCTP_TRANSPORT_USBLIB"
],
"FocusSymbols": [
"mctp_usblib_rx_prepare",
"mctp_usblib_rx_complete",
"mctp_usblib_tx_push",
"mctp_usblib_tx_send_complete",
"mctp_usb_in_complete",
"mctp_usb_out_complete",
"mctp_usb_tx_send",
"mctp_usb_start_xmit",
"mctp_usb_rx_queue"
],
"Reasoning": "The patch introduces a new common library for MCTP-over-USB (`mctp-usblib`) and modifies the MCTP USB transport driver (`mctp-usb`) to use it. The changes are functional and reachable via USB gadget emulation (e.g., raw-gadget).",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d6da7744f259a2362ba413449d66e16101358e81
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jul 8 23:29:47 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig
index cf325ab0b1ef55..c40ac9c665b758 100644
--- a/drivers/net/mctp/Kconfig
+++ b/drivers/net/mctp/Kconfig
@@ -47,9 +47,25 @@ config MCTP_TRANSPORT_I3C
A MCTP protocol network device is created for each I3C bus
having a "mctp-controller" devicetree property.
+config MCTP_TRANSPORT_USBLIB
+ tristate "MCTP over USB common library"
+ depends on USB
+ help
+ Common protocol handling functions for MCTP-over-USB transport
+ implementations, suitable for use in either host- or gadget-side
+ transport driver
+
+ This will be automatically enabled by the transport driver.
+
+config MCTP_TRANSPORT_USBLIB_TEST
+ bool "MCTP usblib tests" if !KUNIT_ALL_TESTS
+ depends on MCTP_TRANSPORT_USBLIB=y && KUNIT=y
+ default KUNIT_ALL_TESTS
+
config MCTP_TRANSPORT_USB
tristate "MCTP USB transport"
depends on USB
+ select MCTP_TRANSPORT_USBLIB
help
Provides a driver to access MCTP devices over USB transport,
defined by DMTF specification DSP0283.
diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile
index c36006849a1e7d..c870b62d3f1c95 100644
--- a/drivers/net/mctp/Makefile
+++ b/drivers/net/mctp/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o
obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o
obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o
obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o
+obj-$(CONFIG_MCTP_TRANSPORT_USBLIB) += mctp-usblib.o
diff --git a/drivers/net/mctp/mctp-usb.c b/drivers/net/mctp/mctp-usb.c
index fade65f2f26995..2d31075374b45b 100644
--- a/drivers/net/mctp/mctp-usb.c
+++ b/drivers/net/mctp/mctp-usb.c
@@ -3,9 +3,9 @@
* mctp-usb.c - MCTP-over-USB (DMTF DSP0283) transport binding driver.
*
* DSP0283 is available at:
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
*
- * Copyright (C) 2024-2025 Code Construct Pty Ltd
+ * Copyright (C) 2024-2026 Code Construct Pty Ltd
*/
#include <linux/module.h>
@@ -22,95 +22,115 @@
struct mctp_usb {
struct usb_device *usbdev;
struct usb_interface *intf;
+ bool span;
struct net_device *netdev;
u8 ep_in;
u8 ep_out;
- struct urb *tx_urb;
+ struct mctp_usblib_rx rx;
struct urb *rx_urb;
+ int in_err_count;
+ int in_err_orig;
+ bool clear_halt;
/* enforces atomic access to rx_stopped and requeuing the retry work */
spinlock_t rx_lock;
bool rx_stopped;
struct delayed_work rx_retry_work;
+
+ struct mctp_usblib_tx tx;
+ struct usb_anchor tx_anchor;
+ /* serialises tx_qmem updates to netdev queue states */
+ spinlock_t tx_qmem_lock;
+ int tx_qmem;
+};
+
+enum {
+ MCTP_USB_SUBCLASS_BASE = 0x00,
+ MCTP_USB_SUBCLASS_SPAN = 0x02,
};
+/* We use a total-size limit for outstanding URBs, as the transfer counts
+ * may vary a lot between spanning- and non-spanning modes. In spanning mode,
+ * this will allow for a couple of max-sized transfers to be in flight. In
+ * non-spanning mode, 32.
+ *
+ * We want to avoid disabling the tx queue if possible; doing so will end up
+ * requeueing to gso_skb, and we only dequeue from that one skb at a time,
+ * so can no longer perform transfer packing.
+ */
+static const unsigned int TX_QMEM_MAX = 16384;
+
static void mctp_usb_out_complete(struct urb *urb)
{
- struct sk_buff *skb = urb->context;
- struct net_device *netdev = skb->dev;
- int status;
+ struct mctp_usblib_tx_ctx *tx_ctx = urb->context;
+ struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);
+ unsigned int len = urb->transfer_buffer_length;
+ struct net_device *netdev = mctp_usb->netdev;
+ unsigned long flags;
- status = urb->status;
+ mctp_usblib_tx_send_complete(tx_ctx, netdev, urb->status == 0);
- switch (status) {
- case -ENOENT:
- case -ECONNRESET:
- case -ESHUTDOWN:
- case -EPROTO:
- dev_dstats_tx_dropped(netdev);
- break;
- case 0:
- dev_dstats_tx_add(netdev, skb->len);
- netif_wake_queue(netdev);
- consume_skb(skb);
- return;
- default:
- netdev_dbg(netdev, "unexpected tx urb status: %d\n", status);
- dev_dstats_tx_dropped(netdev);
- }
+ usb_free_urb(urb);
- kfree_skb(skb);
+ spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags);
+ mctp_usb->tx_qmem -= len;
+ if (mctp_usb->tx_qmem < TX_QMEM_MAX && netif_running(netdev))
+ netif_wake_queue(netdev);
+ spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags);
}
-static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,
- struct net_device *dev)
+static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,
+ void *data, size_t len)
{
- struct mctp_usb *mctp_usb = netdev_priv(dev);
- struct mctp_usb_hdr *hdr;
- unsigned int plen;
+ struct mctp_usb *mctp_usb = mctp_usblib_tx_ctx_priv(tx_ctx);
+ unsigned long flags;
struct urb *urb;
int rc;
- plen = skb->len;
-
- if (plen + sizeof(*hdr) > MCTP_USB_XFER_SIZE)
- goto err_drop;
-
- rc = skb_cow_head(skb, sizeof(*hdr));
- if (rc)
- goto err_drop;
-
- hdr = skb_push(skb, sizeof(*hdr));
- if (!hdr)
- goto err_drop;
-
- hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID);
- hdr->rsvd = 0;
- hdr->len = plen + sizeof(*hdr);
-
- urb = mctp_usb->tx_urb;
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb)
+ return -ENOMEM;
usb_fill_bulk_urb(urb, mctp_usb->usbdev,
usb_sndbulkpipe(mctp_usb->usbdev, mctp_usb->ep_out),
- skb->data, skb->len,
- mctp_usb_out_complete, skb);
+ data, len, mctp_usb_out_complete, tx_ctx);
+
+ if (mctp_usb->span)
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
+ usb_anchor_urb(urb, &mctp_usb->tx_anchor);
- /* Stops TX queue first to prevent race condition with URB complete */
- netif_stop_queue(dev);
rc = usb_submit_urb(urb, GFP_ATOMIC);
if (rc) {
- netif_wake_queue(dev);
- goto err_drop;
+ netdev_dbg(mctp_usb->netdev, "TX urb submit failed, %d\n", rc);
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ } else {
+ spin_lock_irqsave(&mctp_usb->tx_qmem_lock, flags);
+ mctp_usb->tx_qmem += len;
+ if (mctp_usb->tx_qmem >= TX_QMEM_MAX)
+ netif_stop_queue(mctp_usb->netdev);
+ spin_unlock_irqrestore(&mctp_usb->tx_qmem_lock, flags);
}
- return NETDEV_TX_OK;
+ return rc;
+}
+
+static const struct mctp_usblib_tx_ops tx_ops = {
+ .send = mctp_usb_tx_send,
+};
+
+static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct mctp_usb *mctp_usb = netdev_priv(dev);
+ bool more = netdev_xmit_more();
+
+ mctp_usblib_tx_push(dev, &mctp_usb->tx, skb, more);
-err_drop:
- dev_dstats_tx_dropped(dev);
- kfree_skb(skb);
return NETDEV_TX_OK;
}
@@ -125,24 +145,23 @@ static const unsigned long RX_RETRY_DELAY = HZ / 4;
static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)
{
unsigned long flags;
- struct sk_buff *skb;
+ size_t len;
+ void *buf;
int rc;
- skb = __netdev_alloc_skb(mctp_usb->netdev, MCTP_USB_XFER_SIZE, gfp);
- if (!skb) {
- rc = -ENOMEM;
+ rc = mctp_usblib_rx_prepare(mctp_usb->netdev, &mctp_usb->rx,
+ &buf, &len, gfp);
+ if (rc)
goto err_retry;
- }
usb_fill_bulk_urb(mctp_usb->rx_urb, mctp_usb->usbdev,
usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in),
- skb->data, MCTP_USB_XFER_SIZE,
- mctp_usb_in_complete, skb);
+ buf, len, mctp_usb_in_complete, mctp_usb);
rc = usb_submit_urb(mctp_usb->rx_urb, gfp);
if (rc) {
netdev_dbg(mctp_usb->netdev, "rx urb submit failure: %d\n", rc);
- kfree_skb(skb);
+ mctp_usblib_rx_cancel(&mctp_usb->rx);
if (rc == -ENOMEM)
goto err_retry;
}
@@ -157,13 +176,13 @@ static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)
return 0;
}
+static const unsigned int rx_err_max = 10;
+
static void mctp_usb_in_complete(struct urb *urb)
{
- struct sk_buff *skb = urb->context;
- struct net_device *netdev = skb->dev;
- struct mctp_usb *mctp_usb = netdev_priv(netdev);
- struct mctp_skb_cb *cb;
- unsigned int len;
+ struct mctp_usb *mctp_usb = urb->context;
+ struct net_device *netdev = mctp_usb->netdev;
+ unsigned long flags;
int status;
status = urb->status;
@@ -172,80 +191,54 @@ static void mctp_usb_in_complete(struct urb *urb)
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
- case -EPROTO:
- kfree_skb(skb);
+ /* device shutdown, don't resubmit */
+ mctp_usblib_rx_cancel(&mctp_usb->rx);
return;
- case 0:
- break;
- default:
- netdev_dbg(netdev, "unexpected rx urb status: %d\n", status);
- kfree_skb(skb);
- return;
- }
-
- len = urb->actual_length;
- __skb_put(skb, len);
-
- while (skb) {
- struct sk_buff *skb2 = NULL;
- struct mctp_usb_hdr *hdr;
- u8 pkt_len; /* length of MCTP packet, no USB header */
- skb_reset_mac_header(skb);
- hdr = skb_pull_data(skb, sizeof(*hdr));
- if (!hdr)
- break;
-
- if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) {
- netdev_dbg(netdev, "rx: invalid id %04x\n",
- be16_to_cpu(hdr->id));
- break;
- }
+ case -EPIPE:
+ /* endpoint stall: clear halt, which will cause a resubmit */
- if (hdr->len <
- sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) {
- netdev_dbg(netdev, "rx: short packet (hdr) %d\n",
- hdr->len);
- break;
+ if (!mctp_usb->in_err_count++)
+ mctp_usb->in_err_orig = status;
+ if (mctp_usb->in_err_count >= rx_err_max) {
+ netdev_err(netdev, "excessive stalls from IN EP\n");
+ return;
}
- /* we know we have at least sizeof(struct mctp_usb_hdr) here */
- pkt_len = hdr->len - sizeof(struct mctp_usb_hdr);
- if (pkt_len > skb->len) {
- netdev_dbg(netdev,
- "rx: short packet (xfer) %d, actual %d\n",
- hdr->len, skb->len);
- break;
- }
+ mctp_usb->clear_halt = true;
+ spin_lock_irqsave(&mctp_usb->rx_lock, flags);
+ if (!mctp_usb->rx_stopped)
+ schedule_delayed_work(&mctp_usb->rx_retry_work,
+ RX_RETRY_DELAY);
+ spin_unlock_irqrestore(&mctp_usb->rx_lock, flags);
+ mctp_usblib_rx_cancel(&mctp_usb->rx);
+ return;
- if (pkt_len < skb->len) {
- /* more packets may follow - clone to a new
- * skb to use on the next iteration
- */
- skb2 = skb_clone(skb, GFP_ATOMIC);
- if (skb2) {
- if (!skb_pull(skb2, pkt_len)) {
- kfree_skb(skb2);
- skb2 = NULL;
- }
- }
- skb_trim(skb, pkt_len);
+ default:
+ netdev_dbg(netdev, "unexpected rx urb status: %d\n", status);
+ fallthrough;
+ case -ETIME:
+ case -EPROTO:
+ case -EILSEQ:
+ case -EOVERFLOW:
+ /* possibly transient; record first failure, resubmit */
+ mctp_usblib_rx_cancel(&mctp_usb->rx);
+ if (!mctp_usb->in_err_count++)
+ mctp_usb->in_err_orig = status;
+ if (mctp_usb->in_err_count >= rx_err_max) {
+ netdev_err(netdev,
+ "excessive errors from IN EP, first: %d\n",
+ mctp_usb->in_err_orig);
+ return;
}
+ break;
- dev_dstats_rx_add(netdev, skb->len);
-
- skb->protocol = htons(ETH_P_MCTP);
- skb_reset_network_header(skb);
- cb = __mctp_cb(skb);
- cb->halen = 0;
- netif_rx(skb);
-
- skb = skb2;
+ case 0:
+ mctp_usblib_rx_complete(netdev, &mctp_usb->rx, urb->actual_length);
+ mctp_usb->in_err_count = 0;
+ break;
}
- if (skb)
- kfree_skb(skb);
-
mctp_usb_rx_queue(mctp_usb, GFP_ATOMIC);
}
@@ -253,6 +246,20 @@ static void mctp_usb_rx_retry_work(struct work_struct *work)
{
struct mctp_usb *mctp_usb = container_of(work, struct mctp_usb,
rx_retry_work.work);
+ int rc;
+
+ /* We are only called when rx completions are suspended */
+ if (mctp_usb->clear_halt) {
+ int pipe = usb_rcvbulkpipe(mctp_usb->usbdev, mctp_usb->ep_in);
+
+ rc = usb_clear_halt(mctp_usb->usbdev, pipe);
+ if (rc) {
+ netdev_err(mctp_usb->netdev,
+ "can't clear IN EP halt: %d\n", rc);
+ return;
+ }
+ mctp_usb->clear_halt = false;
+ }
mctp_usb_rx_queue(mctp_usb, GFP_KERNEL);
}
@@ -262,6 +269,8 @@ static int mctp_usb_open(struct net_device *dev)
struct mctp_usb *mctp_usb = netdev_priv(dev);
WRITE_ONCE(mctp_usb->rx_stopped, false);
+ mctp_usb->clear_halt = false;
+ mctp_usb->in_err_count = 0;
netif_start_queue(dev);
@@ -284,7 +293,10 @@ static int mctp_usb_stop(struct net_device *dev)
flush_delayed_work(&mctp_usb->rx_retry_work);
usb_kill_urb(mctp_usb->rx_urb);
- usb_kill_urb(mctp_usb->tx_urb);
+
+ usb_kill_anchored_urbs(&mctp_usb->tx_anchor);
+
+ mctp_usblib_tx_cancel(&mctp_usb->tx, dev, SKB_DROP_REASON_DEV_READY);
return 0;
}
@@ -301,7 +313,7 @@ static void mctp_usb_netdev_setup(struct net_device *dev)
dev->mtu = MCTP_USB_MTU_MIN;
dev->min_mtu = MCTP_USB_MTU_MIN;
- dev->max_mtu = MCTP_USB_MTU_MAX;
+ dev->max_mtu = MCTP_USB_1_0_MTU_MAX;
dev->hard_header_len = sizeof(struct mctp_usb_hdr);
dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
@@ -317,6 +329,7 @@ static int mctp_usb_probe(struct usb_interface *intf,
struct usb_host_interface *iface_desc;
struct net_device *netdev;
struct mctp_usb *dev;
+ bool span;
int rc;
/* only one alternate */
@@ -328,6 +341,8 @@ static int mctp_usb_probe(struct usb_interface *intf,
return rc;
}
+ span = iface_desc->desc.bInterfaceSubClass == MCTP_USB_SUBCLASS_SPAN;
+
netdev = alloc_netdev(sizeof(*dev), "mctpusb%d", NET_NAME_ENUM,
mctp_usb_netdev_setup);
if (!netdev)
@@ -335,33 +350,43 @@ static int mctp_usb_probe(struct usb_interface *intf,
SET_NETDEV_DEV(netdev, &intf->dev);
dev = netdev_priv(netdev);
+ dev->span = span;
dev->netdev = netdev;
dev->usbdev = interface_to_usbdev(intf);
dev->intf = intf;
spin_lock_init(&dev->rx_lock);
+ if (dev->span)
+ netdev->max_mtu = MCTP_USB_1_1_MTU_MAX;
+ spin_lock_init(&dev->tx_qmem_lock);
usb_set_intfdata(intf, dev);
+ mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize),
+ dev->span);
+ mctp_usblib_tx_init(&dev->tx, &tx_ops, dev, dev->span);
+ init_usb_anchor(&dev->tx_anchor);
+
dev->ep_in = ep_in->bEndpointAddress;
dev->ep_out = ep_out->bEndpointAddress;
- dev->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
dev->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!dev->tx_urb || !dev->rx_urb) {
+ if (!dev->rx_urb) {
rc = -ENOMEM;
- goto err_free_urbs;
+ goto err_fini_rxtx;
}
INIT_DELAYED_WORK(&dev->rx_retry_work, mctp_usb_rx_retry_work);
rc = mctp_register_netdev(netdev, NULL, MCTP_PHYS_BINDING_USB);
if (rc)
- goto err_free_urbs;
+ goto err_free_urb;
return 0;
-err_free_urbs:
- usb_free_urb(dev->tx_urb);
+err_free_urb:
usb_free_urb(dev->rx_urb);
+err_fini_rxtx:
+ mctp_usblib_tx_fini(&dev->tx);
+ mctp_usblib_rx_fini(&dev->rx);
free_netdev(netdev);
return rc;
}
@@ -371,13 +396,15 @@ static void mctp_usb_disconnect(struct usb_interface *intf)
struct mctp_usb *dev = usb_get_intfdata(intf);
mctp_unregister_netdev(dev->netdev);
- usb_free_urb(dev->tx_urb);
+ mctp_usblib_rx_fini(&dev->rx);
+ mctp_usblib_tx_fini(&dev->tx);
usb_free_urb(dev->rx_urb);
free_netdev(dev->netdev);
}
static const struct usb_device_id mctp_usb_devices[] = {
- { USB_INTERFACE_INFO(USB_CLASS_MCTP, 0x0, 0x1) },
+ { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_BASE, 0x1) },
+ { USB_INTERFACE_INFO(USB_CLASS_MCTP, MCTP_USB_SUBCLASS_SPAN, 0x1) },
{ 0 },
};
diff --git a/drivers/net/mctp/mctp-usblib-test.c b/drivers/net/mctp/mctp-usblib-test.c
new file mode 100644
index 00000000000000..2a22be999fa0a1
--- /dev/null
+++ b/drivers/net/mctp/mctp-usblib-test.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mctp-usblib-test.c - MCTP-over-USB (DMTF DSP0283) transport helper library,
+ * unit test definitions.
+ *
+ * Copyright (C) 2026 Code Construct Pty Ltd
+ */
+
+#include <uapi/linux/netdevice.h>
+#include <linux/netdevice.h>
+#include <kunit/test.h>
+#include <linux/if_arp.h>
+#include <net/mctp.h>
+#include <net/mctpdevice.h>
+#include <linux/usb/mctp-usb.h>
+
+struct mctp_usblib_test_dev {
+ struct net_device *ndev;
+ struct mctp_dev *mdev;
+ struct sk_buff_head rx_pkts;
+};
+
+struct mctp_usblib_test_ctx {
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_route rt;
+};
+
+static netdev_tx_t mctp_usblib_dev_tx(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ /* we don't track any TXed packets at present */
+ kfree_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops mctp_test_netdev_ops = {
+ .ndo_start_xmit = mctp_usblib_dev_tx,
+};
+
+static const u16 ep_maxpacket = 512;
+static const mctp_eid_t local_eid = 8;
+
+static void mctp_usblib_dev_setup(struct net_device *ndev)
+{
+ ndev->type = ARPHRD_MCTP;
+ ndev->mtu = 8192;
+ ndev->flags = IFF_NOARP;
+ ndev->netdev_ops = &mctp_test_netdev_ops;
+ ndev->needs_free_netdev = true;
+ ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
+}
+
+static void mctp_usblib_test_dev_action(void *data)
+{
+ struct mctp_usblib_test_dev *dev = data;
+
+ skb_queue_purge(&dev->rx_pkts);
+ if (dev->mdev)
+ mctp_dev_put(dev->mdev);
+ unregister_netdev(dev->ndev);
+}
+
+static struct mctp_usblib_test_dev *
+mctp_usblib_test_create_dev(struct kunit *test)
+{
+ struct mctp_usblib_test_dev *dev;
+ struct net_device *ndev;
+ int rc;
+
+ ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM,
+ mctp_usblib_dev_setup);
+ if (!ndev)
+ return NULL;
+
+ dev = netdev_priv(ndev);
+ dev->ndev = ndev;
+ skb_queue_head_init(&dev->rx_pkts);
+
+ rc = register_netdev(ndev);
+ if (rc) {
+ free_netdev(ndev);
+ return NULL;
+ }
+
+ rc = kunit_add_action_or_reset(test, mctp_usblib_test_dev_action, dev);
+ if (rc)
+ return NULL;
+
+ rcu_read_lock();
+ dev->mdev = __mctp_dev_get(ndev);
+ if (dev->mdev)
+ dev->mdev->net = mctp_default_net(dev_net(ndev));
+ rcu_read_unlock();
+
+ if (!dev->mdev)
+ return NULL;
+
+ rtnl_lock();
+ rc = dev_open(ndev, NULL);
+ rtnl_unlock();
+ if (rc)
+ return NULL;
+
+ return dev;
+}
+
+static int mctp_usblib_test_dst_output(struct mctp_dst *dst,
+ struct sk_buff *skb)
+{
+ struct mctp_usblib_test_dev *dev = netdev_priv(skb->dev);
+
+ skb_queue_tail(&dev->rx_pkts, skb);
+
+ return 0;
+}
+
+static void mctp_usblib_test_fini_action(void *data)
+{
+ struct mctp_usblib_test_ctx *ctx = data;
+
+ /* The device will have been destroyed, so ->rt will be unlinked.
+ * Just ensure that the refcount is as expected.
+ */
+ KUNIT_ASSERT_TRUE(current->kunit_test,
+ refcount_dec_and_test(&ctx->rt.refs));
+
+ kfree(ctx);
+}
+
+static struct mctp_usblib_test_ctx *mctp_usblib_test_init(struct kunit *test)
+{
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_route *rt;
+ int rc;
+
+ ctx = kzalloc_obj(*ctx);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+ INIT_LIST_HEAD(&ctx->rt.list);
+ rt = &ctx->rt;
+ refcount_set(&rt->refs, 1);
+
+ rc = kunit_add_action_or_reset(test, mctp_usblib_test_fini_action, ctx);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ ctx->dev = mctp_usblib_test_create_dev(test);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->dev);
+
+ rt->min = local_eid;
+ rt->max = local_eid;
+ rt->dst_type = MCTP_ROUTE_DIRECT;
+ rt->type = RTN_LOCAL;
+ rt->dev = ctx->dev->mdev;
+ rt->output = mctp_usblib_test_dst_output;
+
+ rtnl_lock();
+ list_add_rcu(&ctx->rt.list, &init_net.mctp.routes);
+ refcount_inc(&rt->refs);
+ rtnl_unlock();
+
+ return ctx;
+}
+
+/* Init a MCTP-over-USB packet within a buffer. @len is the length of the
+ * buffer to write, @payload_len is the reported size of the MCTP-over-USB
+ * packet.
+ */
+static void mctp_usblib_test_init_pkt(void *data, size_t len,
+ size_t payload_len)
+{
+ struct {
+ struct mctp_usb_hdr usb;
+ struct mctp_hdr mctp;
+ } hdr;
+
+ hdr.usb.id = cpu_to_be16(MCTP_USB_DMTF_ID);
+ hdr.usb.len = cpu_to_be16(payload_len);
+ hdr.mctp.ver = 1;
+ hdr.mctp.dest = local_eid;
+ hdr.mctp.src = 0;
+ hdr.mctp.flags_seq_tag = 0;
+
+ memcpy(data, &hdr, min(len, sizeof(hdr)));
+ if (len > sizeof(hdr))
+ memset(data + sizeof(hdr), 0, len - sizeof(hdr));
+}
+
+static void action_rx_fini(void *data)
+{
+ struct mctp_usblib_rx *rx = data;
+
+ mctp_usblib_rx_fini(rx);
+ kfree(rx);
+}
+
+static struct mctp_usblib_rx *
+mctp_usblib_test_rx_init(struct kunit *test, bool span)
+{
+ struct mctp_usblib_rx *rx;
+ int rc;
+
+ rx = kzalloc_obj(*rx);
+ if (rx) {
+ rc = kunit_add_action_or_reset(test, action_rx_fini, rx);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+ }
+ KUNIT_ASSERT_NOT_NULL(test, rx);
+ mctp_usblib_rx_init(rx, ep_maxpacket, span);
+
+ return rx;
+}
+
+/* Wrappers for usblib's rx_complete callback, which is intended to be called
+ * from atomic context
+ */
+static int mctp_usblib_test_rx_complete(struct net_device *netdev,
+ struct mctp_usblib_rx *rx, size_t len)
+{
+ int rc;
+
+ local_bh_disable();
+ rc = mctp_usblib_rx_complete(netdev, rx, len);
+ local_bh_enable();
+
+ return rc;
+}
+
+/* Single packet, starting on a transfer boundary, contained entirely within
+ * the transfer
+ */
+static void mctp_usblib_test_rx_single(struct kunit *test)
+{
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_usblib_rx *rx;
+ struct sk_buff *skb;
+ size_t len;
+ void *buf;
+ int rc;
+
+ ctx = mctp_usblib_test_init(test);
+ dev = ctx->dev;
+
+ rx = mctp_usblib_test_rx_init(test, true);
+
+ rc = mctp_usblib_rx_prepare(dev->ndev, rx,
+ &buf, &len, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ /* we should always have a maxpacket of transfer available */
+ KUNIT_ASSERT_GE(test, len, ep_maxpacket);
+
+ mctp_usblib_test_init_pkt(buf, 8, 8);
+
+ rc = mctp_usblib_test_rx_complete(dev->ndev, rx, 8);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ skb = __skb_dequeue(&dev->rx_pkts);
+ KUNIT_EXPECT_NOT_NULL(test, skb);
+ if (skb)
+ KUNIT_EXPECT_EQ(test, skb->len, 4);
+ kfree_skb(skb);
+}
+
+struct mctp_usblib_test_pkt_span {
+ const char *name;
+ size_t n_pkts;
+ size_t pkts[6];
+ size_t n_xfers;
+ size_t xfers[6];
+};
+
+static void
+mctp_usblib_test_pkt_span_to_desc(const struct mctp_usblib_test_pkt_span *t,
+ char *desc)
+{
+ strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+static void
+mctp_usblib_test_pkt_span_validate(struct kunit *test,
+ const struct mctp_usblib_test_pkt_span *span,
+ size_t *len)
+{
+ size_t pkt_len = 0, xfer_len = 0;
+ unsigned int i;
+
+ for (i = 0; i < span->n_pkts; i++) {
+ KUNIT_ASSERT_GE_MSG(test, span->pkts[i], 8,
+ "pkt[%d] len too small (%zd) for %s",
+ i, span->pkts[i], span->name);
+ pkt_len += span->pkts[i];
+ }
+
+ for (i = 0; i < span->n_xfers; i++)
+ xfer_len += span->xfers[i];
+
+ KUNIT_ASSERT_EQ_MSG(test, pkt_len, xfer_len,
+ "invalid pkt_len (%zd) != xfer_len (%zd) for %s",
+ pkt_len, xfer_len, span->name);
+
+ *len = pkt_len;
+}
+
+static void mctp_usblib_test_rx_pkt_span(struct kunit *test)
+{
+ const struct mctp_usblib_test_pkt_span *pkt_span = test->param_value;
+ size_t len, xfer_len, off, xfer_off;
+ struct mctp_usblib_test_dev *dev;
+ struct mctp_usblib_test_ctx *ctx;
+ struct mctp_usblib_rx *rx;
+ unsigned int i;
+ u8 *pktbuf;
+ void *buf;
+ int rc;
+
+ mctp_usblib_test_pkt_span_validate(test, pkt_span, &len);
+ pktbuf = kunit_kmalloc_array(test, 1, len, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pktbuf);
+
+ /* lay out packets */
+ for (off = 0, i = 0; i < pkt_span->n_pkts; i++) {
+ len = pkt_span->pkts[i];
+ mctp_usblib_test_init_pkt(pktbuf + off, len, len);
+ off += len;
+ }
+
+ ctx = mctp_usblib_test_init(test);
+ dev = ctx->dev;
+
+ rx = mctp_usblib_test_rx_init(test, true);
+
+ /* feed transfers */
+ for (off = 0, xfer_off = 0, i = 0; i < pkt_span->n_xfers;) {
+ xfer_len = pkt_span->xfers[i] - xfer_off;
+ rc = mctp_usblib_rx_prepare(dev->ndev, rx,
+ &buf, &len, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+
+ KUNIT_ASSERT_GE(test, len, ep_maxpacket);
+
+ len = min(len, xfer_len);
+ memcpy(buf, pktbuf + off, len);
+
+ if (len == xfer_len) {
+ /* whole/end xfer, proceed to next */
+ xfer_off = 0;
+ i++;
+ } else {
+ /* partial */
+ xfer_off += len;
+ }
+
+ rc = mctp_usblib_test_rx_complete(dev->ndev, rx, len);
+ KUNIT_ASSERT_EQ(test, rc, 0);
+ off += len;
+ }
+
+ /* check received packets */
+ KUNIT_EXPECT_EQ(test, dev->rx_pkts.qlen, pkt_span->n_pkts);
+ for (i = 0; ; i++) {
+ struct sk_buff *skb = __skb_dequeue(&dev->rx_pkts);
+
+ if (!skb)
+ break;
+
+ if (i < pkt_span->n_pkts)
+ KUNIT_EXPECT_EQ(test, skb->len, pkt_span->pkts[i] - 4);
+
+ kfree_skb(skb);
+ }
+}
+
+static const struct mctp_usblib_test_pkt_span mctp_usblib_test_pkt_spans[] = {
+ /* One packet completely within a transfer */
+ { "1p1x-complete", 1, { 8 }, 1, { 8 } },
+ /* Two small packets combined within one transfer */
+ { "2p1x-combined", 2, { 8, 8 }, 1, { 16 } },
+ /* A packet split over two transfers, at the MCTP payload */
+ { "1p2x-split-payload", 1, { 16 }, 2, { 8, 8 } },
+ /* A packet split over two transfers, at the USB transport header */
+ { "1p2x-split-usbhdr", 1, { 16 }, 2, { 2, 14 } },
+ /* A packet split over two transfers, at the MCTP header */
+ { "1p2x-split-mctphdr", 1, { 16 }, 2, { 6, 10 } },
+ /* Single packet split over 3 transfers, middle entirely continuation */
+ { "1p3x-split", 1, { 12 }, 3, { 4, 4, 4 } },
+ /* Max-sized single transfer */
+ { "1p1x-large", 1, { 8191 }, 1, { 8191 } },
+ /* Two large packets, split at the worst-case for allocation, with a
+ * single byte continuing the span
+ */
+ { "2p2x-large-split", 2, { 8190, 8190 }, 2, { 8191, 8189 } },
+};
+
+KUNIT_ARRAY_PARAM(mctp_usblib_test_rx_pkt_span, mctp_usblib_test_pkt_spans,
+ mctp_usblib_test_pkt_span_to_desc);
+
+static struct kunit_case mctp_usblib_test_cases[] = {
+ KUNIT_CASE(mctp_usblib_test_rx_single),
+ KUNIT_CASE_PARAM(mctp_usblib_test_rx_pkt_span,
+ mctp_usblib_test_rx_pkt_span_gen_params),
+ {}
+};
+
+static struct kunit_suite mctp_usblib_test_suite = {
+ .name = "mctp-usblib",
+ .test_cases = mctp_usblib_test_cases,
+};
+
+kunit_test_suite(mctp_usblib_test_suite);
diff --git a/drivers/net/mctp/mctp-usblib.c b/drivers/net/mctp/mctp-usblib.c
new file mode 100644
index 00000000000000..fc0282152e6efb
--- /dev/null
+++ b/drivers/net/mctp/mctp-usblib.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mctp-usblib.c - MCTP-over-USB (DMTF DSP0283) transport helper library
+ *
+ * DSP0283 is available at:
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
+ *
+ * Copyright (C) 2024-2026 Code Construct Pty Ltd
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/usb/mctp-usb.h>
+#include <net/mctp.h>
+
+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span)
+{
+ memset(rx, 0, sizeof(*rx));
+ rx->span = span;
+ rx->ep_pktlen = ep_pktlen;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_rx_init);
+
+void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx)
+{
+ kfree_skb(rx->skb);
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_rx_fini);
+
+/*
+ * Prepare a transfer buffer for future completion; *bufp and *lenp will
+ * be populated on success.
+ */
+int mctp_usblib_rx_prepare(struct net_device *netdev,
+ struct mctp_usblib_rx *rx,
+ void **bufp, size_t *lenp, gfp_t gfp)
+{
+ struct sk_buff *skb = rx->skb;
+ unsigned int len = 0;
+
+ if (skb && skb->len >= MCTP_USB_1_1_PKTLEN_MAX) {
+ /* something must have gone terribly wrong. clear and restart */
+ mctp_usblib_rx_cancel(rx);
+ skb = NULL;
+ }
+
+ len = rx->span ? ALIGN(MCTP_USB_1_1_PKTLEN_MAX, rx->ep_pktlen)
+ : MCTP_USB_1_0_XFER_SIZE;
+
+ if (!skb) {
+ skb = __netdev_alloc_skb(netdev, len, gfp);
+ if (!skb)
+ return -ENOMEM;
+
+ } else if (skb->cloned || skb_tailroom(skb) < rx->ep_pktlen) {
+ /* We always need to realloc if ->cloned, as we cannot
+ * resubmit the (now-shared) skb buffer for possible DMA.
+ *
+ * Otherwise (if we have an un-cloned SKB): just ensure we
+ * have sufficient space to prevent babble. Since we allocated
+ * for max size in the last prepare (and have not consumed any
+ * of that space for a prior MCTP packet, because !cloned), we
+ * have sufficient data to finish the current MCTP packet.
+ */
+ struct sk_buff *skb2;
+
+ skb2 = skb_copy_expand(skb, 0, len, gfp);
+ if (!skb2)
+ return -ENOMEM;
+ dev_kfree_skb_any(skb);
+ skb = skb2;
+ }
+
+ rx->skb = skb;
+
+ /* Spanning mode allows ZLPs, so we don't require exactly one
+ * transfer packet. If we have extra tailroom, may as well use it,
+ * and we have ensured that the tailroom >= ep_pktlen.
+ */
+ if (rx->span)
+ len = ALIGN_DOWN(skb_tailroom(skb), rx->ep_pktlen);
+
+ *bufp = skb_tail_pointer(skb);
+ *lenp = len;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_rx_prepare);
+
+static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb)
+{
+ struct pcpu_dstats *dstats = this_cpu_ptr(netdev->dstats);
+ struct mctp_skb_cb *cb;
+ unsigned long flags;
+
+ skb_reset_mac_header(skb);
+ skb_pull(skb, sizeof(struct mctp_usb_hdr));
+
+ /* we're called from an URB completion handler, and cannot assume local
+ * irqs are always disabled
+ */
+ flags = u64_stats_update_begin_irqsave(&dstats->syncp);
+ u64_stats_inc(&dstats->rx_packets);
+ u64_stats_add(&dstats->rx_bytes, skb->len);
+ u64_stats_update_end_irqrestore(&dstats->syncp, flags);
+
+ skb->protocol = htons(ETH_P_MCTP);
+ skb_reset_network_header(skb);
+ cb = __mctp_cb(skb);
+ cb->halen = 0;
+ netif_rx(skb);
+}
+
+static void mctp_usblib_rx_stats_single_drop(struct net_device *dev)
+{
+ struct pcpu_dstats *dstats = this_cpu_ptr(dev->dstats);
+ unsigned long flags;
+
+ flags = u64_stats_update_begin_irqsave(&dstats->syncp);
+ u64_stats_inc(&dstats->rx_drops);
+ u64_stats_update_end_irqrestore(&dstats->syncp, flags);
+}
+
+/*
+ * Receive a USB completion of @len bytes of incoming data. We will then split
+ * this into packets and netif_rx() each. Intended to be called in atomic
+ * contexts - ie., URB completion.
+ *
+ * Assumes @netdev uses dstats.
+ */
+int mctp_usblib_rx_complete(struct net_device *netdev,
+ struct mctp_usblib_rx *rx, size_t len)
+{
+ struct sk_buff *skb = rx->skb;
+ int rc = 0;
+
+ __skb_put(skb, len);
+
+ for (;;) {
+ struct mctp_usb_hdr *hdr;
+ struct sk_buff *skb2;
+ /* length of MCTP packet, including USB header */
+ u16 pkt_len;
+
+ /* no header yet, resubmit for the rest of the packet */
+ if (skb->len < sizeof(*hdr)) {
+ if (!rx->span) {
+ netdev_dbg(netdev,
+ "rx: tiny xfer (%d) in non-span mode",
+ skb->len);
+ rc = -ENOMSG;
+ goto err_reset;
+ }
+ break;
+ }
+
+ hdr = (struct mctp_usb_hdr *)skb->data;
+
+ if (be16_to_cpu(hdr->id) != MCTP_USB_DMTF_ID) {
+ /* By resetting here, will start the next IN transfer
+ * at the beginning of the new skb. This will mean
+ * we re-sync when we next see a spanned packet aligned
+ * with the start of a transfer.
+ *
+ * In non-spanning mode, this just means we'll drop
+ * the current transfer only
+ */
+ netdev_dbg(netdev, "rx: invalid id %04x\n",
+ be16_to_cpu(hdr->id));
+ rc = -EPROTO;
+ goto err_reset;
+ }
+
+ pkt_len = be16_to_cpu(hdr->len);
+ /* v1.1, with span enabled, has a 13-bit length */
+ pkt_len &= rx->span ?
+ MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;
+ if (pkt_len < sizeof(*hdr) + sizeof(struct mctp_hdr)) {
+ netdev_dbg(netdev, "rx: invalid len %d\n", pkt_len);
+ rc = -EPROTO;
+ goto err_reset;
+ }
+
+ /* span continues to the next transfer, resubmit */
+ if (pkt_len > skb->len) {
+ if (!rx->span) {
+ netdev_dbg(netdev,
+ "rx: short xfer (%d vs %d) in non-span mode",
+ pkt_len, skb->len);
+ rc = -ENOMSG;
+ goto err_reset;
+ }
+ break;
+ }
+
+ /* we have (exactly) a complete packet, RX it directly */
+ if (pkt_len == skb->len) {
+ mctp_usblib_rx(netdev, skb);
+ rx->skb = NULL;
+ break;
+ }
+
+ /* more packets follow - RX a clone so that we can continue
+ * processing the current SKB, which may be the start of a
+ * span.
+ */
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2) {
+ skb_trim(skb2, pkt_len);
+ mctp_usblib_rx(netdev, skb2);
+ } else {
+ mctp_usblib_rx_stats_single_drop(netdev);
+ }
+ skb_pull(skb, pkt_len);
+ }
+
+ return 0;
+
+err_reset:
+ dev_kfree_skb_any(rx->skb);
+ rx->skb = NULL;
+ return rc;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete);
+
+/*
+ * Cancel a rx context; subsequent prepare/complete calls will not be a
+ * continuation of any data already received.
+ */
+void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx)
+{
+ dev_kfree_skb_any(rx->skb);
+ rx->skb = NULL;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel);
+
+/* transmit context: encapsulates one transfer */
+struct mctp_usblib_tx_ctx {
+ struct mctp_usblib_tx *tx;
+ struct sk_buff_head skbs;
+ unsigned int buf_len, len;
+ enum mctp_usblib_tx_buf_type {
+ TX_SINGLE,
+ TX_FLAT,
+ } buf_type;
+ u8 buf[] ____cacheline_aligned;
+};
+
+void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,
+ const struct mctp_usblib_tx_ops *ops,
+ void *priv, bool span)
+{
+ memset(tx, 0, sizeof(*tx));
+ tx->ops = *ops;
+ tx->priv = priv;
+ tx->span = span;
+ spin_lock_init(&tx->lock);
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_init);
+
+static int mctp_usblib_tx_avail(struct mctp_usblib_tx_ctx *ctx)
+{
+ return ctx->buf_type == TX_SINGLE ? 0 : ctx->buf_len - ctx->len;
+}
+
+static bool mctp_usblib_tx_should_send(struct mctp_usblib_tx_ctx *ctx)
+{
+ /* Use the baseline length (ie, BTU) as an approximate
+ * "reasonably-sized" packet we could expect. If there is
+ * insufficient capacity for that, then send.
+ */
+ const size_t pkt_len = MCTP_USB_BTU + sizeof(struct mctp_usb_hdr);
+
+ return mctp_usblib_tx_avail(ctx) < pkt_len;
+}
+
+/*
+ * Returns zero on success, non-zero on failure - indicating that the new skb
+ * could not be appended. So, errors reported here to the TX path will result
+ * in the TX being transmitted.
+ */
+static int mctp_usblib_tx_append(struct mctp_usblib_tx_ctx *ctx,
+ struct sk_buff *skb)
+{
+ if (ctx->buf_type == TX_SINGLE)
+ return -EINVAL;
+
+ if (mctp_usblib_tx_avail(ctx) < skb->len)
+ return -ENOBUFS;
+
+ __skb_queue_tail(&ctx->skbs, skb);
+
+ ctx->len += skb->len;
+
+ return 0;
+}
+
+static int mctp_usblib_tx_send(struct mctp_usblib_tx_ctx *ctx)
+{
+ void *buf;
+
+ /* If we have a qlen of 1, we only ended up packing a single skb,
+ * despite allocating for multiple. Skip the copy and send directly
+ * from the skb data.
+ */
+ if (ctx->buf_type == TX_SINGLE || ctx->skbs.qlen == 1) {
+ buf = ctx->skbs.next->data;
+
+ } else if (ctx->buf_type == TX_FLAT) {
+ struct sk_buff *skb;
+ size_t pos = 0;
+
+ skb_queue_walk(&ctx->skbs, skb) {
+ skb_copy_bits(skb, 0, ctx->buf + pos, skb->len);
+ pos += skb->len;
+ }
+
+ buf = ctx->buf;
+ } else {
+ return -EINVAL;
+ }
+
+ return ctx->tx->ops.send(ctx, buf, ctx->len);
+}
+
+static void mctp_usblib_tx_ctx_free(struct mctp_usblib_tx_ctx *ctx,
+ enum skb_drop_reason reason)
+{
+ struct sk_buff *skb;
+
+ if (!ctx)
+ return;
+
+ while ((skb = __skb_dequeue(&ctx->skbs)) != NULL)
+ dev_kfree_skb_any_reason(skb, reason);
+ kfree(ctx);
+}
+
+void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx)
+{
+ return tx_ctx->tx->priv;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_ctx_priv);
+
+/* caller must ensure the tx & completion path is quiesced */
+void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx)
+{
+ mctp_usblib_tx_ctx_free(tx->cur_ctx, SKB_DROP_REASON_NOT_SPECIFIED);
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_fini);
+
+/* Max size of a spanned TX. Since we allocate a separate span buffer, limit
+ * the tx-time allocations to 4k. Larger packets will be sent as single
+ * transfers.
+ */
+static const unsigned int TX_SPAN_MAX = 4096 - sizeof(struct mctp_usblib_tx_ctx);
+
+static struct mctp_usblib_tx_ctx *
+mctp_usblib_tx_ctx_create(struct mctp_usblib_tx *tx, struct sk_buff *skb,
+ bool single)
+{
+ enum mctp_usblib_tx_buf_type type;
+ struct mctp_usblib_tx_ctx *ctx;
+ size_t sz = 0;
+
+ if (single || skb->len > TX_SPAN_MAX) {
+ type = TX_SINGLE;
+ } else {
+ type = TX_FLAT;
+ sz = tx->span ? TX_SPAN_MAX : MCTP_USB_1_0_XFER_SIZE;
+ }
+
+ ctx = kzalloc_flex(*ctx, buf, sz, GFP_ATOMIC);
+ if (!ctx)
+ return NULL;
+
+ ctx->tx = tx;
+ ctx->buf_type = type;
+ ctx->buf_len = sz;
+ ctx->len = skb->len;
+ skb_queue_head_init(&ctx->skbs);
+ __skb_queue_tail(&ctx->skbs, skb);
+
+ return ctx;
+}
+
+static void mctp_usblib_tx_stats_update(struct mctp_usblib_tx_ctx *ctx,
+ struct net_device *dev,
+ bool ok)
+{
+ struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats);
+ unsigned long flags;
+
+ flags = u64_stats_update_begin_irqsave(&dstats->syncp);
+ if (ok) {
+ /* Only include the network-layer data in tx stats; we know
+ * that there is a 4-byte header pushed to all skbs in
+ * tx_skb_prepare()
+ */
+ u64 n = ctx->skbs.qlen;
+ s64 len = ctx->len - (n * sizeof(struct mctp_usb_hdr));
+
+ u64_stats_add(&dstats->tx_packets, n);
+ u64_stats_add(&dstats->tx_bytes, len);
+ } else {
+ u64_stats_add(&dstats->tx_drops, ctx->skbs.qlen);
+ }
+ u64_stats_update_end_irqrestore(&dstats->syncp, flags);
+ put_cpu_ptr(dev->dstats);
+}
+
+static void mctp_usblib_tx_stats_single_drop(struct net_device *dev)
+{
+ struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats);
+ unsigned long flags;
+
+ flags = u64_stats_update_begin_irqsave(&dstats->syncp);
+ u64_stats_inc(&dstats->tx_drops);
+ u64_stats_update_end_irqrestore(&dstats->syncp, flags);
+ put_cpu_ptr(dev->dstats);
+}
+
+/*
+ * Completion for the ->send() op. This will update netdev stats and
+ * free the tx context.
+ *
+ * Likely called from (atomic) URB completion context.
+ */
+void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx,
+ struct net_device *dev, bool ok)
+{
+ enum skb_drop_reason reason =
+ ok ? SKB_CONSUMED : SKB_DROP_REASON_NOT_SPECIFIED;
+
+ mctp_usblib_tx_stats_update(tx_ctx, dev, ok);
+ mctp_usblib_tx_ctx_free(tx_ctx, reason);
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete);
+
+/* Prepare a skb for push()
+ *
+ * On error, populates @reason.
+ */
+static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span,
+ enum skb_drop_reason *reason)
+{
+ unsigned long plen, max_len;
+ struct mctp_usb_hdr *hdr;
+ int rc;
+
+ max_len = span ? MCTP_USB_1_1_PKTLEN_MAX : MCTP_USB_1_0_PKTLEN_MAX;
+
+ plen = skb->len;
+ if (plen + sizeof(*hdr) > max_len) {
+ *reason = SKB_DROP_REASON_PKT_TOO_BIG;
+ return -EMSGSIZE;
+ }
+
+ rc = skb_cow_head(skb, sizeof(*hdr));
+ if (rc) {
+ *reason = SKB_DROP_REASON_NOMEM;
+ return rc;
+ }
+
+ hdr = skb_push(skb, sizeof(*hdr));
+ if (!hdr) {
+ *reason = SKB_DROP_REASON_NOMEM;
+ return -ENOMEM;
+ }
+
+ hdr->id = cpu_to_be16(MCTP_USB_DMTF_ID);
+ hdr->len = cpu_to_be16(plen + sizeof(*hdr));
+
+ return 0;
+}
+
+/*
+ * Push a new skb to the transfer. May result in zero or more calls to
+ * ops->send().
+ *
+ * Takes ownership of @skb, including on error.
+ */
+int mctp_usblib_tx_push(struct net_device *dev,
+ struct mctp_usblib_tx *tx,
+ struct sk_buff *skb, bool more)
+{
+ struct mctp_usblib_tx_ctx *ctx, *send_ctx = NULL;
+ enum skb_drop_reason reason;
+ const int max_tries = 3;
+ unsigned long flags;
+ int try = 1, rc;
+
+ rc = mctp_usblib_tx_skb_prepare(skb, tx->span, &reason);
+ if (rc) {
+ mctp_usblib_tx_stats_single_drop(dev);
+ kfree_skb_reason(skb, reason);
+ /* we may still need to proceed, in case an existing ctx
+ * is now sendable (ie.: !more).
+ */
+ skb = NULL;
+ }
+
+ reason = SKB_DROP_REASON_NOT_SPECIFIED;
+retry:
+ /* Try and queue to the current context. We exit this critical section
+ * with a few bits of state:
+ * - send_ctx: indicating a prior context that needs to be sent
+ * - skb: indicating that a skb still needs to be queued/sent
+ */
+ spin_lock_irqsave(&tx->lock, flags);
+ ctx = tx->cur_ctx;
+ if (ctx) {
+ if (skb) {
+ rc = mctp_usblib_tx_append(ctx, skb);
+ if (rc) {
+ /* can't append to the pending tx - detach for
+ * sending, and we'll create a new tx below.
+ */
+ swap(tx->cur_ctx, send_ctx);
+ } else {
+ /* we have queued */
+ skb = NULL;
+ if (!more || mctp_usblib_tx_should_send(ctx))
+ swap(tx->cur_ctx, send_ctx);
+ }
+ } else if (!more) {
+ swap(tx->cur_ctx, send_ctx);
+ }
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (send_ctx) {
+ rc = mctp_usblib_tx_send(send_ctx);
+ if (rc) {
+ mctp_usblib_tx_stats_update(send_ctx, dev, false);
+ mctp_usblib_tx_ctx_free(send_ctx, reason);
+ }
+ send_ctx = NULL;
+ }
+
+ /* we have either queued, or the prepare failed; nothing more to do */
+ if (!skb)
+ return 0;
+
+ ctx = mctp_usblib_tx_ctx_create(tx, skb, !more);
+ if (!ctx) {
+ netdev_dbg(dev, "TX context create failed\n");
+ mctp_usblib_tx_stats_single_drop(dev);
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+
+ /* if we're ready to send now, no need to enqueue */
+ if (!more || mctp_usblib_tx_should_send(ctx)) {
+ rc = mctp_usblib_tx_send(ctx);
+ if (rc) {
+ mctp_usblib_tx_stats_update(ctx, dev, false);
+ mctp_usblib_tx_ctx_free(ctx, reason);
+ }
+ return 0;
+ }
+
+ spin_lock_irqsave(&tx->lock, flags);
+ if (!tx->cur_ctx) {
+ tx->cur_ctx = ctx;
+ ctx = NULL;
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ /* we may have lost the race with a concurrent tx; shouldn't happen, as
+ * ndo_start_xmit should be serialised over one queue, but try again
+ * from the top, as we may be able to queue the skb to that context.
+ */
+ if (ctx) {
+ /* unlink the new (sole) skb, we don't want it freed with ctx */
+ __skb_queue_head_init(&ctx->skbs);
+ mctp_usblib_tx_ctx_free(ctx, reason);
+ if (++try > max_tries) {
+ kfree_skb(skb);
+ mctp_usblib_tx_stats_single_drop(dev);
+ return -EBUSY;
+ }
+ goto retry;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_push);
+
+/* Cancel a tx: any un-sent context is released. */
+void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev,
+ enum skb_drop_reason reason)
+{
+ struct mctp_usblib_tx_ctx *ctx = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ swap(tx->cur_ctx, ctx);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (!ctx)
+ return;
+
+ mctp_usblib_tx_stats_update(ctx, dev, false);
+ mctp_usblib_tx_ctx_free(ctx, reason);
+}
+EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");
+MODULE_DESCRIPTION("MCTP USB transport library");
+
+#if IS_ENABLED(CONFIG_MCTP_TRANSPORT_USBLIB_TEST)
+#include "mctp-usblib-test.c"
+#endif
diff --git a/include/linux/usb/mctp-usb.h b/include/linux/usb/mctp-usb.h
index a2f6f1e04efbd0..35cc2cc15f9a04 100644
--- a/include/linux/usb/mctp-usb.h
+++ b/include/linux/usb/mctp-usb.h
@@ -2,7 +2,7 @@
/*
* mctp-usb.h - MCTP USB transport binding: common definitions,
* based on DMTF0283 specification:
- * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.0.1.pdf
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0283_1.1.0.pdf
*
* These are protocol-level definitions, that may be shared between host
* and gadget drivers.
@@ -13,18 +13,96 @@
#ifndef __LINUX_USB_MCTP_USB_H
#define __LINUX_USB_MCTP_USB_H
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
#include <linux/types.h>
+/*
+ * MCTP-over-USB transport header. DSP0283 v1.0 has an 8-bit length field
+ * (preceded by 8 reserved bits), v1.1 has a 13-bit length field (preceded by
+ * 3 reserved bits). We use a be16 for our length to handle the larger v1.1
+ * representation, and mask as appropriate.
+ */
struct mctp_usb_hdr {
__be16 id;
- u8 rsvd;
- u8 len;
+ __be16 len;
} __packed;
-#define MCTP_USB_XFER_SIZE 512
+/* max transfer size for DSP0283 v1.0 */
+#define MCTP_USB_1_0_XFER_SIZE 512
#define MCTP_USB_BTU 68
#define MCTP_USB_MTU_MIN MCTP_USB_BTU
-#define MCTP_USB_MTU_MAX (U8_MAX - sizeof(struct mctp_usb_hdr))
+#define MCTP_USB_1_0_PKTLEN_MAX U8_MAX
+#define MCTP_USB_1_0_MTU_MAX (MCTP_USB_1_0_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))
+#define MCTP_USB_1_1_PKTLEN_MAX GENMASK(12, 0)
+#define MCTP_USB_1_1_MTU_MAX (MCTP_USB_1_1_PKTLEN_MAX - sizeof(struct mctp_usb_hdr))
#define MCTP_USB_DMTF_ID 0x1ab4
+/* mctp-usblib */
+
+/*
+ * RX handle: drivers will typically create one on init, which persists for
+ * the life of the driver. The same handle is used for progressive
+ * prepare -> complete operations (for each incoming USB transfer), which
+ * result in netif_rx()-ing the MCTP packets received
+ */
+struct mctp_usblib_rx {
+ struct sk_buff *skb;
+ u16 ep_pktlen;
+ bool span;
+};
+
+void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span);
+void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx);
+
+int mctp_usblib_rx_prepare(struct net_device *netdev,
+ struct mctp_usblib_rx *rx,
+ void **bufp, size_t *lenp, gfp_t gfp);
+
+int mctp_usblib_rx_complete(struct net_device *netdev,
+ struct mctp_usblib_rx *rx, size_t len);
+
+void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx);
+
+/*
+ * TX handle: created by mctp_usblib_tx_push() during the tx path, and
+ * may persist across multiple packet transmits.
+ */
+struct mctp_usblib_tx_ctx;
+
+struct mctp_usblib_tx_ops {
+ /* Start a USB TX for @data. On returning success, the implementation
+ * must arrange for mctp_usblib_tx_send_complete() to be called at some
+ * later point (eg., on urb completion).
+ */
+ int (*send)(struct mctp_usblib_tx_ctx *tx_ctx, void *data, size_t len);
+};
+
+struct mctp_usblib_tx {
+ struct mctp_usblib_tx_ops ops;
+ void *priv;
+ bool span;
+ /* protects access to cur_ctx */
+ spinlock_t lock;
+ /* context to which we are adding packets, cleared on send */
+ struct mctp_usblib_tx_ctx *cur_ctx;
+};
+
+void mctp_usblib_tx_init(struct mctp_usblib_tx *tx,
+ const struct mctp_usblib_tx_ops *ops, void *priv,
+ bool span);
+void mctp_usblib_tx_fini(struct mctp_usblib_tx *tx);
+
+void *mctp_usblib_tx_ctx_priv(struct mctp_usblib_tx_ctx *tx_ctx);
+
+int mctp_usblib_tx_push(struct net_device *dev,
+ struct mctp_usblib_tx *tx,
+ struct sk_buff *skb, bool more);
+
+void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx,
+ struct net_device *dev, bool ok);
+
+void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev,
+ enum skb_drop_reason reason);
+
#endif /* __LINUX_USB_MCTP_USB_H */
|
| 3/2 | 2026/07/08 23:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22567 output: 37 thoughts: 660 Thoughts: **Analyzing the MCTP-over-USB Patch for Fuzzing Potential** Okay, so I've got this patch in front of me that looks like it's making some significant changes to the MCTP USB transport driver. Specifically, it's updating `drivers/net/mctp/mctp-usb.c` and introducing a new common library for MCTP-over-USB, which is `drivers/net/mctp/mctp-usblib.c`. They've also added some KUnit tests in `drivers/net/mctp/mctp-usblib-test.c` – always a good sign. And of course, there are new Kconfig options, `MCTP_TRANSPORT_USBLIB` and `MCTP_TRANSPORT_USBLIB_TEST`. My first thought is, "Can this thing even *be* fuzzed effectively?" Let's see. This `mctp-usb.c` driver is a USB device driver, right? And it binds to USB interfaces with `USB_CLASS_MCTP`. Okay, good. In a virtualized environment like QEMU, we can emulate USB devices using `usb-dummy` or `usb-gadget`. And the really important bit... syzkaller, which is what I'd likely use, *does* support fuzzing USB drivers via `raw-gadget`. So, yes, it seems this is definitely reachable and worth the effort. Excellent. Now, let's dig into the actual code changes. It looks like the patch is refactoring `mctp-usb.c` to leverage the new functions in `mctp-usblib.c`. This is where the juicy bits are. The new functions are: `mctp_usblib_rx_init`, `mctp_usblib_rx_fini`, `mctp_usblib_rx_prepare`, `mctp_usblib_rx_complete`, `mctp_usblib_rx_cancel`, `mctp_usblib_tx_init`, `mctp_usblib_tx_fini`, `mctp_usblib_tx_ctx_priv`, `mctp_usblib_tx_send_complete`, `mctp_usblib_tx_push`, and `mctp_usblib_tx_cancel`. These look like core functions for handling the RX/TX side of the MCTP-over-USB communication. This is definitely a good target for fuzzing. It looks like the patch also has updated some existing `mctp-usb.c` functions to align with the new library: `mctp_usb_out_complete`, `mctp_usb_tx_send`, `mctp_usb_start_xmit`, `mctp_usb_rx_queue`, `mctp_usb_in_complete`, `mctp_usb_rx_retry_work`, `mctp_usb_open`, `mctp_usb_stop`, `mctp_usb_netdev_setup`, `mctp_usb_probe`, and `mctp_usb_disconnect`. They are changing the interaction with the USB stack, which means this could expose new attack surfaces. Regarding configuration, to properly enable this for fuzzing, I'll need `MCTP_TRANSPORT_USB` and, since it looks like `MCTP_TRANSPORT_USBLIB` is selected by `MCTP_TRANSPORT_USB`, I can expect them to be enabled together. Of course, `USB` itself will have to be enabled. I should quickly check `drivers/net/mctp/Kconfig` to confirm any other dependencies or build requirements. |
| 4/2 | 2026/07/08 23:29 | tool | grepper |
0mArgs:{
"Expression": "config MCTP_TRANSPORT_USB",
"PathPrefix": "drivers/net/mctp/Kconfig"
}Results: {
"Output": "drivers/net/mctp/Kconfig=41=config MCTP_TRANSPORT_I3C\n--\ndrivers/net/mctp/Kconfig-49-\ndrivers/net/mctp/Kconfig:50:config MCTP_TRANSPORT_USBLIB\ndrivers/net/mctp/Kconfig-51-\ttristate \"MCTP over USB common library\"\n--\ndrivers/net/mctp/Kconfig-59-\ndrivers/net/mctp/Kconfig:60:config MCTP_TRANSPORT_USBLIB_TEST\ndrivers/net/mctp/Kconfig-61- bool \"MCTP usblib tests\" if !KUNIT_ALL_TESTS\n--\ndrivers/net/mctp/Kconfig-64-\ndrivers/net/mctp/Kconfig:65:config MCTP_TRANSPORT_USB\ndrivers/net/mctp/Kconfig-66-\ttristate \"MCTP USB transport\"\n"
} |
| 5/2 | 2026/07/08 23:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24347 output: 32 thoughts: 14 Thoughts: Here's my thought process, summarized as you requested: **Assessing the MCTP_TRANSPORT_USB Dependencies** Okay, so I need to get a handle on the `MCTP_TRANSPORT_USB` dependency situation. Let's start by mapping out exactly what it relies upon. I need to understand this transport layer inside and out, especially any external libraries or kernel modules it might be tied to. I'm thinking about driver interfaces, hardware abstraction layers (HALs), potentially specific USB host controller dependencies, maybe even firmware interactions, if applicable. A comprehensive dependency tree is crucial here. I need to consider not just direct dependencies, but also transitive ones. Any ripple effects from updating or modifying this component down the line are a major concern. Any known issues or conflicts within the existing system also need to be noted. I want to build a truly robust dependency model. |
| 6/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "depends on",
"PathPrefix": "drivers/net/mctp/Kconfig"
}Results: {
"Output": "drivers/net/mctp/Kconfig=6=config MCTP_SERIAL\ndrivers/net/mctp/Kconfig-7-\ttristate \"MCTP serial transport\"\ndrivers/net/mctp/Kconfig:8:\tdepends on TTY\ndrivers/net/mctp/Kconfig-9-\tselect CRC_CCITT\n--\ndrivers/net/mctp/Kconfig=24=config MCTP_SERIAL_TEST\ndrivers/net/mctp/Kconfig-25- bool \"MCTP serial tests\" if !KUNIT_ALL_TESTS\ndrivers/net/mctp/Kconfig:26: depends on MCTP_SERIAL=y \u0026\u0026 KUNIT=y\ndrivers/net/mctp/Kconfig-27- default KUNIT_ALL_TESTS\n--\ndrivers/net/mctp/Kconfig=29=config MCTP_TRANSPORT_I2C\n--\ndrivers/net/mctp/Kconfig-31-\t# i2c-mux is optional, but we must build as a module if i2c-mux is a module\ndrivers/net/mctp/Kconfig:32:\tdepends on I2C_MUX || !I2C_MUX\ndrivers/net/mctp/Kconfig:33:\tdepends on I2C\ndrivers/net/mctp/Kconfig:34:\tdepends on I2C_SLAVE\ndrivers/net/mctp/Kconfig-35-\tselect MCTP_FLOWS\n--\ndrivers/net/mctp/Kconfig=41=config MCTP_TRANSPORT_I3C\ndrivers/net/mctp/Kconfig-42-\ttristate \"MCTP I3C transport\"\ndrivers/net/mctp/Kconfig:43:\tdepends on I3C\ndrivers/net/mctp/Kconfig-44-\thelp\n--\ndrivers/net/mctp/Kconfig=50=config MCTP_TRANSPORT_USBLIB\ndrivers/net/mctp/Kconfig-51-\ttristate \"MCTP over USB common library\"\ndrivers/net/mctp/Kconfig:52:\tdepends on USB\ndrivers/net/mctp/Kconfig-53-\thelp\n--\ndrivers/net/mctp/Kconfig=60=config MCTP_TRANSPORT_USBLIB_TEST\ndrivers/net/mctp/Kconfig-61- bool \"MCTP usblib tests\" if !KUNIT_ALL_TESTS\ndrivers/net/mctp/Kconfig:62: depends on MCTP_TRANSPORT_USBLIB=y \u0026\u0026 KUNIT=y\ndrivers/net/mctp/Kconfig-63- default KUNIT_ALL_TESTS\n--\ndrivers/net/mctp/Kconfig=65=config MCTP_TRANSPORT_USB\ndrivers/net/mctp/Kconfig-66-\ttristate \"MCTP USB transport\"\ndrivers/net/mctp/Kconfig:67:\tdepends on USB\ndrivers/net/mctp/Kconfig-68-\tselect MCTP_TRANSPORT_USBLIB\n"
} |
| 7/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25341 output: 31 thoughts: 40 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Dependency Deep Dive** Alright, let's break this down. `MCTP_TRANSPORT_USB` relies on `USB`, that's straightforward. Now, the bigger question is, what's the dependency on `MCTP` itself? I need to understand that. Let's dig into `net/mctp/Kconfig`. I'll open up that Kconfig file and see what kind of dependencies are defined for the whole `MCTP` stack and how `MCTP_TRANSPORT_USB` is tied to it. Gotta ensure I'm getting the complete picture. The goal is a clear view of the configuration options and their relationships within the MCTP framework. |
| 8/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "config MCTP",
"PathPrefix": "net/mctp/Kconfig"
}Results: {
"Output": "net/mctp/Kconfig-1-\nnet/mctp/Kconfig:2:menuconfig MCTP\nnet/mctp/Kconfig-3-\tdepends on NET\n--\nnet/mctp/Kconfig-14-\nnet/mctp/Kconfig:15:config MCTP_TEST\nnet/mctp/Kconfig-16- bool \"MCTP core tests\" if !KUNIT_ALL_TESTS\n--\nnet/mctp/Kconfig-20-\nnet/mctp/Kconfig:21:config MCTP_FLOWS\nnet/mctp/Kconfig-22-\tbool\n"
} |
| 9/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25749 output: 46 thoughts: 245 Thoughts: **Fuzzing Focus: MCTP over USB** Alright, let's get down to business. First things first: ensure `MCTP` and `MCTP_TRANSPORT_USB` are enabled. That's the foundation we need. Now, the key is to pinpoint the most fruitful functions for fuzzing. I'll need to identify where to target potential vulnerabilities. Let's begin by examining `mctp-usblib.c`. The obvious candidates for close attention here are: `mctp_usblib_rx_prepare`, `mctp_usblib_rx_complete`, `mctp_usblib_tx_push`, and `mctp_usblib_tx_send_complete`. These seem like the core entry and exit points for data transfer and processing within the usblib component. Next, we'll shift our focus to `mctp-usb.c`. Here, the following functions warrant a deep dive: `mctp_usb_in_complete`, `mctp_usb_out_complete`, `mctp_usb_tx_send`, `mctp_usb_start_xmit`, and `mctp_usb_rx_queue`. These all handle data flow and interaction with the underlying USB hardware. They are of high interest. Wait... I need to double-check something. Let me see if `mctp_usblib_rx` is a function. Ah, no, it's a static function inside `mctp-usblib.c`, which makes it a slightly different target. It's likely important in the overall flow, but we can't directly target it. Let's still make sure to understand its role. I need to take a closer look at the actual implementation of `mctp_usblib_rx` now, specifically in `mctp-usblib.c`. |
| 10/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usblib_rx",
"PathPrefix": "drivers/net/mctp/mctp-usblib.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usblib.c-16-\ndrivers/net/mctp/mctp-usblib.c:17:void mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span)\ndrivers/net/mctp/mctp-usblib.c-18-{\n--\ndrivers/net/mctp/mctp-usblib.c-22-}\ndrivers/net/mctp/mctp-usblib.c:23:EXPORT_SYMBOL_GPL(mctp_usblib_rx_init);\ndrivers/net/mctp/mctp-usblib.c-24-\ndrivers/net/mctp/mctp-usblib.c:25:void mctp_usblib_rx_fini(struct mctp_usblib_rx *rx)\ndrivers/net/mctp/mctp-usblib.c-26-{\n--\ndrivers/net/mctp/mctp-usblib.c-28-}\ndrivers/net/mctp/mctp-usblib.c:29:EXPORT_SYMBOL_GPL(mctp_usblib_rx_fini);\ndrivers/net/mctp/mctp-usblib.c-30-\n--\ndrivers/net/mctp/mctp-usblib.c-34- */\ndrivers/net/mctp/mctp-usblib.c:35:int mctp_usblib_rx_prepare(struct net_device *netdev,\ndrivers/net/mctp/mctp-usblib.c:36:\t\t\t struct mctp_usblib_rx *rx,\ndrivers/net/mctp/mctp-usblib.c-37-\t\t\t void **bufp, size_t *lenp, gfp_t gfp)\n--\ndrivers/net/mctp/mctp-usblib.c-43-\t\t/* something must have gone terribly wrong. clear and restart */\ndrivers/net/mctp/mctp-usblib.c:44:\t\tmctp_usblib_rx_cancel(rx);\ndrivers/net/mctp/mctp-usblib.c-45-\t\tskb = NULL;\n--\ndrivers/net/mctp/mctp-usblib.c-88-}\ndrivers/net/mctp/mctp-usblib.c:89:EXPORT_SYMBOL_GPL(mctp_usblib_rx_prepare);\ndrivers/net/mctp/mctp-usblib.c-90-\ndrivers/net/mctp/mctp-usblib.c:91:static void mctp_usblib_rx(struct net_device *netdev, struct sk_buff *skb)\ndrivers/net/mctp/mctp-usblib.c-92-{\n--\ndrivers/net/mctp/mctp-usblib.c-114-\ndrivers/net/mctp/mctp-usblib.c:115:static void mctp_usblib_rx_stats_single_drop(struct net_device *dev)\ndrivers/net/mctp/mctp-usblib.c-116-{\n--\ndrivers/net/mctp/mctp-usblib.c-131- */\ndrivers/net/mctp/mctp-usblib.c:132:int mctp_usblib_rx_complete(struct net_device *netdev,\ndrivers/net/mctp/mctp-usblib.c:133:\t\t\t struct mctp_usblib_rx *rx, size_t len)\ndrivers/net/mctp/mctp-usblib.c-134-{\n--\ndrivers/net/mctp/mctp-usblib.c-198-\t\tif (pkt_len == skb-\u003elen) {\ndrivers/net/mctp/mctp-usblib.c:199:\t\t\tmctp_usblib_rx(netdev, skb);\ndrivers/net/mctp/mctp-usblib.c-200-\t\t\trx-\u003eskb = NULL;\n--\ndrivers/net/mctp/mctp-usblib.c-210-\t\t\tskb_trim(skb2, pkt_len);\ndrivers/net/mctp/mctp-usblib.c:211:\t\t\tmctp_usblib_rx(netdev, skb2);\ndrivers/net/mctp/mctp-usblib.c-212-\t\t} else {\ndrivers/net/mctp/mctp-usblib.c:213:\t\t\tmctp_usblib_rx_stats_single_drop(netdev);\ndrivers/net/mctp/mctp-usblib.c-214-\t\t}\n--\ndrivers/net/mctp/mctp-usblib.c-224-}\ndrivers/net/mctp/mctp-usblib.c:225:EXPORT_SYMBOL_GPL(mctp_usblib_rx_complete);\ndrivers/net/mctp/mctp-usblib.c-226-\n--\ndrivers/net/mctp/mctp-usblib.c-230- */\ndrivers/net/mctp/mctp-usblib.c:231:void mctp_usblib_rx_cancel(struct mctp_usblib_rx *rx)\ndrivers/net/mctp/mctp-usblib.c-232-{\n--\ndrivers/net/mctp/mctp-usblib.c-235-}\ndrivers/net/mctp/mctp-usblib.c:236:EXPORT_SYMBOL_GPL(mctp_usblib_rx_cancel);\ndrivers/net/mctp/mctp-usblib.c-237-\n"
} |
| 11/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28019 output: 44 thoughts: 152 Thoughts: **Focusing on USB Functionality** Okay, let's zero in on the critical functions for managing the USB interface in this MCTP implementation. My primary concern right now centers around these functions, which are the linchpins of the data flow: `mctp_usblib_rx_prepare`, `mctp_usblib_rx_complete`, `mctp_usblib_tx_push`, `mctp_usblib_tx_send_complete`, `mctp_usb_in_complete`, `mctp_usb_out_complete`, `mctp_usb_tx_send`, `mctp_usb_start_xmit`, and `mctp_usb_rx_queue`. I need to ensure a comprehensive understanding of how data is received, transmitted, and managed throughout the USB communication lifecycle. A thorough examination of these elements will be key to understanding the system. I will start by digging into `mctp_usb_in_complete` within the `mctp-usb.c` file. This function is a good starting point to analyze the RX data processing. |
| 12/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usb_in_complete",
"PathPrefix": "drivers/net/mctp/mctp-usb.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usb.c=126=static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,\n--\ndrivers/net/mctp/mctp-usb.c-136-\ndrivers/net/mctp/mctp-usb.c:137:static void mctp_usb_in_complete(struct urb *urb);\ndrivers/net/mctp/mctp-usb.c-138-\n--\ndrivers/net/mctp/mctp-usb.c=145=static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)\n--\ndrivers/net/mctp/mctp-usb.c-158-\t\t\t usb_rcvbulkpipe(mctp_usb-\u003eusbdev, mctp_usb-\u003eep_in),\ndrivers/net/mctp/mctp-usb.c:159:\t\t\t buf, len, mctp_usb_in_complete, mctp_usb);\ndrivers/net/mctp/mctp-usb.c-160-\n--\ndrivers/net/mctp/mctp-usb.c=179=static const unsigned int rx_err_max = 10;\ndrivers/net/mctp/mctp-usb.c-180-\ndrivers/net/mctp/mctp-usb.c:181:static void mctp_usb_in_complete(struct urb *urb)\ndrivers/net/mctp/mctp-usb.c-182-{\n"
} |
| 13/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28888 output: 44 thoughts: 25 Thoughts: Here's a summary of my thought process: **Diving into the mctp_usb_out_complete Function** Okay, so I need to understand what's happening with the output completion handling in the MCTP-over-USB driver. My immediate focus is on the `mctp_usb_out_complete` function, which resides in `mctp-usb.c`. I need to refresh my memory on the specifics of this function. Let's start by digging into that code and seeing what it's doing. I'm going to quickly review its role in the data transfer process from the system to the USB device, particularly when a transfer is considered complete. I'm thinking about things like: what happens to the data, is there any error handling, and how does it signal the completion to the higher layers? |
| 14/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usb_out_complete",
"PathPrefix": "drivers/net/mctp/mctp-usb.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usb.c=64=static const unsigned int TX_QMEM_MAX = 16384;\ndrivers/net/mctp/mctp-usb.c-65-\ndrivers/net/mctp/mctp-usb.c:66:static void mctp_usb_out_complete(struct urb *urb)\ndrivers/net/mctp/mctp-usb.c-67-{\n--\ndrivers/net/mctp/mctp-usb.c=85=static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,\n--\ndrivers/net/mctp/mctp-usb.c-98-\t\t\t usb_sndbulkpipe(mctp_usb-\u003eusbdev, mctp_usb-\u003eep_out),\ndrivers/net/mctp/mctp-usb.c:99:\t\t\t data, len, mctp_usb_out_complete, tx_ctx);\ndrivers/net/mctp/mctp-usb.c-100-\n"
} |
| 15/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29423 output: 44 thoughts: 25 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into the MCTP-USB TX Send Function** Alright, let's drill down into `mctp_usb_tx_send` within `mctp-usb.c`. This is where the rubber meets the road, the function that actually pushes our MCTP data out over USB. I want to see how it handles the intricacies of the USB transport layer, especially error checking and buffering. I'm keen to understand the nuances of the data transfer, particularly what happens if there's contention on the USB bus or if an endpoint is busy. Are they using DMA effectively? Are there any potential bottlenecks in this send function that might impact overall MCTP performance? Any lock contention issues I should know? Are they using any tricks with the USB transfer descriptors to optimize the process, maybe handling the packetization of MCTP data? Time to get the debugger out and see what's really happening under the hood. |
| 16/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usb_tx_send",
"PathPrefix": "drivers/net/mctp/mctp-usb.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usb.c=66=static void mctp_usb_out_complete(struct urb *urb)\n--\ndrivers/net/mctp/mctp-usb.c-84-\ndrivers/net/mctp/mctp-usb.c:85:static int mctp_usb_tx_send(struct mctp_usblib_tx_ctx *tx_ctx,\ndrivers/net/mctp/mctp-usb.c-86-\t\t\t void *data, size_t len)\n--\ndrivers/net/mctp/mctp-usb.c=122=static const struct mctp_usblib_tx_ops tx_ops = {\ndrivers/net/mctp/mctp-usb.c:123:\t.send = mctp_usb_tx_send,\ndrivers/net/mctp/mctp-usb.c-124-};\n"
} |
| 17/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29950 output: 45 thoughts: 26 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into `mctp_usb_start_xmit`** Alright, let's take a look at `mctp_usb_start_xmit`, specifically in `mctp-usb.c`. I need to refresh my memory on the data flow and how the USB transport initiates transmissions. I want to see how the MCTP messages are marshaled, where the DMA is set up (if applicable), and how the driver handles potential bus contention or errors. I'm primarily interested in the synchronization mechanisms employed to ensure atomic operations, because that's always a point of subtle bugs in multi-threaded contexts. Let's see how the packet descriptor is used, and how the buffer pool is handled. I need to quickly ascertain the interrupt handling setup too; I'll need to remember how the TX completion interrupt is handled and if we're using polling. I'll be looking for potential race conditions or locking issues around shared resources, especially around the ring buffer if it's there. My goal is to quickly verify that this part of the driver is robust and doesn't introduce any new vulnerabilities to the existing MCTP implementation. I'm especially interested in how it handles buffer management to avoid memory corruption issues. Let's dig in. |
| 18/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usb_start_xmit",
"PathPrefix": "drivers/net/mctp/mctp-usb.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usb.c=122=static const struct mctp_usblib_tx_ops tx_ops = {\n--\ndrivers/net/mctp/mctp-usb.c-125-\ndrivers/net/mctp/mctp-usb.c:126:static netdev_tx_t mctp_usb_start_xmit(struct sk_buff *skb,\ndrivers/net/mctp/mctp-usb.c-127-\t\t\t\t struct net_device *dev)\n--\ndrivers/net/mctp/mctp-usb.c=304=static const struct net_device_ops mctp_usb_netdev_ops = {\ndrivers/net/mctp/mctp-usb.c:305:\t.ndo_start_xmit = mctp_usb_start_xmit,\ndrivers/net/mctp/mctp-usb.c-306-\t.ndo_open = mctp_usb_open,\n"
} |
| 19/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30574 output: 44 thoughts: 25 Thoughts: Here's a summary of my thought process, as requested: **Diving into `mctp_usb_rx_queue`** Okay, time to get my hands dirty. I'm going to take a closer look at the `mctp_usb_rx_queue` function within the `mctp-usb.c` file. This is where the incoming USB data reception is handled, so it's a critical piece of the puzzle. I need to understand how this queue operates, how it's managed, and how it interacts with other components in the system. I'll need to pay close attention to potential race conditions, buffer management, and error handling within this part of the code. This function is likely responsible for pulling incoming packets off the USB interface, validating them, and then perhaps enqueuing them for further processing. I expect to see data structures related to buffering, interrupt handling, and perhaps some form of locking or synchronization to ensure data integrity. Let's see what the implementation reveals. |
| 20/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usb_rx_queue",
"PathPrefix": "drivers/net/mctp/mctp-usb.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usb.c=143=static const unsigned long RX_RETRY_DELAY = HZ / 4;\ndrivers/net/mctp/mctp-usb.c-144-\ndrivers/net/mctp/mctp-usb.c:145:static int mctp_usb_rx_queue(struct mctp_usb *mctp_usb, gfp_t gfp)\ndrivers/net/mctp/mctp-usb.c-146-{\n--\ndrivers/net/mctp/mctp-usb.c=181=static void mctp_usb_in_complete(struct urb *urb)\n--\ndrivers/net/mctp/mctp-usb.c-241-\ndrivers/net/mctp/mctp-usb.c:242:\tmctp_usb_rx_queue(mctp_usb, GFP_ATOMIC);\ndrivers/net/mctp/mctp-usb.c-243-}\n--\ndrivers/net/mctp/mctp-usb.c=245=static void mctp_usb_rx_retry_work(struct work_struct *work)\n--\ndrivers/net/mctp/mctp-usb.c-263-\ndrivers/net/mctp/mctp-usb.c:264:\tmctp_usb_rx_queue(mctp_usb, GFP_KERNEL);\ndrivers/net/mctp/mctp-usb.c-265-}\n--\ndrivers/net/mctp/mctp-usb.c=267=static int mctp_usb_open(struct net_device *dev)\n--\ndrivers/net/mctp/mctp-usb.c-276-\ndrivers/net/mctp/mctp-usb.c:277:\treturn mctp_usb_rx_queue(mctp_usb, GFP_KERNEL);\ndrivers/net/mctp/mctp-usb.c-278-}\n"
} |
| 21/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31386 output: 48 thoughts: 29 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into `mctp_usblib_tx_push`** Okay, so I need to understand what's happening with the transmit side of the USB library implementation. The first place to check, naturally, is `mctp_usblib_tx_push` within `mctp-usblib.c`. This function is likely the core of pushing data onto the USB transmit queue. I'll need to see how it handles things like buffer allocation, interrupt protection (probably mutexes or spinlocks), any potential flow control mechanisms, and of course, how it interacts with the USB controller hardware registers or driver functions to actually initiate the transmission. I'm especially interested in how it deals with partial packets or if it has any smart buffering schemes for different message sizes. I should also check if it returns any error codes that would tell me how the function deals with a full queue. Let's see how they handled the DMA configuration too. Got to be sure they're not blocking unnecessarily. |
| 22/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usblib_tx_push",
"PathPrefix": "drivers/net/mctp/mctp-usblib.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usblib.c=445=static int mctp_usblib_tx_skb_prepare(struct sk_buff *skb, bool span,\n--\ndrivers/net/mctp/mctp-usblib.c-483- */\ndrivers/net/mctp/mctp-usblib.c:484:int mctp_usblib_tx_push(struct net_device *dev,\ndrivers/net/mctp/mctp-usblib.c-485-\t\t\tstruct mctp_usblib_tx *tx,\n--\ndrivers/net/mctp/mctp-usblib.c-588-}\ndrivers/net/mctp/mctp-usblib.c:589:EXPORT_SYMBOL_GPL(mctp_usblib_tx_push);\ndrivers/net/mctp/mctp-usblib.c-590-\n"
} |
| 23/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31953 output: 50 thoughts: 31 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the mctp-usblib.c Transaction Completion** Okay, so I need to understand what's happening with the completion of a transmission. Time to zero in on `mctp_usblib_tx_send_complete` within `mctp-usblib.c`. This function is critical for proper transaction handling and error management at the USB layer of the MCTP implementation. I need to refresh my understanding of how this completion callback ties into the overall state machine. I'll need to remember to look at the interactions with the USB driver as well as the MCTP core. Specifically, I need to understand what variables are being set or cleared, any interrupts being triggered, and error codes being handled on success and failure. My immediate focus is on the data structures it manipulates, and how this relates to buffer management. I'll probably start by cross-referencing this function with the relevant interrupt handlers and error paths. |
| 24/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usblib_tx_send_complete",
"PathPrefix": "drivers/net/mctp/mctp-usblib.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usblib.c=413=static void mctp_usblib_tx_stats_single_drop(struct net_device *dev)\n--\ndrivers/net/mctp/mctp-usblib.c-429- */\ndrivers/net/mctp/mctp-usblib.c:430:void mctp_usblib_tx_send_complete(struct mctp_usblib_tx_ctx *tx_ctx,\ndrivers/net/mctp/mctp-usblib.c-431-\t\t\t\t struct net_device *dev, bool ok)\n--\ndrivers/net/mctp/mctp-usblib.c-438-}\ndrivers/net/mctp/mctp-usblib.c:439:EXPORT_SYMBOL_GPL(mctp_usblib_tx_send_complete);\ndrivers/net/mctp/mctp-usblib.c-440-\n"
} |
| 25/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 32521 output: 48 thoughts: 29 Thoughts: Here's my take on that code snippet: **Diving into the MCTP USB Library** Okay, so I need to understand how the transmission cancellation works within the MCTP USB library. Let's start by looking at `mctp_usblib_tx_cancel` in `mctp-usblib.c`. I want to see how this function handles the cessation of data transmission. Is it a graceful shutdown of the USB endpoint, a queuing issue, or just clearing a flag? And, what state does the device or controller enter upon the cancellation? I'll probably need to understand how this interacts with the underlying USB driver and the MCTP protocol layers. I'm expecting some conditional logic regarding different transmission states or error conditions, so that will be the focal point of my scrutiny. |
| 26/2 | 2026/07/08 23:30 | tool | grepper |
0mArgs:{
"Expression": "mctp_usblib_tx_cancel",
"PathPrefix": "drivers/net/mctp/mctp-usblib.c"
}Results: {
"Output": "drivers/net/mctp/mctp-usblib.c=589=EXPORT_SYMBOL_GPL(mctp_usblib_tx_push);\n--\ndrivers/net/mctp/mctp-usblib.c-591-/* Cancel a tx: any un-sent context is released. */\ndrivers/net/mctp/mctp-usblib.c:592:void mctp_usblib_tx_cancel(struct mctp_usblib_tx *tx, struct net_device *dev,\ndrivers/net/mctp/mctp-usblib.c-593-\t\t\t enum skb_drop_reason reason)\n--\ndrivers/net/mctp/mctp-usblib.c-607-}\ndrivers/net/mctp/mctp-usblib.c:608:EXPORT_SYMBOL_GPL(mctp_usblib_tx_cancel);\ndrivers/net/mctp/mctp-usblib.c-609-\n"
} |
| 27/2 | 2026/07/08 23:30 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33041 output: 238 thoughts: 548 Thoughts: **Fuzzing MCTP-over-USB with the New Library** Okay, so I've taken a look at this patch and, based on my understanding, it's definitely worth fuzzing. The key here is the introduction of this new common library, `mctp-usblib`, and its integration with the existing `mctp-usb` transport driver. My main focus is going to be on these functions, I need to make sure they are included in the fuzzing configuration: `mctp_usblib_rx_prepare`, `mctp_usblib_rx_complete`, `mctp_usblib_tx_push`, `mctp_usblib_tx_send_complete`, `mctp_usb_in_complete`, `mctp_usb_out_complete`, `mctp_usb_tx_send`, `mctp_usb_start_xmit`, and `mctp_usb_rx_queue`. These are the core entry points and completion routines, so those are what I'll start with. The patch appears to be functional. It looks like the changes are reachable through USB gadget emulation, which is what I'd expect. To ensure this is setup properly, I'll need to enable a few configurations: First, the basic `MCTP` support. Then, of course, the `MCTP_TRANSPORT_USB` option, and finally, `MCTP_TRANSPORT_USBLIB`. I noted that `MCTP_TRANSPORT_USBLIB` is automatically selected by `MCTP_TRANSPORT_USB`, but I'm going to list it explicitly for clarity. I'm skipping the `MCTP_TRANSPORT_USBLIB_TEST` config, since that's a KUnit test, and fuzzing doesn't typically require those. It's more about code coverage than running unit tests. So, here's what I'll set up for the fuzzing run: **WorthFuzzing: true** **FocusSymbols:** - mctp_usblib_rx_prepare - mctp_usblib_rx_complete - mctp_usblib_tx_push - mctp_usblib_tx_send_complete - mctp_usb_in_complete - mctp_usb_out_complete - mctp_usb_tx_send - mctp_usb_start_xmit - mctp_usb_rx_queue **EnableConfigs:** - MCTP - MCTP_TRANSPORT_USB - MCTP_TRANSPORT_USBLIB The reasoning is that this patch introduces a new common library for MCTP-over-USB and modifies the MCTP USB transport driver to use it. The changes are functional and reachable via USB gadget emulation. It's prime territory for fuzzing. |
| 28/2 | 2026/07/08 23:30 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MCTP",
"MCTP_TRANSPORT_USB",
"MCTP_TRANSPORT_USBLIB"
],
"FocusSymbols": [
"mctp_usblib_rx_prepare",
"mctp_usblib_rx_complete",
"mctp_usblib_tx_push",
"mctp_usblib_tx_send_complete",
"mctp_usb_in_complete",
"mctp_usb_out_complete",
"mctp_usb_tx_send",
"mctp_usb_start_xmit",
"mctp_usb_rx_queue"
],
"Reasoning": "The patch introduces a new common library for MCTP-over-USB (`mctp-usblib`) and modifies the MCTP USB transport driver (`mctp-usb`) to use it. The changes are functional and reachable via USB gadget emulation (e.g., raw-gadget).",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MCTP",
"MCTP_TRANSPORT_USB",
"MCTP_TRANSPORT_USBLIB"
],
"FocusSymbols": [
"mctp_usblib_rx_prepare",
"mctp_usblib_rx_complete",
"mctp_usblib_tx_push",
"mctp_usblib_tx_send_complete",
"mctp_usb_in_complete",
"mctp_usb_out_complete",
"mctp_usb_tx_send",
"mctp_usb_start_xmit",
"mctp_usb_rx_queue"
],
"Reasoning": "The patch introduces a new common library for MCTP-over-USB (`mctp-usblib`) and modifies the MCTP USB transport driver (`mctp-usb`) to use it. The changes are functional and reachable via USB gadget emulation (e.g., raw-gadget).",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|