Thread (19 messages) 19 messages, 4 authors, 18d ago

Re: [net PATCH v2 3/6] eth: fbnic: use the Rx queue napi pointer to find the napi vector

flat view

From: Alexander Duyck <hidden>
Date: 2026-09-18 15:11:44

On Thu, Sep 17, 2026 at 2:12 PM [off-list ref] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 1 (2) · Medium: 0 · Low: 0

- [High] The invariant the patch relies on - "The pointer is NULL exactly
  while the datapath is down" - does not hold across PCIe…

Pre-existing issues:
- [High] fbnic_queue_stop() uses nv->qt[t].sub0.q_idx as the queue
  identifier for every triad, but that field is 0 for two kinds of…
- [High] fbnic_alloc_qt_page_pools(): when the queue has an unreadable
  memory provider, the second page_pool_create() overwrites the local…

--

Here is the review report.

eth: fbnic: use the Rx queue napi pointer to find the napi vector

The queue management ndos stop deriving the napi vector from
fbn->napi[idx % fbn->num_napi] and instead read the association that
fbnic_set_netif_napi() published in netdev_rx_queue.napi.  A new helper
fbnic_rxq_nv() performs the lookup, and fbnic_queue_mem_alloc() returns
-ENETDOWN when the pointer is NULL.
quoted
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
index 81a30e2d449b2..e93174fc1239f 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
[ ... ]
quoted
@@ -2829,6 +2830,17 @@ void fbnic_napi_depletion_check(struct net_device *netdev)
      fbnic_wrfl(fbd);
 }

+/* Returns the napi vector servicing an Rx queue, or NULL if the datapath
+ * is torn down.  The association is published by fbnic_set_netif_napi()
+ * and cleared by fbnic_reset_netif_napi(), both under the instance lock.
+ */
+static struct fbnic_napi_vector *fbnic_rxq_nv(struct net_device *dev, int idx)
+{
+     struct napi_struct *napi = __netif_get_rx_queue(dev, idx)->napi;
+
+     return napi ? container_of(napi, struct fbnic_napi_vector, napi) : NULL;
+}
+
[Severity: High]
The commit message says "The pointer is NULL exactly while the datapath is
down".  Does that hold across the resume path?

In fbnic_pci.c, the publish and the enable are split across two calls with
the instance lock dropped in between:

__fbnic_pm_resume()
    netdev_lock()
    __fbnic_open()
        fbnic_set_netif_queues()
            fbnic_set_netif_napi()
                netif_queue_set_napi()   /* publishes rxq->napi */
    netdev_unlock()

__fbnic_pm_attach()
    if (netif_running(netdev))
        fbnic_up(fbn);               /* napi_enable_locked() happens here */
    netif_device_attach(netdev);
This one has me scratching my head in how the code was able to get
into this state in the first place and has been this way for so long.
I am assuming it was relying on the netif_device_detach/attach for
some security. I would probably fix this as a separate patch as it
requires restructuring for several reasons, and I would classify it as
a pre-existing issue.

First the fact that the fbnic_up operating outside of any locking is
problematic at best as it could easily race with a dev_close request
and end up leaving things in a bad state. Second it looks like this
pattern exists in other drivers as well such as igb so we will need to
likely fix it across several drivers.

Basically we should be holding the rtnl and the netdev_lock and run
things from open through up all in one shot, likely including the
netdev_device_attach. What I will probably look at doing is merging
the __fbnic_pm_resume and __fbnic_pm_attach calls all into
fbnic_pm_resume and then only call it from the err_resume instead of
running down two different paths.
[ ... ]
quoted
@@ -2841,8 +2853,16 @@ static int fbnic_queue_mem_alloc(struct net_device *dev,
      if (!netif_running(dev))
              return fbnic_alloc_qt_page_pools(fbn, qt, idx);

+     /* A failed PCIe recovery or resume can leave the datapath torn down
+      * while netif_running() is still true.  This ndo runs before
+      * netdev_rx_queue_restart() checks netif_running(), so bail out
+      * rather than touching rings and vectors that are already freed.
+      */
+     nv = fbnic_rxq_nv(dev, idx);
+     if (!nv)
+             return -ENETDOWN;
+
[Severity: High]
Continuing the point above: does a non-NULL rxq->napi actually imply the
datapath is up?  For the resume window the pointer is published while the
vectors exist but are not enabled, so this check passes and
fbnic_queue_stop() runs napi_disable_locked() on a NAPI that was only added
by netif_napi_add_config_locked(), which leaves NAPI_STATE_SCHED and
NAPI_STATE_NPSVC set:

net/core/dev.c:napi_disable_locked() {
        ...
        do {
                while (val & (NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC)) {
                        usleep_range(20, 200);
                        val = READ_ONCE(n->state);
                }
        ...
}

napi_enable_locked() is the only thing that clears those bits, and it has
not run yet, so does this loop spin forever with the netdev instance lock
held, blocking the pending fbnic_up(), dev_close(), unregister and ethtool?

Would a "datapath enabled" state test, or moving fbnic_up() inside the
instance-locked region of the resume path, be a better fit than keying off
the published pointer?
Odds are pretty slim for hitting this race, but this would be
addressed via the locking bug fix I called out above. Again the
pattern before was more of a NULL pointer dereference that was a
guaranteed hit versus this locking race.
quoted
+     real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl);
-     nv = fbn->napi[idx % fbn->num_napi];

      fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx,
                      real->sub0.flags);
[Severity: High]
This is a pre-existing issue, but since both branches of this ndo reach it,
could fbnic_alloc_qt_page_pools() be dereferencing an error pointer?  The
second page_pool_create() overwrites the local pp, and the error label then
destroys that value rather than the pool it should be releasing:

        qt->sub0.page_pool = pp;
        if (netif_rxq_has_unreadable_mp(fbn->netdev, rxq_idx)) {
                ...
                pp = page_pool_create(&pp_params);
                if (IS_ERR(pp))
                        goto err_destroy_sub0;
        ...
err_destroy_sub0:
        page_pool_destroy(pp);
        return PTR_ERR(pp);

net/core/page_pool.c:page_pool_destroy() only filters NULL:

        if (!pool)
                return;

        if (!page_pool_put(pool))
                return;

so page_pool_put() would dereference the ERR_PTR, and qt->sub0.page_pool
leaks because netdev_rx_queue_reconfig() does not call ndo_queue_mem_free
on the mem_alloc failure path.  The reachable trigger is a devmem or
io_uring bind on a queue with an unreadable memory provider where the
second page_pool_create() fails with -ENOMEM, or where the provider's
->init() returns an error.  Would saving the error before destroying
qt->sub0.page_pool address this?

[ ... ]
This is yet another pre-existing issue that would likely be better
fixed as it is an unrelated problem. Looks like it should be a 2 liner
to use sub0.page_pool instead of pp in page_pool_destroy, and then to
reset that back to NULL.
quoted
@@ -2915,7 +2935,7 @@ static int fbnic_queue_stop(struct net_device *dev, void *qmem, int idx)
      int err;

      real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl);
-     nv = fbn->napi[idx % fbn->num_napi];
+     nv = fbnic_rxq_nv(dev, idx);
      fbnic_dbg_nv_exit(nv);

      napi_disable_locked(&nv->napi);
[Severity: High]
This isn't a bug introduced by this patch, but does the standalone-Tx
configuration this patch targets remain incorrect afterwards?  The rest of
fbnic_queue_stop() uses sub0.q_idx as the queue identifier:

        for (i = 0; i < nv->txt_count; i++)
                netif_stop_subqueue(dev, nv->qt[i].sub0.q_idx);
        fbnic_nv_disable(fbn, nv);

        for (t = 0; t < nv->txt_count + nv->rxt_count; t++) {
                err = fbnic_wait_queue_idle(fbn, t >= nv->txt_count,
                                            nv->qt[t].sub0.q_idx);

but sub0.q_idx is 0 for two kinds of triads.  For the placeholder Tx triad
of an Rx-only vector, fbnic_alloc_napi_vector() sets:

                } else {
                        fbnic_ring_init(&qt->sub0, db, 0,
                                        FBNIC_RING_F_DISABLED);
                }

and for the Rx BDQ rings:

                db = &uc_addr[FBNIC_QUEUE(rxq_idx) + FBNIC_QUEUE_BDQ_HPQ_TAIL];
                fbnic_ring_init(&qt->sub0, db, 0,
                                FBNIC_RING_F_CTX | FBNIC_RING_F_STATS);

so the real HW block index only lives in cmpl.q_idx.
I agree that this is a pre-existing bug, but I wouldn't fix it in this
patch. I would probably introduce yet another patch to resolve it.

By my count we have 3 pre-existing issues that aren't directly related
to the original problem. If possible I would prefer to address them
separately, otherwise I can tack on 3 additional diffs to the set and
resubmit.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help