Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions drivers/net/ethernet/cadence/macb.h
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,10 @@
#define MACB_CAPS_QUEUE_DISABLE 0x00000800
#define MACB_CAPS_QBV 0x00001000
#define MACB_CAPS_EEE 0x00002000
/* Register writes are posted on the parent fabric and need a non-posted
* read-back to guarantee delivery. Currently set only on RP1.
*/
#define MACB_CAPS_PCIE_POSTED_WRITES 0x00004000
#define MACB_CAPS_PCS 0x01000000
#define MACB_CAPS_HIGH_SPEED 0x02000000
#define MACB_CAPS_CLK_HW_CHG 0x04000000
Expand Down
76 changes: 49 additions & 27 deletions drivers/net/ethernet/cadence/macb_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1951,12 +1951,13 @@ static void macb_tx_restart(struct macb_queue *queue)

spin_lock(&bp->lock);
macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART));
/* Flush the PCIe posted-write queue so the TSTART doorbell
* reliably reaches the MAC. Without this, the write can sit
* in the fabric and the MAC never advances, causing a silent
* TX stall.
/* On PCIe-attached parts, flush the posted-write queue so the
* TSTART doorbell reliably reaches the MAC. Without this the
* write can sit in the fabric and the MAC never advances,
* causing a silent TX stall.
*/
(void)macb_readl(bp, NCR);
if (bp->caps & MACB_CAPS_PCIE_POSTED_WRITES)
(void)macb_readl(bp, NCR);
spin_unlock(&bp->lock);

out_tx_ptr_unlock:
Expand Down Expand Up @@ -2001,24 +2002,31 @@ static int macb_tx_poll(struct napi_struct *napi, int budget)
if (work_done < budget && napi_complete_done(napi, work_done)) {
queue_writel(queue, IER, MACB_BIT(TCOMP));

/* TCOMP events that fire while the interrupt is masked do
* not re-fire when IER is re-enabled. Catch this two ways
* to avoid losing a wakeup:
/* TCOMP events that fire while masked don't re-fire when
* IER is re-enabled (HW errata), so check in software.
* macb_tx_complete_pending() inspects the descriptor at
* tx_tail; the rmb() in there orders prior CPU writes but
* does not retire in-flight peripheral DMA writes that
* may still be racing back to memory on PCIe-attached
* parts.
*
* (1) Read ISR -- catches completions the hardware flagged
* but that we did not see as an interrupt. The MMIO
* read doubles as a PCIe read barrier, flushing any
* in-flight descriptor TX_USED DMA writes into memory.
* (2) macb_tx_complete_pending() inspects the ring after
* that flush, catching a descriptor whose TX_USED is
* now visible as a result of the barrier.
* Read a side-effect-free MMIO register (IMR, the
* read-only mask mirror) to act as a PCIe read barrier
* for prior peripheral DMA writes. After this read, any
* in-flight TX_USED descriptor update has retired and
* macb_tx_complete_pending() will observe it.
*
* This can race with the interrupt handler taking the same
* path if an interrupt fires just after the IER write;
* rescheduling NAPI in that case is harmless.
* Note: an earlier form of this block read ISR directly
* to also sample a latched TCOMP bit, but that is
* destructive on silicon where MACB_CAPS_ISR_CLEAR_ON_WRITE
* is not set (raspberrypi_rp1_config among others): the
* read clears every set bit, and a masked check silently
* consumes RCOMP / ROVR / TXUBR bits the IRQ handler is
* expected to process in one pass. IMR is non-destructive
* on both read-clear and W1C silicon.
*/
if ((queue_readl(queue, ISR) & MACB_BIT(TCOMP)) ||
macb_tx_complete_pending(queue)) {
(void)queue_readl(queue, IMR);
if (macb_tx_complete_pending(queue)) {
queue_writel(queue, IDR, MACB_BIT(TCOMP));
if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
queue_writel(queue, ISR, MACB_BIT(TCOMP));
Expand Down Expand Up @@ -2067,6 +2075,16 @@ static void macb_tx_stall_watchdog(struct work_struct *work)
if (!netif_running(bp->dev))
return;

/* No carrier => no completion is possible. Skip the stall
* check (otherwise queue->tx_head can advance from kernel-
* queued packets between macb_open() and link autoneg
* completion while tx_tail stays unchanged, tripping a false
* positive), but keep the watchdog ticking so it picks up
* once carrier comes up.
*/
if (!netif_carrier_ok(bp->dev))
goto reschedule;

spin_lock_irqsave(&queue->tx_ptr_lock, flags);
cur_tail = queue->tx_tail;
cur_head = queue->tx_head;
Expand All @@ -2076,13 +2094,14 @@ static void macb_tx_stall_watchdog(struct work_struct *work)
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);

if (stalled) {
netdev_warn_once(bp->dev,
"TX stall detected on queue %u (tail=%u head=%u); re-kicking TSTART\n",
(unsigned int)(queue - bp->queues),
cur_tail, cur_head);
netdev_warn_ratelimited(bp->dev,
"TX stall detected on queue %u (tail=%u head=%u); re-kicking TSTART\n",
(unsigned int)(queue - bp->queues),
cur_tail, cur_head);
macb_tx_restart(queue);
}

reschedule:
schedule_delayed_work(&queue->tx_stall_watchdog_work,
msecs_to_jiffies(MACB_TX_STALL_INTERVAL_MS));
}
Expand Down Expand Up @@ -2702,10 +2721,12 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev)
queue->tx_pending = 1;

macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART));
/* Flush the PCIe posted-write queue; see the comment in
* macb_tx_restart() for the reasoning.
/* Flush PCIe posted-write queue; see comment in macb_tx_restart().
* Also flushes the preceding macb_tx_lpi_wake() NCR write and the
* TSR-read tx_pending breadcrumb above.
*/
(void)macb_readl(bp, NCR);
if (bp->caps & MACB_CAPS_PCIE_POSTED_WRITES)
(void)macb_readl(bp, NCR);
spin_unlock(&bp->lock);

if (CIRC_SPACE(queue->tx_head, queue->tx_tail, bp->tx_ring_size) < 1)
Expand Down Expand Up @@ -5810,6 +5831,7 @@ static const struct macb_config raspberrypi_rp1_config = {
.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG |
MACB_CAPS_JUMBO |
MACB_CAPS_GEM_HAS_PTP |
MACB_CAPS_PCIE_POSTED_WRITES |
MACB_CAPS_EEE,
.dma_burst_length = 16,
.clk_init = macb_clk_init,
Expand Down