Thread (3 messages) 3 messages, 3 authors, 12h ago

Re: [PATCH] net: hip04: quiesce tx coalesce timer before teardown

From: Simon Horman <horms@kernel.org>
Date: 2026-07-09 12:48:30
Also in: lkml, stable

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
net: hip04: quiesce tx coalesce timer before teardown

This patch fixes race conditions with the tx_coalesce_timer by properly
canceling it during device stop and remove. It also serializes device
restarts in the tx timeout handler using rtnl_lock().
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
index 18376bcc718a2..cb9b01c79ee6e 100644
--- a/drivers/net/ethernet/hisilicon/hip04_eth.c
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
[ ... ]
quoted hunk ↗ jump to hunk
@@ -761,6 +762,13 @@ static int hip04_mac_stop(struct net_device *ndev)
 
 	napi_disable(&priv->napi);
 	netif_stop_queue(ndev);
[Severity: High]
This is a pre-existing issue, but does netif_stop_queue() adequately protect
against concurrent transmission here?

Since netif_stop_queue() only changes the queue state flag without waiting for
an active ndo_start_xmit callback to finish (unlike netif_tx_disable()), could
this allow the subsequent hip04_tx_reclaim() call to corrupt the TX ring or
cause invalid DMA mappings if a transmission is actively running?
+
+	/* Cancel the TX-coalesce timer after the arming paths (xmit via the
+	 * queue, rx poll via NAPI) are disabled, so a pending tx_done()
+	 * (which dereferences priv) is drained before the device is freed.
+	 */
+	hrtimer_cancel(&priv->tx_coalesce_timer);
[Severity: High]
This is a pre-existing issue, but is it possible for the softirq to re-arm
the timer after hrtimer_cancel() is called?

In hip04_rx_poll(), the NAPI ownership is released via napi_complete_done()
before the timer is armed:

hip04_rx_poll() {
    ...
    napi_complete_done(napi, rx);
done:
    if (rx < budget && tx_remaining)
        hip04_start_tx_timer(priv);
}

If the teardown thread cancels the timer and frees the private structure
while the softirq is preempted just after napi_complete_done(), could this
result in a use-after-free when hip04_start_tx_timer() is eventually executed?
+
 	hip04_mac_disable(ndev);
 	hip04_tx_reclaim(ndev, true);
[Severity: High]
This is a pre-existing issue, but is there missing hardware synchronization
between disabling the MAC and reclaiming the TX ring?

hip04_mac_disable() disables the MAC using a relaxed MMIO write without
polling for an idle state.

Since the very next call is hip04_tx_reclaim() which unmaps buffers and
modifies descriptor memory, could this cause silent data corruption or IOMMU
page faults if the MAC is still actively processing packets in the background?

[ ... ]
quoted hunk ↗ jump to hunk
@@ -791,8 +799,15 @@ static void hip04_tx_timeout_task(struct work_struct *work)
 	struct hip04_priv *priv;
 
 	priv = container_of(work, struct hip04_priv, tx_timeout_task);
+
+	rtnl_lock();
+	if (!netif_running(priv->ndev))
+		goto out;
+
 	hip04_mac_stop(priv->ndev);
 	hip04_mac_open(priv->ndev);
[Severity: High]
This is a pre-existing issue, but what happens if hip04_mac_open() fails
during this restart?

If dma_map_single() fails inside hip04_mac_open(), it returns -EIO without
enabling NAPI. Because this return value is ignored here, the device remains
broken and NAPI is left disabled (with the SCHED bit set).

Will this cause a hard lockup later when hip04_mac_stop() spins forever in
napi_disable() waiting for the SCHED bit to clear?
+out:
+	rtnl_unlock();
 }
 
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1029,10 +1044,15 @@ static void hip04_remove(struct platform_device *pdev)
 	if (priv->phy)
 		phy_disconnect(priv->phy);
 
-	hip04_free_ring(ndev, d);
 	unregister_netdev(ndev);
[Severity: High]
This is a pre-existing issue, but does calling phy_disconnect() before
unregister_netdev() cause a use-after-free and WARN?

phy_disconnect() halts and detaches the PHY, dropping its reference count.
The subsequent call to unregister_netdev() invokes hip04_mac_stop(), which
unconditionally calls phy_stop().

Could calling phy_stop() on a detached and potentially freed PHY device
pointer cause a crash?
-	of_node_put(priv->phy_node);
 	cancel_work_sync(&priv->tx_timeout_task);
+	hrtimer_cancel(&priv->tx_coalesce_timer);
+	/* Free the rings only after the interface is stopped (.ndo_stop via
+	 * unregister_netdev) and the work/timer are drained; the TX/NAPI
+	 * paths touch them while the device is up.
+	 */
+	hip04_free_ring(ndev, d);
[Severity: High]
This is a pre-existing issue, but does hip04_free_ring() safely handle
allocation failures?

During probe, if allocating the TX descriptor ring fails, priv->tx_desc
remains NULL, and the error path calls hip04_free_ring().

Since hip04_free_ring() unconditionally calls dma_free_coherent() on
priv->tx_desc, could this cause a kernel panic on architectures that trap
virtual-to-physical translations on NULL?
+	of_node_put(priv->phy_node);
 	free_netdev(ndev);
[Severity: High]
This is a pre-existing issue, but does explicitly freeing the netdev here
create a use-after-free race with the interrupt handler?

The driver requests its interrupt in hip04_mac_probe() using
devm_request_irq(), meaning the IRQ is only freed during devres cleanup after
hip04_remove() completely finishes.

If an interrupt fires between free_netdev() and the devres cleanup,
hip04_mac_interrupt() will cast dev_id to the freed ndev pointer, which seems
like it would lead to a use-after-free crash.
 }
 
[ ... ]

Regarding the RX polling path in hip04_rx_poll():

[Severity: High]
This is a pre-existing issue, but does a failure in build_skb() cause a memory
and DMA mapping leak?

Looking at hip04_rx_poll():

    if (unlikely(!skb)) {
        net_dbg_ratelimited("build_skb failed\n");
        goto refill;
    }
    ...
refill:
    ...
    priv->rx_buf[priv->rx_head] = buf;
    priv->rx_phys[priv->rx_head] = phys;

If build_skb() fails, the code jumps directly to the refill label, completely
bypassing the dma_unmap_single() call for the original buffer. It then
allocates and maps a new buffer, overwriting the array entries.

Could this permanently leak the original buffer and its DMA mapping,
eventually leading to an OOM crash under memory pressure?
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help