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