diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-07-30 16:55:01 -0700 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-07-30 16:55:02 -0700 |
| commit | 2fbade66245059c78daeaccfce13ecf499fffb51 (patch) | |
| tree | c2e146ed4f50ac8c101a298d7013ec6cba47d8fd | |
| parent | 8e4d7d120734936cb961d9dc46b788e0487d3256 (diff) | |
| parent | 88dbfe0d95817423ec7b2fb2d3fa1212fc5019a5 (diff) | |
| download | linux-2fbade66245059c78daeaccfce13ecf499fffb51.tar.gz linux-2fbade66245059c78daeaccfce13ecf499fffb51.zip | |
Merge branch 'net-mctp-usb-add-support-for-mctp-over-usb-v1-1'
Jeremy Kerr says:
====================
net: mctp: usb: Add support for MCTP-over-USB v1.1
Version 1.1.0 of DSP0283 (MCTP over USB transport binding) has been
released, this patch series updates our current v1.0.1 support for the
changes in v1.1.x.
The major change in v1.1 is the introduction of "packet spanning" mode,
where a single MCTP packet may be split over multiple USB packets
(themselves forming a single USB bulk transfer). This relaxes the
requirement for USB high-speed mode, as we can now send MCTP packets
contained over multiple 64-byte full-speed USB bulk transfers, and gives
us an increase in the maximum MCTP packet size - we now have 13 bits of
packet length (previously 8) in the transport header.
Handling packet spanning introduces some complexity in the transmit and
receive paths, as we lose some constraints on where packet boundaries
may correspond to USB transfer boundaries, and may need to retain state
across separate transfers. To contain this complexity, we introduce a
new library for the transfer packing- and unpacking implementations,
"mctp-usblib". The host driver is a consumer of this library, and a
future gadget driver can use the same implementations. We can now also
implement tests on the API boundary of the library.
The series implements an incremental shift to mctp-usblib, then
implements packet spanning mode in the new library. We have a few
changes to prepare for this, in altering a few constants and
behaviours as v1.0-specific. Once packet spanning is implemented in
mctp-usblib, we enable it in the host-side driver.
====================
Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-0-e66bbba0dbdc@codeconstruct.com.au
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
| -rw-r--r-- | drivers/net/mctp/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/net/mctp/Makefile | 1 | ||||
| -rw-r--r-- | drivers/net/mctp/mctp-usb.c | 348 | ||||
| -rw-r--r-- | drivers/net/mctp/mctp-usblib-test.c | 412 | ||||
| -rw-r--r-- | drivers/net/mctp/mctp-usblib.c | 625 | ||||
| -rw-r--r-- | include/linux/usb/mctp-usb.h | 88 |
6 files changed, 1336 insertions, 154 deletions
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig index cf325ab0b1ef..c40ac9c665b7 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 c36006849a1e..c870b62d3f1c 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 fade65f2f269..542a570c76cc 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,14 +176,34 @@ err_retry: return 0; } +static const unsigned int rx_err_max = 10; + +/* Returns -1 if we have hit excessive errors, zero otherwise. */ +static int mctp_usb_in_urb_err(struct mctp_usb *mctp_usb, int status, + bool stalled) +{ + 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(mctp_usb->netdev, + "excessive errors from%s IN EP, first: %d\n", + stalled ? " (stalled)" : "", + mctp_usb->in_err_orig); + return -1; + } + + return 0; +} + 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; - int status; + struct mctp_usb *mctp_usb = urb->context; + struct net_device *netdev = mctp_usb->netdev; + unsigned long flags; + int rc, status; status = urb->status; @@ -172,80 +211,43 @@ static void mctp_usb_in_complete(struct urb *urb) case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: - case -EPROTO: - kfree_skb(skb); - return; - case 0: - break; - default: - netdev_dbg(netdev, "unexpected rx urb status: %d\n", status); - kfree_skb(skb); + /* device shutdown, don't resubmit */ + mctp_usblib_rx_cancel(&mctp_usb->rx); 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; - } - - if (hdr->len < - sizeof(struct mctp_hdr) + sizeof(struct mctp_usb_hdr)) { - netdev_dbg(netdev, "rx: short packet (hdr) %d\n", - hdr->len); - break; - } - - /* 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; - } - - 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); - } - dev_dstats_rx_add(netdev, skb->len); + case -EPIPE: + /* endpoint stall: clear halt, which will cause a resubmit */ + rc = mctp_usb_in_urb_err(mctp_usb, status, true); + if (rc) + return; + + 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); + return; - skb->protocol = htons(ETH_P_MCTP); - skb_reset_network_header(skb); - cb = __mctp_cb(skb); - cb->halen = 0; - netif_rx(skb); + 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 */ + rc = mctp_usb_in_urb_err(mctp_usb, status, false); + if (rc) + return; + break; - 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 +255,30 @@ 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); + unsigned long flags; + 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); + + if (++mctp_usb->in_err_count >= rx_err_max) + return; + + 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); + return; + } + mctp_usb->clear_halt = false; + } mctp_usb_rx_queue(mctp_usb, GFP_KERNEL); } @@ -262,6 +288,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 +312,11 @@ 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); + mctp_usblib_rx_cancel(&mctp_usb->rx); return 0; } @@ -301,7 +333,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 +349,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 +361,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 +370,46 @@ 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); + rc = mctp_usblib_rx_init(&dev->rx, le16_to_cpu(ep_in->wMaxPacketSize), + dev->span); + if (rc) + goto err_free_netdev; + 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); +err_free_netdev: free_netdev(netdev); return rc; } @@ -371,13 +419,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 000000000000..9df401a914ff --- /dev/null +++ b/drivers/net/mctp/mctp-usblib-test.c @@ -0,0 +1,412 @@ +// 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_EXPECT_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); + + rc = mctp_usblib_rx_init(rx, ep_maxpacket, span); + KUNIT_ASSERT_EQ(test, rc, 0); + + 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[%u] len too small (%zu) 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 (%zu) != xfer_len (%zu) 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 000000000000..31997f989026 --- /dev/null +++ b/drivers/net/mctp/mctp-usblib.c @@ -0,0 +1,625 @@ +// 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/ch9.h> +#include <linux/usb/mctp-usb.h> +#include <net/mctp.h> + +int mctp_usblib_rx_init(struct mctp_usblib_rx *rx, u16 ep_pktlen, bool span) +{ + if (!ep_pktlen) + return -EINVAL; + + if (ep_pktlen & ~USB_ENDPOINT_MAXP_MASK) + return -EINVAL; + + memset(rx, 0, sizeof(*rx)); + rx->span = span; + rx->ep_pktlen = ep_pktlen; + + return 0; +} +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 ? roundup(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 = rounddown(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 = -EPROTO; + 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 a2f6f1e04efb..4bb04a371105 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; +}; + +int 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 */ |
