* [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management
@ 2026-10-15 15:24 Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned Linus Walleij
` (13 more replies)
0 siblings, 14 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The Gemini ethernet uses a custom software free queue shared by both
ports. Its page metadata, DMA mappings and fragment references are not
managed consistently as pages move through the free queue and RX queues.
Correct the DMA address bookkeeping and synchronization.
Track exact fragment mappings with an XArray and keep
in-flight page metadata in bitmap-allocated slots until all hardware
references have been claimed.
Serialize queue resize against the refill interrupt, harden fragment
validation and teardown, then scale the default queue sizes for systems
with 32 or 64 MiB of RAM.
This series wasn't so complex to begin with but Sashiko and the other
LLMs just find more and more corner cases. It's for the better I
guess.
Before these patches the network driver would crash under strong load
but it does not happen any more after. Tested on the D-Link DIR-685
playing back media and transfering new media using ksmbd while
issuing ping storms.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Changes in v5:
- Preserve complete frames terminated by an empty EOF descriptor.
- Recheck concurrently retired pages before consuming fresh metadata slots.
- Fold RX ring metadata sizing and resize rollback into the page lifetime
fix.
- Match free queue teardown unmaps to the saved full-page DMA mapping.
- Preserve the hardware-owned free queue read pointer across rebuilds and
keep replacement rings large enough for its retained position.
- Keep shared initialization parent-owned, quiesce inherited IRQ state
before child handlers register and force first queue setup from ring
state.
- Link to v4: https://patch.msgid.link/20261011-gemini-ethernet-fixes-3-v4-0-4b1631b08f5d@kernel.org
Changes in v4:
- Protect stop-path RX statistics updates against preemption on 32-bit systems.
- Validate final RX lengths against the Ethernet minimum and descriptor
transfer size so malformed chains cannot pass short skbs to GRO.
- Charge the full posted RX fragment size in skb truesize accounting.
- Reject free queue geometries that cannot post one complete page and
document the changed refill and recycling behavior.
- Reset the free queue allocation cursor with its bitmap during teardown.
- Bound recycler IRQ latency and release retained mappings for stack-owned
pages before allocating replacements.
- Rebuild oversized free queues while both ports are down so RX ring
downsizing releases the surplus pages.
- Scope RX statistics writer sections to counter updates for PREEMPT_RT.
- Restrict shared free queue handling to port 1 so IRQ draining covers
every registered handler path.
- Reset and mask free queue pointers when rebuilding the queue.
- Reorder the shared queue ownership fix before the port lifetime fix.
- Publish ports only after setup, serialize their lifetime with RTNL and
suppress independent child unbind while port 1 owns the shared IRQ.
- Move child-unbind suppression and PHY teardown ordering into the
parent-ownership patch.
- Link to v3: https://patch.msgid.link/20261002-gemini-ethernet-fixes-3-v3-0-3e1f25890ae8@kernel.org
Changes in v3:
- Add Fixes tags throughout the series, using the original driver commit
where no more specific offending commit exists, and replace Resolves with
Closes on the lifetime fix. I did not retarget the series to net
because there are dependencies in net-next and we are getting closer
to the merge window.
- Keep PHY-less port 1 bound as the shared free queue IRQ owner and clear
stale parent port pointers.
- Disable the port 1 Linux IRQ throughout free queue resizing so no newly
woken threaded handler can race queue teardown or setup.
- Keep failed queue rebuilds quiescent in the IRQ-drain patch.
- Serialize linear DMA lookups before converting them to an XArray.
- Keep all XArray mutation under the IRQ-safe free queue lock.
- Defer page unmapping until fragment lifetime is tracked, and fold DMA
synchronization into that fix.
- Check free queue space before allocating and mapping a candidate page.
- Rebuild free queue metadata when RX rings grow and roll back rejected
ring changes.
- Recycle released pages with explicit DMA synchronization using the
existing free queue allocator.
- Account inconsistent RX descriptor lengths as drops rather than wire
length errors.
- Count partial RX frames discarded during stop as dropped.
- Drop the cleanup guard conversion because it is not recommended for
network drivers.
- Link to v2: https://patch.msgid.link/20260928-gemini-ethernet-fixes-3-v2-0-758a795d7a78@kernel.org
Changes in v2:
- Replace the page_pool conversion with several smaller fixes using the
existing free queue page allocator.
- Split DMA bookkeeping, XArray lookup, bitmap allocation, cursor rotation,
synchronization, validation and teardown into individual patches.
- Drain the threaded free queue interrupt before resizing the shared queue.
- Allocate and DMA-map refill pages outside the IRQ-disabled free queue
lock.
- Reject and unwind partial initial free queue fills, reset their hardware
pointers and leave the refill interrupt masked so setup can be retried.
- Avoid full-page CPU synchronization after fragments enter the network
stack.
- Bound RX lengths to the posted fragment and account rejected lengths as
receive errors.
- Keep the fixes, and keep the add-on RAM sizing patch.
- Link to v1: https://patch.msgid.link/20260920-gemini-ethernet-fixes-3-v1-0-3a2a50a83d89@kernel.org
---
Linus Walleij (14):
net: ethernet: cortina: Keep shared free queue parent-owned
net: ethernet: cortina: Keep PHY-less port bound for shared IRQ
net: ethernet: cortina: Drain free queue IRQ before resize
net: ethernet: cortina: Normalize free queue pointers on rebuild
net: ethernet: cortina: Correct free queue DMA mappings
net: ethernet: cortina: Index free queue fragments with XArray
net: ethernet: cortina: Preserve in-flight free queue pages
net: ethernet: cortina: Rotate free queue page allocation
net: ethernet: cortina: Recycle claimed free queue pages
net: ethernet: cortina: Validate RX fragment lengths
net: ethernet: cortina: Account RX buffer size in truesize
net: ethernet: cortina: Release partial RX frames on stop
net: ethernet: cortina: Scale Gemini RX queues to system memory
net: ethernet: cortina: Scope RX statistics updates
drivers/net/ethernet/cortina/gemini.c | 830 +++++++++++++++++++++++++---------
drivers/net/ethernet/cortina/gemini.h | 2 +-
2 files changed, 616 insertions(+), 216 deletions(-)
---
base-commit: 071876fd50482a68603a9460d80dd6dd58827ee1
change-id: 20260919-gemini-ethernet-fixes-3-f0403653f23a
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 02/14] net: ethernet: cortina: Keep PHY-less port bound for shared IRQ Linus Walleij
` (12 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The software free queue is shared by both Ethernet ports, but teardown is
not ordered around that ownership. On parent unbind or module unload, the
parent remove callback runs before devres depopulates the child devices. It
therefore destroys the queue while both child netdevs are still registered
and can be doing DMA. Devres then removes each child, and both child remove
callbacks destroy the same queue again, resulting in up to three cleanup
attempts.
Removal before the first interface has been opened is broken as well. The
shared block initialization assigns a nonzero queue order, but allocation
is deferred until open. Cleanup consequently derives a nonzero page count
and dereferences the NULL queue ring.
Depopulate both port devices before cleaning up the queue, and perform that
cleanup only from the parent. Disable the free-queue interrupt after the
managed handlers have drained, since its threaded handler can re-enable it.
Guard cleanup for paths where no queue was allocated.
Port 1 owns the shared refill interrupt, so prevent the children from being
unbound independently. Unregister each netdev before detaching its PHY so
ndo_stop can still access netdev->phydev.
Reported-by: Myeonghun Pak <mhun512@gmail.com>
Closes: https://lore.kernel.org/netdev/20260917192835.58126-1-mhun512@gmail.com/
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 2fe7fd0202d2..bf7652b04f1b 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -1004,6 +1004,9 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
unsigned int pages = len >> fpp_order;
unsigned int pn;
+ if (!geth->freeq_ring)
+ return;
+
writew(readw(geth->base + GLOBAL_SWFQ_RWPTR_REG),
geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
writel(0, geth->base + GLOBAL_SW_FREEQ_BASE_SIZE_REG);
@@ -2387,11 +2390,10 @@ static irqreturn_t gemini_port_irq(int irq, void *data)
static void gemini_port_remove(struct gemini_ethernet_port *port)
{
if (port->netdev) {
- phy_disconnect(port->netdev->phydev);
unregister_netdev(port->netdev);
+ phy_disconnect(port->netdev->phydev);
}
clk_disable_unprepare(port->pclk);
- geth_cleanup_freeq(port->geth);
}
static void gemini_ethernet_init(struct gemini_ethernet *geth)
@@ -2635,6 +2637,7 @@ static struct platform_driver gemini_ethernet_port_driver = {
.driver = {
.name = "gemini-ethernet-port",
.of_match_table = gemini_ethernet_port_of_match,
+ .suppress_bind_attrs = true,
},
.probe = gemini_ethernet_port_probe,
.remove = gemini_ethernet_port_remove,
@@ -2683,6 +2686,8 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
{
struct gemini_ethernet *geth = platform_get_drvdata(pdev);
+ devm_of_platform_depopulate(&pdev->dev);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
geth_cleanup_freeq(geth);
geth->initialized = false;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 02/14] net: ethernet: cortina: Keep PHY-less port bound for shared IRQ
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 03/14] net: ethernet: cortina: Drain free queue IRQ before resize Linus Walleij
` (11 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The software free queue interrupt is routed through port 1 even on
boards which only connect a PHY to port 0. Since PHY setup was moved
into probe, the PHY-less port fails to bind and releases the threaded
handler needed by the shared queue.
Keep children without a PHY description bound as IRQ-only ports.
Publish a port to the parent only after its IRQ and optional netdev are
ready, and protect port lifetime with RTNL. Refuse shared queue setup
until both ports have completed initialization.
Keep shared hardware initialization parent-owned across individual child
removal so recreating one child cannot reset a live sibling. Mask
firmware interrupt state before child IRQ handlers are registered, and
use the allocated ring rather than an order sentinel to decide whether
the queue still needs initial setup.
Fixes: 3e813d61401a ("net: gemini: Clean up phy registration")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 110 +++++++++++++++++++++++++---------
1 file changed, 82 insertions(+), 28 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index bf7652b04f1b..79c046364292 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -25,6 +25,7 @@
#include <linux/interrupt.h>
#include <linux/reset.h>
#include <linux/clk.h>
+#include <linux/rtnetlink.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
@@ -155,6 +156,7 @@ struct gemini_ethernet_port {
struct gemini_ethernet {
struct device *dev;
void __iomem *base;
+ /* Protected by RTNL. */
struct gemini_ethernet_port *port0;
struct gemini_ethernet_port *port1;
bool initialized;
@@ -1051,10 +1053,17 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
u32 en;
int ret;
+ ASSERT_RTNL();
+
+ /* The software free queue interrupt is routed through port 1. */
+ if (!geth->initialized || !geth->port0 || !geth->port1)
+ return -ENODEV;
+
if (netdev->dev_id == 0)
- other_netdev = geth->port1->netdev;
+ other_port = geth->port1;
else
- other_netdev = geth->port0->netdev;
+ other_port = geth->port0;
+ other_netdev = other_port ? other_port->netdev : NULL;
if (other_netdev && netif_running(other_netdev))
return -EBUSY;
@@ -1065,7 +1074,6 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
new_size,
port->rxq_order);
if (other_netdev) {
- other_port = netdev_priv(other_netdev);
new_size += 1 << (other_port->rxq_order + 1);
netdev_dbg(other_netdev, "port %d size: %d order %d\n",
other_netdev->dev_id,
@@ -1076,7 +1084,7 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
new_order = min(15, ilog2(new_size - 1) + 1);
dev_dbg(geth->dev, "set shared queue to size %d order %d\n",
new_size, new_order);
- if (geth->freeq_order == new_order)
+ if (geth->freeq_ring && geth->freeq_order == new_order)
return 0;
spin_lock_irqsave(&geth->irq_lock, flags);
@@ -2387,8 +2395,35 @@ static irqreturn_t gemini_port_irq(int irq, void *data)
return ret;
}
+static void gemini_port_clear(struct gemini_ethernet_port *port)
+{
+ struct gemini_ethernet *geth = port->geth;
+
+ ASSERT_RTNL();
+
+ if (!port->id && geth->port0 == port)
+ geth->port0 = NULL;
+ else if (port->id && geth->port1 == port)
+ geth->port1 = NULL;
+ else
+ return;
+}
+
+static void gemini_ethernet_disable_irqs(struct gemini_ethernet *geth)
+{
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_0_REG);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_1_REG);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_2_REG);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_3_REG);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
+}
+
static void gemini_port_remove(struct gemini_ethernet_port *port)
{
+ rtnl_lock();
+ gemini_port_clear(port);
+ rtnl_unlock();
+
if (port->netdev) {
unregister_netdev(port->netdev);
phy_disconnect(port->netdev->phydev);
@@ -2398,19 +2433,15 @@ static void gemini_port_remove(struct gemini_ethernet_port *port)
static void gemini_ethernet_init(struct gemini_ethernet *geth)
{
+ ASSERT_RTNL();
+
/* Only do this once both ports are online */
if (geth->initialized)
return;
- if (geth->port0 && geth->port1)
- geth->initialized = true;
- else
+ if (!geth->port0 || !geth->port1)
return;
- writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_0_REG);
- writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_1_REG);
- writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_2_REG);
- writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_3_REG);
- writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
+ gemini_ethernet_disable_irqs(geth);
/* Interrupt config:
*
@@ -2442,10 +2473,20 @@ static void gemini_ethernet_init(struct gemini_ethernet *geth)
writel(0, geth->base + GLOBAL_HWFQ_RWPTR_REG);
geth->freeq_frag_order = DEFAULT_RX_BUF_ORDER;
- /* This makes the queue resize on probe() so that we
- * set up and enable the queue IRQ. FIXME: fragile.
- */
- geth->freeq_order = 1;
+ geth->initialized = true;
+}
+
+static void gemini_port_publish(struct gemini_ethernet_port *port)
+{
+ struct gemini_ethernet *geth = port->geth;
+
+ ASSERT_RTNL();
+
+ if (!port->id)
+ geth->port0 = port;
+ else
+ geth->port1 = port;
+ gemini_ethernet_init(geth);
}
static void gemini_port_save_mac_addr(struct gemini_ethernet_port *port)
@@ -2541,15 +2582,6 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev)
reset_control_reset(port->reset);
usleep_range(100, 500);
- /* Assign pointer in the main state container */
- if (!id)
- geth->port0 = port;
- else
- geth->port1 = port;
-
- /* This will just be done once both ports are up and reset */
- gemini_ethernet_init(geth);
-
platform_set_drvdata(pdev, port);
/* Set up and register the netdev */
@@ -2600,6 +2632,16 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev)
if (ret)
goto unprepare;
+ if (!of_property_present(np, "phy-handle") &&
+ !of_phy_is_fixed_link(np)) {
+ dev_info(dev, "no PHY, keeping port for shared IRQ\n");
+ port->netdev = NULL;
+ rtnl_lock();
+ gemini_port_publish(port);
+ rtnl_unlock();
+ return 0;
+ }
+
ret = gmac_setup_phy(netdev);
if (ret) {
netdev_err(netdev,
@@ -2607,9 +2649,15 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev)
goto unprepare;
}
- ret = register_netdev(netdev);
- if (ret)
+ rtnl_lock();
+ ret = register_netdevice(netdev);
+ if (!ret)
+ gemini_port_publish(port);
+ rtnl_unlock();
+ if (ret) {
+ phy_disconnect(netdev->phydev);
goto unprepare;
+ }
return 0;
@@ -2675,6 +2723,9 @@ static int gemini_ethernet_probe(struct platform_device *pdev)
spin_lock_init(&geth->irq_lock);
spin_lock_init(&geth->freeq_lock);
+ /* Quiesce firmware state before child IRQ handlers are registered. */
+ gemini_ethernet_disable_irqs(geth);
+
/* The children will use this */
platform_set_drvdata(pdev, geth);
@@ -2686,10 +2737,13 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
{
struct gemini_ethernet *geth = platform_get_drvdata(pdev);
+ rtnl_lock();
+ geth->initialized = false;
+ rtnl_unlock();
+
devm_of_platform_depopulate(&pdev->dev);
writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
geth_cleanup_freeq(geth);
- geth->initialized = false;
}
static const struct of_device_id gemini_ethernet_of_match[] = {
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 03/14] net: ethernet: cortina: Drain free queue IRQ before resize
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 02/14] net: ethernet: cortina: Keep PHY-less port bound for shared IRQ Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild Linus Walleij
` (10 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The software free queue interrupt remains registered while both netdevs
are down. Merely masking the device source does not stop a threaded
handler which was already woken. That handler can refill the queue while
resize frees its ring and page metadata, or re-enable the source after
teardown.
The free queue interrupt is routed through port 1, but both port IRQs
register the global handler. Ignore the source on port 0, then disable the
port 1 Linux IRQ for the entire resize. disable_irq() drains hard and
threaded handlers and prevents a new thread from being woken.
After the drain, mask the device source and rebuild the queue. If setup
succeeds, re-enable the source before the Linux IRQ. On failure, keep the
source masked, clear freed queue state and restore the Linux IRQ so a later
open retries setup safely.
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 53 +++++++++++++++++++++--------------
1 file changed, 32 insertions(+), 21 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 79c046364292..003108270d06 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -986,6 +986,8 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
}
kfree(geth->freeq_pages);
+ geth->freeq_pages = NULL;
+ geth->num_freeq_pages = 0;
err_freeq:
dma_free_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
@@ -1026,20 +1028,37 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
}
kfree(geth->freeq_pages);
+ geth->freeq_pages = NULL;
+ geth->num_freeq_pages = 0;
dma_free_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
geth->freeq_ring, geth->freeq_dma_base);
+ geth->freeq_ring = NULL;
+}
+
+static void geth_set_freeq_irq(struct gemini_ethernet *geth, bool enable)
+{
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&geth->irq_lock, flags);
+ val = readl(geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
+ if (enable)
+ val |= SWFQ_EMPTY_INT_BIT;
+ else
+ val &= ~SWFQ_EMPTY_INT_BIT;
+ writel(val, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
+ spin_unlock_irqrestore(&geth->irq_lock, flags);
}
/**
* geth_resize_freeq() - resize the software queue depth
* @port: the port requesting the change
*
- * This gets called at least once during probe() so the device queue gets
- * "resized" from the hardware defaults. Since both ports/net devices share
- * the same hardware queue, some synchronization between the ports is
- * needed.
+ * This is called when a port is opened or its RX ring size changes. Since
+ * both ports/net devices share the same hardware queue, some synchronization
+ * between the ports is needed.
*/
static int geth_resize_freeq(struct gemini_ethernet_port *port)
{
@@ -1049,8 +1068,6 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
struct net_device *other_netdev;
unsigned int new_size = 0;
unsigned int new_order;
- unsigned long flags;
- u32 en;
int ret;
ASSERT_RTNL();
@@ -1087,13 +1104,8 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
if (geth->freeq_ring && geth->freeq_order == new_order)
return 0;
- spin_lock_irqsave(&geth->irq_lock, flags);
-
- /* Disable the software queue IRQs */
- en = readl(geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
- en &= ~SWFQ_EMPTY_INT_BIT;
- writel(en, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
- spin_unlock_irqrestore(&geth->irq_lock, flags);
+ disable_irq(geth->port1->irq);
+ geth_set_freeq_irq(geth, false);
/* Drop the old queue */
if (geth->freeq_ring)
@@ -1103,14 +1115,10 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
geth->freeq_order = new_order;
ret = geth_setup_freeq(geth);
- /* Restart the interrupts - NOTE if this is the first resize
- * after probe(), this is where the interrupts get turned on
- * in the first place.
- */
- spin_lock_irqsave(&geth->irq_lock, flags);
- en |= SWFQ_EMPTY_INT_BIT;
- writel(en, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
- spin_unlock_irqrestore(&geth->irq_lock, flags);
+ /* Enable the queue interrupt only after successful setup. */
+ if (!ret)
+ geth_set_freeq_irq(geth, true);
+ enable_irq(geth->port1->irq);
return ret;
}
@@ -2373,6 +2381,9 @@ static irqreturn_t gemini_port_irq(int irq, void *data)
irqreturn_t ret = IRQ_NONE;
u32 val, en;
+ if (!port->id)
+ return IRQ_NONE;
+
geth = port->geth;
spin_lock(&geth->irq_lock);
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (2 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 03/14] net: ethernet: cortina: Drain free queue IRQ before resize Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings Linus Walleij
` (9 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The free queue read pointer is owned by hardware and can retain a
position from the previous ring when the queue is rebuilt at a smaller
order. Using an unmasked pointer to index the replacement page table can
then access it out of bounds. A replacement ring which does not contain
the raw retained pointer can also let hardware address past the new
allocation.
Mask software page indices to the active queue order. Keep the
replacement ring large enough for the retained hardware pointer and
initialize the software-owned write pointer from it before filling the
ring. This represents an empty queue without trying to write the
read-only pointer.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 003108270d06..7ebd62f8a3a4 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -887,6 +887,7 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
pn = (refill ? rw.bits.wptr : rw.bits.rptr) >> fpp_order;
+ pn &= m_pn;
epn = (rw.bits.rptr >> fpp_order) - 1;
epn &= m_pn;
@@ -936,6 +937,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
union dma_skb_size skbsz;
unsigned int filled;
unsigned int pn;
+ u16 rptr;
geth->freeq_ring = dma_alloc_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
@@ -958,6 +960,10 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
if (!geth_freeq_alloc_map_page(geth, pn))
goto err_freeq_alloc;
+ /* Match the software pointer to the retained hardware pointer. */
+ rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ SET_WPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG, rptr & (len - 1));
+
filled = geth_fill_freeq(geth, false);
if (!filled)
goto err_freeq_alloc;
@@ -1068,6 +1074,7 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
struct net_device *other_netdev;
unsigned int new_size = 0;
unsigned int new_order;
+ u16 rptr;
int ret;
ASSERT_RTNL();
@@ -1099,6 +1106,10 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
}
new_order = min(15, ilog2(new_size - 1) + 1);
+ rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ if (rptr >= BIT(15))
+ return -EIO;
+ new_order = max_t(unsigned int, new_order, fls(rptr));
dev_dbg(geth->dev, "set shared queue to size %d order %d\n",
new_size, new_order);
if (geth->freeq_ring && geth->freeq_order == new_order)
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (3 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 06/14] net: ethernet: cortina: Index free queue fragments with XArray Linus Walleij
` (8 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The free queue maps complete pages and splits each mapping between its
fragment descriptors. The mapping variable is advanced while filling the
descriptors and that advanced address is then saved as the page mapping.
Replacement also reads the old address after overwriting the descriptor
with the new mapping, so it unmaps the new buffer while leaving the old
mapping live.
Keep the page DMA base separate from the fragment iterator and remove the
invalid replacement unmap. The replaced mapping cannot be released until
its in-flight fragments have been claimed, which is handled with their
lifetime later in the series. Use the saved DMA base and PAGE_SIZE when
unwinding setup and tearing down the current queue.
Reject mappings that cannot fit in the 32-bit hardware descriptors and
derive fragment offsets from the saved DMA base instead of assuming
page-aligned DMA addresses. Snapshot the page, DMA base and offset under
the free queue lock so refill cannot replace them between lookup and use.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 112 ++++++++++++++++++----------------
1 file changed, 58 insertions(+), 54 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 7ebd62f8a3a4..006362a03395 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -726,32 +726,43 @@ static int gmac_setup_rxq(struct net_device *netdev)
return 0;
}
-static struct gmac_queue_page *
-gmac_get_queue_page(struct gemini_ethernet *geth,
- struct gemini_ethernet_port *port,
- dma_addr_t addr)
+static struct page *geth_freeq_lookup(struct gemini_ethernet *geth,
+ dma_addr_t mapping,
+ unsigned int *page_offs)
{
+ unsigned int frag_len = 1 << geth->freeq_frag_order;
struct gmac_queue_page *gpage;
- dma_addr_t mapping;
+ unsigned long flags;
+ dma_addr_t page_mapping;
+ struct page *page = NULL;
int i;
- /* Only look for even pages */
- mapping = addr & PAGE_MASK;
-
+ spin_lock_irqsave(&geth->freeq_lock, flags);
if (!geth->freeq_pages) {
dev_err_ratelimited(geth->dev,
"try to get page with no page list\n");
- return NULL;
+ goto unlock;
}
/* Look up a ring buffer page from virtual mapping */
for (i = 0; i < geth->num_freeq_pages; i++) {
gpage = &geth->freeq_pages[i];
- if (gpage->mapping == mapping)
- return gpage;
+ if (!gpage->page || mapping < gpage->mapping)
+ continue;
+
+ page_mapping = gpage->mapping;
+ if (mapping - page_mapping > PAGE_SIZE - frag_len ||
+ ((mapping - page_mapping) & (frag_len - 1)))
+ continue;
+
+ page = gpage->page;
+ *page_offs = mapping - page_mapping;
+ break;
}
- return NULL;
+unlock:
+ spin_unlock_irqrestore(&geth->freeq_lock, flags);
+ return page;
}
static void gmac_cleanup_rxq(struct net_device *netdev)
@@ -759,11 +770,12 @@ static void gmac_cleanup_rxq(struct net_device *netdev)
struct gemini_ethernet_port *port = netdev_priv(netdev);
struct gemini_ethernet *geth = port->geth;
struct gmac_rxdesc *rxd = port->rxq_ring;
- static struct gmac_queue_page *gpage;
struct nontoe_qhdr __iomem *qhdr;
void __iomem *dma_reg;
void __iomem *ptr_reg;
+ unsigned int page_offs;
dma_addr_t mapping;
+ struct page *page;
union dma_rwptr rw;
unsigned int r, w;
@@ -790,14 +802,13 @@ static void gmac_cleanup_rxq(struct net_device *netdev)
if (!mapping)
continue;
- /* Freeq pointers are one page off */
- gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE);
- if (!gpage) {
+ page = geth_freeq_lookup(geth, mapping, &page_offs);
+ if (!page) {
dev_err(geth->dev, "could not find page\n");
continue;
}
/* Release the RX queue reference to the page */
- put_page(gpage->page);
+ put_page(page);
}
dma_free_coherent(geth->dev, sizeof(*port->rxq_ring) << port->rxq_order,
@@ -811,6 +822,7 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
struct gmac_queue_page *gpage;
unsigned int fpp_order;
unsigned int frag_len;
+ dma_addr_t page_mapping;
dma_addr_t mapping;
struct page *page;
int i;
@@ -820,9 +832,17 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
if (!page)
return NULL;
- mapping = dma_map_single(geth->dev, page_address(page),
- PAGE_SIZE, DMA_FROM_DEVICE);
- if (dma_mapping_error(geth->dev, mapping)) {
+ page_mapping = dma_map_single(geth->dev, page_address(page),
+ PAGE_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(geth->dev, page_mapping)) {
+ put_page(page);
+ return NULL;
+ }
+ if (page_mapping > U32_MAX - (PAGE_SIZE - 1)) {
+ dev_err_ratelimited(geth->dev,
+ "freeq DMA mapping exceeds 32 bits\n");
+ dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
put_page(page);
return NULL;
}
@@ -835,31 +855,25 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
*/
frag_len = 1 << geth->freeq_frag_order; /* Usually 2048 */
fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
+ gpage = &geth->freeq_pages[pn];
+ if (gpage->page)
+ put_page(gpage->page);
+
+ /* Then put our new mapping into the page table */
+ gpage->mapping = page_mapping;
+ gpage->page = page;
+
freeq_entry = geth->freeq_ring + (pn << fpp_order);
dev_dbg(geth->dev, "allocate page %d fragment length %d fragments per page %d, freeq entry %p\n",
- pn, frag_len, (1 << fpp_order), freeq_entry);
+ pn, frag_len, (1 << fpp_order), freeq_entry);
+ mapping = page_mapping;
for (i = (1 << fpp_order); i > 0; i--) {
freeq_entry->word2.buf_adr = mapping;
freeq_entry++;
mapping += frag_len;
}
-
- /* If the freeq entry already has a page mapped, then unmap it. */
- gpage = &geth->freeq_pages[pn];
- if (gpage->page) {
- mapping = geth->freeq_ring[pn << fpp_order].word2.buf_adr;
- dma_unmap_single(geth->dev, mapping, frag_len, DMA_FROM_DEVICE);
- /* This should be the last reference to the page so it gets
- * released
- */
- put_page(gpage->page);
- }
-
- /* Then put our new mapping into the page table */
- dev_dbg(geth->dev, "page %d, DMA addr: %08x, page %p\n",
- pn, (unsigned int)mapping, page);
- gpage->mapping = mapping;
- gpage->page = page;
+ dev_dbg(geth->dev, "page %d, DMA addr: %pad, page %p\n",
+ pn, &page_mapping, page);
return page;
}
@@ -930,7 +944,6 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
static int geth_setup_freeq(struct gemini_ethernet *geth)
{
unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
- unsigned int frag_len = 1 << geth->freeq_frag_order;
unsigned int len = 1 << geth->freeq_order;
unsigned int pages = len >> fpp_order;
union queue_threshold qt;
@@ -982,12 +995,11 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
err_freeq_alloc:
while (pn > 0) {
struct gmac_queue_page *gpage;
- dma_addr_t mapping;
--pn;
- mapping = geth->freeq_ring[pn << fpp_order].word2.buf_adr;
- dma_unmap_single(geth->dev, mapping, frag_len, DMA_FROM_DEVICE);
gpage = &geth->freeq_pages[pn];
+ dma_unmap_single(geth->dev, gpage->mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
put_page(gpage->page);
}
@@ -1009,7 +1021,6 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
static void geth_cleanup_freeq(struct gemini_ethernet *geth)
{
unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
- unsigned int frag_len = 1 << geth->freeq_frag_order;
unsigned int len = 1 << geth->freeq_order;
unsigned int pages = len >> fpp_order;
unsigned int pn;
@@ -1023,12 +1034,10 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
for (pn = 0; pn < pages; pn++) {
struct gmac_queue_page *gpage;
- dma_addr_t mapping;
-
- mapping = geth->freeq_ring[pn << fpp_order].word2.buf_adr;
- dma_unmap_single(geth->dev, mapping, frag_len, DMA_FROM_DEVICE);
gpage = &geth->freeq_pages[pn];
+ dma_unmap_single(geth->dev, gpage->mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
while (page_ref_count(gpage->page) > 0)
put_page(gpage->page);
}
@@ -1486,7 +1495,6 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
unsigned int consumed = 0;
unsigned int frame_len, frag_len;
struct gmac_rxdesc *rx = NULL;
- struct gmac_queue_page *gpage;
unsigned int received = 0;
bool dropping = port->rx_dropping;
union gmac_rxdesc_0 word0;
@@ -1523,8 +1531,6 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
frag_len = word0.bits.buffer_size;
frame_len = word1.bits.byte_count;
- page_offs = mapping & ~PAGE_MASK;
-
if (word3.bits32 & SOF_BIT) {
if (skb) {
napi_free_frags(&port->napi);
@@ -1543,14 +1549,12 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
goto err_drop;
}
- /* Freeq pointers are one page off */
- gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE);
- if (!gpage) {
+ page = geth_freeq_lookup(geth, mapping, &page_offs);
+ if (!page) {
dev_err_ratelimited(geth->dev,
"could not find mapping\n");
goto err_drop;
}
- page = gpage->page;
if (word3.bits32 & SOF_BIT) {
skb = gmac_skb_if_good_frame(port, word0, frame_len);
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 06/14] net: ethernet: cortina: Index free queue fragments with XArray
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (4 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages Linus Walleij
` (7 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
RX descriptors return the DMA address of the free queue fragment used by
the hardware. The driver currently recovers its page by scanning every
free queue page and comparing address ranges.
This isn't very efficient...
Instead index each fragment in an XArray using its DMA address in
fragment units.
Populate and erase the fragment index under the free queue lock. The
lookup remains serialized with refill and validates the exact fragment
address before returning the page and offset snapshot.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 105 ++++++++++++++++++++++++----------
1 file changed, 76 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 006362a03395..c1c42e1f93f4 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -38,6 +38,7 @@
#include <linux/ethtool.h>
#include <linux/tcp.h>
#include <linux/u64_stats_sync.h>
+#include <linux/xarray.h>
#include <linux/in.h>
#include <linux/ip.h>
@@ -167,6 +168,7 @@ struct gemini_ethernet {
struct gmac_rxdesc *freeq_ring;
dma_addr_t freeq_dma_base;
struct gmac_queue_page *freeq_pages;
+ struct xarray freeq_mappings;
unsigned int num_freeq_pages;
spinlock_t freeq_lock; /* Locks queue from reentrance */
};
@@ -726,43 +728,47 @@ static int gmac_setup_rxq(struct net_device *netdev)
return 0;
}
+static unsigned long
+geth_freeq_mapping_index(const struct gemini_ethernet *geth,
+ dma_addr_t mapping)
+{
+ return (unsigned long)(mapping >> geth->freeq_frag_order);
+}
+
static struct page *geth_freeq_lookup(struct gemini_ethernet *geth,
dma_addr_t mapping,
unsigned int *page_offs)
{
unsigned int frag_len = 1 << geth->freeq_frag_order;
struct gmac_queue_page *gpage;
+ unsigned long index;
unsigned long flags;
dma_addr_t page_mapping;
- struct page *page = NULL;
- int i;
-
- spin_lock_irqsave(&geth->freeq_lock, flags);
- if (!geth->freeq_pages) {
- dev_err_ratelimited(geth->dev,
- "try to get page with no page list\n");
- goto unlock;
- }
-
- /* Look up a ring buffer page from virtual mapping */
- for (i = 0; i < geth->num_freeq_pages; i++) {
- gpage = &geth->freeq_pages[i];
- if (!gpage->page || mapping < gpage->mapping)
- continue;
-
- page_mapping = gpage->mapping;
- if (mapping - page_mapping > PAGE_SIZE - frag_len ||
- ((mapping - page_mapping) & (frag_len - 1)))
- continue;
+ struct page *page;
+ bool valid;
- page = gpage->page;
- *page_offs = mapping - page_mapping;
- break;
- }
+ index = geth_freeq_mapping_index(geth, mapping);
-unlock:
+ spin_lock_irqsave(&geth->freeq_lock, flags);
+ gpage = xa_load(&geth->freeq_mappings, index);
+ if (!gpage || !gpage->page)
+ goto err_unlock;
+
+ page = gpage->page;
+ page_mapping = gpage->mapping;
+ valid = mapping >= page_mapping &&
+ mapping - page_mapping <= PAGE_SIZE - frag_len &&
+ !((mapping - page_mapping) & (frag_len - 1));
+ if (!valid)
+ goto err_unlock;
+
+ *page_offs = mapping - page_mapping;
spin_unlock_irqrestore(&geth->freeq_lock, flags);
return page;
+
+err_unlock:
+ spin_unlock_irqrestore(&geth->freeq_lock, flags);
+ return NULL;
}
static void gmac_cleanup_rxq(struct net_device *netdev)
@@ -821,12 +827,16 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
struct gmac_rxdesc *freeq_entry;
struct gmac_queue_page *gpage;
unsigned int fpp_order;
+ unsigned int fragments;
unsigned int frag_len;
dma_addr_t page_mapping;
dma_addr_t mapping;
struct page *page;
+ int ret;
int i;
+ lockdep_assert_held(&geth->freeq_lock);
+
/* First allocate and DMA map a single page */
page = alloc_page(GFP_ATOMIC);
if (!page)
@@ -855,9 +865,26 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
*/
frag_len = 1 << geth->freeq_frag_order; /* Usually 2048 */
fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
+ fragments = 1 << fpp_order;
+
gpage = &geth->freeq_pages[pn];
- if (gpage->page)
+ for (i = 0; i < fragments; i++) {
+ mapping = page_mapping + i * frag_len;
+ ret = xa_insert(&geth->freeq_mappings,
+ geth_freeq_mapping_index(geth, mapping),
+ gpage, GFP_ATOMIC);
+ if (ret)
+ goto err_mappings;
+ }
+
+ if (gpage->page) {
+ for (i = 0; i < fragments; i++) {
+ mapping = gpage->mapping + i * frag_len;
+ xa_erase(&geth->freeq_mappings,
+ geth_freeq_mapping_index(geth, mapping));
+ }
put_page(gpage->page);
+ }
/* Then put our new mapping into the page table */
gpage->mapping = page_mapping;
@@ -876,6 +903,17 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
pn, &page_mapping, page);
return page;
+
+err_mappings:
+ while (i--) {
+ mapping = page_mapping + i * frag_len;
+ xa_erase(&geth->freeq_mappings,
+ geth_freeq_mapping_index(geth, mapping));
+ }
+ dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ put_page(page);
+ return NULL;
}
/**
@@ -948,6 +986,8 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
unsigned int pages = len >> fpp_order;
union queue_threshold qt;
union dma_skb_size skbsz;
+ unsigned long flags;
+ struct page *page;
unsigned int filled;
unsigned int pn;
u16 rptr;
@@ -962,16 +1002,20 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
goto err_freeq;
}
- /* Allocate a mapping to page look-up index */
+ /* Allocate page metadata referenced by the mapping index */
geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, pages);
if (!geth->freeq_pages)
goto err_freeq;
geth->num_freeq_pages = pages;
dev_info(geth->dev, "allocate %d pages for queue\n", pages);
- for (pn = 0; pn < pages; pn++)
- if (!geth_freeq_alloc_map_page(geth, pn))
+ for (pn = 0; pn < pages; pn++) {
+ spin_lock_irqsave(&geth->freeq_lock, flags);
+ page = geth_freeq_alloc_map_page(geth, pn);
+ spin_unlock_irqrestore(&geth->freeq_lock, flags);
+ if (!page)
goto err_freeq_alloc;
+ }
/* Match the software pointer to the retained hardware pointer. */
rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
@@ -1002,6 +1046,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
DMA_FROM_DEVICE);
put_page(gpage->page);
}
+ xa_destroy(&geth->freeq_mappings);
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
@@ -1041,6 +1086,7 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
while (page_ref_count(gpage->page) > 0)
put_page(gpage->page);
}
+ xa_destroy(&geth->freeq_mappings);
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
@@ -2748,6 +2794,7 @@ static int gemini_ethernet_probe(struct platform_device *pdev)
spin_lock_init(&geth->irq_lock);
spin_lock_init(&geth->freeq_lock);
+ xa_init(&geth->freeq_mappings);
/* Quiesce firmware state before child IRQ handlers are registered. */
gemini_ethernet_disable_irqs(geth);
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (5 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 06/14] net: ethernet: cortina: Index free queue fragments with XArray Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 08/14] net: ethernet: cortina: Rotate free queue page allocation Linus Walleij
` (6 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The software free queue and the two per-port RX queues have independent
lifetimes. A free queue ring position becomes reusable once hardware has
taken its buffer address, but the resulting RX descriptor can remain
unprocessed for longer.
With the default 2K fragments, the sequence is:
1. Free queue entries 2n and 2n + 1 contain the first and second halves
of page A.
2. Hardware consumes those entries and copies their DMA addresses into RX
descriptors, potentially in the RX queues of different ports.
3. The free queue read pointer advances, making entries 2n and 2n + 1
available for refill.
4. NAPI may not yet have processed the RX descriptors referring to page A,
so their DMA-to-page association must remain valid.
5. Refilling the same free queue positions with page B must therefore not
replace the metadata describing page A.
The driver currently indexes page metadata by free queue position. Step 5
therefore overwrites page A's association with page B. When NAPI later
processes an old descriptor containing page A's DMA address, the driver can
no longer find the mapping. This may explain long-standing reports of the
driver failing to find RX mappings.
This is not just caused by splitting a 4K page into two 2K
fragments: the metadata would also have to outlive its free queue
position with one full-page buffer per entry, but the 2K split makes
the lifetime more involved because the two halves of one page are
independently consumed and may be waiting in different port RX queues.
The page cannot be unmapped until both fragment references have been
claimed.
Allocate page metadata slots independently of free queue positions and
track occupied slots with a bitmap. Keep each exact fragment DMA address in
the XArray until software claims its descriptor.
The pages use streaming DMA_FROM_DEVICE mappings. Synchronize each
fragment when claimed and unmap the page with DMA_ATTR_SKIP_CPU_SYNC after
both are claimed. This has no known visible effect on Gemini's FA526,
where CPU sync is currently a no-op, but follows the DMA API and avoids
resynchronizing a sibling on other implementations. During teardown,
synchronize only fragments still owned by hardware.
Give each posted fragment its own page reference. Queue and error cleanup
can then release precisely the outstanding hardware references without
dropping references held by delivered skbs.
Allocate and DMA-map each candidate page before taking the free queue
lock, then re-read the hardware pointers and publish one complete page at
a time. The old refill path reused pages by queue position and only
allocated replacements once the queued fill fell to 64 descriptors or
fewer. This change instead allocates replacements until the ring is full.
A later patch restores recycling independently of queue position.
Size the metadata pool for the shared free queue and both registered RX
rings. Include that capacity in resize decisions, reuse an oversized queue
only while the peer is running, and roll back rejected RX ring changes.
This prevents same-order ring growth from exhausting page slots and lets
downsizing release surplus pages when both ports are idle.
Reject queue geometries that cannot post one complete page before tearing
down the existing queue. Require the initial fill to populate every usable
queue entry. If mapping or metadata allocation stops the fill early,
rewind the hardware write pointer, release the partial population and fail
setup.
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 357 +++++++++++++++++++++-------------
1 file changed, 226 insertions(+), 131 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index c1c42e1f93f4..faab1bb865ab 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -14,6 +14,7 @@
* Gary Chen & Ch Hsu Storlink Semiconductor
*/
#include <linux/kernel.h>
+#include <linux/bitmap.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/net.h>
@@ -89,11 +90,13 @@ MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
/**
* struct gmac_queue_page - page buffer per-page info
* @page: the page struct
- * @mapping: the dma address handle
+ * @mapping: DMA address of the first fragment
+ * @fragments: number of fragments not yet claimed from the hardware
*/
struct gmac_queue_page {
struct page *page;
dma_addr_t mapping;
+ unsigned int fragments;
};
struct gmac_txq {
@@ -170,6 +173,7 @@ struct gemini_ethernet {
struct gmac_queue_page *freeq_pages;
struct xarray freeq_mappings;
unsigned int num_freeq_pages;
+ unsigned long *freeq_page_bitmap;
spinlock_t freeq_lock; /* Locks queue from reentrance */
};
@@ -735,15 +739,32 @@ geth_freeq_mapping_index(const struct gemini_ethernet *geth,
return (unsigned long)(mapping >> geth->freeq_frag_order);
}
-static struct page *geth_freeq_lookup(struct gemini_ethernet *geth,
- dma_addr_t mapping,
- unsigned int *page_offs)
+static int geth_freeq_alloc_slot(struct gemini_ethernet *geth)
+{
+ unsigned int slot;
+
+ lockdep_assert_held(&geth->freeq_lock);
+
+ slot = find_first_zero_bit(geth->freeq_page_bitmap,
+ geth->num_freeq_pages);
+ if (slot == geth->num_freeq_pages)
+ return -ENOSPC;
+
+ __set_bit(slot, geth->freeq_page_bitmap);
+
+ return slot;
+}
+
+static struct page *geth_freeq_claim(struct gemini_ethernet *geth,
+ dma_addr_t mapping,
+ unsigned int *page_offs)
{
unsigned int frag_len = 1 << geth->freeq_frag_order;
struct gmac_queue_page *gpage;
unsigned long index;
unsigned long flags;
dma_addr_t page_mapping;
+ unsigned int slot;
struct page *page;
bool valid;
@@ -751,7 +772,7 @@ static struct page *geth_freeq_lookup(struct gemini_ethernet *geth,
spin_lock_irqsave(&geth->freeq_lock, flags);
gpage = xa_load(&geth->freeq_mappings, index);
- if (!gpage || !gpage->page)
+ if (!gpage || !gpage->page || !gpage->fragments)
goto err_unlock;
page = gpage->page;
@@ -762,6 +783,21 @@ static struct page *geth_freeq_lookup(struct gemini_ethernet *geth,
if (!valid)
goto err_unlock;
+ dma_sync_single_range_for_cpu(geth->dev, page_mapping,
+ mapping - page_mapping, frag_len,
+ DMA_FROM_DEVICE);
+ xa_erase(&geth->freeq_mappings, index);
+ if (!--gpage->fragments) {
+ slot = gpage - geth->freeq_pages;
+ dma_unmap_single_attrs(geth->dev, page_mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ gpage->page = NULL;
+ gpage->mapping = 0;
+ __clear_bit(slot, geth->freeq_page_bitmap);
+ put_page(page);
+ }
+
*page_offs = mapping - page_mapping;
spin_unlock_irqrestore(&geth->freeq_lock, flags);
return page;
@@ -808,7 +844,7 @@ static void gmac_cleanup_rxq(struct net_device *netdev)
if (!mapping)
continue;
- page = geth_freeq_lookup(geth, mapping, &page_offs);
+ page = geth_freeq_claim(geth, mapping, &page_offs);
if (!page) {
dev_err(geth->dev, "could not find page\n");
continue;
@@ -821,32 +857,22 @@ static void gmac_cleanup_rxq(struct net_device *netdev)
port->rxq_ring, port->rxq_dma_base);
}
-static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
- int pn)
+static int geth_freeq_map_page(struct gemini_ethernet *geth,
+ struct page **pagep,
+ dma_addr_t *page_mappingp)
{
- struct gmac_rxdesc *freeq_entry;
- struct gmac_queue_page *gpage;
- unsigned int fpp_order;
- unsigned int fragments;
- unsigned int frag_len;
dma_addr_t page_mapping;
- dma_addr_t mapping;
struct page *page;
- int ret;
- int i;
- lockdep_assert_held(&geth->freeq_lock);
-
- /* First allocate and DMA map a single page */
page = alloc_page(GFP_ATOMIC);
if (!page)
- return NULL;
+ return -ENOMEM;
page_mapping = dma_map_single(geth->dev, page_address(page),
PAGE_SIZE, DMA_FROM_DEVICE);
if (dma_mapping_error(geth->dev, page_mapping)) {
put_page(page);
- return NULL;
+ return -ENOMEM;
}
if (page_mapping > U32_MAX - (PAGE_SIZE - 1)) {
dev_err_ratelimited(geth->dev,
@@ -854,9 +880,34 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
DMA_FROM_DEVICE);
put_page(page);
- return NULL;
+ return -EOVERFLOW;
}
+ *pagep = page;
+ *page_mappingp = page_mapping;
+
+ return 0;
+}
+
+static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
+ struct page *page, dma_addr_t page_mapping)
+{
+ struct gmac_rxdesc *freeq_entry;
+ struct gmac_queue_page *gpage;
+ unsigned int fpp_order;
+ unsigned int fragments;
+ unsigned int frag_len;
+ dma_addr_t mapping;
+ int ret;
+ int slot;
+ int i;
+
+ lockdep_assert_held(&geth->freeq_lock);
+
+ slot = geth_freeq_alloc_slot(geth);
+ if (slot < 0)
+ return slot;
+
/* The assign the page mapping (physical address) to the buffer address
* in the hardware queue. PAGE_SHIFT on ARM is 12 (1 page is 4096 bytes,
* 4k), and the default RX frag order is 11 (fragments are up 20 2048
@@ -867,7 +918,9 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
fragments = 1 << fpp_order;
- gpage = &geth->freeq_pages[pn];
+ gpage = &geth->freeq_pages[slot];
+ gpage->page = page;
+ gpage->mapping = page_mapping;
for (i = 0; i < fragments; i++) {
mapping = page_mapping + i * frag_len;
ret = xa_insert(&geth->freeq_mappings,
@@ -876,19 +929,8 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
if (ret)
goto err_mappings;
}
-
- if (gpage->page) {
- for (i = 0; i < fragments; i++) {
- mapping = gpage->mapping + i * frag_len;
- xa_erase(&geth->freeq_mappings,
- geth_freeq_mapping_index(geth, mapping));
- }
- put_page(gpage->page);
- }
-
- /* Then put our new mapping into the page table */
- gpage->mapping = page_mapping;
- gpage->page = page;
+ gpage->fragments = fragments;
+ page_ref_add(page, fragments);
freeq_entry = geth->freeq_ring + (pn << fpp_order);
dev_dbg(geth->dev, "allocate page %d fragment length %d fragments per page %d, freeq entry %p\n",
@@ -902,7 +944,7 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
dev_dbg(geth->dev, "page %d, DMA addr: %pad, page %p\n",
pn, &page_mapping, page);
- return page;
+ return 0;
err_mappings:
while (i--) {
@@ -910,20 +952,20 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
xa_erase(&geth->freeq_mappings,
geth_freeq_mapping_index(geth, mapping));
}
- dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
- DMA_FROM_DEVICE);
- put_page(page);
- return NULL;
+ gpage->page = NULL;
+ gpage->mapping = 0;
+ __clear_bit(slot, geth->freeq_page_bitmap);
+ return ret;
}
/**
* geth_fill_freeq() - Fill the freeq with empty fragments to use
* @geth: the ethernet adapter
- * @refill: whether to reset the queue by filling in all freeq entries or
- * just refill it, usually the interrupt to refill the queue happens when
- * the queue is half empty.
+ *
+ * Usually the interrupt to refill the queue happens when the queue is half
+ * empty.
*/
-static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
+static unsigned int geth_fill_freeq(struct gemini_ethernet *geth)
{
unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
unsigned int count = 0;
@@ -935,63 +977,124 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
/* Mask for page */
m_pn = (1 << (geth->freeq_order - fpp_order)) - 1;
- spin_lock_irqsave(&geth->freeq_lock, flags);
-
- rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
- pn = (refill ? rw.bits.wptr : rw.bits.rptr) >> fpp_order;
- pn &= m_pn;
- epn = (rw.bits.rptr >> fpp_order) - 1;
- epn &= m_pn;
-
/* Loop over the freeq ring buffer entries */
- while (pn != epn) {
- struct gmac_queue_page *gpage;
+ for (;;) {
+ dma_addr_t page_mapping;
struct page *page;
+ int ret;
- gpage = &geth->freeq_pages[pn];
- page = gpage->page;
+ spin_lock_irqsave(&geth->freeq_lock, flags);
+ rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ pn = (rw.bits.wptr >> fpp_order) & m_pn;
+ epn = (rw.bits.rptr >> fpp_order) - 1;
+ epn &= m_pn;
+ spin_unlock_irqrestore(&geth->freeq_lock, flags);
+ if (pn == epn)
+ break;
- dev_dbg(geth->dev, "fill entry %d page ref count %d add %d refs\n",
- pn, page_ref_count(page), 1 << fpp_order);
+ ret = geth_freeq_map_page(geth, &page, &page_mapping);
+ if (ret)
+ break;
+
+ spin_lock_irqsave(&geth->freeq_lock, flags);
- if (page_ref_count(page) > 1) {
- unsigned int fl = (pn - epn) & m_pn;
+ rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
+ pn = (rw.bits.wptr >> fpp_order) & m_pn;
+ epn = (rw.bits.rptr >> fpp_order) - 1;
+ epn &= m_pn;
+ if (pn == epn) {
+ ret = -ENOSPC;
+ } else {
+ ret = geth_freeq_add_page(geth, pn, page,
+ page_mapping);
+ if (!ret) {
+ count += 1 << fpp_order;
+ pn++;
+ pn &= m_pn;
+ writew(pn << fpp_order,
+ geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
+ }
+ }
- if (fl > 64 >> fpp_order)
- break;
+ spin_unlock_irqrestore(&geth->freeq_lock, flags);
- page = geth_freeq_alloc_map_page(geth, pn);
- if (!page)
- break;
+ if (ret) {
+ dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ put_page(page);
+ break;
}
+ }
+
+ return count;
+}
+
+static void geth_freeq_release_pages(struct gemini_ethernet *geth)
+{
+ unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
+ unsigned int frag_len = 1 << geth->freeq_frag_order;
+ unsigned int pn;
- /* Add one reference per fragment in the page */
- page_ref_add(page, 1 << fpp_order);
- count += 1 << fpp_order;
- pn++;
- pn &= m_pn;
+ for (pn = 0; pn < geth->num_freeq_pages; pn++) {
+ struct gmac_queue_page *gpage;
+ dma_addr_t mapping;
+ struct page *page;
+ unsigned int i;
+
+ gpage = &geth->freeq_pages[pn];
+ if (!gpage->page)
+ continue;
+
+ page = gpage->page;
+ for (i = 0; i < (1 << fpp_order); i++) {
+ mapping = gpage->mapping + i * frag_len;
+ if (xa_load(&geth->freeq_mappings,
+ geth_freeq_mapping_index(geth, mapping)) != gpage)
+ continue;
+
+ dma_sync_single_range_for_cpu(geth->dev, gpage->mapping,
+ i * frag_len, frag_len,
+ DMA_FROM_DEVICE);
+ }
+ dma_unmap_single_attrs(geth->dev, gpage->mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ while (gpage->fragments) {
+ put_page(page);
+ gpage->fragments--;
+ }
+ put_page(page);
}
+ xa_destroy(&geth->freeq_mappings);
+}
- writew(pn << fpp_order, geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
+static unsigned int
+geth_freeq_page_slots(struct gemini_ethernet *geth, unsigned int order)
+{
+ unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
+ unsigned int slots = 1 << (order - fpp_order);
- spin_unlock_irqrestore(&geth->freeq_lock, flags);
+ if (geth->port0 && geth->port0->netdev)
+ slots += 1 << geth->port0->rxq_order;
+ if (geth->port1 && geth->port1->netdev)
+ slots += 1 << geth->port1->rxq_order;
- return count;
+ return slots;
}
static int geth_setup_freeq(struct gemini_ethernet *geth)
{
unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
unsigned int len = 1 << geth->freeq_order;
- unsigned int pages = len >> fpp_order;
+ unsigned int page_slots;
+ unsigned int expected;
union queue_threshold qt;
union dma_skb_size skbsz;
- unsigned long flags;
- struct page *page;
unsigned int filled;
- unsigned int pn;
u16 rptr;
+ page_slots = geth_freeq_page_slots(geth, geth->freeq_order);
+
geth->freeq_ring = dma_alloc_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
&geth->freeq_dma_base, GFP_KERNEL);
@@ -1003,27 +1106,22 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
}
/* Allocate page metadata referenced by the mapping index */
- geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, pages);
+ geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, page_slots);
if (!geth->freeq_pages)
goto err_freeq;
- geth->num_freeq_pages = pages;
-
- dev_info(geth->dev, "allocate %d pages for queue\n", pages);
- for (pn = 0; pn < pages; pn++) {
- spin_lock_irqsave(&geth->freeq_lock, flags);
- page = geth_freeq_alloc_map_page(geth, pn);
- spin_unlock_irqrestore(&geth->freeq_lock, flags);
- if (!page)
- goto err_freeq_alloc;
- }
+ geth->freeq_page_bitmap = bitmap_zalloc(page_slots, GFP_KERNEL);
+ if (!geth->freeq_page_bitmap)
+ goto err_freeq_pages;
+ geth->num_freeq_pages = page_slots;
/* Match the software pointer to the retained hardware pointer. */
rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
SET_WPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG, rptr & (len - 1));
- filled = geth_fill_freeq(geth, false);
- if (!filled)
- goto err_freeq_alloc;
+ expected = len - (1 << fpp_order);
+ filled = geth_fill_freeq(geth);
+ if (filled != expected)
+ goto err_freeq_bitmap;
qt.bits32 = readl(geth->base + GLOBAL_QUEUE_THRESHOLD_REG);
qt.bits.swfq_empty = 32;
@@ -1036,18 +1134,13 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
return 0;
-err_freeq_alloc:
- while (pn > 0) {
- struct gmac_queue_page *gpage;
-
- --pn;
- gpage = &geth->freeq_pages[pn];
- dma_unmap_single(geth->dev, gpage->mapping, PAGE_SIZE,
- DMA_FROM_DEVICE);
- put_page(gpage->page);
- }
- xa_destroy(&geth->freeq_mappings);
-
+err_freeq_bitmap:
+ writew(readw(geth->base + GLOBAL_SWFQ_RWPTR_REG),
+ geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
+ geth_freeq_release_pages(geth);
+ bitmap_free(geth->freeq_page_bitmap);
+ geth->freeq_page_bitmap = NULL;
+err_freeq_pages:
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
geth->num_freeq_pages = 0;
@@ -1065,11 +1158,6 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
*/
static void geth_cleanup_freeq(struct gemini_ethernet *geth)
{
- unsigned int fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
- unsigned int len = 1 << geth->freeq_order;
- unsigned int pages = len >> fpp_order;
- unsigned int pn;
-
if (!geth->freeq_ring)
return;
@@ -1077,17 +1165,10 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
writel(0, geth->base + GLOBAL_SW_FREEQ_BASE_SIZE_REG);
- for (pn = 0; pn < pages; pn++) {
- struct gmac_queue_page *gpage;
-
- gpage = &geth->freeq_pages[pn];
- dma_unmap_single(geth->dev, gpage->mapping, PAGE_SIZE,
- DMA_FROM_DEVICE);
- while (page_ref_count(gpage->page) > 0)
- put_page(gpage->page);
- }
- xa_destroy(&geth->freeq_mappings);
+ geth_freeq_release_pages(geth);
+ bitmap_free(geth->freeq_page_bitmap);
+ geth->freeq_page_bitmap = NULL;
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
geth->num_freeq_pages = 0;
@@ -1127,7 +1208,9 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
struct net_device *netdev = port->netdev;
struct gemini_ethernet_port *other_port;
struct net_device *other_netdev;
+ bool other_running;
unsigned int new_size = 0;
+ unsigned int page_slots;
unsigned int new_order;
u16 rptr;
int ret;
@@ -1143,9 +1226,7 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
else
other_port = geth->port0;
other_netdev = other_port ? other_port->netdev : NULL;
-
- if (other_netdev && netif_running(other_netdev))
- return -EBUSY;
+ other_running = other_netdev && netif_running(other_netdev);
new_size = 1 << (port->rxq_order + 1);
netdev_dbg(netdev, "port %d size: %d order %d\n",
@@ -1167,8 +1248,19 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
new_order = max_t(unsigned int, new_order, fls(rptr));
dev_dbg(geth->dev, "set shared queue to size %d order %d\n",
new_size, new_order);
- if (geth->freeq_ring && geth->freeq_order == new_order)
- return 0;
+ if (new_order <= PAGE_SHIFT - geth->freeq_frag_order)
+ return -EINVAL;
+
+ if (geth->freeq_ring) {
+ page_slots = geth_freeq_page_slots(geth, geth->freeq_order);
+ if (geth->num_freeq_pages >= page_slots &&
+ (geth->freeq_order == new_order ||
+ (other_running && geth->freeq_order > new_order)))
+ return 0;
+ }
+
+ if (other_running)
+ return -EBUSY;
disable_irq(geth->port1->irq);
geth_set_freeq_irq(geth, false);
@@ -1595,7 +1687,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
goto err_drop;
}
- page = geth_freeq_lookup(geth, mapping, &page_offs);
+ page = geth_freeq_claim(geth, mapping, &page_offs);
if (!page) {
dev_err_ratelimited(geth->dev,
"could not find mapping\n");
@@ -1692,7 +1784,7 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget)
port->freeq_refill += freeq_consumed;
if (port->freeq_refill > freeq_threshold) {
port->freeq_refill -= freeq_threshold;
- geth_fill_freeq(geth, true);
+ geth_fill_freeq(geth);
}
if (!reschedule && received < budget &&
@@ -1947,10 +2039,7 @@ static int gmac_open(struct net_device *netdev)
phy_start(netdev->phydev);
err = geth_resize_freeq(port);
- /* It's fine if it's just busy, the other port has set up
- * the freeq in that case.
- */
- if (err && (err != -EBUSY)) {
+ if (err) {
netdev_err(netdev, "could not resize freeq\n");
goto err_stop_phy;
}
@@ -2309,14 +2398,20 @@ static int gmac_set_ringparam(struct net_device *netdev,
struct netlink_ext_ack *extack)
{
struct gemini_ethernet_port *port = netdev_priv(netdev);
+ unsigned int old_rxq_order;
int err = 0;
if (netif_running(netdev))
return -EBUSY;
if (rp->rx_pending) {
+ old_rxq_order = port->rxq_order;
port->rxq_order = min(15, ilog2(rp->rx_pending - 1) + 1);
err = geth_resize_freeq(port);
+ if (err) {
+ port->rxq_order = old_rxq_order;
+ return err;
+ }
}
if (rp->tx_pending) {
port->txq_order = min(15, ilog2(rp->tx_pending - 1) + 1);
@@ -2422,7 +2517,7 @@ static irqreturn_t gemini_port_irq_thread(int irq, void *data)
geth = port->geth;
/* The queue is half empty so refill it */
- geth_fill_freeq(geth, true);
+ geth_fill_freeq(geth);
spin_lock_irqsave(&geth->irq_lock, flags);
/* ACK queue interrupt */
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 08/14] net: ethernet: cortina: Rotate free queue page allocation
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (6 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages Linus Walleij
` (5 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The free queue metadata bitmap currently searches for a free slot from
zero for every page allocation. Under sustained traffic this repeatedly
scans occupied low-numbered slots.
Remember the position after the most recently allocated slot and wrap the
bitmap search at its end. Reset the cursor when the bitmap is released so
the two remain consistent across failed setup and teardown. This keeps
allocation cost distributed across the metadata table.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index faab1bb865ab..1d71c5066457 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -174,6 +174,7 @@ struct gemini_ethernet {
struct xarray freeq_mappings;
unsigned int num_freeq_pages;
unsigned long *freeq_page_bitmap;
+ unsigned int freeq_page_cursor;
spinlock_t freeq_lock; /* Locks queue from reentrance */
};
@@ -745,12 +746,20 @@ static int geth_freeq_alloc_slot(struct gemini_ethernet *geth)
lockdep_assert_held(&geth->freeq_lock);
- slot = find_first_zero_bit(geth->freeq_page_bitmap,
- geth->num_freeq_pages);
- if (slot == geth->num_freeq_pages)
- return -ENOSPC;
+ slot = find_next_zero_bit(geth->freeq_page_bitmap,
+ geth->num_freeq_pages,
+ geth->freeq_page_cursor);
+ if (slot == geth->num_freeq_pages) {
+ slot = find_first_zero_bit(geth->freeq_page_bitmap,
+ geth->freeq_page_cursor);
+ if (slot == geth->freeq_page_cursor)
+ return -ENOSPC;
+ }
__set_bit(slot, geth->freeq_page_bitmap);
+ geth->freeq_page_cursor = slot + 1;
+ if (geth->freeq_page_cursor == geth->num_freeq_pages)
+ geth->freeq_page_cursor = 0;
return slot;
}
@@ -1113,6 +1122,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
if (!geth->freeq_page_bitmap)
goto err_freeq_pages;
geth->num_freeq_pages = page_slots;
+ geth->freeq_page_cursor = 0;
/* Match the software pointer to the retained hardware pointer. */
rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
@@ -1144,6 +1154,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
geth->num_freeq_pages = 0;
+ geth->freeq_page_cursor = 0;
err_freeq:
dma_free_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
@@ -1172,6 +1183,7 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
kfree(geth->freeq_pages);
geth->freeq_pages = NULL;
geth->num_freeq_pages = 0;
+ geth->freeq_page_cursor = 0;
dma_free_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (7 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 08/14] net: ethernet: cortina: Rotate free queue page allocation Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths Linus Walleij
` (4 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The preceding "Preserve in-flight free queue pages" fix releases each
mapping after its last descriptor is claimed. Refilling therefore
allocates and maps a fresh page at line rate even after the network stack
has released all references to an old page.
Keep the free queue base reference and streaming mapping after the
fragments are claimed. Track fully claimed slots in a separate bitmap and
inspect one at a time during refill. Recycle a page once its reference
count returns to one. If the stack still owns fragments, unmap the page
and relinquish the base reference before allocating its replacement.
This prevents retained pages from exhausting the finite metadata table
and bounds the IRQ-disabled search to a bitmap lookup and one reference
count check. It retains the existing free queue allocator without
introducing a page pool.
Recheck the recycle bitmap on each refill iteration and again after mapping
a candidate page. If another RX path retires a page concurrently, release
the candidate and return to the recycler before consuming another metadata
slot. Retry recycling immediately when a retained page is discarded so
other pending entries are examined before allocating a replacement. This
avoids stopping refill while recyclable slots are pending.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 154 +++++++++++++++++++++++++++++-----
1 file changed, 131 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 1d71c5066457..e42eeefd4d19 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -174,7 +174,9 @@ struct gemini_ethernet {
struct xarray freeq_mappings;
unsigned int num_freeq_pages;
unsigned long *freeq_page_bitmap;
+ unsigned long *freeq_recycle_bitmap;
unsigned int freeq_page_cursor;
+ unsigned int freeq_recycle_pending;
spinlock_t freeq_lock; /* Locks queue from reentrance */
};
@@ -764,6 +766,34 @@ static int geth_freeq_alloc_slot(struct gemini_ethernet *geth)
return slot;
}
+static int geth_freeq_recycle_slot(struct gemini_ethernet *geth)
+{
+ unsigned int slot = geth->freeq_page_cursor;
+
+ lockdep_assert_held(&geth->freeq_lock);
+
+ if (!geth->freeq_recycle_pending)
+ return -ENOSPC;
+
+ slot = find_next_bit(geth->freeq_recycle_bitmap,
+ geth->num_freeq_pages,
+ geth->freeq_page_cursor);
+ if (slot == geth->num_freeq_pages) {
+ slot = find_first_bit(geth->freeq_recycle_bitmap,
+ geth->freeq_page_cursor);
+ if (slot == geth->freeq_page_cursor)
+ return -ENOSPC;
+ }
+
+ __clear_bit(slot, geth->freeq_recycle_bitmap);
+ geth->freeq_page_cursor = slot + 1;
+ if (geth->freeq_page_cursor == geth->num_freeq_pages)
+ geth->freeq_page_cursor = 0;
+ geth->freeq_recycle_pending--;
+
+ return slot;
+}
+
static struct page *geth_freeq_claim(struct gemini_ethernet *geth,
dma_addr_t mapping,
unsigned int *page_offs)
@@ -773,8 +803,8 @@ static struct page *geth_freeq_claim(struct gemini_ethernet *geth,
unsigned long index;
unsigned long flags;
dma_addr_t page_mapping;
- unsigned int slot;
struct page *page;
+ unsigned int slot;
bool valid;
index = geth_freeq_mapping_index(geth, mapping);
@@ -798,13 +828,8 @@ static struct page *geth_freeq_claim(struct gemini_ethernet *geth,
xa_erase(&geth->freeq_mappings, index);
if (!--gpage->fragments) {
slot = gpage - geth->freeq_pages;
- dma_unmap_single_attrs(geth->dev, page_mapping, PAGE_SIZE,
- DMA_FROM_DEVICE,
- DMA_ATTR_SKIP_CPU_SYNC);
- gpage->page = NULL;
- gpage->mapping = 0;
- __clear_bit(slot, geth->freeq_page_bitmap);
- put_page(page);
+ __set_bit(slot, geth->freeq_recycle_bitmap);
+ geth->freeq_recycle_pending++;
}
*page_offs = mapping - page_mapping;
@@ -898,25 +923,21 @@ static int geth_freeq_map_page(struct gemini_ethernet *geth,
return 0;
}
-static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
- struct page *page, dma_addr_t page_mapping)
+static int geth_freeq_post_page(struct gemini_ethernet *geth, unsigned int pn,
+ struct gmac_queue_page *gpage, bool recycle)
{
struct gmac_rxdesc *freeq_entry;
- struct gmac_queue_page *gpage;
unsigned int fpp_order;
unsigned int fragments;
unsigned int frag_len;
+ dma_addr_t page_mapping = gpage->mapping;
dma_addr_t mapping;
+ struct page *page = gpage->page;
int ret;
- int slot;
int i;
lockdep_assert_held(&geth->freeq_lock);
- slot = geth_freeq_alloc_slot(geth);
- if (slot < 0)
- return slot;
-
/* The assign the page mapping (physical address) to the buffer address
* in the hardware queue. PAGE_SHIFT on ARM is 12 (1 page is 4096 bytes,
* 4k), and the default RX frag order is 11 (fragments are up 20 2048
@@ -927,9 +948,6 @@ static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
fragments = 1 << fpp_order;
- gpage = &geth->freeq_pages[slot];
- gpage->page = page;
- gpage->mapping = page_mapping;
for (i = 0; i < fragments; i++) {
mapping = page_mapping + i * frag_len;
ret = xa_insert(&geth->freeq_mappings,
@@ -938,11 +956,14 @@ static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
if (ret)
goto err_mappings;
}
+ if (recycle)
+ dma_sync_single_for_device(geth->dev, page_mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE);
gpage->fragments = fragments;
page_ref_add(page, fragments);
freeq_entry = geth->freeq_ring + (pn << fpp_order);
- dev_dbg(geth->dev, "allocate page %d fragment length %d fragments per page %d, freeq entry %p\n",
+ dev_dbg(geth->dev, "post page %d fragment length %d fragments per page %d, freeq entry %p\n",
pn, frag_len, (1 << fpp_order), freeq_entry);
mapping = page_mapping;
for (i = (1 << fpp_order); i > 0; i--) {
@@ -961,9 +982,68 @@ static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
xa_erase(&geth->freeq_mappings,
geth_freeq_mapping_index(geth, mapping));
}
- gpage->page = NULL;
- gpage->mapping = 0;
- __clear_bit(slot, geth->freeq_page_bitmap);
+ return ret;
+}
+
+static int geth_freeq_add_page(struct gemini_ethernet *geth, unsigned int pn,
+ struct page *page, dma_addr_t page_mapping)
+{
+ struct gmac_queue_page *gpage;
+ int ret;
+ int slot;
+
+ lockdep_assert_held(&geth->freeq_lock);
+
+ slot = geth_freeq_alloc_slot(geth);
+ if (slot < 0)
+ return slot;
+
+ gpage = &geth->freeq_pages[slot];
+ gpage->page = page;
+ gpage->mapping = page_mapping;
+ ret = geth_freeq_post_page(geth, pn, gpage, false);
+ if (ret) {
+ gpage->page = NULL;
+ gpage->mapping = 0;
+ __clear_bit(slot, geth->freeq_page_bitmap);
+ }
+
+ return ret;
+}
+
+static int geth_freeq_recycle_page(struct gemini_ethernet *geth,
+ unsigned int pn)
+{
+ struct gmac_queue_page *gpage;
+ struct page *page;
+ int ret;
+ int slot;
+
+ lockdep_assert_held(&geth->freeq_lock);
+
+ slot = geth_freeq_recycle_slot(geth);
+ if (slot < 0)
+ return slot;
+
+ gpage = &geth->freeq_pages[slot];
+ if (page_ref_count(gpage->page) > 1) {
+ page = gpage->page;
+ dma_unmap_single_attrs(geth->dev, gpage->mapping, PAGE_SIZE,
+ DMA_FROM_DEVICE,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ gpage->page = NULL;
+ gpage->mapping = 0;
+ __clear_bit(slot, geth->freeq_page_bitmap);
+ put_page(page);
+ return -EAGAIN;
+ }
+
+ ret = geth_freeq_post_page(geth, pn, gpage, true);
+ if (ret) {
+ __set_bit(slot, geth->freeq_recycle_bitmap);
+ geth->freeq_recycle_pending++;
+ }
+
return ret;
}
@@ -997,9 +1077,24 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth)
pn = (rw.bits.wptr >> fpp_order) & m_pn;
epn = (rw.bits.rptr >> fpp_order) - 1;
epn &= m_pn;
+ ret = -ENOSPC;
+ if (pn != epn && geth->freeq_recycle_pending) {
+ ret = geth_freeq_recycle_page(geth, pn);
+ if (!ret) {
+ count += 1 << fpp_order;
+ pn++;
+ pn &= m_pn;
+ writew(pn << fpp_order,
+ geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
+ }
+ }
spin_unlock_irqrestore(&geth->freeq_lock, flags);
if (pn == epn)
break;
+ if (!ret || ret == -EAGAIN)
+ continue;
+ if (ret != -ENOSPC)
+ break;
ret = geth_freeq_map_page(geth, &page, &page_mapping);
if (ret)
@@ -1013,6 +1108,8 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth)
epn &= m_pn;
if (pn == epn) {
ret = -ENOSPC;
+ } else if (geth->freeq_recycle_pending) {
+ ret = -EAGAIN;
} else {
ret = geth_freeq_add_page(geth, pn, page,
page_mapping);
@@ -1031,6 +1128,8 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth)
dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE,
DMA_FROM_DEVICE);
put_page(page);
+ if (ret == -EAGAIN)
+ continue;
break;
}
}
@@ -1075,6 +1174,7 @@ static void geth_freeq_release_pages(struct gemini_ethernet *geth)
put_page(page);
}
xa_destroy(&geth->freeq_mappings);
+ geth->freeq_recycle_pending = 0;
}
static unsigned int
@@ -1121,6 +1221,9 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
geth->freeq_page_bitmap = bitmap_zalloc(page_slots, GFP_KERNEL);
if (!geth->freeq_page_bitmap)
goto err_freeq_pages;
+ geth->freeq_recycle_bitmap = bitmap_zalloc(page_slots, GFP_KERNEL);
+ if (!geth->freeq_recycle_bitmap)
+ goto err_freeq_page_bitmap;
geth->num_freeq_pages = page_slots;
geth->freeq_page_cursor = 0;
@@ -1148,6 +1251,9 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
writew(readw(geth->base + GLOBAL_SWFQ_RWPTR_REG),
geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
geth_freeq_release_pages(geth);
+ bitmap_free(geth->freeq_recycle_bitmap);
+ geth->freeq_recycle_bitmap = NULL;
+err_freeq_page_bitmap:
bitmap_free(geth->freeq_page_bitmap);
geth->freeq_page_bitmap = NULL;
err_freeq_pages:
@@ -1178,6 +1284,8 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
geth_freeq_release_pages(geth);
+ bitmap_free(geth->freeq_recycle_bitmap);
+ geth->freeq_recycle_bitmap = NULL;
bitmap_free(geth->freeq_page_bitmap);
geth->freeq_page_bitmap = NULL;
kfree(geth->freeq_pages);
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (8 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize Linus Walleij
` (3 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
RX descriptor lengths are used to adjust the page offset and populate skb
fragments without checking that the resulting range remains inside the
posted free queue fragment. A zero-length descriptor is logged but is still
appended.
The backing page is larger than the default 2 KiB DMA fragment, so checking
only the page boundary would allow a malformed descriptor for the first
half of a page to consume data from the sibling fragment. That fragment may
still be owned by the device.
Reject a short initial fragment before applying NET_IP_ALIGN, reject frame
length underflow and ranges extending beyond either the DMA fragment or the
page. Derive the final fragment length from the total frame byte count
rather than treating the descriptor buffer size as an EOF payload length.
Drop zero-length non-final descriptors. If an EOF descriptor carries no new
data after earlier descriptors completed the frame, release its unused page
reference and pass the assembled skb to GRO. Check this case before the
fragment limit so an empty EOF can terminate a full fragment array.
Correct the copied buffer_size comment to match the Gemini manual, which
defines it as the transfer buffer size associated with the descriptor.
Require the final frame length to cover an Ethernet frame so a malformed
chain cannot pass a short skb to GRO. Account descriptor inconsistencies as
drops rather than wire length errors.
The issue was found by inspecting the descriptor arithmetic and validated
against the Gemini receive descriptor definitions. The series was tested on
a D-Link DIR-685 with media playback and ksmbd transfers under ping load.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 42 +++++++++++++++++++++++++++--------
drivers/net/ethernet/cortina/gemini.h | 2 +-
2 files changed, 34 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index e42eeefd4d19..70aa418d7037 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -1745,6 +1745,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
struct gemini_ethernet_port *port = netdev_priv(netdev);
unsigned short m = (1 << port->rxq_order) - 1;
struct gemini_ethernet *geth = port->geth;
+ unsigned int freeq_frag_len = 1 << geth->freeq_frag_order;
void __iomem *ptr_reg = port->rxq_rwptr;
unsigned int frag_nr = port->rx_frag_nr;
struct sk_buff *skb = port->rx_skb;
@@ -1819,6 +1820,9 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
if (!skb)
goto err_drop;
+ if (frag_len < NET_IP_ALIGN)
+ goto err_drop;
+
page_offs += NET_IP_ALIGN;
frag_len -= NET_IP_ALIGN;
frag_nr = 0;
@@ -1827,15 +1831,33 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
goto err_drop;
}
- if (word3.bits32 & EOF_BIT)
+ if (word3.bits32 & EOF_BIT) {
+ if (frame_len < ETH_ZLEN ||
+ frame_len < skb->len)
+ goto err_drop;
frag_len = frame_len - skb->len;
+ }
+
+ if (!frag_len) {
+ if (word3.bits32 & EOF_BIT) {
+ put_page(page);
+ page = NULL;
+ goto complete_frame;
+ }
+
+ if (net_ratelimit())
+ netdev_err(netdev,
+ "Received fragment with len = 0\n");
+ goto err_drop;
+ }
/* append page frag to skb */
if (frag_nr == MAX_SKB_FRAGS)
goto err_drop;
-
- if (frag_len == 0 && net_ratelimit())
- netdev_err(netdev, "Received fragment with len = 0\n");
+ if (frag_len > freeq_frag_len -
+ (page_offs & (freeq_frag_len - 1)) ||
+ frag_len > PAGE_SIZE - page_offs)
+ goto err_drop;
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
skb->len += frag_len;
@@ -1843,11 +1865,13 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
skb->truesize += frag_len;
frag_nr++;
- if (word3.bits32 & EOF_BIT) {
- napi_gro_frags(&port->napi);
- skb = NULL;
- frag_nr = 0;
- }
+ if (!(word3.bits32 & EOF_BIT))
+ goto next_desc;
+
+complete_frame:
+ napi_gro_frags(&port->napi);
+ skb = NULL;
+ frag_nr = 0;
goto next_desc;
err_drop:
diff --git a/drivers/net/ethernet/cortina/gemini.h b/drivers/net/ethernet/cortina/gemini.h
index d359c633c5de..43006fe44c9f 100644
--- a/drivers/net/ethernet/cortina/gemini.h
+++ b/drivers/net/ethernet/cortina/gemini.h
@@ -519,7 +519,7 @@ struct gmac_txdesc {
union gmac_rxdesc_0 {
unsigned int bits32;
struct bit_8060 {
- /* bit 15:0 number of descriptors used for the current frame */
+ /* bit 15:0 transfer buffer size for this descriptor */
unsigned int buffer_size:16;
/* bit 21:16 number of descriptors used for the current frame */
unsigned int desc_count:6;
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (9 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop Linus Walleij
` (2 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
gmac_rx() charges only the received payload length for each page fragment
attached to an skb. Each fragment pins a complete free queue DMA buffer, so
short packets understate their memory use to GRO and socket accounting.
Charge the full posted fragment size for every attached fragment. The skb
already accounts for its own head separately.
The issue was found by reviewing the RX fragment accounting. The series was
tested on a D-Link DIR-685 with media playback and ksmbd transfers under
ping load.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 70aa418d7037..d4e90aa97d22 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -1862,7 +1862,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
skb->len += frag_len;
skb->data_len += frag_len;
- skb->truesize += frag_len;
+ skb->truesize += freeq_frag_len;
frag_nr++;
if (!(word3.bits32 & EOF_BIT))
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (10 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 13/14] net: ethernet: cortina: Scale Gemini RX queues to system memory Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates Linus Walleij
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
A NAPI poll can retain a partially assembled frame across invocations. The
stop path clears that pointer without releasing its fragment references,
leaking every page already attached to the frame.
Free a pending fragment skb after disabling NAPI and before clearing the
saved RX state. Count the discarded frame as dropped under the RX
statistics sequence counter. Use the irqsave update helpers because
ndo_stop runs in preemptible process context while 32-bit statistics
writers must not be preempted.
The issue was found by inspecting the partial frame lifetime and statistics
writer contexts. The series was tested on a D-Link DIR-685 with media
playback and ksmbd transfers under ping load.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index d4e90aa97d22..843423187f8d 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -2224,12 +2224,19 @@ static int gmac_open(struct net_device *netdev)
static int gmac_stop(struct net_device *netdev)
{
struct gemini_ethernet_port *port = netdev_priv(netdev);
+ unsigned long flags;
hrtimer_cancel(&port->rx_coalesce_timer);
netif_tx_stop_all_queues(netdev);
gmac_disable_tx_rx(netdev);
gmac_stop_dma(port);
napi_disable(&port->napi);
+ if (port->rx_skb) {
+ napi_free_frags(&port->napi);
+ flags = u64_stats_update_begin_irqsave(&port->rx_stats_syncp);
+ port->stats.rx_dropped++;
+ u64_stats_update_end_irqrestore(&port->rx_stats_syncp, flags);
+ }
port->rx_skb = NULL;
port->rx_frag_nr = 0;
port->rx_dropping = false;
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 13/14] net: ethernet: cortina: Scale Gemini RX queues to system memory
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (11 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates Linus Walleij
13 siblings, 0 replies; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
The default RX queues contain 512 descriptors per port. The shared
software free queue is sized at twice their combined capacity, requiring
4 MiB of page fragments even on Gemini systems with only 32 or 64 MiB of
RAM.
Choose smaller default RX queues on memory-constrained systems. Use 256
descriptors per port and a 2 MiB free queue with at most 64 MiB of RAM,
and 128 descriptors per port with a 1 MiB free queue with at most 32 MiB.
Keep the existing defaults on systems with more memory.
This preserves the existing relationship between RX descriptor capacity
and the free queue. Users can still override the defaults with ethtool.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 843423187f8d..0c6c7c938b2e 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -16,6 +16,7 @@
#include <linux/kernel.h>
#include <linux/bitmap.h>
#include <linux/init.h>
+#include <linux/mm.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/platform_device.h>
@@ -36,6 +37,7 @@
#include <linux/skbuff.h>
#include <linux/phy.h>
#include <linux/crc32.h>
+#include <linux/sizes.h>
#include <linux/ethtool.h>
#include <linux/tcp.h>
#include <linux/u64_stats_sync.h>
@@ -474,6 +476,17 @@ static int gmac_pick_rx_max_len(unsigned int max_l3_len)
return -1;
}
+static unsigned int gmac_pick_rxq_order(void)
+{
+ if (totalram_pages() <= (SZ_32M >> PAGE_SHIFT))
+ return DEFAULT_GMAC_RXQ_ORDER - 2;
+
+ if (totalram_pages() <= (SZ_64M >> PAGE_SHIFT))
+ return DEFAULT_GMAC_RXQ_ORDER - 1;
+
+ return DEFAULT_GMAC_RXQ_ORDER;
+}
+
static int gmac_init(struct net_device *netdev)
{
struct gemini_ethernet_port *port = netdev_priv(netdev);
@@ -542,7 +555,7 @@ static int gmac_init(struct net_device *netdev)
writel(sw_weigh.bits32,
port->dma_base + GMAC_TX_WEIGHTING_CTRL_1_REG);
- port->rxq_order = DEFAULT_GMAC_RXQ_ORDER;
+ port->rxq_order = gmac_pick_rxq_order();
port->txq_order = DEFAULT_GMAC_TXQ_ORDER;
port->rx_coalesce_nsecs = DEFAULT_RX_COALESCE_NSECS;
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
` (12 preceding siblings ...)
2026-10-15 15:24 ` [PATCH net-next v5 13/14] net: ethernet: cortina: Scale Gemini RX queues to system memory Linus Walleij
@ 2026-10-15 15:24 ` Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
13 siblings, 1 reply; 24+ messages in thread
From: Linus Walleij @ 2026-10-15 15:24 UTC (permalink / raw)
To: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Michał Mirosław, Myeonghun Pak,
Eric Dumazet
Cc: netdev, Linus Walleij
On 32-bit PREEMPT_RT systems u64_stats_update_begin() disables
preemption. The RX poll currently holds that writer section across
gmac_rx(), which takes spinlock_t locks and calls into GRO and fragment
handling. spinlock_t can sleep on PREEMPT_RT and must not be acquired
with preemption disabled.
Keep the writer sections around the counter updates only. NAPI ownership
continues to serialize normal RX writers, while the stop path updates its
counter after napi_disable(). This preserves the existing accounting
without covering potentially sleeping operations.
The issue was found by inspecting the u64_stats_sync writer scope. The
series was tested on a D-Link DIR-685 with media playback and ksmbd
transfers under ping load. PREEMPT_RT was not runtime-tested.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 0c6c7c938b2e..7ecc8db9b7d3 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -1720,12 +1720,12 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port,
unsigned int rx_status = word0.bits.status;
struct sk_buff *skb = NULL;
- port->rx_stats[rx_status]++;
- port->rx_csum_stats[rx_csum]++;
-
if (word0.bits.derr || word0.bits.perr ||
rx_status || frame_len < ETH_ZLEN ||
rx_csum >= RX_CHKSUM_IP_ERR_UNKNOWN) {
+ u64_stats_update_begin(&port->rx_stats_syncp);
+ port->rx_stats[rx_status]++;
+ port->rx_csum_stats[rx_csum]++;
port->stats.rx_errors++;
if (frame_len < ETH_ZLEN || RX_ERROR_LENGTH(rx_status))
@@ -1736,6 +1736,7 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port,
port->stats.rx_crc_errors++;
if (RX_ERROR_FRAME(rx_status))
port->stats.rx_frame_errors++;
+ u64_stats_update_end(&port->rx_stats_syncp);
return NULL;
}
@@ -1747,8 +1748,12 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port,
skb->ip_summed = CHECKSUM_UNNECESSARY;
update_exit:
+ u64_stats_update_begin(&port->rx_stats_syncp);
+ port->rx_stats[rx_status]++;
+ port->rx_csum_stats[rx_csum]++;
port->stats.rx_bytes += frame_len;
port->stats.rx_packets++;
+ u64_stats_update_end(&port->rx_stats_syncp);
return skb;
}
@@ -1806,7 +1811,9 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
if (word3.bits32 & SOF_BIT) {
if (skb) {
napi_free_frags(&port->napi);
+ u64_stats_update_begin(&port->rx_stats_syncp);
port->stats.rx_dropped++;
+ u64_stats_update_end(&port->rx_stats_syncp);
skb = NULL;
frag_nr = 0;
}
@@ -1898,7 +1905,9 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
put_page(page);
if (!dropping) {
+ u64_stats_update_begin(&port->rx_stats_syncp);
port->stats.rx_dropped++;
+ u64_stats_update_end(&port->rx_stats_syncp);
dropping = true;
}
@@ -1930,13 +1939,13 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget)
bool reschedule;
freeq_threshold = 1 << (geth->freeq_order - 1);
- u64_stats_update_begin(&port->rx_stats_syncp);
received = gmac_rx(napi->dev, budget, &freeq_consumed, &reschedule);
- if (!reschedule && received < budget)
+ if (!reschedule && received < budget) {
+ u64_stats_update_begin(&port->rx_stats_syncp);
++port->rx_napi_exits;
-
- u64_stats_update_end(&port->rx_stats_syncp);
+ u64_stats_update_end(&port->rx_stats_syncp);
+ }
port->freeq_refill += freeq_consumed;
if (port->freeq_refill > freeq_threshold) {
--
2.56.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned
2026-10-15 15:24 ` [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 (1) · High: 2 (1) · Medium: 0 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] At this commit, gemini_ethernet_remove() calls
devm_of_platform_depopulate() (line 2689), which destroys the children…
- [High] net: cortina: MMIO access after pclk disable in remove
Pre-existing issues:
- [Critical] At this commit, geth_cleanup_freeq() releases each free-
queue page with 'while (page_ref_count(gpage->page) > 0)…
- [High] This problem wasn't introduced by this patch, and it is still
present at the end of the series.
--
net: ethernet: cortina: Keep shared free queue parent-owned
This patch moves cleanup of the shared software free queue into the parent
remove callback. That cleanup now runs after both port devices have been
depopulated, and it is skipped when no queue was allocated. The patch also
suppresses bind attributes on the port driver and unregisters each netdev
before disconnecting its PHY.
Closes: https://lore.kernel.org/netdev/20260917192835.58126-1-mhun512@gmail.com/
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 2fe7fd0202d26..bf7652b04f1ba 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -1004,6 +1004,9 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
> unsigned int pages = len >> fpp_order;
> unsigned int pn;
>
> + if (!geth->freeq_ring)
> + return;
> +
> writew(readw(geth->base + GLOBAL_SWFQ_RWPTR_REG),
> geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
> writel(0, geth->base + GLOBAL_SW_FREEQ_BASE_SIZE_REG);
[Severity: Critical]
This is a pre-existing issue, not one introduced by this patch. But now
that geth_cleanup_freeq() is the single teardown point, is the page release
loop later in this function safe?
gpage = &geth->freeq_pages[pn];
while (page_ref_count(gpage->page) > 0)
put_page(gpage->page);
gmac_rx() hands references on these same pages to skbs:
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
Those skbs can still be sitting in socket queues when this runs from
gemini_ethernet_remove(). The same applies when it runs from
geth_resize_freeq() on ifup or ethtool -G while the other port is down.
If the refcount is forced to zero, the page goes back to the allocator while
live skbs still point at it. Wouldn't that lead to a double put_page() on the
later kfree_skb(), and to possible info leaks or corruption once the page is
reallocated?
The later commit "net: ethernet: cortina: Preserve in-flight free queue
pages" in this series seems to address this. It replaces the loop with
geth_freeq_release_pages(), which drops only the outstanding hardware
fragment references plus the base reference.
> @@ -2387,11 +2390,10 @@ static irqreturn_t gemini_port_irq(int irq, void *data)
> static void gemini_port_remove(struct gemini_ethernet_port *port)
> {
> if (port->netdev) {
> - phy_disconnect(port->netdev->phydev);
> unregister_netdev(port->netdev);
[Severity: High]
This is a pre-existing issue, not one introduced by this patch.
unregister_netdev() here ends up in gmac_stop(), and devres then frees the
netdev that embeds the port. Can rx_coalesce_timer still be queued at that
point?
gmac_stop() cancels the timer before anything else:
hrtimer_cancel(&port->rx_coalesce_timer);
netif_tx_stop_all_queues(netdev);
...
napi_disable(&port->napi);
...
gmac_enable_irq(netdev, 0);
...
free_irq(netdev->irq, netdev);
While napi_disable() waits, gmac_napi_poll() can re-enable the RX IRQ
through napi_complete_done(), and gmac_irq() is still registered. If
rx_coalesce_nsecs was set with ethtool -C, gmac_irq() then re-arms the
timer:
hrtimer_start(&port->rx_coalesce_timer, ktime,
HRTIMER_MODE_REL);
Nothing cancels the timer again after free_irq(). If the driver is unbound
next, gmac_coalesce_delay_expired() would call napi_schedule(&port->napi) on
freed memory.
A quick reopen would also run hrtimer_setup() in gmac_open() on a timer that
is still queued.
The managed SWFQ handler draining added to gemini_ethernet_remove() doesn't
cover this timer, and nothing later in the series seems to fix it either.
Should the timer be cancelled again after free_irq() in gmac_stop()?
> + phy_disconnect(port->netdev->phydev);
> }
> clk_disable_unprepare(port->pclk);
> - geth_cleanup_freeq(port->geth);
> }
[ ... ]
> @@ -2683,6 +2686,8 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
> {
> struct gemini_ethernet *geth = platform_get_drvdata(pdev);
>
> + devm_of_platform_depopulate(&pdev->dev);
[Severity: High]
devm_of_platform_depopulate() removes the children one at a time in reverse
order, so port 1 goes first. gemini_port_remove() unregisters port 1's
netdev but never clears geth->port1. Devres then frees the refill IRQ and
the netdev that contains the port.
Port 0 is still registered and usable during that window. Could bringing
eth0 up or running ethtool -G on it then reach geth_resize_freeq() and
dereference the freed port?
if (netdev->dev_id == 0)
other_netdev = geth->port1->netdev;
...
if (other_netdev && netif_running(other_netdev))
return -EBUSY;
...
other_port = netdev_priv(other_netdev);
new_size += 1 << (other_port->rxq_order + 1);
If the resize goes ahead, it also sets SWFQ_EMPTY_INT_BIT in
GLOBAL_INTERRUPT_ENABLE_4_REG again, even though port 1's refill handler is
already gone.
The commit message says:
Port 1 owns the shared refill interrupt, so prevent the children from
being unbound independently.
This teardown order still lets port 0 outlive port 1, though.
The later commit "net: ethernet: cortina: Keep PHY-less port bound for
shared IRQ" in this series appears to close this window. It clears
geth->initialized under RTNL before depopulating, and it clears
geth->port0/port1 under RTNL in gemini_port_clear(). It also makes
geth_resize_freeq() return -ENODEV in those cases.
> + writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
> geth_cleanup_freeq(geth);
> geth->initialized = false;
> }
[Severity: High]
By the time devm_of_platform_depopulate() returns,
gemini_port_remove() has run for both ports. Each of those calls ends
with clk_disable_unprepare(port->pclk), so both GMAC gate clocks are off
when gemini_ethernet_remove() writes GLOBAL_INTERRUPT_ENABLE_4_REG. They
are still off when geth_cleanup_freeq() goes on to touch
GLOBAL_SWFQ_RWPTR_REG and GLOBAL_SW_FREEQ_BASE_SIZE_REG.
The parent driver doesn't take a clock of its own, so nothing keeps the
block behind geth->base clocked at this point. Is that global register
space clocked independently of the port pclks? If it isn't, these
accesses hit a gated IP block. On ARM that usually ends in an external
abort rather than a silently dropped write.
Register accesses that work in gemini_ethernet_probe() don't show that
the clocks are unnecessary. Probe can run before clk_disable_unused() at
late_initcall, so gates the bootloader left on may still be enabled
then. In the remove path, the explicit clk_disable_unprepare() calls
reliably turn them off first.
Should the parent hold a reference on whichever clock feeds the global
block for its whole lifetime? Alternatively, should the port that goes
last mask the free-queue interrupt and let the queue be torn down before
it drops its pclk? Nothing later in the series seems to change this
ordering.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild
2026-10-15 15:24 ` [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The commit fixes an out-of-bounds access but has no Fixes: tag.
Pre-existing issues:
- [Medium] The hardware SWFQ read pointer advances one fragment
descriptor at a time.
- [Medium] In geth_freeq_alloc_map_page(), when a slot already holds a
page, the loop `freeq_entry->word2.buf_adr = mapping` first overwrites…
--
net: ethernet: cortina: Normalize free queue pointers on rebuild
This patch masks the software free queue page index to the active queue
order in geth_fill_freeq(). It sizes the rebuilt free queue so that the
retained hardware read pointer fits inside it. Before the ring is filled,
it sets the write pointer from that read pointer.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 003108270d060..7ebd62f8a3a46 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -887,6 +887,7 @@ static unsigned int geth_fill_freeq(struct gemini_ethernet *geth, bool refill)
>
> rw.bits32 = readl(geth->base + GLOBAL_SWFQ_RWPTR_REG);
> pn = (refill ? rw.bits.wptr : rw.bits.rptr) >> fpp_order;
> + pn &= m_pn;
> epn = (rw.bits.rptr >> fpp_order) - 1;
> epn &= m_pn;
[Severity: Low]
Should this patch carry a Fixes: tag?
Without this mask, rebuilding the queue at a smaller order through ethtool
-G could use the retained hardware rptr to index geth->freeq_pages[pn] past
the end of the kzalloc'd array:
gmac_set_ringparam()
geth_resize_freeq()
geth_setup_freeq()
geth_fill_freeq(geth, false)
pn = rw.bits.rptr >> fpp_order;
gpage = &geth->freeq_pages[pn];
The unmasked computation appears to come from 4d5ae32f5e1e ("net:
ethernet: Add a driver for Gemini gigabit ethernet").
The other fixes in this series ("Keep shared free queue parent-owned",
"Drain free queue IRQ before resize" and "Preserve in-flight free queue
pages") all carry Fixes: 4d5ae32f5e1e. Without the tag here, stable trees
may not pick up this out-of-bounds fix.
[ ... ]
> @@ -958,6 +960,10 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
> if (!geth_freeq_alloc_map_page(geth, pn))
> goto err_freeq_alloc;
[Severity: Medium]
This is a pre-existing issue and was not introduced by this patch. Does
geth_freeq_alloc_map_page() unmap the right address when it replaces a
page?
The function writes the new page's mapping into the ring and then reads
the "old" mapping back from the same slot:
for (i = (1 << fpp_order); i > 0; i--) {
freeq_entry->word2.buf_adr = mapping;
freeq_entry++;
mapping += frag_len;
}
gpage = &geth->freeq_pages[pn];
if (gpage->page) {
mapping = geth->freeq_ring[pn << fpp_order].word2.buf_adr;
dma_unmap_single(geth->dev, mapping, frag_len, DMA_FROM_DEVICE);
...
put_page(gpage->page);
}
The refill path in geth_fill_freeq() calls this to replace an in-use page
when page_ref_count(page) > 1 and fl <= 64 >> fpp_order. In that case it
looks like the new buffer, which the hardware is about to DMA into, gets
unmapped, and the old page's mapping is never unmapped.
The unmap also uses frag_len, not the PAGE_SIZE passed to
dma_map_single(). The error path in geth_setup_freeq() and
geth_cleanup_freeq() also unmap with frag_len.
When no page was present, gpage->mapping = mapping stores the address
after the fragment loop has moved past it, not the page base.
The next commit in this series, "Correct free queue DMA mappings", appears
to fix this. It removes the invalid replacement unmap, keeps the page DMA
base separate from the fragment iterator, and unmaps with PAGE_SIZE.
>
> + /* Match the software pointer to the retained hardware pointer. */
> + rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
> + SET_WPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG, rptr & (len - 1));
> +
> filled = geth_fill_freeq(geth, false);
[Severity: Medium]
This isn't a bug introduced by this patch, but what happens when the
retained rptr is odd?
The hardware read pointer moves one fragment descriptor at a time. With
the default fpp_order of 1, each page has two descriptors. However,
geth_fill_freeq() works in whole pages:
pn = (refill ? rw.bits.wptr : rw.bits.rptr) >> fpp_order;
pn &= m_pn;
epn = (rw.bits.rptr >> fpp_order) - 1;
...
page_ref_add(page, 1 << fpp_order);
...
writew(pn << fpp_order, geth->base + GLOBAL_SWFQ_RWPTR_REG + 2);
Take len = 1024 and rptr = 701. pn starts at 350 and the final wptr is
698. Both fragments of page 350 get a reference, but entry 700 sits
behind rptr and outside [rptr, wptr). The hardware is never offered it on
this lap.
On the next refill, page 350 still has page_ref_count() > 1, so the loop
breaks until the queue drains to 64 descriptors or fewer. That can let
the free queue fall to the swfq_empty threshold.
geth_freeq_alloc_map_page() then replaces the page but drops only one
reference. Does that leak the old page and its DMA mapping?
At the end of the series, the orphaned fragment seems to hold its
metadata slot, page and mapping until the next queue teardown.
The old setup fill also used rptr >> fpp_order, so this truncation
predates this patch.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings
2026-10-15 15:24 ` [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 (3) · Medium: 1 (1) · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] This patch fixes a real RX-path bug that dates back to the
original driver, but the commit message has no Fixes: tag.
- [Low] This is a netdev hardware-driver bug fix, but the commit message
does not say how the issue was found, how it was validated, or how the…
Pre-existing issues:
- [High] geth_cleanup_freeq() still runs 'while
(page_ref_count(gpage->page) > 0) put_page(gpage->page);'.
- [High] gmac_rx() never checks the descriptor-supplied fragment length
against the posted buffer.
- [High] Free-queue pages keep one DMA_FROM_DEVICE mapping for the
queue's whole lifetime.
- [Medium] When geth_fill_freeq() replaces a slot whose
page_ref_count(page) > 1, geth_freeq_alloc_map_page() calls put_page()…
--
net: ethernet: cortina: Correct free queue DMA mappings
This commit keeps the free queue page DMA base separate from the
per-fragment iterator. It removes the replacement-path unmap, which was
unmapping the new buffer, and unmaps the full PAGE_SIZE mapping on setup
unwind and teardown. It also replaces gmac_get_queue_page() with
geth_freeq_lookup(), which works out fragment offsets from the saved DMA
base under freeq_lock, and rejects mappings that do not fit in 32-bit
descriptors.
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
[Severity: Medium]
This looks like a fix for behaviour that has been there since the driver
was added, but there is no Fixes: tag. maintainer-netdev.rst says "for
fixes the Fixes: tag is required, regardless of the tree".
Should this carry something like:
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Other patches in the series do have Fixes: tags. That includes "net:
ethernet: cortina: Preserve in-flight free queue pages", which builds on
this one. Without a tag here, could stable tooling pick up the dependent
patches but not this one?
Could the commit message also describe the symptom users saw before the
fix?
Before this patch, gpage->mapping held base + PAGE_SIZE for pages mapped
at setup. For pages replaced from geth_fill_freeq() it held the new
page's base. The lookup always used:
gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE);
This only matches the setup case. For a replaced page at base X the key
becomes X + PAGE_SIZE, so one of two things happens in gmac_rx():
- it logs "could not find mapping" and drops the frame, or
- it matches a different page whose stored value happens to be
X + PAGE_SIZE, attaches that page to the skb and drops that page's
reference.
[Severity: Low]
The "Bug fixes" section of Documentation/process/maintainer-netdev.rst
says "All bug fixes must state how the issue was discovered and
validated, and how the fix was tested." The only trailers here are
Assisted-by: and Signed-off-by:, so they don't supply that information.
Later patches in the series include a statement such as "The series was
tested on a D-Link DIR-685...". Could a similar note on discovery and
testing be added here? This patch changes DMA map/unmap sizes,
descriptor programming and the RX lookup path.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 7ebd62f8a3a46..006362a033952 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
[ ... ]
> @@ -820,9 +832,17 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
> if (!page)
> return NULL;
>
> - mapping = dma_map_single(geth->dev, page_address(page),
> - PAGE_SIZE, DMA_FROM_DEVICE);
> - if (dma_mapping_error(geth->dev, mapping)) {
> + page_mapping = dma_map_single(geth->dev, page_address(page),
> + PAGE_SIZE, DMA_FROM_DEVICE);
[Severity: High]
This is a pre-existing issue, but free queue pages keep this one
DMA_FROM_DEVICE mapping for the whole life of the queue. When the stack
releases a page, geth_fill_freeq() gives it back to the hardware with
only:
/* Add one reference per fragment in the page */
page_ref_add(page, 1 << fpp_order);
plus the SWFQ write pointer update. There is no
dma_sync_single_for_device() call.
gmac_rx() also passes fragments to the stack without calling
dma_sync_single_range_for_cpu().
The FA526 is not cache-coherent. Can cache lines filled while the stack
read an earlier frame survive the reuse? If so, the CPU would see stale
data for the next frame DMA'd into the same page.
This seems to be handled later in the series:
- "net: ethernet: cortina: Preserve in-flight free queue pages" adds
dma_sync_single_range_for_cpu() in geth_freeq_claim().
- "net: ethernet: cortina: Recycle claimed free queue pages" adds
dma_sync_single_for_device() in geth_freeq_post_page().
> + if (dma_mapping_error(geth->dev, page_mapping)) {
[ ... ]
> @@ -835,31 +855,25 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
> */
> frag_len = 1 << geth->freeq_frag_order; /* Usually 2048 */
> fpp_order = PAGE_SHIFT - geth->freeq_frag_order;
> + gpage = &geth->freeq_pages[pn];
> + if (gpage->page)
> + put_page(gpage->page);
> +
> + /* Then put our new mapping into the page table */
> + gpage->mapping = page_mapping;
> + gpage->page = page;
[Severity: Medium]
This isn't a bug introduced by this patch. When geth_fill_freeq()
replaces a slot because page_ref_count(page) > 1, the old page's
PAGE_SIZE DMA_FROM_DEVICE mapping is overwritten here and not recorded
anywhere else.
Is that mapping ever unmapped? geth_cleanup_freeq() only unmaps the
current gpage->mapping.
RX descriptors that still point into the old page also can't be found by
geth_freeq_lookup(), because it only searches the current freeq_pages[]
entries. In gmac_rx() that ends up in:
page = geth_freeq_lookup(geth, mapping, &page_offs);
if (!page) {
...
goto err_drop;
}
err_drop runs with page == NULL here. Does that drop the frame and leak
the fragment's reference on the old page?
The commit message defers this ("The replaced mapping cannot be released
until its in-flight fragments have been claimed, which is handled with
their lifetime later in the series"). Two later commits appear to fix
it:
- "net: ethernet: cortina: Preserve in-flight free queue pages" adds
geth_freeq_claim() with per-fragment xarray tracking.
- "net: ethernet: cortina: Recycle claimed free queue pages" adds
dma_unmap_single_attrs() in geth_freeq_recycle_page().
[ ... ]
> @@ -1023,12 +1034,10 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth)
>
> for (pn = 0; pn < pages; pn++) {
> struct gmac_queue_page *gpage;
> - dma_addr_t mapping;
> -
> - mapping = geth->freeq_ring[pn << fpp_order].word2.buf_adr;
> - dma_unmap_single(geth->dev, mapping, frag_len, DMA_FROM_DEVICE);
>
> gpage = &geth->freeq_pages[pn];
> + dma_unmap_single(geth->dev, gpage->mapping, PAGE_SIZE,
> + DMA_FROM_DEVICE);
> while (page_ref_count(gpage->page) > 0)
> put_page(gpage->page);
[Severity: High]
This is a pre-existing issue, but can this loop drop references that
belong to skbs already passed to the stack?
gmac_rx() attaches fragments with skb_fill_page_desc() and hands them
off with napi_gro_frags(). Those skbs may still be in socket queues when
the free queue is torn down.
Draining page_ref_count() to zero would free the page under them. After
the page is freed and possibly reallocated, the next page_ref_count()
read could also lead to dropping a reference that belongs to the new
owner.
Later in the series, "net: ethernet: cortina: Preserve in-flight free
queue pages" gives each posted fragment its own reference. With that
change, geth_freeq_release_pages() only puts gpage->fragments + 1
references.
> }
[ ... ]
> @@ -1543,14 +1549,12 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
> goto err_drop;
> }
>
> - /* Freeq pointers are one page off */
> - gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE);
> - if (!gpage) {
> + page = geth_freeq_lookup(geth, mapping, &page_offs);
> + if (!page) {
> dev_err_ratelimited(geth->dev,
> "could not find mapping\n");
> goto err_drop;
> }
> - page = gpage->page;
>
> if (word3.bits32 & SOF_BIT) {
> skb = gmac_skb_if_good_frame(port, word0, frame_len);
[Severity: High]
This is a pre-existing issue. geth_freeq_lookup() only checks the
fragment's starting offset, and gmac_rx() never checks the length from
the descriptor against the posted buffer:
page_offs += NET_IP_ALIGN;
frag_len -= NET_IP_ALIGN;
...
if (word3.bits32 & EOF_BIT)
frag_len = frame_len - skb->len;
...
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
If the hardware reports an inconsistent buffer_size or byte_count, can
frag_len underflow or exceed the 2 KiB fragment? The skb fragment would
then describe memory past the buffer or past the end of the page.
The later commit "net: ethernet: cortina: Validate RX fragment lengths"
adds this validation:
- it rejects frag_len < NET_IP_ALIGN and frame_len < skb->len
- it limits frag_len to what fits in the fragment and the page
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages
2026-10-15 15:24 ` [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] geth_setup_freeq() sets the software write pointer to the
hardware read pointer kept from the previous queue, using…
- [Low] geth_setup_freeq() allocates geth->freeq_pages with
kzalloc_objs() sized by page_slots.
Pre-existing issues:
- [Low] The kernel-doc for geth_fill_freeq() says 'Usually the interrupt
to refill the queue happens when the queue is half empty.' The SWFQ…
--
net: ethernet: cortina: Preserve in-flight free queue pages
This patch allocates free queue page metadata slots independently of free
queue positions. Occupied slots are tracked with a bitmap, and each fragment
DMA address stays in the XArray until its RX descriptor is claimed. The patch
also sizes the metadata pool for the free queue plus both RX rings, and
requires the initial fill to populate every usable entry.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index c1c42e1f93f42..faab1bb865ab4 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
[ ... ]
> @@ -910,20 +952,20 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth,
[ ... ]
> /**
> * geth_fill_freeq() - Fill the freeq with empty fragments to use
> * @geth: the ethernet adapter
> - * @refill: whether to reset the queue by filling in all freeq entries or
> - * just refill it, usually the interrupt to refill the queue happens when
> - * the queue is half empty.
> + *
> + * Usually the interrupt to refill the queue happens when the queue is half
> + * empty.
> */
[Severity: Low]
This problem was not introduced by this patch. The "half empty" wording comes
from the old kernel-doc, and it doesn't seem to match the code.
geth_setup_freeq() programs the SWFQ empty interrupt with:
qt.bits.swfq_empty = 32;
gemini.h documents that field as the "Software Free Queue Empty Threshold".
Only the NAPI refill in gmac_napi_poll() uses a half-queue threshold:
freeq_threshold = 1 << (geth->freeq_order - 1);
gemini_port_irq_thread() has the same "half empty" comment. Could both
comments be fixed while this kernel-doc is being rewritten?
[ ... ]
> @@ -1003,27 +1106,22 @@ static int geth_setup_freeq(struct gemini_ethernet *geth)
> }
>
> /* Allocate page metadata referenced by the mapping index */
> - geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, pages);
> + geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, page_slots);
[Severity: Low]
Could this allocation become hard to satisfy at larger ring sizes?
page_slots now includes both RX ring sizes, added in
geth_freeq_page_slots():
if (geth->port0 && geth->port0->netdev)
slots += 1 << geth->port0->rxq_order;
if (geth->port1 && geth->port1->netdev)
slots += 1 << geth->port1->rxq_order;
struct gmac_queue_page has also grown to 12 bytes on 32-bit Gemini.
At the ethtool maximum (rxq_order 15 on both ports, freeq_order 15) this is
16384 + 32768 + 32768 = 81920 entries. That is about 960 KiB of physically
contiguous kmalloc memory (order 8). Before this patch it was 128 KiB.
With rxq_order 11 on both ports it needs 96 KiB (order 5). The old code
needed 32 KiB.
If the allocation fails under fragmentation, geth_resize_freeq() fails, and
gmac_open() or the ethtool -G request returns -ENOMEM. Only the CPU accesses
this array, so would kvcalloc() and kvfree() be a better fit?
> if (!geth->freeq_pages)
> goto err_freeq;
> - geth->num_freeq_pages = pages;
> -
[ ... ]
> + geth->freeq_page_bitmap = bitmap_zalloc(page_slots, GFP_KERNEL);
> + if (!geth->freeq_page_bitmap)
> + goto err_freeq_pages;
> + geth->num_freeq_pages = page_slots;
>
> /* Match the software pointer to the retained hardware pointer. */
> rptr = GET_RPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG);
> SET_WPTR(geth->base + GLOBAL_SWFQ_RWPTR_REG, rptr & (len - 1));
>
> - filled = geth_fill_freeq(geth, false);
> - if (!filled)
> - goto err_freeq_alloc;
> + expected = len - (1 << fpp_order);
> + filled = geth_fill_freeq(geth);
> + if (filled != expected)
> + goto err_freeq_bitmap;
[Severity: Medium]
Can the first page posted here straddle the hardware read pointer?
rptr is the read-only hardware SWFQ read pointer left over from the previous
queue. geth_cleanup_freeq() only sets wptr = rptr, and geth_resize_freeq()
keeps rptr through:
new_order = max_t(unsigned int, new_order, fls(rptr));
If the hardware consumed an odd number of 2K fragments before the resize,
rptr is odd. geth_fill_freeq() then rounds the write pointer down to a page
index:
pn = (rw.bits.wptr >> fpp_order) & m_pn;
geth_freeq_add_page() writes both fragments at freeq_ring + (pn << fpp_order).
So fragment 0 ends up in entry rptr - 1, behind the hardware read pointer.
For example, take len = 16 and rptr = 5:
- Page A goes into entries 4 and 5, and the fill ends at wptr = 2.
- The hardware reads entries 5..15, then 0 and 1.
- A later refill at rptr >= 8 writes page B into entries 4 and 5.
- The fragment 0 address of page A never comes back in an RX descriptor.
geth_freeq_claim() is the only path that releases a slot, and only when
gpage->fragments drops to zero. Does page A then keep its XArray entry, DMA
mapping, page references and freeq_page_bitmap bit until the queue is torn
down?
The old position-indexed code dropped the stale page when that position was
refilled, so this looks new with this patch. The later recycle patch in the
series only recycles pages whose fragments count reaches 0, so the problem
seems to remain at the end of the series.
The check above also counts the fragment that the hardware cannot see:
expected = len - (1 << fpp_order);
So it passes in this case. Is this sentence in the commit message accurate
when rptr is not page aligned?
Require the initial fill to populate every usable
queue entry.
The impact looks bounded to one page and one slot per free queue lifetime.
Later refills always write page-aligned wptr values, and each setup gets a
fresh bitmap. It does use up one slot of the headroom computed by
geth_freeq_page_slots().
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages
2026-10-15 15:24 ` [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The stable dependency on this patch is not recorded.
--
net: ethernet: cortina: Recycle claimed free queue pages
This patch keeps the base page reference and streaming DMA mapping of a
free queue page after all its fragments have been claimed, and records
such slots in a new recycle bitmap. During refill, a recycled page is
reposted if its refcount is back to one. Otherwise it is unmapped and
released before a replacement is allocated.
> net: ethernet: cortina: Recycle claimed free queue pages
>
> The preceding "Preserve in-flight free queue pages" fix releases each
> mapping after its last descriptor is claimed. Refilling therefore
> allocates and maps a fresh page at line rate even after the network stack
> has released all references to an old page.
[Severity: Low]
Should this patch be tied to the earlier fix for stable?
The earlier patch in this series, "net: ethernet: cortina: Preserve
in-flight free queue pages", carries:
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
so it is a stable candidate. Its commit message also says:
A later patch restores recycling independently of queue position.
This patch is that later patch, and the paragraph quoted above describes
the earlier fix's behaviour as a problem. This patch has no Fixes: tag,
no Cc: stable and no stable dependency note.
Mainline is not affected because both patches land together. Suppose a
stable backport takes only the Fixes-tagged patch, plus the untagged
prerequisites it needs to apply, such as "Index free queue fragments
with XArray". Wouldn't geth_fill_freeq() then do one alloc_page(GFP_ATOMIC)
and one full-page dma_map_single() for every refilled page, with nothing
pointing out that recycling is missing?
Would it make sense to give this patch the same Fixes: tag? The
alternative would be a stable dependency note on the earlier patch that
points to this one.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths
2026-10-15 15:24 ` [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The commit "net: ethernet: cortina: Validate RX fragment lengths"
fixes real RX descriptor defects that have been in gmac_rx() since the…
- [Low] The commit message says the patch will "Derive the final fragment
length from the total frame byte count rather than treating the…
--
net: ethernet: cortina: Validate RX fragment lengths
This patch adds bounds checks on RX descriptor lengths in gmac_rx(). It
rejects a short initial fragment before NET_IP_ALIGN is applied, frame
length underflow on EOF, zero-length non-final descriptors, and fragments
that run past the DMA fragment or the page. An empty EOF descriptor now
completes the skb that was already assembled, and the buffer_size comment
in gemini.h is corrected.
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
[Severity: Low]
Should this patch have a Fixes: tag? The defects it closes in gmac_rx()
look like they go back to when the driver was added:
- frag_len -= NET_IP_ALIGN with no lower bound check
- frag_len = frame_len - skb->len on EOF with no underflow check, so a
wrapped length can reach skb_fill_page_desc()
- a zero-length fragment that is logged but still appended
Other patches in this series that fix bugs of the same age carry this tag.
Examples are "Keep shared free queue parent-owned", "Drain free queue IRQ
before resize" and "Preserve in-flight free queue pages":
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Without the tag, stable backporters may not pick up the underflow fix.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index e42eeefd4d195..70aa418d70370 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
[ ... ]
> @@ -1827,15 +1831,33 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget,
> goto err_drop;
> }
>
> - if (word3.bits32 & EOF_BIT)
> + if (word3.bits32 & EOF_BIT) {
> + if (frame_len < ETH_ZLEN ||
> + frame_len < skb->len)
> + goto err_drop;
> frag_len = frame_len - skb->len;
> + }
[Severity: Low]
The commit message says:
Derive the final fragment length from the total frame byte count
rather than treating the descriptor buffer size as an EOF payload length.
Does that match the code? Before this patch, gmac_rx() already derived
the EOF fragment length from byte_count:
if (word3.bits32 & EOF_BIT)
frag_len = frame_len - skb->len;
In this hunk the assignment is an unchanged context line. The only new
code here is the frame_len < ETH_ZLEN || frame_len < skb->len guard.
Could this sentence be reworded so it doesn't suggest that the old code
used buffer_size as the EOF payload length?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize
2026-10-15 15:24 ` [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The commit "net: ethernet: cortina: Account RX buffer size in
truesize" fixes a long-standing bug but has no Fixes: trailer.
--
net: ethernet: cortina: Account RX buffer size in truesize
This commit changes gmac_rx() in drivers/net/ethernet/cortina/gemini.c so
that each attached RX page fragment adds the full posted free queue buffer
size (freeq_frag_len) to skb->truesize. Previously it added only the
received payload length (frag_len).
> net: ethernet: cortina: Account RX buffer size in truesize
>
> gmac_rx() charges only the received payload length for each page fragment
> attached to an skb. Each fragment pins a complete free queue DMA buffer, so
> short packets understate their memory use to GRO and socket accounting.
[ ... ]
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
[Severity: Low]
The message describes the old truesize undercount as an issue being fixed.
Should this commit carry a Fixes: tag?
The line being replaced in gmac_rx() looks like it came in unchanged with
the original driver:
skb->truesize += frag_len;
Git blame on the parent commit attributes it to:
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
The other patches in this series that fix bugs from the same commit
("Keep shared free queue parent-owned", "Drain free queue IRQ before
resize" and "Preserve in-flight free queue pages") all carry
Fixes: 4d5ae32f5e1e, but this one does not.
Without the tag, stable tooling may not pick this fix up along with the
others.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop
2026-10-15 15:24 ` [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit 'net: ethernet: cortina: Release partial RX frames
on stop' fixes a regression introduced by 06937db21ee3 ("net:…
- [Low] The commit message describes the bug only as "leaking every page
already attached to the frame".
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but gmac_stop()
calls hrtimer_cancel(&port->rx_coalesce_timer) (line 2229) as its…
--
net: ethernet: cortina: Release partial RX frames on stop
This patch makes gmac_stop() free a partially assembled RX skb with
napi_free_frags() before it clears port->rx_skb. The discarded frame is
counted as rx_dropped under rx_stats_syncp, using the irqsave update
helpers.
> A NAPI poll can retain a partially assembled frame across invocations. The
> stop path clears that pointer without releasing its fragment references,
> leaking every page already attached to the frame.
[Severity: Low]
Is "leaking every page" an accurate description of the unfixed behaviour?
gmac_stop() only clears port->rx_skb, so napi->skb still holds the partial
skb. After a reopen, the first good SOF gets that same skb back:
gmac_rx()
gmac_skb_if_good_frame()
skb = napi_get_frags(&port->napi); <- stale napi->skb
gmac_rx() then resets frag_nr to 0 and runs:
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
skb->len += frag_len;
skb->data_len += frag_len;
This overwrites frags[0] and sets nr_frags to 1, so the old page
references are lost. Meanwhile len and data_len keep growing from their
stale values.
At EOF, this uses the stale length:
frag_len = frame_len - skb->len;
When frame_len is at least the stale len, the skb reaches napi_gro_frags()
with a data_len larger than the bytes actually in its frags. When the two
are equal, it carries the old frame's payload.
When frame_len is smaller, err_drop frees the skb with napi_free_frags()
and nothing leaks. If the device is removed instead of reopened,
free_netdev()->__netif_napi_del_locked() also frees napi->skb.
Could the commit message say that a malformed skb could reach GRO, and not
only that pages leak?
[ ... ]
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
[Severity: Medium]
Should this have a Fixes: tag? Commit 06937db21ee3 ("net: ethernet:
cortina: Make RX SKB per-port") added port->rx_skb = NULL to gmac_stop()
without a call to napi_free_frags().
Before that commit, the static assembly skb survived a stop/open cycle,
and the next SOF in gmac_rx() called napi_free_frags() on it. Since that
commit, a partial frame left over at stop stays in napi->skb, and
napi_disable() does not touch it.
06937db21ee3 has its own Fixes: 4d5ae32f5e1e tag, so it is probably in
stable trees. The other fixes in this series also carry Fixes: tags.
Perhaps:
Fixes: 06937db21ee3 ("net: ethernet: cortina: Make RX SKB per-port")
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index d4e90aa97d220..843423187f8da 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -2224,12 +2224,19 @@ static int gmac_open(struct net_device *netdev)
> static int gmac_stop(struct net_device *netdev)
> {
> struct gemini_ethernet_port *port = netdev_priv(netdev);
> + unsigned long flags;
>
> hrtimer_cancel(&port->rx_coalesce_timer);
[Severity: High]
This is a pre-existing issue and was not introduced by this patch. Can
rx_coalesce_timer be re-armed after this cancel? The RX interrupt is still
unmasked at this point. It is only masked later, by
gmac_enable_irq(netdev, 0) and free_irq().
An RX interrupt can land anywhere in that window, including while
napi_disable() sleeps. gmac_irq() then does this, with no check for a
stopping device:
if (val & (DEFAULT_Q0_INT_BIT << netdev->dev_id)) {
gmac_enable_rx_irq(netdev, 0);
if (!port->rx_coalesce_nsecs) {
napi_schedule(&port->napi);
} else {
...
hrtimer_start(&port->rx_coalesce_timer, ktime,
HRTIMER_MODE_REL);
}
}
gmac_set_coalesce() sets rx_coalesce_nsecs to ethtool rx_coalesce_usecs *
1000 with no upper bound. The re-armed timer can therefore fire up to
about 4.29 seconds after gmac_stop() returns.
If the interface is reopened before then, gmac_open() calls
hrtimer_setup() on a timer that is still queued in the clock base. Would
that corrupt the hrtimer rbtree?
If the netdev is unregistered and freed instead, would
gmac_coalesce_delay_expired() then run container_of() on freed port
memory and call napi_schedule() on it?
This ordering goes back to 4d5ae32f5e1e, and the rest of the series still
calls hrtimer_cancel() first in gmac_stop(). Could the hrtimer be
cancelled after the RX interrupt is masked or after free_irq(), or be
cancelled a second time there?
> netif_tx_stop_all_queues(netdev);
> gmac_disable_tx_rx(netdev);
> gmac_stop_dma(port);
> napi_disable(&port->napi);
> + if (port->rx_skb) {
> + napi_free_frags(&port->napi);
> + flags = u64_stats_update_begin_irqsave(&port->rx_stats_syncp);
> + port->stats.rx_dropped++;
> + u64_stats_update_end_irqrestore(&port->rx_stats_syncp, flags);
> + }
> port->rx_skb = NULL;
> port->rx_frag_nr = 0;
> port->rx_dropping = false;
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates
2026-10-15 15:24 ` [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates Linus Walleij
@ 2026-10-19 17:28 ` netdev-bot+sashiko
0 siblings, 0 replies; 24+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 17:28 UTC (permalink / raw)
To: linusw
Cc: ulli.kroll, andrew+netdev, davem, kuba, pabeni, mirq-linux,
mhun512, edumazet, netdev
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch fixes a real bug but its commit message has no Fixes:
trailer.
Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but
rx_stats_syncp (and tx_stats_syncp and ir_stats_syncp) in struct…
--
net: ethernet: cortina: Scope RX statistics updates
This commit narrows the rx_stats_syncp writer sections in the Gemini
ethernet driver so they cover only the counter updates. Before, the
section in gmac_napi_poll() wrapped all of gmac_rx(). On 32-bit
PREEMPT_RT, u64_stats_update_begin() disables preemption, so the old
scope took spinlock_t locks with preemption disabled.
> On 32-bit PREEMPT_RT systems u64_stats_update_begin() disables
> preemption. The RX poll currently holds that writer section across
> gmac_rx(), which takes spinlock_t locks and calls into GRO and fragment
> handling. spinlock_t can sleep on PREEMPT_RT and must not be acquired
> with preemption disabled.
[ ... ]
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
[Severity: Low]
Should this have a Fixes: tag?
The writer section in gmac_napi_poll() that spans gmac_rx() and
geth_fill_freeq() looks like it has been there since the driver was
added:
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Other patches in this series carry that tag, for example "net: ethernet:
cortina: Keep shared free queue parent-owned", "net: ethernet: cortina:
Drain free queue IRQ before resize" and "net: ethernet: cortina: Preserve
in-flight free queue pages".
arch/arm/Kconfig selects ARCH_SUPPORTS_RT. Without the tag, this fix may
not reach stable ARM PREEMPT_RT kernels.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 0c6c7c938b2eb..7ecc8db9b7d3d 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -1720,12 +1720,12 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port,
> unsigned int rx_status = word0.bits.status;
> struct sk_buff *skb = NULL;
>
> - port->rx_stats[rx_status]++;
> - port->rx_csum_stats[rx_csum]++;
> -
> if (word0.bits.derr || word0.bits.perr ||
> rx_status || frame_len < ETH_ZLEN ||
> rx_csum >= RX_CHKSUM_IP_ERR_UNKNOWN) {
> + u64_stats_update_begin(&port->rx_stats_syncp);
[Severity: Medium]
This isn't a bug introduced by this patch, but is rx_stats_syncp ever
initialized with u64_stats_init()?
I couldn't find any u64_stats_init() calls in
drivers/net/ethernet/cortina/ for rx_stats_syncp, tx_stats_syncp or
ir_stats_syncp.
The port lives in the netdev private area allocated in
gemini_ethernet_port_probe():
netdev = devm_alloc_etherdev_mqs(dev, sizeof(*port), TX_QUEUE_NUM, TX_QUEUE_NUM);
On 32-bit kernels with CONFIG_DEBUG_LOCK_ALLOC, the seqcount's dep_map
therefore has no key. The first writer goes through:
gmac_skb_if_good_frame()
u64_stats_update_begin()
write_seqcount_begin()
do_write_seqcount_begin_nested()
seqcount_acquire(&s->dep_map, ...)
...
assign_lock_key()
The object is neither static nor per-CPU. Will assign_lock_key() then
print "INFO: trying to register non-static key" and call
debug_locks_off(), which turns off lockdep for the whole system?
Non-debug builds are not affected, because the zeroed sequence starts at
a valid 0.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015-gemini-ethernet-fixes-3-v5-0-b5c9e2f7b8a3%40kernel.org
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-10-19 17:28 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 15:24 [PATCH net-next v5 00/14] net: ethernet: cortina: Fix Gemini RX buffer management Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 01/14] net: ethernet: cortina: Keep shared free queue parent-owned Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 02/14] net: ethernet: cortina: Keep PHY-less port bound for shared IRQ Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 03/14] net: ethernet: cortina: Drain free queue IRQ before resize Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 04/14] net: ethernet: cortina: Normalize free queue pointers on rebuild Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 05/14] net: ethernet: cortina: Correct free queue DMA mappings Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 06/14] net: ethernet: cortina: Index free queue fragments with XArray Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 07/14] net: ethernet: cortina: Preserve in-flight free queue pages Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 08/14] net: ethernet: cortina: Rotate free queue page allocation Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 09/14] net: ethernet: cortina: Recycle claimed free queue pages Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 10/14] net: ethernet: cortina: Validate RX fragment lengths Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 11/14] net: ethernet: cortina: Account RX buffer size in truesize Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 12/14] net: ethernet: cortina: Release partial RX frames on stop Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
2026-10-15 15:24 ` [PATCH net-next v5 13/14] net: ethernet: cortina: Scale Gemini RX queues to system memory Linus Walleij
2026-10-15 15:24 ` [PATCH net-next v5 14/14] net: ethernet: cortina: Scope RX statistics updates Linus Walleij
2026-10-19 17:28 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox