| // 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 |