* [PATCH] net: gemini: Free the shared queue after removing the ports
@ 2026-09-17 19:28 Myeonghun Pak
2026-09-18 2:25 ` Andrew Lunn
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Myeonghun Pak @ 2026-09-17 19:28 UTC (permalink / raw)
To: Linus Walleij, Hans Ulli Kroll
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, netdev, linux-kernel, Ijae Kim,
Myeonghun Pak
The software free queue is shared by both Ethernet ports, but each port
remove callback frees it. The parent also frees the queue before its
managed child devices are removed. This can free the queue while a port
or its threaded free-queue IRQ handler still uses it, and free the same
allocation again when the children are removed.
Keep the queue owned by the parent. Explicitly depopulate its child
devices before freeing it, so both ports are unregistered and their
managed IRQ handlers have been released. Disable the free-queue
interrupt after those handlers have drained, since the threaded handler
can re-enable it. Only clean up the queue if it was allocated; it may
never have been set up if neither interface was opened.
This issue was identified during our ongoing static-analysis research
while reviewing kernel code.
Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
Assisted-by: LLM
Co-developed-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
---
drivers/net/ethernet/cortina/gemini.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 4c762229ce42..e493d495f2ef 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -2364,7 +2364,6 @@ static void gemini_port_remove(struct gemini_ethernet_port *port)
unregister_netdev(port->netdev);
}
clk_disable_unprepare(port->pclk);
- geth_cleanup_freeq(port->geth);
}
static void gemini_ethernet_init(struct gemini_ethernet *geth)
@@ -2656,7 +2655,10 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
{
struct gemini_ethernet *geth = platform_get_drvdata(pdev);
- geth_cleanup_freeq(geth);
+ devm_of_platform_depopulate(&pdev->dev);
+ writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
+ if (geth->freeq_ring)
+ geth_cleanup_freeq(geth);
geth->initialized = false;
}
--
2.47.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] net: gemini: Free the shared queue after removing the ports
2026-09-17 19:28 [PATCH] net: gemini: Free the shared queue after removing the ports Myeonghun Pak
@ 2026-09-18 2:25 ` Andrew Lunn
2026-09-20 0:52 ` Myeonghun Pak
2026-09-20 19:10 ` Linus Walleij
2026-09-21 19:44 ` netdev-bot+sashiko
2 siblings, 1 reply; 5+ messages in thread
From: Andrew Lunn @ 2026-09-18 2:25 UTC (permalink / raw)
To: Myeonghun Pak
Cc: Linus Walleij, Hans Ulli Kroll, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-arm-kernel,
netdev, linux-kernel, Ijae Kim
> Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
> Assisted-by: LLM
> Co-developed-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
> ---
> drivers/net/ethernet/cortina/gemini.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 4c762229ce42..e493d495f2ef 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -2364,7 +2364,6 @@ static void gemini_port_remove(struct gemini_ethernet_port *port)
> unregister_netdev(port->netdev);
> }
> clk_disable_unprepare(port->pclk);
> - geth_cleanup_freeq(port->geth);
> }
>
> static void gemini_ethernet_init(struct gemini_ethernet *geth)
> @@ -2656,7 +2655,10 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
> {
> struct gemini_ethernet *geth = platform_get_drvdata(pdev);
>
> - geth_cleanup_freeq(geth);
> + devm_of_platform_depopulate(&pdev->dev);
This seems wrong. If you have to call devm_of_platform_depopulate()
why are you using devm?
> + writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
> + if (geth->freeq_ring)
> + geth_cleanup_freeq(geth);
gemini_ethernet_remove() is supposed to do the opposite of
gemini_ethernet_probe(). What is the opposite of geth_cleanup_freeq()?
Where is it called in gemini_ethernet_probe()?
Andrew
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] net: gemini: Free the shared queue after removing the ports
2026-09-18 2:25 ` Andrew Lunn
@ 2026-09-20 0:52 ` Myeonghun Pak
0 siblings, 0 replies; 5+ messages in thread
From: Myeonghun Pak @ 2026-09-20 0:52 UTC (permalink / raw)
To: Andrew Lunn
Cc: Linus Walleij, Hans Ulli Kroll, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-arm-kernel,
netdev, linux-kernel, Ijae Kim
Thanks for the review. You are right on both points. I will keep
devm_of_platform_populate() and let the parent's devres release the free
queue instead, and drop the teardown from gemini_ethernet_remove(). I
will send a v2 with that.
2026년 9월 17일 (목) 오후 10:25, Andrew Lunn <andrew@lunn.ch>님이 작성:
>
> > Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
> > Assisted-by: LLM
> > Co-developed-by: Ijae Kim <ae878000@gmail.com>
> > Signed-off-by: Ijae Kim <ae878000@gmail.com>
> > Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
> > ---
> > drivers/net/ethernet/cortina/gemini.c | 6 ++++--
> > 1 file changed, 4 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> > index 4c762229ce42..e493d495f2ef 100644
> > --- a/drivers/net/ethernet/cortina/gemini.c
> > +++ b/drivers/net/ethernet/cortina/gemini.c
> > @@ -2364,7 +2364,6 @@ static void gemini_port_remove(struct gemini_ethernet_port *port)
> > unregister_netdev(port->netdev);
> > }
> > clk_disable_unprepare(port->pclk);
> > - geth_cleanup_freeq(port->geth);
> > }
> >
> > static void gemini_ethernet_init(struct gemini_ethernet *geth)
> > @@ -2656,7 +2655,10 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
> > {
> > struct gemini_ethernet *geth = platform_get_drvdata(pdev);
> >
> > - geth_cleanup_freeq(geth);
> > + devm_of_platform_depopulate(&pdev->dev);
>
> This seems wrong. If you have to call devm_of_platform_depopulate()
> why are you using devm?
>
> > + writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
> > + if (geth->freeq_ring)
> > + geth_cleanup_freeq(geth);
>
> gemini_ethernet_remove() is supposed to do the opposite of
> gemini_ethernet_probe(). What is the opposite of geth_cleanup_freeq()?
> Where is it called in gemini_ethernet_probe()?
>
> Andrew
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] net: gemini: Free the shared queue after removing the ports
2026-09-17 19:28 [PATCH] net: gemini: Free the shared queue after removing the ports Myeonghun Pak
2026-09-18 2:25 ` Andrew Lunn
@ 2026-09-20 19:10 ` Linus Walleij
2026-09-21 19:44 ` netdev-bot+sashiko
2 siblings, 0 replies; 5+ messages in thread
From: Linus Walleij @ 2026-09-20 19:10 UTC (permalink / raw)
To: Myeonghun Pak
Cc: Hans Ulli Kroll, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-arm-kernel, netdev,
linux-kernel, Ijae Kim
On Thu, Sep 17, 2026 at 9:28 PM Myeonghun Pak <mhun512@gmail.com> wrote:
> The software free queue is shared by both Ethernet ports, but each port
> remove callback frees it. The parent also frees the queue before its
> managed child devices are removed. This can free the queue while a port
> or its threaded free-queue IRQ handler still uses it, and free the same
> allocation again when the children are removed.
>
> Keep the queue owned by the parent. Explicitly depopulate its child
> devices before freeing it, so both ports are unregistered and their
> managed IRQ handlers have been released. Disable the free-queue
> interrupt after those handlers have drained, since the threaded handler
> can re-enable it. Only clean up the queue if it was allocated; it may
> never have been set up if neither interface was opened.
>
> This issue was identified during our ongoing static-analysis research
> while reviewing kernel code.
>
> Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
> Assisted-by: LLM
> Co-developed-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
A good patch, but there is a better solution!
Replace the crazy freeq queue with a private
page pool.
I have been cooking a patch, let me test it on
hardware and send it out.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] net: gemini: Free the shared queue after removing the ports
2026-09-17 19:28 [PATCH] net: gemini: Free the shared queue after removing the ports Myeonghun Pak
2026-09-18 2:25 ` Andrew Lunn
2026-09-20 19:10 ` Linus Walleij
@ 2026-09-21 19:44 ` netdev-bot+sashiko
2 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:44 UTC (permalink / raw)
To: mhun512
Cc: linusw, ulli.kroll, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-arm-kernel, netdev, linux-kernel, ae878000
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 (4) · 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] The commit message states the goal of keeping the shared free
queue from being "free[d] ... while a port or its threaded free-queue…
Pre-existing issues:
- [High] Stale free-queue state after a failed re-allocation:
`geth_cleanup_freeq()`…
- [High] `gemini_port_remove()`
(drivers/net/ethernet/cortina/gemini.c:2377-2384) calls…
- [High] `geth_cleanup_freeq()` force-drops every reference on the RX
pages: `while (page_ref_count(gpage->page) > 0)…
- [High] `gemini_port_remove()`
(drivers/net/ethernet/cortina/gemini.c:2377-2384) never clears the…
--
net: gemini: Free the shared queue after removing the ports
This moves ownership of the shared software free queue to the parent
device: the per-port remove callback no longer calls geth_cleanup_freeq(),
and gemini_ethernet_remove() depopulates its children first, masks the
free-queue interrupt, and only frees the queue if freeq_ring is set.
A few questions about the surrounding teardown paths below.
> diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
> index 2dd2fa801829c..498285caebe3d 100644
> --- a/drivers/net/ethernet/cortina/gemini.c
> +++ b/drivers/net/ethernet/cortina/gemini.c
> @@ -2381,7 +2381,6 @@ static void gemini_port_remove(struct gemini_ethernet_port *port)
> unregister_netdev(port->netdev);
> }
> clk_disable_unprepare(port->pclk);
> - geth_cleanup_freeq(port->geth);
> }
[Severity: High]
This isn't a bug introduced by this patch, but since the new
devm_of_platform_depopulate() call makes the parent drive this path, is the
ordering in gemini_port_remove() safe when the interface is still up?
phy_disconnect() runs before unregister_netdev():
if (port->netdev) {
phy_disconnect(port->netdev->phydev);
unregister_netdev(port->netdev);
}
phy_disconnect() -> phy_detach() clears the back pointer:
drivers/net/phy/phy_device.c:phy_detach() {
...
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
...
}
If the device is still IFF_UP, unregister_netdev() ->
unregister_netdevice_many_notify() -> netif_close_many() calls ndo_stop,
which is gmac_stop():
phy_stop(netdev->phydev);
and phy_stop() dereferences it right away:
drivers/net/phy/phy.c:phy_stop() {
struct net_device *dev = phydev->attached_dev;
...
}
Can this oops on phy_stop(NULL) when a port is unbound (or the parent is
removed) while the interface is up? Would calling unregister_netdev()
before phy_disconnect() avoid it?
[Severity: High]
This is a pre-existing issue, but the new depopulate sequence widens the
window: gemini_port_remove() never clears geth->port0 / geth->port1 (or
geth->initialized). The port structure is netdev_priv() of a netdev
allocated on the child device with devm_alloc_etherdev_mqs(), so it is
released by the child's devres as soon as its remove() returns, while the
parent keeps the pointer.
geth_resize_freeq() then dereferences the sibling unconditionally:
if (netdev->dev_id == 0)
other_netdev = geth->port1->netdev;
else
other_netdev = geth->port0->netdev;
if (other_netdev && netif_running(other_netdev))
return -EBUSY;
...
if (other_netdev) {
other_port = netdev_priv(other_netdev);
new_size += 1 << (other_port->rxq_order + 1);
Does this read freed memory after unbinding one gemini-ethernet-port child
while the parent stays bound, and then bringing the surviving port up
(gmac_open() -> geth_resize_freeq()) or calling gmac_set_ringparam()? The
rxq_order value read from the freed structure also feeds new_order, which
sizes the dma_alloc_coherent() ring and the freeq_pages array. The same
half-removed state exists inside the new devm_of_platform_depopulate()
window, where one child's devres is already released while the other netdev
is still registered and openable. Should gemini_port_remove() also clear
the parent's port0/port1 pointer?
>
> static void gemini_ethernet_init(struct gemini_ethernet *geth)
[ ... ]
> @@ -2673,7 +2672,10 @@ static void gemini_ethernet_remove(struct platform_device *pdev)
> {
> struct gemini_ethernet *geth = platform_get_drvdata(pdev);
>
> - geth_cleanup_freeq(geth);
> + devm_of_platform_depopulate(&pdev->dev);
> + writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
> + if (geth->freeq_ring)
> + geth_cleanup_freeq(geth);
> geth->initialized = false;
> }
[Severity: High]
This isn't a bug introduced by this patch, but the new freeq_ring sentinel
relies on an invariant that the free path does not maintain.
geth_cleanup_freeq() frees both allocations and leaves the pointers stale:
kfree(geth->freeq_pages);
dma_free_coherent(geth->dev,
sizeof(*geth->freeq_ring) << geth->freeq_order,
geth->freeq_ring, geth->freeq_dma_base);
Only geth_setup_freeq()'s error path clears one of them:
kfree(geth->freeq_pages);
err_freeq:
dma_free_coherent(...);
geth->freeq_ring = NULL;
return -ENOMEM;
so after a failed re-allocation freeq_pages still points at the kfree()d
array and num_freeq_pages keeps the new count. geth_resize_freeq() then
re-arms the interrupt without looking at ret:
ret = geth_setup_freeq(geth);
...
en |= SWFQ_EMPTY_INT_BIT;
writel(en, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG);
and because freeq_order was already set to new_order before the failed
setup, every later call returns early:
if (geth->freeq_order == new_order)
return 0;
Does that leave the interface openable with no free queue while the
free-queue interrupt is armed, so gemini_port_irq_thread() ->
geth_fill_freeq() reads the freed array:
gpage = &geth->freeq_pages[pn];
page = gpage->page;
and geth_freeq_alloc_map_page() writes through the NULL freeq_ring? Would
it make sense for geth_cleanup_freeq() to clear freeq_ring, freeq_pages and
num_freeq_pages so the new "if (geth->freeq_ring)" test actually reflects
allocation state?
[Severity: High]
This is a pre-existing issue, but it is the cleanup the patch now calls
after unregistering the children. geth_cleanup_freeq() force-drops every
reference on the RX pages:
gpage = &geth->freeq_pages[pn];
while (page_ref_count(gpage->page) > 0)
put_page(gpage->page);
Those pages are handed to the stack as SKB fragments in gmac_rx():
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
and geth_fill_freeq() explicitly avoids reusing entries whose page is still
referenced elsewhere:
if (page_ref_count(page) > 1) {
Can this loop drop references owned by live SKBs, returning the page to the
allocator while an SKB queued in a socket or in GRO still maps it?
Unregistering the netdevs first drains NAPI, but does it end the lifetime of
SKBs already delivered to sockets?
[Severity: High]
The commit message says the goal is to avoid "free[ing] the queue while a
port or its threaded free-queue IRQ handler still uses it", but is that true
for the other free site?
geth_resize_freeq() performs the identical free, protected only by masking
the enable bit:
spin_lock_irqsave(&geth->irq_lock, flags);
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);
/* Drop the old queue */
if (geth->freeq_ring)
geth_cleanup_freeq(geth);
geth_cleanup_freeq() holds neither freeq_lock nor irq_lock and there is no
synchronize_irq(). Since gemini_port_irq() masks the same bit itself and
returns IRQ_WAKE_THREAD, an already-dispatched gemini_port_irq_thread() ->
geth_fill_freeq() can run concurrently, take only geth->freeq_lock (a lock
the cleanup path never takes), walk the freed freeq_ring[]/freeq_pages[],
and then re-enable the interrupt the resize path just masked.
The freeq threaded IRQ is registered at probe independent of netif state, so
can this race be hit on a sibling port's gmac_open() or on
gmac_set_ringparam() for a down port, leaving the bug the commit claims to
close still reachable? Should the resize path get the same drain treatment,
or should the queue free be serialized against freeq_lock?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917192835.58126-1-mhun512%40gmail.com
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-21 19:44 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 19:28 [PATCH] net: gemini: Free the shared queue after removing the ports Myeonghun Pak
2026-09-18 2:25 ` Andrew Lunn
2026-09-20 0:52 ` Myeonghun Pak
2026-09-20 19:10 ` Linus Walleij
2026-09-21 19:44 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®