Merge branch 'eth-fbnic-a-collection-of-fixes'

Alexander Duyck says:

====================
eth: fbnic: a collection of fixes

This series collects a handful of independent fbnic fixes for issues on
released kernels, plus one core ethtool fix needed by the fbnic offline
self test.

The first patch keeps rtnl_lock held on the ethtool ioctl path for the self
test. Since the ioctl path became rtnl-optional for ops-locked drivers,
fbnic's offline self test (which brings the interface down and up via
netif_close()/netif_open()) runs holding only the instance lock, tripping a
lockdep splat / ASSERT_RTNL and reconfiguring the device without the lock
it requires. A similar issue was found with Broadcom drivers so we expanded
the scope for v2 to just have the rtnl lock held for all selftest calls.

The second addresses a comparison issue in that we were limiting the
maximum number of standalone Tx queues to one less than the maximum number
of Tx queues. To resolve this it was just a matter of replacing a "<" with
a "<=".

The third addresses an indexing issue with netdev queues on fbnic in which
the NAPI vector was assumed to be findable as the Rx index modulo the
number of NAPI vectors. However this is actually not the case for if Tx
only and Rx only queues are setup. To resolve this we make use of the
cached NAPI pointer in the netdev Rx queues themselves.

The fourth patch fixes a NULL pointer dereference on unbind after a failed
PCIe error recovery: fbnic_pm_suspend() frees the napi vectors via a direct
ndo_stop() while leaving netif_running() true, and when slot_reset ->
resume fails the data path is never re-allocated. To prevent the panic we
reset num_napi to 0 before we free the IRQs which prevents walking the
unallocated napi vectors when we unbind the interface later.

The last two patches address the FW mailbox. One sets AW_FLUSH_MODE
alongside AW_FLUSH when tearing down the Rx ring, so the write pipeline
actually drains the staged requests instead of hanging on the BME halt.
The other handles completions flagged with FW_ERR on both mailboxes, which
the driver previously ignored. This resulted in us parsing a stale Rx page,
and spinning the capabilities poll to a timeout on a healthy ring.
====================

Link: https://patch.msgid.link/178941996343.7700.9376081102002673062.stgit@ahduyck-xeon-server.home.arpa
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-09-18 17:20:16 -07:00
commit 6c01564da3
10 changed files with 99 additions and 14 deletions

View File

@ -5733,7 +5733,8 @@ const struct ethtool_ops bnxt_ethtool_ops = {
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
ETHTOOL_OP_NEEDS_RTNL_SCOALESCE |
ETHTOOL_OP_NEEDS_RTNL_RSS,
ETHTOOL_OP_NEEDS_RTNL_RSS |
ETHTOOL_OP_NEEDS_RTNL_TEST,
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES |
ETHTOOL_COALESCE_USECS_IRQ |

View File

@ -974,6 +974,7 @@ enum {
/* PUL User Registers */
#define FBNIC_CSR_START_PUL_USER 0x31000 /* CSR section delimiter */
#define FBNIC_PUL_OB_TLP_HDR_AW_CFG 0x3103d /* 0xc40f4 */
#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE CSR_BIT(20)
#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH CSR_BIT(19)
#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_BME CSR_BIT(18)
#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_RDE_ATTR CSR_GENMASK(17, 15)
@ -1215,6 +1216,10 @@ enum {
#define FBNIC_IPC_MBX_DESC_LEN_MASK DESC_GENMASK(63, 48)
#define FBNIC_IPC_MBX_DESC_EOM DESC_BIT(46)
#define FBNIC_IPC_MBX_DESC_ADDR_MASK DESC_GENMASK(45, 3)
/* Set with FW_CMPL when the FW completed a descriptor without successfully
* processing it (e.g. a mailbox DMA error); the completion has no valid data.
*/
#define FBNIC_IPC_MBX_DESC_FW_ERR DESC_BIT(2)
#define FBNIC_IPC_MBX_DESC_FW_CMPL DESC_BIT(1)
#define FBNIC_IPC_MBX_DESC_HOST_CMPL DESC_BIT(0)

View File

@ -539,8 +539,8 @@ static void fbnic_dbg_fw_mbx_display(struct seq_file *s,
/* Generate header */
seq_puts(s, mbx_idx == FBNIC_IPC_MBX_RX_IDX ? "Rx\n" : "Tx\n");
seq_printf(s, "Rdy: %d Head: %d Tail: %d\n",
mbx->ready, mbx->head, mbx->tail);
seq_printf(s, "Rdy: %d Head: %d Tail: %d resp_error: %llu\n",
mbx->ready, mbx->head, mbx->tail, mbx->resp_error);
snprintf(hdr, sizeof(hdr), "%3s %-4s %s %-12s %s %-3s %-16s\n",
"Idx", "Len", "E", "Addr", "F", "H", "Raw");

View File

@ -2025,7 +2025,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM |
ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
ETHTOOL_OP_NEEDS_RTNL_GLINK,
ETHTOOL_OP_NEEDS_RTNL_GLINK |
ETHTOOL_OP_NEEDS_RTNL_TEST,
.get_drvinfo = fbnic_get_drvinfo,
.get_regs_len = fbnic_get_regs_len,
.get_regs = fbnic_get_regs,

View File

@ -60,8 +60,15 @@ static void fbnic_mbx_reset_desc_ring(struct fbnic_dev *fbd, int mbx_idx)
*/
switch (mbx_idx) {
case FBNIC_IPC_MBX_RX_IDX:
/* Clearing BME blocks the device from writing to the host
* but leaves the requests parked in the write pipeline. The
* write path only clears outstanding requests when both FLUSH
* and FLUSH_MODE are set; FLUSH_MODE lets them drain without
* landing on the host.
*/
wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AW_CFG,
FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH);
FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH |
FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE);
break;
case FBNIC_IPC_MBX_TX_IDX:
wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AR_CFG,
@ -285,6 +292,12 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd)
if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL))
break;
if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) {
tx_mbx->resp_error++;
dev_warn_ratelimited(fbd->dev,
"FW completed a Tx mailbox request with an error\n");
}
fbnic_mbx_unmap_and_free_msg(fbd, FBNIC_IPC_MBX_TX_IDX, head);
head++;
@ -1666,6 +1679,13 @@ static void fbnic_mbx_process_rx_msgs(struct fbnic_dev *fbd)
if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL))
break;
if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) {
rx_mbx->resp_error++;
dev_warn_ratelimited(fbd->dev,
"FW reported an error on an Rx mailbox message; dropping\n");
goto next_page;
}
dma_sync_single_for_cpu(fbd->dev, rx_mbx->buf_info[head].addr,
FBNIC_RX_PAGE_SIZE, DMA_FROM_DEVICE);
@ -1733,7 +1753,9 @@ void fbnic_mbx_poll(struct fbnic_dev *fbd)
int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd)
{
struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX];
struct fbnic_fw_mbx *rx_mbx = &fbd->mbx[FBNIC_IPC_MBX_RX_IDX];
unsigned long timeout = jiffies + 10 * HZ + 1;
u64 tx_resp_error, rx_resp_error;
int err, i;
do {
@ -1764,6 +1786,9 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd)
* mgmt.version once we get the actual version from the firmware
* in the capabilities request message.
*/
send_cap_req:
tx_resp_error = tx_mbx->resp_error;
rx_resp_error = rx_mbx->resp_error;
err = fbnic_fw_xmit_simple_msg(fbd, FBNIC_TLV_MSG_ID_HOST_CAP_REQ);
if (err)
goto clean_mbx;
@ -1781,9 +1806,27 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd)
msleep(20);
fbnic_mbx_poll(fbd);
/* A valid capabilities response ends the poll. Check it
* before the FW_ERR retry below so a response parsed in the
* same poll as an unrelated FW_ERR is not discarded.
*/
if (fbd->fw_cap.running.mgmt.version >= MIN_FW_VER_CODE)
break;
/* set err, but wait till mgmt.version check to report it */
if (!time_is_after_jiffies(timeout))
if (!time_is_after_jiffies(timeout)) {
err = -ETIMEDOUT;
continue;
}
/* The FW can flag our capabilities request (Tx) or its
* response (Rx) with FW_ERR, in which case it produced no
* usable response. The ring is not wedged, so re-issue the
* request instead of spinning until the timeout.
*/
if (tx_mbx->resp_error != tx_resp_error ||
rx_mbx->resp_error != rx_resp_error)
goto send_cap_req;
}
return 0;

View File

@ -13,6 +13,7 @@ struct fbnic_tlv_msg;
struct fbnic_fw_mbx {
u8 ready, head, tail;
u64 resp_error;
struct {
struct fbnic_tlv_msg *msg;
dma_addr_t addr;

View File

@ -434,6 +434,7 @@ static int fbnic_pm_suspend(struct device *dev)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
struct net_device *netdev = fbd->netdev;
struct fbnic_net *fbn;
if (fbnic_init_failure(fbd))
goto null_uc_addr;
@ -441,11 +442,16 @@ static int fbnic_pm_suspend(struct device *dev)
rtnl_lock();
netdev_lock(netdev);
fbn = netdev_priv(netdev);
netif_device_detach(netdev);
if (netif_running(netdev))
netdev->netdev_ops->ndo_stop(netdev);
/* The IRQs are about to be freed, so drop the napi vector count */
fbn->num_napi = 0;
netdev_unlock(netdev);
rtnl_unlock();
@ -508,16 +514,20 @@ static int __fbnic_pm_resume(struct device *dev)
if (fbnic_init_failure(fbd))
return 0;
rtnl_lock();
netdev_lock(netdev);
fbn = netdev_priv(netdev);
/* Reset the queues if needed */
fbnic_reset_queues(fbn, fbn->num_tx_queues, fbn->num_rx_queues);
rtnl_lock();
netdev_lock(netdev);
if (netif_running(netdev))
if (netif_running(netdev)) {
err = __fbnic_open(fbn);
/* On failure the vectors are freed, so drop the count */
if (err)
fbn->num_napi = 0;
}
netdev_unlock(netdev);
rtnl_unlock();

View File

@ -7,6 +7,7 @@
#include <linux/iopoll.h>
#include <linux/pci.h>
#include <net/netdev_queues.h>
#include <net/netdev_rx_queue.h>
#include <net/page_pool/helpers.h>
#include <net/tcp.h>
#include <net/xdp.h>
@ -1791,7 +1792,7 @@ int fbnic_alloc_napi_vectors(struct fbnic_net *fbn)
int err;
/* Allocate 1 Tx queue per napi vector */
if (num_napi < FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) {
if (num_napi <= FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) {
while (num_tx) {
err = fbnic_alloc_napi_vector(fbd, fbn,
num_napi, v_idx,
@ -2853,6 +2854,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;
}
static int fbnic_queue_mem_alloc(struct net_device *dev,
struct netdev_queue_config *qcfg,
void *qmem, int idx)
@ -2865,8 +2877,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;
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);
@ -2917,7 +2937,7 @@ static int fbnic_queue_start(struct net_device *dev,
struct fbnic_q_triad *real;
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_aggregate_ring_bdq_counters(fbn, &real->sub0);
fbnic_aggregate_ring_bdq_counters(fbn, &real->sub1);
@ -2939,7 +2959,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);

View File

@ -944,6 +944,7 @@ struct kernel_ethtool_ts_info {
#define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6)
#define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7)
#define ETHTOOL_OP_NEEDS_RTNL_GLINK BIT(8)
#define ETHTOOL_OP_NEEDS_RTNL_TEST BIT(9)
/**
* struct ethtool_ops - optional netdev operations
@ -981,6 +982,7 @@ struct kernel_ethtool_ts_info {
* - netdev_update_features()
* - netif_set_real_num_tx_queues()
* - ethtool_op_get_link() (syncs link watch under rtnl_lock)
* - netif_open() / netif_close() (used by @self_test)
*
* @get_drvinfo: Report driver/device information. Modern drivers no
* longer have to implement this callback. Most fields are

View File

@ -163,6 +163,8 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd)
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS;
case ETHTOOL_GLINK:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK;
case ETHTOOL_TEST:
return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_TEST;
}
return false;
}