| From ec63457374787b7992800dee9839023c323de015 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 15 Aug 2024 03:07:04 +0300 |
| Subject: net: mscc: ocelot: serialize access to the injection/extraction |
| groups |
| |
| From: Vladimir Oltean <vladimir.oltean@nxp.com> |
| |
| [ Upstream commit c5e12ac3beb0dd3a718296b2d8af5528e9ab728e ] |
| |
| As explained by Horatiu Vultur in commit 603ead96582d ("net: sparx5: Add |
| spinlock for frame transmission from CPU") which is for a similar |
| hardware design, multiple CPUs can simultaneously perform injection |
| or extraction. There are only 2 register groups for injection and 2 |
| for extraction, and the driver only uses one of each. So we'd better |
| serialize access using spin locks, otherwise frame corruption is |
| possible. |
| |
| Note that unlike in sparx5, FDMA in ocelot does not have this issue |
| because struct ocelot_fdma_tx_ring already contains an xmit_lock. |
| |
| I guess this is mostly a problem for NXP LS1028A, as that is dual core. |
| I don't think VSC7514 is. So I'm blaming the commit where LS1028A (aka |
| the felix DSA driver) started using register-based packet injection and |
| extraction. |
| |
| Fixes: 0a6f17c6ae21 ("net: dsa: tag_ocelot_8021q: add support for PTP timestamping") |
| Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/net/dsa/ocelot/felix.c | 11 +++++ |
| drivers/net/ethernet/mscc/ocelot.c | 52 ++++++++++++++++++++++ |
| drivers/net/ethernet/mscc/ocelot_vsc7514.c | 4 ++ |
| include/soc/mscc/ocelot.h | 9 ++++ |
| 4 files changed, 76 insertions(+) |
| |
| diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c |
| index 2d2c6f941272c..73da407bb0685 100644 |
| --- a/drivers/net/dsa/ocelot/felix.c |
| +++ b/drivers/net/dsa/ocelot/felix.c |
| @@ -528,7 +528,9 @@ static int felix_tag_8021q_setup(struct dsa_switch *ds) |
| * so we need to be careful that there are no extra frames to be |
| * dequeued over MMIO, since we would never know to discard them. |
| */ |
| + ocelot_lock_xtr_grp_bh(ocelot, 0); |
| ocelot_drain_cpu_queue(ocelot, 0); |
| + ocelot_unlock_xtr_grp_bh(ocelot, 0); |
| |
| return 0; |
| } |
| @@ -1493,6 +1495,8 @@ static void felix_port_deferred_xmit(struct kthread_work *work) |
| int port = xmit_work->dp->index; |
| int retries = 10; |
| |
| + ocelot_lock_inj_grp(ocelot, 0); |
| + |
| do { |
| if (ocelot_can_inject(ocelot, 0)) |
| break; |
| @@ -1501,6 +1505,7 @@ static void felix_port_deferred_xmit(struct kthread_work *work) |
| } while (--retries); |
| |
| if (!retries) { |
| + ocelot_unlock_inj_grp(ocelot, 0); |
| dev_err(ocelot->dev, "port %d failed to inject skb\n", |
| port); |
| ocelot_port_purge_txtstamp_skb(ocelot, port, skb); |
| @@ -1510,6 +1515,8 @@ static void felix_port_deferred_xmit(struct kthread_work *work) |
| |
| ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb); |
| |
| + ocelot_unlock_inj_grp(ocelot, 0); |
| + |
| consume_skb(skb); |
| kfree(xmit_work); |
| } |
| @@ -1658,6 +1665,8 @@ static bool felix_check_xtr_pkt(struct ocelot *ocelot) |
| if (!felix->info->quirk_no_xtr_irq) |
| return false; |
| |
| + ocelot_lock_xtr_grp(ocelot, grp); |
| + |
| while (ocelot_read(ocelot, QS_XTR_DATA_PRESENT) & BIT(grp)) { |
| struct sk_buff *skb; |
| unsigned int type; |
| @@ -1694,6 +1703,8 @@ static bool felix_check_xtr_pkt(struct ocelot *ocelot) |
| ocelot_drain_cpu_queue(ocelot, 0); |
| } |
| |
| + ocelot_unlock_xtr_grp(ocelot, grp); |
| + |
| return true; |
| } |
| |
| diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c |
| index ad179a9dca61a..310a36356f568 100644 |
| --- a/drivers/net/ethernet/mscc/ocelot.c |
| +++ b/drivers/net/ethernet/mscc/ocelot.c |
| @@ -994,6 +994,48 @@ void ocelot_ptp_rx_timestamp(struct ocelot *ocelot, struct sk_buff *skb, |
| } |
| EXPORT_SYMBOL(ocelot_ptp_rx_timestamp); |
| |
| +void ocelot_lock_inj_grp(struct ocelot *ocelot, int grp) |
| + __acquires(&ocelot->inj_lock) |
| +{ |
| + spin_lock(&ocelot->inj_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_lock_inj_grp); |
| + |
| +void ocelot_unlock_inj_grp(struct ocelot *ocelot, int grp) |
| + __releases(&ocelot->inj_lock) |
| +{ |
| + spin_unlock(&ocelot->inj_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_unlock_inj_grp); |
| + |
| +void ocelot_lock_xtr_grp(struct ocelot *ocelot, int grp) |
| + __acquires(&ocelot->inj_lock) |
| +{ |
| + spin_lock(&ocelot->inj_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_lock_xtr_grp); |
| + |
| +void ocelot_unlock_xtr_grp(struct ocelot *ocelot, int grp) |
| + __releases(&ocelot->inj_lock) |
| +{ |
| + spin_unlock(&ocelot->inj_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_unlock_xtr_grp); |
| + |
| +void ocelot_lock_xtr_grp_bh(struct ocelot *ocelot, int grp) |
| + __acquires(&ocelot->xtr_lock) |
| +{ |
| + spin_lock_bh(&ocelot->xtr_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_lock_xtr_grp_bh); |
| + |
| +void ocelot_unlock_xtr_grp_bh(struct ocelot *ocelot, int grp) |
| + __releases(&ocelot->xtr_lock) |
| +{ |
| + spin_unlock_bh(&ocelot->xtr_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(ocelot_unlock_xtr_grp_bh); |
| + |
| int ocelot_xtr_poll_frame(struct ocelot *ocelot, int grp, struct sk_buff **nskb) |
| { |
| u64 timestamp, src_port, len; |
| @@ -1004,6 +1046,8 @@ int ocelot_xtr_poll_frame(struct ocelot *ocelot, int grp, struct sk_buff **nskb) |
| u32 val, *buf; |
| int err; |
| |
| + lockdep_assert_held(&ocelot->xtr_lock); |
| + |
| err = ocelot_xtr_poll_xfh(ocelot, grp, xfh); |
| if (err) |
| return err; |
| @@ -1079,6 +1123,8 @@ bool ocelot_can_inject(struct ocelot *ocelot, int grp) |
| { |
| u32 val = ocelot_read(ocelot, QS_INJ_STATUS); |
| |
| + lockdep_assert_held(&ocelot->inj_lock); |
| + |
| if (!(val & QS_INJ_STATUS_FIFO_RDY(BIT(grp)))) |
| return false; |
| if (val & QS_INJ_STATUS_WMARK_REACHED(BIT(grp))) |
| @@ -1131,6 +1177,8 @@ void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp, |
| u32 ifh[OCELOT_TAG_LEN / 4]; |
| unsigned int i, count, last; |
| |
| + lockdep_assert_held(&ocelot->inj_lock); |
| + |
| ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(1) | |
| QS_INJ_CTRL_SOF, QS_INJ_CTRL, grp); |
| |
| @@ -1167,6 +1215,8 @@ EXPORT_SYMBOL(ocelot_port_inject_frame); |
| |
| void ocelot_drain_cpu_queue(struct ocelot *ocelot, int grp) |
| { |
| + lockdep_assert_held(&ocelot->xtr_lock); |
| + |
| while (ocelot_read(ocelot, QS_XTR_DATA_PRESENT) & BIT(grp)) |
| ocelot_read_rix(ocelot, QS_XTR_RD, grp); |
| } |
| @@ -2758,6 +2808,8 @@ int ocelot_init(struct ocelot *ocelot) |
| mutex_init(&ocelot->tas_lock); |
| spin_lock_init(&ocelot->ptp_clock_lock); |
| spin_lock_init(&ocelot->ts_id_lock); |
| + spin_lock_init(&ocelot->inj_lock); |
| + spin_lock_init(&ocelot->xtr_lock); |
| |
| ocelot->owq = alloc_ordered_workqueue("ocelot-owq", 0); |
| if (!ocelot->owq) |
| diff --git a/drivers/net/ethernet/mscc/ocelot_vsc7514.c b/drivers/net/ethernet/mscc/ocelot_vsc7514.c |
| index 6f22aea08a644..bf39a053dc82f 100644 |
| --- a/drivers/net/ethernet/mscc/ocelot_vsc7514.c |
| +++ b/drivers/net/ethernet/mscc/ocelot_vsc7514.c |
| @@ -159,6 +159,8 @@ static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg) |
| struct ocelot *ocelot = arg; |
| int grp = 0, err; |
| |
| + ocelot_lock_xtr_grp(ocelot, grp); |
| + |
| while (ocelot_read(ocelot, QS_XTR_DATA_PRESENT) & BIT(grp)) { |
| struct sk_buff *skb; |
| |
| @@ -177,6 +179,8 @@ static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg) |
| if (err < 0) |
| ocelot_drain_cpu_queue(ocelot, 0); |
| |
| + ocelot_unlock_xtr_grp(ocelot, grp); |
| + |
| return IRQ_HANDLED; |
| } |
| |
| diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h |
| index 9b904ea2f0db9..9b5562f545486 100644 |
| --- a/include/soc/mscc/ocelot.h |
| +++ b/include/soc/mscc/ocelot.h |
| @@ -977,6 +977,9 @@ struct ocelot { |
| const struct ocelot_stat_layout *stats_layout; |
| struct list_head stats_regions; |
| |
| + spinlock_t inj_lock; |
| + spinlock_t xtr_lock; |
| + |
| u32 pool_size[OCELOT_SB_NUM][OCELOT_SB_POOL_NUM]; |
| int packet_buffer_size; |
| int num_frame_refs; |
| @@ -1125,6 +1128,12 @@ void __ocelot_target_write_ix(struct ocelot *ocelot, enum ocelot_target target, |
| u32 val, u32 reg, u32 offset); |
| |
| /* Packet I/O */ |
| +void ocelot_lock_inj_grp(struct ocelot *ocelot, int grp); |
| +void ocelot_unlock_inj_grp(struct ocelot *ocelot, int grp); |
| +void ocelot_lock_xtr_grp(struct ocelot *ocelot, int grp); |
| +void ocelot_unlock_xtr_grp(struct ocelot *ocelot, int grp); |
| +void ocelot_lock_xtr_grp_bh(struct ocelot *ocelot, int grp); |
| +void ocelot_unlock_xtr_grp_bh(struct ocelot *ocelot, int grp); |
| bool ocelot_can_inject(struct ocelot *ocelot, int grp); |
| void ocelot_port_inject_frame(struct ocelot *ocelot, int port, int grp, |
| u32 rew_op, struct sk_buff *skb); |
| -- |
| 2.43.0 |
| |