* [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support
@ 2026-06-11 10:00 Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 1/4] net: rnpgbe: Add interrupt handling Dong Yibo
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Dong Yibo @ 2026-06-11 10:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, horms, u.kleine-koenig
Cc: linux-kernel, netdev, 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:
- Msix/msi interrupt handling with NAPI support
- TX path with scatter-gather DMA and completion handling
- RX path with page pool buffer management
- Link status monitoring and carrier management
These changes enable the RNPGBE driver to support basic tx/rx
network operations.
Changelog:
v6 -> v7:
[patch 2/4]:
1. Fix 'frag_idx' error in rnpgbe_tx_map. (Sashiko-gemini)
[patch 3/4]:
1. Fix skb leak in invalid size path in rnpgbe_clean_rx_irq.
(Sashiko-gemini)
2. Fix invalid size range check for rxdesc. (Sashiko-gemini)
[patch 4/4]:
1. Fix 'data race on the reply payload'. (Sashiko-gemini)
2. Fix 'asymmetric behaviour' when report up/down. (andrew)
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/
Additional Notes:
1.
Sashiko:
> 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);
Could this sequence lead to a queue stall?
If an interrupt arrives immediately after rnpgbe_request_irq(), the handler
will mask the interrupt and call napi_schedule_irqoff(). Since NAPI is not
yet enabled, the scheduling request is silently dropped.
When rnpgbe_up_complete() later enables NAPI, the interrupt remains masked
and the queue stalls. Should NAPI be enabled before requesting the IRQ?
Answer: The sequence is safe because the hardware cannot generate any Rx/Tx or link
interrupts until rnpgbe_up_complete() enables DMA engines and interrupt
sources. There is no race window where an interrupt can arrive before
NAPI is ready. Therefore no stall can occur.
2.
Sashiko:
> +static bool rnpgbe_cleanup_headers(struct sk_buff *skb)
> +{
> + if (IS_ERR(skb))
> + return true;
> + /* place header in linear portion of buffer */
> + if (!skb_headlen(skb))
> + rnpgbe_pull_tail(skb);
> + /* if eth_skb_pad returns an error the skb was freed */
> + if (eth_skb_pad(skb))
> + return true;
> +
> + return false;
> +}
If the first hardware descriptor provides a size between 1 and 13 bytes,
skb_headlen(skb) will be greater than 0, causing this to skip the
rnpgbe_pull_tail() call.
When eth_type_trans() is called later, it assumes a full 14 byte header and
calls __skb_pull(skb, ETH_HLEN). If the packet spans multiple fragments,
could subtracting 14 from skb->len cause it to drop below skb->data_len,
triggering the BUG_ON in __skb_pull() and crashing the kernel?
Answer:
This issue is not reproducible:
Hardware enforces a minimum packet size of 33 bytes (no 1–13 bytes
descriptor sizes). Multiple descriptors are only used Format
frames >1536(M_DEFAULT_SG is 96) bytes; all small packets use a single
descriptor (no skb fragmentation). Thus the problematic __skb_pull() condition
cannot occur.
3
Sashiko:
> +static int rnpgbe_clean_rx_irq(struct mucse_q_vector *q_vector,
> + struct mucse_ring *rx_ring,
> + int budget)
> +{
> + unsigned int total_rx_bytes = 0, total_rx_packets = 0;
> + u16 cleaned_count = mucse_desc_unused_rx(rx_ring);
> + bool fail_alloc = false;
> +
> + while (likely(total_rx_packets < budget)) {
> + struct mucse_rx_buffer *rx_buffer;
> + union rnpgbe_rx_desc *rx_desc;
> + struct sk_buff *skb;
> + unsigned int size;
[ ... ]
> + /* 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();
> + size = le16_to_cpu(rx_desc->wb.len);
> + rx_buffer = rnpgbe_get_buffer(rx_ring, &skb, size);
Is the size value validated against the allocated page size anywhere?
If a hardware device provides an oversized length, such as 65535, could this
cause dma_sync_single_range_for_cpu() to operate out of bounds?
Additionally, since size is passed to rnpgbe_build_skb() which uses
__skb_put(), could this cause skb->tail and skb->len to advance beyond the
allocated linear buffer?
Answer:
Can not happen, hardware splits data into multiple descs with 1536 bytes
(M_DEFAULT_SG is 96) for each desc.
4.
Sashiko:
> + if (!size)
> + break;
If size is zero, does this cause an infinite loop? By breaking here, the
driver doesn't advance rx_ring->next_to_clean or clear the descriptor's DD
bit. On subsequent NAPI polls, it will examine the exact same descriptor, see
the DD bit set and size 0, and immediately break again, locking up the CPU in
softirq context.
Answer:
No, an infinite loop cannot occur here.The hardware guarantees that the
descriptor size is always non-zero when writing back the DD (Descriptor Done) bit.
5.
Sashiko:
> +static void rnpgbe_configure_rx_ring(struct mucse *mucse,
> + struct mucse_ring *ring)
> +{
[ ... ]
> + 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->next_to_use = ring->next_to_clean;
Can RNPGBE_RX_HEAD contain a value greater than or equal to ring->count?
If the device register holds an out-of-bounds value, it looks like it will be
directly used as an index into rx_ring->rx_buffer_info[] and by M_RX_DESC(),
causing an out-of-bounds memory access.
Answer:
No out-of-bounds risk:
RNPGBE_RX_HEAD is 0 after hardware reset (always valid).
This is just driver/hardware sync for queue down/up (no full reset).
If ring->count changes, a full reset sets head back to 0.
Head value always stays within valid index rang.
6.
Sashiko:
> +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) {
> + spin_lock_irqsave(&mucse->link_lock, flags);
> +
> + if (le16_to_cpu(req->link_stat.port_status))
> + hw->link = true;
Does this correctly handle multi-port link events?
Since req->link_stat.port_status is a bitmask of changed ports, checking it
as a simple boolean might cause this port to process events meant for other
ports on the same NIC. Additionally, this unconditionally reads from st[0],
which would cause all ports to adopt the speed and duplex of port 0
whenever any port changes state.
Answer:
This driver follows a 1:1 mapping — one PF (Physical Function) corresponds
to exactly one port. The multi-port bitmask (port_status) and multi-port structure
definitions in the firmware command are only for forward compatibility interfacethe
driver-firmware communication interface, to support both multi-port and
single-port designs.
7.
Sashiko:
> +void rnpgbe_set_rx(struct mucse_hw *hw, bool enable)
> +{
> + u32 value = mucse_hw_rd32(hw, GMAC_CONTROL);
> +
> + if (enable)
> + value |= GMAC_CONTROL_RE;
> + else
> + value &= ~GMAC_CONTROL_RE;
> +
> + mucse_hw_wr32(hw, GMAC_CONTROL, value);
> +
> + value = mucse_hw_rd32(hw, GMAC_FRAME_FILTER);
> + mucse_hw_wr32(hw, GMAC_FRAME_FILTER, value | BIT(0));
Does this lockless read-modify-write race with standard network operations
like ndo_set_rx_mode?
Answer:
No race here, ndo_set_rx_mode is not added now. Even in the future,
GMAC_FRAME_FILTER will not controlled in ndo_set_rx_mode, hw has other register
to control promiscuous mode.
8.
Sashiko:
> +/**
> + * register_mbx_irq - Register mbx routine
> + * @mucse: pointer to private structure
> + *
> + * @return: 0 on success, negative on failure
> + **/
> +int register_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));
> +
> + if (mucse->flags & M_FLAG_MSIX_EN) {
> + err = request_irq(pci_irq_vector(pdev, 0),
> + rnpgbe_msix_other, 0, mucse->mbx_name,
> + mucse);
> + }
> +
> + return err;
> +}
Is it intentional that register_mbx_irq() only installs a handler when
M_FLAG_MSIX_EN is set?
In MSIX_SINGLE_EN and MSI modes, the mailbox is serviced only through
the multiplexed rnpgbe_int_single() that rnpgbe_open() registers and
rnpgbe_close() tears down. Between the end of probe and the first
open, and again between every close and the next open, there appears
to be no handler attached to the interrupt that firmware can signal
through.
Does that leave a window where a firmware-initiated mailbox interrupt
in those modes has no registered handler?
Combined with the empty body of mucse_fw_irq_handler(), how does this
match the commit message wording:
> Add comprehensive interrupt handling for the RNPGBE driver:
Answer:
Fw only tirg irq after driver set mucse_fw_link_report_en true (in
rnpgbe_up_complete). So this is not 'no handler attached' contition.
9.
Sashiko:
> +static int rnpgbe_set_interrupt_capability(struct mucse *mucse)
> +{
> + int v_budget;
> +
> + v_budget = min_t(int, mucse->num_tx_queues, mucse->num_rx_queues);
> + v_budget = min_t(int, v_budget, MAX_Q_VECTORS);
> + v_budget = min_t(int, v_budget, num_online_cpus());
> + /* add one vector for mbx */
> + v_budget += 1;
> + v_budget = pci_alloc_irq_vectors(mucse->pdev, 1, v_budget,
> + PCI_IRQ_MSI | PCI_IRQ_MSIX);
> + if (v_budget < 0)
> + return v_budget;
> +
> + if (mucse->pdev->msix_enabled) {
> + /* q_vector not include mbx */
> + if (v_budget > 1) {
> + mucse->flags |= M_FLAG_MSIX_EN;
> + mucse->num_q_vectors = v_budget - 1;
> + } else {
> + mucse->flags |= M_FLAG_MSIX_SINGLE_EN;
> + mucse->num_q_vectors = 1;
> + }
> + } else {
> + /* msi use only 1 irq */
> + mucse->num_q_vectors = 1;
> + mucse->flags |= M_FLAG_MSI_EN;
> + }
> +
> + return 0;
> +}
If the PCI subsystem allocates multiple MSI vectors because v_budget > 1
and the device supports Multiple Message MSI, does this code leak the
unused vectors? It looks like num_q_vectors is forced to 1 and the
remaining allocated vectors are ignored.
Answer:
Hw only suports 1 msi. MSI mode should falls back to single vector.
10.
Sashiko:
> +static void rnpgbe_configure_rx_ring(struct mucse *mucse,
> + struct mucse_ring *ring)
> +{
> + struct mucse_hw *hw = &mucse->hw;
> +
> + /* disable queue to avoid issues while updating state */
> + mucse_ring_wr32(ring, RNPGBE_RX_START, 0);
[ ... ]
> + mucse_ring_wr32(ring, RNPGBE_RX_INT_PKTCNT, M_DEFAULT_RX_INT_PKTCNT);
> + rnpgbe_alloc_rx_buffers(ring, mucse_desc_unused_rx(ring));
> +}
The return value of rnpgbe_alloc_rx_buffers() is discarded here. If
the very first population fails under memory pressure, the ring is
left partially populated (the remaining descriptors keep the zeroed
contents from dma_alloc_coherent()), and rnpgbe_up_complete() then
writes RNPGBE_RX_START=1 unconditionally. When hardware walks into
one of those zeroed descriptors, it will DMA an incoming packet to an
invalid address (just fun_id, or 0 if pfvfnum == 0). Because
rnpgbe_configure_rx() returns void, rnpgbe_open() returns success and
the failure is invisible to callers. Should the allocation failure
propagate up and unwind RX resources?
Answer:
Cannot happen, hw only start request rx descriptors after driver
update rx_tail(which is only update after desc is write correct with
memory alloc success).
11.
Sashiko:
> +static void rnpgbe_pull_tail(struct sk_buff *skb)
> +{
> + skb_frag_t *frag = &skb_shinfo(skb)->frags[0];
> + unsigned int pull_len;
> + unsigned char *va;
> +
> + va = skb_frag_address(frag);
> + pull_len = eth_get_headlen(skb->dev, va, M_RX_HDR_SIZE);
Because rnpgbe_build_skb() places the first descriptor's data into the linear
SKB area, skb_headlen(skb) equals the descriptor's size. This means
rnpgbe_pull_tail() is only called from rnpgbe_cleanup_headers() if size
is 0.
If a 0-length descriptor with EOP is received, nr_frags will be 0. Can this
code then dereference skb_shinfo(skb)->frags[0], leading to an out-of-bounds
read on uninitialized memory?
This could result in a severe length underflow in skb_frag_size_sub() since it
passes a hardcoded M_RX_HDR_SIZE (256) to eth_get_headlen().
Answer:
Cannot happen, hw never writeback 0-size descriptor.
12.
Sashiko:
> +void rnpgbe_set_rx(struct mucse_hw *hw, bool enable)
> +{
> + u32 value = mucse_hw_rd32(hw, GMAC_CONTROL);
> +
> + if (enable)
> + value |= GMAC_CONTROL_RE;
> + else
> + value &= ~GMAC_CONTROL_RE;
> +
> + mucse_hw_wr32(hw, GMAC_CONTROL, value);
> +
> + if (enable)
> + mucse_hw_wr32(hw, GMAC_FRAME_FILTER, GMAC_RX_ALL);
> + else
> + mucse_hw_wr32(hw, GMAC_FRAME_FILTER, 0);
> +}
On the DesignWare GMAC block this driver is using (GMAC_CONTROL at MAC
offset 0x0, GMAC_FRAME_FILTER at 0x4), bit 31 of the frame filter is RA
(Receive All) and bit 0 is PR (Promiscuous Mode).
Given:
#define GMAC_RX_ALL (BIT(31) | BIT(0))
every link-up transition forces both bits on, with no destination-address
filtering applied at all. Is that the intended steady-state receive
configuration for this NIC?
Also, because rnpgbe_watchdog_link_is_up() re-applies this filter on every
link change, any future ndo_set_rx_mode() would be overwritten on the
next carrier flap. Would a perfect-match filter driven by netdev->flags
and the uc/mc lists be more appropriate?
Answer:
This is intentional for the initial submission. The hardware has a
separate register set for MAC/promiscuous filtering beyond the standard
DW GMAC frame filter; full Rx mode management will be added together
with ndo_set_rx_mode() in a follow-up series. The current GMAC_RX_ALL
ensures basic connectivity works without packet loss.
13.
Sashiko
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
> index 5b024f9f7e17..cbe60f168346 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
> @@ -35,21 +39,63 @@ 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;
This isn't a bug, but the driver mixes the mucse_ and rnpgbe_ prefixes
throughout. The Makefile and module are named rnpgbe, yet the core
structures are mucse_hw, mucse_ring, mucse_q_vector, and the helpers
mucse_add_ring(), mucse_for_each_ring, mucse_fw_irq_handler(),
mucse_hw_rd32() coexist with rnpgbe_poll(), rnpgbe_request_irq(),
rnpgbe_set_ring_vector(), etc.
Is there a planned partitioning between a shared mucse common layer and
rnpgbe-specific code, or would it be preferable to settle on a single
prefix to avoid future collisions if another mucse PCIe driver appears?
Answer:
The mucse_ and rnpgbe_ prefixes follow a layered convention:
mucse_ — Hardware abstraction layer. These operate on the
mucse_hw / mucse_ring / mucse_q_vector structures and contain logic that is
shared across Mucse product lines (e.g., ring traversal, register RMW,
mailbox protocol). They are not specific to the RNPGBE device and are expected
to be reused by future Mucse PCIe drivers.
rnpgbe_ — Driver-level operations. These implement the ndo callbacks,
interrupt policy, NAPI poll, and other Linux networking stack integration
that is specific to the rnpgbe driver instance.
This is an intentional separation: the mucse layer is the cross-product hardware
library, and the rnpgbe layer is the Linux driver that uses it.
14.
Sashiko:
> +static void rnpgbe_irq_enable_queues(struct mucse_q_vector *q_vector)
> +{
> + struct mucse_ring *ring;
> +
> + /* tx/rx use one register, different bit */
> + mucse_for_each_ring(ring, q_vector->tx) {
> + writel(0, ring->irq_mask);
> + writel(INT_VALID | TX_INT_MASK | RX_INT_MASK, ring->trig);
Does this write to ring->trig during NAPI unmasking risk stalling queues or
causing an interrupt storm?
If ring->trig is an Interrupt Status/ACK register (W1C), writing to it will
clear any pending interrupts that arrived between the end of the NAPI poll
and this unmasking step, resulting in a lost interrupt.
If it is a software interrupt trigger register, writing to it will
unconditionally force an interrupt, leading to an endless interrupt storm.
Should interrupt status clearance happen only in the hardirq handler instead?
Answer:
The RNPGBE_DMA_INT_TRIG register is neither a W1C status register
nor a software interrupt trigger. It is a hardware lost-interrupt
recovery mechanism: writing to it instructs the hardware to check
whether any interrupt events were missed while the queue was masked.
If the hardware detects a lost event, it re-issues the interrupt
after a short delay.
15.
Sashiko:
> diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> index 70a2b0082ba86..9e541c2419232 100644
> --- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> +++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
> @@ -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);
Is it safe to call rnpgbe_request_irq() before napi_enable() (which is
called inside rnpgbe_up_complete())?
This exposes a race window where an interrupt can fire, mask the queues, and
call napi_schedule_irqoff(). Because napi_enable() has not been called yet,
the NAPI state still has NAPI_STATE_SCHED set, causing napi_schedule_prep()
to fail and napi_schedule_irqoff() to be a no-op.
When napi_enable() is subsequently called, it unconditionally clears the NAPI
state, losing the missed schedule. For edge-triggered interrupts, the
condition won't re-assert by unmasking, leading to permanently stalled queues.
Could NAPI be fully initialized and enabled before registering the IRQ handler?
Answer:
For the per-ring MSI-X handlers (rnpgbe_msix_clean_rings), this is safe
because the hardware interrupt source remains masked until
rnpgbe_irq_enable(), which runs after napi_enable_all(). No interrupt
can fire between request_irq() and napi_enable(), so no race exists.
For the single-interrupt mode (rnpgbe_int_single), the handler is
registered at probe time and persists across open/close cycles. Here
we guard with __MUCSE_DOWN:
rnpgbe_int_single():
if (test_bit(__MUCSE_DOWN, &mucse->state))
return IRQ_HANDLED; // device not ready, discard
__MUCSE_DOWN is set at probe (before any handler is registered) and
only cleared after NAPI is fully enabled:
rnpgbe_up_complete():
rnpgbe_napi_enable_all(mucse); // NAPI ready first
clear_bit(__MUCSE_DOWN, &mucse->state); // handler can proceed
rnpgbe_irq_enable(mucse); // hw unmasked last
The invariant is: clear_bit(__MUCSE_DOWN) happens strictly after
napi_enable_all() and before rnpgbe_irq_enable(), so by the time the
handler sees DOWN=0 and proceeds to napi_schedule_irqoff(), NAPI is
already enabled. On the teardown side, rnpgbe_down() sets the bit then
calls synchronize_irq(), guaranteeing in-flight handlers observe the
transition.
Both paths are safe, just via different mechanisms.
Dong Yibo (4):
net: rnpgbe: Add interrupt handling
net: rnpgbe: Add basic TX packet transmission support
net: rnpgbe: Add RX packet reception 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 | 208 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 45 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 19 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 2156 +++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 87 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 99 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c | 24 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h | 1 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 228 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 39 +
12 files changed, 2887 insertions(+), 23 deletions(-)
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
create mode 100644 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
--
2.25.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v7 1/4] net: rnpgbe: Add interrupt handling
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
@ 2026-06-11 10:00 ` Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 2/4] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Dong Yibo @ 2026-06-11 10:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, horms, u.kleine-koenig
Cc: linux-kernel, netdev, dong100, yaojun
Add comprehensive interrupt handling for the RNPGBE driver:
- Implement msi-x/msi interrupt configuration and management
- Create library functions for interrupt registration and cleanup
This infrastructure enables proper interrupt handling for the
RNPGBE driver.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/Makefile | 3 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 53 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 4 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 2 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 636 ++++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 34 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 48 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 8 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 1 +
9 files changed, 786 insertions(+), 3 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..fa6c7a8507c5 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -6,6 +6,10 @@
#include <linux/types.h>
#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+
+#include "rnpgbe_hw.h"
enum rnpgbe_boards {
board_n500,
@@ -35,21 +39,68 @@ 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;
+ int v_idx;
+ 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
+
+enum mucse_state_t {
+ __MUCSE_DOWN,
+};
+
struct mucse {
struct net_device *netdev;
struct pci_dev *pdev;
struct mucse_hw hw;
struct mucse_stats stats;
+#define M_FLAG_MSI_EN BIT(0)
+#define M_FLAG_MSIX_SINGLE_EN BIT(1)
+#define M_FLAG_MSIX_EN BIT(2)
+ u32 flags;
+ 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];
+ unsigned long state;
};
int rnpgbe_get_permanent_mac(struct mucse_hw *hw, u8 *perm_addr);
@@ -68,4 +119,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..cb241dc339d8
--- /dev/null
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -0,0 +1,636 @@
+// 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"
+
+/**
+ * rnpgbe_msix_other - Other irq handler
+ * @irq: irq num
+ * @data: private data
+ *
+ * @return: IRQ_HANDLED
+ **/
+static irqreturn_t rnpgbe_msix_other(int irq, void *data)
+{
+ struct mucse *mucse = (struct mucse *)data;
+
+ mucse_fw_irq_handler(&mucse->hw);
+
+ return IRQ_HANDLED;
+}
+
+static void rnpgbe_irq_disable_queues(struct mucse_q_vector *q_vector)
+{
+ struct mucse_ring *ring;
+
+ /* tx/rx use one register, different bit */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ writel(INT_VALID, ring->trig);
+ writel((RX_INT_MASK | TX_INT_MASK), ring->irq_mask);
+ }
+}
+
+/**
+ * rnpgbe_int_single - Msix-signle/msi irq handler
+ * @irq: irq num
+ * @data: private data
+ * @return: IRQ_HANDLED
+ **/
+static irqreturn_t rnpgbe_int_single(int irq, void *data)
+{
+ struct mucse *mucse = (struct mucse *)data;
+ struct mucse_q_vector *q_vector;
+
+ mucse_fw_irq_handler(&mucse->hw);
+
+ if (test_bit(__MUCSE_DOWN, &mucse->state))
+ return IRQ_HANDLED;
+
+ q_vector = mucse->q_vector[0];
+ rnpgbe_irq_disable_queues(q_vector);
+ if (q_vector->rx.ring || q_vector->tx.ring)
+ napi_schedule_irqoff(&q_vector->napi);
+
+ return IRQ_HANDLED;
+}
+
+static void rnpgbe_irq_enable_queues(struct mucse_q_vector *q_vector)
+{
+ struct mucse_ring *ring;
+
+ /* tx/rx use one register, different bit */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ writel(0, ring->irq_mask);
+
+ /* Trigger hw to re-check events lost during the
+ * masking window. This is not a W1C status clear
+ * nor a software interrupt trigger — hw re-issues
+ * the interrupt only if an event was actually dropped.
+ */
+ writel(INT_VALID | TX_INT_MASK | RX_INT_MASK, ring->trig);
+ }
+}
+
+/**
+ * rnpgbe_poll - NAPI Rx polling callback
+ * @napi: structure for representing this polling device
+ * @budget: how many packets driver is allowed to clean
+ *
+ * @return: work done in this call
+ * This function is used for legacy and MSI, NAPI mode
+ **/
+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
+ *
+ * In MSIX mode, register a dedicated handler for vector 0 (mailbox)
+ * In MSI/MSI-X_SINGLE mode, mailbox is multiplexed through
+ * data tx/rx handler.
+ *
+ * @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));
+
+ if (mucse->flags & M_FLAG_MSIX_EN) {
+ err = request_irq(pci_irq_vector(pdev, 0),
+ rnpgbe_msix_other, 0, mucse->mbx_name,
+ mucse);
+ } else {
+ err = request_irq(pci_irq_vector(pdev, 0),
+ rnpgbe_int_single, 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);
+}
+
+/**
+ * rnpgbe_set_num_queues - Allocate queues for device, feature dependent
+ * @mucse: pointer to private structure
+ *
+ * Determine tx/rx queue nums
+ **/
+static void rnpgbe_set_num_queues(struct mucse *mucse)
+{
+ /* start from 1 queue */
+ mucse->num_tx_queues = 1;
+ mucse->num_rx_queues = 1;
+}
+
+/**
+ * rnpgbe_set_interrupt_capability - Set MSI-X or MSI if supported
+ * @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;
+
+ /* Hardware limitation: only 1 MSI vector is supported even
+ * if multiple messages are requested. MSI mode falls back
+ * to single vector automatically.
+ */
+ v_budget = pci_alloc_irq_vectors(mucse->pdev, 1, v_budget,
+ PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (v_budget < 0)
+ return v_budget;
+
+ if (mucse->pdev->msix_enabled) {
+ /* q_vector not include mbx */
+ if (v_budget > 1) {
+ mucse->flags |= M_FLAG_MSIX_EN;
+ mucse->num_q_vectors = v_budget - 1;
+ } else {
+ mucse->flags |= M_FLAG_MSIX_SINGLE_EN;
+ mucse->num_q_vectors = 1;
+ }
+ } else {
+ /* hw only support 1 msi irq */
+ mucse->num_q_vectors = 1;
+ mucse->flags |= M_FLAG_MSI_EN;
+ }
+
+ 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
+ * @v_idx: index of vector used for this q_vector
+ * @r_idx: total number of rings to allocate
+ * @r_count: ring count
+ * @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 v_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;
+
+ 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[v_idx] = q_vector;
+ q_vector->mucse = mucse;
+ q_vector->v_idx = v_idx;
+ /* if mbx use separate irq, we should add 1 */
+ if (mucse->flags & M_FLAG_MSIX_EN)
+ q_vector->v_idx++;
+
+ 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;
+ 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;
+ 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
+ * @v_idx: index of vector to be freed
+ *
+ * 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 v_idx)
+{
+ struct mucse_q_vector *q_vector = mucse->q_vector[v_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[v_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 if success. if allocation fails we return -ENOMEM.
+ **/
+static int rnpgbe_alloc_q_vectors(struct mucse *mucse)
+{
+ int err, ring_cnt, v_remaing = mucse->num_q_vectors;
+ int r_remaing = min_t(int, mucse->num_tx_queues,
+ mucse->num_rx_queues);
+ int q_vector_nums = 0;
+ int eth_queue_idx = 0;
+ int ring_step = 1;
+ int ring_idx = 0;
+ int v_idx = 0;
+
+ for (; r_remaing > 0 && v_remaing > 0; v_remaing--) {
+ ring_cnt = DIV_ROUND_UP(r_remaing, v_remaing);
+ err = rnpgbe_alloc_q_vector(mucse, eth_queue_idx,
+ v_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_remaing -= ring_cnt;
+ q_vector_nums++;
+ v_idx++;
+ }
+ /* Fix the real used q_vectors_nums */
+ mucse->num_q_vectors = q_vector_nums;
+ 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 (v_idx--)
+ rnpgbe_free_q_vector(mucse, v_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);
+ mucse->flags &= ~(M_FLAG_MSIX_EN |
+ M_FLAG_MSIX_SINGLE_EN |
+ M_FLAG_MSI_EN);
+}
+
+/**
+ * rnpgbe_init_interrupt_scheme - Determine proper interrupt scheme
+ * @mucse: pointer to private structure
+ *
+ * We determine which interrupt scheme to use based on...
+ * - Hardware queue count
+ * - cpu numbers
+ * - irq mode (msi/legacy force 1)
+ *
+ * @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 v_idx = mucse->num_q_vectors;
+
+ mucse->num_rx_queues = 0;
+ mucse->num_tx_queues = 0;
+ mucse->num_q_vectors = 0;
+
+ while (v_idx--)
+ rnpgbe_free_q_vector(mucse, v_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)
+{
+ mucse->num_tx_queues = 0;
+ mucse->num_rx_queues = 0;
+ rnpgbe_free_q_vectors(mucse);
+ rnpgbe_reset_interrupt_capability(mucse);
+}
+
+/**
+ * rnpgbe_msix_clean_rings - Msix irq handler for ring irq
+ * @irq: irq num
+ * @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);
+ if (q_vector->rx.ring || q_vector->tx.ring)
+ 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;
+
+ if (mucse->flags & M_FLAG_MSIX_EN) {
+ 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;
+
+ if (mucse->flags & M_FLAG_MSIX_EN) {
+ 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_msi - Configure MSI hardware
+ * @mucse: pointer to private structure
+ *
+ * rnpgbe_configure_msi sets up the hardware to properly generate MSI
+ * interrupts.
+ **/
+static void rnpgbe_configure_msi(struct mucse *mucse)
+{
+ struct mucse_q_vector *q_vector = mucse->q_vector[0];
+ struct mucse_ring *ring;
+
+ /* tx/rx use one register, different bit */
+ mucse_for_each_ring(ring, q_vector->tx)
+ rnpgbe_set_ring_vector(mucse, ring->rnpgbe_queue_idx, 0);
+}
+
+/**
+ * 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 use one register, different bit */
+ mucse_for_each_ring(ring, q_vector->tx) {
+ rnpgbe_set_ring_vector(mucse, ring->rnpgbe_queue_idx,
+ q_vector->v_idx);
+ }
+ }
+}
+
+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 off interrupt generation on the NIC
+ * @mucse: board private structure
+ **/
+void rnpgbe_irq_disable(struct mucse *mucse)
+{
+ struct pci_dev *pdev = mucse->pdev;
+
+ if (mucse->flags & M_FLAG_MSIX_EN) {
+ 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));
+ }
+ } else {
+ rnpgbe_irq_disable_queues(mucse->q_vector[0]);
+ synchronize_irq(pci_irq_vector(pdev, 0));
+ }
+}
+
+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)
+{
+ set_bit(__MUCSE_DOWN, &mucse->state);
+ rnpgbe_napi_disable_all(mucse);
+ rnpgbe_irq_disable(mucse);
+}
+
+/**
+ * rnpgbe_up_complete - Final step for port up
+ * @mucse: pointer to private structure
+ **/
+void rnpgbe_up_complete(struct mucse *mucse)
+{
+ if (mucse->flags & (M_FLAG_MSIX_EN | M_FLAG_MSIX_SINGLE_EN))
+ rnpgbe_configure_msix(mucse);
+ else
+ rnpgbe_configure_msi(mucse);
+ rnpgbe_napi_enable_all(mucse);
+ clear_bit(__MUCSE_DOWN, &mucse->state);
+ 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..f0b90bf8bd55 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;
}
@@ -106,6 +129,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
mucse = netdev_priv(netdev);
mucse->netdev = netdev;
mucse->pdev = pdev;
+ set_bit(__MUCSE_DOWN, &mucse->state);
pci_set_drvdata(pdev, mucse);
hw = &mucse->hw;
@@ -166,11 +190,28 @@ 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 = 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) {
@@ -180,6 +221,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
err_notify);
}
err_free_net:
+ pci_set_drvdata(pdev, NULL);
free_netdev(netdev);
return err;
}
@@ -253,9 +295,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);
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
index 8c8bd5e8e1db..05684d716792 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c
@@ -189,3 +189,11 @@ int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
return 0;
}
+
+/**
+ * mucse_fw_irq_handler - Try to handle a req from hw
+ * @hw: pointer to the HW structure
+ **/
+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 fb24fc12b613..aa26c729588c 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
@@ -85,4 +85,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.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH net-next v7 2/4] net: rnpgbe: Add basic TX packet transmission support
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 1/4] net: rnpgbe: Add interrupt handling Dong Yibo
@ 2026-06-11 10:00 ` Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 3/4] net: rnpgbe: Add RX packet reception support Dong Yibo
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Dong Yibo @ 2026-06-11 10:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, horms, u.kleine-koenig
Cc: linux-kernel, netdev, dong100, yaojun
Implement basic transmit path for the RNPGBE driver:
- Add TX descriptor structure (rnpgbe_tx_desc) and TX buffer management
- Implement rnpgbe_xmit_frame_ring() for packet transmission
- Add TX ring resource allocation and cleanup functions
- Implement TX completion handling via rnpgbe_clean_tx_irq()
- Implement statistics collection for TX packets/bytes
- introduces atomic64_t dropped and tx_dropped reporting via
ndo_get_stats64
- Fix DMA mask setup to use dma_set_mask_and_coherent() with proper
fallback
- Adds cycles_per_us to struct mucse_hw to scale RNPGBE_TX_INT_TIME
This enables basic packet transmission functionality for the RNPGBE driver.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 79 ++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 4 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 3 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 632 ++++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 30 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 38 +-
6 files changed, 776 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index fa6c7a8507c5..b2045891f205 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -43,20 +43,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;
@@ -72,12 +137,11 @@ struct mucse_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_DOWN,
};
@@ -86,7 +150,6 @@ struct mucse {
struct net_device *netdev;
struct pci_dev *pdev;
struct mucse_hw hw;
- struct mucse_stats stats;
#define M_FLAG_MSI_EN BIT(0)
#define M_FLAG_MSIX_SINGLE_EN BIT(1)
#define M_FLAG_MSIX_EN BIT(2)
@@ -96,6 +159,8 @@ struct mucse {
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;
@@ -121,4 +186,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..cbc593902030 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -7,12 +7,15 @@
#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 TX_AXI_RW_EN 0xc
#define RNPGBE_DMA_AXI_EN 0x0010
#define RNPGBE_MAX_QUEUES 8
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
index cb241dc339d8..4ce5063a0c50 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -3,6 +3,8 @@
#include <linux/pci.h>
#include <linux/netdevice.h>
+#include <linux/vmalloc.h>
+#include <net/netdev_queues.h>
#include "rnpgbe_lib.h"
#include "rnpgbe.h"
@@ -26,6 +28,7 @@ static irqreturn_t rnpgbe_msix_other(int irq, void *data)
static void rnpgbe_irq_disable_queues(struct mucse_q_vector *q_vector)
{
+ struct mucse_hw *hw = &q_vector->mucse->hw;
struct mucse_ring *ring;
/* tx/rx use one register, different bit */
@@ -33,6 +36,8 @@ 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 */
+ readl(hw->hw_addr);
}
/**
@@ -76,6 +81,114 @@ 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 is for work done within 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_carrier_ok(tx_ring->netdev) ||
+ test_bit(__MUCSE_DOWN, &mucse->state));
+
+ return !!budget;
+}
+
/**
* rnpgbe_poll - NAPI Rx polling callback
* @napi: structure for representing this polling device
@@ -88,12 +201,22 @@ 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);
@@ -256,12 +379,17 @@ 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->pfvfnum = hw->pfvfnum;
+ u64_stats_init(&ring->syncp);
mucse->tx_ring[ring->queue_index] = ring;
txr_idx += step;
ring++;
@@ -613,11 +741,118 @@ static void rnpgbe_napi_disable_all(struct mucse *mucse)
napi_disable(&mucse->q_vector[i]->napi);
}
+/**
+ * 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;
+
+ /* Stop hw. hardware design guarantees:
+ * - No new descriptors will be fetched after TX_START=0
+ * - No DMA will be initiated for already-fetched descriptors
+ */
+ mucse_ring_wr32(tx_ring, RNPGBE_TX_START, 0);
+ /* Flush posted write to ensure hardware sees the disable command.
+ * After this read completes, all TX DMA for this ring is
+ * guaranteed quiesced.
+ */
+ (void)mucse_ring_rd32(tx_ring, RNPGBE_TX_START);
+ /* Wait for in-flight DMA to quiesce */
+ usleep_range(300, 500);
+
+ /* 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 - Free Tx Buffers for all queues
+ * @mucse: board private structure
+ **/
+static void rnpgbe_clean_all_tx_rings(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+
+ for (int i = 0; i < mucse->num_tx_queues; i++)
+ rnpgbe_clean_tx_ring(mucse->tx_ring[i]);
+
+ 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);
+}
+
void rnpgbe_down(struct mucse *mucse)
{
+ struct net_device *netdev = mucse->netdev;
+
set_bit(__MUCSE_DOWN, &mucse->state);
+ netif_tx_stop_all_queues(netdev);
+ netif_tx_disable(netdev);
rnpgbe_napi_disable_all(mucse);
rnpgbe_irq_disable(mucse);
+ rnpgbe_clean_all_tx_rings(mucse);
}
/**
@@ -626,11 +861,408 @@ void rnpgbe_down(struct mucse *mucse)
**/
void rnpgbe_up_complete(struct mucse *mucse)
{
+ struct net_device *netdev = mucse->netdev;
+
if (mucse->flags & (M_FLAG_MSIX_EN | M_FLAG_MSIX_SINGLE_EN))
rnpgbe_configure_msix(mucse);
else
rnpgbe_configure_msi(mucse);
+
rnpgbe_napi_enable_all(mucse);
clear_bit(__MUCSE_DOWN, &mucse->state);
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)
+{
+ rnpgbe_clean_tx_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
+ * @mucse: pointer to private structure
+ *
+ * @return: 0 on success, negative on failure
+ **/
+static int rnpgbe_setup_tx_resources(struct mucse_ring *tx_ring,
+ struct mucse *mucse)
+{
+ 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;
+
+ /* Stop hw. hardware design guarantees:
+ * - No new descriptors will be fetched after TX_START=0
+ * - No DMA will be initiated for already-fetched descriptors
+ */
+ mucse_ring_wr32(ring, RNPGBE_TX_START, 0);
+ /* Flush posted write to ensure hardware sees the disable command.
+ * After this read completes, all TX DMA for this ring is
+ * guaranteed quiesced.
+ */
+ (void)mucse_ring_rd32(ring, RNPGBE_TX_START);
+ /* Wait for in-flight DMA to quiesce */
+ usleep_range(300, 500);
+
+ 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) | (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 - 1);
+ 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);
+ /* Ensure all config is written before enabling queue */
+ wmb();
+ mucse_ring_wr32(ring, RNPGBE_TX_START, 1);
+}
+
+/**
+ * rnpgbe_configure_tx - Configure Transmit Unit after Reset
+ * @mucse: pointer to private structure
+ *
+ * Configure the Tx DMA after a reset.
+ **/
+void rnpgbe_configure_tx(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 i, dma_axi_ctl;
+
+ 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);
+ /* Setup the HW Tx Head and Tail descriptor pointers */
+ for (i = 0; i < mucse->num_tx_queues; i++)
+ rnpgbe_configure_tx_ring(mucse, mucse->tx_ring[i]);
+}
+
+/**
+ * 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], mucse);
+ 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 sk_buff *skb = first->skb;
+ struct skb_shared_info *shinfo = skb_shinfo(skb);
+ /* hw need this in high 8 bytes desc */
+ u64 fun_id = ((u64)(tx_ring->pfvfnum) << (56));
+ struct mucse_tx_buffer *tx_buffer;
+ int nr_frags = shinfo->nr_frags;
+ struct rnpgbe_tx_desc *tx_desc;
+ u16 i = tx_ring->next_to_use;
+ unsigned int data_len, size;
+ int frag_idx = 0;
+ skb_frag_t *frag;
+ dma_addr_t dma;
+
+ 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;
+ }
+ 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)
+{
+ u16 count = TXD_USE_COUNT(skb_headlen(skb));
+ /* 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;
+
+ 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;
+ dropped = atomic64_read(&ring->stats.dropped);
+ } while (u64_stats_fetch_retry(&ring->syncp, start));
+ 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..ba9206678ede 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -6,17 +6,40 @@
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
+#define RNPGBE_DMA_INT_TRIG 0x2c
/* | 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
+/* hw require this not zero */
+#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 +54,11 @@ 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);
+void rnpgbe_configure_tx(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 f0b90bf8bd55..b8f79603dd97 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -26,6 +26,17 @@ static struct pci_device_id rnpgbe_pci_tbl[] = {
{ },
};
+/**
+ * rnpgbe_configure - Configure info to hw
+ * @mucse: pointer to private structure
+ *
+ * rnpgbe_configure configure mac, tx, rx regs to hw
+ **/
+static void rnpgbe_configure(struct mucse *mucse)
+{
+ rnpgbe_configure_tx(mucse);
+}
+
/**
* rnpgbe_open - Called when a network interface is made active
* @netdev: network interface device structure
@@ -49,6 +60,11 @@ 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;
+
+ rnpgbe_configure(mucse);
rnpgbe_up_complete(mucse);
return 0;
@@ -72,6 +88,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 +98,32 @@ 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;
+ 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
@@ -173,6 +197,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);
@@ -202,6 +227,9 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
goto err_clear_interrupt;
}
+ netdev->features |= NETIF_F_SG;
+ netdev->features |= NETIF_F_HIGHDMA;
+
err = register_netdev(netdev);
if (err)
goto err_remove_mbx;
@@ -245,7 +273,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));
+ err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(56));
if (err) {
dev_err(&pdev->dev,
"No usable DMA configuration, aborting %d\n", err);
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH net-next v7 3/4] net: rnpgbe: Add RX packet reception support
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 1/4] net: rnpgbe: Add interrupt handling Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 2/4] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
@ 2026-06-11 10:00 ` Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 4/4] net: rnpgbe: Add link status handling support Dong Yibo
2026-06-12 18:44 ` [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Simon Horman
4 siblings, 0 replies; 6+ messages in thread
From: Dong Yibo @ 2026-06-11 10:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, horms, u.kleine-koenig
Cc: linux-kernel, netdev, dong100, yaojun
Add basic RX packet reception infrastructure to the rnpgbe driver:
- Add RX descriptor structure (union rnpgbe_rx_desc) with write-back
format for hardware status
- Add RX buffer management using page_pool for efficient page recycling
(basic version, 1 page for 1 descriptor, no page splitting; ring depth
is fixed at 512, 'ethtool ringparm' not yet supported)
- Implement NAPI poll callback (rnpgbe_poll) for RX processing
- Add RX ring setup and cleanup functions
- Implement packet building from page buffer
- Add RX statistics tracking
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/Kconfig | 1 +
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 60 +-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 1 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 718 ++++++++++++++++++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 24 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 8 +
6 files changed, 810 insertions(+), 2 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 b2045891f205..5982fb975642 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/if.h>
+#include <linux/workqueue.h>
#include "rnpgbe_hw.h"
@@ -61,7 +62,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;
@@ -79,13 +105,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;
@@ -101,6 +138,8 @@ struct mucse_ring {
unsigned int size;
struct mucse_queue_stats stats;
struct u64_stats_sync syncp;
+ bool drop_status;
+ struct delayed_work alloc_retry_work;
} ____cacheline_internodealigned_in_smp;
static inline u16 mucse_desc_unused(struct mucse_ring *ring)
@@ -111,6 +150,23 @@ 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;
+
+ /* Reserve 16 descriptors to keep the RX head aligned to a 16-descriptor
+ * boundary after refill. Buffers are always refilled in batches of 16
+ * (M_RX_BUFFER_WRITE) and the hardware DMA engine performs cache-line
+ * prefetch based on the current head position. Keeping the head at a
+ * 16-aligned boundary prevents partial cache-line fetches and improves
+ * DMA efficiency.
+ *
+ * Effective ring depth: ring->count - 16
+ */
+ return ((ntc > ntu) ? 0 : ring->count) + ntc - ntu - 16;
+}
+
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) |
@@ -140,6 +196,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 +220,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 cbc593902030..03688586b447 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -16,6 +16,7 @@
#define M_DEFAULT_N210_MHZ 62
#define TX_AXI_RW_EN 0xc
+#define RX_AXI_RW_EN 0x03
#define RNPGBE_DMA_AXI_EN 0x0010
#define RNPGBE_MAX_QUEUES 8
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
index 4ce5063a0c50..79cbac7cd28d 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -3,8 +3,10 @@
#include <linux/pci.h>
#include <linux/netdevice.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"
@@ -189,6 +191,420 @@ 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;
+
+ if (rx_ring->next_to_use != i) {
+ /*
+ * Force memory writes to complete before letting h/w know
+ * there are new rx descriptors to fetch. (Only applicable
+ * for weak-ordered memory model archs, such as IA-64).
+ */
+ dma_wmb();
+ /* Notify hw new rx descriptors is ready */
+ 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 no-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)
+{
+ /* FIXME: truesize is PAGE_SIZE for 1 page = 1 descriptor.
+ * Optimize with page splitting later when refactoring
+ * the Rx buffer management.
+ */
+ 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 sbk 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 = 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 - Setup skb header fields from desc
+ * @rx_ring: structure containing ring specific data
+ * @skb: skb currently being received and modified
+ *
+ * rnpgbe_process_skb_fields checks the ring, descriptor information
+ * in order to setup the hash, chksum, vlan, protocol, and other
+ * fields within the skb.
+ **/
+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_alloc_retry_work - Deferred alloc retry callback
+ * @work: pointer to delayed_work embedded in mucse_ring
+ *
+ * Called after a delay when page_pool allocation failed in the NAPI
+ * poll loop. Re-schedules NAPI to retry descriptor refill without
+ * busy-looping.
+ **/
+static void rnpgbe_alloc_retry_work(struct work_struct *work)
+{
+ struct delayed_work *dw = to_delayed_work(work);
+ struct mucse_ring *rx_ring =
+ container_of(dw, struct mucse_ring, alloc_retry_work);
+ struct mucse_q_vector *q_vector = rx_ring->q_vector;
+
+ napi_schedule(&q_vector->napi);
+}
+
+/**
+ * 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);
+
+ while (likely(total_rx_packets < 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)) {
+ queue_delayed_work(system_wq,
+ &rx_ring->alloc_retry_work,
+ msecs_to_jiffies(500));
+ cleaned_count = mucse_desc_unused_rx(rx_ring);
+ } else {
+ cancel_delayed_work(&rx_ring->alloc_retry_work);
+ 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 enforces: minimum 33-bytes descriptor(no 1-13 byte
+ * size), multi-descriptors only for jumbo frames > 1536 bytes
+ * (controlled by M_DEFAULT_SG=96, each descriptor no more than
+ * 1536 bytes). Small packets use 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;
+
+ 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 any skb left from a previous non-EOP descriptor.
+ * When an earlier descriptor for a scattered (jumbo)
+ * packet stored its skb in this slot via
+ * rnpgbe_is_non_eop() and this descriptor turns out to
+ * be invalid, we must release the partial skb 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))
+ 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;
+ }
+
+ /* TODO: hardware error checks (wb.status: crc_err, etc.) in
+ * the future. For now the basic RX path relies on the stack
+ * to drop malformed packets naturally.
+ */
+
+ 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)))
+ 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;
+ 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 budget accounting */
+ total_rx_packets++;
+ }
+
+ 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 total_rx_packets;
+}
+
/**
* rnpgbe_poll - NAPI Rx polling callback
* @napi: structure for representing this polling device
@@ -203,6 +619,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) {
@@ -214,6 +631,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;
@@ -396,12 +827,17 @@ 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->pfvfnum = hw->pfvfnum;
+ u64_stats_init(&ring->syncp);
mucse->rx_ring[ring->queue_index] = ring;
rxr_idx += step;
ring++;
@@ -843,6 +1279,23 @@ static void rnpgbe_clean_all_tx_rings(struct mucse *mucse)
mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
}
+/**
+ * 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)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_clean_rx_ring(mucse->rx_ring[i]);
+
+ 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);
+}
+
void rnpgbe_down(struct mucse *mucse)
{
struct net_device *netdev = mucse->netdev;
@@ -853,6 +1306,7 @@ void rnpgbe_down(struct mucse *mucse)
rnpgbe_napi_disable_all(mucse);
rnpgbe_irq_disable(mucse);
rnpgbe_clean_all_tx_rings(mucse);
+ rnpgbe_clean_all_rx_rings(mucse);
}
/**
@@ -872,6 +1326,8 @@ void rnpgbe_up_complete(struct mucse *mucse)
clear_bit(__MUCSE_DOWN, &mucse->state);
rnpgbe_irq_enable(mucse);
netif_tx_start_all_queues(netdev);
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ mucse_ring_wr32(mucse->rx_ring[i], RNPGBE_RX_START, 1);
}
/**
@@ -1264,5 +1720,267 @@ 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;
+ dropped = atomic64_read(&ring->stats.dropped);
+ } while (u64_stats_fetch_retry(&ring->syncp, start));
+ 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)
+{
+ int ret = 0;
+
+ 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,
+ .dma_dir = DMA_FROM_DEVICE,
+ .offset = 0,
+ .max_len = PAGE_SIZE,
+ };
+
+ 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
+ * @mucse: pointer to private structure
+ *
+ * @return: 0 on success, negative on failure
+ **/
+static int rnpgbe_setup_rx_resources(struct mucse_ring *rx_ring,
+ struct mucse *mucse)
+{
+ 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;
+
+ INIT_DELAYED_WORK(&rx_ring->alloc_retry_work,
+ rnpgbe_alloc_retry_work);
+
+ 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_clean_rx_ring - Free Rx Buffers per Queue
+ * @rx_ring: ring to free buffers from
+ **/
+void rnpgbe_clean_rx_ring(struct mucse_ring *rx_ring)
+{
+ struct mucse_rx_buffer *rx_buffer;
+ u16 i;
+
+ /* 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 the disable command.
+ * After this read completes, all RX DMA for this ring is
+ * guaranteed quiesced.
+ */
+ (void)mucse_ring_rd32(rx_ring, RNPGBE_RX_START);
+ /* Wait for in-flight DMA to quiesce */
+ usleep_range(300, 500);
+
+ cancel_delayed_work_sync(&rx_ring->alloc_retry_work);
+
+ /* 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_free_rx_resources - Free Rx Resources
+ * @rx_ring: ring to clean the resources from
+ *
+ * Free all receive software resources
+ **/
+static void rnpgbe_free_rx_resources(struct mucse_ring *rx_ring)
+{
+ rnpgbe_clean_rx_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], mucse);
+ 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;
+
+ /* 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(ring, RNPGBE_RX_START, 0);
+ /* Flush posted write to ensure hardware sees the disable command.
+ * After this read completes, all RX DMA for this ring is
+ * guaranteed quiesced.
+ */
+ (void)mucse_ring_rd32(ring, RNPGBE_RX_START);
+ /* Wait for in-flight DMA to quiesce */
+ usleep_range(300, 500);
+ /* set descripts registers*/
+ 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) | (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 - 1);
+ 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);
+ 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.
+ **/
+void rnpgbe_configure_rx(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ u32 dma_axi_ctl;
+
+ for (int i = 0; i < mucse->num_rx_queues; i++)
+ rnpgbe_configure_rx_ring(mucse, mucse->rx_ring[i]);
+
+ 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);
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
index ba9206678ede..29fa6cd56e21 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -9,12 +9,28 @@ 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_ARB_DEF_LVL 0x50
+#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
@@ -38,13 +54,15 @@ 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)
+#define M_RX_BUFFER_WRITE 16
+
/* hw require this not zero */
#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);
@@ -55,10 +73,14 @@ void rnpgbe_irq_disable(struct mucse *mucse);
void rnpgbe_down(struct mucse *mucse);
void rnpgbe_up_complete(struct mucse *mucse);
void rnpgbe_configure_tx(struct mucse *mucse);
+void rnpgbe_configure_rx(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);
+void rnpgbe_clean_rx_ring(struct mucse_ring *rx_ring);
+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 b8f79603dd97..cfb2328bfb75 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -35,6 +35,7 @@ static struct pci_device_id rnpgbe_pci_tbl[] = {
static void rnpgbe_configure(struct mucse *mucse)
{
rnpgbe_configure_tx(mucse);
+ rnpgbe_configure_rx(mucse);
}
/**
@@ -63,11 +64,16 @@ 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;
rnpgbe_configure(mucse);
rnpgbe_up_complete(mucse);
return 0;
+err_free_tx:
+ rnpgbe_free_all_tx_resources(mucse);
err_free_irqs:
rnpgbe_free_irq(mucse);
return err;
@@ -89,6 +95,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;
}
@@ -121,6 +128,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.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH net-next v7 4/4] net: rnpgbe: Add link status handling support
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
` (2 preceding siblings ...)
2026-06-11 10:00 ` [PATCH net-next v7 3/4] net: rnpgbe: Add RX packet reception support Dong Yibo
@ 2026-06-11 10:00 ` Dong Yibo
2026-06-12 18:44 ` [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Simon Horman
4 siblings, 0 replies; 6+ messages in thread
From: Dong Yibo @ 2026-06-11 10:00 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, horms, u.kleine-koenig
Cc: linux-kernel, netdev, dong100, yaojun
Add link status management infrastructure to the rnpgbe driver:
- Add link status related data structures (speed, duplex, link state)
- Implement firmware link event handling via mailboxa
(fw controls link states, don't support phylink)
- Add service task for periodic link status monitoring
- Implement carrier status management (netif_carrier_on/off)
- Add port up/down notification to firmware
This enables the driver to properly track and report link status changes.
Signed-off-by: Dong Yibo <dong100@mucse.com>
---
drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h | 22 +-
.../net/ethernet/mucse/rnpgbe/rnpgbe_chip.c | 37 ++-
drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h | 13 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.c | 174 +++++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_lib.h | 1 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 5 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c | 24 ++
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h | 1 +
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.c | 220 +++++++++++++++++-
.../net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h | 38 +++
10 files changed, 521 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
index 5982fb975642..894d82371dbd 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe.h
@@ -17,25 +17,34 @@ enum rnpgbe_boards {
board_n210
};
+struct mbx_req_cookie {
+ int timeout;
+ struct completion comp;
+ spinlock_t lock; /* serializes cmd access (IRQ vs process context) */
+ /* must match sizeof(struct mbx_fw_cmd_reply) */
+ u8 cmd[56];
+};
+
struct mucse_mbx_info {
u32 timeout_us;
u32 delay_us;
u16 fw_req;
u16 fw_ack;
+ struct mbx_req_cookie cookie;
/* lock for only one use mbx */
struct mutex lock;
/* fw <--> pf mbx */
+ bool irq_en;
u32 fwpf_shm_base;
u32 pf2fw_mbx_ctrl;
u32 fwpf_mbx_mask;
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 {
@@ -44,8 +53,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 {
@@ -210,6 +222,7 @@ struct mucse {
#define M_FLAG_MSI_EN BIT(0)
#define M_FLAG_MSIX_SINGLE_EN BIT(1)
#define M_FLAG_MSIX_EN BIT(2)
+#define M_FLAG_NEED_LINK_UPDATE BIT(3)
u32 flags;
struct mucse_ring *tx_ring[RNPGBE_MAX_QUEUES]
____cacheline_aligned_in_smp;
@@ -224,6 +237,8 @@ struct mucse {
int num_rx_queues;
char mbx_name[32];
unsigned long state;
+ 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);
@@ -232,6 +247,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_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 291e77d573fe..98c7c1492f6a 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_chip.c
@@ -66,11 +66,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;
}
@@ -149,3 +155,32 @@ int rnpgbe_init_hw(struct mucse_hw *hw, int board_type)
return 0;
}
+
+/**
+ * rnpgbe_set_link - Setup 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);
+
+ if (linkup)
+ value |= GMAC_CONTROL_RE;
+ else
+ value &= ~GMAC_CONTROL_RE;
+
+ mucse_hw_wr32(hw, GMAC_CONTROL, value);
+
+ /* RX_ALL always be set when link is up -- the GMAC requires it
+ * for passing all packet. Promiscuous/unicast filtering is
+ * handled by other separate chip-level register.
+ */
+ if (linkup)
+ mucse_hw_wr32(hw, GMAC_FRAME_FILTER, GMAC_RX_ALL);
+ else
+ 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 03688586b447..4d1a9a386e9d 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_hw.h
@@ -17,7 +17,20 @@
#define TX_AXI_RW_EN 0xc
#define RX_AXI_RW_EN 0x03
+/* mask all valid info */
+#define M_ST_MASK 0xff000f11
+/* 31:28 set 0xa to valid it is a driver set info */
+#define M_DEFAULT_ST 0xa0000000
+/* driver setup this by own info */
+/*bit: 25:24 | 11:8 | 4 | 0 */
+/*fun: pause | speed | duplex | up/down */
+#define RNPGBE_LINK_ST 0x000c
#define RNPGBE_DMA_AXI_EN 0x0010
+#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 79cbac7cd28d..8c68d9879b93 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.c
@@ -645,11 +645,16 @@ static int rnpgbe_poll(struct napi_struct *napi, int budget)
clean_complete = false;
}
+ if (test_bit(__MUCSE_DOWN, &q_vector->mucse->state))
+ clean_complete = true;
+
if (!clean_complete)
return budget;
- if (likely(napi_complete_done(napi, work_done)))
- rnpgbe_irq_enable_queues(q_vector);
+ if (likely(napi_complete_done(napi, work_done))) {
+ if (!test_bit(__MUCSE_DOWN, &q_vector->mucse->state))
+ rnpgbe_irq_enable_queues(q_vector);
+ }
return work_done;
}
@@ -667,6 +672,7 @@ static int rnpgbe_poll(struct napi_struct *napi, int budget)
int rnpgbe_request_mbx_irq(struct mucse *mucse)
{
struct pci_dev *pdev = mucse->pdev;
+ struct mucse_hw *hw = &mucse->hw;
int err = 0;
snprintf(mucse->mbx_name, sizeof(mucse->mbx_name),
@@ -682,6 +688,9 @@ int rnpgbe_request_mbx_irq(struct mucse *mucse)
mucse);
}
+ if (!err)
+ hw->mbx.irq_en = true;
+
return err;
}
@@ -692,8 +701,10 @@ int rnpgbe_request_mbx_irq(struct mucse *mucse)
void rnpgbe_free_mbx_irq(struct mucse *mucse)
{
struct pci_dev *pdev = mucse->pdev;
+ struct mucse_hw *hw = &mucse->hw;
free_irq(pci_irq_vector(pdev, 0), mucse);
+ hw->mbx.irq_en = false;
}
/**
@@ -1299,8 +1310,35 @@ static void rnpgbe_clean_all_rx_rings(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;
set_bit(__MUCSE_DOWN, &mucse->state);
+ cancel_delayed_work_sync(&mucse->serv_task);
+
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ hw->link = false;
+ hw->speed = 0;
+ hw->duplex = 0;
+ mucse->flags &= ~M_FLAG_NEED_LINK_UPDATE;
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ rnpgbe_set_link(hw, false);
+
+ 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");
+ }
+ netif_carrier_off(netdev);
netif_tx_stop_all_queues(netdev);
netif_tx_disable(netdev);
rnpgbe_napi_disable_all(mucse);
@@ -1316,6 +1354,9 @@ void rnpgbe_down(struct mucse *mucse)
void rnpgbe_up_complete(struct mucse *mucse)
{
struct net_device *netdev = mucse->netdev;
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+ int err;
if (mucse->flags & (M_FLAG_MSIX_EN | M_FLAG_MSIX_SINGLE_EN))
rnpgbe_configure_msix(mucse);
@@ -1328,6 +1369,31 @@ void rnpgbe_up_complete(struct mucse *mucse)
netif_tx_start_all_queues(netdev);
for (int i = 0; i < mucse->num_rx_queues; i++)
mucse_ring_wr32(mucse->rx_ring[i], RNPGBE_RX_START, 1);
+
+ err = rnpgbe_send_notify(hw, true, mucse_fw_portup);
+ if (err) {
+ dev_warn(&hw->pdev->dev, "Send portup to hw failed %d\n", err);
+ dev_warn(&hw->pdev->dev, "Port is not truly up\n");
+ }
+
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ hw->link = false;
+ hw->speed = 0;
+ hw->duplex = 0;
+ mucse->flags &= ~M_FLAG_NEED_LINK_UPDATE;
+ /* echo driver down to hw */
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_DEFAULT_ST);
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+
+ err = rnpgbe_send_notify(hw, true, 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 not report link event\n");
+ }
+
+ queue_delayed_work(system_wq, &mucse->serv_task,
+ msecs_to_jiffies(500));
}
/**
@@ -1984,3 +2050,107 @@ void rnpgbe_configure_rx(struct mucse *mucse)
dma_axi_ctl |= RX_AXI_RW_EN;
mucse_hw_wr32(hw, RNPGBE_DMA_AXI_EN, dma_axi_ctl);
}
+
+/**
+ * rnpgbe_process_link_event - Update the link status
+ * @mucse: pointer to the device private structure
+ *
+ * @return: link status
+ **/
+static int rnpgbe_process_link_event(struct mucse *mucse)
+{
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+ bool link;
+
+ /* lockless fast-path ,serv_task retry handles race */
+ if (!(mucse->flags & M_FLAG_NEED_LINK_UPDATE))
+ return hw->link;
+
+ spin_lock_irqsave(&mucse->link_lock, flags);
+
+ link = hw->link;
+
+ mucse->flags &= ~M_FLAG_NEED_LINK_UPDATE;
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+
+ return link;
+}
+
+/**
+ * rnpgbe_link_is_up - Update netif_carrier status and
+ * print link up message
+ * @mucse: pointer to the device private structure
+ **/
+static void rnpgbe_link_is_up(struct mucse *mucse)
+{
+ struct net_device *netdev = mucse->netdev;
+ struct mucse_hw *hw = &mucse->hw;
+ unsigned long flags;
+
+ /* Only continue if link was previously down */
+ if (netif_carrier_ok(netdev))
+ return;
+ spin_lock_irqsave(&mucse->link_lock, flags);
+ netdev_info(netdev, "NIC Link is Up %d Mbps, %s Duplex\n",
+ hw->speed, hw->duplex ? "Full" : "Half");
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ rnpgbe_set_link(hw, true);
+ 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;
+ struct mucse_hw *hw = &mucse->hw;
+
+ /* Only continue if link was up previously */
+ if (!netif_carrier_ok(netdev))
+ return;
+ netdev_info(netdev, "NIC Link is Down\n");
+ rnpgbe_set_link(hw, false);
+ netif_carrier_off(netdev);
+}
+
+/**
+ * rnpgbe_process_link_subtask - Check and bring link up
+ * @mucse: pointer to the device private structure
+ **/
+static void rnpgbe_process_link_subtask(struct mucse *mucse)
+{
+ /* if interface is down do nothing */
+ if (test_bit(__MUCSE_DOWN, &mucse->state))
+ return;
+
+ if (rnpgbe_process_link_event(mucse))
+ rnpgbe_link_is_up(mucse);
+ 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 (test_bit(__MUCSE_DOWN, &mucse->state))
+ return;
+
+ rnpgbe_process_link_subtask(mucse);
+
+ /* Periodic requeue is intentional: future patches will add
+ * statistics polling and other housekeeping tasks beyond
+ * link state handling.
+ */
+ if (!test_bit(__MUCSE_DOWN, &mucse->state))
+ queue_delayed_work(system_wq, &mucse->serv_task,
+ msecs_to_jiffies(500));
+}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
index 29fa6cd56e21..9511f4c96457 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_lib.h
@@ -83,4 +83,5 @@ void rnpgbe_get_stats64(struct net_device *netdev,
void rnpgbe_clean_rx_ring(struct mucse_ring *rx_ring);
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 cfb2328bfb75..5d7cb51fcb0b 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -52,6 +52,7 @@ static int rnpgbe_open(struct net_device *netdev)
struct mucse *mucse = netdev_priv(netdev);
int err;
+ netif_carrier_off(netdev);
err = rnpgbe_request_irq(mucse);
if (err)
return err;
@@ -181,6 +182,7 @@ static int rnpgbe_add_adapter(struct pci_dev *pdev,
dev_err(&pdev->dev, "Init hw err %d\n", err);
goto err_free_net;
}
+
/* Step 1: Send power-up notification to firmware (no response expected)
* This informs firmware to initialize hardware power state, but
* firmware only acknowledges receipt without returning data. Must be
@@ -223,6 +225,9 @@ 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);
+
err = rnpgbe_init_interrupt_scheme(mucse);
if (err) {
dev_err(&pdev->dev, "init interrupt failed %d\n", err);
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
index de5e29230b3c..74919c668b99 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.c
@@ -247,6 +247,26 @@ int mucse_poll_and_read_mbx(struct mucse_hw *hw, u32 *msg, u16 size)
return mucse_read_mbx_pf(hw, msg, size);
}
+/**
+ * mucse_check_and_read_mbx - check if there is notification and receive message
+ * @hw: pointer to the HW structure
+ * @msg: the message buffer
+ * @size: length of buffer
+ *
+ * Return: 0 if it successfully received a message notification and
+ * copied it into the receive buffer, negative errno on failure
+ **/
+int mucse_check_and_read_mbx(struct mucse_hw *hw, u32 *msg, u16 size)
+{
+ int err;
+
+ err = mucse_check_for_msg_pf(hw);
+ if (err)
+ return err;
+
+ return mucse_read_mbx_pf(hw, msg, size);
+}
+
/**
* mucse_mbx_get_fwack - Read fw ack from reg
* @mbx: pointer to the MBX structure
@@ -402,5 +422,9 @@ void mucse_init_mbx_params_pf(struct mucse_hw *hw)
mbx->delay_us = 100;
mbx->timeout_us = 4 * USEC_PER_SEC;
mutex_init(&mbx->lock);
+ init_completion(&mbx->cookie.comp);
+ spin_lock_init(&mbx->cookie.lock);
+ mbx->cookie.timeout = 5 * HZ;
+ mbx->irq_en = false;
mucse_mbx_reset(hw);
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
index e6fcc8d1d3ca..cba54a07a7fa 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx.h
@@ -17,4 +17,5 @@
int mucse_write_and_wait_ack_mbx(struct mucse_hw *hw, u32 *msg, u16 size);
void mucse_init_mbx_params_pf(struct mucse_hw *hw);
int mucse_poll_and_read_mbx(struct mucse_hw *hw, u32 *msg, u16 size);
+int mucse_check_and_read_mbx(struct mucse_hw *hw, u32 *msg, u16 size);
#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 05684d716792..e46dbf7d8d80 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/ethtool.h>
#include "rnpgbe.h"
#include "rnpgbe_mbx.h"
@@ -23,23 +24,47 @@ static int mucse_fw_send_cmd_wait_resp(struct mucse_hw *hw,
struct mbx_fw_cmd_req *req,
struct mbx_fw_cmd_reply *reply)
{
+ struct mbx_req_cookie *cookie = &hw->mbx.cookie;
int len = le16_to_cpu(req->datalen);
int retry_cnt = 3;
int err;
+ BUILD_BUG_ON(sizeof(struct mbx_fw_cmd_reply) != 56);
+
mutex_lock(&hw->mbx.lock);
+
+ if (hw->mbx.irq_en)
+ reinit_completion(&cookie->comp);
+
err = mucse_write_and_wait_ack_mbx(hw, (u32 *)req, len);
if (err)
goto out;
+
do {
- err = mucse_poll_and_read_mbx(hw, (u32 *)reply,
- sizeof(*reply));
- if (err)
- goto out;
- /* mucse_write_and_wait_ack_mbx return 0 means fw has
- * received request, wait for the expect opcode
- * reply with 'retry_cnt' times.
- */
+ if (hw->mbx.irq_en) {
+ unsigned long flags;
+
+ err = wait_for_completion_timeout(&cookie->comp,
+ cookie->timeout);
+ if (!err) {
+ err = -ETIMEDOUT;
+ goto out;
+ }
+
+ spin_lock_irqsave(&cookie->lock, flags);
+ memcpy((u8 *)reply, cookie->cmd, sizeof(*reply));
+ spin_unlock_irqrestore(&cookie->lock, flags);
+ err = 0;
+ } else {
+ err = mucse_poll_and_read_mbx(hw, (u32 *)reply,
+ sizeof(*reply));
+ if (err)
+ goto out;
+ /* mucse_write_and_wait_ack_mbx return 0 means fw has
+ * received request, wait for the expect opcode
+ * reply with 'retry_cnt' times.
+ */
+ }
} while (--retry_cnt >= 0 && reply->opcode != req->opcode);
out:
mutex_unlock(&hw->mbx.lock);
@@ -190,10 +215,189 @@ int mucse_mbx_get_macaddr(struct mucse_hw *hw, int pfvfnum,
return 0;
}
+/**
+ * mucse_mbx_phyup - Echo fw let the phy up
+ * @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)
+{
+ struct mbx_fw_cmd_req req = {
+ .datalen = cpu_to_le16(sizeof(req.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.datalen);
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_write_and_wait_ack_mbx(hw, (u32 *)&req, len);
+ mutex_unlock(&hw->mbx.lock);
+
+ return err;
+}
+
+/**
+ * mucse_mbx_link_report - Echo fw report link change event or not
+ * @hw: pointer to the HW structure
+ * @is_report: true for report, false for no
+ *
+ * mucse_mbx_link_eventup 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)
+{
+ struct mbx_fw_cmd_req req = {
+ .datalen = cpu_to_le16(sizeof(req.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.datalen);
+ mutex_lock(&hw->mbx.lock);
+ err = mucse_write_and_wait_ack_mbx(hw, (u32 *)&req, len);
+ mutex_unlock(&hw->mbx.lock);
+
+ return err;
+}
+
+/**
+ * mucse_update_link_status_reg - update driver speed inf to reg
+ * @hw: pointer to the HW structure
+ * @req: pointer to req data
+ *
+ * mucse_update_link_status_reg update reg according to driver info,
+ * fw will send irq if status is differ with reg
+ *
+ **/
+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);
+ u32 value;
+
+ value = mucse_hw_rd32(hw, RNPGBE_LINK_ST);
+ value &= ~M_ST_MASK;
+ value |= M_DEFAULT_ST;
+
+ if (le16_to_cpu(req->link_stat.port_status)) {
+ value |= BIT(0);
+ switch (hw->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:
+ /* invalid speed do nothing */
+ break;
+ }
+
+ value |= FIELD_PREP(BIT(4), !!hw->duplex);
+ 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
+ *
+ * rnpgbe_mbx_fw_req_handler handler fw req, such as a link event req.
+ *
+ * @return: 0 on success, negative on failure
+ **/
+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) {
+ spin_lock_irqsave(&mucse->link_lock, flags);
+
+ if (le16_to_cpu(req->link_stat.port_status))
+ hw->link = true;
+ else
+ hw->link = false;
+
+ if (magic == ST_VALID_MAGIC) {
+ hw->speed = le16_to_cpu(req->link_stat.st.speed);
+ hw->duplex = req->link_stat.st.flags & DUPLEX_BIT;
+ /* update regs to notify link info is received */
+ mucse_update_link_status_reg(hw, req);
+ } else {
+ hw->speed = 0;
+ hw->duplex = 0;
+ mucse_hw_wr32(hw, RNPGBE_LINK_ST, M_DEFAULT_ST);
+ }
+ mucse->flags |= M_FLAG_NEED_LINK_UPDATE;
+ spin_unlock_irqrestore(&mucse->link_lock, flags);
+ }
+}
+
+static void mucse_mbx_fw_reply_handler(struct mucse_hw *hw,
+ struct mbx_fw_cmd_reply *reply)
+{
+ struct mbx_req_cookie *cookie = &hw->mbx.cookie;
+
+ BUILD_BUG_ON(sizeof(struct mbx_fw_cmd_reply) != 56);
+
+ spin_lock(&cookie->lock);
+ /* avoid splice fields from two different replies */
+ if (completion_done(&cookie->comp)) {
+ spin_unlock(&cookie->lock);
+ return;
+ }
+
+ memcpy(cookie->cmd, (u8 *)reply, sizeof(*reply));
+ spin_unlock(&cookie->lock);
+ complete(&cookie->comp);
+}
+
/**
* mucse_fw_irq_handler - Try to handle a req from hw
* @hw: pointer to the HW structure
**/
void mucse_fw_irq_handler(struct mucse_hw *hw)
{
+ struct mbx_fw_cmd_reply reply = {};
+
+ /* try to check and read fw req */
+ if (mucse_check_and_read_mbx(hw, (u32 *)&reply, sizeof(reply)))
+ return;
+
+ if (le16_to_cpu(reply.flags) & FLAGS_REPLY)
+ mucse_mbx_fw_reply_handler(hw, &reply);
+ else
+ mucse_mbx_fw_req_handler(hw, (struct mbx_fw_cmd_req *)&reply);
}
diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
index aa26c729588c..044d8dfd2c2b 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_mbx_fw.h
@@ -14,6 +14,9 @@ enum MUCSE_FW_CMD {
GET_HW_INFO = 0x0601,
GET_MAC_ADDRESS = 0x0602,
RESET_HW = 0x0603,
+ LINK_CHANGE_EVT = 0x0608,
+ LINK_REPORT_EN = 0x0613,
+ SET_PHY_UP = 0x0800,
POWER_UP = 0x0803,
};
@@ -36,6 +39,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,10 +68,27 @@ struct mbx_fw_cmd_req {
__le32 port_mask;
__le32 pfvf_num;
} get_mac_addr;
+ struct {
+ __le32 port_mask;
+ __le32 status;
+ } phy_status;
+ struct {
+ __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;
struct mbx_fw_cmd_reply {
+#define FLAGS_REPLY BIT(0)
+#define FLAGS_ERR BIT(2)
__le16 flags;
__le16 opcode;
__le16 error_code;
@@ -80,10 +110,18 @@ struct mbx_fw_cmd_reply {
};
} __packed;
+enum mucse_speed {
+ mucse_speed_10,
+ mucse_speed_100,
+ mucse_speed_1000,
+};
+
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_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.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
` (3 preceding siblings ...)
2026-06-11 10:00 ` [PATCH net-next v7 4/4] net: rnpgbe: Add link status handling support Dong Yibo
@ 2026-06-12 18:44 ` Simon Horman
4 siblings, 0 replies; 6+ messages in thread
From: Simon Horman @ 2026-06-12 18:44 UTC (permalink / raw)
To: Dong Yibo
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, danishanwar,
vadim.fedorenko, u.kleine-koenig, linux-kernel, netdev, yaojun
On Thu, Jun 11, 2026 at 06:00:32PM +0800, Dong Yibo wrote:
> 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:
> - Msix/msi interrupt handling with NAPI support
> - TX path with scatter-gather DMA and completion handling
> - RX path with page pool buffer management
> - Link status monitoring and carrier management
>
> These changes enable the RNPGBE driver to support basic tx/rx
> network operations.
>
> Changelog:
> v6 -> v7:
> [patch 2/4]:
> 1. Fix 'frag_idx' error in rnpgbe_tx_map. (Sashiko-gemini)
> [patch 3/4]:
> 1. Fix skb leak in invalid size path in rnpgbe_clean_rx_irq.
> (Sashiko-gemini)
> 2. Fix invalid size range check for rxdesc. (Sashiko-gemini)
> [patch 4/4]:
> 1. Fix 'data race on the reply payload'. (Sashiko-gemini)
> 2. Fix 'asymmetric behaviour' when report up/down. (andrew)
>
> 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/
>
> Additional Notes:
Thanks for the update and the notes.
There is another round of AI-generated review of this patch-set available
on both https://sashiko.dev and https://netdev-ai.bots.linux.dev/sashiko/
I would appreciate it if you could look over that too. With a view to
addressing any issues that directly affect this patch.
...
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-12 18:44 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11 10:00 [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 1/4] net: rnpgbe: Add interrupt handling Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 2/4] net: rnpgbe: Add basic TX packet transmission support Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 3/4] net: rnpgbe: Add RX packet reception support Dong Yibo
2026-06-11 10:00 ` [PATCH net-next v7 4/4] net: rnpgbe: Add link status handling support Dong Yibo
2026-06-12 18:44 ` [PATCH net-next v7 0/4] net: rnpgbe: Add TX/RX and link status support Simon Horman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox