mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge branch 'net-macb-fix-close-races-and-rx-refill-error-handling'
Théo Lebrun says: ==================== net: macb: fix close races (and RX refill error handling) The context-swapping series [0] has been ongoing for a while. The most interesting part is a proper hardware shutdown sequence, which is truly lacking in other parts of the MACB driver: at close, at suspend and in the HRESP error task. Instead of introducing that sequence for a new feature, apply it now to fix the main offender, the close path. That fixes the races listed in patch 3 and will allow the sequence to be reused later. The first two patches are also fixes, but not related to the shutdown sequence: they address allocation-failure codepaths. They are sent alongside patch 3 because they touch the same code and would cause merge conflicts if applied separately. Now, let me list issues I'm aware of that we do *not* fix here, to make the scope explicit: - HRESP task should sync with all other contexts. It frees buffers under the feet of the whole driver. The hardware shutdown sequence will help. - Suspend callback is also racy with BH primitives. We'll be able to reuse the hardware shutdown sequence. - Even if we assume tasks are frozen, the phylink ops aren't and might trigger between suspend and resume callbacks. Here we need to (1) early return in phylink ops and (2) at resume put the HW in its proper state according to phylink ops that occured. - Alloc failure codepaths aren't perfect outside open. Hardware and ring buffers are left in a sad state. We can probably do better. [0]: https://lore.kernel.org/all/20260812-macb-context-v9-0-7ddbf5f715e0@bootlin.com/ ==================== Link: https://patch.msgid.link/20260925-macb-close-v2-0-360efa565914@bootlin.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
0737138c55
|
|
@ -1197,7 +1197,7 @@ struct macb_queue;
|
|||
struct macb_or_gem_ops {
|
||||
int (*mog_alloc_rx_buffers)(struct macb *bp);
|
||||
void (*mog_free_rx_buffers)(struct macb *bp);
|
||||
void (*mog_init_rings)(struct macb *bp);
|
||||
int (*mog_init_rings)(struct macb *bp);
|
||||
int (*mog_rx)(struct macb_queue *queue, struct napi_struct *napi,
|
||||
int budget);
|
||||
};
|
||||
|
|
@ -1382,6 +1382,11 @@ struct macb {
|
|||
struct delayed_work tx_lpi_work;
|
||||
u32 tx_lpi_timer;
|
||||
|
||||
/* ISR must not drive NAPI & BH mechanisms. True when the interface
|
||||
* is closed. Protected by bp->lock.
|
||||
*/
|
||||
bool irq_quiesced;
|
||||
|
||||
int rx_bd_rd_prefetch;
|
||||
int tx_bd_rd_prefetch;
|
||||
|
||||
|
|
|
|||
|
|
@ -1505,7 +1505,7 @@ static int macb_tx_complete(struct macb_queue *queue, int budget)
|
|||
return packets;
|
||||
}
|
||||
|
||||
static void gem_rx_refill(struct macb_queue *queue)
|
||||
static int gem_rx_refill(struct macb_queue *queue)
|
||||
{
|
||||
struct macb *bp = queue->bp;
|
||||
struct macb_dma_desc *desc;
|
||||
|
|
@ -1577,6 +1577,14 @@ static void gem_rx_refill(struct macb_queue *queue)
|
|||
|
||||
netdev_vdbg(bp->netdev, "rx ring: queue: %p, prepared head %d, tail %d\n",
|
||||
queue, queue->rx_prepared_head, queue->rx_tail);
|
||||
|
||||
/* Fail if queue has zero prepared descriptors. This is critical because
|
||||
* nothing will ever trigger a refill again.
|
||||
*/
|
||||
if (queue->rx_prepared_head == queue->rx_tail)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark DMA descriptors from begin up to and not including end as unused */
|
||||
|
|
@ -1615,6 +1623,13 @@ static int gem_rx(struct macb_queue *queue, struct napi_struct *napi,
|
|||
dma_addr_t addr;
|
||||
bool rxused;
|
||||
|
||||
/* Only descriptors in [rx_tail, rx_prepared_head) were armed
|
||||
* for hardware. Outside, we might have RX_USED descriptors for
|
||||
* alloc failures.
|
||||
*/
|
||||
if (queue->rx_tail == queue->rx_prepared_head)
|
||||
break;
|
||||
|
||||
entry = macb_rx_ring_wrap(bp, queue->rx_tail);
|
||||
desc = macb_rx_desc(queue, entry);
|
||||
|
||||
|
|
@ -1878,6 +1893,10 @@ static bool macb_rx_pending(struct macb_queue *queue)
|
|||
struct macb_dma_desc *desc;
|
||||
unsigned int entry;
|
||||
|
||||
/* No armed descriptor left: nothing can be pending. */
|
||||
if (macb_is_gem(bp) && queue->rx_tail == queue->rx_prepared_head)
|
||||
return false;
|
||||
|
||||
entry = macb_rx_ring_wrap(bp, queue->rx_tail);
|
||||
desc = macb_rx_desc(queue, entry);
|
||||
|
||||
|
|
@ -2009,6 +2028,53 @@ static int macb_tx_poll(struct napi_struct *napi, int budget)
|
|||
return work_done;
|
||||
}
|
||||
|
||||
static void macb_quiesce_start(struct macb *bp)
|
||||
{
|
||||
struct macb_queue *queue;
|
||||
unsigned long flags;
|
||||
unsigned int q;
|
||||
|
||||
spin_lock_irqsave(&bp->lock, flags);
|
||||
bp->irq_quiesced = true;
|
||||
spin_unlock_irqrestore(&bp->lock, flags);
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
|
||||
synchronize_irq(queue->irq);
|
||||
|
||||
cancel_work_sync(&bp->hresp_err_bh_work);
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
/* Must be done before NAPI is disabled: the task ends with a
|
||||
* napi_enable() call.
|
||||
*/
|
||||
cancel_work_sync(&queue->tx_error_task);
|
||||
|
||||
napi_disable(&queue->napi_rx);
|
||||
napi_disable(&queue->napi_tx);
|
||||
}
|
||||
|
||||
/* Must be done after napi_tx is disabled: its completion re-arms
|
||||
* the LPI timer.
|
||||
*/
|
||||
cancel_delayed_work_sync(&bp->tx_lpi_work);
|
||||
}
|
||||
|
||||
static void macb_quiesce_end(struct macb *bp)
|
||||
{
|
||||
struct macb_queue *queue;
|
||||
unsigned long flags;
|
||||
unsigned int q;
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
napi_enable(&queue->napi_rx);
|
||||
napi_enable(&queue->napi_tx);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&bp->lock, flags);
|
||||
bp->irq_quiesced = false;
|
||||
spin_unlock_irqrestore(&bp->lock, flags);
|
||||
}
|
||||
|
||||
static void macb_hresp_error_task(struct work_struct *work)
|
||||
{
|
||||
struct macb *bp = from_work(bp, work, hresp_err_bh_work);
|
||||
|
|
@ -2151,8 +2217,8 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
|
|||
spin_lock(&bp->lock);
|
||||
|
||||
while (status) {
|
||||
/* close possible race with dev_close */
|
||||
if (unlikely(!netif_running(netdev))) {
|
||||
/* self-disarm while the netdev is closed */
|
||||
if (unlikely(bp->irq_quiesced)) {
|
||||
queue_writel(queue, IDR, -1);
|
||||
macb_queue_isr_clear(bp, queue, -1);
|
||||
break;
|
||||
|
|
@ -2812,19 +2878,26 @@ static int macb_alloc(struct macb *bp)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void gem_init_rx_ring(struct macb_queue *queue)
|
||||
static int gem_init_rx_ring(struct macb_queue *queue)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
queue->rx_tail = 0;
|
||||
queue->rx_prepared_head = 0;
|
||||
|
||||
gem_rx_refill(queue);
|
||||
for (i = 0; i < queue->bp->rx_ring_size; i++)
|
||||
macb_rx_desc(queue, i)->addr |= MACB_BIT(RX_USED);
|
||||
|
||||
return gem_rx_refill(queue);
|
||||
}
|
||||
|
||||
static void gem_init_rings(struct macb *bp)
|
||||
static int gem_init_rings(struct macb *bp)
|
||||
{
|
||||
struct macb_queue *queue;
|
||||
struct macb_dma_desc *desc = NULL;
|
||||
int last_err = 0;
|
||||
unsigned int q;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
|
|
@ -2837,11 +2910,15 @@ static void gem_init_rings(struct macb *bp)
|
|||
queue->tx_head = 0;
|
||||
queue->tx_tail = 0;
|
||||
|
||||
gem_init_rx_ring(queue);
|
||||
err = gem_init_rx_ring(queue);
|
||||
if (err)
|
||||
last_err = err;
|
||||
}
|
||||
|
||||
return last_err;
|
||||
}
|
||||
|
||||
static void macb_init_rings(struct macb *bp)
|
||||
static int macb_init_rings(struct macb *bp)
|
||||
{
|
||||
int i;
|
||||
struct macb_dma_desc *desc = NULL;
|
||||
|
|
@ -2856,6 +2933,8 @@ static void macb_init_rings(struct macb *bp)
|
|||
bp->queues[0].tx_head = 0;
|
||||
bp->queues[0].tx_tail = 0;
|
||||
desc->ctrl |= MACB_BIT(TX_WRAP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void macb_reset_hw(struct macb *bp)
|
||||
|
|
@ -3166,8 +3245,6 @@ static int macb_open(struct net_device *netdev)
|
|||
{
|
||||
size_t bufsz = netdev->mtu + ETH_HLEN + ETH_FCS_LEN + NET_IP_ALIGN;
|
||||
struct macb *bp = netdev_priv(netdev);
|
||||
struct macb_queue *queue;
|
||||
unsigned int q;
|
||||
int err;
|
||||
|
||||
netdev_dbg(bp->netdev, "open\n");
|
||||
|
|
@ -3186,13 +3263,12 @@ static int macb_open(struct net_device *netdev)
|
|||
goto pm_exit;
|
||||
}
|
||||
|
||||
bp->macbgem_ops.mog_init_rings(bp);
|
||||
err = bp->macbgem_ops.mog_init_rings(bp);
|
||||
if (err)
|
||||
goto free_rings;
|
||||
macb_init_buffers(bp);
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
napi_enable(&queue->napi_rx);
|
||||
napi_enable(&queue->napi_tx);
|
||||
}
|
||||
macb_quiesce_end(bp);
|
||||
|
||||
macb_init_hw(bp);
|
||||
|
||||
|
|
@ -3219,11 +3295,11 @@ static int macb_open(struct net_device *netdev)
|
|||
phy_power_off(bp->phy);
|
||||
|
||||
reset_hw:
|
||||
/* The netdev stays down: quiesce and drain, as macb_close() does. */
|
||||
macb_quiesce_start(bp);
|
||||
|
||||
macb_reset_hw(bp);
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
napi_disable(&queue->napi_rx);
|
||||
napi_disable(&queue->napi_tx);
|
||||
}
|
||||
free_rings:
|
||||
macb_free(bp);
|
||||
pm_exit:
|
||||
pm_runtime_put_sync(&bp->pdev->dev);
|
||||
|
|
@ -3233,19 +3309,17 @@ static int macb_open(struct net_device *netdev)
|
|||
static int macb_close(struct net_device *netdev)
|
||||
{
|
||||
struct macb *bp = netdev_priv(netdev);
|
||||
struct macb_queue *queue;
|
||||
unsigned long flags;
|
||||
unsigned int q;
|
||||
|
||||
macb_quiesce_start(bp);
|
||||
|
||||
/* Drain the BH contexts before stopping the queues: NAPI completion
|
||||
* and tx_error_task wake them up.
|
||||
*/
|
||||
netif_tx_stop_all_queues(netdev);
|
||||
|
||||
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
|
||||
napi_disable(&queue->napi_rx);
|
||||
napi_disable(&queue->napi_tx);
|
||||
for (q = 0; q < bp->num_queues; ++q)
|
||||
netdev_tx_reset_queue(netdev_get_tx_queue(netdev, q));
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&bp->tx_lpi_work);
|
||||
|
||||
phylink_stop(bp->phylink);
|
||||
phylink_disconnect_phy(bp->phylink);
|
||||
|
|
@ -4767,6 +4841,11 @@ static int macb_init_dflt(struct platform_device *pdev)
|
|||
bp->tx_ring_size = DEFAULT_TX_RING_SIZE;
|
||||
bp->rx_ring_size = DEFAULT_RX_RING_SIZE;
|
||||
|
||||
/* No locking needed because the IRQs are not requested yet. The
|
||||
* flag is cleared by macb_open() and re-armed by macb_close().
|
||||
*/
|
||||
bp->irq_quiesced = true;
|
||||
|
||||
/* set the queue register mapping once for all: queue0 has a special
|
||||
* register mapping but we don't want to test the queue index then
|
||||
* compute the corresponding register offset at run time.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user