aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch')
-rw-r--r--target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch409
1 files changed, 409 insertions, 0 deletions
diff --git a/target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch b/target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch
new file mode 100644
index 0000000000..e3925cce87
--- /dev/null
+++ b/target/linux/layerscape/patches-5.4/701-net-0234-enetc-WA-for-MDIO-register-access-issue.patch
@@ -0,0 +1,409 @@
+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, v->count_tx_rings)
+- 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, v->count_tx_rings)
+- 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
+@@ -321,8 +321,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
+@@ -341,12 +348,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
+@@ -986,6 +986,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. */