mirror of
https://github.com/torvalds/linux.git
synced 2026-10-17 06:20:02 +02:00
Add some initial tests for the usblib receive path, where we're extracting MCTP packets from incoming USB transfer data. Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au> Link: https://patch.msgid.link/20260724-dev-mctp-usb-1-1-v5-10-e66bbba0dbdc@codeconstruct.com.au Signed-off-by: Jakub Kicinski <kuba@kernel.org>
626 lines
16 KiB
C
626 lines
16 KiB
C
// 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
|