* [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support
@ 2026-09-20 9:24 Dong Yibo
2026-09-20 9:26 ` [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling Dong Yibo
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:24 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Hi maintainers,
This patch series adds the packet transmission, reception, and link status
management features to the RNPGBE driver, building upon the previously
introduced mailbox communication and basic driver infrastructure.
The series introduces:
- MSI-X interrupt handling with NAPI support
- TX path with scatter-gather DMA and completion handling
- RX path with page pool buffer management
- Unicast/Multicast filter.
- Firmware link-event processing and carrier management
These changes enable the RNPGBE driver to support basic TX/RX
network operations.
---
Changelog:
v12 -> v13:
[patch 1/5]:
1. Remove __MUCSE_DOWN. (Jakub Kicinski)
2. Move the NAPI shutdown synchronization from the patch3 into this
patch. Mask queue interrupts before disabling NAPI, wait for in-flight
poll callbacks with synchronize_net(), and mask the interrupts again
afterwards.
3. Clarify that the mailbox workqueue is plumbing for asynchronous
firmware event handling added later in the series.
4. Update the queue-count, interrupt-scheme, and interrupt-disable
comments to match the current implementation.
[patch 2/5]:
1. Use WRITE_ONCE() when publishing and clearing TX/RX ring pointers to
pair with the lockless READ_ONCE() statistics path.
2. Remove the unused mucse argument from rnpgbe_setup_tx_resources().
3. Clarify the commit message description of the hardware's 56-bit DMA
addressing capability. Keep the dma_set_mask_and_coherent() return
value unchecked, as the DMA API documents that masks wider than 32
bits do not fail.
[patch 3/5]:
1. Clarify the initial RX ring fill and allocation retry timing.
2. Document the sticky AXI fault return semantics and the RX resource
teardown preconditions.
3. Move rnpgbe_clean_rx_ring() before its first caller and remove the
unnecessary forward declaration.
[patch 5/5]:
1. Update the GMAC link state before acknowledging firmware link
events, keeping mailbox ownership throughout event processing.
2. Coalesce a pending level-like link event before sending port or
link-report commands, avoiding collisions in the shared mailbox window.
3. Remove the unused periodic link-service scheduling and run the
service task only when a link event is received.
4. Distinguish an empty mailbox from reset and other mailbox errors,
report actual failures with a rate-limited warning, and document the
firmware mailbox lock and acknowledgment protocol.
Links:
v1: https://lore.kernel.org/netdev/20260325091204.94015-1-dong100@mucse.com/
v2: https://lore.kernel.org/netdev/20260403025713.527841-1-dong100@mucse.com/
v3: https://lore.kernel.org/netdev/20260507081539.171844-1-dong100@mucse.com/
v4: https://lore.kernel.org/netdev/20260526033539.164061-1-dong100@mucse.com/
v5: https://lore.kernel.org/netdev/20260528023150.239532-1-dong100@mucse.com/
v6: https://lore.kernel.org/netdev/20260604112750.769215-1-dong100@mucse.com/
v7: https://lore.kernel.org/netdev/20260611100036.36370-1-dong100@mucse.com/
v8: https://lore.kernel.org/netdev/20260731120322.895955-1-dong100@mucse.com/
v9: https://lore.kernel.org/netdev/20260814111317.1741087-1-dong100@mucse.com/
v10: https://lore.kernel.org/netdev/F44E6669E6C9E7C4+20260831073608.401988-2-dong100@mucse.com/
v11: https://lore.kernel.org/netdev/745DADAC2A5786BD+20260911112416.2142101-1-dong100@mucse.com/
v12: https://lore.kernel.org/netdev/64BE41C2D2C0836A+20260914013019.2262317-1-dong100@mucse.com/
---
Dong Yibo (5):
net: rnpgbe: Add interrupt handling
net: rnpgbe: Add basic TX packet transmission support
net: rnpgbe: Add basic RX data path support
net: rnpgbe: Add receive mode support
net: rnpgbe: Add link status handling support
drivers/net/ethernet/mucse/Kconfig | 1 +
drivers/net/ethernet/mucse/rnpgbe/Makefile | 3 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 201 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 137 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 43 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 2221 +++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 84 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 162 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c | 181 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h | 4 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 283 +++
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 60 +
12 files changed, 3344 insertions(+), 36 deletions(-)
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
@ 2026-09-20 9:26 ` Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:26 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Add interrupt setup and teardown for the RNPGBE driver.
Require at least two MSI-X vectors. Reserve vector 0 for the mailbox
and assign the remaining vectors to the data path. Set up mailbox
workqueue plumbing for later asynchronous firmware event handling,
while data-path interrupts schedule NAPI.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/Makefile | 3 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 47 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 4 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 2 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 571 ++++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 34 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 56 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c | 12 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 11 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 1 +
10 files changed, 731 insertions(+), 10 deletions(-)
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
diff --git a/drivers/net/ethernet/mucse/rnpgbe/Makefile b/drivers/net/ethernet/mucse/rnpgbe/Makefile
index de8bcb7772ab..17574cad392a 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/Makefile
+++ b/drivers/net/ethernet/mucse/rnpgbe/Makefile
@@ -8,4 +8,5 @@ obj-$(CONFIG_MGBE) += rnpgbe.o
rnpgbe-objs := rnpgbe_main.o\
rnpgbe_chip.o\
rnpgbe_mbx.o\
- rnpgbe_mbx_fw.o
+ rnpgbe_mbx_fw.o\
+ rnpgbe_lib.o
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index 5b024f9f7e17..40e41521a279 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -6,6 +6,11 @@
#include <linux/types.h>
#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/workqueue.h>
+
+#include "rnpgbe_hw.h"
enum rnpgbe_boards {
board_n500,
@@ -35,21 +40,61 @@ enum {
struct mucse_hw {
void __iomem *hw_addr;
+ void __iomem *ring_msix_base;
struct pci_dev *pdev;
struct mucse_mbx_info mbx;
int port;
u8 pfvfnum;
};
+struct mucse_ring {
+ struct mucse_ring *next;
+ struct mucse_q_vector *q_vector;
+ void __iomem *ring_addr;
+ void __iomem *irq_mask;
+ void __iomem *trig;
+ u8 queue_index;
+ /* hw ring idx */
+ u8 rnpgbe_queue_idx;
+} ____cacheline_internodealigned_in_smp;
+
+struct mucse_ring_container {
+ struct mucse_ring *ring;
+ u16 count;
+};
+
+struct mucse_q_vector {
+ struct mucse *mucse;
+ /* hardware interrupt vector number */
+ int hw_vector;
+ struct mucse_ring_container rx, tx;
+ struct napi_struct napi;
+ char name[IFNAMSIZ + 18];
+ /* for dynamic allocation of rings associated with this q_vector */
+ struct mucse_ring ring[] ____cacheline_internodealigned_in_smp;
+};
+
struct mucse_stats {
u64 tx_dropped;
};
+#define MAX_Q_VECTORS 8
+
struct mucse {
struct net_device *netdev;
struct pci_dev *pdev;
struct mucse_hw hw;
struct mucse_stats stats;
+ struct mucse_ring *tx_ring[RNPGBE_MAX_QUEUES]
+ ____cacheline_aligned_in_smp;
+ struct mucse_ring *rx_ring[RNPGBE_MAX_QUEUES]
+ ____cacheline_aligned_in_smp;
+ struct mucse_q_vector *q_vector[MAX_Q_VECTORS];
+ int num_tx_queues;
+ int num_q_vectors;
+ int num_rx_queues;
+ char mbx_name[32];
+ struct work_struct mbx_work;
};
int rnpgbe_get_permanent_mac(struct mucse_hw *hw, u8 *perm_addr);
@@ -68,4 +113,6 @@ int rnpgbe_init_hw(struct mucse_hw *hw, int board_type);
#define mucse_hw_wr32(hw, reg, val) \
writel((val), (hw)->hw_addr + (reg))
+#define mucse_hw_rd32(hw, reg) \
+ readl((hw)->hw_addr + (reg))
#endif /* _RNPGBE_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
index ebc7b3750157..921cc325a991 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
@@ -89,6 +89,8 @@ static void rnpgbe_init_n500(struct mucse_hw *hw)
{
struct mucse_mbx_info *mbx = &hw->mbx;
+ hw->ring_msix_base = hw->hw_addr + MUCSE_N500_RING_MSIX_BASE;
+
mbx->fwpf_ctrl_base = MUCSE_N500_FWPF_CTRL_BASE;
mbx->fwpf_shm_base = MUCSE_N500_FWPF_SHM_BASE;
}
@@ -104,6 +106,8 @@ static void rnpgbe_init_n210(struct mucse_hw *hw)
{
struct mucse_mbx_info *mbx = &hw->mbx;
+ hw->ring_msix_base = hw->hw_addr + MUCSE_N210_RING_MSIX_BASE;
+
mbx->fwpf_ctrl_base = MUCSE_N210_FWPF_CTRL_BASE;
mbx->fwpf_shm_base = MUCSE_N210_FWPF_SHM_BASE;
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
index e77e6bc3d3e3..0dce78e4a91b 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -6,10 +6,12 @@
#define MUCSE_N500_FWPF_CTRL_BASE 0x28b00
#define MUCSE_N500_FWPF_SHM_BASE 0x2d000
+#define MUCSE_N500_RING_MSIX_BASE 0x28700
#define MUCSE_GBE_PFFW_MBX_CTRL_OFFSET 0x5500
#define MUCSE_GBE_FWPF_MBX_MASK_OFFSET 0x5700
#define MUCSE_N210_FWPF_CTRL_BASE 0x29400
#define MUCSE_N210_FWPF_SHM_BASE 0x2d900
+#define MUCSE_N210_RING_MSIX_BASE 0x29000
#define RNPGBE_DMA_AXI_EN 0x0010
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
new file mode 100644
index 000000000000..9196fa130ca9
--- /dev/null
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -0,0 +1,571 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright(c) 2020 - 2025 Mucse Corporation. */
+
+#include <linux/pci.h>
+#include <linux/netdevice.h>
+
+#include "rnpgbe_lib.h"
+#include "rnpgbe.h"
+#include "rnpgbe_mbx_fw.h"
+
+static void rnpgbe_mbx_work(struct work_struct *work)
+{
+ struct mucse *mucse = container_of(work, struct mucse, mbx_work);
+
+ mucse_fw_irq_handler(&mucse->hw);
+}
+
+/**
+ * rnpgbe_msix_other - Other irq handler
+ * @irq: interrupt number
+ * @data: private data
+ *
+ * Return: IRQ_HANDLED
+ **/
+static irqreturn_t rnpgbe_msix_other(int irq, void *data)
+{
+ struct mucse *mucse = (struct mucse *)data;
+
+ queue_work(system_percpu_wq, &mucse->mbx_work);
+
+ return IRQ_HANDLED;
+}
+
+static void rnpgbe_irq_disable_queues(struct mucse_q_vector *q_vector)
+{
+ struct mucse_ring *ring;
+
+ /*
+ * TX/RX pairs share interrupt control registers; update them through
+ * the TX ring list.
+ */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ writel(INT_VALID, ring->trig);
+ writel((RX_INT_MASK | TX_INT_MASK), ring->irq_mask);
+ }
+
+ /* flush posted writes to ensure hardware sees the mask */
+ if (q_vector->tx.ring)
+ readl(q_vector->tx.ring->irq_mask);
+}
+
+static void rnpgbe_irq_enable_queues(struct mucse_q_vector *q_vector)
+{
+ struct mucse_ring *ring;
+
+ /*
+ * TX/RX pairs share interrupt control registers; update them through
+ * the TX ring list.
+ */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ writel(0, ring->irq_mask);
+
+ /* Re-trigger hw to re-check events lost while masked. */
+ writel(INT_VALID | TX_INT_MASK | RX_INT_MASK, ring->trig);
+ }
+}
+
+/**
+ * rnpgbe_poll - NAPI polling callback
+ * @napi: structure for representing this polling device
+ * @budget: polling budget
+ *
+ * Complete NAPI polling and re-enable queue interrupts. Ring cleaning is
+ * added when TX and RX support is enabled.
+ *
+ * Return: 0
+ **/
+static int rnpgbe_poll(struct napi_struct *napi, int budget)
+{
+ struct mucse_q_vector *q_vector =
+ container_of(napi, struct mucse_q_vector, napi);
+ int work_done = 0;
+
+ /* Exit if we are called by netpoll */
+ if (unlikely(!budget))
+ return 0;
+
+ if (likely(napi_complete_done(napi, work_done)))
+ rnpgbe_irq_enable_queues(q_vector);
+
+ return work_done;
+}
+
+/**
+ * rnpgbe_request_mbx_irq - Register mbx routine
+ * @mucse: pointer to private structure
+ *
+ * Register the dedicated mailbox handler on vector 0.
+ *
+ * Return: 0 on success, negative on failure
+ **/
+int rnpgbe_request_mbx_irq(struct mucse *mucse)
+{
+ struct pci_dev *pdev = mucse->pdev;
+ int err = 0;
+
+ snprintf(mucse->mbx_name, sizeof(mucse->mbx_name),
+ "rnpgbe-mbx:%s", pci_name(pdev));
+ INIT_WORK(&mucse->mbx_work, rnpgbe_mbx_work);
+
+ err = request_irq(pci_irq_vector(pdev, 0), rnpgbe_msix_other, 0,
+ mucse->mbx_name, mucse);
+
+ return err;
+}
+
+/**
+ * rnpgbe_free_mbx_irq - Remove mbx routine
+ * @mucse: pointer to private structure
+ **/
+void rnpgbe_free_mbx_irq(struct mucse *mucse)
+{
+ struct pci_dev *pdev = mucse->pdev;
+
+ free_irq(pci_irq_vector(pdev, 0), mucse);
+ cancel_work_sync(&mucse->mbx_work);
+}
+
+/**
+ * rnpgbe_set_num_queues - Set initial TX/RX queue counts
+ * @mucse: pointer to private structure
+ *
+ * Start with one TX queue and one RX queue.
+ **/
+static void rnpgbe_set_num_queues(struct mucse *mucse)
+{
+ mucse->num_tx_queues = 1;
+ mucse->num_rx_queues = 1;
+}
+
+/**
+ * rnpgbe_set_interrupt_capability - Set MSI-X interrupt capability
+ * @mucse: pointer to private structure
+ *
+ * Attempt to configure the interrupts using the best available
+ * capabilities of the hardware.
+ *
+ * Return: 0 on success, negative on failure
+ **/
+static int rnpgbe_set_interrupt_capability(struct mucse *mucse)
+{
+ int v_budget;
+
+ v_budget = min3(mucse->num_tx_queues, mucse->num_rx_queues,
+ MAX_Q_VECTORS);
+ v_budget = min_t(int, v_budget, num_online_cpus());
+ /* add one vector for mbx */
+ v_budget += 1;
+
+ /* Vector 0 is reserved for mailbox events and must not share NAPI. */
+ v_budget = pci_alloc_irq_vectors(mucse->pdev, 2, v_budget,
+ PCI_IRQ_MSIX);
+ if (v_budget < 0)
+ return v_budget;
+
+ /* q_vectors do not include the mailbox vector. */
+ mucse->num_q_vectors = v_budget - 1;
+
+ return 0;
+}
+
+/**
+ * mucse_add_ring - Add ring to ring container
+ * @ring: ring to be added
+ * @head: ring container
+ **/
+static void mucse_add_ring(struct mucse_ring *ring,
+ struct mucse_ring_container *head)
+{
+ ring->next = head->ring;
+ head->ring = ring;
+ head->count++;
+}
+
+/**
+ * rnpgbe_alloc_q_vector - Allocate memory for a single interrupt vector
+ * @mucse: pointer to private structure
+ * @eth_queue_idx: queue_index idx for this q_vector
+ * @vector_idx: q_vector array index
+ * @r_idx: starting hardware ring index
+ * @r_count: number of TX/RX ring pairs
+ * @step: ring step
+ *
+ * Return: 0 on success. If allocation fails we return -ENOMEM.
+ **/
+static int rnpgbe_alloc_q_vector(struct mucse *mucse,
+ int eth_queue_idx, int vector_idx, int r_idx,
+ int r_count, int step)
+{
+ int rxr_idx = r_idx, txr_idx = r_idx;
+ struct mucse_hw *hw = &mucse->hw;
+ struct mucse_q_vector *q_vector;
+ int txr_count, rxr_count, idx;
+ struct mucse_ring *ring;
+ int ring_count;
+
+ /*
+ * TX and RX rings are always allocated as pairs with the same hardware
+ * ring index. Interrupt control is shared by the pair, so the TX ring
+ * list is used to update the common registers.
+ */
+ txr_count = r_count;
+ rxr_count = r_count;
+ ring_count = txr_count + rxr_count;
+
+ q_vector = kzalloc_flex(*q_vector, ring, ring_count);
+ if (!q_vector)
+ return -ENOMEM;
+
+ netif_napi_add(mucse->netdev, &q_vector->napi, rnpgbe_poll);
+ /* tie q_vector and mucse together */
+ mucse->q_vector[vector_idx] = q_vector;
+ q_vector->mucse = mucse;
+ q_vector->hw_vector = vector_idx;
+ /* Vector 0 is reserved for the mailbox. */
+ q_vector->hw_vector++;
+
+ ring = q_vector->ring;
+
+ for (idx = 0; idx < txr_count; idx++) {
+ mucse_add_ring(ring, &q_vector->tx);
+ ring->queue_index = eth_queue_idx + idx;
+ ring->rnpgbe_queue_idx = txr_idx;
+ ring->ring_addr = hw->hw_addr + RING_OFFSET(txr_idx);
+ ring->irq_mask = ring->ring_addr + RNPGBE_DMA_INT_MASK;
+ ring->trig = ring->ring_addr + RNPGBE_DMA_INT_TRIG;
+ ring->q_vector = q_vector;
+ mucse->tx_ring[ring->queue_index] = ring;
+ txr_idx += step;
+ ring++;
+ }
+
+ for (idx = 0; idx < rxr_count; idx++) {
+ mucse_add_ring(ring, &q_vector->rx);
+ ring->queue_index = eth_queue_idx + idx;
+ ring->rnpgbe_queue_idx = rxr_idx;
+ ring->ring_addr = hw->hw_addr + RING_OFFSET(rxr_idx);
+ ring->irq_mask = ring->ring_addr + RNPGBE_DMA_INT_MASK;
+ ring->trig = ring->ring_addr + RNPGBE_DMA_INT_TRIG;
+ ring->q_vector = q_vector;
+ mucse->rx_ring[ring->queue_index] = ring;
+ rxr_idx += step;
+ ring++;
+ }
+
+ return 0;
+}
+
+/**
+ * rnpgbe_free_q_vector - Free memory allocated for specific interrupt vector
+ * @mucse: pointer to private structure
+ * @vector_idx: q_vector array index
+ *
+ * This function frees the memory allocated to the q_vector. In addition if
+ * NAPI is enabled it will delete any references to the NAPI struct prior
+ * to freeing the q_vector.
+ **/
+static void rnpgbe_free_q_vector(struct mucse *mucse, int vector_idx)
+{
+ struct mucse_q_vector *q_vector = mucse->q_vector[vector_idx];
+ struct mucse_ring *ring;
+
+ mucse_for_each_ring(ring, q_vector->tx)
+ mucse->tx_ring[ring->queue_index] = NULL;
+ mucse_for_each_ring(ring, q_vector->rx)
+ mucse->rx_ring[ring->queue_index] = NULL;
+ mucse->q_vector[vector_idx] = NULL;
+ netif_napi_del(&q_vector->napi);
+ kfree(q_vector);
+}
+
+/**
+ * rnpgbe_alloc_q_vectors - Allocate memory for interrupt vectors
+ * @mucse: pointer to private structure
+ *
+ * Return: 0 on success, or -ENOMEM on allocation failure.
+ **/
+static int rnpgbe_alloc_q_vectors(struct mucse *mucse)
+{
+ int err, ring_cnt, v_remaining = mucse->num_q_vectors;
+ int r_remaining = min_t(int, mucse->num_tx_queues,
+ mucse->num_rx_queues);
+ int num_q_vectors = 0;
+ int eth_queue_idx = 0;
+ int vector_idx = 0;
+ int ring_step = 1;
+ int ring_idx = 0;
+
+ for (; r_remaining > 0 && v_remaining > 0; v_remaining--) {
+ ring_cnt = DIV_ROUND_UP(r_remaining, v_remaining);
+ err = rnpgbe_alloc_q_vector(mucse, eth_queue_idx,
+ vector_idx, ring_idx, ring_cnt,
+ ring_step);
+ if (err)
+ goto err_free_q_vector;
+ ring_idx += ring_step * ring_cnt;
+ eth_queue_idx += ring_cnt;
+ r_remaining -= ring_cnt;
+ num_q_vectors++;
+ vector_idx++;
+ }
+ /* Record the number of queue vectors actually allocated */
+ mucse->num_q_vectors = num_q_vectors;
+ mucse->num_tx_queues = eth_queue_idx;
+ mucse->num_rx_queues = eth_queue_idx;
+
+ return 0;
+
+err_free_q_vector:
+ mucse->num_tx_queues = 0;
+ mucse->num_rx_queues = 0;
+ mucse->num_q_vectors = 0;
+
+ while (vector_idx--)
+ rnpgbe_free_q_vector(mucse, vector_idx);
+
+ return err;
+}
+
+/**
+ * rnpgbe_reset_interrupt_capability - Reset irq capability setup
+ * @mucse: pointer to private structure
+ **/
+static void rnpgbe_reset_interrupt_capability(struct mucse *mucse)
+{
+ pci_free_irq_vectors(mucse->pdev);
+}
+
+/**
+ * rnpgbe_init_interrupt_scheme - Initialize the interrupt scheme
+ * @mucse: pointer to private structure
+ *
+ * Set up one TX/RX queue pair with a dedicated mailbox vector.
+ *
+ * Return: 0 on success, negative on failure
+ **/
+int rnpgbe_init_interrupt_scheme(struct mucse *mucse)
+{
+ int err;
+
+ rnpgbe_set_num_queues(mucse);
+
+ err = rnpgbe_set_interrupt_capability(mucse);
+ if (err)
+ return err;
+
+ err = rnpgbe_alloc_q_vectors(mucse);
+ if (err) {
+ rnpgbe_reset_interrupt_capability(mucse);
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * rnpgbe_free_q_vectors - Free memory allocated for interrupt vectors
+ * @mucse: pointer to private structure
+ *
+ * This function frees the memory allocated to the q_vectors. In addition if
+ * NAPI is enabled it will delete any references to the NAPI struct prior
+ * to freeing the q_vector.
+ **/
+static void rnpgbe_free_q_vectors(struct mucse *mucse)
+{
+ int vector_idx = mucse->num_q_vectors;
+
+ mucse->num_q_vectors = 0;
+
+ while (vector_idx--)
+ rnpgbe_free_q_vector(mucse, vector_idx);
+}
+
+/**
+ * rnpgbe_clear_interrupt_scheme - Clear the current interrupt scheme settings
+ * @mucse: pointer to private structure
+ *
+ * Clear interrupt specific resources and reset the structure
+ **/
+void rnpgbe_clear_interrupt_scheme(struct mucse *mucse)
+{
+ rnpgbe_free_q_vectors(mucse);
+ rnpgbe_reset_interrupt_capability(mucse);
+}
+
+/**
+ * rnpgbe_msix_clean_rings - MSI-x interrupt handler for ring irq
+ * @irq: interrupt number
+ * @data: private data
+ *
+ * rnpgbe_msix_clean_rings handle irq from ring, start napi
+ * Return: IRQ_HANDLED
+ **/
+static irqreturn_t rnpgbe_msix_clean_rings(int irq, void *data)
+{
+ struct mucse_q_vector *q_vector = (struct mucse_q_vector *)data;
+
+ rnpgbe_irq_disable_queues(q_vector);
+ napi_schedule_irqoff(&q_vector->napi);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * rnpgbe_request_irq - Initialize interrupts
+ * @mucse: pointer to private structure
+ *
+ * Attempts to configure interrupts using the best available
+ * capabilities of the hardware and kernel.
+ *
+ * Return: 0 on success, negative value on failure
+ **/
+int rnpgbe_request_irq(struct mucse *mucse)
+{
+ struct net_device *netdev = mucse->netdev;
+ struct pci_dev *pdev = mucse->pdev;
+ struct mucse_q_vector *q_vector;
+ int err, i;
+
+ for (i = 0; i < mucse->num_q_vectors; i++) {
+ q_vector = mucse->q_vector[i];
+
+ snprintf(q_vector->name, sizeof(q_vector->name),
+ "%s-%s-%d", netdev->name, "TxRx", i);
+
+ err = request_irq(pci_irq_vector(pdev, i + 1),
+ rnpgbe_msix_clean_rings, 0,
+ q_vector->name, q_vector);
+ if (err) {
+ dev_err(&pdev->dev, "MSI-X req err %d: %d\n",
+ i + 1, err);
+ goto err_free_irqs;
+ }
+ }
+
+ return 0;
+err_free_irqs:
+ while (i--) {
+ q_vector = mucse->q_vector[i];
+ synchronize_irq(pci_irq_vector(pdev, i + 1));
+ free_irq(pci_irq_vector(pdev, i + 1), q_vector);
+ }
+
+ return err;
+}
+
+/**
+ * rnpgbe_free_irq - Free interrupts
+ * @mucse: pointer to private structure
+ *
+ * Attempts to free interrupts according initialized type.
+ **/
+void rnpgbe_free_irq(struct mucse *mucse)
+{
+ struct pci_dev *pdev = mucse->pdev;
+ struct mucse_q_vector *q_vector;
+
+ for (int i = 0; i < mucse->num_q_vectors; i++) {
+ q_vector = mucse->q_vector[i];
+ if (!q_vector)
+ continue;
+
+ free_irq(pci_irq_vector(pdev, i + 1), q_vector);
+ }
+}
+
+/**
+ * rnpgbe_set_ring_vector - Set the ring_vector registers,
+ * mapping interrupt causes to vectors
+ * @mucse: pointer to private structure
+ * @queue: queue to map the corresponding interrupt to
+ * @vector: the vector num to map to the corresponding queue
+ *
+ */
+static void rnpgbe_set_ring_vector(struct mucse *mucse,
+ u8 queue, u8 vector)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 data;
+
+ data = hw->pfvfnum << 24;
+ data |= (vector << 8);
+ data |= vector;
+ writel(data, hw->ring_msix_base + RING_VECTOR(queue));
+}
+
+/**
+ * rnpgbe_configure_msix - Configure MSI-X hardware
+ * @mucse: pointer to private structure
+ *
+ * rnpgbe_configure_msix sets up the hardware to properly generate MSI-X
+ * interrupts.
+ **/
+static void rnpgbe_configure_msix(struct mucse *mucse)
+{
+ struct mucse_q_vector *q_vector;
+
+ for (int i = 0; i < mucse->num_q_vectors; i++) {
+ struct mucse_ring *ring;
+
+ q_vector = mucse->q_vector[i];
+ /* TX/RX pairs share vector registers.
+ * Update them through TX.
+ */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ rnpgbe_set_ring_vector(mucse, ring->rnpgbe_queue_idx,
+ q_vector->hw_vector);
+ }
+ }
+}
+
+static void rnpgbe_irq_enable(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_q_vectors; i++)
+ rnpgbe_irq_enable_queues(mucse->q_vector[i]);
+}
+
+/**
+ * rnpgbe_irq_disable - Mask queue interrupt generation on the NIC
+ * @mucse: board private structure
+ **/
+void rnpgbe_irq_disable(struct mucse *mucse)
+{
+ struct pci_dev *pdev = mucse->pdev;
+
+ for (int i = 0; i < mucse->num_q_vectors; i++) {
+ rnpgbe_irq_disable_queues(mucse->q_vector[i]);
+ synchronize_irq(pci_irq_vector(pdev, i + 1));
+ }
+}
+
+static void rnpgbe_napi_enable_all(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_q_vectors; i++)
+ napi_enable(&mucse->q_vector[i]->napi);
+}
+
+static void rnpgbe_napi_disable_all(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_q_vectors; i++)
+ napi_disable(&mucse->q_vector[i]->napi);
+}
+
+void rnpgbe_down(struct mucse *mucse)
+{
+ rnpgbe_irq_disable(mucse);
+ rnpgbe_napi_disable_all(mucse);
+ synchronize_net();
+ rnpgbe_irq_disable(mucse);
+}
+
+/**
+ * rnpgbe_up_complete - Final step for port up
+ * @mucse: pointer to private structure
+ **/
+void rnpgbe_up_complete(struct mucse *mucse)
+{
+ rnpgbe_configure_msix(mucse);
+ rnpgbe_napi_enable_all(mucse);
+ rnpgbe_irq_enable(mucse);
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
new file mode 100644
index 000000000000..d38c08df6b7c
--- /dev/null
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright(c) 2020 - 2025 Mucse Corporation. */
+
+#ifndef _RNPGBE_LIB_H
+#define _RNPGBE_LIB_H
+
+struct mucse;
+struct mucse_hw;
+
+#define RING_OFFSET(n) (0x1000 + 0x100 * (n))
+#define RNPGBE_DMA_INT_MASK 0x24
+#define TX_INT_MASK BIT(1)
+#define RX_INT_MASK BIT(0)
+#define INT_VALID (BIT(16) | BIT(17))
+#define RNPGBE_DMA_INT_TRIG 0x2c /* lost-interrupt recovery trigger */
+/* | 31:24 | .... | 15:8 | 7:0 | */
+/* | pfvfnum | | tx vector | rx vector | */
+#define RING_VECTOR(n) (0x04 * (n))
+
+#define mucse_for_each_ring(pos, head)\
+ for (typeof((head).ring) __pos = (head).ring;\
+ __pos ? ({ pos = __pos; 1; }) : 0;\
+ __pos = __pos->next)
+
+int rnpgbe_init_interrupt_scheme(struct mucse *mucse);
+void rnpgbe_clear_interrupt_scheme(struct mucse *mucse);
+int rnpgbe_request_mbx_irq(struct mucse *mucse);
+void rnpgbe_free_mbx_irq(struct mucse *mucse);
+int rnpgbe_request_irq(struct mucse *mucse);
+void rnpgbe_free_irq(struct mucse *mucse);
+void rnpgbe_irq_disable(struct mucse *mucse);
+void rnpgbe_down(struct mucse *mucse);
+void rnpgbe_up_complete(struct mucse *mucse);
+#endif
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index 70a2b0082ba8..debed2f3fe64 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -7,6 +7,7 @@
#include "rnpgbe.h"
#include "rnpgbe_hw.h"
+#include "rnpgbe_lib.h"
#include "rnpgbe_mbx_fw.h"
static const char rnpgbe_driver_name[] = "rnpgbe";
@@ -32,11 +33,28 @@ static struct pci_device_id rnpgbe_pci_tbl[] = {
* The open entry point is called when a network interface is made
* active by the system (IFF_UP).
*
- * Return: 0
+ * Return: 0 on success, negative value on failure
**/
static int rnpgbe_open(struct net_device *netdev)
{
+ struct mucse *mucse = netdev_priv(netdev);
+ int err;
+
+ err = rnpgbe_request_irq(mucse);
+ if (err)
+ return err;
+
+ err = netif_set_real_num_queues(netdev, mucse->num_tx_queues,
+ mucse->num_rx_queues);
+ if (err)
+ goto err_free_irqs;
+
+ rnpgbe_up_complete(mucse);
+
return 0;
+err_free_irqs:
+ rnpgbe_free_irq(mucse);
+ return err;
}
/**
@@ -50,6 +68,11 @@ static int rnpgbe_open(struct net_device *netdev)
**/
static int rnpgbe_close(struct net_device *netdev)
{
+ struct mucse *mucse = netdev_priv(netdev);
+
+ rnpgbe_down(mucse);
+ rnpgbe_free_irq(mucse);
+
return 0;
}
@@ -166,11 +189,33 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
goto err_powerdown;
}
+ err = rnpgbe_init_interrupt_scheme(mucse);
+ if (err) {
+ dev_err(&pdev->dev, "init interrupt failed %d\n", err);
+ goto err_powerdown;
+ }
+
+ err = netif_set_real_num_queues(netdev, mucse->num_tx_queues,
+ mucse->num_rx_queues);
+ if (err)
+ goto err_clear_interrupt;
+
+ err = rnpgbe_request_mbx_irq(mucse);
+ if (err) {
+ dev_err(&pdev->dev, "register mbx irq failed %d\n", err);
+ goto err_clear_interrupt;
+ }
+
err = register_netdev(netdev);
if (err)
- goto err_powerdown;
+ goto err_remove_mbx;
return 0;
+
+err_remove_mbx:
+ rnpgbe_free_mbx_irq(mucse);
+err_clear_interrupt:
+ rnpgbe_clear_interrupt_scheme(mucse);
err_powerdown:
/* notify powerdown only powerup ok */
if (!err_notify) {
@@ -253,9 +298,11 @@ static void rnpgbe_rm_adapter(struct pci_dev *pdev)
return;
netdev = mucse->netdev;
unregister_netdev(netdev);
+ rnpgbe_free_mbx_irq(mucse);
err = rnpgbe_send_notify(hw, false, mucse_fw_powerup);
if (err)
dev_warn(&pdev->dev, "Send powerdown to hw failed %d\n", err);
+ rnpgbe_clear_interrupt_scheme(mucse);
free_netdev(netdev);
}
@@ -287,8 +334,11 @@ static void rnpgbe_dev_shutdown(struct pci_dev *pdev)
rtnl_lock();
netif_device_detach(netdev);
if (netif_running(netdev))
- rnpgbe_close(netdev);
+ dev_close(netdev);
rtnl_unlock();
+
+ rnpgbe_free_mbx_irq(mucse);
+ rnpgbe_clear_interrupt_scheme(mucse);
pci_disable_device(pdev);
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
index c46408698263..2310f01df1f5 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
@@ -84,7 +84,7 @@ static u32 mucse_mbx_get_lock_pf(struct mucse_hw *hw)
* @hw: pointer to the HW structure
*
* Pair with mucse_release_mbx_lock_pf()
- * This function maybe used in an irq handler.
+ * All mailbox access runs in process context.
*
* Return: 0 on success, negative errno on failure
**/
@@ -93,11 +93,11 @@ static int mucse_obtain_mbx_lock_pf(struct mucse_hw *hw)
struct mucse_mbx_info *mbx = &hw->mbx;
u32 val;
- return read_poll_timeout_atomic(mucse_mbx_get_lock_pf,
- val, val & MUCSE_MBX_PFU,
- mbx->delay_us,
- mbx->timeout_us,
- false, hw);
+ return read_poll_timeout(mucse_mbx_get_lock_pf,
+ val, val & MUCSE_MBX_PFU,
+ mbx->delay_us,
+ mbx->timeout_us,
+ false, hw);
}
/**
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
index 5ba74997beac..e28f3193aa85 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
@@ -197,3 +197,14 @@ int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
return 0;
}
+
+/**
+ * mucse_fw_irq_handler - Deferred firmware mailbox notification hook
+ * @hw: pointer to the HW structure
+ *
+ * No asynchronous firmware events are enabled until link status support is
+ * added.
+ **/
+void mucse_fw_irq_handler(struct mucse_hw *hw)
+{
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
index fe996aeffc4d..a6bf5de55aa3 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
@@ -99,4 +99,5 @@ int mucse_mbx_powerup(struct mucse_hw *hw, bool is_powerup);
int mucse_mbx_reset_hw(struct mucse_hw *hw);
int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
u8 *mac_addr, int port);
+void mucse_fw_irq_handler(struct mucse_hw *hw);
#endif /* _RNPGBE_MBX_FW_H */
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-09-20 9:26 ` [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling Dong Yibo
@ 2026-09-20 9:26 ` Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support Dong Yibo
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:26 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Add the basic TX path for the RNPGBE driver.
Introduce the TX descriptor structure and buffer management, the
rnpgbe_xmit_frame_ring() transmit function, TX ring allocation and
teardown, and TX completion handling in rnpgbe_clean_tx_irq(). Track TX
packet and byte statistics, and report dropped and tx_dropped counts
through ndo_get_stats64().
Configure streaming and coherent DMA masks for the hardware's 56-bit
DMA addressing capability. Add cycles_per_us to struct mucse_hw to scale
the TX interrupt timer, and enable NETIF_F_HIGHDMA when the DMA mask
allows it.
A TX DMA idle timeout is terminal for the current driver instance. The
hardware stops PCIe DMA requests, allowing cleanup to release DMA
resources. Recovery requires a chip-level reset and driver rebind.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 86 ++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 4 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 7 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 709 +++++++++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 30 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 63 +-
6 files changed, 875 insertions(+), 24 deletions(-)
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index 40e41521a279..864cbf0caf44 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -7,6 +7,7 @@
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
+#include <linux/rcupdate.h>
#include <linux/if.h>
#include <linux/workqueue.h>
@@ -44,20 +45,85 @@ struct mucse_hw {
struct pci_dev *pdev;
struct mucse_mbx_info mbx;
int port;
+ u16 cycles_per_us;
u8 pfvfnum;
};
+struct rnpgbe_tx_desc {
+ __le64 pkt_addr; /* Packet buffer address */
+ union {
+ __le64 vlan_cmd_bsz;
+ struct {
+ __le32 blen_mac_ip_len;
+ __le32 vlan_cmd; /* vlan & cmd status */
+ };
+ };
+#define M_TXD_CMD_RS 0x040000 /* Report Status */
+#define M_TXD_STAT_DD 0x020000 /* Descriptor Done */
+#define M_TXD_CMD_EOP 0x010000 /* End of Packet */
+};
+
+#define M_TX_DESC(R, i) (&(((struct rnpgbe_tx_desc *)((R)->desc))[i]))
+
+struct mucse_tx_buffer {
+ struct rnpgbe_tx_desc *next_to_watch;
+ struct sk_buff *skb;
+ unsigned int bytecount;
+ unsigned short gso_segs;
+ DEFINE_DMA_UNMAP_ADDR(dma);
+ DEFINE_DMA_UNMAP_LEN(len);
+ bool mapped_as_page; /* true if dma was mapped with dma_map_page */
+};
+
+struct mucse_queue_stats {
+ u64 packets;
+ u64 bytes;
+ atomic64_t dropped;
+};
+
struct mucse_ring {
struct mucse_ring *next;
struct mucse_q_vector *q_vector;
+ struct net_device *netdev;
+ struct device *dev;
+ void *desc;
+ struct mucse_tx_buffer *tx_buffer_info;
void __iomem *ring_addr;
+ void __iomem *tail;
void __iomem *irq_mask;
void __iomem *trig;
u8 queue_index;
/* hw ring idx */
u8 rnpgbe_queue_idx;
+ u8 pfvfnum;
+ u16 count;
+ u16 next_to_use;
+ u16 next_to_clean;
+ dma_addr_t dma;
+ unsigned int size;
+ struct mucse_queue_stats stats;
+ struct u64_stats_sync syncp;
} ____cacheline_internodealigned_in_smp;
+static inline u16 mucse_desc_unused(struct mucse_ring *ring)
+{
+ u16 ntc = ring->next_to_clean;
+ u16 ntu = ring->next_to_use;
+
+ return ((ntc > ntu) ? 0 : ring->count) + ntc - ntu - 1;
+}
+
+static inline __le64 build_ctob(u32 vlan_cmd, u32 mac_ip_len, u32 size)
+{
+ return cpu_to_le64(((u64)vlan_cmd << 32) | ((u64)mac_ip_len << 16) |
+ ((u64)size));
+}
+
+static inline struct netdev_queue *txring_txq(const struct mucse_ring *ring)
+{
+ return netdev_get_tx_queue(ring->netdev, ring->queue_index);
+}
+
struct mucse_ring_container {
struct mucse_ring *ring;
u16 count;
@@ -69,31 +135,37 @@ struct mucse_q_vector {
int hw_vector;
struct mucse_ring_container rx, tx;
struct napi_struct napi;
+ struct rcu_head rcu;
char name[IFNAMSIZ + 18];
/* for dynamic allocation of rings associated with this q_vector */
struct mucse_ring ring[] ____cacheline_internodealigned_in_smp;
};
-struct mucse_stats {
- u64 tx_dropped;
-};
-
#define MAX_Q_VECTORS 8
+#define M_DEFAULT_TXD 512
+#define M_DEFAULT_TX_WORK 256
+
+enum mucse_state_t {
+ __MUCSE_AXI_FAULT,
+};
+
struct mucse {
struct net_device *netdev;
struct pci_dev *pdev;
struct mucse_hw hw;
- struct mucse_stats stats;
struct mucse_ring *tx_ring[RNPGBE_MAX_QUEUES]
____cacheline_aligned_in_smp;
struct mucse_ring *rx_ring[RNPGBE_MAX_QUEUES]
____cacheline_aligned_in_smp;
struct mucse_q_vector *q_vector[MAX_Q_VECTORS];
+ int tx_ring_item_count;
+ int tx_work_limit;
int num_tx_queues;
int num_q_vectors;
int num_rx_queues;
char mbx_name[32];
+ unsigned long state;
struct work_struct mbx_work;
};
@@ -115,4 +187,8 @@ int rnpgbe_init_hw(struct mucse_hw *hw, int board_type);
writel((val), (hw)->hw_addr + (reg))
#define mucse_hw_rd32(hw, reg) \
readl((hw)->hw_addr + (reg))
+#define mucse_ring_wr32(ring, reg, val) \
+ writel((val), (ring)->ring_addr + (reg))
+#define mucse_ring_rd32(ring, reg) \
+ readl((ring)->ring_addr + (reg))
#endif /* _RNPGBE_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
index 921cc325a991..291e77d573fe 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
@@ -93,6 +93,8 @@ static void rnpgbe_init_n500(struct mucse_hw *hw)
mbx->fwpf_ctrl_base = MUCSE_N500_FWPF_CTRL_BASE;
mbx->fwpf_shm_base = MUCSE_N500_FWPF_SHM_BASE;
+
+ hw->cycles_per_us = M_DEFAULT_N500_MHZ;
}
/**
@@ -110,6 +112,8 @@ static void rnpgbe_init_n210(struct mucse_hw *hw)
mbx->fwpf_ctrl_base = MUCSE_N210_FWPF_CTRL_BASE;
mbx->fwpf_shm_base = MUCSE_N210_FWPF_SHM_BASE;
+
+ hw->cycles_per_us = M_DEFAULT_N210_MHZ;
}
/**
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
index 0dce78e4a91b..6dc29ebe6fa7 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -7,13 +7,20 @@
#define MUCSE_N500_FWPF_CTRL_BASE 0x28b00
#define MUCSE_N500_FWPF_SHM_BASE 0x2d000
#define MUCSE_N500_RING_MSIX_BASE 0x28700
+#define M_DEFAULT_N500_MHZ 125
#define MUCSE_GBE_PFFW_MBX_CTRL_OFFSET 0x5500
#define MUCSE_GBE_FWPF_MBX_MASK_OFFSET 0x5700
#define MUCSE_N210_FWPF_CTRL_BASE 0x29400
#define MUCSE_N210_FWPF_SHM_BASE 0x2d900
#define MUCSE_N210_RING_MSIX_BASE 0x29000
+#define M_DEFAULT_N210_MHZ 62
+#define RNPGBE_DMA_STATUS 0x0008
+#define TX_AXI_RW_EN 0xc
+/* DMA_STATUS_REG[23:20]: tx_wr, tx_rd, rx_wr, rx_rd done status. */
+#define RNPGBE_DMA_TX_STATUS GENMASK_U32(23, 22)
#define RNPGBE_DMA_AXI_EN 0x0010
+#define RNPGBE_TX_MIN_PKT_LEN 33
#define RNPGBE_MAX_QUEUES 8
#endif /* _RNPGBE_HW_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
index 9196fa130ca9..af5cef987051 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -3,6 +3,9 @@
#include <linux/pci.h>
#include <linux/netdevice.h>
+#include <linux/iopoll.h>
+#include <linux/vmalloc.h>
+#include <net/netdev_queues.h>
#include "rnpgbe_lib.h"
#include "rnpgbe.h"
@@ -43,7 +46,6 @@ static void rnpgbe_irq_disable_queues(struct mucse_q_vector *q_vector)
writel(INT_VALID, ring->trig);
writel((RX_INT_MASK | TX_INT_MASK), ring->irq_mask);
}
-
/* flush posted writes to ensure hardware sees the mask */
if (q_vector->tx.ring)
readl(q_vector->tx.ring->irq_mask);
@@ -65,26 +67,143 @@ static void rnpgbe_irq_enable_queues(struct mucse_q_vector *q_vector)
}
}
+/**
+ * rnpgbe_clean_tx_irq - Reclaim resources after transmit completes
+ * @q_vector: structure containing interrupt and ring information
+ * @tx_ring: tx ring to clean
+ * @napi_budget: Used to determine if we are in netpoll
+ *
+ * Return: true if cleaning completed within the budget, otherwise false
+ **/
+static bool rnpgbe_clean_tx_irq(struct mucse_q_vector *q_vector,
+ struct mucse_ring *tx_ring,
+ int napi_budget)
+{
+ int budget = q_vector->mucse->tx_work_limit;
+ u64 total_bytes = 0, total_packets = 0;
+ struct mucse *mucse = q_vector->mucse;
+ struct mucse_tx_buffer *tx_buffer;
+ struct rnpgbe_tx_desc *tx_desc;
+ int i = tx_ring->next_to_clean;
+
+ tx_buffer = &tx_ring->tx_buffer_info[i];
+ tx_desc = M_TX_DESC(tx_ring, i);
+ i -= tx_ring->count;
+
+ do {
+ struct rnpgbe_tx_desc *eop_desc = tx_buffer->next_to_watch;
+
+ /* if next_to_watch is not set then there is no work pending */
+ if (!eop_desc)
+ break;
+
+ /* prevent any other reads prior to eop_desc */
+ dma_rmb();
+
+ /* if eop DD is not set pending work has not been completed */
+ if (!(eop_desc->vlan_cmd & cpu_to_le32(M_TXD_STAT_DD)))
+ break;
+ /* clear next_to_watch to prevent false hangs */
+ tx_buffer->next_to_watch = NULL;
+ total_bytes += tx_buffer->bytecount;
+ total_packets += tx_buffer->gso_segs;
+ napi_consume_skb(tx_buffer->skb, napi_budget);
+ if (tx_buffer->mapped_as_page) {
+ dma_unmap_page(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ } else {
+ dma_unmap_single(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ }
+ tx_buffer->skb = NULL;
+ dma_unmap_len_set(tx_buffer, len, 0);
+
+ /* unmap remaining buffers */
+ while (tx_desc != eop_desc) {
+ tx_buffer++;
+ tx_desc++;
+ i++;
+ if (unlikely(!i)) {
+ i -= tx_ring->count;
+ tx_buffer = tx_ring->tx_buffer_info;
+ tx_desc = M_TX_DESC(tx_ring, 0);
+ }
+
+ /* unmap any remaining paged data */
+ if (dma_unmap_len(tx_buffer, len)) {
+ dma_unmap_page(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ dma_unmap_len_set(tx_buffer, len, 0);
+ }
+ }
+
+ /* move us one more past the eop_desc for start of next pkt */
+ tx_buffer++;
+ tx_desc++;
+ i++;
+ if (unlikely(!i)) {
+ i -= tx_ring->count;
+ tx_buffer = tx_ring->tx_buffer_info;
+ tx_desc = M_TX_DESC(tx_ring, 0);
+ }
+
+ prefetch(tx_desc);
+ budget--;
+ } while (likely(budget > 0));
+
+ i += tx_ring->count;
+ tx_ring->next_to_clean = i;
+ u64_stats_update_begin(&tx_ring->syncp);
+ tx_ring->stats.bytes += total_bytes;
+ tx_ring->stats.packets += total_packets;
+ u64_stats_update_end(&tx_ring->syncp);
+
+#define TX_WAKE_THRESHOLD (DESC_NEEDED * 2)
+ __netif_txq_completed_wake(txring_txq(tx_ring),
+ total_packets, total_bytes,
+ mucse_desc_unused(tx_ring),
+ TX_WAKE_THRESHOLD,
+ !netif_running(mucse->netdev));
+
+ return !!budget;
+}
+
/**
* rnpgbe_poll - NAPI polling callback
* @napi: structure for representing this polling device
* @budget: polling budget
*
- * Complete NAPI polling and re-enable queue interrupts. Ring cleaning is
- * added when TX and RX support is enabled.
+ * Clean completed TX packets and complete NAPI polling when all queues are
+ * clean.
*
- * Return: 0
+ * Return: 0 if all TX queues are clean, otherwise @budget
**/
static int rnpgbe_poll(struct napi_struct *napi, int budget)
{
struct mucse_q_vector *q_vector =
container_of(napi, struct mucse_q_vector, napi);
+ bool clean_complete = true;
+ struct mucse_ring *ring;
int work_done = 0;
+ mucse_for_each_ring(ring, q_vector->tx) {
+ if (!rnpgbe_clean_tx_irq(q_vector, ring, budget))
+ clean_complete = false;
+ }
+
/* Exit if we are called by netpoll */
if (unlikely(!budget))
return 0;
+ if (!clean_complete)
+ return budget;
+
if (likely(napi_complete_done(napi, work_done)))
rnpgbe_irq_enable_queues(q_vector);
@@ -228,14 +347,19 @@ static int rnpgbe_alloc_q_vector(struct mucse *mucse,
ring = q_vector->ring;
for (idx = 0; idx < txr_count; idx++) {
+ ring->dev = &mucse->pdev->dev;
mucse_add_ring(ring, &q_vector->tx);
+ ring->count = mucse->tx_ring_item_count;
+ ring->netdev = mucse->netdev;
ring->queue_index = eth_queue_idx + idx;
ring->rnpgbe_queue_idx = txr_idx;
ring->ring_addr = hw->hw_addr + RING_OFFSET(txr_idx);
ring->irq_mask = ring->ring_addr + RNPGBE_DMA_INT_MASK;
ring->trig = ring->ring_addr + RNPGBE_DMA_INT_TRIG;
ring->q_vector = q_vector;
- mucse->tx_ring[ring->queue_index] = ring;
+ ring->pfvfnum = hw->pfvfnum;
+ u64_stats_init(&ring->syncp);
+ WRITE_ONCE(mucse->tx_ring[ring->queue_index], ring);
txr_idx += step;
ring++;
}
@@ -248,7 +372,7 @@ static int rnpgbe_alloc_q_vector(struct mucse *mucse,
ring->irq_mask = ring->ring_addr + RNPGBE_DMA_INT_MASK;
ring->trig = ring->ring_addr + RNPGBE_DMA_INT_TRIG;
ring->q_vector = q_vector;
- mucse->rx_ring[ring->queue_index] = ring;
+ WRITE_ONCE(mucse->rx_ring[ring->queue_index], ring);
rxr_idx += step;
ring++;
}
@@ -271,12 +395,12 @@ static void rnpgbe_free_q_vector(struct mucse *mucse, int vector_idx)
struct mucse_ring *ring;
mucse_for_each_ring(ring, q_vector->tx)
- mucse->tx_ring[ring->queue_index] = NULL;
+ WRITE_ONCE(mucse->tx_ring[ring->queue_index], NULL);
mucse_for_each_ring(ring, q_vector->rx)
- mucse->rx_ring[ring->queue_index] = NULL;
+ WRITE_ONCE(mucse->rx_ring[ring->queue_index], NULL);
mucse->q_vector[vector_idx] = NULL;
netif_napi_del(&q_vector->napi);
- kfree(q_vector);
+ kfree_rcu(q_vector, rcu);
}
/**
@@ -551,12 +675,173 @@ static void rnpgbe_napi_disable_all(struct mucse *mucse)
napi_disable(&mucse->q_vector[i]->napi);
}
+static void rnpgbe_stop_tx_ring(struct mucse_ring *tx_ring)
+{
+ if (!tx_ring->tx_buffer_info)
+ return;
+
+ /* Stop hw. No new descriptors are fetched after TX_START=0.
+ * DMA for descriptors fetched before the stop may still be in flight.
+ */
+ mucse_ring_wr32(tx_ring, RNPGBE_TX_START, 0);
+ /* Flush posted write to ensure hardware sees TX_START=0 */
+ (void)mucse_ring_rd32(tx_ring, RNPGBE_TX_START);
+}
+
+static int rnpgbe_wait_tx_dma_idle(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_status;
+ int err;
+
+ /* A timeout indicates a terminal AXI fault. Hardware stops all PCIe
+ * DMA requests in this state, including requests accepted before the
+ * fault. Teardown may therefore release mappings and descriptor memory.
+ * Recovery requires a chip-level reset.
+ */
+ err = readl_poll_timeout(hw->hw_addr + RNPGBE_DMA_STATUS,
+ dma_status,
+ (dma_status & RNPGBE_DMA_TX_STATUS) ==
+ RNPGBE_DMA_TX_STATUS,
+ 10, 100000);
+ if (err) {
+ set_bit(__MUCSE_AXI_FAULT, &mucse->state);
+ dev_err(&mucse->pdev->dev,
+ "TX DMA failed to quiesce, status %#x\n", dma_status);
+ }
+
+ return err;
+}
+
+/**
+ * rnpgbe_stop_all_tx_rings - Stop TX DMA on all queues
+ * @mucse: board private structure
+ *
+ * Return: 0 when TX DMA was quiesced, negative errno otherwise
+ **/
+static int rnpgbe_stop_all_tx_rings(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+ int err = 0;
+
+ for (int i = 0; i < mucse->num_tx_queues; i++)
+ rnpgbe_stop_tx_ring(mucse->tx_ring[i]);
+
+ if (mucse->num_tx_queues &&
+ !test_bit(__MUCSE_AXI_FAULT, &mucse->state))
+ err = rnpgbe_wait_tx_dma_idle(mucse);
+
+ dma_axi_ctl = mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+ dma_axi_ctl &= ~TX_AXI_RW_EN;
+ mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
+ /* Flush the posted write before continuing. */
+ (void)mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+
+ if (!test_bit(__MUCSE_AXI_FAULT, &mucse->state))
+ return 0;
+
+ return err ? err : -EIO;
+}
+
+/**
+ * rnpgbe_clean_tx_ring - Free Tx Buffers
+ * @tx_ring: ring to be cleaned
+ **/
+static void rnpgbe_clean_tx_ring(struct mucse_ring *tx_ring)
+{
+ struct mucse_tx_buffer *tx_buffer;
+ u16 i = tx_ring->next_to_clean;
+ unsigned long size;
+
+ /* ring already cleared, nothing to do */
+ if (!tx_ring->tx_buffer_info)
+ return;
+
+ tx_buffer = &tx_ring->tx_buffer_info[i];
+
+ while (i != tx_ring->next_to_use) {
+ struct rnpgbe_tx_desc *eop_desc, *tx_desc;
+
+ dev_kfree_skb_any(tx_buffer->skb);
+ /* unmap skb header data */
+ if (dma_unmap_len(tx_buffer, len)) {
+ if (tx_buffer->mapped_as_page) {
+ dma_unmap_page(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ } else {
+ dma_unmap_single(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ }
+ }
+ eop_desc = tx_buffer->next_to_watch;
+ tx_desc = M_TX_DESC(tx_ring, i);
+ /* unmap remaining buffers */
+ while (tx_desc != eop_desc) {
+ tx_buffer++;
+ tx_desc++;
+ i++;
+ if (unlikely(i == tx_ring->count)) {
+ i = 0;
+ tx_buffer = tx_ring->tx_buffer_info;
+ tx_desc = M_TX_DESC(tx_ring, 0);
+ }
+
+ /* unmap any remaining paged data */
+ if (dma_unmap_len(tx_buffer, len))
+ dma_unmap_page(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ }
+ /* move us one more past the eop_desc for start of next pkt */
+ tx_buffer++;
+ i++;
+ if (unlikely(i == tx_ring->count)) {
+ i = 0;
+ tx_buffer = tx_ring->tx_buffer_info;
+ }
+ }
+
+ netdev_tx_reset_queue(txring_txq(tx_ring));
+ size = sizeof(struct mucse_tx_buffer) * tx_ring->count;
+ memset(tx_ring->tx_buffer_info, 0, size);
+ /* Zero out the descriptor ring */
+ memset(tx_ring->desc, 0, tx_ring->size);
+ tx_ring->next_to_use = 0;
+ tx_ring->next_to_clean = 0;
+}
+
+/**
+ * rnpgbe_clean_all_tx_rings - Stop TX DMA and free Tx buffers for all queues
+ * @mucse: board private structure
+ **/
+void rnpgbe_clean_all_tx_rings(struct mucse *mucse)
+{
+ /* rnpgbe_stop_all_tx_rings() logs and latches any AXI fault. Hardware
+ * stops DMA requests in this state, so cleanup intentionally ignores
+ * an error and releases the mappings and descriptor memory.
+ */
+ rnpgbe_stop_all_tx_rings(mucse);
+
+ for (int i = 0; i < mucse->num_tx_queues; i++)
+ rnpgbe_clean_tx_ring(mucse->tx_ring[i]);
+}
+
void rnpgbe_down(struct mucse *mucse)
{
+ struct net_device *netdev = mucse->netdev;
+
rnpgbe_irq_disable(mucse);
rnpgbe_napi_disable_all(mucse);
synchronize_net();
rnpgbe_irq_disable(mucse);
+ netif_tx_disable(netdev);
+ rnpgbe_clean_all_tx_rings(mucse);
}
/**
@@ -565,7 +850,413 @@ void rnpgbe_down(struct mucse *mucse)
**/
void rnpgbe_up_complete(struct mucse *mucse)
{
+ struct net_device *netdev = mucse->netdev;
+
rnpgbe_configure_msix(mucse);
rnpgbe_napi_enable_all(mucse);
rnpgbe_irq_enable(mucse);
+ netif_tx_start_all_queues(netdev);
+}
+
+/**
+ * rnpgbe_free_tx_resources - Free Tx Resources per Queue
+ * @tx_ring: tx descriptor ring for a specific queue
+ *
+ * Free all transmit software resources
+ **/
+static void rnpgbe_free_tx_resources(struct mucse_ring *tx_ring)
+{
+ vfree(tx_ring->tx_buffer_info);
+ tx_ring->tx_buffer_info = NULL;
+ /* if not set, then don't free */
+ if (!tx_ring->desc)
+ return;
+
+ dma_free_coherent(tx_ring->dev, tx_ring->size, tx_ring->desc,
+ tx_ring->dma);
+ tx_ring->desc = NULL;
+}
+
+/**
+ * rnpgbe_setup_tx_resources - allocate Tx resources (Descriptors)
+ * @tx_ring: tx descriptor ring (for a specific queue) to setup
+ *
+ * Return: 0 on success, negative on failure
+ **/
+static int rnpgbe_setup_tx_resources(struct mucse_ring *tx_ring)
+{
+ struct device *dev = tx_ring->dev;
+ int size;
+
+ size = sizeof(struct mucse_tx_buffer) * tx_ring->count;
+
+ tx_ring->tx_buffer_info = vzalloc(size);
+ if (!tx_ring->tx_buffer_info)
+ goto err_return;
+ /* round up to nearest 4K */
+ tx_ring->size = tx_ring->count * sizeof(struct rnpgbe_tx_desc);
+ tx_ring->size = ALIGN(tx_ring->size, 4096);
+ tx_ring->desc = dma_alloc_coherent(dev, tx_ring->size, &tx_ring->dma,
+ GFP_KERNEL);
+ if (!tx_ring->desc)
+ goto err_free_buffer;
+
+ tx_ring->next_to_use = 0;
+ tx_ring->next_to_clean = 0;
+
+ return 0;
+
+err_free_buffer:
+ vfree(tx_ring->tx_buffer_info);
+err_return:
+ tx_ring->tx_buffer_info = NULL;
+ return -ENOMEM;
+}
+
+/**
+ * rnpgbe_configure_tx_ring - Configure Tx ring after Reset
+ * @mucse: pointer to private structure
+ * @ring: structure containing ring specific data
+ *
+ * Configure the Tx descriptor ring after a reset.
+ **/
+static void rnpgbe_configure_tx_ring(struct mucse *mucse,
+ struct mucse_ring *ring)
+{
+ struct mucse_hw *hw = &mucse->hw;
+
+ mucse_ring_wr32(ring, RNPGBE_TX_BASE_ADDR_LO, (u32)ring->dma);
+ mucse_ring_wr32(ring, RNPGBE_TX_BASE_ADDR_HI,
+ (u32)(((u64)ring->dma) >> 32) |
+ ((u32)hw->pfvfnum << 24));
+ mucse_ring_wr32(ring, RNPGBE_TX_LEN, ring->count);
+ ring->next_to_clean = mucse_ring_rd32(ring, RNPGBE_TX_HEAD) %
+ ring->count;
+ ring->next_to_use = ring->next_to_clean;
+ ring->tail = ring->ring_addr + RNPGBE_TX_TAIL;
+ writel(ring->next_to_use, ring->tail);
+ mucse_ring_wr32(ring, RNPGBE_TX_FETCH_CTRL, M_DEFAULT_TX_FETCH);
+ mucse_ring_wr32(ring, RNPGBE_TX_INT_TIMER,
+ M_DEFAULT_INT_TIMER * hw->cycles_per_us);
+ mucse_ring_wr32(ring, RNPGBE_TX_INT_PKTCNT, M_DEFAULT_INT_PKTCNT);
+}
+
+/**
+ * rnpgbe_configure_tx - Configure Transmit Unit after Reset
+ * @mucse: pointer to private structure
+ *
+ * Configure the Tx DMA after a reset.
+ *
+ * Return: 0 on success, negative errno if TX DMA did not quiesce
+ **/
+int rnpgbe_configure_tx(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 i, dma_axi_ctl;
+ int err;
+
+ err = rnpgbe_stop_all_tx_rings(mucse);
+ if (err)
+ return err;
+
+ for (i = 0; i < mucse->num_tx_queues; i++)
+ rnpgbe_configure_tx_ring(mucse, mucse->tx_ring[i]);
+
+ /* Ensure all ring configuration is visible before enabling TX DMA. */
+ wmb();
+ dma_axi_ctl = mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+ dma_axi_ctl |= TX_AXI_RW_EN;
+ mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
+ /* Ensure TX_AXI_RW_EN is visible before starting the rings. */
+ (void)mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+
+ for (i = 0; i < mucse->num_tx_queues; i++)
+ mucse_ring_wr32(mucse->tx_ring[i], RNPGBE_TX_START, 1);
+
+ return 0;
+}
+
+/**
+ * rnpgbe_setup_all_tx_resources - allocate all queues Tx resources
+ * @mucse: pointer to private structure
+ *
+ * Allocate memory for tx_ring.
+ *
+ * Return: 0 on success, negative on failure
+ **/
+int rnpgbe_setup_all_tx_resources(struct mucse *mucse)
+{
+ int i, err = 0;
+
+ for (i = 0; i < mucse->num_tx_queues; i++) {
+ err = rnpgbe_setup_tx_resources(mucse->tx_ring[i]);
+ if (!err)
+ continue;
+
+ goto err_free_res;
+ }
+
+ return 0;
+err_free_res:
+ while (i--)
+ rnpgbe_free_tx_resources(mucse->tx_ring[i]);
+ return err;
+}
+
+/**
+ * rnpgbe_free_all_tx_resources - Free Tx Resources for All Queues
+ * @mucse: pointer to private structure
+ *
+ * Free all transmit software resources
+ **/
+void rnpgbe_free_all_tx_resources(struct mucse *mucse)
+{
+ for (int i = 0; i < (mucse->num_tx_queues); i++)
+ rnpgbe_free_tx_resources(mucse->tx_ring[i]);
+}
+
+static int rnpgbe_tx_map(struct mucse_ring *tx_ring,
+ struct mucse_tx_buffer *first, u32 mac_ip_len,
+ u32 tx_flags)
+{
+ struct mucse_tx_buffer *tx_buffer;
+ struct rnpgbe_tx_desc *tx_desc;
+ struct skb_shared_info *shinfo;
+ unsigned int data_len, size;
+ struct sk_buff *skb;
+ skb_frag_t *frag;
+ dma_addr_t dma;
+ int nr_frags;
+ int frag_idx;
+ /* Hardware requires this in the upper 8 bits of
+ * the descriptor address
+ */
+ u64 fun_id;
+ u16 i;
+
+ skb = first->skb;
+ shinfo = skb_shinfo(skb);
+ fun_id = (u64)tx_ring->pfvfnum << 56;
+ nr_frags = shinfo->nr_frags;
+ i = tx_ring->next_to_use;
+ frag_idx = 0;
+ tx_desc = M_TX_DESC(tx_ring, i);
+ size = skb_headlen(skb);
+ data_len = skb->data_len;
+
+ if (size) {
+ dma = dma_map_single(tx_ring->dev, skb->data, size,
+ DMA_TO_DEVICE);
+ first->mapped_as_page = false;
+ } else if (data_len) {
+ while (frag_idx < nr_frags &&
+ !skb_frag_size(&shinfo->frags[frag_idx]))
+ frag_idx++;
+
+ if (frag_idx == nr_frags)
+ goto err_unmap;
+
+ frag = &shinfo->frags[frag_idx];
+ size = skb_frag_size(frag);
+
+ dma = skb_frag_dma_map(tx_ring->dev, frag, 0,
+ size, DMA_TO_DEVICE);
+ first->mapped_as_page = true;
+ data_len -= size;
+ frag_idx++;
+ } else {
+ goto err_unmap;
+ }
+
+ tx_buffer = first;
+
+ dma_unmap_len_set(tx_buffer, len, 0);
+ dma_unmap_addr_set(tx_buffer, dma, 0);
+
+ for (;; frag_idx++) {
+ if (dma_mapping_error(tx_ring->dev, dma))
+ goto err_unmap;
+
+ /* record length, and DMA address */
+ dma_unmap_len_set(tx_buffer, len, size);
+ dma_unmap_addr_set(tx_buffer, dma, dma);
+
+ tx_desc->pkt_addr = cpu_to_le64(dma | fun_id);
+
+ while (unlikely(size > M_MAX_DATA_PER_TXD)) {
+ tx_desc->vlan_cmd_bsz = build_ctob(tx_flags,
+ mac_ip_len,
+ M_MAX_DATA_PER_TXD);
+ i++;
+ tx_desc++;
+ if (i == tx_ring->count) {
+ tx_desc = M_TX_DESC(tx_ring, 0);
+ i = 0;
+ }
+
+ tx_buffer = &tx_ring->tx_buffer_info[i];
+ dma_unmap_len_set(tx_buffer, len, 0);
+ dma += M_MAX_DATA_PER_TXD;
+ size -= M_MAX_DATA_PER_TXD;
+ tx_desc->pkt_addr = cpu_to_le64(dma | fun_id);
+ }
+
+ if (likely(!data_len))
+ break;
+ tx_desc->vlan_cmd_bsz = build_ctob(tx_flags, mac_ip_len, size);
+ i++;
+ tx_desc++;
+ if (i == tx_ring->count) {
+ tx_desc = M_TX_DESC(tx_ring, 0);
+ i = 0;
+ }
+
+ while (frag_idx < nr_frags &&
+ !skb_frag_size(&shinfo->frags[frag_idx]))
+ frag_idx++;
+
+ if (frag_idx == nr_frags)
+ goto err_unmap;
+
+ frag = &shinfo->frags[frag_idx];
+ size = skb_frag_size(frag);
+ data_len -= size;
+ dma = skb_frag_dma_map(tx_ring->dev, frag, 0, size,
+ DMA_TO_DEVICE);
+ tx_buffer = &tx_ring->tx_buffer_info[i];
+ tx_buffer->mapped_as_page = true;
+ }
+
+ /* write last descriptor with RS and EOP bits */
+ tx_desc->vlan_cmd_bsz = build_ctob(tx_flags | M_TXD_CMD_EOP |
+ M_TXD_CMD_RS,
+ mac_ip_len, size);
+
+ /* Force memory writes to complete before letting h/w know there
+ * are new descriptors to fetch. (Only applicable for weak-ordered
+ * memory model archs, such as IA-64).
+ *
+ * We also need this memory barrier to make certain all of the
+ * status bits have been updated before next_to_watch is written.
+ */
+ dma_wmb();
+ /* set next_to_watch value indicating a packet is present */
+ first->next_to_watch = tx_desc;
+ i++;
+ if (i == tx_ring->count)
+ i = 0;
+ tx_ring->next_to_use = i;
+ skb_tx_timestamp(skb);
+ netdev_tx_sent_queue(txring_txq(tx_ring), first->bytecount);
+ /* notify HW of packet */
+ writel(i, tx_ring->tail);
+
+ return 0;
+err_unmap:
+ for (;;) {
+ tx_buffer = &tx_ring->tx_buffer_info[i];
+ if (dma_unmap_len(tx_buffer, len)) {
+ if (tx_buffer->mapped_as_page) {
+ dma_unmap_page(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ } else {
+ dma_unmap_single(tx_ring->dev,
+ dma_unmap_addr(tx_buffer, dma),
+ dma_unmap_len(tx_buffer, len),
+ DMA_TO_DEVICE);
+ }
+ }
+ dma_unmap_len_set(tx_buffer, len, 0);
+ dma_unmap_addr_set(tx_buffer, dma, 0);
+ if (tx_buffer == first)
+ break;
+ if (i == 0)
+ i += tx_ring->count;
+ i--;
+ }
+ dev_kfree_skb_any(first->skb);
+ first->skb = NULL;
+ tx_ring->next_to_use = i;
+
+ return -ENOMEM;
+}
+
+netdev_tx_t rnpgbe_xmit_frame_ring(struct sk_buff *skb,
+ struct mucse_ring *tx_ring)
+{
+ /* hw requires non-zero mac_ip_len even without offload;
+ * Will be updated per-packet when offload is added
+ */
+ u32 mac_ip_len = M_DEFAULT_MAC_IP_LEN;
+ struct mucse_tx_buffer *first;
+ u32 tx_flags = 0;
+ unsigned short f;
+ u16 count;
+
+ count = TXD_USE_COUNT(skb_headlen(skb));
+ for (f = 0; f < skb_shinfo(skb)->nr_frags; f++) {
+ skb_frag_t *frag_temp = &skb_shinfo(skb)->frags[f];
+
+ count += TXD_USE_COUNT(skb_frag_size(frag_temp));
+ }
+
+ if (!netif_txq_maybe_stop(txring_txq(tx_ring),
+ mucse_desc_unused(tx_ring),
+ count + RESV_DESC_NEEDED,
+ count + RESV_DESC_NEEDED))
+ return NETDEV_TX_BUSY;
+
+ /* record the location of the first descriptor for this packet */
+ first = &tx_ring->tx_buffer_info[tx_ring->next_to_use];
+ first->skb = skb;
+ first->bytecount = skb->len;
+ first->gso_segs = 1;
+
+ if (rnpgbe_tx_map(tx_ring, first, mac_ip_len, tx_flags)) {
+ atomic64_inc(&tx_ring->stats.dropped);
+
+ goto out;
+ }
+
+ /* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue. */
+ netif_txq_maybe_stop(txring_txq(tx_ring),
+ mucse_desc_unused(tx_ring),
+ DESC_NEEDED, DESC_NEEDED);
+out:
+ return NETDEV_TX_OK;
+}
+
+/**
+ * rnpgbe_get_stats64 - Get stats for this netdev
+ * @netdev: network interface device structure
+ * @stats: stats data
+ **/
+void rnpgbe_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct mucse *mucse = netdev_priv(netdev);
+ int i;
+
+ rcu_read_lock();
+ for (i = 0; i < mucse->num_tx_queues; i++) {
+ struct mucse_ring *ring = READ_ONCE(mucse->tx_ring[i]);
+ u64 bytes, packets, dropped;
+ unsigned int start;
+
+ if (ring) {
+ do {
+ start = u64_stats_fetch_begin(&ring->syncp);
+ packets = ring->stats.packets;
+ bytes = ring->stats.bytes;
+ } while (u64_stats_fetch_retry(&ring->syncp, start));
+ dropped = atomic64_read(&ring->stats.dropped);
+
+ stats->tx_packets += packets;
+ stats->tx_dropped += dropped;
+ stats->tx_bytes += bytes;
+ }
+ }
+ rcu_read_unlock();
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
index d38c08df6b7c..1fe290ed224d 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -6,17 +6,39 @@
struct mucse;
struct mucse_hw;
+struct mucse_ring;
#define RING_OFFSET(n) (0x1000 + 0x100 * (n))
+#define RNPGBE_TX_START 0x18
#define RNPGBE_DMA_INT_MASK 0x24
#define TX_INT_MASK BIT(1)
#define RX_INT_MASK BIT(0)
#define INT_VALID (BIT(16) | BIT(17))
#define RNPGBE_DMA_INT_TRIG 0x2c /* lost-interrupt recovery trigger */
+#define RNPGBE_TX_BASE_ADDR_HI 0x60
+#define RNPGBE_TX_BASE_ADDR_LO 0x64
+#define RNPGBE_TX_LEN 0x68
+#define RNPGBE_TX_HEAD 0x6c
+#define RNPGBE_TX_TAIL 0x70
+#define M_DEFAULT_TX_FETCH 0x80008
+#define RNPGBE_TX_FETCH_CTRL 0x74
+#define M_DEFAULT_INT_TIMER 100
+#define RNPGBE_TX_INT_TIMER 0x78
+#define M_DEFAULT_INT_PKTCNT 48
+#define RNPGBE_TX_INT_PKTCNT 0x7c
/* | 31:24 | .... | 15:8 | 7:0 | */
/* | pfvfnum | | tx vector | rx vector | */
#define RING_VECTOR(n) (0x04 * (n))
+#define M_MAX_TXD_PWR 12
+#define M_MAX_DATA_PER_TXD (0x1 << M_MAX_TXD_PWR)
+#define TXD_USE_COUNT(S) DIV_ROUND_UP((S), M_MAX_DATA_PER_TXD)
+#define DESC_NEEDED (MAX_SKB_FRAGS + 4)
+/* 2 desc gap to keep tail from touching head */
+/* 1 desc for context descriptor */
+#define RESV_DESC_NEEDED 3
+/* Hardware requires this to be nonzero */
+#define M_DEFAULT_MAC_IP_LEN 20
#define mucse_for_each_ring(pos, head)\
for (typeof((head).ring) __pos = (head).ring;\
__pos ? ({ pos = __pos; 1; }) : 0;\
@@ -31,4 +53,12 @@ void rnpgbe_free_irq(struct mucse *mucse);
void rnpgbe_irq_disable(struct mucse *mucse);
void rnpgbe_down(struct mucse *mucse);
void rnpgbe_up_complete(struct mucse *mucse);
+int rnpgbe_configure_tx(struct mucse *mucse);
+void rnpgbe_clean_all_tx_rings(struct mucse *mucse);
+int rnpgbe_setup_all_tx_resources(struct mucse *mucse);
+void rnpgbe_free_all_tx_resources(struct mucse *mucse);
+netdev_tx_t rnpgbe_xmit_frame_ring(struct sk_buff *skb,
+ struct mucse_ring *tx_ring);
+void rnpgbe_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *stats);
#endif
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index debed2f3fe64..cdda5f3d09b2 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -2,6 +2,7 @@
/* Copyright(c) 2020 - 2025 Mucse Corporation. */
#include <linux/pci.h>
+#include <linux/skbuff.h>
#include <net/rtnetlink.h>
#include <linux/etherdevice.h>
@@ -26,6 +27,19 @@ static struct pci_device_id rnpgbe_pci_tbl[] = {
{ },
};
+/**
+ * rnpgbe_configure - Configure the hardware
+ * @mucse: pointer to private structure
+ *
+ * Configure Tx registers in hardware.
+ *
+ * Return: 0 on success, negative errno if hardware configuration fails
+ **/
+static int rnpgbe_configure(struct mucse *mucse)
+{
+ return rnpgbe_configure_tx(mucse);
+}
+
/**
* rnpgbe_open - Called when a network interface is made active
* @netdev: network interface device structure
@@ -40,6 +54,9 @@ static int rnpgbe_open(struct net_device *netdev)
struct mucse *mucse = netdev_priv(netdev);
int err;
+ if (test_bit(__MUCSE_AXI_FAULT, &mucse->state))
+ return -EIO;
+
err = rnpgbe_request_irq(mucse);
if (err)
return err;
@@ -49,9 +66,19 @@ static int rnpgbe_open(struct net_device *netdev)
if (err)
goto err_free_irqs;
+ err = rnpgbe_setup_all_tx_resources(mucse);
+ if (err)
+ goto err_free_irqs;
+
+ err = rnpgbe_configure(mucse);
+ if (err)
+ goto err_free_tx;
rnpgbe_up_complete(mucse);
return 0;
+err_free_tx:
+ rnpgbe_clean_all_tx_rings(mucse);
+ rnpgbe_free_all_tx_resources(mucse);
err_free_irqs:
rnpgbe_free_irq(mucse);
return err;
@@ -72,6 +99,7 @@ static int rnpgbe_close(struct net_device *netdev)
rnpgbe_down(mucse);
rnpgbe_free_irq(mucse);
+ rnpgbe_free_all_tx_resources(mucse);
return 0;
}
@@ -81,25 +109,37 @@ static int rnpgbe_close(struct net_device *netdev)
* @skb: skb structure to be sent
* @netdev: network interface device structure
*
- * Return: NETDEV_TX_OK
+ * Return: NETDEV_TX_OK or NETDEV_TX_BUSY when insufficient descriptors
**/
static netdev_tx_t rnpgbe_xmit_frame(struct sk_buff *skb,
struct net_device *netdev)
{
struct mucse *mucse = netdev_priv(netdev);
+ struct mucse_ring *tx_ring;
- dev_kfree_skb_any(skb);
- mucse->stats.tx_dropped++;
+ tx_ring = mucse->tx_ring[skb_get_queue_mapping(skb)];
- return NETDEV_TX_OK;
+ if (unlikely(skb_put_padto(skb, RNPGBE_TX_MIN_PKT_LEN))) {
+ atomic64_inc(&tx_ring->stats.dropped);
+ return NETDEV_TX_OK;
+ }
+
+ return rnpgbe_xmit_frame_ring(skb, tx_ring);
}
static const struct net_device_ops rnpgbe_netdev_ops = {
.ndo_open = rnpgbe_open,
.ndo_stop = rnpgbe_close,
.ndo_start_xmit = rnpgbe_xmit_frame,
+ .ndo_get_stats64 = rnpgbe_get_stats64,
};
+static void rnpgbe_sw_init(struct mucse *mucse)
+{
+ mucse->tx_ring_item_count = M_DEFAULT_TXD;
+ mucse->tx_work_limit = M_DEFAULT_TX_WORK;
+}
+
/**
* rnpgbe_add_adapter - Add netdev for this pci_dev
* @pdev: PCI device information structure
@@ -172,6 +212,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
}
netdev->netdev_ops = &rnpgbe_netdev_ops;
+ rnpgbe_sw_init(mucse);
err = rnpgbe_reset_hw(hw);
if (err) {
dev_err(&pdev->dev, "Hw reset failed %d\n", err);
@@ -206,6 +247,13 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
goto err_clear_interrupt;
}
+ netdev->features |= NETIF_F_SG;
+ netdev->hw_features |= NETIF_F_SG;
+ if (dma_get_mask(&pdev->dev) > DMA_BIT_MASK(32)) {
+ netdev->features |= NETIF_F_HIGHDMA;
+ netdev->hw_features |= NETIF_F_HIGHDMA;
+ }
+
err = register_netdev(netdev);
if (err)
goto err_remove_mbx;
@@ -248,12 +296,7 @@ static int rnpgbe_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (err)
return err;
- err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(56));
- if (err) {
- dev_err(&pdev->dev,
- "No usable DMA configuration, aborting %d\n", err);
- goto err_disable_dev;
- }
+ dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(56));
err = pci_request_mem_regions(pdev, rnpgbe_driver_name);
if (err) {
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-09-20 9:26 ` [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling Dong Yibo
2026-09-20 9:26 ` [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
@ 2026-09-20 9:26 ` Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 4/5] net: rnpgbe: Add receive mode support Dong Yibo
2026-09-20 9:27 ` [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support Dong Yibo
4 siblings, 1 reply; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:26 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Add basic RX DMA data path for the RNPGBE driver.
Introduce the RX descriptor structure (union rnpgbe_rx_desc) with the
hardware write-back format, and manage RX buffers with page_pool using a
single page per descriptor without page splitting. Implement the NAPI
poll callback, RX ring setup and teardown, and packet assembly from page
buffers, along with RX statistics.
Keep a 16-descriptor gap in the fixed 512-entry RX ring so at most 496
descriptors are posted to hardware. Retry failed page allocations with
one timer per q_vector that schedules NAPI and is cancelled before RX
cleanup and q_vector teardown.
An RX DMA quiesce timeout is a terminal AXI fault: keep DMA disabled,
release existing mappings safely, and reject subsequent opens. Recovery
requires a chip-level reset and driver rebind.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/Kconfig | 1 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 58 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 2 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 794 +++++++++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 21 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 19 +-
6 files changed, 887 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/mucse/Kconfig b/drivers/net/ethernet/mucse/Kconfig
index 0b3e853d625f..be0fdf268484 100644
--- a/drivers/net/ethernet/mucse/Kconfig
+++ b/drivers/net/ethernet/mucse/Kconfig
@@ -19,6 +19,7 @@ if NET_VENDOR_MUCSE
config MGBE
tristate "Mucse(R) 1GbE PCI Express adapters support"
depends on PCI
+ select PAGE_POOL
help
This driver supports Mucse(R) 1GbE PCI Express family of
adapters.
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index 864cbf0caf44..c1a72e8e2770 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -8,6 +8,7 @@
#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/rcupdate.h>
+#include <linux/timer.h>
#include <linux/if.h>
#include <linux/workqueue.h>
@@ -63,7 +64,32 @@ struct rnpgbe_tx_desc {
#define M_TXD_CMD_EOP 0x010000 /* End of Packet */
};
+union rnpgbe_rx_desc {
+ struct {
+ __le64 pkt_addr; /* Packet buffer address */
+ __le64 resv_cmd; /* cmd status */
+ };
+ struct {
+ __le32 rss_hash; /* RSS HASH */
+ __le16 mark; /* mark info */
+ __le16 rev1;
+ __le16 len; /* Packet length */
+ __le16 padding_len;
+ __le16 vlan; /* VLAN tag */
+ __le16 cmd; /* cmd status */
+#define M_RXD_STAT_DD BIT(1) /* Descriptor Done */
+#define M_RXD_STAT_EOP BIT(0) /* End of Packet */
+ } wb;
+};
+
#define M_TX_DESC(R, i) (&(((struct rnpgbe_tx_desc *)((R)->desc))[i]))
+#define M_RX_DESC(R, i) (&(((union rnpgbe_rx_desc *)((R)->desc))[i]))
+
+static inline __le16 rnpgbe_test_staterr(union rnpgbe_rx_desc *rx_desc,
+ const u16 stat_err_bits)
+{
+ return rx_desc->wb.cmd & cpu_to_le16(stat_err_bits);
+}
struct mucse_tx_buffer {
struct rnpgbe_tx_desc *next_to_watch;
@@ -81,13 +107,24 @@ struct mucse_queue_stats {
atomic64_t dropped;
};
+struct mucse_rx_buffer {
+ struct sk_buff *skb;
+ dma_addr_t dma;
+ struct page *page;
+ u32 page_offset;
+};
+
struct mucse_ring {
struct mucse_ring *next;
struct mucse_q_vector *q_vector;
struct net_device *netdev;
struct device *dev;
+ struct page_pool *page_pool;
void *desc;
- struct mucse_tx_buffer *tx_buffer_info;
+ union {
+ struct mucse_tx_buffer *tx_buffer_info;
+ struct mucse_rx_buffer *rx_buffer_info;
+ };
void __iomem *ring_addr;
void __iomem *tail;
void __iomem *irq_mask;
@@ -103,8 +140,12 @@ struct mucse_ring {
unsigned int size;
struct mucse_queue_stats stats;
struct u64_stats_sync syncp;
+ bool drop_status;
} ____cacheline_internodealigned_in_smp;
+/* Refill RX descriptors in batches of this size. */
+#define M_RX_BUFFER_WRITE 16
+
static inline u16 mucse_desc_unused(struct mucse_ring *ring)
{
u16 ntc = ring->next_to_clean;
@@ -113,6 +154,18 @@ static inline u16 mucse_desc_unused(struct mucse_ring *ring)
return ((ntc > ntu) ? 0 : ring->count) + ntc - ntu - 1;
}
+static inline u16 mucse_desc_unused_rx(struct mucse_ring *ring)
+{
+ u16 ntc = ring->next_to_clean;
+ u16 ntu = ring->next_to_use;
+
+ /* Keep M_RX_BUFFER_WRITE descriptors unused so the ring is not filled
+ * completely. Refill is attempted once at least this many descriptors
+ * are available.
+ */
+ return ((ntc > ntu) ? 0 : ring->count) + ntc - ntu - M_RX_BUFFER_WRITE;
+}
+
static inline __le64 build_ctob(u32 vlan_cmd, u32 mac_ip_len, u32 size)
{
return cpu_to_le64(((u64)vlan_cmd << 32) | ((u64)mac_ip_len << 16) |
@@ -136,6 +189,7 @@ struct mucse_q_vector {
struct mucse_ring_container rx, tx;
struct napi_struct napi;
struct rcu_head rcu;
+ struct timer_list rx_alloc_timer;
char name[IFNAMSIZ + 18];
/* for dynamic allocation of rings associated with this q_vector */
struct mucse_ring ring[] ____cacheline_internodealigned_in_smp;
@@ -144,6 +198,7 @@ struct mucse_q_vector {
#define MAX_Q_VECTORS 8
#define M_DEFAULT_TXD 512
+#define M_DEFAULT_RXD 512
#define M_DEFAULT_TX_WORK 256
enum mucse_state_t {
@@ -163,6 +218,7 @@ struct mucse {
int tx_work_limit;
int num_tx_queues;
int num_q_vectors;
+ int rx_ring_item_count;
int num_rx_queues;
char mbx_name[32];
unsigned long state;
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
index 6dc29ebe6fa7..1d87edfba3d7 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -19,6 +19,8 @@
#define TX_AXI_RW_EN 0xc
/* DMA_STATUS_REG[23:20]: tx_wr, tx_rd, rx_wr, rx_rd done status. */
#define RNPGBE_DMA_TX_STATUS GENMASK_U32(23, 22)
+#define RNPGBE_DMA_RX_STATUS GENMASK_U32(21, 20)
+#define RX_AXI_RW_EN 0x03
#define RNPGBE_DMA_AXI_EN 0x0010
#define RNPGBE_TX_MIN_PKT_LEN 33
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
index af5cef987051..9afd10273a62 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -4,8 +4,10 @@
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/iopoll.h>
+#include <linux/etherdevice.h>
#include <linux/vmalloc.h>
#include <net/netdev_queues.h>
+#include <net/page_pool/helpers.h>
#include "rnpgbe_lib.h"
#include "rnpgbe.h"
@@ -174,15 +176,436 @@ static bool rnpgbe_clean_tx_irq(struct mucse_q_vector *q_vector,
return !!budget;
}
+static bool mucse_alloc_mapped_page(struct mucse_ring *rx_ring,
+ struct mucse_rx_buffer *bi)
+{
+ struct page *page = bi->page;
+ dma_addr_t dma;
+
+ if (page) {
+ /* Buffer is being reused without going back through the
+ * page_pool. Do dma_sync for hw use.
+ */
+ dma_sync_single_range_for_device(rx_ring->dev, bi->dma,
+ bi->page_offset,
+ PAGE_SIZE - bi->page_offset,
+ DMA_FROM_DEVICE);
+ return true;
+ }
+
+ page = page_pool_dev_alloc_pages(rx_ring->page_pool);
+ if (unlikely(!page))
+ return false;
+ dma = page_pool_get_dma_addr(page);
+
+ bi->dma = dma;
+ bi->page = page;
+ bi->page_offset = RNPGBE_SKB_PAD;
+
+ return true;
+}
+
+static void mucse_update_rx_tail(struct mucse_ring *rx_ring,
+ u32 val)
+{
+ rx_ring->next_to_use = val;
+ writel(val, rx_ring->tail);
+}
+
+/**
+ * rnpgbe_alloc_rx_buffers - Replace used receive buffers
+ * @rx_ring: ring to place buffers on
+ * @cleaned_count: number of buffers to replace
+ *
+ * Return: true if alloc failed
+ **/
+static bool rnpgbe_alloc_rx_buffers(struct mucse_ring *rx_ring,
+ u16 cleaned_count)
+{
+ u64 fun_id = ((u64)(rx_ring->pfvfnum) << 56);
+ union rnpgbe_rx_desc *rx_desc;
+ u16 i = rx_ring->next_to_use;
+ struct mucse_rx_buffer *bi;
+ bool err = false;
+ u64 addr;
+ /* nothing to do */
+ if (!cleaned_count)
+ return err;
+
+ rx_desc = M_RX_DESC(rx_ring, i);
+ bi = &rx_ring->rx_buffer_info[i];
+ i -= rx_ring->count;
+
+ do {
+ if (!mucse_alloc_mapped_page(rx_ring, bi)) {
+ err = true;
+ break;
+ }
+
+ addr = (u64)(bi->dma + bi->page_offset);
+ rx_desc->pkt_addr = cpu_to_le64(addr | fun_id);
+ /* clean dd */
+ rx_desc->resv_cmd = 0;
+ rx_desc++;
+ bi++;
+ i++;
+ if (unlikely(!i)) {
+ rx_desc = M_RX_DESC(rx_ring, 0);
+ bi = rx_ring->rx_buffer_info;
+ i -= rx_ring->count;
+ }
+ cleaned_count--;
+ } while (cleaned_count);
+
+ i += rx_ring->count;
+
+ /* Publish the tail even when allocation stopped early, so a stale
+ * tail from a previous session cannot point hardware at a
+ * reallocated ring.
+ */
+ dma_wmb();
+ mucse_update_rx_tail(rx_ring, i);
+
+ return err;
+}
+
+/**
+ * rnpgbe_get_buffer - Get the rx_buffer to be used
+ * @rx_ring: pointer to rx ring
+ * @skb: pointer skb for this packet
+ * @size: data size in this desc
+ *
+ * Return: rx_buffer.
+ **/
+static struct mucse_rx_buffer *rnpgbe_get_buffer(struct mucse_ring *rx_ring,
+ struct sk_buff **skb,
+ const unsigned int size)
+{
+ struct mucse_rx_buffer *rx_buffer;
+
+ rx_buffer = &rx_ring->rx_buffer_info[rx_ring->next_to_clean];
+ *skb = rx_buffer->skb;
+ prefetchw(page_address(rx_buffer->page) + rx_buffer->page_offset);
+ /* we are reusing so sync this buffer for CPU use */
+ dma_sync_single_range_for_cpu(rx_ring->dev, rx_buffer->dma,
+ rx_buffer->page_offset, size,
+ DMA_FROM_DEVICE);
+
+ return rx_buffer;
+}
+
+/**
+ * rnpgbe_add_rx_frag - Add non-linear data to the skb
+ * @rx_buffer: pointer to rx_buffer
+ * @skb: pointer skb for this packet
+ * @size: data size in this desc
+ **/
+static void rnpgbe_add_rx_frag(struct mucse_rx_buffer *rx_buffer,
+ struct sk_buff *skb,
+ unsigned int size)
+{
+ unsigned int truesize = PAGE_SIZE;
+
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, rx_buffer->page,
+ rx_buffer->page_offset, size, truesize);
+}
+
+/**
+ * rnpgbe_build_skb - Try to build a skb based on rx_buffer
+ * @rx_buffer: pointer to rx_buffer
+ * @size: data size in this desc
+ *
+ * Return: skb for this rx_buffer
+ **/
+static struct sk_buff *rnpgbe_build_skb(struct mucse_rx_buffer *rx_buffer,
+ unsigned int size)
+{
+ void *va = page_address(rx_buffer->page) + rx_buffer->page_offset;
+ unsigned int truesize = PAGE_SIZE;
+ struct sk_buff *skb;
+
+ net_prefetch(va);
+ /* build an skb around the page buffer */
+ skb = napi_build_skb(va - RNPGBE_SKB_PAD, truesize);
+ if (unlikely(!skb))
+ return NULL;
+ /* update pointers within the skb to store the data */
+ skb_reserve(skb, RNPGBE_SKB_PAD);
+ __skb_put(skb, size);
+ skb_mark_for_recycle(skb);
+
+ return skb;
+}
+
+/**
+ * rnpgbe_is_non_eop - Process handling of non-EOP buffers
+ * @rx_ring: rx ring being processed
+ * @rx_desc: rx descriptor for current buffer
+ * @skb: current socket buffer containing buffer in progress
+ *
+ * This function updates next to clean. If the buffer is an EOP buffer
+ * this function exits returning false, otherwise it will place the
+ * sk_buff in the next buffer to be chained and return true indicating
+ * that this is in fact a non-EOP buffer.
+ *
+ * Return: true for not end of packet
+ **/
+static bool rnpgbe_is_non_eop(struct mucse_ring *rx_ring,
+ union rnpgbe_rx_desc *rx_desc,
+ struct sk_buff *skb)
+{
+ u32 ntc = rx_ring->next_to_clean + 1;
+
+ /* fetch, update, and store next to clean */
+ ntc = (ntc < rx_ring->count) ? ntc : 0;
+ rx_ring->next_to_clean = ntc;
+ prefetch(M_RX_DESC(rx_ring, ntc));
+ /* if we are the last buffer then there is nothing else to do */
+ if (likely(rnpgbe_test_staterr(rx_desc, M_RXD_STAT_EOP)))
+ return false;
+ if (skb_shinfo(skb)->nr_frags < MAX_SKB_FRAGS) {
+ /* place skb in next buffer to be received */
+ rx_ring->rx_buffer_info[ntc].skb = skb;
+ } else {
+ atomic64_inc(&rx_ring->stats.dropped);
+ /* too much frags, force free */
+ dev_kfree_skb_any(skb);
+ rx_ring->drop_status = true;
+ }
+ /* we should clean it since we used all info in it */
+ rx_desc->wb.cmd = 0;
+
+ return true;
+}
+
+/**
+ * rnpgbe_cleanup_headers - Correct corrupted or empty headers
+ * @skb: current socket buffer containing buffer in progress
+ *
+ * Return: true if an error was encountered and skb was freed.
+ **/
+static bool rnpgbe_cleanup_headers(struct sk_buff *skb)
+{
+ /* if eth_skb_pad returns an error the skb was freed */
+ if (eth_skb_pad(skb))
+ return true;
+
+ return false;
+}
+
+/**
+ * rnpgbe_process_skb_fields - Set the RX queue and protocol fields
+ * @rx_ring: RX descriptor ring containing the queue information
+ * @skb: skb currently being received
+ *
+ * Records the RX queue that received the skb and sets its protocol from
+ * the Ethernet header.
+ **/
+static void rnpgbe_process_skb_fields(struct mucse_ring *rx_ring,
+ struct sk_buff *skb)
+{
+ struct net_device *dev = rx_ring->netdev;
+
+ skb_record_rx_queue(skb, rx_ring->queue_index);
+ skb->protocol = eth_type_trans(skb, dev);
+}
+
+/**
+ * rnpgbe_rx_alloc_retry - Retry RX buffer allocation
+ * @timer: RX allocation retry timer
+ *
+ * Schedules NAPI after RX buffer allocation fails.
+ **/
+static void rnpgbe_rx_alloc_retry(struct timer_list *timer)
+{
+ struct mucse_q_vector *q_vector =
+ timer_container_of(q_vector, timer, rx_alloc_timer);
+
+ napi_schedule(&q_vector->napi);
+}
+
+static void rnpgbe_schedule_rx_retry(struct mucse *mucse)
+{
+ struct mucse_q_vector *q_vector;
+ struct mucse_ring *ring;
+
+ for (int i = 0; i < mucse->num_q_vectors; i++) {
+ q_vector = mucse->q_vector[i];
+ mucse_for_each_ring(ring, q_vector->rx) {
+ if (mucse_desc_unused_rx(ring)) {
+ mod_timer(&q_vector->rx_alloc_timer,
+ jiffies + msecs_to_jiffies(500));
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * rnpgbe_clean_rx_irq - Clean completed descriptors from Rx ring
+ * @q_vector: structure containing interrupt and ring information
+ * @rx_ring: rx descriptor ring to transact packets on
+ * @budget: total limit on number of packets to process
+ *
+ * rnpgbe_clean_rx_irq tries to check dd in desc, handle this desc
+ * if dd is set which means data is write-back by hw
+ *
+ * Return: amount of work completed.
+ **/
+static int rnpgbe_clean_rx_irq(struct mucse_q_vector *q_vector,
+ struct mucse_ring *rx_ring,
+ int budget)
+{
+ unsigned int max_size = SKB_WITH_OVERHEAD(PAGE_SIZE) - RNPGBE_SKB_PAD;
+ unsigned int total_rx_bytes = 0, total_rx_packets = 0;
+ u16 cleaned_count = mucse_desc_unused_rx(rx_ring);
+ unsigned int work_done = 0;
+
+ while (likely(work_done < budget)) {
+ struct mucse_rx_buffer *rx_buffer;
+ union rnpgbe_rx_desc *rx_desc;
+ struct sk_buff *skb;
+ unsigned int size;
+
+ if (cleaned_count >= M_RX_BUFFER_WRITE) {
+ if (rnpgbe_alloc_rx_buffers(rx_ring, cleaned_count)) {
+ mod_timer(&q_vector->rx_alloc_timer,
+ jiffies + msecs_to_jiffies(500));
+ cleaned_count = mucse_desc_unused_rx(rx_ring);
+ } else {
+ cleaned_count = 0;
+ }
+ }
+ rx_desc = M_RX_DESC(rx_ring, rx_ring->next_to_clean);
+
+ if (!rnpgbe_test_staterr(rx_desc, M_RXD_STAT_DD))
+ break;
+
+ /* This memory barrier is needed to keep us from reading
+ * any other fields out of the rx_desc until we know the
+ * descriptor has been written back
+ */
+ dma_rmb();
+ /* Hardware guarantees the first descriptor of each packet is at
+ * least 33 bytes, including multi-descriptor packets.
+ * Multi-descriptor packets are only used for jumbo frames over
+ * 1536 bytes (M_DEFAULT_SG = 96). Each descriptor is at most
+ * 1536 bytes. Small packets use a single descriptor.
+ */
+ size = le16_to_cpu(rx_desc->wb.len);
+
+ if (unlikely(rx_ring->drop_status)) {
+ cleaned_count++;
+ /* drop data until eop */
+ if (rnpgbe_test_staterr(rx_desc, M_RXD_STAT_EOP)) {
+ rx_ring->drop_status = false;
+ work_done++;
+ }
+
+ rx_desc->wb.cmd = 0;
+ rx_ring->next_to_clean++;
+ if (rx_ring->next_to_clean >= rx_ring->count)
+ rx_ring->next_to_clean = 0;
+ continue;
+ }
+
+ if (unlikely(!size || size > max_size)) {
+ struct mucse_rx_buffer *err_rx_buffer;
+ u16 idx = rx_ring->next_to_clean;
+
+ cleaned_count++;
+ atomic64_inc(&rx_ring->stats.dropped);
+
+ /* Free the partial skb from a previous non-EOP
+ * descriptor before advancing next_to_clean.
+ */
+ err_rx_buffer = &rx_ring->rx_buffer_info[idx];
+ if (unlikely(err_rx_buffer->skb)) {
+ dev_kfree_skb_any(err_rx_buffer->skb);
+ err_rx_buffer->skb = NULL;
+ }
+
+ /* drop data until eop */
+ if (rnpgbe_test_staterr(rx_desc, M_RXD_STAT_EOP))
+ work_done++;
+ else
+ rx_ring->drop_status = true;
+
+ rx_desc->wb.cmd = 0;
+ rx_ring->next_to_clean++;
+ if (rx_ring->next_to_clean >= rx_ring->count)
+ rx_ring->next_to_clean = 0;
+ continue;
+ }
+
+ rx_buffer = rnpgbe_get_buffer(rx_ring, &skb, size);
+
+ if (skb)
+ rnpgbe_add_rx_frag(rx_buffer, skb, size);
+ else
+ skb = rnpgbe_build_skb(rx_buffer, size);
+
+ if (!skb) {
+ cleaned_count++;
+
+ /* drop until eop if multiple descriptors */
+ if (rnpgbe_test_staterr(rx_desc, M_RXD_STAT_EOP))
+ work_done++;
+ else
+ rx_ring->drop_status = true;
+
+ rx_desc->wb.cmd = 0;
+ rx_ring->next_to_clean++;
+ atomic64_inc(&rx_ring->stats.dropped);
+ if (rx_ring->next_to_clean >= rx_ring->count)
+ rx_ring->next_to_clean = 0;
+
+ continue;
+ }
+
+ rx_buffer->page = NULL;
+ rx_buffer->skb = NULL;
+ cleaned_count++;
+
+ if (rnpgbe_is_non_eop(rx_ring, rx_desc, skb))
+ continue;
+
+ /* verify the packet layout is correct */
+ if (rnpgbe_cleanup_headers(skb)) {
+ /* we should clean it since we used all info in it */
+ atomic64_inc(&rx_ring->stats.dropped);
+ rx_desc->wb.cmd = 0;
+ work_done++;
+ continue;
+ }
+
+ /* probably a little skewed due to removing CRC */
+ total_rx_bytes += skb->len;
+ rnpgbe_process_skb_fields(rx_ring, skb);
+ rx_desc->wb.cmd = 0;
+ napi_gro_receive(&q_vector->napi, skb);
+ /* Update packet statistics and NAPI work accounting. */
+ total_rx_packets++;
+ work_done++;
+ }
+
+ u64_stats_update_begin(&rx_ring->syncp);
+ rx_ring->stats.packets += total_rx_packets;
+ rx_ring->stats.bytes += total_rx_bytes;
+ u64_stats_update_end(&rx_ring->syncp);
+
+ return work_done;
+}
+
/**
* rnpgbe_poll - NAPI polling callback
* @napi: structure for representing this polling device
* @budget: polling budget
*
- * Clean completed TX packets and complete NAPI polling when all queues are
- * clean.
+ * Clean completed TX and RX packets and complete NAPI polling when all
+ * queues are clean.
*
- * Return: 0 if all TX queues are clean, otherwise @budget
+ * Return: number of RX packets processed, or @budget if polling continues
**/
static int rnpgbe_poll(struct napi_struct *napi, int budget)
{
@@ -190,6 +613,7 @@ static int rnpgbe_poll(struct napi_struct *napi, int budget)
container_of(napi, struct mucse_q_vector, napi);
bool clean_complete = true;
struct mucse_ring *ring;
+ int per_ring_budget;
int work_done = 0;
mucse_for_each_ring(ring, q_vector->tx) {
@@ -201,6 +625,20 @@ static int rnpgbe_poll(struct napi_struct *napi, int budget)
if (unlikely(!budget))
return 0;
+ if (q_vector->rx.count > 1)
+ per_ring_budget = max(budget / q_vector->rx.count, 1);
+ else
+ per_ring_budget = budget;
+
+ mucse_for_each_ring(ring, q_vector->rx) {
+ int cleaned = 0;
+
+ cleaned = rnpgbe_clean_rx_irq(q_vector, ring, per_ring_budget);
+ work_done += cleaned;
+ if (cleaned >= per_ring_budget)
+ clean_complete = false;
+ }
+
if (!clean_complete)
return budget;
@@ -340,6 +778,7 @@ static int rnpgbe_alloc_q_vector(struct mucse *mucse,
/* tie q_vector and mucse together */
mucse->q_vector[vector_idx] = q_vector;
q_vector->mucse = mucse;
+ timer_setup(&q_vector->rx_alloc_timer, rnpgbe_rx_alloc_retry, 0);
q_vector->hw_vector = vector_idx;
/* Vector 0 is reserved for the mailbox. */
q_vector->hw_vector++;
@@ -365,13 +804,18 @@ static int rnpgbe_alloc_q_vector(struct mucse *mucse,
}
for (idx = 0; idx < rxr_count; idx++) {
+ ring->dev = &mucse->pdev->dev;
mucse_add_ring(ring, &q_vector->rx);
+ ring->count = mucse->rx_ring_item_count;
+ ring->netdev = mucse->netdev;
ring->queue_index = eth_queue_idx + idx;
ring->rnpgbe_queue_idx = rxr_idx;
ring->ring_addr = hw->hw_addr + RING_OFFSET(rxr_idx);
ring->irq_mask = ring->ring_addr + RNPGBE_DMA_INT_MASK;
ring->trig = ring->ring_addr + RNPGBE_DMA_INT_TRIG;
ring->q_vector = q_vector;
+ ring->pfvfnum = hw->pfvfnum;
+ u64_stats_init(&ring->syncp);
WRITE_ONCE(mucse->rx_ring[ring->queue_index], ring);
rxr_idx += step;
ring++;
@@ -394,6 +838,8 @@ static void rnpgbe_free_q_vector(struct mucse *mucse, int vector_idx)
struct mucse_q_vector *q_vector = mucse->q_vector[vector_idx];
struct mucse_ring *ring;
+ timer_delete_sync(&q_vector->rx_alloc_timer);
+
mucse_for_each_ring(ring, q_vector->tx)
WRITE_ONCE(mucse->tx_ring[ring->queue_index], NULL);
mucse_for_each_ring(ring, q_vector->rx)
@@ -713,6 +1159,31 @@ static int rnpgbe_wait_tx_dma_idle(struct mucse *mucse)
return err;
}
+static int rnpgbe_wait_rx_dma_idle(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_status;
+ int err;
+
+ /* A timeout indicates a terminal AXI fault. Hardware stops all PCIe
+ * DMA requests in this state, including requests accepted before the
+ * fault. Teardown may therefore release mappings and descriptor memory.
+ * Recovery requires a chip-level reset.
+ */
+ err = readl_poll_timeout(hw->hw_addr + RNPGBE_DMA_STATUS,
+ dma_status,
+ (dma_status & RNPGBE_DMA_RX_STATUS) ==
+ RNPGBE_DMA_RX_STATUS,
+ 10, 100000);
+ if (err) {
+ set_bit(__MUCSE_AXI_FAULT, &mucse->state);
+ dev_err(&mucse->pdev->dev,
+ "RX DMA failed to quiesce, status %#x\n", dma_status);
+ }
+
+ return err;
+}
+
/**
* rnpgbe_stop_all_tx_rings - Stop TX DMA on all queues
* @mucse: board private structure
@@ -832,6 +1303,99 @@ void rnpgbe_clean_all_tx_rings(struct mucse *mucse)
rnpgbe_clean_tx_ring(mucse->tx_ring[i]);
}
+static void rnpgbe_stop_rx_ring(struct mucse_ring *rx_ring)
+{
+ /* Stop hw. hardware design guarantees:
+ * - No new descriptors will be fetched after RX_START=0
+ * - No DMA will be initiated for already-fetched descriptors
+ */
+ mucse_ring_wr32(rx_ring, RNPGBE_RX_START, 0);
+ /* Flush posted write to ensure hardware sees RX_START=0 */
+ (void)mucse_ring_rd32(rx_ring, RNPGBE_RX_START);
+}
+
+/**
+ * rnpgbe_stop_all_rx_rings - Stop RX DMA for all queues
+ * @mucse: board private structure
+ *
+ * Return: 0 if RX DMA is quiesced and no AXI fault is latched,
+ * negative errno otherwise
+ **/
+static int rnpgbe_stop_all_rx_rings(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+ int err = 0;
+
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_stop_rx_ring(mucse->rx_ring[i]);
+
+ if (mucse->num_rx_queues &&
+ !test_bit(__MUCSE_AXI_FAULT, &mucse->state))
+ err = rnpgbe_wait_rx_dma_idle(mucse);
+
+ dma_axi_ctl = mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+ dma_axi_ctl &= ~RX_AXI_RW_EN;
+ mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
+ /* Flush the posted write before continuing. */
+ (void)mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+
+ if (!test_bit(__MUCSE_AXI_FAULT, &mucse->state))
+ return 0;
+
+ return err ? err : -EIO;
+}
+
+/**
+ * rnpgbe_clean_rx_ring - Free Rx Buffers per Queue
+ * @rx_ring: ring to free buffers from
+ **/
+static void rnpgbe_clean_rx_ring(struct mucse_ring *rx_ring)
+{
+ struct mucse_rx_buffer *rx_buffer;
+ u16 i;
+
+ /* ring already cleared, nothing to do */
+ if (!rx_ring->rx_buffer_info)
+ return;
+ /* Free all the Rx ring sk_buffs */
+ for (i = 0; i < rx_ring->count; i++) {
+ rx_buffer = &rx_ring->rx_buffer_info[i];
+
+ if (rx_buffer->skb) {
+ struct sk_buff *skb = rx_buffer->skb;
+
+ dev_kfree_skb(skb);
+ rx_buffer->skb = NULL;
+ }
+
+ if (rx_buffer->page) {
+ page_pool_put_full_page(rx_ring->page_pool,
+ rx_buffer->page, false);
+ rx_buffer->page = NULL;
+ }
+ }
+
+ rx_ring->next_to_clean = 0;
+ rx_ring->next_to_use = 0;
+}
+
+/**
+ * rnpgbe_clean_all_rx_rings - Free Rx buffers for all queues
+ * @mucse: board private structure
+ **/
+static void rnpgbe_clean_all_rx_rings(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_clean_rx_ring(mucse->rx_ring[i]);
+}
+
+static void rnpgbe_cancel_rx_retry_timers(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_q_vectors; i++)
+ timer_delete_sync(&mucse->q_vector[i]->rx_alloc_timer);
+}
+
void rnpgbe_down(struct mucse *mucse)
{
struct net_device *netdev = mucse->netdev;
@@ -841,7 +1405,10 @@ void rnpgbe_down(struct mucse *mucse)
synchronize_net();
rnpgbe_irq_disable(mucse);
netif_tx_disable(netdev);
+ rnpgbe_stop_all_rx_rings(mucse);
+ rnpgbe_cancel_rx_retry_timers(mucse);
rnpgbe_clean_all_tx_rings(mucse);
+ rnpgbe_clean_all_rx_rings(mucse);
}
/**
@@ -854,6 +1421,7 @@ void rnpgbe_up_complete(struct mucse *mucse)
rnpgbe_configure_msix(mucse);
rnpgbe_napi_enable_all(mucse);
+ rnpgbe_schedule_rx_retry(mucse);
rnpgbe_irq_enable(mucse);
netif_tx_start_all_queues(netdev);
}
@@ -1258,5 +1826,225 @@ void rnpgbe_get_stats64(struct net_device *netdev,
stats->tx_bytes += bytes;
}
}
+
+ for (i = 0; i < mucse->num_rx_queues; i++) {
+ struct mucse_ring *ring = READ_ONCE(mucse->rx_ring[i]);
+ u64 bytes, packets, dropped;
+ unsigned int start;
+
+ if (ring) {
+ do {
+ start = u64_stats_fetch_begin(&ring->syncp);
+ packets = ring->stats.packets;
+ bytes = ring->stats.bytes;
+ } while (u64_stats_fetch_retry(&ring->syncp, start));
+ dropped = atomic64_read(&ring->stats.dropped);
+
+ stats->rx_packets += packets;
+ stats->rx_dropped += dropped;
+ stats->rx_bytes += bytes;
+ }
+ }
rcu_read_unlock();
}
+
+static int mucse_alloc_page_pool(struct mucse_ring *rx_ring)
+{
+ struct page_pool_params pp_params = {
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .order = 0,
+ .pool_size = rx_ring->count,
+ .nid = dev_to_node(rx_ring->dev),
+ .dev = rx_ring->dev,
+ .napi = &rx_ring->q_vector->napi,
+ .dma_dir = DMA_FROM_DEVICE,
+ .offset = 0,
+ .max_len = PAGE_SIZE,
+ .netdev = rx_ring->netdev,
+ .queue_idx = rx_ring->queue_index,
+ };
+ int ret = 0;
+
+ rx_ring->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(rx_ring->page_pool)) {
+ ret = PTR_ERR(rx_ring->page_pool);
+ rx_ring->page_pool = NULL;
+ }
+
+ return ret;
+}
+
+/**
+ * rnpgbe_setup_rx_resources - allocate Rx resources (Descriptors)
+ * @rx_ring: rx descriptor ring (for a specific queue) to setup
+ *
+ * Return: 0 on success, negative on failure
+ **/
+static int rnpgbe_setup_rx_resources(struct mucse_ring *rx_ring)
+{
+ struct device *dev = rx_ring->dev;
+ int size;
+
+ size = sizeof(struct mucse_rx_buffer) * rx_ring->count;
+
+ rx_ring->rx_buffer_info = vzalloc(size);
+
+ if (!rx_ring->rx_buffer_info)
+ goto err_return;
+ /* Round up to nearest 4K */
+ rx_ring->size = rx_ring->count * sizeof(union rnpgbe_rx_desc);
+ rx_ring->size = ALIGN(rx_ring->size, 4096);
+ rx_ring->desc = dma_alloc_coherent(dev, rx_ring->size, &rx_ring->dma,
+ GFP_KERNEL);
+ if (!rx_ring->desc)
+ goto err_free_buffer;
+
+ rx_ring->next_to_clean = 0;
+ rx_ring->next_to_use = 0;
+
+ if (mucse_alloc_page_pool(rx_ring))
+ goto err_free_desc;
+
+ return 0;
+err_free_desc:
+ dma_free_coherent(dev, rx_ring->size, rx_ring->desc,
+ rx_ring->dma);
+ rx_ring->desc = NULL;
+err_free_buffer:
+ vfree(rx_ring->rx_buffer_info);
+err_return:
+ rx_ring->rx_buffer_info = NULL;
+ return -ENOMEM;
+}
+
+/**
+ * rnpgbe_free_rx_resources - Free Rx Resources
+ * @rx_ring: ring to clean the resources from
+ *
+ * RX DMA must be quiesced and all ring-held buffers released before this
+ * function is called.
+ **/
+static void rnpgbe_free_rx_resources(struct mucse_ring *rx_ring)
+{
+ vfree(rx_ring->rx_buffer_info);
+ rx_ring->rx_buffer_info = NULL;
+ /* if not set, then don't free */
+ if (!rx_ring->desc)
+ return;
+
+ dma_free_coherent(rx_ring->dev, rx_ring->size, rx_ring->desc,
+ rx_ring->dma);
+ rx_ring->desc = NULL;
+ if (rx_ring->page_pool) {
+ page_pool_destroy(rx_ring->page_pool);
+ rx_ring->page_pool = NULL;
+ }
+}
+
+/**
+ * rnpgbe_setup_all_rx_resources - allocate all queues Rx resources
+ * @mucse: pointer to private structure
+ *
+ * Return: 0 on success, negative on failure
+ **/
+int rnpgbe_setup_all_rx_resources(struct mucse *mucse)
+{
+ int i, err = 0;
+
+ for (i = 0; i < mucse->num_rx_queues; i++) {
+ err = rnpgbe_setup_rx_resources(mucse->rx_ring[i]);
+ if (!err)
+ continue;
+
+ goto err_setup_rx;
+ }
+
+ return 0;
+err_setup_rx:
+ while (i--)
+ rnpgbe_free_rx_resources(mucse->rx_ring[i]);
+ return err;
+}
+
+/**
+ * rnpgbe_free_all_rx_resources - Free Rx Resources for All Queues
+ * @mucse: pointer to private structure
+ *
+ * Free all receive software resources
+ **/
+void rnpgbe_free_all_rx_resources(struct mucse *mucse)
+{
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_free_rx_resources(mucse->rx_ring[i]);
+}
+
+/**
+ * rnpgbe_configure_rx_ring - Configure Rx ring info to hw
+ * @mucse: pointer to private structure
+ * @ring: structure containing ring specific data
+ *
+ * Configure the Rx descriptor ring after a reset.
+ **/
+static void rnpgbe_configure_rx_ring(struct mucse *mucse,
+ struct mucse_ring *ring)
+{
+ struct mucse_hw *hw = &mucse->hw;
+
+ /* Set the descriptor registers after RX DMA has quiesced. */
+ mucse_ring_wr32(ring, RNPGBE_RX_BASE_ADDR_LO, (u32)ring->dma);
+ mucse_ring_wr32(ring, RNPGBE_RX_BASE_ADDR_HI,
+ (u32)((u64)ring->dma >> 32) |
+ ((u32)hw->pfvfnum << 24));
+ mucse_ring_wr32(ring, RNPGBE_RX_LEN, ring->count);
+ ring->tail = ring->ring_addr + RNPGBE_RX_TAIL;
+ ring->next_to_clean = mucse_ring_rd32(ring, RNPGBE_RX_HEAD) %
+ ring->count;
+ ring->next_to_use = ring->next_to_clean;
+ ring->drop_status = false;
+ mucse_ring_wr32(ring, RNPGBE_RX_SG_LEN, M_DEFAULT_SG);
+ mucse_ring_wr32(ring, RNPGBE_RX_FETCH, M_DEFAULT_RX_FETCH);
+ mucse_ring_wr32(ring, RNPGBE_RX_TIMEOUT_TH, 0);
+ mucse_ring_wr32(ring, RNPGBE_RX_INT_TIMER,
+ M_DEFAULT_INT_TIMER_R * hw->cycles_per_us);
+ mucse_ring_wr32(ring, RNPGBE_RX_INT_PKTCNT, M_DEFAULT_RX_INT_PKTCNT);
+ /* Fill the ring before RX DMA is enabled. Any allocation shortfall is
+ * retried after NAPI is enabled by rnpgbe_up_complete().
+ */
+ rnpgbe_alloc_rx_buffers(ring, mucse_desc_unused_rx(ring));
+}
+
+/**
+ * rnpgbe_configure_rx - Configure Receive Unit after Reset
+ * @mucse: pointer to private structure
+ *
+ * Configure the Rx unit after a reset.
+ *
+ * Return: 0 on success, negative errno if RX DMA does not quiesce or
+ * an AXI fault is already latched
+ **/
+int rnpgbe_configure_rx(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+ int err;
+
+ err = rnpgbe_stop_all_rx_rings(mucse);
+ if (err)
+ return err;
+
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_configure_rx_ring(mucse, mucse->rx_ring[i]);
+
+ /* Ensure all ring configuration is visible before enabling RX DMA. */
+ wmb();
+ dma_axi_ctl = mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+ dma_axi_ctl |= RX_AXI_RW_EN;
+ mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
+ /* Ensure RX_AXI_RW_EN is visible before starting the rings. */
+ (void)mucse_hw_rd32(hw, RNPGBE_DMA_AXI_EN);
+
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ mucse_ring_wr32(mucse->rx_ring[i], RNPGBE_RX_START, 1);
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
index 1fe290ed224d..76b5b0c0ae4a 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -9,12 +9,27 @@ struct mucse_hw;
struct mucse_ring;
#define RING_OFFSET(n) (0x1000 + 0x100 * (n))
+#define RNPGBE_RX_START 0x10
#define RNPGBE_TX_START 0x18
#define RNPGBE_DMA_INT_MASK 0x24
#define TX_INT_MASK BIT(1)
#define RX_INT_MASK BIT(0)
#define INT_VALID (BIT(16) | BIT(17))
#define RNPGBE_DMA_INT_TRIG 0x2c /* lost-interrupt recovery trigger */
+#define RNPGBE_RX_BASE_ADDR_HI 0x30
+#define RNPGBE_RX_BASE_ADDR_LO 0x34
+#define RNPGBE_RX_LEN 0x38
+#define RNPGBE_RX_HEAD 0x3c
+#define RNPGBE_RX_TAIL 0x40
+#define M_DEFAULT_RX_FETCH 0x100020
+#define RNPGBE_RX_FETCH 0x44
+#define M_DEFAULT_INT_TIMER_R 30
+#define RNPGBE_RX_INT_TIMER 0x48
+#define M_DEFAULT_RX_INT_PKTCNT 64
+#define RNPGBE_RX_INT_PKTCNT 0x4c
+#define RNPGBE_RX_TIMEOUT_TH 0x54
+#define M_DEFAULT_SG 96 /* unit 16b, 1536 bytes */
+#define RNPGBE_RX_SG_LEN 0x58
#define RNPGBE_TX_BASE_ADDR_HI 0x60
#define RNPGBE_TX_BASE_ADDR_LO 0x64
#define RNPGBE_TX_LEN 0x68
@@ -37,13 +52,14 @@ struct mucse_ring;
/* 2 desc gap to keep tail from touching head */
/* 1 desc for context descriptor */
#define RESV_DESC_NEEDED 3
+#define RNPGBE_SKB_PAD (NET_SKB_PAD + NET_IP_ALIGN)
+
/* Hardware requires this to be nonzero */
#define M_DEFAULT_MAC_IP_LEN 20
#define mucse_for_each_ring(pos, head)\
for (typeof((head).ring) __pos = (head).ring;\
__pos ? ({ pos = __pos; 1; }) : 0;\
__pos = __pos->next)
-
int rnpgbe_init_interrupt_scheme(struct mucse *mucse);
void rnpgbe_clear_interrupt_scheme(struct mucse *mucse);
int rnpgbe_request_mbx_irq(struct mucse *mucse);
@@ -54,6 +70,7 @@ void rnpgbe_irq_disable(struct mucse *mucse);
void rnpgbe_down(struct mucse *mucse);
void rnpgbe_up_complete(struct mucse *mucse);
int rnpgbe_configure_tx(struct mucse *mucse);
+int rnpgbe_configure_rx(struct mucse *mucse);
void rnpgbe_clean_all_tx_rings(struct mucse *mucse);
int rnpgbe_setup_all_tx_resources(struct mucse *mucse);
void rnpgbe_free_all_tx_resources(struct mucse *mucse);
@@ -61,4 +78,6 @@ netdev_tx_t rnpgbe_xmit_frame_ring(struct sk_buff *skb,
struct mucse_ring *tx_ring);
void rnpgbe_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *stats);
+int rnpgbe_setup_all_rx_resources(struct mucse *mucse);
+void rnpgbe_free_all_rx_resources(struct mucse *mucse);
#endif
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index cdda5f3d09b2..b0363509a3c5 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -31,13 +31,19 @@ static struct pci_device_id rnpgbe_pci_tbl[] = {
* rnpgbe_configure - Configure the hardware
* @mucse: pointer to private structure
*
- * Configure Tx registers in hardware.
+ * Configure Tx and Rx registers in hardware.
*
* Return: 0 on success, negative errno if hardware configuration fails
**/
static int rnpgbe_configure(struct mucse *mucse)
{
- return rnpgbe_configure_tx(mucse);
+ int err;
+
+ err = rnpgbe_configure_tx(mucse);
+ if (err)
+ return err;
+
+ return rnpgbe_configure_rx(mucse);
}
/**
@@ -69,13 +75,18 @@ static int rnpgbe_open(struct net_device *netdev)
err = rnpgbe_setup_all_tx_resources(mucse);
if (err)
goto err_free_irqs;
+ err = rnpgbe_setup_all_rx_resources(mucse);
+ if (err)
+ goto err_free_tx;
err = rnpgbe_configure(mucse);
if (err)
- goto err_free_tx;
+ goto err_free_rx;
rnpgbe_up_complete(mucse);
return 0;
+err_free_rx:
+ rnpgbe_free_all_rx_resources(mucse);
err_free_tx:
rnpgbe_clean_all_tx_rings(mucse);
rnpgbe_free_all_tx_resources(mucse);
@@ -100,6 +111,7 @@ static int rnpgbe_close(struct net_device *netdev)
rnpgbe_down(mucse);
rnpgbe_free_irq(mucse);
rnpgbe_free_all_tx_resources(mucse);
+ rnpgbe_free_all_rx_resources(mucse);
return 0;
}
@@ -137,6 +149,7 @@ static const struct net_device_ops rnpgbe_netdev_ops = {
static void rnpgbe_sw_init(struct mucse *mucse)
{
mucse->tx_ring_item_count = M_DEFAULT_TXD;
+ mucse->rx_ring_item_count = M_DEFAULT_RXD;
mucse->tx_work_limit = M_DEFAULT_TX_WORK;
}
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v13 4/5] net: rnpgbe: Add receive mode support
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
` (2 preceding siblings ...)
2026-09-20 9:26 ` [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support Dong Yibo
@ 2026-09-20 9:26 ` Dong Yibo
2026-09-20 9:27 ` [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support Dong Yibo
4 siblings, 0 replies; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:26 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Add chip-level receive filtering and the ndo_set_rx_mode callback.
Program the primary and additional unicast addresses into the available
RAR entries, and use the 4096-bit multicast hash table for multicast
addresses. Advertise RAR-based secondary unicast filtering to the
networking core, so it does not force promiscuous mode for a non-empty
secondary unicast list. Fall back to accepting all unicast addresses if
the RAR table is exhausted, and handle IFF_PROMISC and IFF_ALLMULTI
through the chip-level filter control register.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 1 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 75 +++++++++++++++++++
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 15 ++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 12 ++-
4 files changed, 100 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index c1a72e8e2770..bd7491671e1e 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -231,6 +231,7 @@ int rnpgbe_send_notify(struct mucse_hw *hw,
bool enable,
int mode);
int rnpgbe_init_hw(struct mucse_hw *hw, int board_type);
+void rnpgbe_set_rx_mode(struct net_device *netdev);
/* Device IDs */
#define PCI_VENDOR_ID_MUCSE 0x8848
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
index 291e77d573fe..4e2835b71f32 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
@@ -149,3 +149,78 @@ int rnpgbe_init_hw(struct mucse_hw *hw, int board_type)
return 0;
}
+
+static void rnpgbe_set_rar(struct mucse_hw *hw, u32 index, const u8 *addr)
+{
+ u32 rar_low, rar_high;
+
+ /* The RAR stores the Ethernet address in reverse byte order. */
+ rar_low = (u32)addr[5] | ((u32)addr[4] << 8) |
+ ((u32)addr[3] << 16) | ((u32)addr[2] << 24);
+ rar_high = (u32)addr[1] | ((u32)addr[0] << 8) |
+ RNPGBE_RX_RAR_VALID;
+ mucse_hw_wr32(hw, RNPGBE_RX_RAR_HIGH(index),
+ rar_high & ~RNPGBE_RX_RAR_VALID);
+ mucse_hw_wr32(hw, RNPGBE_RX_RAR_LOW(index), rar_low);
+ mucse_hw_wr32(hw, RNPGBE_RX_RAR_HIGH(index), rar_high);
+}
+
+static void rnpgbe_clear_rar(struct mucse_hw *hw, u32 index)
+{
+ mucse_hw_wr32(hw, RNPGBE_RX_RAR_HIGH(index), 0);
+ mucse_hw_wr32(hw, RNPGBE_RX_RAR_LOW(index), 0);
+}
+
+void rnpgbe_set_rx_mode(struct net_device *netdev)
+{
+ u32 mcast_hash[RNPGBE_RX_MCAST_HASH_ENTRIES] = {};
+ u32 mcast_ctrl = RNPGBE_RX_UCAST_TABLE_EN;
+ u32 filter_ctrl = RNPGBE_RX_FILTER_BCAST;
+ struct netdev_hw_addr *ha;
+ struct mucse_hw *hw;
+ struct mucse *mucse;
+ int rar = 1;
+ u16 hash;
+ int i;
+
+ mucse = netdev_priv(netdev);
+ hw = &mucse->hw;
+
+ /* RAR 0 always holds the interface's primary unicast address. */
+ rnpgbe_set_rar(hw, 0, netdev->dev_addr);
+
+ netdev_for_each_uc_addr(ha, netdev) {
+ if (rar == RNPGBE_RX_RAR_ENTRIES) {
+ filter_ctrl |= RNPGBE_RX_FILTER_UCAST_ALL;
+ break;
+ }
+
+ rnpgbe_set_rar(hw, rar, ha->addr);
+ rar++;
+ }
+
+ for (; rar < RNPGBE_RX_RAR_ENTRIES; rar++)
+ rnpgbe_clear_rar(hw, rar);
+
+ if (netdev->flags & IFF_PROMISC) {
+ filter_ctrl |= RNPGBE_RX_FILTER_UCAST_ALL |
+ RNPGBE_RX_FILTER_MCAST_ALL;
+ } else if (netdev->flags & IFF_ALLMULTI) {
+ filter_ctrl |= RNPGBE_RX_FILTER_MCAST_ALL;
+ } else {
+ /* MTA type 0 uses ((addr[4] << 8) | addr[5]) & 0xfff. */
+ netdev_for_each_mc_addr(ha, netdev) {
+ hash = ((ha->addr[4] & 0xf) << 8) | ha->addr[5];
+ mcast_hash[hash >> 5] |= BIT(hash & 0x1f);
+ }
+
+ if (!netdev_mc_empty(netdev))
+ mcast_ctrl |= RNPGBE_RX_MCAST_HASH_EN;
+ }
+
+ for (i = 0; i < RNPGBE_RX_MCAST_HASH_ENTRIES; i++)
+ mucse_hw_wr32(hw, RNPGBE_RX_MCAST_HASH(i), mcast_hash[i]);
+
+ mucse_hw_wr32(hw, RNPGBE_RX_MCAST_CTRL, mcast_ctrl);
+ mucse_hw_wr32(hw, RNPGBE_RX_FILTER_CTRL, filter_ctrl);
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
index 1d87edfba3d7..feb84704d602 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -24,5 +24,20 @@
#define RNPGBE_DMA_AXI_EN 0x0010
#define RNPGBE_TX_MIN_PKT_LEN 33
+#define MUCSE_ETH_OFF(_n) (0x10000 + (_n))
+#define RNPGBE_RX_RAR_LOW(_n) MUCSE_ETH_OFF(0xa000 + 4 * (_n))
+#define RNPGBE_RX_RAR_HIGH(_n) MUCSE_ETH_OFF(0xa400 + 4 * (_n))
+#define RNPGBE_RX_MCAST_HASH(_n) MUCSE_ETH_OFF(0xac00 + 4 * (_n))
+#define RNPGBE_RX_FILTER_CTRL MUCSE_ETH_OFF(0x9110)
+#define RNPGBE_RX_MCAST_CTRL MUCSE_ETH_OFF(0x9114)
+#define RNPGBE_RX_RAR_VALID BIT(31)
+#define RNPGBE_RX_FILTER_BCAST BIT(10)
+#define RNPGBE_RX_FILTER_UCAST_ALL BIT(9)
+#define RNPGBE_RX_FILTER_MCAST_ALL BIT(8)
+#define RNPGBE_RX_MCAST_HASH_EN BIT(4)
+#define RNPGBE_RX_UCAST_TABLE_EN BIT(3)
+/* The final two of the 32 hardware RAR entries are reserved for NCSI. */
+#define RNPGBE_RX_RAR_ENTRIES 30
+#define RNPGBE_RX_MCAST_HASH_ENTRIES 128
#define RNPGBE_MAX_QUEUES 8
#endif /* _RNPGBE_HW_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index b0363509a3c5..35eb858eb2c1 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -43,6 +43,10 @@ static int rnpgbe_configure(struct mucse *mucse)
if (err)
return err;
+ netif_addr_lock_bh(mucse->netdev);
+ rnpgbe_set_rx_mode(mucse->netdev);
+ netif_addr_unlock_bh(mucse->netdev);
+
return rnpgbe_configure_rx(mucse);
}
@@ -140,9 +144,10 @@ static netdev_tx_t rnpgbe_xmit_frame(struct sk_buff *skb,
}
static const struct net_device_ops rnpgbe_netdev_ops = {
- .ndo_open = rnpgbe_open,
- .ndo_stop = rnpgbe_close,
- .ndo_start_xmit = rnpgbe_xmit_frame,
+ .ndo_open = rnpgbe_open,
+ .ndo_stop = rnpgbe_close,
+ .ndo_start_xmit = rnpgbe_xmit_frame,
+ .ndo_set_rx_mode = rnpgbe_set_rx_mode,
.ndo_get_stats64 = rnpgbe_get_stats64,
};
@@ -225,6 +230,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
}
netdev->netdev_ops = &rnpgbe_netdev_ops;
+ netdev->priv_flags |= IFF_UNICAST_FLT;
rnpgbe_sw_init(mucse);
err = rnpgbe_reset_hw(hw);
if (err) {
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
` (3 preceding siblings ...)
2026-09-20 9:26 ` [PATCH net-next v13 4/5] net: rnpgbe: Add receive mode support Dong Yibo
@ 2026-09-20 9:27 ` Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
4 siblings, 1 reply; 10+ messages in thread
From: Dong Yibo @ 2026-09-20 9:27 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig
Cc: netdev, linux-kernel, dong100, yaojun
Add link status handling for the RNPGBE driver.
Introduce link state data structures for speed, duplex and link state,
and process firmware link events delivered over the mailbox, since
firmware controls the link and phylink is not used. Add a service task
to handle pending events and manage carrier status with
netif_carrier_on()/netif_carrier_off().
Notify firmware when the interface is brought up and after the data path
is quiesced during teardown, and fail interface open if the firmware
port-up or link-report setup fails. Validate firmware link events before
updating the cached link state. Enable the GMAC receiver in receive-all
mode after the chip-level receive filter has been configured, and
document the driver-to-firmware link-state snapshot and reset semantics.
Configure firmware after reset to enable autonegotiation. Advertise
10/100Mb/s half- and full-duplex modes and 1000 Mb/s full-duplex mode.
Fail probe if firmware does not acknowledge the link setup command.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 15 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 54 +++-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 17 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 173 ++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 3 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 20 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c | 169 ++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h | 4 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 278 +++++++++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 59 ++++
10 files changed, 776 insertions(+), 16 deletions(-)
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index bd7491671e1e..10aff417b647 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -5,6 +5,7 @@
#define _RNPGBE_H
#include <linux/types.h>
+#include <linux/atomic.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/rcupdate.h>
@@ -33,11 +34,10 @@ struct mucse_mbx_info {
u32 fwpf_ctrl_base;
};
-/* Enum for firmware notification modes,
- * more modes (e.g., portup, link_report) will be added in future
- **/
enum {
mucse_fw_powerup,
+ mucse_fw_portup,
+ mucse_fw_link_report_en,
};
struct mucse_hw {
@@ -46,8 +46,11 @@ struct mucse_hw {
struct pci_dev *pdev;
struct mucse_mbx_info mbx;
int port;
+ int speed;
+ bool link;
u16 cycles_per_us;
u8 pfvfnum;
+ u8 duplex;
};
struct rnpgbe_tx_desc {
@@ -222,16 +225,22 @@ struct mucse {
int num_rx_queues;
char mbx_name[32];
unsigned long state;
+ atomic_t link_pending;
+ atomic_t mbx_irq_seq;
struct work_struct mbx_work;
+ struct delayed_work serv_task;
+ spinlock_t link_lock; /* spinlock for link update */
};
int rnpgbe_get_permanent_mac(struct mucse_hw *hw, u8 *perm_addr);
int rnpgbe_reset_hw(struct mucse_hw *hw);
+int rnpgbe_setup_default_link(struct mucse_hw *hw);
int rnpgbe_send_notify(struct mucse_hw *hw,
bool enable,
int mode);
int rnpgbe_init_hw(struct mucse_hw *hw, int board_type);
void rnpgbe_set_rx_mode(struct net_device *netdev);
+void rnpgbe_set_link(struct mucse_hw *hw, bool linkup);
/* Device IDs */
#define PCI_VENDOR_ID_MUCSE 0x8848
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
index 4e2835b71f32..b56263de4a36 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
@@ -4,12 +4,19 @@
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
#include "rnpgbe.h"
#include "rnpgbe_hw.h"
#include "rnpgbe_mbx.h"
#include "rnpgbe_mbx_fw.h"
+#define RNPGBE_DEFAULT_ADV (RNPGBE_FW_ADV_10_FULL | \
+ RNPGBE_FW_ADV_100_FULL | \
+ RNPGBE_FW_ADV_1000_FULL | \
+ RNPGBE_FW_ADV_10_HALF | \
+ RNPGBE_FW_ADV_100_HALF)
+
/**
* rnpgbe_get_permanent_mac - Get permanent mac
* @hw: hw information structure
@@ -53,6 +60,18 @@ int rnpgbe_reset_hw(struct mucse_hw *hw)
return mucse_mbx_reset_hw(hw);
}
+/**
+ * rnpgbe_setup_default_link - Configure the default link settings
+ * @hw: hardware information structure
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+int rnpgbe_setup_default_link(struct mucse_hw *hw)
+{
+ return mucse_mbx_set_link(hw, RNPGBE_DEFAULT_ADV, true, 0, 0,
+ ETH_TP_MDI_AUTO);
+}
+
/**
* rnpgbe_send_notify - Echo fw status
* @hw: hw information structure
@@ -66,11 +85,17 @@ int rnpgbe_send_notify(struct mucse_hw *hw,
int mode)
{
int err;
- /* Keep switch struct to support more modes in the future */
+
switch (mode) {
case mucse_fw_powerup:
err = mucse_mbx_powerup(hw, enable);
break;
+ case mucse_fw_portup:
+ err = mucse_mbx_phyup(hw, enable);
+ break;
+ case mucse_fw_link_report_en:
+ err = mucse_mbx_link_report(hw, enable);
+ break;
default:
err = -EINVAL;
}
@@ -224,3 +249,30 @@ void rnpgbe_set_rx_mode(struct net_device *netdev)
mucse_hw_wr32(hw, RNPGBE_RX_MCAST_CTRL, mcast_ctrl);
mucse_hw_wr32(hw, RNPGBE_RX_FILTER_CTRL, filter_ctrl);
}
+
+/**
+ * rnpgbe_set_link - Set the hardware link state
+ * @hw: hw information structure
+ * @linkup: link on or not
+ *
+ * rnpgbe_set_link setup link status
+ *
+ **/
+void rnpgbe_set_link(struct mucse_hw *hw, bool linkup)
+{
+ u32 value = mucse_hw_rd32(hw, GMAC_CONTROL);
+
+ /* The chip-level filter is programmed by ndo_set_rx_mode(). Keep the
+ * GMAC in receive-all mode so it does not discard frames accepted by
+ * that filter.
+ */
+ if (linkup) {
+ mucse_hw_wr32(hw, GMAC_FRAME_FILTER, GMAC_RX_ALL);
+ value |= GMAC_CONTROL_RE;
+ mucse_hw_wr32(hw, GMAC_CONTROL, value);
+ } else {
+ value &= ~GMAC_CONTROL_RE;
+ mucse_hw_wr32(hw, GMAC_CONTROL, value);
+ mucse_hw_wr32(hw, GMAC_FRAME_FILTER, 0);
+ }
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
index feb84704d602..5659e91f3497 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -21,6 +21,17 @@
#define RNPGBE_DMA_TX_STATUS GENMASK_U32(23, 22)
#define RNPGBE_DMA_RX_STATUS GENMASK_U32(21, 20)
#define RX_AXI_RW_EN 0x03
+/* RNPGBE_LINK_ST is a driver-owned link-state snapshot consumed by firmware.
+ * Set the driver-state marker; all other driver status fields start clear.
+ */
+#define M_DEFAULT_ST 0xa0000000
+/* Firmware never reports 0xf in the speed field. */
+#define M_INVALID_ST (M_DEFAULT_ST | GENMASK_U32(11, 8))
+/* Driver-reported fields: 25:24 pause, 11:8 speed, 6 LLDP, 4 duplex,
+ * and 0 link up/down. M_DEFAULT_ST resets these fields so firmware reports
+ * the current hardware state again.
+ */
+#define RNPGBE_LINK_ST 0x000c
#define RNPGBE_DMA_AXI_EN 0x0010
#define RNPGBE_TX_MIN_PKT_LEN 33
@@ -39,5 +50,11 @@
/* The final two of the 32 hardware RAR entries are reserved for NCSI. */
#define RNPGBE_RX_RAR_ENTRIES 30
#define RNPGBE_RX_MCAST_HASH_ENTRIES 128
+
+#define MUCSE_GMAC_OFF(_n) (0x20000 + (_n))
+#define GMAC_CONTROL_RE 0x00000004
+#define GMAC_CONTROL MUCSE_GMAC_OFF(0)
+#define GMAC_RX_ALL (BIT(31) | BIT(0))
+#define GMAC_FRAME_FILTER MUCSE_GMAC_OFF(0x4)
#define RNPGBE_MAX_QUEUES 8
#endif /* _RNPGBE_HW_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
index 9afd10273a62..2a5349f03a02 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -16,8 +16,14 @@
static void rnpgbe_mbx_work(struct work_struct *work)
{
struct mucse *mucse = container_of(work, struct mucse, mbx_work);
+ int irq_seq;
+ irq_seq = atomic_read(&mucse->mbx_irq_seq);
mucse_fw_irq_handler(&mucse->hw);
+
+ /* A mailbox interrupt while this work runs needs another invocation. */
+ if (irq_seq != atomic_read(&mucse->mbx_irq_seq))
+ queue_work(system_percpu_wq, &mucse->mbx_work);
}
/**
@@ -31,6 +37,7 @@ static irqreturn_t rnpgbe_msix_other(int irq, void *data)
{
struct mucse *mucse = (struct mucse *)data;
+ atomic_inc(&mucse->mbx_irq_seq);
queue_work(system_percpu_wq, &mucse->mbx_work);
return IRQ_HANDLED;
@@ -664,6 +671,7 @@ int rnpgbe_request_mbx_irq(struct mucse *mucse)
snprintf(mucse->mbx_name, sizeof(mucse->mbx_name),
"rnpgbe-mbx:%s", pci_name(pdev));
INIT_WORK(&mucse->mbx_work, rnpgbe_mbx_work);
+ atomic_set(&mucse->mbx_irq_seq, 0);
err = request_irq(pci_irq_vector(pdev, 0), rnpgbe_msix_other, 0,
mucse->mbx_name, mucse);
@@ -1399,7 +1407,19 @@ static void rnpgbe_cancel_rx_retry_timers(struct mucse *mucse)
void rnpgbe_down(struct mucse *mucse)
{
struct net_device *netdev = mucse->netdev;
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+ int err;
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ WRITE_ONCE(hw->link, false);
+ WRITE_ONCE(hw->speed, 0);
+ WRITE_ONCE(hw->duplex, 0);
+ atomic_set(&mucse->link_pending, 0);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ cancel_delayed_work_sync(&mucse->serv_task);
+
+ netif_carrier_off(netdev);
rnpgbe_irq_disable(mucse);
rnpgbe_napi_disable_all(mucse);
synchronize_net();
@@ -1408,22 +1428,77 @@ void rnpgbe_down(struct mucse *mucse)
rnpgbe_stop_all_rx_rings(mucse);
rnpgbe_cancel_rx_retry_timers(mucse);
rnpgbe_clean_all_tx_rings(mucse);
+
+ err = rnpgbe_send_notify(hw, false, mucse_fw_link_report_en);
+ if (err) {
+ dev_warn(&hw->pdev->dev, "Send link report to hw failed %d\n",
+ err);
+ dev_warn(&hw->pdev->dev, "Fw will still report link event\n");
+ }
+
+ err = rnpgbe_send_notify(hw, false, mucse_fw_portup);
+ if (err) {
+ dev_warn(&hw->pdev->dev, "Send port down to hw failed %d\n",
+ err);
+ dev_warn(&hw->pdev->dev, "Port is not truly down\n");
+ }
+ rnpgbe_set_link(hw, false);
rnpgbe_clean_all_rx_rings(mucse);
}
/**
* rnpgbe_up_complete - Final step for port up
* @mucse: pointer to private structure
+ *
+ * Return: 0 on success, negative errno if firmware setup fails
**/
-void rnpgbe_up_complete(struct mucse *mucse)
+int rnpgbe_up_complete(struct mucse *mucse)
{
struct net_device *netdev = mucse->netdev;
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+ int err;
rnpgbe_configure_msix(mucse);
rnpgbe_napi_enable_all(mucse);
+ /* Clear stale link state and tell firmware the driver hasn't
+ * seen any link yet. Firmware will re-assert LINK_CHANGE_EVT
+ * when link_report_en is sent below since RNPGBE_LINK_ST
+ * no longer matches the actual (possibly up) link state.
+ */
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ WRITE_ONCE(hw->link, false);
+ WRITE_ONCE(hw->speed, 0);
+ WRITE_ONCE(hw->duplex, 0);
+ atomic_set(&mucse->link_pending, 0);
+ /* echo fw driver now in default state */
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_DEFAULT_ST);
+ /* Keep the cached state and default snapshot atomic with mailbox
+ * link-event handling.
+ */
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
rnpgbe_schedule_rx_retry(mucse);
rnpgbe_irq_enable(mucse);
netif_tx_start_all_queues(netdev);
+ err = rnpgbe_send_notify(hw, true, mucse_fw_portup);
+ if (err) {
+ dev_err(&hw->pdev->dev,
+ "Failed to notify firmware that port is up: %d\n", err);
+ return err;
+ }
+ /* Firmware checks RNPGBE_LINK_ST (driver's last-received link state)
+ * and only asserts LINK_CHANGE_EVT when it differs from the actual link
+ * state AND link_report_en is true.
+ */
+ err = rnpgbe_send_notify(hw, true, mucse_fw_link_report_en);
+ if (err) {
+ dev_err(&hw->pdev->dev,
+ "Failed to enable firmware link reporting: %d\n",
+ err);
+ return err;
+ }
+
+ return 0;
}
/**
@@ -2048,3 +2123,99 @@ int rnpgbe_configure_rx(struct mucse *mucse)
return 0;
}
+
+/**
+ * rnpgbe_process_link_event - Consume a pending link event
+ * @mucse: pointer to the device private structure
+ * @link: link status snapshot
+ * @speed: link speed snapshot
+ * @duplex: link duplex snapshot
+ *
+ * Return: true if a link event was consumed, false otherwise
+ **/
+static bool rnpgbe_process_link_event(struct mucse *mucse, bool *link,
+ int *speed, u8 *duplex)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+
+ if (!atomic_xchg(&mucse->link_pending, 0))
+ return false;
+
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ *link = hw->link;
+ *speed = hw->speed;
+ *duplex = hw->duplex;
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+
+ return true;
+}
+
+/**
+ * rnpgbe_link_is_up - Update netif_carrier status and
+ * print link up message
+ * @mucse: pointer to the device private structure
+ * @speed: link speed snapshot
+ * @duplex: link duplex snapshot
+ **/
+static void rnpgbe_link_is_up(struct mucse *mucse, int speed, u8 duplex)
+{
+ struct net_device *netdev = mucse->netdev;
+
+ /* Only continue if link was previously down */
+ if (netif_carrier_ok(netdev))
+ return;
+
+ netdev_info(netdev, "NIC Link is Up %d Mbps, %s Duplex\n",
+ speed, duplex ? "Full" : "Half");
+ netif_carrier_on(netdev);
+}
+
+/**
+ * rnpgbe_link_is_down - Update netif_carrier status and
+ * print link down message
+ * @mucse: pointer to the private structure
+ **/
+static void rnpgbe_link_is_down(struct mucse *mucse)
+{
+ struct net_device *netdev = mucse->netdev;
+
+ /* Only continue if link was up previously */
+ if (!netif_carrier_ok(netdev))
+ return;
+ netdev_info(netdev, "NIC Link is Down\n");
+ netif_carrier_off(netdev);
+}
+
+/**
+ * rnpgbe_process_link_subtask - Process a link-state update
+ * @mucse: pointer to the device private structure
+ **/
+static void rnpgbe_process_link_subtask(struct mucse *mucse)
+{
+ bool link;
+ int speed;
+ u8 duplex;
+
+ if (!rnpgbe_process_link_event(mucse, &link, &speed, &duplex))
+ return;
+
+ if (link)
+ rnpgbe_link_is_up(mucse, speed, duplex);
+ else
+ rnpgbe_link_is_down(mucse);
+}
+
+/**
+ * rnpgbe_service_task - Manages and runs subtasks
+ * @work: pointer to work_struct containing our data
+ **/
+void rnpgbe_service_task(struct work_struct *work)
+{
+ struct mucse *mucse = container_of(work, struct mucse, serv_task.work);
+
+ if (!netif_running(mucse->netdev))
+ return;
+
+ rnpgbe_process_link_subtask(mucse);
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
index 76b5b0c0ae4a..08b7d22f98ee 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -68,7 +68,7 @@ int rnpgbe_request_irq(struct mucse *mucse);
void rnpgbe_free_irq(struct mucse *mucse);
void rnpgbe_irq_disable(struct mucse *mucse);
void rnpgbe_down(struct mucse *mucse);
-void rnpgbe_up_complete(struct mucse *mucse);
+int rnpgbe_up_complete(struct mucse *mucse);
int rnpgbe_configure_tx(struct mucse *mucse);
int rnpgbe_configure_rx(struct mucse *mucse);
void rnpgbe_clean_all_tx_rings(struct mucse *mucse);
@@ -80,4 +80,5 @@ void rnpgbe_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *stats);
int rnpgbe_setup_all_rx_resources(struct mucse *mucse);
void rnpgbe_free_all_rx_resources(struct mucse *mucse);
+void rnpgbe_service_task(struct work_struct *work);
#endif
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index 35eb858eb2c1..9ba53bb3629b 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -67,6 +67,7 @@ static int rnpgbe_open(struct net_device *netdev)
if (test_bit(__MUCSE_AXI_FAULT, &mucse->state))
return -EIO;
+ netif_carrier_off(netdev);
err = rnpgbe_request_irq(mucse);
if (err)
return err;
@@ -86,9 +87,16 @@ static int rnpgbe_open(struct net_device *netdev)
err = rnpgbe_configure(mucse);
if (err)
goto err_free_rx;
- rnpgbe_up_complete(mucse);
+ err = rnpgbe_up_complete(mucse);
+ if (err)
+ goto err_down;
return 0;
+err_down:
+ rnpgbe_down(mucse);
+ rnpgbe_free_all_rx_resources(mucse);
+ rnpgbe_free_all_tx_resources(mucse);
+ goto err_free_irqs;
err_free_rx:
rnpgbe_free_all_rx_resources(mucse);
err_free_tx:
@@ -237,6 +245,11 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
dev_err(&pdev->dev, "Hw reset failed %d\n", err);
goto err_powerdown;
}
+ err = rnpgbe_setup_default_link(hw);
+ if (err) {
+ dev_err(&pdev->dev, "Setup link failed %d\n", err);
+ goto err_powerdown;
+ }
err = rnpgbe_get_permanent_mac(hw, perm_addr);
if (!err) {
@@ -249,6 +262,10 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
goto err_powerdown;
}
+ INIT_DELAYED_WORK(&mucse->serv_task, rnpgbe_service_task);
+ spin_lock_init(&mucse->link_lock);
+ atomic_set(&mucse->link_pending, 0);
+
err = rnpgbe_init_interrupt_scheme(mucse);
if (err) {
dev_err(&pdev->dev, "init interrupt failed %d\n", err);
@@ -273,6 +290,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
netdev->hw_features |= NETIF_F_HIGHDMA;
}
+ netif_carrier_off(netdev);
err = register_netdev(netdev);
if (err)
goto err_remove_mbx;
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
index 2310f01df1f5..74f42be975c2 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
@@ -197,7 +197,8 @@ static int mucse_read_mbx_pf(struct mucse_hw *hw, __le32 *msg, u16 size)
* mucse_check_for_msg_pf - Check to see if the fw has sent mail
* @hw: pointer to the HW structure
*
- * Return: 0 if the fw has set the Status bit or else -EIO
+ * Return: 0 if firmware has posted a new message, -ENOMSG if there is no
+ * new message, or -EIO if the mailbox has been reset
**/
static int mucse_check_for_msg_pf(struct mucse_hw *hw)
{
@@ -205,12 +206,11 @@ static int mucse_check_for_msg_pf(struct mucse_hw *hw)
u16 fw_req;
fw_req = mucse_mbx_get_fwreq(mbx);
- /* chip's register is reset to 0 when rc send reset
- * mbx command. Return -EIO if in this state, others
- * fw == hw->mbx.fw_req means no new msg.
- **/
- if (fw_req == 0 || fw_req == hw->mbx.fw_req)
+ /* The request counter is reset to zero by a mailbox reset. */
+ if (!fw_req)
return -EIO;
+ if (fw_req == hw->mbx.fw_req)
+ return -ENOMSG;
return 0;
}
@@ -252,6 +252,68 @@ int mucse_poll_and_read_mbx(struct mucse_hw *hw, __le32 *msg, u16 size)
return mucse_read_mbx_pf(hw, msg, size);
}
+/**
+ * mucse_mbx_event_begin - Lock and read a pending firmware event
+ * @hw: pointer to the HW structure
+ * @msg: the message buffer
+ * @size: length of buffer
+ *
+ * On success the hardware mailbox remains locked. The caller must process
+ * the event and call mucse_mbx_event_end() to acknowledge it and release
+ * the hardware mailbox lock.
+ *
+ * Firmware releases the hardware mailbox lock before raising the event
+ * interrupt and does not access the shared window again until the PF
+ * acknowledges the event. Therefore, failure to acquire the lock indicates
+ * a mailbox fault rather than transient contention.
+ *
+ * Return: 0 on success, -ENOMSG if no event is pending, or another negative
+ * errno on failure
+ **/
+int mucse_mbx_event_begin(struct mucse_hw *hw, __le32 *msg, u16 size)
+{
+ const int size_in_words = size / sizeof(__le32);
+ struct mucse_mbx_info *mbx = &hw->mbx;
+ int off = MUCSE_MBX_FWPF_SHM;
+ int err;
+
+ err = mucse_check_for_msg_pf(hw);
+ if (err)
+ return err;
+
+ err = mucse_obtain_mbx_lock_pf(hw);
+ if (err)
+ return err;
+
+ /* Check again after taking ownership of the shared mailbox. */
+ err = mucse_check_for_msg_pf(hw);
+ if (err) {
+ mucse_release_mbx_lock_pf(hw, false);
+ return err;
+ }
+
+ for (int i = 0; i < size_in_words; i++)
+ msg[i] = cpu_to_le32(mbx_data_rd32(mbx, off + 4 * i));
+
+ return 0;
+}
+
+/**
+ * mucse_mbx_event_end - Acknowledge a firmware event and unlock the mailbox
+ * @hw: pointer to the HW structure
+ *
+ * Pair with a successful mucse_mbx_event_begin().
+ **/
+void mucse_mbx_event_end(struct mucse_hw *hw)
+{
+ struct mucse_mbx_info *mbx = &hw->mbx;
+
+ mbx_data_wr32(mbx, MUCSE_MBX_FWPF_SHM, 0);
+ hw->mbx.fw_req = mucse_mbx_get_fwreq(mbx);
+ mucse_mbx_inc_pf_ack(hw);
+ mucse_release_mbx_lock_pf(hw, false);
+}
+
/**
* mucse_mbx_get_fwack - Read fw ack from reg
* @mbx: pointer to the MBX structure
@@ -286,6 +348,36 @@ static void mucse_mbx_inc_pf_req(struct mucse_hw *hw)
mbx_data_wr32(mbx, MUCSE_MBX_PF2FW_CNT, val);
}
+/**
+ * mucse_ack_pending_event - Acknowledge a level-like firmware event
+ * @hw: pointer to the HW structure
+ *
+ * The caller must own the hardware mailbox lock.
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+static int mucse_ack_pending_event(struct mucse_hw *hw)
+{
+ struct mucse_mbx_info *mbx = &hw->mbx;
+ u16 fw_req;
+
+ fw_req = mucse_mbx_get_fwreq(mbx);
+ if (fw_req == hw->mbx.fw_req)
+ return 0;
+ if (!fw_req)
+ return -EIO;
+
+ /* Firmware only posts LINK_CHANGE_EVT asynchronously. It is level-like,
+ * so leaving the link snapshot unchanged lets firmware report the
+ * current state again if reporting stays enabled.
+ */
+ mbx_data_wr32(mbx, MUCSE_MBX_FWPF_SHM, 0);
+ hw->mbx.fw_req = fw_req;
+ mucse_mbx_inc_pf_ack(hw);
+
+ return 0;
+}
+
/**
* mucse_write_mbx_pf - Place a message in the mailbox
* @hw: pointer to the HW structure
@@ -320,6 +412,44 @@ static int mucse_write_mbx_pf(struct mucse_hw *hw, const __le32 *msg, u16 size)
return 0;
}
+/**
+ * mucse_write_mbx_pf_coalesce_event - Write around a level-like event
+ * @hw: pointer to the HW structure
+ * @msg: the message buffer
+ * @size: length of buffer
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+static int mucse_write_mbx_pf_coalesce_event(struct mucse_hw *hw,
+ const __le32 *msg, u16 size)
+{
+ const int size_in_words = size / sizeof(__le32);
+ struct mucse_mbx_info *mbx = &hw->mbx;
+ int err;
+
+ err = mucse_obtain_mbx_lock_pf(hw);
+ if (err)
+ return err;
+
+ err = mucse_ack_pending_event(hw);
+ if (err)
+ goto release;
+
+ for (int i = 0; i < size_in_words; i++)
+ mbx_data_wr32(mbx, MUCSE_MBX_FWPF_SHM + i * 4,
+ le32_to_cpu(msg[i]));
+
+ hw->mbx.fw_ack = mucse_mbx_get_fwack(mbx);
+ mucse_mbx_inc_pf_req(hw);
+ mucse_release_mbx_lock_pf(hw, true);
+
+ return 0;
+
+release:
+ mucse_release_mbx_lock_pf(hw, false);
+ return err;
+}
+
/**
* mucse_check_for_ack_pf - Check to see if the fw has ACKed
* @hw: pointer to the HW structure
@@ -381,6 +511,33 @@ int mucse_write_and_wait_ack_mbx(struct mucse_hw *hw, const __le32 *msg,
return mucse_poll_for_ack(hw);
}
+/**
+ * mucse_write_mbx_coalesce_event - Send a command while coalescing an event
+ * @hw: pointer to the HW structure
+ * @msg: the message buffer
+ * @size: length of buffer
+ *
+ * A pending link event is acknowledged without updating its snapshot, then
+ * the PF command is written while the hardware mailbox remains locked. If
+ * event reporting remains enabled, firmware reports the event again because
+ * its snapshot still differs from the hardware state. This function waits
+ * for firmware to acknowledge the PF command before returning.
+ *
+ * Return: 0 after firmware acknowledges the command, negative errno on
+ * failure
+ **/
+int mucse_write_mbx_coalesce_event(struct mucse_hw *hw,
+ const __le32 *msg, u16 size)
+{
+ int err;
+
+ err = mucse_write_mbx_pf_coalesce_event(hw, msg, size);
+ if (err)
+ return err;
+
+ return mucse_poll_for_ack(hw);
+}
+
/**
* mucse_mbx_reset - Reset mbx info, sync info from regs
* @hw: pointer to the HW structure
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
index 75b88b18b04d..50597dd9dde9 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
@@ -16,6 +16,10 @@
int mucse_write_and_wait_ack_mbx(struct mucse_hw *hw,
const __le32 *msg, u16 size);
+int mucse_write_mbx_coalesce_event(struct mucse_hw *hw,
+ const __le32 *msg, u16 size);
void mucse_init_mbx_params_pf(struct mucse_hw *hw);
int mucse_poll_and_read_mbx(struct mucse_hw *hw, __le32 *msg, u16 size);
+int mucse_mbx_event_begin(struct mucse_hw *hw, __le32 *msg, u16 size);
+void mucse_mbx_event_end(struct mucse_hw *hw);
#endif /* _RNPGBE_MBX_H */
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
index e28f3193aa85..6415b6430efc 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
@@ -3,6 +3,7 @@
#include <linux/if_ether.h>
#include <linux/bitfield.h>
+#include <linux/pci.h>
#include "rnpgbe.h"
#include "rnpgbe_mbx.h"
@@ -199,12 +200,283 @@ int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
}
/**
- * mucse_fw_irq_handler - Deferred firmware mailbox notification hook
+ * mucse_mbx_set_link - Configure firmware link settings
* @hw: pointer to the HW structure
+ * @advertising: firmware link-mode advertisement mask
+ * @autoneg: whether to enable autonegotiation
+ * @speed: forced link speed when autonegotiation is disabled
+ * @duplex: forced duplex mode when autonegotiation is disabled
+ * @mdix_ctrl: MDI/MDI-X control mode
*
- * No asynchronous firmware events are enabled until link status support is
- * added.
+ * Firmware ignores @speed, @duplex when @autoneg is true.
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+int mucse_mbx_set_link(struct mucse_hw *hw, u32 advertising, bool autoneg,
+ u32 speed, u32 duplex, u32 mdix_ctrl)
+{
+ union mbx_fw_cmd_req_u req = {
+ .r = {
+ .datalen = cpu_to_le16(sizeof(req.r.phy_link_set) +
+ MUCSE_MBX_REQ_HDR_LEN),
+ .opcode = cpu_to_le16(PHY_LINK_SET),
+ .phy_link_set = {
+ .adv_speed_mask = cpu_to_le32(advertising),
+ .autoneg = cpu_to_le32(autoneg),
+ .speed = cpu_to_le32(speed),
+ .duplex = cpu_to_le32(duplex),
+ .nr_lane = cpu_to_le32(hw->port),
+ .tp_mdix_ctrl = cpu_to_le32(mdix_ctrl),
+ },
+ },
+ };
+ int len, err;
+
+ len = le16_to_cpu(req.r.datalen);
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_write_and_wait_ack_mbx(hw, req.dwords, len);
+ mutex_unlock(&hw->mbx.lock);
+
+ return err;
+}
+
+/**
+ * mucse_mbx_phyup - Request that firmware bring the PHY up or down
+ * @hw: pointer to the HW structure
+ * @is_phyup: true for up, false for down
+ *
+ * mucse_mbx_phyup echo fw to change phy status
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+int mucse_mbx_phyup(struct mucse_hw *hw, bool is_phyup)
+{
+ union mbx_fw_cmd_req_u req = {
+ .r = {
+ .datalen = cpu_to_le16(sizeof(req.r.phy_status) +
+ MUCSE_MBX_REQ_HDR_LEN),
+ .opcode = cpu_to_le16(SET_PHY_UP),
+ .phy_status = {
+ .port_mask = cpu_to_le32(BIT(hw->port)),
+ .status = cpu_to_le32(is_phyup ? 1 : 0),
+ },
+ },
+ };
+ int len, err;
+
+ len = le16_to_cpu(req.r.datalen);
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_write_mbx_coalesce_event(hw, req.dwords, len);
+ mutex_unlock(&hw->mbx.lock);
+
+ return err;
+}
+
+/**
+ * mucse_mbx_link_report - Configure firmware link-change event reporting
+ * @hw: pointer to the HW structure
+ * @is_report: true for report, false for no
+ *
+ * mucse_mbx_link_report echo fw to change event report state
+ *
+ * Return: 0 on success, negative errno on failure
+ **/
+int mucse_mbx_link_report(struct mucse_hw *hw, bool is_report)
+{
+ union mbx_fw_cmd_req_u req = {
+ .r = {
+ .datalen = cpu_to_le16(sizeof(req.r.report_status) +
+ MUCSE_MBX_REQ_HDR_LEN),
+ .opcode = cpu_to_le16(LINK_REPORT_EN),
+ .report_status = {
+ .port_mask = cpu_to_le16(BIT(hw->port)),
+ .status = cpu_to_le16(is_report ? 1 : 0),
+ },
+ },
+ };
+ int len, err;
+
+ len = le16_to_cpu(req.r.datalen);
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_write_mbx_coalesce_event(hw, req.dwords, len);
+ mutex_unlock(&hw->mbx.lock);
+
+ return err;
+}
+
+static bool mucse_link_speed_valid(const struct mbx_fw_cmd_req *req)
+{
+ switch (le16_to_cpu(req->link_stat.st.speed)) {
+ case 10:
+ case 100:
+ case 1000:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mucse_link_is_up(const struct mucse_hw *hw,
+ const struct mbx_fw_cmd_req *req)
+{
+ return le16_to_cpu(req->link_stat.port_status) & BIT(hw->port);
+}
+
+/**
+ * mucse_update_link_status_reg - update driver speed inf to reg
+ * @hw: pointer to the HW structure
+ * @req: pointer to req data
+ *
+ * Update the driver's link-state snapshot exported to firmware. Firmware
+ * sends a new event when this snapshot differs from the hardware state.
+ * The default snapshot clears the driver-reported fields;
+ * a valid event then repopulates them, including the LLDP status in bit 6.
+ *
+ **/
+static void mucse_update_link_status_reg(struct mucse_hw *hw,
+ struct mbx_fw_cmd_req *req)
+{
+ u16 status = le16_to_cpu(req->link_stat.st.status);
+ u16 speed = le16_to_cpu(req->link_stat.st.speed);
+ u32 value;
+
+ value = M_DEFAULT_ST;
+
+ if (mucse_link_is_up(hw, req)) {
+ value |= BIT(0);
+ switch (speed) {
+ case 10:
+ value |= (mucse_speed_10 << 8);
+ break;
+ case 100:
+ value |= (mucse_speed_100 << 8);
+ break;
+ case 1000:
+ value |= (mucse_speed_1000 << 8);
+ break;
+ default:
+ break;
+ }
+
+ value |= FIELD_PREP(BIT(4),
+ !!(req->link_stat.st.flags & DUPLEX_BIT));
+ value |= FIELD_PREP(GENMASK_U32(25, 24),
+ status & GENMASK(1, 0));
+ } else {
+ value &= ~BIT(0);
+ }
+
+ if (status & ST_STATUS_LLDP_STATUS_MASK)
+ value |= BIT(6);
+ else
+ value &= ~BIT(6);
+
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, value);
+}
+
+/**
+ * mucse_mbx_fw_req_handler - Handle fw req
+ * @hw: pointer to the HW structure
+ * @req: pointer to req data
+ *
+ * mucse_mbx_fw_req_handler handler fw req, such as a link event req.
+ **/
+static void mucse_mbx_fw_req_handler(struct mucse_hw *hw,
+ struct mbx_fw_cmd_req *req)
+{
+ struct mucse *mucse = container_of(hw, struct mucse, hw);
+ u32 magic = le32_to_cpu(req->link_stat.port_magic);
+ unsigned long flags;
+
+ if (le16_to_cpu(req->opcode) == LINK_CHANGE_EVT) {
+ u16 speed = le16_to_cpu(req->link_stat.st.speed);
+
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ if (magic != ST_VALID_MAGIC) {
+ /* Do not change the cached state for an invalid event.
+ * Use an invalid speed encoding to make firmware report
+ * again.
+ */
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_INVALID_ST);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ return;
+ }
+
+ if (mucse_link_is_up(hw, req) &&
+ !mucse_link_speed_valid(req)) {
+ /* Do not acknowledge an invalid speed as valid.
+ * Keep the snapshot mismatched so firmware retries it.
+ * Firmware limits link reports to one per 500 ms.
+ */
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_DEFAULT_ST);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ dev_warn_ratelimited(&hw->pdev->dev,
+ "unsupported link speed %u Mbps\n",
+ speed);
+ return;
+ }
+
+ if (!netif_running(mucse->netdev)) {
+ rnpgbe_set_link(hw, false);
+ mucse_update_link_status_reg(hw, req);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ return;
+ }
+
+ if (mucse_link_is_up(hw, req))
+ WRITE_ONCE(hw->link, true);
+ else
+ WRITE_ONCE(hw->link, false);
+
+ WRITE_ONCE(hw->speed, le16_to_cpu(req->link_stat.st.speed));
+ WRITE_ONCE(hw->duplex, req->link_stat.st.flags & DUPLEX_BIT);
+ rnpgbe_set_link(hw, mucse_link_is_up(hw, req));
+ /* update regs to notify link info is received */
+ mucse_update_link_status_reg(hw, req);
+ atomic_set_release(&mucse->link_pending, 1);
+ /* Run link handling immediately. */
+ mod_delayed_work(system_percpu_wq, &mucse->serv_task, 0);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ }
+}
+
+/**
+ * mucse_fw_handle_event - Handle one pending firmware event
+ * @hw: pointer to the hardware structure
+ **/
+static void mucse_fw_handle_event(struct mucse_hw *hw)
+{
+ union mbx_fw_cmd_req_u msg = {};
+ int err;
+
+ /* try to check and read fw req */
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_mbx_event_begin(hw, msg.dwords, sizeof(msg));
+ /* A PF command may have coalesced the event before stale mailbox work
+ * runs, in which case there is no event left to handle.
+ */
+ if (err) {
+ mutex_unlock(&hw->mbx.lock);
+ if (err != -ENOMSG)
+ dev_warn_ratelimited(&hw->pdev->dev,
+ "failed to read firmware event: %d\n",
+ err);
+ return;
+ }
+
+ mucse_mbx_fw_req_handler(hw, &msg.r);
+ mucse_mbx_event_end(hw);
+ mutex_unlock(&hw->mbx.lock);
+}
+
+/**
+ * mucse_fw_irq_handler - Handle one pending firmware mailbox event
+ * @hw: pointer to the HW structure
+ *
+ * Process at most one event per work-item invocation. The caller requeues
+ * mailbox work when a dedicated mailbox interrupt arrives during handling.
**/
void mucse_fw_irq_handler(struct mucse_hw *hw)
{
+ mucse_fw_handle_event(hw);
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
index a6bf5de55aa3..93ce0e796442 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
@@ -14,9 +14,19 @@ enum MUCSE_FW_CMD {
GET_HW_INFO = 0x0601,
GET_MAC_ADDRESS = 0x0602,
RESET_HW = 0x0603,
+ LINK_CHANGE_EVT = 0x0608,
+ LINK_REPORT_EN = 0x0613,
+ PHY_LINK_SET = 0x0630,
+ SET_PHY_UP = 0x0800,
POWER_UP = 0x0803,
};
+#define RNPGBE_FW_ADV_10_FULL BIT(2)
+#define RNPGBE_FW_ADV_100_FULL BIT(3)
+#define RNPGBE_FW_ADV_1000_FULL BIT(4)
+#define RNPGBE_FW_ADV_10_HALF BIT(10)
+#define RNPGBE_FW_ADV_100_HALF BIT(11)
+
struct mucse_hw_info {
u8 link_stat;
u8 port_mask;
@@ -36,6 +46,16 @@ struct mucse_hw_info {
__le32 ext_info;
} __packed;
+#define ST_STATUS_LLDP_STATUS_MASK BIT(12)
+
+#define DUPLEX_BIT BIT(0)
+struct st_status {
+ u8 phyid;
+ u8 flags;
+ __le16 speed;
+ __le16 status;
+} __packed;
+
struct mbx_fw_cmd_req {
__le16 flags;
__le16 opcode;
@@ -55,6 +75,32 @@ struct mbx_fw_cmd_req {
__le32 port_mask;
__le32 pfvf_num;
} get_mac_addr;
+ struct {
+ __le32 port_mask;
+ __le32 status;
+ } phy_status;
+ struct {
+ __le32 adv_speed_mask;
+ __le32 autoneg;
+ __le32 speed;
+ __le32 duplex;
+ __le32 nr_lane;
+ __le32 tp_mdix_ctrl;
+ } phy_link_set;
+ struct {
+ /* LINK_REPORT_EN uses its own 16-bit payload layout:
+ * status precedes port_mask.
+ */
+ __le16 status;
+ __le16 port_mask;
+ } report_status;
+ struct {
+ __le16 changed_lanes;
+ __le16 port_status;
+ __le32 port_magic;
+#define ST_VALID_MAGIC 0xa4a6a8a9
+ struct st_status st;
+ } link_stat;
};
} __packed;
@@ -94,10 +140,23 @@ union mbx_fw_cmd_reply_u {
__le32 dwords[sizeof(struct mbx_fw_cmd_reply) / sizeof(__le32)];
};
+/* Firmware encoding for RNPGBE_LINK_ST[11:8]. Valid link events report
+ * only these three speeds.
+ */
+enum mucse_speed {
+ mucse_speed_10 = 0,
+ mucse_speed_100 = 1,
+ mucse_speed_1000 = 2,
+};
+
int mucse_mbx_sync_fw(struct mucse_hw *hw);
int mucse_mbx_powerup(struct mucse_hw *hw, bool is_powerup);
int mucse_mbx_reset_hw(struct mucse_hw *hw);
int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
u8 *mac_addr, int port);
+int mucse_mbx_set_link(struct mucse_hw *hw, u32 advertising, bool autoneg,
+ u32 speed, u32 duplex, u32 mdix_ctrl);
+int mucse_mbx_phyup(struct mucse_hw *hw, bool is_phyup);
+int mucse_mbx_link_report(struct mucse_hw *hw, bool is_report);
void mucse_fw_irq_handler(struct mucse_hw *hw);
#endif /* _RNPGBE_MBX_FW_H */
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling
2026-09-20 9:26 ` [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling Dong Yibo
@ 2026-09-21 10:20 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 10:20 UTC (permalink / raw)
To: dong100
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig, netdev, linux-kernel, yaojun
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 3
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).
- [Medium] rnpgbe_dev_shutdown() (the .shutdown callback) now performs
the same interrupt teardown as rnpgbe_rm_adapter() (the .remove path):…
- [Low] rnpgbe_lib.h declares 'void rnpgbe_irq_disable(struct mucse
*mucse);' as a cross-file interface, but the only callers are inside…
- [Low] CONFIG_MGBE only 'depends on PCI', so CONFIG_MGBE=y/m is
selectable with CONFIG_PCI_MSI=n, but after this patch the driver…
- [Low] Reported concern: nothing in the interrupt or NAPI path
acknowledges a ring's pending interrupt cause.
--
net: rnpgbe: Add interrupt handling
This patch adds MSI-X setup and teardown for the rnpgbe driver, reserving
vector 0 for the mailbox and giving the remaining vectors to the data
path. It also adds a mailbox work item plus the NAPI skeleton, and wires
the new interrupt scheme into open/close, probe, remove and shutdown.
A few questions below about the new code.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> new file mode 100644
> index 000000000000..9196fa130ca9
> --- /dev/null
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> @@ -0,0 +1,571 @@
[ ... ]
> +static void rnpgbe_irq_enable_queues(struct mucse_q_vector *q_vector)
> +{
> + struct mucse_ring *ring;
> +
> + /*
> + * TX/RX pairs share interrupt control registers; update them through
> + * the TX ring list.
> + */
> + mucse_for_each_ring(ring, q_vector->tx) {
> + writel(0, ring->irq_mask);
> +
> + /* Re-trigger hw to re-check events lost while masked. */
> + writel(INT_VALID | TX_INT_MASK | RX_INT_MASK, ring->trig);
> + }
> +}
> +
> +/**
> + * rnpgbe_poll - NAPI polling callback
> + * @napi: structure for representing this polling device
> + * @budget: polling budget
> + *
> + * Complete NAPI polling and re-enable queue interrupts. Ring cleaning is
> + * added when TX and RX support is enabled.
> + *
> + * Return: 0
> + **/
> +static int rnpgbe_poll(struct napi_struct *napi, int budget)
> +{
> + struct mucse_q_vector *q_vector =
> + container_of(napi, struct mucse_q_vector, napi);
> + int work_done = 0;
> +
> + /* Exit if we are called by netpoll */
> + if (unlikely(!budget))
> + return 0;
> +
> + if (likely(napi_complete_done(napi, work_done)))
> + rnpgbe_irq_enable_queues(q_vector);
> +
> + return work_done;
> +}
[Severity: Low]
This isn't a bug as far as I can tell, but it is worth confirming the
interrupt cause handling here.
Nothing in the new interrupt path acknowledges a ring's pending cause.
rnpgbe_msix_clean_rings() only masks and schedules NAPI:
mucse_for_each_ring(ring, q_vector->tx) {
writel(INT_VALID, ring->trig);
writel((RX_INT_MASK | TX_INT_MASK), ring->irq_mask);
}
rnpgbe_poll() then processes no descriptors (work_done stays 0), so
napi_complete_done() always succeeds and rnpgbe_irq_enable_queues()
unmasks and re-writes the lost-interrupt recovery trigger.
If the trigger write asks hardware to re-evaluate a still pending cause,
would that re-assert the vector and loop irq -> NAPI -> unmask -> irq
forever?
Checking the state at this commit suggests it cannot fire: rnpgbe_reset_hw()
leaves RNPGBE_DMA_AXI_EN at 0 and this patch programs no descriptor bases,
lengths or ring starts, so the DMA engines cannot raise a completion for the
trigger to re-check. The real ring cleaning arrives later with
rnpgbe_clean_tx_irq() in "net: rnpgbe: Add basic TX packet transmission
support" and rnpgbe_clean_rx_irq() in "net: rnpgbe: Add basic RX data path
support", and the same unconditional trigger write survives there without
spinning.
Also, rnpgbe_up_complete() arms per-ring interrupt generation for rings
whose DMA and descriptor state has never been programmed at this point in
the series - is that intentional ordering for the later patches?
[ ... ]
> +static int rnpgbe_set_interrupt_capability(struct mucse *mucse)
> +{
> + int v_budget;
> +
> + v_budget = min3(mucse->num_tx_queues, mucse->num_rx_queues,
> + MAX_Q_VECTORS);
> + v_budget = min_t(int, v_budget, num_online_cpus());
> + /* add one vector for mbx */
> + v_budget += 1;
> +
> + /* Vector 0 is reserved for mailbox events and must not share NAPI. */
> + v_budget = pci_alloc_irq_vectors(mucse->pdev, 2, v_budget,
> + PCI_IRQ_MSIX);
> + if (v_budget < 0)
> + return v_budget;
[Severity: Low]
Should CONFIG_MGBE gain a PCI_MSI dependency now that two MSI-X vectors
are mandatory?
drivers/net/ethernet/mucse/Kconfig still has only:
config MGBE
tristate "Mucse(R) 1GbE PCI Express adapters support"
depends on PCI
With CONFIG_PCI=y and CONFIG_PCI_MSI=n the driver still builds, but the
stub in include/linux/pci.h rejects this request:
static inline int
pci_alloc_irq_vectors_affinity(struct pci_dev *dev, unsigned int min_vecs,
unsigned int max_vecs, unsigned int flags,
struct irq_affinity *aff_desc)
{
if ((flags & PCI_IRQ_INTX) && min_vecs == 1 && dev->irq)
return 1;
return -ENOSPC;
}
min_vecs is 2 and PCI_IRQ_INTX is not set, so every probe fails through
rnpgbe_init_interrupt_scheme() with "init interrupt failed -28". The
Kconfig entry is unchanged at the end of the series, so would
"depends on PCI && PCI_MSI" match what the code now requires?
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> new file mode 100644
> index 000000000000..d38c08df6b7c
> --- /dev/null
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> @@ -0,0 +1,34 @@
[ ... ]
> +int rnpgbe_request_irq(struct mucse *mucse);
> +void rnpgbe_free_irq(struct mucse *mucse);
> +void rnpgbe_irq_disable(struct mucse *mucse);
[Severity: Low]
Can rnpgbe_irq_disable() be static instead? Its only callers are the two
calls in rnpgbe_down() in the same file:
void rnpgbe_down(struct mucse *mucse)
{
rnpgbe_irq_disable(mucse);
rnpgbe_napi_disable_all(mucse);
synchronize_net();
rnpgbe_irq_disable(mucse);
}
Grepping drivers/net/ethernet/mucse/ shows no caller outside
rnpgbe_lib.c, at this commit or at the end of the series.
On a related note, the same patch adds mucse_hw_rd32() to rnpgbe.h with no
caller at this commit (the new register reads use the per-ring iomem
pointers, for example readl(q_vector->tx.ring->irq_mask)). Callers do
appear in the following patches, so that half is just an observation.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> index 70a2b0082ba8..debed2f3fe64 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
[ ... ]
> @@ -253,9 +298,11 @@ static void rnpgbe_rm_adapter(struct pci_dev *pdev)
> return;
> netdev = mucse->netdev;
> unregister_netdev(netdev);
> + rnpgbe_free_mbx_irq(mucse);
> err = rnpgbe_send_notify(hw, false, mucse_fw_powerup);
> if (err)
> dev_warn(&pdev->dev, "Send powerdown to hw failed %d\n", err);
> + rnpgbe_clear_interrupt_scheme(mucse);
> free_netdev(netdev);
> }
>
> @@ -287,8 +334,11 @@ static void rnpgbe_dev_shutdown(struct pci_dev *pdev)
> rtnl_lock();
> netif_device_detach(netdev);
> if (netif_running(netdev))
> - rnpgbe_close(netdev);
> + dev_close(netdev);
> rtnl_unlock();
> +
> + rnpgbe_free_mbx_irq(mucse);
> + rnpgbe_clear_interrupt_scheme(mucse);
> pci_disable_device(pdev);
> }
[Severity: Medium]
What happens if .shutdown runs and the device is then unbound? Both
rnpgbe_dev_shutdown() and rnpgbe_rm_adapter() now perform the same
teardown, and nothing records that it already ran:
rnpgbe_free_mbx_irq(mucse);
rnpgbe_clear_interrupt_scheme(mucse);
rnpgbe_free_mbx_irq() is unconditional:
void rnpgbe_free_mbx_irq(struct mucse *mucse)
{
struct pci_dev *pdev = mucse->pdev;
free_irq(pci_irq_vector(pdev, 0), mucse);
cancel_work_sync(&mucse->mbx_work);
}
After the first rnpgbe_clear_interrupt_scheme() has called
pci_free_irq_vectors(), MSI-X is disabled, so pci_irq_vector(pdev, 0)
takes the !msi_enabled && !msix_enabled path and returns pdev->irq, the
legacy INTx line. The second free_irq() is then issued with
dev_id == mucse against a line the driver never requested, which should
produce the "Trying to free already-free IRQ" warning from __free_irq().
mucse->num_q_vectors = 0 covers the q_vector frees and the data-path
free_irq() loop, but there is no equivalent guard for the mailbox vector.
The netdev is also left registered and bound after shutdown while the
q_vectors and NAPI contexts have been destroyed - is that intended?
This looks like a narrow cold path (device_shutdown() blocks probing but
not removal, so a sysfs unbind or PCI hot-unplug racing reboot can reach
it), and the duplicated teardown is unchanged at the end of the series.
Would a flag, or dropping the teardown from the shutdown path, be
preferable?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/B8AF0FDE24FAFB51%2B20260920092424.345309-1-dong100%40mucse.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support
2026-09-20 9:26 ` [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
@ 2026-09-21 10:20 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 10:20 UTC (permalink / raw)
To: dong100
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig, netdev, linux-kernel, yaojun
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 3
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).
- [Medium] rnpgbe_probe() replaces the previously error-checked
`dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(56))` (which aborted…
- [Medium] In this patch ndo_start_xmit becomes reachable before the TX
ring software resources exist: rnpgbe_add_adapter() calls…
- [Low] In drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h,
RESV_DESC_NEEDED is documented as "2 desc gap to keep tail from…
- [Low] Two headers touched by the patch are not self-contained.
- [Low] The patch builds an RCU protocol for the
mucse->tx_ring[]/rx_ring[] slots (adds `struct rcu_head rcu` to struct…
--
net: rnpgbe: Add basic TX packet transmission support
This adds the TX path for rnpgbe: the TX descriptor layout and buffer
management, rnpgbe_xmit_frame_ring(), TX ring setup/teardown, TX completion
handling in rnpgbe_clean_tx_irq(), and per-ring TX stats reported through
ndo_get_stats64(). It also switches the DMA mask setup to
dma_set_mask_and_coherent() and enables NETIF_F_SG/NETIF_F_HIGHDMA.
A few questions below.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
> index 40e41521a279..864cbf0caf44 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
[ ... ]
> @@ -69,31 +135,37 @@ struct mucse_q_vector {
> int hw_vector;
> struct mucse_ring_container rx, tx;
> struct napi_struct napi;
> + struct rcu_head rcu;
> char name[IFNAMSIZ + 18];
> /* for dynamic allocation of rings associated with this q_vector */
> struct mucse_ring ring[] ____cacheline_internodealigned_in_smp;
> };
>
> -struct mucse_stats {
> - u64 tx_dropped;
> -};
> -
> #define MAX_Q_VECTORS 8
>
> +#define M_DEFAULT_TXD 512
> +#define M_DEFAULT_TX_WORK 256
> +
> +enum mucse_state_t {
> + __MUCSE_AXI_FAULT,
> +};
> +
> struct mucse {
> struct net_device *netdev;
> struct pci_dev *pdev;
> struct mucse_hw hw;
> - struct mucse_stats stats;
> struct mucse_ring *tx_ring[RNPGBE_MAX_QUEUES]
> ____cacheline_aligned_in_smp;
> struct mucse_ring *rx_ring[RNPGBE_MAX_QUEUES]
> ____cacheline_aligned_in_smp;
[Severity: Low]
The patch introduces an RCU protocol for these slots: struct mucse_q_vector
gains an rcu_head, rnpgbe_free_q_vector() switches from kfree() to
kfree_rcu(), and rnpgbe_get_stats64() reads the slots under
rcu_read_lock(). Should the arrays carry __rcu and should the publish and
consume sides use rcu_assign_pointer()/rcu_dereference() rather than
WRITE_ONCE()/READ_ONCE()?
In rnpgbe_alloc_q_vector() the store is:
ring->pfvfnum = hw->pfvfnum;
u64_stats_init(&ring->syncp);
WRITE_ONCE(mucse->tx_ring[ring->queue_index], ring);
WRITE_ONCE() carries no release semantics, so the pointer store is not
ordered against the preceding ring->dev/ring->count/ring->netdev stores or
u64_stats_init(&ring->syncp), and rnpgbe_get_stats64() reads the slot with a
plain READ_ONCE().
Today this looks unreachable: rnpgbe_alloc_q_vector() runs from
rnpgbe_init_interrupt_scheme() before register_netdev(), and
rnpgbe_free_q_vector() runs either on the probe error path or after
unregister_netdev(), so no ndo_get_stats64 caller can race. Would it be
worth completing the idiom now, before any runtime queue reconfiguration or
reset path is added, and so sparse/RCU checkers can validate it?
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> index 0dce78e4a91b..6dc29ebe6fa7 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> @@ -7,13 +7,20 @@
> +#define RNPGBE_DMA_STATUS 0x0008
> +#define TX_AXI_RW_EN 0xc
> +/* DMA_STATUS_REG[23:20]: tx_wr, tx_rd, rx_wr, rx_rd done status. */
> +#define RNPGBE_DMA_TX_STATUS GENMASK_U32(23, 22)
[Severity: Low]
This isn't a bug, but rnpgbe_hw.h has no #include lines at all while now
using GENMASK_U32() (and BIT() already). It builds only because the .c files
happen to pull in the right headers first. Would adding
#include <linux/bits.h> here make sense? The driver's other GENMASK_U32
users, rnpgbe_mbx.c and rnpgbe_mbx_fw.c, do include <linux/bitfield.h>.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> index 9196fa130ca9..af5cef987051 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
[ ... ]
> +static int rnpgbe_tx_map(struct mucse_ring *tx_ring,
> + struct mucse_tx_buffer *first, u32 mac_ip_len,
> + u32 tx_flags)
> +{
[ ... ]
> + if (size) {
> + dma = dma_map_single(tx_ring->dev, skb->data, size,
> + DMA_TO_DEVICE);
> + first->mapped_as_page = false;
[ ... ]
> + /* write last descriptor with RS and EOP bits */
> + tx_desc->vlan_cmd_bsz = build_ctob(tx_flags | M_TXD_CMD_EOP |
> + M_TXD_CMD_RS,
> + mac_ip_len, size);
[Severity: Low]
This isn't a bug, but rnpgbe_tx_map() only ever writes data descriptors
(pkt_addr plus build_ctob()), with tx_flags hardcoded to 0 and mac_ip_len
fixed at M_DEFAULT_MAC_IP_LEN, so the "1 desc for context descriptor"
comment on RESV_DESC_NEEDED in rnpgbe_lib.h describes a descriptor class the
code never emits. Should the comment be trimmed to the two-descriptor gap
until the offload code that needs the context descriptor lands?
[ ... ]
> +netdev_tx_t rnpgbe_xmit_frame_ring(struct sk_buff *skb,
> + struct mucse_ring *tx_ring)
> +{
[ ... ]
> + /* record the location of the first descriptor for this packet */
> + first = &tx_ring->tx_buffer_info[tx_ring->next_to_use];
> + first->skb = skb;
[Severity: Medium]
Can this dereference a NULL tx_buffer_info during the first ifup?
At this patch rnpgbe_add_adapter() calls register_netdev() without
netif_carrier_off() or netif_tx_stop_all_queues(), and __dev_open() sets
__LINK_STATE_START before calling ndo_open, so all three conditions checked
by __dev_direct_xmit() are satisfied while rnpgbe_open() is still running:
netif_running() && netif_carrier_ok() &&
!netif_xmit_frozen_or_drv_stopped()
An AF_PACKET socket with PACKET_QDISC_BYPASS can then enter
rnpgbe_xmit_frame() -> rnpgbe_xmit_frame_ring() in the window inside
rnpgbe_open():
err = netif_set_real_num_queues(netdev, mucse->num_tx_queues,
mucse->num_rx_queues);
if (err)
goto err_free_irqs;
err = rnpgbe_setup_all_tx_resources(mucse);
where tx_buffer_info is still NULL, and a slightly later landing reaches
writel(i, tx_ring->tail) in rnpgbe_tx_map() with tail still NULL.
The window is closed later in the series by "net: rnpgbe: Add link status
handling support", which adds netif_carrier_off() before register_netdev()
and at the top of rnpgbe_open(). Would it make sense to fold that
netif_carrier_off() into this patch so it stands on its own?
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> index d38c08df6b7c..1fe290ed224d 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
[ ... ]
> +#define M_MAX_TXD_PWR 12
> +#define M_MAX_DATA_PER_TXD (0x1 << M_MAX_TXD_PWR)
> +#define TXD_USE_COUNT(S) DIV_ROUND_UP((S), M_MAX_DATA_PER_TXD)
> +#define DESC_NEEDED (MAX_SKB_FRAGS + 4)
> +/* 2 desc gap to keep tail from touching head */
> +/* 1 desc for context descriptor */
> +#define RESV_DESC_NEEDED 3
[Severity: Low]
Same self-containment question as for rnpgbe_hw.h: rnpgbe_lib.h has no
#include lines, yet now uses MAX_SKB_FRAGS and DIV_ROUND_UP here, and
netdev_tx_t, struct sk_buff, struct net_device and struct rtnl_link_stats64
in the prototypes below. Should it include <linux/skbuff.h> and
<linux/netdevice.h> so it does not depend on include order in the .c files?
> @@ -31,4 +53,12 @@ void rnpgbe_free_irq(struct mucse *mucse);
> +netdev_tx_t rnpgbe_xmit_frame_ring(struct sk_buff *skb,
> + struct mucse_ring *tx_ring);
> +void rnpgbe_get_stats64(struct net_device *netdev,
> + struct rtnl_link_stats64 *stats);
> #endif
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> index debed2f3fe64..cdda5f3d09b2 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
[ ... ]
> @@ -206,6 +247,13 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
> goto err_clear_interrupt;
> }
>
> + netdev->features |= NETIF_F_SG;
> + netdev->hw_features |= NETIF_F_SG;
> + if (dma_get_mask(&pdev->dev) > DMA_BIT_MASK(32)) {
> + netdev->features |= NETIF_F_HIGHDMA;
> + netdev->hw_features |= NETIF_F_HIGHDMA;
> + }
> +
> err = register_netdev(netdev);
> if (err)
> goto err_remove_mbx;
> @@ -248,12 +296,7 @@ static int rnpgbe_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> if (err)
> return err;
>
> - err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(56));
> - if (err) {
> - dev_err(&pdev->dev,
> - "No usable DMA configuration, aborting %d\n", err);
> - goto err_disable_dev;
> - }
> + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(56));
[Severity: Medium]
Should the return value of dma_set_mask_and_coherent() still be checked
here? The previous code aborted probe and printed "No usable DMA
configuration, aborting", and dma_set_mask_and_coherent() is a plain static
inline int with no __must_check, so a failure is now completely silent.
If dma_supported() rejects a 56-bit mask, neither the streaming nor the
coherent mask is changed, the device keeps the PCI core's 32-bit default,
and probe continues through pci_request_mem_regions() to register_netdev().
The dma_get_mask() test above keeps highmem frags away by not setting
NETIF_F_HIGHDMA, but it does not cover the linear-data mapping in
rnpgbe_tx_map():
dma = dma_map_single(tx_ring->dev, skb->data, size,
DMA_TO_DEVICE);
...
if (dma_mapping_error(tx_ring->dev, dma))
goto err_unmap;
nor the descriptor allocation in rnpgbe_setup_tx_resources():
tx_ring->desc = dma_alloc_coherent(dev, tx_ring->size, &tx_ring->dma,
GFP_KERNEL);
So every frame whose linear data sits above 4GB would be dropped with only
the private per-ring dropped counter moving and no kernel message.
The commit message says only:
"Configure streaming and coherent DMA masks for the hardware's 56-bit
DMA addressing capability."
Could it also state that a DMA configuration failure is now non-fatal and
unreported, or could the check and the error path be restored?
The call is still unchecked at the end of the series, so no later patch
covers this.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/B8AF0FDE24FAFB51%2B20260920092424.345309-1-dong100%40mucse.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support
2026-09-20 9:26 ` [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support Dong Yibo
@ 2026-09-21 10:20 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 10:20 UTC (permalink / raw)
To: dong100
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig, netdev, linux-kernel, yaojun
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 0
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).
- [Medium] RX descriptor length from the device (`rx_desc->wb.len`) is
only bounded by the skb/page geometry (`max_size =…
- [Medium] `rnpgbe_clean_rx_irq()` counts a received packet in
`rx_ring->stats.packets` / `.bytes` only when the skb is successfully…
- [Medium] For a multi-descriptor (non-EOP) frame the linear skb is built
from the first descriptor with `__skb_put(skb, size)` where the only…
--
net: rnpgbe: Add basic RX data path support
This adds the RX DMA data path for rnpgbe: the RX write-back descriptor
format, page_pool backed RX buffers, the NAPI poll callback with RX ring
setup/teardown, and RX statistics. A per-q_vector timer retries failed
page allocations and RX DMA quiesce timeouts are latched as terminal AXI
faults.
A few questions about the descriptor length handling and the RX
statistics below.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> index af5cef987051..9afd10273a62 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
[ ... ]
> @@ -174,15 +176,436 @@ static bool rnpgbe_clean_tx_irq(struct mucse_q_vector *q_vector,
[ ... ]
> +/**
> + * rnpgbe_add_rx_frag - Add non-linear data to the skb
> + * @rx_buffer: pointer to rx_buffer
> + * @skb: pointer skb for this packet
> + * @size: data size in this desc
> + **/
> +static void rnpgbe_add_rx_frag(struct mucse_rx_buffer *rx_buffer,
> + struct sk_buff *skb,
> + unsigned int size)
> +{
> + unsigned int truesize = PAGE_SIZE;
> +
> + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, rx_buffer->page,
> + rx_buffer->page_offset, size, truesize);
> +}
> +
> +/**
> + * rnpgbe_build_skb - Try to build a skb based on rx_buffer
> + * @rx_buffer: pointer to rx_buffer
> + * @size: data size in this desc
> + *
> + * Return: skb for this rx_buffer
> + **/
> +static struct sk_buff *rnpgbe_build_skb(struct mucse_rx_buffer *rx_buffer,
> + unsigned int size)
> +{
> + void *va = page_address(rx_buffer->page) + rx_buffer->page_offset;
> + unsigned int truesize = PAGE_SIZE;
> + struct sk_buff *skb;
> +
> + net_prefetch(va);
> + /* build an skb around the page buffer */
> + skb = napi_build_skb(va - RNPGBE_SKB_PAD, truesize);
> + if (unlikely(!skb))
> + return NULL;
> + /* update pointers within the skb to store the data */
> + skb_reserve(skb, RNPGBE_SKB_PAD);
> + __skb_put(skb, size);
> + skb_mark_for_recycle(skb);
> +
> + return skb;
> +}
[ ... ]
> +/**
> + * rnpgbe_cleanup_headers - Correct corrupted or empty headers
> + * @skb: current socket buffer containing buffer in progress
> + *
> + * Return: true if an error was encountered and skb was freed.
> + **/
> +static bool rnpgbe_cleanup_headers(struct sk_buff *skb)
> +{
> + /* if eth_skb_pad returns an error the skb was freed */
> + if (eth_skb_pad(skb))
> + return true;
> +
> + return false;
> +}
> +
> +/**
> + * rnpgbe_process_skb_fields - Set the RX queue and protocol fields
> + * @rx_ring: RX descriptor ring containing the queue information
> + * @skb: skb currently being received
> + *
> + * Records the RX queue that received the skb and sets its protocol from
> + * the Ethernet header.
> + **/
> +static void rnpgbe_process_skb_fields(struct mucse_ring *rx_ring,
> + struct sk_buff *skb)
> +{
> + struct net_device *dev = rx_ring->netdev;
> +
> + skb_record_rx_queue(skb, rx_ring->queue_index);
> + skb->protocol = eth_type_trans(skb, dev);
> +}
[Severity: Medium]
Is there anything guaranteeing that the first descriptor of a chained
frame carries at least ETH_HLEN bytes before eth_type_trans() is called
here?
For a non-EOP first descriptor, rnpgbe_clean_rx_irq() only rejects
!size || size > max_size, so size may be as small as 1, and
rnpgbe_build_skb() puts exactly that into the linear area:
skb_reserve(skb, RNPGBE_SKB_PAD);
__skb_put(skb, size);
The following descriptors go into frags, so skb->len is already >= ETH_ZLEN
and the eth_skb_pad() in rnpgbe_cleanup_headers() does nothing.
eth_type_trans() -> eth_skb_pull_mac() -> skb_pull_inline(skb, ETH_HLEN)
does not return NULL because ETH_HLEN <= skb->len, so __skb_pull() runs:
include/linux/skbuff.h:__skb_pull() {
skb->len -= len;
if (unlikely(skb->len < skb->data_len)) {
...
BUG();
}
...
}
With a first descriptor of S bytes and F bytes of frags, after the pull
skb->len = S + F - 14 while data_len = F, so S < 14 hits the BUG(). It
also reads eth->h_proto past the bytes the device actually wrote.
The comment in rnpgbe_clean_rx_irq() states the 33-byte minimum as a
hardware guarantee, but nothing enforces it. Would it make sense to
check size >= ETH_HLEN for a non-EOP first descriptor, or to call
pskb_may_pull() before eth_type_trans()?
[ ... ]
> +static int rnpgbe_clean_rx_irq(struct mucse_q_vector *q_vector,
> + struct mucse_ring *rx_ring,
> + int budget)
> +{
> + unsigned int max_size = SKB_WITH_OVERHEAD(PAGE_SIZE) - RNPGBE_SKB_PAD;
> + unsigned int total_rx_bytes = 0, total_rx_packets = 0;
> + u16 cleaned_count = mucse_desc_unused_rx(rx_ring);
> + unsigned int work_done = 0;
[ ... ]
> + dma_rmb();
> + /* Hardware guarantees the first descriptor of each packet is at
> + * least 33 bytes, including multi-descriptor packets.
> + * Multi-descriptor packets are only used for jumbo frames over
> + * 1536 bytes (M_DEFAULT_SG = 96). Each descriptor is at most
> + * 1536 bytes. Small packets use a single descriptor.
> + */
> + size = le16_to_cpu(rx_desc->wb.len);
[ ... ]
> + if (unlikely(!size || size > max_size)) {
[Severity: Medium]
Should this bound be the programmed per-descriptor DMA window rather than
the page geometry?
max_size is SKB_WITH_OVERHEAD(PAGE_SIZE) - RNPGBE_SKB_PAD, roughly 3.7 KB
with 4K pages, while rnpgbe_configure_rx_ring() tells the device:
mucse_ring_wr32(ring, RNPGBE_RX_SG_LEN, M_DEFAULT_SG);
which the header documents as 1536 bytes, and the comment right above the
length read says "Each descriptor is at most 1536 bytes".
A writeback length in the range (1536, max_size] passes the check, so
rnpgbe_build_skb()'s __skb_put(skb, size) or rnpgbe_add_rx_frag()'s
skb_add_rx_frag(..., size, truesize) would publish up to ~2.1 KB of page
content the device never wrote. page_pool pages are not zeroed, so that
is previously freed kernel memory on a fresh page and earlier packet data
on a recycled one, and it reaches napi_gro_receive() and from there
AF_PACKET or the wire. Would clamping against the M_DEFAULT_SG derived
1536-byte limit be more appropriate here?
> + struct mucse_rx_buffer *err_rx_buffer;
> + u16 idx = rx_ring->next_to_clean;
> +
> + cleaned_count++;
> + atomic64_inc(&rx_ring->stats.dropped);
[ ... ]
> + rx_desc->wb.cmd = 0;
> + rx_ring->next_to_clean++;
> + if (rx_ring->next_to_clean >= rx_ring->count)
> + rx_ring->next_to_clean = 0;
> + continue;
> + }
> +
> + rx_buffer = rnpgbe_get_buffer(rx_ring, &skb, size);
> +
> + if (skb)
> + rnpgbe_add_rx_frag(rx_buffer, skb, size);
> + else
> + skb = rnpgbe_build_skb(rx_buffer, size);
> +
> + if (!skb) {
> + cleaned_count++;
> +
> + /* drop until eop if multiple descriptors */
> + if (rnpgbe_test_staterr(rx_desc, M_RXD_STAT_EOP))
> + work_done++;
> + else
> + rx_ring->drop_status = true;
> +
> + rx_desc->wb.cmd = 0;
> + rx_ring->next_to_clean++;
> + atomic64_inc(&rx_ring->stats.dropped);
[Severity: Medium]
Should good frames dropped inside the driver still be counted in
rx_ring->stats.packets and .bytes?
Here, and in the MAX_SKB_FRAGS overflow path of rnpgbe_is_non_eop() and
the rnpgbe_cleanup_headers() failure path, only stats.dropped is bumped;
total_rx_packets and total_rx_bytes are updated only on successful
delivery.
rnpgbe_get_stats64() then exports those success-only counters directly:
stats->rx_packets += packets;
stats->rx_dropped += dropped;
stats->rx_bytes += bytes;
include/uapi/linux/if_link.h documents rx_packets as counting "all good
packets received from the device by the host, including packets which host
had to drop at various stages of processing (even in the driver)", with
rx_bytes corresponding to rx_packets.
Under memory pressure ip -s link would show rx_dropped climbing without
those frames appearing in rx_packets/rx_bytes. Was that intentional?
> + if (rx_ring->next_to_clean >= rx_ring->count)
> + rx_ring->next_to_clean = 0;
> +
> + continue;
> + }
> +
> + rx_buffer->page = NULL;
> + rx_buffer->skb = NULL;
> + cleaned_count++;
> +
> + if (rnpgbe_is_non_eop(rx_ring, rx_desc, skb))
> + continue;
> +
> + /* verify the packet layout is correct */
> + if (rnpgbe_cleanup_headers(skb)) {
> + /* we should clean it since we used all info in it */
> + atomic64_inc(&rx_ring->stats.dropped);
> + rx_desc->wb.cmd = 0;
> + work_done++;
> + continue;
> + }
> +
> + /* probably a little skewed due to removing CRC */
> + total_rx_bytes += skb->len;
> + rnpgbe_process_skb_fields(rx_ring, skb);
> + rx_desc->wb.cmd = 0;
> + napi_gro_receive(&q_vector->napi, skb);
> + /* Update packet statistics and NAPI work accounting. */
> + total_rx_packets++;
> + work_done++;
> + }
> +
> + u64_stats_update_begin(&rx_ring->syncp);
> + rx_ring->stats.packets += total_rx_packets;
> + rx_ring->stats.bytes += total_rx_bytes;
> + u64_stats_update_end(&rx_ring->syncp);
> +
> + return work_done;
> +}
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> index 1fe290ed224d..76b5b0c0ae4a 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
> @@ -9,12 +9,27 @@ struct mucse_hw;
[ ... ]
> +#define M_DEFAULT_SG 96 /* unit 16b, 1536 bytes */
> +#define RNPGBE_RX_SG_LEN 0x58
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/B8AF0FDE24FAFB51%2B20260920092424.345309-1-dong100%40mucse.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support
2026-09-20 9:27 ` [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support Dong Yibo
@ 2026-09-21 10:20 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 10:20 UTC (permalink / raw)
To: dong100
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, vadim.fedorenko,
u.kleine-koenig, netdev, linux-kernel, yaojun
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 3 · Low: 0
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] netif_running() is used as the gate for 'the data path is
configured, it is safe to enable the GMAC receiver and assert carrier'.
- [High] mucse->serv_task (delayed work) is queued from the mailbox work
item — mucse_mbx_fw_req_handler() calls…
- [Medium] M_DEFAULT_ST (0xa0000000) is simultaneously the 'driver has
not seen link / make firmware report again' snapshot and the steady-…
- [Medium] The firmware->PF request counter read by mucse_mbx_get_fwreq()
is only 16 bits (FIELD_GET(GENMASK_U32(15,0), MUCSE_MBX_FW2PF_CNT))…
- [Medium] rnpgbe_set_link() performs a read-modify-write of GMAC_CONTROL
plus a write of GMAC_FRAME_FILTER, but the two callers use different…
--
net: rnpgbe: Add link status handling support
This adds firmware-driven link handling to rnpgbe: link state is cached in
struct mucse_hw, firmware link events arrive over the mailbox, and a delayed
work item (serv_task) converts them into netif_carrier_on()/off(). The patch
also adds the GMAC receiver enable in rnpgbe_set_link() and makes ndo_open
fail when the firmware port-up or link-report commands do not complete.
A few questions about the new paths below.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
> index 4e2835b71f32..b56263de4a36 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
[ ... ]
> @@ -224,3 +249,30 @@ void rnpgbe_set_rx_mode(struct net_device *netdev)
> mucse_hw_wr32(hw, RNPGBE_RX_MCAST_CTRL, mcast_ctrl);
> mucse_hw_wr32(hw, RNPGBE_RX_FILTER_CTRL, filter_ctrl);
> }
> +
> +/**
> + * rnpgbe_set_link - Set the hardware link state
> + * @hw: hw information structure
> + * @linkup: link on or not
> + *
> + * rnpgbe_set_link setup link status
> + *
> + **/
> +void rnpgbe_set_link(struct mucse_hw *hw, bool linkup)
> +{
> + u32 value = mucse_hw_rd32(hw, GMAC_CONTROL);
> +
> + /* The chip-level filter is programmed by ndo_set_rx_mode(). Keep the
> + * GMAC in receive-all mode so it does not discard frames accepted by
> + * that filter.
> + */
> + if (linkup) {
> + mucse_hw_wr32(hw, GMAC_FRAME_FILTER, GMAC_RX_ALL);
> + value |= GMAC_CONTROL_RE;
> + mucse_hw_wr32(hw, GMAC_CONTROL, value);
> + } else {
> + value &= ~GMAC_CONTROL_RE;
> + mucse_hw_wr32(hw, GMAC_CONTROL, value);
> + mucse_hw_wr32(hw, GMAC_FRAME_FILTER, 0);
> + }
> +}
[Severity: Medium]
Should the read-modify-write of GMAC_CONTROL here be serialized?
The two callers disagree on the lockset. mucse_mbx_fw_req_handler() calls
rnpgbe_set_link() with mucse->link_lock held:
spin_lock_irqsave(&mucse->link_lock, flags);
...
rnpgbe_set_link(hw, mucse_link_is_up(hw, req));
while rnpgbe_down() calls it after link_lock has already been dropped:
spin_unlock_irqrestore(&mucse->link_lock, flags);
...
rnpgbe_set_link(hw, false);
So the intersection of locks held for GMAC_CONTROL and GMAC_FRAME_FILTER is
empty. Can the read of GMAC_CONTROL in one path interleave with the write
from the other, and can the mailbox path re-set GMAC_CONTROL_RE plus
GMAC_FRAME_FILTER = GMAC_RX_ALL after rnpgbe_down() has cleared them?
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> index feb84704d602..5659e91f3497 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
> @@ -21,6 +21,17 @@
> #define RNPGBE_DMA_TX_STATUS GENMASK_U32(23, 22)
> #define RNPGBE_DMA_RX_STATUS GENMASK_U32(21, 20)
> #define RX_AXI_RW_EN 0x03
> +/* RNPGBE_LINK_ST is a driver-owned link-state snapshot consumed by firmware.
> + * Set the driver-state marker; all other driver status fields start clear.
> + */
> +#define M_DEFAULT_ST 0xa0000000
> +/* Firmware never reports 0xf in the speed field. */
> +#define M_INVALID_ST (M_DEFAULT_ST | GENMASK_U32(11, 8))
> +/* Driver-reported fields: 25:24 pause, 11:8 speed, 6 LLDP, 4 duplex,
> + * and 0 link up/down. M_DEFAULT_ST resets these fields so firmware reports
> + * the current hardware state again.
> + */
> +#define RNPGBE_LINK_ST 0x000c
> #define RNPGBE_DMA_AXI_EN 0x0010
> #define RNPGBE_TX_MIN_PKT_LEN 33
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> index 9afd10273a62..2a5349f03a02 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
[ ... ]
> @@ -1399,7 +1407,19 @@ static void rnpgbe_cancel_rx_retry_timers(struct mucse *mucse)
> void rnpgbe_down(struct mucse *mucse)
> {
> struct net_device *netdev = mucse->netdev;
> + struct mucse_hw *hw = &mucse->hw;
> + unsigned long flags;
> + int err;
>
> + spin_lock_irqsave(&mucse->link_lock, flags);
> + WRITE_ONCE(hw->link, false);
> + WRITE_ONCE(hw->speed, 0);
> + WRITE_ONCE(hw->duplex, 0);
> + atomic_set(&mucse->link_pending, 0);
> + spin_unlock_irqrestore(&mucse->link_lock, flags);
> + cancel_delayed_work_sync(&mucse->serv_task);
[Severity: High]
This is the only cancel_delayed_work_sync(&mucse->serv_task) in the driver.
Is that enough to keep the work item from outliving the struct mucse?
The producer is mucse_mbx_fw_req_handler(), running from mbx_work, gated
only on netif_running():
atomic_set_release(&mucse->link_pending, 1);
mod_delayed_work(system_percpu_wq, &mucse->serv_task, 0);
In net/core/dev.c, __dev_open() sets __LINK_STATE_START before calling
ndo_open and clears it only if ndo_open returns an error, so netif_running()
is already true on entry to rnpgbe_open() and stays true across the new
err_down unwind. Can a mailbox event serviced right after the cancel above
re-arm serv_task? Note firmware link reporting is only disabled later in
rnpgbe_down(), and that failure is merely warned about ("Fw will still
report link event").
If serv_task is left queued and the interface was never successfully
opened, removal does not appear to cancel it:
rnpgbe_rm_adapter()
unregister_netdev() /* no ndo_stop, device not IFF_UP */
rnpgbe_free_mbx_irq() /* cancels mbx_work only */
rnpgbe_clear_interrupt_scheme()
free_netdev(netdev) /* frees the mucse holding serv_task */
and rnpgbe_service_task() then dereferences mucse and mucse->netdev from
that freed allocation. rnpgbe_dev_shutdown() has no serv_task cancellation
either. Should the remove and shutdown paths call
cancel_delayed_work_sync(&mucse->serv_task) before the netdev is freed?
> +
> + netif_carrier_off(netdev);
> rnpgbe_irq_disable(mucse);
> rnpgbe_napi_disable_all(mucse);
> synchronize_net();
> @@ -1408,22 +1428,77 @@ void rnpgbe_down(struct mucse *mucse)
> rnpgbe_stop_all_rx_rings(mucse);
> rnpgbe_cancel_rx_retry_timers(mucse);
> rnpgbe_clean_all_tx_rings(mucse);
> +
> + err = rnpgbe_send_notify(hw, false, mucse_fw_link_report_en);
> + if (err) {
> + dev_warn(&hw->pdev->dev, "Send link report to hw failed %d\n",
> + err);
> + dev_warn(&hw->pdev->dev, "Fw will still report link event\n");
> + }
> +
> + err = rnpgbe_send_notify(hw, false, mucse_fw_portup);
> + if (err) {
> + dev_warn(&hw->pdev->dev, "Send port down to hw failed %d\n",
> + err);
> + dev_warn(&hw->pdev->dev, "Port is not truly down\n");
> + }
> + rnpgbe_set_link(hw, false);
> rnpgbe_clean_all_rx_rings(mucse);
> }
[ ... ]
> + err = rnpgbe_send_notify(hw, true, mucse_fw_portup);
> + if (err) {
> + dev_err(&hw->pdev->dev,
> + "Failed to notify firmware that port is up: %d\n", err);
> + return err;
> + }
> + /* Firmware checks RNPGBE_LINK_ST (driver's last-received link state)
> + * and only asserts LINK_CHANGE_EVT when it differs from the actual link
> + * state AND link_report_en is true.
> + */
> + err = rnpgbe_send_notify(hw, true, mucse_fw_link_report_en);
> + if (err) {
> + dev_err(&hw->pdev->dev,
> + "Failed to enable firmware link reporting: %d\n",
> + err);
> + return err;
> + }
> +
> + return 0;
> }
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> index 35eb858eb2c1..9ba53bb3629b 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
[ ... ]
> @@ -86,9 +87,16 @@ static int rnpgbe_open(struct net_device *netdev)
> err = rnpgbe_configure(mucse);
> if (err)
> goto err_free_rx;
> - rnpgbe_up_complete(mucse);
> + err = rnpgbe_up_complete(mucse);
> + if (err)
> + goto err_down;
>
> return 0;
> +err_down:
> + rnpgbe_down(mucse);
> + rnpgbe_free_all_rx_resources(mucse);
> + rnpgbe_free_all_tx_resources(mucse);
> + goto err_free_irqs;
> err_free_rx:
> rnpgbe_free_all_rx_resources(mucse);
> err_free_tx:
[ ... ]
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
> index 2310f01df1f5..74f42be975c2 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
> @@ -197,7 +197,8 @@ static int mucse_read_mbx_pf(struct mucse_hw *hw, __le32 *msg, u16 size)
> * mucse_check_for_msg_pf - Check to see if the fw has sent mail
> * @hw: pointer to the HW structure
> *
> - * Return: 0 if the fw has set the Status bit or else -EIO
> + * Return: 0 if firmware has posted a new message, -ENOMSG if there is no
> + * new message, or -EIO if the mailbox has been reset
> **/
> static int mucse_check_for_msg_pf(struct mucse_hw *hw)
> {
> @@ -205,12 +206,11 @@ static int mucse_check_for_msg_pf(struct mucse_hw *hw)
> u16 fw_req;
>
> fw_req = mucse_mbx_get_fwreq(mbx);
> - /* chip's register is reset to 0 when rc send reset
> - * mbx command. Return -EIO if in this state, others
> - * fw == hw->mbx.fw_req means no new msg.
> - **/
> - if (fw_req == 0 || fw_req == hw->mbx.fw_req)
> + /* The request counter is reset to zero by a mailbox reset. */
> + if (!fw_req)
> return -EIO;
> + if (fw_req == hw->mbx.fw_req)
> + return -ENOMSG;
>
> return 0;
> }
[Severity: Medium]
Is zero really reserved for a mailbox reset?
mucse_mbx_get_fwreq() returns a 16-bit field:
u32 val = mbx_data_rd32(mbx, MUCSE_MBX_FW2PF_CNT);
return FIELD_GET(GENMASK_U32(15, 0), val);
and the corresponding producer side, mucse_mbx_inc_pf_req(), just does a
plain u16 increment with nothing skipping zero. So zero looks like a normal
counter value after 0xffff.
On the new asynchronous path, what recovers from that? mucse_fw_handle_event()
returns on any error without calling mucse_mbx_event_end():
err = mucse_mbx_event_begin(hw, msg.dwords, sizeof(msg));
if (err) {
mutex_unlock(&hw->mbx.lock);
if (err != -ENOMSG)
dev_warn_ratelimited(...);
return;
}
so the event is never acknowledged and hw->mbx.fw_req stays stale. Per the
contract documented in mucse_mbx_event_begin(), firmware does not touch the
shared window again until the PF acknowledges, so does the counter ever get
past zero?
The new mucse_ack_pending_event() has the same test:
fw_req = mucse_mbx_get_fwreq(mbx);
if (fw_req == hw->mbx.fw_req)
return 0;
if (!fw_req)
return -EIO;
which would make every SET_PHY_UP and LINK_REPORT_EN command fail, and with
the new error returns in rnpgbe_up_complete() that turns into a persistent
failure of ndo_open. RESET_HW is only issued from probe.
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
> index e28f3193aa85..6415b6430efc 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
[ ... ]
> +static void mucse_update_link_status_reg(struct mucse_hw *hw,
> + struct mbx_fw_cmd_req *req)
> +{
> + u16 status = le16_to_cpu(req->link_stat.st.status);
> + u16 speed = le16_to_cpu(req->link_stat.st.speed);
> + u32 value;
> +
> + value = M_DEFAULT_ST;
> +
> + if (mucse_link_is_up(hw, req)) {
[ ... ]
> + } else {
> + value &= ~BIT(0);
> + }
[ ... ]
> +static void mucse_mbx_fw_req_handler(struct mucse_hw *hw,
> + struct mbx_fw_cmd_req *req)
> +{
> + struct mucse *mucse = container_of(hw, struct mucse, hw);
> + u32 magic = le32_to_cpu(req->link_stat.port_magic);
> + unsigned long flags;
> +
> + if (le16_to_cpu(req->opcode) == LINK_CHANGE_EVT) {
> + u16 speed = le16_to_cpu(req->link_stat.st.speed);
> +
> + spin_lock_irqsave(&mucse->link_lock, flags);
> + if (magic != ST_VALID_MAGIC) {
> + /* Do not change the cached state for an invalid event.
> + * Use an invalid speed encoding to make firmware report
> + * again.
> + */
> + mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_INVALID_ST);
> + spin_unlock_irqrestore(&mucse->link_lock, flags);
> + return;
> + }
> +
> + if (mucse_link_is_up(hw, req) &&
> + !mucse_link_speed_valid(req)) {
> + /* Do not acknowledge an invalid speed as valid.
> + * Keep the snapshot mismatched so firmware retries it.
> + * Firmware limits link reports to one per 500 ms.
> + */
> + mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_DEFAULT_ST);
> + spin_unlock_irqrestore(&mucse->link_lock, flags);
> + dev_warn_ratelimited(&hw->pdev->dev,
> + "unsupported link speed %u Mbps\n",
> + speed);
> + return;
> + }
[Severity: Medium]
Can M_DEFAULT_ST here be distinguished by firmware from an acknowledged
link-down snapshot?
In mucse_update_link_status_reg() the link-down branch is:
value = M_DEFAULT_ST;
...
} else {
value &= ~BIT(0);
}
Bit 0 is already clear in M_DEFAULT_ST, so that mask is a no-op and a
link-down snapshot with LLDP clear is byte-identical to M_DEFAULT_ST. The
speed field also collides, since mucse_speed_10 is 0 in enum mucse_speed.
Given the comment added in rnpgbe_hw.h that M_DEFAULT_ST "resets these
fields so firmware reports the current hardware state again", what happens
on this path if the cable is pulled before a valid retry arrives? The
exported snapshot already reads as link down, which matches the hardware, so
firmware can stop reporting, and rnpgbe_process_link_event() only consumes
pending events:
if (!atomic_xchg(&mucse->link_pending, 0))
return false;
so carrier would stay up with no way to refresh it. Under the other reading
of the register, every acknowledged link-down snapshot mismatches reality
and firmware re-reports at its 500 ms rate for as long as the link is down.
Note this path also leaves the previously cached link-up state untouched and
does not set link_pending, while the caller mucse_fw_handle_event() goes on
to acknowledge the event via mucse_mbx_event_end(). Would M_INVALID_ST,
as used by the invalid-magic branch just above, be the right value here?
> +
> + if (!netif_running(mucse->netdev)) {
> + rnpgbe_set_link(hw, false);
> + mucse_update_link_status_reg(hw, req);
> + spin_unlock_irqrestore(&mucse->link_lock, flags);
> + return;
> + }
> +
> + if (mucse_link_is_up(hw, req))
> + WRITE_ONCE(hw->link, true);
> + else
> + WRITE_ONCE(hw->link, false);
> +
> + WRITE_ONCE(hw->speed, le16_to_cpu(req->link_stat.st.speed));
> + WRITE_ONCE(hw->duplex, req->link_stat.st.flags & DUPLEX_BIT);
> + rnpgbe_set_link(hw, mucse_link_is_up(hw, req));
[Severity: High]
Does netif_running() mean the receive data path is configured here?
__dev_open() in net/core/dev.c sets __LINK_STATE_START before it calls
ndo_open, and clears it only if ndo_open failed, so netif_running() is true
from the first statement of rnpgbe_open() until __dev_open() returns. Can a
LINK_CHANGE_EVT delivered in that window take the link-up branch above and
have rnpgbe_set_link(hw, true) write GMAC_FRAME_FILTER = GMAC_RX_ALL and set
GMAC_CONTROL_RE before rnpgbe_configure_rx() has programmed the ring bases,
RNPGBE_DMA_AXI_EN |= RX_AXI_RW_EN and RNPGBE_RX_START? rnpgbe_service_task()
uses the same gate and would call netif_carrier_on() for an interface whose
open has not completed.
The teardown side looks similar. rnpgbe_down() does not cancel or flush
mbx_work (that only happens in rnpgbe_free_mbx_irq()), so can an event
processed after rnpgbe_down()'s final rnpgbe_set_link(hw, false) re-enable
the receiver in receive-all mode with the rx rings stopped and drained? On
the new err_down path in rnpgbe_open() the rings are freed right after
rnpgbe_down() while netif_running() is still true:
rnpgbe_up_complete() /* LINK_REPORT_EN written, ack times out */
return err
rnpgbe_open()
goto err_down
rnpgbe_down()
rnpgbe_free_all_rx_resources()
with firmware link reporting enabled at that point.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/B8AF0FDE24FAFB51%2B20260920092424.345309-1-dong100%40mucse.com
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-21 10:20 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-20 9:24 [PATCH net-next v13 0/5] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-09-20 9:26 ` [PATCH net-next v13 1/5] net: rnpgbe: Add interrupt handling Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 2/5] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 3/5] net: rnpgbe: Add basic RX data path support Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
2026-09-20 9:26 ` [PATCH net-next v13 4/5] net: rnpgbe: Add receive mode support Dong Yibo
2026-09-20 9:27 ` [PATCH net-next v13 5/5] net: rnpgbe: Add link status handling support Dong Yibo
2026-09-21 10:20 ` netdev-bot+sashiko
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®