From: "Daniel Zahka" <daniel.zahka@gmail.com>
To: <netdev-bot+sashiko@kernel.org>, <daniel.zahka@gmail.com>
Cc: <alexanderduyck@fb.com>, <kuba@kernel.org>,
<kernel-team@meta.com>, <andrew+netdev@lunn.ch>,
<davem@davemloft.net>, <edumazet@google.com>, <pabeni@redhat.com>,
<ast@kernel.org>, <daniel@iogearbox.net>, <hawk@kernel.org>,
<john.fastabend@gmail.com>, <sdf@fomichev.me>,
<dimitri.daskalakis1@gmail.com>, <mohsin.bashr@gmail.com>,
<linux-kernel@vger.kernel.org>, <netdev@vger.kernel.org>,
<bpf@vger.kernel.org>
Subject: Re: [PATCH net-next v2 4/8] eth: mpnic: implement Tx queue allocation and cleanup
Date: Mon, 28 Sep 2026 10:46:31 -0400 [thread overview]
Message-ID: <DLR14RY3N3LF.3J84UDH79H9PS@gmail.com> (raw)
In-Reply-To: <179055370187.3145.16714537922103048975@kernel.org>
On Sun Sep 27, 2026 at 8:01 PM EDT, netdev-bot+sashiko wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider.
>
> Critical: 0 · High: 2 · Medium: 0 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] net: mpnic: missing bounds check on hardware completion head
> causes NULL deref
> - [High] net: mpnic: integer underflow in desc_cnt causes massive out-of-
> bounds unmaps
> - [Low] struct mpnic_ring::deferred_meta is documented as '-1 if the
> doorbell is in sync with the tail', yet at this commit the rings come…
>
> --
>
> eth: mpnic: implement Tx queue allocation and cleanup
>
> This adds the Tx ring structures and the per-NAPI-vector allocation of
> queue triads, descriptor memory and interrupts. It also adds completion
> processing, where a single completion reports a work queue head and can
> release many packets. The queues are not yet enabled in hardware.
>
>> diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
>> new file mode 100644
>> index 0000000000000..853976e11b032
>> --- /dev/null
>> +++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
>> @@ -0,0 +1,495 @@
>
> [ ... ]
>
>> +static void mpnic_ring_init(struct mpnic_ring *ring, u32 __iomem *doorbell,
>> + int q_idx)
>> +{
>> + ring->doorbell = doorbell;
>> + ring->q_idx = q_idx;
>> +}
>
> [Severity: Low]
> This isn't a bug at this point in the series, but the struct mpnic_ring
> comment in mpnic_txrx.h describes deferred_meta like this:
>
> /* TWQ only, index of the metadata descriptor of the last packet
> * placed in the ring without ringing the doorbell, -1 if the
> * doorbell is in sync with the tail.
> */
> s32 deferred_meta;
>
> mpnic_alloc_napi_vector() gets the rings from kzalloc_flex(), and
> mpnic_ring_init() never sets deferred_meta. So the field starts at 0, not
> -1.
>
> Would it make sense to set deferred_meta to -1 in mpnic_ring_init(), so the
> field matches its comment from the start?
>
> Nothing reads deferred_meta in this commit. The later patch "eth: mpnic:
> start and stop the Tx HW queues" adds mpnic_enable_twq(), which sets tail
> and head to 0 and deferred_meta to -1 on every enable. In mpnic_open()
> that runs before mpnic_napi_enable() and netif_tx_wake_all_queues(), so
> this only affects the intermediate state of the series.
>
Hmm, I guess the comment is slightly ahead of the implementation (next
patch)... I suppose introducing deferred_meta later would probably be
the solution, but to me that would be a bit out of place, given the
other ring fields are really initialized in that next patch.
> [ ... ]
>
>> @@ -0,0 +1,495 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (c) Meta Platforms, Inc. and affiliates. */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/pci.h>
>> +#include <linux/slab.h>
>> +
>> +#include "mpnic.h"
>> +#include "mpnic_netdev.h"
>> +#include "mpnic_txrx.h"
>> +
>> +struct mpnic_xmit_cb {
>> + u32 bytecount;
>> + u8 desc_count;
>> +};
>> +
>> +#define MPNIC_XMIT_CB(__skb) ((struct mpnic_xmit_cb *)((__skb)->cb))
>> +
>> +/* Leave the interrupt moderation counters alone when arming or masking */
>> +#define MPNIC_TIM_PARAM_CFG_PRESERVE_MASK \
>> + (MPNIC_TIM_CTL1_UPD_IGN_LONG_EVENT_CNT | \
>> + MPNIC_TIM_CTL1_UPD_IGN_LONG_TIME_CNT | \
>> + MPNIC_TIM_CTL1_UPD_IGN_SHORT_TIME_CNT)
>> +
>> +static void mpnic_nv_irq_disable(struct mpnic_napi_vector *nv)
>> +{
>> + mpnic_wr64(nv->mpd, MPNIC_TIM_CTL1(nv->qt[0].cmpl.q_idx),
>> + MPNIC_TIM_PARAM_CFG_PRESERVE_MASK |
>> + MPNIC_TIM_CTL1_MASK_EN | MPNIC_TIM_CTL1_MASK);
>> +}
>> +
>> +static void mpnic_nv_irq_rearm(struct mpnic_napi_vector *nv)
>> +{
>> + /* Rearming a single queue on a given IRQ rearms all the other
>> + * queues mapped to the same IRQ.
>> + */
>> + mpnic_wr64(nv->mpd, MPNIC_TIM_CTL1(nv->qt[0].cmpl.q_idx),
>> + MPNIC_TIM_PARAM_CFG_PRESERVE_MASK | MPNIC_TIM_CTL1_MASK_EN);
>> +}
>> +
>> +static void mpnic_nv_irq_trigger(struct mpnic_napi_vector *nv)
>> +{
>> + mpnic_wr64(nv->mpd, MPNIC_TIM_CTL1(nv->qt[0].cmpl.q_idx),
>> + MPNIC_TIM_PARAM_CFG_PRESERVE_MASK |
>> + MPNIC_TIM_CTL1_MASK_EN | MPNIC_TIM_CTL1_TRIGGER);
>> +}
>> +
>> +static unsigned int mpnic_desc_unused(struct mpnic_ring *ring)
>> +{
>> + return (ring->head - ring->tail - 1) & ring->size_mask;
>> +}
>> +
>> +static struct netdev_queue *mpnic_txring_txq(const struct net_device *dev,
>> + const struct mpnic_ring *ring)
>> +{
>> + return netdev_get_tx_queue(dev, ring->q_idx);
>> +}
>> +
>> +static void mpnic_unmap_single_twd(struct device *dev, __le64 *twd)
>> +{
>> + u64 raw_twd = le64_to_cpu(*twd);
>> +
>> + dma_unmap_single(dev, FIELD_GET(MPNIC_TWD_ADDR, raw_twd),
>> + FIELD_GET(MPNIC_TWD_LEN, raw_twd), DMA_TO_DEVICE);
>> +}
>> +
>> +static void mpnic_unmap_page_twd(struct device *dev, __le64 *twd)
>> +{
>> + u64 raw_twd = le64_to_cpu(*twd);
>> +
>> + dma_unmap_page(dev, FIELD_GET(MPNIC_TWD_ADDR, raw_twd),
>> + FIELD_GET(MPNIC_TWD_LEN, raw_twd), DMA_TO_DEVICE);
>> +}
>> +
>> +static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
>> + struct mpnic_ring *ring, bool discard,
>> + unsigned int hw_head)
>> +{
>> + u64 total_bytes = 0, total_packets = 0;
>> + unsigned int head = ring->head;
>> + struct netdev_queue *txq;
>> + unsigned int clean_desc;
>> +
>> + clean_desc = (hw_head - head) & ring->size_mask;
>> +
>> + while (clean_desc) {
>> + struct sk_buff *skb = ring->tx_buf[head];
>> + unsigned int desc_cnt;
>> +
>> + desc_cnt = MPNIC_XMIT_CB(skb)->desc_count;
>> + if (desc_cnt > clean_desc)
>> + break;
>> +
>> + ring->tx_buf[head] = NULL;
>> +
>> + clean_desc -= desc_cnt;
>> +
>> + /* Step over the metadata descriptor */
>> + head++;
>> + head &= ring->size_mask;
>> + desc_cnt--;
>> +
>> + mpnic_unmap_single_twd(nv->dev, &ring->desc[head]);
>> + head++;
>> + head &= ring->size_mask;
>> + desc_cnt--;
>> +
>> + while (desc_cnt--) {
>
> [Severity: High]
> What happens here if desc_count is 0 or 1? desc_cnt is an unsigned
> int, and it is decremented twice unconditionally before the frag loop:
> once for the metadata descriptor and once for the head descriptor.
>
> With desc_count == 0 the desc_cnt > clean_desc check passes and
> clean_desc is left unchanged. The second decrement then wraps desc_cnt
> around to UINT_MAX. The frag loop calls mpnic_unmap_page_twd() about
> four billion times, walking round and round the ring. Each call hands
> whatever address and length it finds to dma_unmap_page() from softirq
> context.
>
> With desc_count == 1, mpnic_unmap_single_twd() is already applied to
> the following slot, which does not belong to this packet. The same
> wrap happens after that.
>
> desc_count lives in skb->cb and is only written by the xmit path added
> later in the series. This relies on every skb in tx_buf carrying a
> sane count. It also relies on hw_head never covering a packet whose
> descriptors are still being written. A device reporting a head that
> runs ahead of what was posted is enough to get here.
>
> Would it make sense to reject desc_cnt < 2 with a WARN_ON_ONCE()
> before stepping over the metadata descriptor? Possibly
> desc_cnt > MPNIC_MAX_SKB_DESC should be rejected as well.
>
Not reachable. We control the desc_count that is put into the control
block on xmit. We know that we allocate a primary metadata desc and at
least one dma mapped region for the skb head.
>> + mpnic_unmap_page_twd(nv->dev, &ring->desc[head]);
>> + head++;
>> + head &= ring->size_mask;
>> + }
>> +
>> + total_bytes += MPNIC_XMIT_CB(skb)->bytecount;
>> + total_packets++;
>> +
>> + napi_consume_skb(skb, napi_budget);
>> + }
>> +
>> + if (!total_bytes)
>> + return;
>> +
>> + ring->head = head;
>> +
>> + if (discard)
>> + return;
>> +
>> + txq = mpnic_txring_txq(nv->napi.dev, ring);
>> + netif_txq_completed_wake(txq, total_packets, total_bytes,
>> + mpnic_desc_unused(ring),
>> + MPNIC_TX_DESC_WAKEUP);
>> +}
>> +
>> +static void mpnic_commit_cq_head(struct mpnic_ring *cmpl)
>> +{
>> + u32 head = cmpl->head;
>> +
>> + /* The tail shadows the last value written to the doorbell, so a
>> + * completion queue which has not moved costs no MMIO write.
>> + */
>> + if (cmpl->tail != head) {
>> + cmpl->tail = head;
>> + writeq(head & cmpl->size_mask, cmpl->doorbell);
>> + }
>> +}
>> +
>> +static void mpnic_clean_tcq(struct mpnic_napi_vector *nv,
>> + struct mpnic_q_triad *qt, int napi_budget)
>> +{
>> + struct mpnic_ring *cmpl = &qt->cmpl;
>> + __le64 *raw_tcd, done;
>> + u32 head = cmpl->head;
>> + s32 head0 = -1;
>> +
>> + done = (head & (cmpl->size_mask + 1)) ? 0 : cpu_to_le64(MPNIC_TCD_DONE);
>> + raw_tcd = &cmpl->desc[head & cmpl->size_mask];
>> +
>> + /* Walk the completion queue collecting the heads reported by NIC.
>> + * Only the first work queue is enabled and no packet asks for a
>> + * timestamp, so every completion is a plain head update and the
>> + * descriptor type does not have to be decoded.
>> + */
>> + while ((*raw_tcd & cpu_to_le64(MPNIC_TCD_DONE)) == done) {
>> + u64 tcd;
>> +
>> + dma_rmb();
>> +
>> + tcd = le64_to_cpu(*raw_tcd);
>> + head0 = FIELD_GET(MPNIC_TCD_TYPE0_HEAD0, tcd);
>> +
>> + raw_tcd++;
>> + head++;
>> +
>> + if (unlikely(!(head & cmpl->size_mask))) {
>> + done ^= cpu_to_le64(MPNIC_TCD_DONE);
>> + raw_tcd = &cmpl->desc[0];
>> + }
>> + }
>> +
>> + cmpl->head = head;
>> +
>> + if (head0 >= 0)
>> + mpnic_clean_twq0(nv, napi_budget, &qt->sub0, false, head0);
>> +}
>> +
>> +static int mpnic_poll(struct napi_struct *napi, int budget)
>> +{
>> + struct mpnic_napi_vector *nv = container_of(napi,
>> + struct mpnic_napi_vector,
>> + napi);
>> + int i;
>> +
>> + for (i = 0; i < nv->txt_count; i++)
>> + mpnic_clean_tcq(nv, &nv->qt[i], budget);
>> +
>> + for (i = 0; i < nv->txt_count; i++)
>> + mpnic_commit_cq_head(&nv->qt[i].cmpl);
>> +
>> + if (likely(napi_complete_done(napi, 0)))
>> + mpnic_nv_irq_rearm(nv);
>> +
>> + return 0;
>> +}
>> +
>> +static irqreturn_t mpnic_msix_clean_rings(int __always_unused irq, void *data)
>> +{
>> + struct mpnic_napi_vector *nv = data;
>> +
>> + napi_schedule_irqoff(&nv->napi);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void mpnic_free_napi_vector(struct mpnic_net *mpn,
>> + struct mpnic_napi_vector *nv)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < nv->txt_count; i++)
>> + mpn->tx[nv->qt[i].sub0.q_idx] = NULL;
>> +
>> + mpnic_free_irq(nv->mpd, nv->v_idx, nv);
>> + netif_napi_del_locked(&nv->napi);
>> + mpn->napi[nv->v_idx - MPNIC_NON_NAPI_VECTORS] = NULL;
>> + kfree(nv);
>> +}
>> +
>> +void mpnic_free_napi_vectors(struct mpnic_net *mpn)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < mpn->num_napi; i++)
>> + if (mpn->napi[i])
>> + mpnic_free_napi_vector(mpn, mpn->napi[i]);
>> +}
>> +
>> +static void mpnic_ring_init(struct mpnic_ring *ring, u32 __iomem *doorbell,
>> + int q_idx)
>> +{
>> + ring->doorbell = doorbell;
>> + ring->q_idx = q_idx;
>> +}
>> +
>> +static int mpnic_alloc_napi_vector(struct mpnic_dev *mpd,
>> + struct mpnic_net *mpn, unsigned int idx)
>> +{
>> + u32 __iomem *uc_addr = READ_ONCE(mpd->uc_addr0);
>> + struct mpnic_napi_vector *nv;
>> + int err;
>> +
>> + /* Doorbells are plain pointers into the register window, they have
>> + * no way of noticing that it went away.
>> + */
>> + if (!uc_addr)
>> + return -EIO;
>> +
>> + nv = kzalloc_flex(*nv, qt, 1);
>> + if (!nv)
>> + return -ENOMEM;
>> +
>> + nv->txt_count = 1;
>> + nv->mpd = mpd;
>> + nv->dev = mpd->dev;
>> + nv->v_idx = idx + MPNIC_NON_NAPI_VECTORS;
>> +
>> + mpn->napi[idx] = nv;
>> + netif_napi_add_config_locked(mpn->netdev, &nv->napi, mpnic_poll, idx);
>> + netif_napi_set_irq_locked(&nv->napi,
>> + pci_irq_vector(to_pci_dev(mpd->dev),
>> + nv->v_idx));
>> +
>> + snprintf(nv->name, sizeof(nv->name), "%s-TxRx-%u",
>> + mpn->netdev->name, idx);
>> +
>> + err = mpnic_request_irq(mpd, nv->v_idx, mpnic_msix_clean_rings, 0,
>> + nv->name, nv);
>> + if (err)
>> + goto err_napi_del;
>> +
>> + mpnic_ring_init(&nv->qt[0].sub0, &uc_addr[MPNIC_TWQ_TAIL(idx, 0)], idx);
>> + mpnic_ring_init(&nv->qt[0].cmpl, &uc_addr[MPNIC_TCQ_HEAD(idx)], idx);
>> + mpn->tx[idx] = &nv->qt[0].sub0;
>> +
>> + return 0;
>> +
>> +err_napi_del:
>> + netif_napi_del_locked(&nv->napi);
>> + mpn->napi[idx] = NULL;
>> + kfree(nv);
>> + return err;
>> +}
>> +
>> +int mpnic_alloc_napi_vectors(struct mpnic_net *mpn)
>> +{
>> + unsigned int i;
>> + int err;
>> +
>> + for (i = 0; i < mpn->num_napi; i++) {
>> + err = mpnic_alloc_napi_vector(mpn->mpd, mpn, i);
>> + if (err)
>> + goto err_free_vectors;
>> + }
>> +
>> + return 0;
>> +
>> +err_free_vectors:
>> + mpnic_free_napi_vectors(mpn);
>> +
>> + return err;
>> +}
>> +
>> +static void mpnic_free_ring_resources(struct device *dev,
>> + struct mpnic_ring *ring)
>> +{
>> + kvfree(ring->tx_buf);
>> + ring->tx_buf = NULL;
>> +
>> + /* If size is not set there are no descriptors present */
>> + if (!ring->size)
>> + return;
>> +
>> + dma_free_coherent(dev, ring->size, ring->desc, ring->dma);
>> + ring->size_mask = 0;
>> + ring->size = 0;
>> +}
>> +
>> +static int mpnic_alloc_ring_desc(struct mpnic_net *mpn,
>> + struct mpnic_ring *ring, u32 count)
>> +{
>> + struct device *dev = mpn->netdev->dev.parent;
>> + size_t size;
>> +
>> + size = ALIGN(array_size(sizeof(*ring->desc), count), 4096);
>> +
>> + ring->desc = dma_alloc_coherent(dev, size, &ring->dma,
>> + GFP_KERNEL | __GFP_NOWARN);
>> + if (!ring->desc)
>> + return -ENOMEM;
>> +
>> + ring->size_mask = count - 1;
>> + ring->size = size;
>> +
>> + return 0;
>> +}
>> +
>> +static void mpnic_free_tx_qt_resources(struct mpnic_net *mpn,
>> + struct mpnic_q_triad *qt)
>> +{
>> + struct device *dev = mpn->netdev->dev.parent;
>> +
>> + mpnic_free_ring_resources(dev, &qt->cmpl);
>> + mpnic_free_ring_resources(dev, &qt->sub0);
>> +}
>> +
>> +static int mpnic_alloc_tx_qt_resources(struct mpnic_net *mpn,
>> + struct mpnic_q_triad *qt)
>> +{
>> + int err;
>> +
>> + err = mpnic_alloc_ring_desc(mpn, &qt->sub0, mpn->txq_size);
>> + if (err)
>> + return err;
>> +
>> + qt->sub0.tx_buf = kvzalloc_objs(*qt->sub0.tx_buf, mpn->txq_size,
>> + GFP_KERNEL | __GFP_NOWARN);
>> + if (!qt->sub0.tx_buf) {
>> + err = -ENOMEM;
>> + goto err_free_qt;
>> + }
>> +
>> + err = mpnic_alloc_ring_desc(mpn, &qt->cmpl, mpn->txq_size);
>> + if (err)
>> + goto err_free_qt;
>> +
>> + return 0;
>> +
>> +err_free_qt:
>> + mpnic_free_tx_qt_resources(mpn, qt);
>> + return err;
>> +}
>> +
>> +static void mpnic_free_nv_resources(struct mpnic_net *mpn,
>> + struct mpnic_napi_vector *nv)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < nv->txt_count; i++)
>> + mpnic_free_tx_qt_resources(mpn, &nv->qt[i]);
>> +}
>> +
>> +static int mpnic_alloc_nv_resources(struct mpnic_net *mpn,
>> + struct mpnic_napi_vector *nv)
>> +{
>> + int i, err;
>> +
>> + for (i = 0; i < nv->txt_count; i++) {
>> + err = mpnic_alloc_tx_qt_resources(mpn, &nv->qt[i]);
>> + if (err)
>> + goto err_free_qt_resources;
>> + }
>> +
>> + return 0;
>> +
>> +err_free_qt_resources:
>> + while (i--)
>> + mpnic_free_tx_qt_resources(mpn, &nv->qt[i]);
>> + return err;
>> +}
>> +
>> +void mpnic_free_resources(struct mpnic_net *mpn)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < mpn->num_napi; i++)
>> + mpnic_free_nv_resources(mpn, mpn->napi[i]);
>> +}
>> +
>> +int mpnic_alloc_resources(struct mpnic_net *mpn)
>> +{
>> + int i, err;
>> +
>> + for (i = 0; i < mpn->num_napi; i++) {
>> + err = mpnic_alloc_nv_resources(mpn, mpn->napi[i]);
>> + if (err)
>> + goto err_free_resources;
>> + }
>> +
>> + return 0;
>> +
>> +err_free_resources:
>> + while (i--)
>> + mpnic_free_nv_resources(mpn, mpn->napi[i]);
>> +
>> + return err;
>> +}
>> +
>> +int mpnic_set_netif_queues(struct mpnic_net *mpn)
>> +{
>> + int i, j, err;
>> +
>> + err = netif_set_real_num_tx_queues(mpn->netdev, mpn->num_tx_queues);
>> + if (err)
>> + return err;
>> +
>> + for (i = 0; i < mpn->num_napi; i++) {
>> + struct mpnic_napi_vector *nv = mpn->napi[i];
>> +
>> + for (j = 0; j < nv->txt_count; j++)
>> + netif_queue_set_napi(mpn->netdev, nv->qt[j].sub0.q_idx,
>> + NETDEV_QUEUE_TYPE_TX, &nv->napi);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +void mpnic_reset_netif_queues(struct mpnic_net *mpn)
>> +{
>> + int i, j;
>> +
>> + for (i = 0; i < mpn->num_napi; i++) {
>> + struct mpnic_napi_vector *nv = mpn->napi[i];
>> +
>> + for (j = 0; j < nv->txt_count; j++)
>> + netif_queue_set_napi(mpn->netdev, nv->qt[j].sub0.q_idx,
>> + NETDEV_QUEUE_TYPE_TX, NULL);
>> + }
>> +}
>> +
>> +void mpnic_napi_disable(struct mpnic_net *mpn)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < mpn->num_napi; i++) {
>> + napi_disable_locked(&mpn->napi[i]->napi);
>> +
>> + mpnic_nv_irq_disable(mpn->napi[i]);
>> + }
>> +}
>> +
>> +void mpnic_napi_enable(struct mpnic_net *mpn)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < mpn->num_napi; i++)
>> + napi_enable_locked(&mpn->napi[i]->napi);
>> +
>> + /* Force the first interrupt on each vector to guarantee that any
>> + * completions posted during bringup are processed.
>> + */
>> + for (i = 0; i < mpn->num_napi; i++)
>> + mpnic_nv_irq_trigger(mpn->napi[i]);
>> +
>> + mpnic_wrfl(mpn->mpd);
>> +}
>
> [Severity: High]
> Is hw_head used here without any check against what was actually
> posted? It comes straight from the TCD read in mpnic_clean_tcq(), and
> clean_desc is derived only from its distance to ring->head. It is never
> compared with ring->tail.
>
> The device could report a head beyond the last descriptor the driver
> posted, for example through a firmware bug or a completion type that
> mpnic_clean_tcq() does not decode. clean_desc then covers slots that
> were never filled. tx_buf comes from kvzalloc_objs(), and only the
> metadata slot of each packet holds an skb. So ring->tx_buf[head] is
> NULL there, and MPNIC_XMIT_CB(skb)->desc_count dereferences a NULL
> pointer from NAPI context.
>
> Could clean_desc be clamped to the number of in-flight descriptors,
> i.e. (ring->tail - head) & ring->size_mask? Alternatively, could the
> loop bail out with a WARN_ON_ONCE() when it finds an empty tx_buf
> slot? Ignoring a bogus completion seems preferable to taking the host
> down.
We trust the device to fill out this completion field correctly.
next prev parent reply other threads:[~2026-09-28 14:46 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 0:35 [PATCH net-next v2 0/8] eth: mpnic: initial support for Meta Platforms NIC Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 1/8] eth: mpnic: add scaffolding " Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 12:13 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 2/8] eth: mpnic: add register init for the device Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 12:14 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 3/8] eth: mpnic: allocate MSI-X vectors Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 16:01 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 4/8] eth: mpnic: implement Tx queue allocation and cleanup Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 14:46 ` Daniel Zahka [this message]
2026-09-25 0:35 ` [PATCH net-next v2 5/8] eth: mpnic: start and stop the Tx HW queues Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 15:00 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 6/8] eth: mpnic: add a netdevice and basic Tx handling Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 15:10 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 7/8] eth: mpnic: implement Rx queue allocation and cleanup Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 15:11 ` Daniel Zahka
2026-09-25 0:35 ` [PATCH net-next v2 8/8] eth: mpnic: add basic Rx handling Daniel Zahka
2026-09-28 0:01 ` netdev-bot+sashiko
2026-09-28 15:17 ` Daniel Zahka
2026-09-28 18:16 ` [PATCH net-next v2 0/8] eth: mpnic: initial support for Meta Platforms NIC Daniel Zahka
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DLR14RY3N3LF.3J84UDH79H9PS@gmail.com \
--to=daniel.zahka@gmail.com \
--cc=alexanderduyck@fb.com \
--cc=andrew+netdev@lunn.ch \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=dimitri.daskalakis1@gmail.com \
--cc=edumazet@google.com \
--cc=hawk@kernel.org \
--cc=john.fastabend@gmail.com \
--cc=kernel-team@meta.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mohsin.bashr@gmail.com \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@fomichev.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®