Skip to content

Commit fd5736b

Browse files
Alex Margineankuba-moo
authored andcommitted
enetc: Workaround for MDIO register access issue
Due to a hardware issue, an access to MDIO registers that is concurrent with other ENETC register accesses may lead to the MDIO access being dropped or corrupted. The workaround introduces locking for all register accesses to the ENETC register space. To reduce performance impact, a readers-writers locking scheme has been implemented. The writer in this case is the MDIO access code (irrelevant whether that MDIO access is a register read or write), and the reader is any access code to non-MDIO ENETC registers. Also, the datapath functions acquire the read lock fewer times and use _hot accessors. All the rest of the code uses the _wa accessors which lock every register access. The commit introducing MDIO support is - commit ebfcb23 ("enetc: Add ENETC PF level external MDIO support") but due to subsequent refactoring this patch is applicable on top of a later commit. Fixes: 6517798 ("enetc: Make MDIO accessors more generic and export to include/linux/fsl") Signed-off-by: Alex Marginean <[email protected]> Signed-off-by: Vladimir Oltean <[email protected]> Signed-off-by: Claudiu Manoil <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 1b9e2a8 commit fd5736b

File tree

4 files changed

+161
-25
lines changed

4 files changed

+161
-25
lines changed

drivers/net/ethernet/freescale/enetc/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ config FSL_ENETC
1616
config FSL_ENETC_VF
1717
tristate "ENETC VF driver"
1818
depends on PCI && PCI_MSI
19+
select FSL_ENETC_MDIO
1920
select PHYLINK
2021
select DIMLIB
2122
help

drivers/net/ethernet/freescale/enetc/enetc.c

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ netdev_tx_t enetc_xmit(struct sk_buff *skb, struct net_device *ndev)
3333
return NETDEV_TX_BUSY;
3434
}
3535

36+
enetc_lock_mdio();
3637
count = enetc_map_tx_buffs(tx_ring, skb, priv->active_offloads);
38+
enetc_unlock_mdio();
39+
3740
if (unlikely(!count))
3841
goto drop_packet_err;
3942

@@ -239,7 +242,7 @@ static int enetc_map_tx_buffs(struct enetc_bdr *tx_ring, struct sk_buff *skb,
239242
skb_tx_timestamp(skb);
240243

241244
/* let H/W know BD ring has been updated */
242-
enetc_wr_reg(tx_ring->tpir, i); /* includes wmb() */
245+
enetc_wr_reg_hot(tx_ring->tpir, i); /* includes wmb() */
243246

244247
return count;
245248

@@ -262,12 +265,16 @@ static irqreturn_t enetc_msix(int irq, void *data)
262265
struct enetc_int_vector *v = data;
263266
int i;
264267

268+
enetc_lock_mdio();
269+
265270
/* disable interrupts */
266-
enetc_wr_reg(v->rbier, 0);
267-
enetc_wr_reg(v->ricr1, v->rx_ictt);
271+
enetc_wr_reg_hot(v->rbier, 0);
272+
enetc_wr_reg_hot(v->ricr1, v->rx_ictt);
268273

269274
for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
270-
enetc_wr_reg(v->tbier_base + ENETC_BDR_OFF(i), 0);
275+
enetc_wr_reg_hot(v->tbier_base + ENETC_BDR_OFF(i), 0);
276+
277+
enetc_unlock_mdio();
271278

272279
napi_schedule(&v->napi);
273280

@@ -334,19 +341,23 @@ static int enetc_poll(struct napi_struct *napi, int budget)
334341

335342
v->rx_napi_work = false;
336343

344+
enetc_lock_mdio();
345+
337346
/* enable interrupts */
338-
enetc_wr_reg(v->rbier, ENETC_RBIER_RXTIE);
347+
enetc_wr_reg_hot(v->rbier, ENETC_RBIER_RXTIE);
339348

340349
for_each_set_bit(i, &v->tx_rings_map, ENETC_MAX_NUM_TXQS)
341-
enetc_wr_reg(v->tbier_base + ENETC_BDR_OFF(i),
342-
ENETC_TBIER_TXTIE);
350+
enetc_wr_reg_hot(v->tbier_base + ENETC_BDR_OFF(i),
351+
ENETC_TBIER_TXTIE);
352+
353+
enetc_unlock_mdio();
343354

344355
return work_done;
345356
}
346357

347358
static int enetc_bd_ready_count(struct enetc_bdr *tx_ring, int ci)
348359
{
349-
int pi = enetc_rd_reg(tx_ring->tcir) & ENETC_TBCIR_IDX_MASK;
360+
int pi = enetc_rd_reg_hot(tx_ring->tcir) & ENETC_TBCIR_IDX_MASK;
350361

351362
return pi >= ci ? pi - ci : tx_ring->bd_count - ci + pi;
352363
}
@@ -386,7 +397,10 @@ static bool enetc_clean_tx_ring(struct enetc_bdr *tx_ring, int napi_budget)
386397

387398
i = tx_ring->next_to_clean;
388399
tx_swbd = &tx_ring->tx_swbd[i];
400+
401+
enetc_lock_mdio();
389402
bds_to_clean = enetc_bd_ready_count(tx_ring, i);
403+
enetc_unlock_mdio();
390404

391405
do_tstamp = false;
392406

@@ -429,16 +443,20 @@ static bool enetc_clean_tx_ring(struct enetc_bdr *tx_ring, int napi_budget)
429443
tx_swbd = tx_ring->tx_swbd;
430444
}
431445

446+
enetc_lock_mdio();
447+
432448
/* BD iteration loop end */
433449
if (is_eof) {
434450
tx_frm_cnt++;
435451
/* re-arm interrupt source */
436-
enetc_wr_reg(tx_ring->idr, BIT(tx_ring->index) |
437-
BIT(16 + tx_ring->index));
452+
enetc_wr_reg_hot(tx_ring->idr, BIT(tx_ring->index) |
453+
BIT(16 + tx_ring->index));
438454
}
439455

440456
if (unlikely(!bds_to_clean))
441457
bds_to_clean = enetc_bd_ready_count(tx_ring, i);
458+
459+
enetc_unlock_mdio();
442460
}
443461

444462
tx_ring->next_to_clean = i;
@@ -515,8 +533,6 @@ static int enetc_refill_rx_ring(struct enetc_bdr *rx_ring, const int buff_cnt)
515533
if (likely(j)) {
516534
rx_ring->next_to_alloc = i; /* keep track from page reuse */
517535
rx_ring->next_to_use = i;
518-
/* update ENETC's consumer index */
519-
enetc_wr_reg(rx_ring->rcir, i);
520536
}
521537

522538
return j;
@@ -534,8 +550,8 @@ static void enetc_get_rx_tstamp(struct net_device *ndev,
534550
u64 tstamp;
535551

536552
if (le16_to_cpu(rxbd->r.flags) & ENETC_RXBD_FLAG_TSTMP) {
537-
lo = enetc_rd(hw, ENETC_SICTR0);
538-
hi = enetc_rd(hw, ENETC_SICTR1);
553+
lo = enetc_rd_reg_hot(hw->reg + ENETC_SICTR0);
554+
hi = enetc_rd_reg_hot(hw->reg + ENETC_SICTR1);
539555
rxbd = enetc_rxbd_ext(rxbd);
540556
tstamp_lo = le32_to_cpu(rxbd->ext.tstamp);
541557
if (lo <= tstamp_lo)
@@ -684,23 +700,31 @@ static int enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
684700
u32 bd_status;
685701
u16 size;
686702

703+
enetc_lock_mdio();
704+
687705
if (cleaned_cnt >= ENETC_RXBD_BUNDLE) {
688706
int count = enetc_refill_rx_ring(rx_ring, cleaned_cnt);
689707

708+
/* update ENETC's consumer index */
709+
enetc_wr_reg_hot(rx_ring->rcir, rx_ring->next_to_use);
690710
cleaned_cnt -= count;
691711
}
692712

693713
rxbd = enetc_rxbd(rx_ring, i);
694714
bd_status = le32_to_cpu(rxbd->r.lstatus);
695-
if (!bd_status)
715+
if (!bd_status) {
716+
enetc_unlock_mdio();
696717
break;
718+
}
697719

698-
enetc_wr_reg(rx_ring->idr, BIT(rx_ring->index));
720+
enetc_wr_reg_hot(rx_ring->idr, BIT(rx_ring->index));
699721
dma_rmb(); /* for reading other rxbd fields */
700722
size = le16_to_cpu(rxbd->r.buf_len);
701723
skb = enetc_map_rx_buff_to_skb(rx_ring, i, size);
702-
if (!skb)
724+
if (!skb) {
725+
enetc_unlock_mdio();
703726
break;
727+
}
704728

705729
enetc_get_offloads(rx_ring, rxbd, skb);
706730

@@ -712,6 +736,7 @@ static int enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
712736

713737
if (unlikely(bd_status &
714738
ENETC_RXBD_LSTATUS(ENETC_RXBD_ERR_MASK))) {
739+
enetc_unlock_mdio();
715740
dev_kfree_skb(skb);
716741
while (!(bd_status & ENETC_RXBD_LSTATUS_F)) {
717742
dma_rmb();
@@ -751,6 +776,8 @@ static int enetc_clean_rx_ring(struct enetc_bdr *rx_ring,
751776

752777
enetc_process_skb(rx_ring, skb);
753778

779+
enetc_unlock_mdio();
780+
754781
napi_gro_receive(napi, skb);
755782

756783
rx_frm_cnt++;
@@ -1225,6 +1252,7 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring)
12251252
rx_ring->idr = hw->reg + ENETC_SIRXIDR;
12261253

12271254
enetc_refill_rx_ring(rx_ring, enetc_bd_unused(rx_ring));
1255+
enetc_wr(hw, ENETC_SIRXIDR, rx_ring->next_to_use);
12281256

12291257
/* enable ring */
12301258
enetc_rxbdr_wr(hw, idx, ENETC_RBMR, rbmr);

drivers/net/ethernet/freescale/enetc/enetc_hw.h

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,100 @@ struct enetc_hw {
324324
void __iomem *global;
325325
};
326326

327-
/* general register accessors */
328-
#define enetc_rd_reg(reg) ioread32((reg))
329-
#define enetc_wr_reg(reg, val) iowrite32((val), (reg))
327+
/* ENETC register accessors */
328+
329+
/* MDIO issue workaround (on LS1028A) -
330+
* Due to a hardware issue, an access to MDIO registers
331+
* that is concurrent with other ENETC register accesses
332+
* may lead to the MDIO access being dropped or corrupted.
333+
* To protect the MDIO accesses a readers-writers locking
334+
* scheme is used, where the MDIO register accesses are
335+
* protected by write locks to insure exclusivity, while
336+
* the remaining ENETC registers are accessed under read
337+
* locks since they only compete with MDIO accesses.
338+
*/
339+
extern rwlock_t enetc_mdio_lock;
340+
341+
/* use this locking primitive only on the fast datapath to
342+
* group together multiple non-MDIO register accesses to
343+
* minimize the overhead of the lock
344+
*/
345+
static inline void enetc_lock_mdio(void)
346+
{
347+
read_lock(&enetc_mdio_lock);
348+
}
349+
350+
static inline void enetc_unlock_mdio(void)
351+
{
352+
read_unlock(&enetc_mdio_lock);
353+
}
354+
355+
/* use these accessors only on the fast datapath under
356+
* the enetc_lock_mdio() locking primitive to minimize
357+
* the overhead of the lock
358+
*/
359+
static inline u32 enetc_rd_reg_hot(void __iomem *reg)
360+
{
361+
lockdep_assert_held(&enetc_mdio_lock);
362+
363+
return ioread32(reg);
364+
}
365+
366+
static inline void enetc_wr_reg_hot(void __iomem *reg, u32 val)
367+
{
368+
lockdep_assert_held(&enetc_mdio_lock);
369+
370+
iowrite32(val, reg);
371+
}
372+
373+
/* internal helpers for the MDIO w/a */
374+
static inline u32 _enetc_rd_reg_wa(void __iomem *reg)
375+
{
376+
u32 val;
377+
378+
enetc_lock_mdio();
379+
val = ioread32(reg);
380+
enetc_unlock_mdio();
381+
382+
return val;
383+
}
384+
385+
static inline void _enetc_wr_reg_wa(void __iomem *reg, u32 val)
386+
{
387+
enetc_lock_mdio();
388+
iowrite32(val, reg);
389+
enetc_unlock_mdio();
390+
}
391+
392+
static inline u32 _enetc_rd_mdio_reg_wa(void __iomem *reg)
393+
{
394+
unsigned long flags;
395+
u32 val;
396+
397+
write_lock_irqsave(&enetc_mdio_lock, flags);
398+
val = ioread32(reg);
399+
write_unlock_irqrestore(&enetc_mdio_lock, flags);
400+
401+
return val;
402+
}
403+
404+
static inline void _enetc_wr_mdio_reg_wa(void __iomem *reg, u32 val)
405+
{
406+
unsigned long flags;
407+
408+
write_lock_irqsave(&enetc_mdio_lock, flags);
409+
iowrite32(val, reg);
410+
write_unlock_irqrestore(&enetc_mdio_lock, flags);
411+
}
412+
330413
#ifdef ioread64
331-
#define enetc_rd_reg64(reg) ioread64((reg))
414+
static inline u64 _enetc_rd_reg64(void __iomem *reg)
415+
{
416+
return ioread64(reg);
417+
}
332418
#else
333419
/* using this to read out stats on 32b systems */
334-
static inline u64 enetc_rd_reg64(void __iomem *reg)
420+
static inline u64 _enetc_rd_reg64(void __iomem *reg)
335421
{
336422
u32 low, high, tmp;
337423

@@ -345,12 +431,29 @@ static inline u64 enetc_rd_reg64(void __iomem *reg)
345431
}
346432
#endif
347433

434+
static inline u64 _enetc_rd_reg64_wa(void __iomem *reg)
435+
{
436+
u64 val;
437+
438+
enetc_lock_mdio();
439+
val = _enetc_rd_reg64(reg);
440+
enetc_unlock_mdio();
441+
442+
return val;
443+
}
444+
445+
/* general register accessors */
446+
#define enetc_rd_reg(reg) _enetc_rd_reg_wa((reg))
447+
#define enetc_wr_reg(reg, val) _enetc_wr_reg_wa((reg), (val))
348448
#define enetc_rd(hw, off) enetc_rd_reg((hw)->reg + (off))
349449
#define enetc_wr(hw, off, val) enetc_wr_reg((hw)->reg + (off), val)
350-
#define enetc_rd64(hw, off) enetc_rd_reg64((hw)->reg + (off))
450+
#define enetc_rd64(hw, off) _enetc_rd_reg64_wa((hw)->reg + (off))
351451
/* port register accessors - PF only */
352452
#define enetc_port_rd(hw, off) enetc_rd_reg((hw)->port + (off))
353453
#define enetc_port_wr(hw, off, val) enetc_wr_reg((hw)->port + (off), val)
454+
#define enetc_port_rd_mdio(hw, off) _enetc_rd_mdio_reg_wa((hw)->port + (off))
455+
#define enetc_port_wr_mdio(hw, off, val) _enetc_wr_mdio_reg_wa(\
456+
(hw)->port + (off), val)
354457
/* global register accessors - PF only */
355458
#define enetc_global_rd(hw, off) enetc_rd_reg((hw)->global + (off))
356459
#define enetc_global_wr(hw, off, val) enetc_wr_reg((hw)->global + (off), val)

drivers/net/ethernet/freescale/enetc/enetc_mdio.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
static inline u32 _enetc_mdio_rd(struct enetc_mdio_priv *mdio_priv, int off)
1818
{
19-
return enetc_port_rd(mdio_priv->hw, mdio_priv->mdio_base + off);
19+
return enetc_port_rd_mdio(mdio_priv->hw, mdio_priv->mdio_base + off);
2020
}
2121

2222
static inline void _enetc_mdio_wr(struct enetc_mdio_priv *mdio_priv, int off,
2323
u32 val)
2424
{
25-
enetc_port_wr(mdio_priv->hw, mdio_priv->mdio_base + off, val);
25+
enetc_port_wr_mdio(mdio_priv->hw, mdio_priv->mdio_base + off, val);
2626
}
2727

2828
#define enetc_mdio_rd(mdio_priv, off) \
@@ -174,3 +174,7 @@ struct enetc_hw *enetc_hw_alloc(struct device *dev, void __iomem *port_regs)
174174
return hw;
175175
}
176176
EXPORT_SYMBOL_GPL(enetc_hw_alloc);
177+
178+
/* Lock for MDIO access errata on LS1028A */
179+
DEFINE_RWLOCK(enetc_mdio_lock);
180+
EXPORT_SYMBOL_GPL(enetc_mdio_lock);

0 commit comments

Comments
 (0)