Ran update_kernel.sh in a fresh clone without any existing toolchains. Manually rebased: bcm27xx/950-0993-xhci-quirks-add-link-TRB-quirk-for-VL805.patch layerscape/701-net-0231-enetc-Use-DT-protocol-information-to-set-up-the-port.patch Build system: x86_64 Build-tested: ipq806x/R7800 Run-tested: ipq806x/R7800 No dmesg regressions, everything functional Signed-off-by: John Audia <graysky@archlinux.us> [remove accidental whitespace edit] Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de>
409 lines
12 KiB
Diff
409 lines
12 KiB
Diff
From 0f62a794b41ad14a962c70445844d61a8097805e Mon Sep 17 00:00:00 2001
|
|
From: Alex Marginean <alexandru.marginean@nxp.com>
|
|
Date: Tue, 20 Aug 2019 12:34:22 +0300
|
|
Subject: [PATCH] enetc: WA for MDIO register access issue
|
|
|
|
Due to a hardware issue access to MDIO registers concurrent with other
|
|
ENETC register access may lead to the MDIO access being dropped or
|
|
corrupted. The workaround introduces locking for all register access in
|
|
ENETC space. To reduce performance impact, code except MDIO uses per-cpu
|
|
locks, MDIO code having to acquire all per-CPU locks to perform an access.
|
|
To further reduce the performance impact, datapath functions acquire the
|
|
per-cpu lock fewer times and use _hot accessors. All the rest of the code
|
|
uses the _wa accessors which lock every time a register is accessed.
|
|
|
|
Signed-off-by: Alex Marginean <alexandru.marginean@nxp.com>
|
|
---
|
|
drivers/net/ethernet/freescale/enetc/enetc.c | 85 ++++++++++++++----
|
|
drivers/net/ethernet/freescale/enetc/enetc_hw.h | 105 +++++++++++++++++++++-
|
|
drivers/net/ethernet/freescale/enetc/enetc_mdio.c | 4 +-
|
|
drivers/net/ethernet/freescale/enetc/enetc_pf.c | 3 +
|
|
4 files changed, 176 insertions(+), 21 deletions(-)
|
|
|
|
--- a/drivers/net/ethernet/freescale/enetc/enetc.c
|
|
+++ b/drivers/net/ethernet/freescale/enetc/enetc.c
|
|
@@ -20,8 +20,13 @@ netdev_tx_t enetc_xmit(struct sk_buff *s
|
|
{
|
|
struct enetc_ndev_priv *priv = netdev_priv(ndev);
|
|
struct enetc_bdr *tx_ring;
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
int count;
|
|
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+
|
|
tx_ring = priv->tx_ring[skb->queue_mapping];
|
|
|
|
if (unlikely(skb_shinfo(skb)->nr_frags > ENETC_MAX_SKB_FRAGS))
|
|
@@ -34,7 +39,12 @@ netdev_tx_t enetc_xmit(struct sk_buff *s
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+
|
|
count = enetc_map_tx_buffs(tx_ring, skb, priv->active_offloads);
|
|
+
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
+
|
|
if (unlikely(!count))
|
|
goto drop_packet_err;
|
|
|
|
@@ -228,7 +238,7 @@ static int enetc_map_tx_buffs(struct ene
|
|
tx_ring->next_to_use = i;
|
|
|
|
/* let H/W know BD ring has been updated */
|
|
- enetc_wr_reg(tx_ring->tpir, i); /* includes wmb() */
|
|
+ enetc_wr_reg_hot(tx_ring->tpir, i); /* includes wmb() */
|
|
|
|
return count;
|
|
|
|
@@ -249,13 +259,21 @@ dma_err:
|
|
static irqreturn_t enetc_msix(int irq, void *data)
|
|
{
|
|
struct enetc_int_vector *v = data;
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
int i;
|
|
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+
|
|
/* disable interrupts */
|
|
- enetc_wr_reg(v->rbier, 0);
|
|
+ enetc_wr_reg_hot(v->rbier, 0);
|
|
|
|
for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
|
|
- enetc_wr_reg(v->tbier_base + ENETC_BDR_OFF(i), 0);
|
|
+ enetc_wr_reg_hot(v->tbier_base + ENETC_BDR_OFF(i), 0);
|
|
+
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
|
|
napi_schedule_irqoff(&v->napi);
|
|
|
|
@@ -271,6 +289,9 @@ static int enetc_poll(struct napi_struct
|
|
struct enetc_int_vector
|
|
*v = container_of(napi, struct enetc_int_vector, napi);
|
|
bool complete = true;
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
int work_done;
|
|
int i;
|
|
|
|
@@ -287,19 +308,24 @@ static int enetc_poll(struct napi_struct
|
|
|
|
napi_complete_done(napi, work_done);
|
|
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+
|
|
/* enable interrupts */
|
|
- enetc_wr_reg(v->rbier, ENETC_RBIER_RXTIE);
|
|
+ enetc_wr_reg_hot(v->rbier, ENETC_RBIER_RXTIE);
|
|
|
|
for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
|
|
- enetc_wr_reg(v->tbier_base + ENETC_BDR_OFF(i),
|
|
- ENETC_TBIER_TXTIE);
|
|
+ enetc_wr_reg_hot(v->tbier_base + ENETC_BDR_OFF(i),
|
|
+ ENETC_TBIER_TXTIE);
|
|
+
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
|
|
return work_done;
|
|
}
|
|
|
|
static int enetc_bd_ready_count(struct enetc_bdr *tx_ring, int ci)
|
|
{
|
|
- int pi = enetc_rd_reg(tx_ring->tcir) & ENETC_TBCIR_IDX_MASK;
|
|
+ int pi = enetc_rd_reg_hot(tx_ring->tcir) & ENETC_TBCIR_IDX_MASK;
|
|
|
|
return pi >= ci ? pi - ci : tx_ring->bd_count - ci + pi;
|
|
}
|
|
@@ -337,9 +363,18 @@ static bool enetc_clean_tx_ring(struct e
|
|
bool do_tstamp;
|
|
u64 tstamp = 0;
|
|
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
+
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+
|
|
i = tx_ring->next_to_clean;
|
|
tx_swbd = &tx_ring->tx_swbd[i];
|
|
+
|
|
+ spin_lock_irqsave(lock, flags);
|
|
bds_to_clean = enetc_bd_ready_count(tx_ring, i);
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
|
|
do_tstamp = false;
|
|
|
|
@@ -382,16 +417,20 @@ static bool enetc_clean_tx_ring(struct e
|
|
tx_swbd = tx_ring->tx_swbd;
|
|
}
|
|
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+
|
|
/* BD iteration loop end */
|
|
if (is_eof) {
|
|
tx_frm_cnt++;
|
|
/* re-arm interrupt source */
|
|
- enetc_wr_reg(tx_ring->idr, BIT(tx_ring->index) |
|
|
- BIT(16 + tx_ring->index));
|
|
+ enetc_wr_reg_hot(tx_ring->idr, BIT(tx_ring->index) |
|
|
+ BIT(16 + tx_ring->index));
|
|
}
|
|
|
|
if (unlikely(!bds_to_clean))
|
|
bds_to_clean = enetc_bd_ready_count(tx_ring, i);
|
|
+
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
}
|
|
|
|
tx_ring->next_to_clean = i;
|
|
@@ -470,13 +509,14 @@ static int enetc_refill_rx_ring(struct e
|
|
rx_ring->next_to_alloc = i; /* keep track from page reuse */
|
|
rx_ring->next_to_use = i;
|
|
/* update ENETC's consumer index */
|
|
- enetc_wr_reg(rx_ring->rcir, i);
|
|
+ enetc_wr_reg_hot(rx_ring->rcir, i);
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
#ifdef CONFIG_FSL_ENETC_HW_TIMESTAMPING
|
|
+/* Must be called with &enetc_gregs spinlock held */
|
|
static void enetc_get_rx_tstamp(struct net_device *ndev,
|
|
union enetc_rx_bd *rxbd,
|
|
struct sk_buff *skb)
|
|
@@ -488,8 +528,8 @@ static void enetc_get_rx_tstamp(struct n
|
|
u64 tstamp;
|
|
|
|
if (le16_to_cpu(rxbd->r.flags) & ENETC_RXBD_FLAG_TSTMP) {
|
|
- lo = enetc_rd(hw, ENETC_SICTR0);
|
|
- hi = enetc_rd(hw, ENETC_SICTR1);
|
|
+ lo = enetc_rd_reg_hot(hw->reg + ENETC_SICTR0);
|
|
+ hi = enetc_rd_reg_hot(hw->reg + ENETC_SICTR1);
|
|
tstamp_lo = le32_to_cpu(rxbd->r.tstamp);
|
|
if (lo <= tstamp_lo)
|
|
hi -= 1;
|
|
@@ -627,6 +667,12 @@ static int enetc_clean_rx_ring(struct en
|
|
int rx_frm_cnt = 0, rx_byte_cnt = 0;
|
|
int cleaned_cnt, i;
|
|
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
+
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+
|
|
cleaned_cnt = enetc_bd_unused(rx_ring);
|
|
/* next descriptor to process */
|
|
i = rx_ring->next_to_clean;
|
|
@@ -637,6 +683,8 @@ static int enetc_clean_rx_ring(struct en
|
|
u32 bd_status;
|
|
u16 size;
|
|
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+
|
|
if (cleaned_cnt >= ENETC_RXBD_BUNDLE) {
|
|
int count = enetc_refill_rx_ring(rx_ring, cleaned_cnt);
|
|
|
|
@@ -645,15 +693,19 @@ static int enetc_clean_rx_ring(struct en
|
|
|
|
rxbd = ENETC_RXBD(*rx_ring, i);
|
|
bd_status = le32_to_cpu(rxbd->r.lstatus);
|
|
- if (!bd_status)
|
|
+ if (!bd_status) {
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
break;
|
|
+ }
|
|
|
|
- enetc_wr_reg(rx_ring->idr, BIT(rx_ring->index));
|
|
+ enetc_wr_reg_hot(rx_ring->idr, BIT(rx_ring->index));
|
|
dma_rmb(); /* for reading other rxbd fields */
|
|
size = le16_to_cpu(rxbd->r.buf_len);
|
|
skb = enetc_map_rx_buff_to_skb(rx_ring, i, size);
|
|
- if (!skb)
|
|
+ if (!skb) {
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
break;
|
|
+ }
|
|
|
|
enetc_get_offloads(rx_ring, rxbd, skb);
|
|
|
|
@@ -667,6 +719,7 @@ static int enetc_clean_rx_ring(struct en
|
|
|
|
if (unlikely(bd_status &
|
|
ENETC_RXBD_LSTATUS(ENETC_RXBD_ERR_MASK))) {
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
dev_kfree_skb(skb);
|
|
while (!(bd_status & ENETC_RXBD_LSTATUS_F)) {
|
|
dma_rmb();
|
|
@@ -710,6 +763,8 @@ static int enetc_clean_rx_ring(struct en
|
|
|
|
enetc_process_skb(rx_ring, skb);
|
|
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
+
|
|
napi_gro_receive(napi, skb);
|
|
|
|
rx_frm_cnt++;
|
|
--- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
|
|
+++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
|
|
@@ -327,8 +327,15 @@ struct enetc_hw {
|
|
};
|
|
|
|
/* general register accessors */
|
|
-#define enetc_rd_reg(reg) ioread32((reg))
|
|
-#define enetc_wr_reg(reg, val) iowrite32((val), (reg))
|
|
+#define enetc_rd_reg(reg) enetc_rd_reg_wa((reg))
|
|
+#define enetc_wr_reg(reg, val) enetc_wr_reg_wa((reg), (val))
|
|
+
|
|
+/* accessors for data-path, due to MDIO issue on LS1028 these should be called
|
|
+ * only under enetc_gregs per-cpu lock
|
|
+ */
|
|
+#define enetc_rd_reg_hot(reg) ioread32((reg))
|
|
+#define enetc_wr_reg_hot(reg, val) iowrite32((val), (reg))
|
|
+
|
|
#ifdef ioread64
|
|
#define enetc_rd_reg64(reg) ioread64((reg))
|
|
#else
|
|
@@ -347,12 +354,102 @@ static inline u64 enetc_rd_reg64(void __
|
|
}
|
|
#endif
|
|
|
|
+extern DEFINE_PER_CPU(spinlock_t, enetc_gregs);
|
|
+
|
|
+static inline u32 enetc_rd_reg_wa(void *reg)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
+ u32 val;
|
|
+
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+ val = ioread32(reg);
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+static inline void enetc_wr_reg_wa(void *reg, u32 val)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ /* pointer to per-cpu ENETC lock for register access issue WA */
|
|
+ spinlock_t *lock;
|
|
+
|
|
+ lock = this_cpu_ptr(&enetc_gregs);
|
|
+ spin_lock_irqsave(lock, flags);
|
|
+ iowrite32(val, reg);
|
|
+ spin_unlock_irqrestore(lock, flags);
|
|
+}
|
|
+
|
|
+/* NR_CPUS=256 in ARM64 defconfig and using it as array size triggers stack
|
|
+ * frame warnings for the functions below. Use a custom define of 2 for now,
|
|
+ * LS1028 has just two cores.
|
|
+ */
|
|
+#define ENETC_NR_CPU_LOCKS 2
|
|
+
|
|
+static inline u32 enetc_rd_reg_wa_single(void *reg)
|
|
+{
|
|
+ u32 val;
|
|
+ int cpu;
|
|
+ /* per-cpu ENETC lock array for register access issue WA */
|
|
+ spinlock_t *lock[ENETC_NR_CPU_LOCKS];
|
|
+ unsigned long flags;
|
|
+
|
|
+ local_irq_save(flags);
|
|
+ preempt_disable();
|
|
+
|
|
+ for_each_online_cpu(cpu) {
|
|
+ lock[cpu] = per_cpu_ptr(&enetc_gregs, cpu);
|
|
+ spin_lock(lock[cpu]);
|
|
+ }
|
|
+
|
|
+ val = ioread32(reg);
|
|
+
|
|
+ for_each_online_cpu(cpu)
|
|
+ spin_unlock(lock[cpu]);
|
|
+ local_irq_restore(flags);
|
|
+
|
|
+ preempt_enable();
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+static inline void enetc_wr_reg_wa_single(void *reg, u32 val)
|
|
+{
|
|
+ int cpu;
|
|
+ /* per-cpu ENETC lock array for register access issue WA */
|
|
+ spinlock_t *lock[ENETC_NR_CPU_LOCKS];
|
|
+ unsigned long flags;
|
|
+
|
|
+ local_irq_save(flags);
|
|
+ preempt_disable();
|
|
+
|
|
+ for_each_online_cpu(cpu) {
|
|
+ lock[cpu] = per_cpu_ptr(&enetc_gregs, cpu);
|
|
+ spin_lock(lock[cpu]);
|
|
+ }
|
|
+
|
|
+ iowrite32(val, reg);
|
|
+
|
|
+ for_each_online_cpu(cpu)
|
|
+ spin_unlock(lock[cpu]);
|
|
+ local_irq_restore(flags);
|
|
+
|
|
+ preempt_enable();
|
|
+}
|
|
+
|
|
#define enetc_rd(hw, off) enetc_rd_reg((hw)->reg + (off))
|
|
#define enetc_wr(hw, off, val) enetc_wr_reg((hw)->reg + (off), val)
|
|
#define enetc_rd64(hw, off) enetc_rd_reg64((hw)->reg + (off))
|
|
/* port register accessors - PF only */
|
|
-#define enetc_port_rd(hw, off) enetc_rd_reg((hw)->port + (off))
|
|
-#define enetc_port_wr(hw, off, val) enetc_wr_reg((hw)->port + (off), val)
|
|
+#define enetc_port_rd(hw, off) enetc_rd_reg_wa((hw)->port + (off))
|
|
+#define enetc_port_wr(hw, off, val) enetc_wr_reg_wa((hw)->port + (off), val)
|
|
+#define enetc_port_rd_single(hw, off) enetc_rd_reg_wa_single(\
|
|
+ (hw)->port + (off))
|
|
+#define enetc_port_wr_single(hw, off, val) enetc_wr_reg_wa_single(\
|
|
+ (hw)->port + (off), val)
|
|
/* global register accessors - PF only */
|
|
#define enetc_global_rd(hw, off) enetc_rd_reg((hw)->global + (off))
|
|
#define enetc_global_wr(hw, off, val) enetc_wr_reg((hw)->global + (off), val)
|
|
--- a/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
|
|
+++ b/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
|
|
@@ -16,13 +16,13 @@
|
|
|
|
static inline u32 _enetc_mdio_rd(struct enetc_mdio_priv *mdio_priv, int off)
|
|
{
|
|
- return enetc_port_rd(mdio_priv->hw, mdio_priv->mdio_base + off);
|
|
+ return enetc_port_rd_single(mdio_priv->hw, mdio_priv->mdio_base + off);
|
|
}
|
|
|
|
static inline void _enetc_mdio_wr(struct enetc_mdio_priv *mdio_priv, int off,
|
|
u32 val)
|
|
{
|
|
- enetc_port_wr(mdio_priv->hw, mdio_priv->mdio_base + off, val);
|
|
+ enetc_port_wr_single(mdio_priv->hw, mdio_priv->mdio_base + off, val);
|
|
}
|
|
|
|
#define enetc_mdio_rd(mdio_priv, off) \
|
|
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
|
|
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
|
|
@@ -1076,6 +1076,9 @@ static void enetc_pf_remove(struct pci_d
|
|
enetc_pci_remove(pdev);
|
|
}
|
|
|
|
+DEFINE_PER_CPU(spinlock_t, enetc_gregs);
|
|
+EXPORT_PER_CPU_SYMBOL(enetc_gregs);
|
|
+
|
|
static const struct pci_device_id enetc_pf_id_table[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, ENETC_DEV_ID_PF) },
|
|
{ 0, } /* End of table. */
|