* [PATCH v6 0/3] can: rx-offload: add a per-IRQ receive context
@ 2026-09-25 14:45 Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown Ciprian Costea
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Ciprian Costea @ 2026-09-25 14:45 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Dario Binacchi, Max Staudt,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo, Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
The rx-offload IRQ handler fills skb_irq_queue without a lock and the
finish helpers splice it into skb_queue under skb_queue.lock. This only
works with a single producer. flexcan requests the same handler on every
IRQ line: the mailbox line, the bus off and error lines
(FLEXCAN_QUIRK_NR_IRQ_3) and a second mailbox line
(FLEXCAN_QUIRK_SECONDARY_MB_IRQ). On NXP S32G2 all four are used, so the
handlers can run at the same time on different CPUs and corrupt the queue.
As Marc suggested, each IRQ line now gets its own receive context, similar
to NAPI. struct can_rx_offload_irq holds skb_irq_queue, skb_queue_len_max
and the mailbox range, skb_queue and napi stay in struct can_rx_offload.
With a single context the finish helpers still use
skb_queue_splice_tail_init(), with more than one the skbs are sorted into
skb_queue.
Patch 1 is an at91_can fix, independent of the rest. Patch 2 adds the
per-IRQ context and converts all rx-offload users. Patch 3 gives each
flexcan IRQ line its own context.
All lines still read the whole mailbox range, so two of them can read the
same mailbox. This series does not change that, the range split is part
of the flexcan multi-IRQ patches along with the S32N79 FlexCAN support.
Testing on S32G2 is in progress, I will follow up in this thread.
Changes since v5:
- Replaced the per-CPU skb_irq_queue, which does not work in preemptible
context, with a per-IRQ receive context, as suggested by Marc Kleine-Budde.
- Added patch 3 with the flexcan conversion. The bus off and error lines get
their own context too, not only the mailbox lines.
- Moved the at91_can fix to its own patch.
- Dropped the gs_usb can_rx_offload_add_manual() return value check, the
NULL pointer dereference it guarded against went away with the per-CPU
allocation.
- Dropped Haibo's Reviewed-by from the rx-offload patch, since it was
rewritten.
- Added Assisted-by tags.
Changes since v4:
- rx-offload: expand the comment above the for_each_possible_cpu() loop
in can_rx_offload_threaded_irq_finish() to add the single-producer
assumption (IRQ requested with IRQF_ONESHOT / handler non-reentrant).
Suggested by Haibo Chen.
- rx-offload: add Reviewed-by: Haibo Chen <haibo.chen@nxp.com>
Changes since v3:
- In gs_usb driver, check the can_rx_offload_add_manual() return value,
the same NULL-deref the per-CPU change exposes.
Changes since v2:
- at91_can: also add can_rx_offload_del() on the register_candev() error
path and check the can_rx_offload_add_timestamp() return value.
Changes since v1:
- The enqueue helpers used this_cpu_ptr() without disabling preemption.
All four enqueue helpers now use get_cpu_ptr()/put_cpu_ptr().
- Guard can_rx_offload_del() against skb_irq_queue == NULL.
- Fix 'at91_can' memory leak by adding missing 'can_rx_offload_del'.
Ciprian Marian Costea (3):
can: at91_can: release the rx-offload on teardown
can: rx-offload: add a per-IRQ receive context
can: flexcan: use one rx-offload context per IRQ line
drivers/net/can/at91_can.c | 28 ++-
drivers/net/can/bxcan.c | 14 +-
drivers/net/can/can327.c | 8 +-
drivers/net/can/dev/rx-offload.c | 196 ++++++++++++------
drivers/net/can/flexcan/flexcan-core.c | 109 ++++++++--
drivers/net/can/flexcan/flexcan-ethtool.c | 4 +-
drivers/net/can/flexcan/flexcan.h | 4 +
drivers/net/can/m_can/m_can.c | 7 +-
drivers/net/can/m_can/m_can.h | 1 +
.../net/can/rockchip/rockchip_canfd-core.c | 9 +-
drivers/net/can/rockchip/rockchip_canfd-rx.c | 2 +-
drivers/net/can/rockchip/rockchip_canfd-tx.c | 2 +-
drivers/net/can/rockchip/rockchip_canfd.h | 1 +
.../net/can/spi/mcp251xfd/mcp251xfd-core.c | 13 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c | 2 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c | 2 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd.h | 1 +
drivers/net/can/ti_hecc.c | 18 +-
drivers/net/can/usb/gs_usb.c | 23 +-
drivers/net/can/usb/nct6694_canfd.c | 20 +-
include/linux/can/rx-offload.h | 48 ++++-
21 files changed, 349 insertions(+), 163 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown
2026-09-25 14:45 [PATCH v6 0/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
@ 2026-09-25 14:45 ` Ciprian Costea
2026-09-26 14:53 ` Max Staudt
2026-09-25 14:45 ` [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 3/3] can: flexcan: use one rx-offload context per IRQ line Ciprian Costea
2 siblings, 1 reply; 6+ messages in thread
From: Ciprian Costea @ 2026-09-25 14:45 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Dario Binacchi, Max Staudt,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo, Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
at91_can_probe() ignores the return value of can_rx_offload_add_timestamp()
and can_rx_offload_del() is never called, neither on the register_candev()
error path nor in at91_can_remove(). Any skbs left in the offload queues
are leaked.
Check the return value and call can_rx_offload_del() on both paths.
Fixes: 137f59d5dab4 ("can: at91_can: switch to rx-offload implementation")
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
drivers/net/can/at91_can.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/net/can/at91_can.c b/drivers/net/can/at91_can.c
index 58da323f14d7..09aa676a03fb 100644
--- a/drivers/net/can/at91_can.c
+++ b/drivers/net/can/at91_can.c
@@ -1123,7 +1123,11 @@ static int at91_can_probe(struct platform_device *pdev)
priv->offload.mb_first = devtype_data->rx_first;
priv->offload.mb_last = devtype_data->rx_last;
- can_rx_offload_add_timestamp(dev, &priv->offload);
+ err = can_rx_offload_add_timestamp(dev, &priv->offload);
+ if (err) {
+ dev_err(&pdev->dev, "can_rx_offload_add_timestamp() failed\n");
+ goto exit_free;
+ }
if (transceiver)
priv->can.bitrate_max = transceiver->attrs.max_link_rate;
@@ -1137,7 +1141,7 @@ static int at91_can_probe(struct platform_device *pdev)
err = register_candev(dev);
if (err) {
dev_err(&pdev->dev, "registering netdev failed\n");
- goto exit_free;
+ goto exit_offload;
}
dev_info(&pdev->dev, "device registered (reg_base=%p, irq=%d)\n",
@@ -1145,6 +1149,8 @@ static int at91_can_probe(struct platform_device *pdev)
return 0;
+ exit_offload:
+ can_rx_offload_del(&priv->offload);
exit_free:
free_candev(dev);
exit_iounmap:
@@ -1165,6 +1171,8 @@ static void at91_can_remove(struct platform_device *pdev)
unregister_netdev(dev);
+ can_rx_offload_del(&priv->offload);
+
iounmap(priv->reg_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context
2026-09-25 14:45 [PATCH v6 0/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown Ciprian Costea
@ 2026-09-25 14:45 ` Ciprian Costea
2026-09-26 15:26 ` Max Staudt
2026-09-25 14:45 ` [PATCH v6 3/3] can: flexcan: use one rx-offload context per IRQ line Ciprian Costea
2 siblings, 1 reply; 6+ messages in thread
From: Ciprian Costea @ 2026-09-25 14:45 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Dario Binacchi, Max Staudt,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo, Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
The IRQ handler fills skb_irq_queue without a lock and the finish helpers
then splice it into skb_queue under skb_queue.lock. This breaks when a
driver uses the helpers from more than one IRQ line. On NXP S32G2, the
flexcan handlers can run at the same time on different CPUs and corrupt
skb_irq_queue.
Add struct can_rx_offload_irq, one per IRQ line, which holds
skb_irq_queue, skb_queue_len_max and the mailbox range. skb_queue and napi
stay in struct can_rx_offload and are shared by all contexts.
With a single context the finish helpers splice the queue as before. With
more than one, the skbs are sorted into skb_queue by timestamp, unless the
driver queues them without one, in which case they are appended.
The queue, echo and finish helpers now take a struct can_rx_offload_irq,
the add helpers take the offload and the context. The NAPI weight is the
largest weight of the registered contexts and the queue length limit
derived from it is shared.
Convert all users to a single context, no functional change for them.
flexcan is converted to one context per IRQ line in the next patch.
Suggested-by: Marc Kleine-Budde <mkl@pengutronix.de>
Assisted-by: LLM
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
drivers/net/can/at91_can.c | 18 +-
drivers/net/can/bxcan.c | 14 +-
drivers/net/can/can327.c | 8 +-
drivers/net/can/dev/rx-offload.c | 196 ++++++++++++------
drivers/net/can/flexcan/flexcan-core.c | 26 +--
drivers/net/can/flexcan/flexcan-ethtool.c | 4 +-
drivers/net/can/flexcan/flexcan.h | 1 +
drivers/net/can/m_can/m_can.c | 7 +-
drivers/net/can/m_can/m_can.h | 1 +
.../net/can/rockchip/rockchip_canfd-core.c | 9 +-
drivers/net/can/rockchip/rockchip_canfd-rx.c | 2 +-
drivers/net/can/rockchip/rockchip_canfd-tx.c | 2 +-
drivers/net/can/rockchip/rockchip_canfd.h | 1 +
.../net/can/spi/mcp251xfd/mcp251xfd-core.c | 13 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c | 2 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c | 2 +-
drivers/net/can/spi/mcp251xfd/mcp251xfd.h | 1 +
drivers/net/can/ti_hecc.c | 18 +-
drivers/net/can/usb/gs_usb.c | 23 +-
drivers/net/can/usb/nct6694_canfd.c | 20 +-
include/linux/can/rx-offload.h | 48 ++++-
21 files changed, 263 insertions(+), 153 deletions(-)
diff --git a/drivers/net/can/at91_can.c b/drivers/net/can/at91_can.c
index 09aa676a03fb..f7b775f7c6d7 100644
--- a/drivers/net/can/at91_can.c
+++ b/drivers/net/can/at91_can.c
@@ -151,6 +151,7 @@ struct at91_devtype_data {
struct at91_priv {
struct can_priv can; /* must be the first member! */
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct phy *transceiver;
void __iomem *reg_base;
@@ -568,7 +569,7 @@ static void at91_rx_overflow_err(struct net_device *dev)
cf->can_id |= CAN_ERR_CRTL;
cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
}
@@ -745,7 +746,7 @@ static void at91_irq_err_line(struct net_device *dev, const u32 reg_sr)
cf->data[7] = bec.rxerr;
}
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
}
@@ -810,7 +811,7 @@ static void at91_irq_err_frame(struct net_device *dev, const u32 reg_sr)
if (!cf)
return;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
}
@@ -836,7 +837,7 @@ static irqreturn_t at91_irq(int irq, void *dev_id)
* Some bits of AT91_SR are cleared on read, keep them in reg_sr.
*/
while ((reg_sr_rx = at91_get_reg_sr_rx(priv, ®_sr))) {
- ret = can_rx_offload_irq_offload_timestamp(&priv->offload,
+ ret = can_rx_offload_irq_offload_timestamp(&priv->offload_irq,
reg_sr_rx);
handled = IRQ_HANDLED;
@@ -864,7 +865,7 @@ static irqreturn_t at91_irq(int irq, void *dev_id)
}
if (handled)
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return handled;
}
@@ -1120,10 +1121,11 @@ static int at91_can_probe(struct platform_device *pdev)
priv->pdata = dev_get_platdata(&pdev->dev);
priv->mb0_id = 0x7ff;
priv->offload.mailbox_read = at91_mailbox_read;
- priv->offload.mb_first = devtype_data->rx_first;
- priv->offload.mb_last = devtype_data->rx_last;
+ priv->offload_irq.mb_first = devtype_data->rx_first;
+ priv->offload_irq.mb_last = devtype_data->rx_last;
- err = can_rx_offload_add_timestamp(dev, &priv->offload);
+ err = can_rx_offload_add_timestamp(dev, &priv->offload,
+ &priv->offload_irq);
if (err) {
dev_err(&pdev->dev, "can_rx_offload_add_timestamp() failed\n");
goto exit_free;
diff --git a/drivers/net/can/bxcan.c b/drivers/net/can/bxcan.c
index baf494d20bef..fd032e094ee3 100644
--- a/drivers/net/can/bxcan.c
+++ b/drivers/net/can/bxcan.c
@@ -165,6 +165,7 @@ struct bxcan_regs {
struct bxcan_priv {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct device *dev;
struct net_device *ndev;
@@ -420,8 +421,8 @@ static irqreturn_t bxcan_rx_isr(int irq, void *dev_id)
if (!(rf0r & BXCAN_RF0R_FMP0_MASK))
return IRQ_NONE;
- can_rx_offload_irq_offload_fifo(&priv->offload);
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_offload_fifo(&priv->offload_irq);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return IRQ_HANDLED;
}
@@ -507,7 +508,7 @@ static void bxcan_handle_state_change(struct net_device *ndev, u32 esr)
if (skb) {
int err;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb,
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb,
priv->timestamp);
if (err)
ndev->stats.rx_fifo_errors++;
@@ -592,7 +593,7 @@ static void bxcan_handle_bus_err(struct net_device *ndev, u32 esr)
if (skb) {
int err;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb,
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb,
priv->timestamp);
if (err)
ndev->stats.rx_fifo_errors++;
@@ -618,7 +619,7 @@ static irqreturn_t bxcan_state_change_isr(int irq, void *dev_id)
msr |= BXCAN_MSR_ERRI;
writel(msr, ®s->msr);
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return IRQ_HANDLED;
}
@@ -1008,7 +1009,8 @@ static int bxcan_probe(struct platform_device *pdev)
CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING;
priv->offload.mailbox_read = bxcan_mailbox_read;
- err = can_rx_offload_add_fifo(ndev, &priv->offload, BXCAN_NAPI_WEIGHT);
+ err = can_rx_offload_add_fifo(ndev, &priv->offload,
+ &priv->offload_irq, BXCAN_NAPI_WEIGHT);
if (err) {
dev_err(dev, "failed to add FIFO rx_offload\n");
goto out_free_candev;
diff --git a/drivers/net/can/can327.c b/drivers/net/can/can327.c
index 90f5e35f3c8f..6d505ac7d8b9 100644
--- a/drivers/net/can/can327.c
+++ b/drivers/net/can/can327.c
@@ -69,6 +69,7 @@ struct can327 {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
/* TTY buffers */
u8 txbuf[CAN327_SIZE_TXBUF];
@@ -272,11 +273,11 @@ static void can327_feed_frame_to_netdev(struct can327 *elm, struct sk_buff *skb)
/* Queue for NAPI pickup.
* rx-offload will update stats and LEDs for us.
*/
- if (can_rx_offload_queue_tail(&elm->offload, skb))
+ if (can_rx_offload_queue_tail(&elm->offload_irq, skb))
elm->dev->stats.rx_fifo_errors++;
/* Wake NAPI */
- can_rx_offload_irq_finish(&elm->offload);
+ can_rx_offload_irq_finish(&elm->offload_irq);
}
/* Called when we're out of ideas and just want it all to end. */
@@ -772,7 +773,8 @@ static int can327_netdev_open(struct net_device *dev)
can327_init_device(elm);
spin_unlock_bh(&elm->lock);
- err = can_rx_offload_add_manual(dev, &elm->offload, CAN327_NAPI_WEIGHT);
+ err = can_rx_offload_add_manual(dev, &elm->offload,
+ &elm->offload_irq, CAN327_NAPI_WEIGHT);
if (err) {
close_candev(dev);
return err;
diff --git a/drivers/net/can/dev/rx-offload.c b/drivers/net/can/dev/rx-offload.c
index 46e7b6db4a1e..854be0b2c051 100644
--- a/drivers/net/can/dev/rx-offload.c
+++ b/drivers/net/can/dev/rx-offload.c
@@ -21,19 +21,19 @@ can_rx_offload_get_cb(struct sk_buff *skb)
}
static inline bool
-can_rx_offload_le(struct can_rx_offload *offload,
+can_rx_offload_le(struct can_rx_offload_irq *offload_irq,
unsigned int a, unsigned int b)
{
- if (offload->inc)
+ if (offload_irq->inc)
return a <= b;
else
return a >= b;
}
static inline unsigned int
-can_rx_offload_inc(struct can_rx_offload *offload, unsigned int *val)
+can_rx_offload_inc(struct can_rx_offload_irq *offload_irq, unsigned int *val)
{
- if (offload->inc)
+ if (offload_irq->inc)
return (*val)++;
else
return (*val)--;
@@ -118,7 +118,7 @@ static int can_rx_offload_compare(struct sk_buff *a, struct sk_buff *b)
/**
* can_rx_offload_offload_one() - Read one CAN frame from HW
- * @offload: pointer to rx_offload context
+ * @offload_irq: pointer to rx_offload per-IRQ context
* @n: number of mailbox to read
*
* The task of this function is to read a CAN frame from mailbox @n
@@ -126,9 +126,9 @@ static int can_rx_offload_compare(struct sk_buff *a, struct sk_buff *b)
* sk_buff.
*
* If the struct can_rx_offload::skb_queue exceeds the maximal queue
- * length (struct can_rx_offload::skb_queue_len_max) or no skb can be
- * allocated, the mailbox contents is discarded by reading it into an
- * overflow buffer. This way the mailbox is marked as free by the
+ * length (struct can_rx_offload_irq::skb_queue_len_max) or no skb can
+ * be allocated, the mailbox contents is discarded by reading it into
+ * an overflow buffer. This way the mailbox is marked as free by the
* driver.
*
* Return: A pointer to skb containing the CAN frame on success.
@@ -138,8 +138,10 @@ static int can_rx_offload_compare(struct sk_buff *a, struct sk_buff *b)
* ERR_PTR() in case of an error
*/
static struct sk_buff *
-can_rx_offload_offload_one(struct can_rx_offload *offload, unsigned int n)
+can_rx_offload_offload_one(struct can_rx_offload_irq *offload_irq,
+ unsigned int n)
{
+ struct can_rx_offload *offload = offload_irq->offload;
struct sk_buff *skb;
struct can_rx_offload_cb *cb;
bool drop = false;
@@ -147,7 +149,7 @@ can_rx_offload_offload_one(struct can_rx_offload *offload, unsigned int n)
/* If queue is full drop frame */
if (unlikely(skb_queue_len(&offload->skb_queue) >
- offload->skb_queue_len_max))
+ offload_irq->skb_queue_len_max))
drop = true;
skb = offload->mailbox_read(offload, n, ×tamp, drop);
@@ -172,25 +174,25 @@ can_rx_offload_offload_one(struct can_rx_offload *offload, unsigned int n)
return skb;
}
-int can_rx_offload_irq_offload_timestamp(struct can_rx_offload *offload,
+int can_rx_offload_irq_offload_timestamp(struct can_rx_offload_irq *offload_irq,
u64 pending)
{
unsigned int i;
int received = 0;
- for (i = offload->mb_first;
- can_rx_offload_le(offload, i, offload->mb_last);
- can_rx_offload_inc(offload, &i)) {
+ for (i = offload_irq->mb_first;
+ can_rx_offload_le(offload_irq, i, offload_irq->mb_last);
+ can_rx_offload_inc(offload_irq, &i)) {
struct sk_buff *skb;
if (!(pending & BIT_ULL(i)))
continue;
- skb = can_rx_offload_offload_one(offload, i);
+ skb = can_rx_offload_offload_one(offload_irq, i);
if (IS_ERR_OR_NULL(skb))
continue;
- __skb_queue_add_sort(&offload->skb_irq_queue, skb,
+ __skb_queue_add_sort(&offload_irq->skb_irq_queue, skb,
can_rx_offload_compare);
received++;
}
@@ -199,19 +201,19 @@ int can_rx_offload_irq_offload_timestamp(struct can_rx_offload *offload,
}
EXPORT_SYMBOL_GPL(can_rx_offload_irq_offload_timestamp);
-int can_rx_offload_irq_offload_fifo(struct can_rx_offload *offload)
+int can_rx_offload_irq_offload_fifo(struct can_rx_offload_irq *offload_irq)
{
struct sk_buff *skb;
int received = 0;
while (1) {
- skb = can_rx_offload_offload_one(offload, 0);
+ skb = can_rx_offload_offload_one(offload_irq, 0);
if (IS_ERR(skb))
continue;
if (!skb)
break;
- __skb_queue_tail(&offload->skb_irq_queue, skb);
+ __skb_queue_tail(&offload_irq->skb_irq_queue, skb);
received++;
}
@@ -219,13 +221,14 @@ int can_rx_offload_irq_offload_fifo(struct can_rx_offload *offload)
}
EXPORT_SYMBOL_GPL(can_rx_offload_irq_offload_fifo);
-int can_rx_offload_queue_timestamp(struct can_rx_offload *offload,
+int can_rx_offload_queue_timestamp(struct can_rx_offload_irq *offload_irq,
struct sk_buff *skb, u32 timestamp)
{
+ struct can_rx_offload *offload = offload_irq->offload;
struct can_rx_offload_cb *cb;
if (skb_queue_len(&offload->skb_queue) >
- offload->skb_queue_len_max) {
+ offload_irq->skb_queue_len_max) {
dev_kfree_skb_any(skb);
return -ENOBUFS;
}
@@ -233,7 +236,7 @@ int can_rx_offload_queue_timestamp(struct can_rx_offload *offload,
cb = can_rx_offload_get_cb(skb);
cb->timestamp = timestamp;
- __skb_queue_add_sort(&offload->skb_irq_queue, skb,
+ __skb_queue_add_sort(&offload_irq->skb_irq_queue, skb,
can_rx_offload_compare);
return 0;
@@ -241,11 +244,11 @@ int can_rx_offload_queue_timestamp(struct can_rx_offload *offload,
EXPORT_SYMBOL_GPL(can_rx_offload_queue_timestamp);
unsigned int
-can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload *offload,
+can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload_irq *offload_irq,
unsigned int idx, u32 timestamp,
unsigned int *frame_len_ptr)
{
- struct net_device *dev = offload->dev;
+ struct net_device *dev = offload_irq->offload->dev;
struct net_device_stats *stats = &dev->stats;
struct sk_buff *skb;
unsigned int len;
@@ -255,7 +258,7 @@ can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload *offload,
if (!skb)
return 0;
- err = can_rx_offload_queue_timestamp(offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(offload_irq, skb, timestamp);
if (err) {
stats->rx_errors++;
stats->tx_fifo_errors++;
@@ -265,27 +268,29 @@ can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload *offload,
}
EXPORT_SYMBOL_GPL(can_rx_offload_get_echo_skb_queue_timestamp);
-int can_rx_offload_queue_tail(struct can_rx_offload *offload,
+int can_rx_offload_queue_tail(struct can_rx_offload_irq *offload_irq,
struct sk_buff *skb)
{
+ struct can_rx_offload *offload = offload_irq->offload;
+
if (skb_queue_len(&offload->skb_queue) >
- offload->skb_queue_len_max) {
+ offload_irq->skb_queue_len_max) {
dev_kfree_skb_any(skb);
return -ENOBUFS;
}
- __skb_queue_tail(&offload->skb_irq_queue, skb);
+ __skb_queue_tail(&offload_irq->skb_irq_queue, skb);
return 0;
}
EXPORT_SYMBOL_GPL(can_rx_offload_queue_tail);
unsigned int
-can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload *offload,
+can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload_irq *offload_irq,
unsigned int idx,
unsigned int *frame_len_ptr)
{
- struct net_device *dev = offload->dev;
+ struct net_device *dev = offload_irq->offload->dev;
struct net_device_stats *stats = &dev->stats;
struct sk_buff *skb;
unsigned int len;
@@ -295,7 +300,7 @@ can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload *offload,
if (!skb)
return 0;
- err = can_rx_offload_queue_tail(offload, skb);
+ err = can_rx_offload_queue_tail(offload_irq, skb);
if (err) {
stats->rx_errors++;
stats->tx_fifo_errors++;
@@ -305,20 +310,44 @@ can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload *offload,
}
EXPORT_SYMBOL_GPL(can_rx_offload_get_echo_skb_queue_tail);
-void can_rx_offload_irq_finish(struct can_rx_offload *offload)
+static void
+can_rx_offload_move_to_skb_queue(struct can_rx_offload_irq *offload_irq)
{
+ struct can_rx_offload *offload = offload_irq->offload;
unsigned long flags;
+
+ spin_lock_irqsave(&offload->skb_queue.lock, flags);
+
+ if (offload->irq_cnt > 1 && offload->sort) {
+ /* Merge this IRQ's sorted queue into skb_queue to keep the
+ * overall timestamp order.
+ */
+ struct sk_buff *skb;
+
+ while ((skb = __skb_dequeue(&offload_irq->skb_irq_queue)))
+ __skb_queue_add_sort(&offload->skb_queue, skb,
+ can_rx_offload_compare);
+ } else {
+ /* Single producer, or no timestamp to sort by. */
+ skb_queue_splice_tail_init(&offload_irq->skb_irq_queue,
+ &offload->skb_queue);
+ }
+
+ spin_unlock_irqrestore(&offload->skb_queue.lock, flags);
+}
+
+void can_rx_offload_irq_finish(struct can_rx_offload_irq *offload_irq)
+{
+ struct can_rx_offload *offload = offload_irq->offload;
int queue_len;
- if (skb_queue_empty_lockless(&offload->skb_irq_queue))
+ if (skb_queue_empty_lockless(&offload_irq->skb_irq_queue))
return;
- spin_lock_irqsave(&offload->skb_queue.lock, flags);
- skb_queue_splice_tail_init(&offload->skb_irq_queue, &offload->skb_queue);
- spin_unlock_irqrestore(&offload->skb_queue.lock, flags);
+ can_rx_offload_move_to_skb_queue(offload_irq);
queue_len = skb_queue_len(&offload->skb_queue);
- if (queue_len > offload->skb_queue_len_max / 8)
+ if (queue_len > offload_irq->skb_queue_len_max / 8)
netdev_dbg(offload->dev, "%s: queue_len=%d\n",
__func__, queue_len);
@@ -326,20 +355,18 @@ void can_rx_offload_irq_finish(struct can_rx_offload *offload)
}
EXPORT_SYMBOL_GPL(can_rx_offload_irq_finish);
-void can_rx_offload_threaded_irq_finish(struct can_rx_offload *offload)
+void can_rx_offload_threaded_irq_finish(struct can_rx_offload_irq *offload_irq)
{
- unsigned long flags;
+ struct can_rx_offload *offload = offload_irq->offload;
int queue_len;
- if (skb_queue_empty_lockless(&offload->skb_irq_queue))
+ if (skb_queue_empty_lockless(&offload_irq->skb_irq_queue))
return;
- spin_lock_irqsave(&offload->skb_queue.lock, flags);
- skb_queue_splice_tail_init(&offload->skb_irq_queue, &offload->skb_queue);
- spin_unlock_irqrestore(&offload->skb_queue.lock, flags);
+ can_rx_offload_move_to_skb_queue(offload_irq);
queue_len = skb_queue_len(&offload->skb_queue);
- if (queue_len > offload->skb_queue_len_max / 8)
+ if (queue_len > offload_irq->skb_queue_len_max / 8)
netdev_dbg(offload->dev, "%s: queue_len=%d\n",
__func__, queue_len);
@@ -351,64 +378,88 @@ EXPORT_SYMBOL_GPL(can_rx_offload_threaded_irq_finish);
static int can_rx_offload_init_queue(struct net_device *dev,
struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq,
unsigned int weight)
{
- offload->dev = dev;
-
- /* Limit queue len to 4x the weight (rounded to next power of two) */
- offload->skb_queue_len_max = 2 << fls(weight);
- offload->skb_queue_len_max *= 4;
- skb_queue_head_init(&offload->skb_queue);
- __skb_queue_head_init(&offload->skb_irq_queue);
+ struct can_rx_offload_irq *pos;
+ u32 skb_queue_len_max;
+
+ offload_irq->offload = offload;
+ __skb_queue_head_init(&offload_irq->skb_irq_queue);
+
+ /* The first registered IRQ initializes the shared state. */
+ if (!offload->irq_cnt) {
+ offload->dev = dev;
+ skb_queue_head_init(&offload->skb_queue);
+ INIT_LIST_HEAD(&offload->irqs);
+ netif_napi_add_weight(dev, &offload->napi,
+ can_rx_offload_napi_poll, weight);
+ } else if (weight > offload->napi.weight) {
+ /* All contexts feed the same NAPI, keep the largest weight. */
+ offload->napi.weight = weight;
+ }
+ list_add_tail(&offload_irq->node, &offload->irqs);
+ offload->irq_cnt++;
- netif_napi_add_weight(dev, &offload->napi, can_rx_offload_napi_poll,
- weight);
+ /* Limit queue len to 4x the weight (rounded to next power of two).
+ * All contexts feed the same skb_queue, so they share its limit.
+ */
+ skb_queue_len_max = 2 << fls(offload->napi.weight);
+ skb_queue_len_max *= 4;
+ list_for_each_entry(pos, &offload->irqs, node)
+ pos->skb_queue_len_max = skb_queue_len_max;
dev_dbg(dev->dev.parent, "%s: skb_queue_len_max=%d\n",
- __func__, offload->skb_queue_len_max);
+ __func__, skb_queue_len_max);
return 0;
}
int can_rx_offload_add_timestamp(struct net_device *dev,
- struct can_rx_offload *offload)
+ struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq)
{
unsigned int weight;
- if (offload->mb_first > BITS_PER_LONG_LONG ||
- offload->mb_last > BITS_PER_LONG_LONG || !offload->mailbox_read)
+ if (offload_irq->mb_first > BITS_PER_LONG_LONG ||
+ offload_irq->mb_last > BITS_PER_LONG_LONG || !offload->mailbox_read)
return -EINVAL;
- if (offload->mb_first < offload->mb_last) {
- offload->inc = true;
- weight = offload->mb_last - offload->mb_first;
+ if (offload_irq->mb_first < offload_irq->mb_last) {
+ offload_irq->inc = true;
+ weight = offload_irq->mb_last - offload_irq->mb_first;
} else {
- offload->inc = false;
- weight = offload->mb_first - offload->mb_last;
+ offload_irq->inc = false;
+ weight = offload_irq->mb_first - offload_irq->mb_last;
}
- return can_rx_offload_init_queue(dev, offload, weight);
+ offload->sort = true;
+
+ return can_rx_offload_init_queue(dev, offload, offload_irq, weight);
}
EXPORT_SYMBOL_GPL(can_rx_offload_add_timestamp);
int can_rx_offload_add_fifo(struct net_device *dev,
- struct can_rx_offload *offload, unsigned int weight)
+ struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq,
+ unsigned int weight)
{
if (!offload->mailbox_read)
return -EINVAL;
- return can_rx_offload_init_queue(dev, offload, weight);
+ return can_rx_offload_init_queue(dev, offload, offload_irq, weight);
}
EXPORT_SYMBOL_GPL(can_rx_offload_add_fifo);
int can_rx_offload_add_manual(struct net_device *dev,
struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq,
unsigned int weight)
{
if (offload->mailbox_read)
return -EINVAL;
- return can_rx_offload_init_queue(dev, offload, weight);
+ return can_rx_offload_init_queue(dev, offload, offload_irq, weight);
}
EXPORT_SYMBOL_GPL(can_rx_offload_add_manual);
@@ -420,8 +471,19 @@ EXPORT_SYMBOL_GPL(can_rx_offload_enable);
void can_rx_offload_del(struct can_rx_offload *offload)
{
+ struct can_rx_offload_irq *offload_irq, *tmp;
+
netif_napi_del(&offload->napi);
skb_queue_purge(&offload->skb_queue);
- __skb_queue_purge(&offload->skb_irq_queue);
+
+ /* Drop the contexts, so that re-adding them (e.g. on the next
+ * ndo_open()) initializes the shared state again.
+ */
+ list_for_each_entry_safe(offload_irq, tmp, &offload->irqs, node) {
+ __skb_queue_purge(&offload_irq->skb_irq_queue);
+ list_del_init(&offload_irq->node);
+ }
+ offload->irq_cnt = 0;
+ offload->sort = false;
}
EXPORT_SYMBOL_GPL(can_rx_offload_del);
diff --git a/drivers/net/can/flexcan/flexcan-core.c b/drivers/net/can/flexcan/flexcan-core.c
index f5d22c61503f..a81132d3afa8 100644
--- a/drivers/net/can/flexcan/flexcan-core.c
+++ b/drivers/net/can/flexcan/flexcan-core.c
@@ -885,7 +885,7 @@ static void flexcan_irq_bus_err(struct net_device *dev, u32 reg_esr)
if (tx_errors)
dev->stats.tx_errors++;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
dev->stats.rx_fifo_errors++;
}
@@ -932,7 +932,7 @@ static void flexcan_irq_state(struct net_device *dev, u32 reg_esr)
if (unlikely(new_state == CAN_STATE_BUS_OFF))
can_bus_off(dev);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
dev->stats.rx_fifo_errors++;
}
@@ -1088,7 +1088,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
while ((reg_iflag_rx = flexcan_read_reg_iflag_rx(priv))) {
handled = IRQ_HANDLED;
- ret = can_rx_offload_irq_offload_timestamp(&priv->offload,
+ ret = can_rx_offload_irq_offload_timestamp(&priv->offload_irq,
reg_iflag_rx);
if (!ret)
break;
@@ -1099,7 +1099,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
reg_iflag1 = priv->read(®s->iflag1);
if (reg_iflag1 & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE) {
handled = IRQ_HANDLED;
- can_rx_offload_irq_offload_fifo(&priv->offload);
+ can_rx_offload_irq_offload_fifo(&priv->offload_irq);
}
/* FIFO overflow interrupt */
@@ -1120,7 +1120,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
handled = IRQ_HANDLED;
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload, 0,
+ can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload_irq, 0,
reg_ctrl << 16, NULL);
stats->tx_packets++;
@@ -1189,7 +1189,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
}
if (handled)
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return handled;
}
@@ -1410,16 +1410,18 @@ static int flexcan_rx_offload_setup(struct net_device *dev)
priv->offload.mailbox_read = flexcan_mailbox_read;
if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX) {
- priv->offload.mb_first = FLEXCAN_RX_MB_RX_MAILBOX_FIRST;
- priv->offload.mb_last = priv->mb_count - 2;
+ priv->offload_irq.mb_first = FLEXCAN_RX_MB_RX_MAILBOX_FIRST;
+ priv->offload_irq.mb_last = priv->mb_count - 2;
- priv->rx_mask = GENMASK_ULL(priv->offload.mb_last,
- priv->offload.mb_first);
- err = can_rx_offload_add_timestamp(dev, &priv->offload);
+ priv->rx_mask = GENMASK_ULL(priv->offload_irq.mb_last,
+ priv->offload_irq.mb_first);
+ err = can_rx_offload_add_timestamp(dev, &priv->offload,
+ &priv->offload_irq);
} else {
priv->rx_mask = FLEXCAN_IFLAG_RX_FIFO_OVERFLOW |
FLEXCAN_IFLAG_RX_FIFO_AVAILABLE;
err = can_rx_offload_add_fifo(dev, &priv->offload,
+ &priv->offload_irq,
FLEXCAN_NAPI_WEIGHT);
}
@@ -1600,7 +1602,7 @@ static int flexcan_chip_start(struct net_device *dev)
}
if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX) {
- for (i = priv->offload.mb_first; i <= priv->offload.mb_last; i++) {
+ for (i = priv->offload_irq.mb_first; i <= priv->offload_irq.mb_last; i++) {
mb = flexcan_get_mb(priv, i);
priv->write(FLEXCAN_MB_CODE_RX_EMPTY,
&mb->can_ctrl);
diff --git a/drivers/net/can/flexcan/flexcan-ethtool.c b/drivers/net/can/flexcan/flexcan-ethtool.c
index 50e86b2da532..e34afafbbd54 100644
--- a/drivers/net/can/flexcan/flexcan-ethtool.c
+++ b/drivers/net/can/flexcan/flexcan-ethtool.c
@@ -28,8 +28,8 @@ flexcan_get_ringparam(struct net_device *ndev, struct ethtool_ringparam *ring,
ring->tx_max_pending = priv->mb_count;
if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX)
- ring->rx_pending = priv->offload.mb_last -
- priv->offload.mb_first + 1;
+ ring->rx_pending = priv->offload_irq.mb_last -
+ priv->offload_irq.mb_first + 1;
else
ring->rx_pending = 6; /* RX-FIFO depth is fixed */
diff --git a/drivers/net/can/flexcan/flexcan.h b/drivers/net/can/flexcan/flexcan.h
index 16692a2502eb..90f9c2b9fed8 100644
--- a/drivers/net/can/flexcan/flexcan.h
+++ b/drivers/net/can/flexcan/flexcan.h
@@ -88,6 +88,7 @@ struct flexcan_stop_mode {
struct flexcan_priv {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct device *dev;
struct flexcan_regs __iomem *regs;
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index 16f80607e150..be66c52a09ff 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -543,7 +543,7 @@ static void m_can_receive_skb(struct m_can_classdev *cdev,
struct net_device_stats *stats = &cdev->net->stats;
int err;
- err = can_rx_offload_queue_timestamp(&cdev->offload, skb,
+ err = can_rx_offload_queue_timestamp(&cdev->offload_irq, skb,
timestamp);
if (err)
stats->rx_fifo_errors++;
@@ -1100,7 +1100,7 @@ static unsigned int m_can_tx_update_stats(struct m_can_classdev *cdev,
if (cdev->is_peripheral)
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&cdev->offload,
+ can_rx_offload_get_echo_skb_queue_timestamp(&cdev->offload_irq,
msg_mark,
timestamp,
&frame_len);
@@ -1303,7 +1303,7 @@ static int m_can_interrupt_handler(struct m_can_classdev *cdev)
}
if (cdev->is_peripheral)
- can_rx_offload_threaded_irq_finish(&cdev->offload);
+ can_rx_offload_threaded_irq_finish(&cdev->offload_irq);
return IRQ_HANDLED;
}
@@ -2535,6 +2535,7 @@ int m_can_class_register(struct m_can_classdev *cdev)
if (cdev->is_peripheral) {
ret = can_rx_offload_add_manual(cdev->net, &cdev->offload,
+ &cdev->offload_irq,
NAPI_POLL_WEIGHT);
if (ret)
goto out_reset_control_assert;
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index 4743342b2fba..dfec64aeffa5 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -81,6 +81,7 @@ struct m_can_tx_op {
struct m_can_classdev {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct napi_struct napi;
struct net_device *net;
struct device *dev;
diff --git a/drivers/net/can/rockchip/rockchip_canfd-core.c b/drivers/net/can/rockchip/rockchip_canfd-core.c
index 37c1c22c40c9..db4fa0c8a171 100644
--- a/drivers/net/can/rockchip/rockchip_canfd-core.c
+++ b/drivers/net/can/rockchip/rockchip_canfd-core.c
@@ -563,7 +563,7 @@ static int rkcanfd_handle_error_int(struct rkcanfd_priv *priv)
if (!cf)
return 0;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
@@ -608,7 +608,7 @@ static int rkcanfd_handle_state_error_int(struct rkcanfd_priv *priv)
cf->data[7] = bec.rxerr;
}
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
@@ -641,7 +641,7 @@ rkcanfd_handle_rx_fifo_overflow_int(struct rkcanfd_priv *priv)
cf->data[6] = bec.txerr;
cf->data[7] = bec.rxerr;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
stats->rx_fifo_errors++;
@@ -715,7 +715,7 @@ static irqreturn_t rkcanfd_irq(int irq, void *dev_id)
if (reg_int & RKCANFD_REG_INT_OVERLOAD_INT)
netdev_info(priv->ndev, "%s: OVERLOAD_INT\n", __func__);
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return IRQ_HANDLED;
}
@@ -930,6 +930,7 @@ static int rkcanfd_probe(struct platform_device *pdev)
}
err = can_rx_offload_add_manual(ndev, &priv->offload,
+ &priv->offload_irq,
RKCANFD_NAPI_WEIGHT);
if (err)
goto out_free_candev;
diff --git a/drivers/net/can/rockchip/rockchip_canfd-rx.c b/drivers/net/can/rockchip/rockchip_canfd-rx.c
index 24e87daa1df0..85af71cc60f2 100644
--- a/drivers/net/can/rockchip/rockchip_canfd-rx.c
+++ b/drivers/net/can/rockchip/rockchip_canfd-rx.c
@@ -269,7 +269,7 @@ static int rkcanfd_handle_rx_int_one(struct rkcanfd_priv *priv)
memcpy(skb_cfd, cfd, len);
rkcanfd_skb_set_timestamp(priv, skb, header->ts);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, header->ts);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, header->ts);
if (err)
stats->rx_fifo_errors++;
diff --git a/drivers/net/can/rockchip/rockchip_canfd-tx.c b/drivers/net/can/rockchip/rockchip_canfd-tx.c
index 12200dcfd338..3cfbdb2b8373 100644
--- a/drivers/net/can/rockchip/rockchip_canfd-tx.c
+++ b/drivers/net/can/rockchip/rockchip_canfd-tx.c
@@ -160,7 +160,7 @@ void rkcanfd_handle_tx_done_one(struct rkcanfd_priv *priv, const u32 ts,
if (skb)
rkcanfd_skb_set_timestamp(priv, skb, ts);
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload,
+ can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload_irq,
tx_tail, ts,
frame_len_p);
stats->tx_packets++;
diff --git a/drivers/net/can/rockchip/rockchip_canfd.h b/drivers/net/can/rockchip/rockchip_canfd.h
index 95bea9bfd8a2..6b4cdbaffe4a 100644
--- a/drivers/net/can/rockchip/rockchip_canfd.h
+++ b/drivers/net/can/rockchip/rockchip_canfd.h
@@ -472,6 +472,7 @@ struct rkcanfd_stats {
struct rkcanfd_priv {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct net_device *ndev;
void __iomem *regs;
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
index f441f2265299..e4ad461cd9e8 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c
@@ -972,7 +972,7 @@ static int mcp251xfd_handle_rxovif(struct mcp251xfd_priv *priv)
cf->can_id |= CAN_ERR_CRTL;
cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, ts_raw);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, ts_raw);
if (err)
stats->rx_fifo_errors++;
@@ -1077,7 +1077,7 @@ static int mcp251xfd_handle_ivmif(struct mcp251xfd_priv *priv)
return 0;
mcp251xfd_skb_set_timestamp_raw(priv, skb, ts_raw);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, ts_raw);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, ts_raw);
if (err)
stats->rx_fifo_errors++;
@@ -1151,7 +1151,7 @@ static int mcp251xfd_handle_cerrif(struct mcp251xfd_priv *priv)
cf->data[7] = bec.rxerr;
}
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, ts_raw);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, ts_raw);
if (err)
stats->rx_fifo_errors++;
@@ -1515,7 +1515,7 @@ static irqreturn_t mcp251xfd_irq(int irq, void *dev_id)
priv->regs_status.intf);
if (!(intf_pending)) {
- can_rx_offload_threaded_irq_finish(&priv->offload);
+ can_rx_offload_threaded_irq_finish(&priv->offload_irq);
return handled;
}
@@ -1610,7 +1610,7 @@ static irqreturn_t mcp251xfd_irq(int irq, void *dev_id)
* directly.
*/
if (priv->can.state == CAN_STATE_BUS_OFF) {
- can_rx_offload_threaded_irq_finish(&priv->offload);
+ can_rx_offload_threaded_irq_finish(&priv->offload_irq);
return IRQ_HANDLED;
}
}
@@ -1619,7 +1619,7 @@ static irqreturn_t mcp251xfd_irq(int irq, void *dev_id)
} while (1);
out_fail:
- can_rx_offload_threaded_irq_finish(&priv->offload);
+ can_rx_offload_threaded_irq_finish(&priv->offload_irq);
netdev_err(priv->ndev, "IRQ handler returned %d (intf=0x%08x).\n",
err, priv->regs_status.intf);
@@ -2358,6 +2358,7 @@ static int mcp251xfd_probe(struct spi_device *spi)
goto out_free_candev;
err = can_rx_offload_add_manual(ndev, &priv->offload,
+ &priv->offload_irq,
MCP251XFD_NAPI_WEIGHT);
if (err)
goto out_free_candev;
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c
index fe897f3e4c12..8c5a371bcf61 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-rx.c
@@ -201,7 +201,7 @@ mcp251xfd_handle_rxif_one(struct mcp251xfd_priv *priv,
mcp251xfd_skb_set_timestamp(skb, timestamp);
mcp251xfd_hw_rx_obj_to_skb(priv, hw_rx_obj, skb);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, hw_rx_obj->ts);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, hw_rx_obj->ts);
if (err)
stats->rx_fifo_errors++;
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c
index e94321849fd7..752068691958 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-tef.c
@@ -104,7 +104,7 @@ mcp251xfd_handle_tefif_one(struct mcp251xfd_priv *priv,
if (skb)
mcp251xfd_skb_set_timestamp_raw(priv, skb, hw_tef_obj->ts);
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload,
+ can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload_irq,
tef_tail, hw_tef_obj->ts,
frame_len_ptr);
stats->tx_packets++;
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd.h b/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
index d3f4704e2678..0706bc5459be 100644
--- a/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
+++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd.h
@@ -624,6 +624,7 @@ enum mcp251xfd_flags {
struct mcp251xfd_priv {
struct can_priv can;
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct net_device *ndev;
struct regmap *map_reg; /* register access */
diff --git a/drivers/net/can/ti_hecc.c b/drivers/net/can/ti_hecc.c
index 1d3dbf28b105..bf7a2df0435a 100644
--- a/drivers/net/can/ti_hecc.c
+++ b/drivers/net/can/ti_hecc.c
@@ -177,6 +177,7 @@ static const struct can_bittiming_const ti_hecc_bittiming_const = {
struct ti_hecc_priv {
struct can_priv can; /* MUST be first member/field */
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct net_device *ndev;
struct clk *clk;
void __iomem *base;
@@ -623,7 +624,7 @@ static int ti_hecc_error(struct net_device *ndev, int int_status,
cf->data[3] = CAN_ERR_PROT_LOC_ACK;
timestamp = hecc_read(priv, HECC_CANLNT);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb,
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb,
timestamp);
if (err)
ndev->stats.rx_fifo_errors++;
@@ -659,7 +660,7 @@ static void ti_hecc_change_state(struct net_device *ndev,
}
timestamp = hecc_read(priv, HECC_CANLNT);
- err = can_rx_offload_queue_timestamp(&priv->offload, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
if (err)
ndev->stats.rx_fifo_errors++;
}
@@ -747,7 +748,7 @@ static irqreturn_t ti_hecc_interrupt(int irq, void *dev_id)
spin_unlock_irqrestore(&priv->mbx_lock, flags);
stamp = hecc_read_stamp(priv, mbxno);
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload,
+ can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload_irq,
mbxno, stamp, NULL);
stats->tx_packets++;
--priv->tx_tail;
@@ -762,7 +763,7 @@ static irqreturn_t ti_hecc_interrupt(int irq, void *dev_id)
/* offload RX mailboxes and let NAPI deliver them */
while ((rx_pending = hecc_read(priv, HECC_CANRMP))) {
- can_rx_offload_irq_offload_timestamp(&priv->offload,
+ can_rx_offload_irq_offload_timestamp(&priv->offload_irq,
rx_pending);
}
}
@@ -776,7 +777,7 @@ static irqreturn_t ti_hecc_interrupt(int irq, void *dev_id)
int_status = hecc_read(priv, HECC_CANGIF0);
}
- can_rx_offload_irq_finish(&priv->offload);
+ can_rx_offload_irq_finish(&priv->offload_irq);
return IRQ_HANDLED;
}
@@ -930,9 +931,10 @@ static int ti_hecc_probe(struct platform_device *pdev)
}
priv->offload.mailbox_read = ti_hecc_mailbox_read;
- priv->offload.mb_first = HECC_RX_FIRST_MBOX;
- priv->offload.mb_last = HECC_RX_LAST_MBOX;
- err = can_rx_offload_add_timestamp(ndev, &priv->offload);
+ priv->offload_irq.mb_first = HECC_RX_FIRST_MBOX;
+ priv->offload_irq.mb_last = HECC_RX_LAST_MBOX;
+ err = can_rx_offload_add_timestamp(ndev, &priv->offload,
+ &priv->offload_irq);
if (err) {
dev_err(&pdev->dev, "can_rx_offload_add_timestamp() failed\n");
goto probe_exit_disable_clk;
diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c
index 3b9b2f104d86..db9c17642131 100644
--- a/drivers/net/can/usb/gs_usb.c
+++ b/drivers/net/can/usb/gs_usb.c
@@ -305,6 +305,7 @@ struct gs_can {
struct can_priv can; /* must be the first member */
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct gs_usb *parent;
struct net_device *netdev;
@@ -539,15 +540,15 @@ static u32 gs_usb_set_timestamp(struct gs_can *dev, struct sk_buff *skb,
static void gs_usb_rx_offload(struct gs_can *dev, struct sk_buff *skb,
const struct gs_host_frame *hf)
{
- struct can_rx_offload *offload = &dev->offload;
+ struct can_rx_offload_irq *offload_irq = &dev->offload_irq;
int rc;
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) {
const u32 ts = gs_usb_set_timestamp(dev, skb, hf);
- rc = can_rx_offload_queue_timestamp(offload, skb, ts);
+ rc = can_rx_offload_queue_timestamp(offload_irq, skb, ts);
} else {
- rc = can_rx_offload_queue_tail(offload, skb);
+ rc = can_rx_offload_queue_tail(offload_irq, skb);
}
if (rc)
@@ -558,18 +559,19 @@ static unsigned int
gs_usb_get_echo_skb(struct gs_can *dev, struct sk_buff *skb,
const struct gs_host_frame *hf)
{
- struct can_rx_offload *offload = &dev->offload;
+ struct can_rx_offload_irq *offload_irq = &dev->offload_irq;
const u32 echo_id = hf->echo_id;
unsigned int len;
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) {
const u32 ts = gs_usb_set_timestamp(dev, skb, hf);
- len = can_rx_offload_get_echo_skb_queue_timestamp(offload, echo_id,
- ts, NULL);
+ len = can_rx_offload_get_echo_skb_queue_timestamp(offload_irq,
+ echo_id, ts,
+ NULL);
} else {
- len = can_rx_offload_get_echo_skb_queue_tail(offload, echo_id,
- NULL);
+ len = can_rx_offload_get_echo_skb_queue_tail(offload_irq,
+ echo_id, NULL);
}
return len;
@@ -743,7 +745,7 @@ static void gs_usb_receive_bulk_callback(struct urb *urb)
gs_usb_rx_offload(dev, skb, hf);
}
- can_rx_offload_irq_finish(&dev->offload);
+ can_rx_offload_irq_finish(&dev->offload_irq);
resubmit_urb:
usb_fill_bulk_urb(urb, parent->udev,
@@ -1487,7 +1489,8 @@ static struct gs_can *gs_make_candev(unsigned int channel,
dev->can.fd.data_bittiming_const = &dev->data_bt_const;
}
- can_rx_offload_add_manual(netdev, &dev->offload, GS_NAPI_WEIGHT);
+ can_rx_offload_add_manual(netdev, &dev->offload,
+ &dev->offload_irq, GS_NAPI_WEIGHT);
SET_NETDEV_DEV(netdev, &intf->dev);
rc = register_candev(dev->netdev);
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index e5f7f8849a73..7d0360708b93 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -133,6 +133,7 @@ struct __packed nct6694_canfd_frame {
struct nct6694_canfd_priv {
struct can_priv can; /* must be the first member */
struct can_rx_offload offload;
+ struct can_rx_offload_irq offload_irq;
struct net_device *ndev;
struct nct6694 *nct6694;
struct workqueue_struct *wq;
@@ -172,13 +173,13 @@ static const struct can_bittiming_const nct6694_canfd_bittiming_data_const = {
.brp_inc = 1,
};
-static void nct6694_canfd_rx_offload(struct can_rx_offload *offload,
+static void nct6694_canfd_rx_offload(struct can_rx_offload_irq *offload_irq,
struct sk_buff *skb)
{
- struct nct6694_canfd_priv *priv = rx_offload_to_priv(offload);
+ struct nct6694_canfd_priv *priv = rx_offload_to_priv(offload_irq->offload);
int ret;
- ret = can_rx_offload_queue_tail(offload, skb);
+ ret = can_rx_offload_queue_tail(offload_irq, skb);
if (ret)
priv->ndev->stats.rx_fifo_errors++;
}
@@ -202,7 +203,7 @@ static void nct6694_canfd_handle_lost_msg(struct net_device *ndev)
cf->can_id |= CAN_ERR_CRTL;
cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
- nct6694_canfd_rx_offload(&priv->offload, skb);
+ nct6694_canfd_rx_offload(&priv->offload_irq, skb);
}
static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt)
@@ -262,7 +263,7 @@ static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt)
memcpy(cf->data, frame->data, cf->len);
}
- nct6694_canfd_rx_offload(&priv->offload, skb);
+ nct6694_canfd_rx_offload(&priv->offload_irq, skb);
}
static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
@@ -305,7 +306,7 @@ static void nct6694_canfd_handle_state_change(struct net_device *ndev, u8 status
}
if (skb)
- nct6694_canfd_rx_offload(&priv->offload, skb);
+ nct6694_canfd_rx_offload(&priv->offload_irq, skb);
}
static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_err)
@@ -363,7 +364,7 @@ static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_err)
}
if (skb)
- nct6694_canfd_rx_offload(&priv->offload, skb);
+ nct6694_canfd_rx_offload(&priv->offload_irq, skb);
}
static void nct6694_canfd_handle_tx(struct net_device *ndev)
@@ -371,7 +372,7 @@ static void nct6694_canfd_handle_tx(struct net_device *ndev)
struct nct6694_canfd_priv *priv = netdev_priv(ndev);
struct net_device_stats *stats = &ndev->stats;
- stats->tx_bytes += can_rx_offload_get_echo_skb_queue_tail(&priv->offload,
+ stats->tx_bytes += can_rx_offload_get_echo_skb_queue_tail(&priv->offload_irq,
0, NULL);
stats->tx_packets++;
netif_wake_queue(ndev);
@@ -422,7 +423,7 @@ static irqreturn_t nct6694_canfd_irq(int irq, void *data)
}
if (handled)
- can_rx_offload_threaded_irq_finish(&priv->offload);
+ can_rx_offload_threaded_irq_finish(&priv->offload_irq);
priv->bec.rxerr = event->rec;
priv->bec.txerr = event->tec;
@@ -775,6 +776,7 @@ static int nct6694_canfd_probe(struct platform_device *pdev)
goto free_candev;
ret = can_rx_offload_add_manual(ndev, &priv->offload,
+ &priv->offload_irq,
NCT6694_NAPI_WEIGHT);
if (ret) {
dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
diff --git a/include/linux/can/rx-offload.h b/include/linux/can/rx-offload.h
index d29bb4521947..6f9a5739ecf5 100644
--- a/include/linux/can/rx-offload.h
+++ b/include/linux/can/rx-offload.h
@@ -20,40 +20,66 @@ struct can_rx_offload {
bool drop);
struct sk_buff_head skb_queue;
+
+ /* Registered per-IRQ contexts, only changed on add/del. A single
+ * context lets the finish path splice, more than one makes it sort
+ * the skbs into skb_queue.
+ */
+ struct list_head irqs;
+ unsigned int irq_cnt;
+
+ /* Set if the queued skbs carry a timestamp to sort them by. */
+ bool sort;
+
+ /* weight is the largest one over the registered per-IRQ contexts */
+ struct napi_struct napi;
+};
+
+/* Per-IRQ receive context, one per IRQ line producing RX frames.
+ * skb_irq_queue has a single producer, so it needs no lock. The finish
+ * helpers move the skbs into can_rx_offload::skb_queue under its lock.
+ */
+struct can_rx_offload_irq {
+ struct can_rx_offload *offload;
+
struct sk_buff_head skb_irq_queue;
u32 skb_queue_len_max;
unsigned int mb_first;
unsigned int mb_last;
- struct napi_struct napi;
-
bool inc;
+
+ /* entry on can_rx_offload::irqs */
+ struct list_head node;
};
int can_rx_offload_add_timestamp(struct net_device *dev,
- struct can_rx_offload *offload);
+ struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq);
int can_rx_offload_add_fifo(struct net_device *dev,
struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq,
unsigned int weight);
int can_rx_offload_add_manual(struct net_device *dev,
struct can_rx_offload *offload,
+ struct can_rx_offload_irq *offload_irq,
unsigned int weight);
-int can_rx_offload_irq_offload_timestamp(struct can_rx_offload *offload,
+int can_rx_offload_irq_offload_timestamp(struct can_rx_offload_irq *offload_irq,
u64 reg);
-int can_rx_offload_irq_offload_fifo(struct can_rx_offload *offload);
-int can_rx_offload_queue_timestamp(struct can_rx_offload *offload,
+int can_rx_offload_irq_offload_fifo(struct can_rx_offload_irq *offload_irq);
+int can_rx_offload_queue_timestamp(struct can_rx_offload_irq *offload_irq,
struct sk_buff *skb, u32 timestamp);
-unsigned int can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload *offload,
+unsigned int can_rx_offload_get_echo_skb_queue_timestamp(struct can_rx_offload_irq *offload_irq,
unsigned int idx, u32 timestamp,
unsigned int *frame_len_ptr);
-int can_rx_offload_queue_tail(struct can_rx_offload *offload,
+int can_rx_offload_queue_tail(struct can_rx_offload_irq *offload_irq,
struct sk_buff *skb);
-unsigned int can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload *offload,
+unsigned int can_rx_offload_get_echo_skb_queue_tail(struct can_rx_offload_irq *offload_irq,
unsigned int idx,
unsigned int *frame_len_ptr);
-void can_rx_offload_irq_finish(struct can_rx_offload *offload);
-void can_rx_offload_threaded_irq_finish(struct can_rx_offload *offload);
+void can_rx_offload_irq_finish(struct can_rx_offload_irq *offload_irq);
+void can_rx_offload_threaded_irq_finish(struct can_rx_offload_irq *offload_irq);
void can_rx_offload_del(struct can_rx_offload *offload);
void can_rx_offload_enable(struct can_rx_offload *offload);
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 3/3] can: flexcan: use one rx-offload context per IRQ line
2026-09-25 14:45 [PATCH v6 0/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
@ 2026-09-25 14:45 ` Ciprian Costea
2 siblings, 0 replies; 6+ messages in thread
From: Ciprian Costea @ 2026-09-25 14:45 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Dario Binacchi, Max Staudt,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo, Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
flexcan_irq() is requested on every IRQ line of the controller: the
mailbox line, the bus off and error lines (FLEXCAN_QUIRK_NR_IRQ_3) and a
second mailbox line (FLEXCAN_QUIRK_SECONDARY_MB_IRQ). On NXP S32G2, which
has all four, the handlers can run at the same time on different CPUs and
corrupt the rx-offload queue they share.
Add a struct can_rx_offload_irq for each line, pick it in flexcan_irq()
based on the IRQ number and pass it to the functions that queue skbs.
This only fixes the queue corruption. All handlers still process the
whole mailbox range and the same ESR events. A dedicated handler per line
and splitting the mailbox range between the two mailbox lines will follow
in a separate series, which also removes the lookup added here.
Fixes: 8503a4b1a24d ("can: flexcan: add NXP S32G2/S32G3 SoC support")
Assisted-by: LLM
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
drivers/net/can/flexcan/flexcan-core.c | 109 ++++++++++++++++++++-----
drivers/net/can/flexcan/flexcan.h | 3 +
2 files changed, 90 insertions(+), 22 deletions(-)
diff --git a/drivers/net/can/flexcan/flexcan-core.c b/drivers/net/can/flexcan/flexcan-core.c
index a81132d3afa8..b600680ed983 100644
--- a/drivers/net/can/flexcan/flexcan-core.c
+++ b/drivers/net/can/flexcan/flexcan-core.c
@@ -828,7 +828,9 @@ static netdev_tx_t flexcan_start_xmit(struct sk_buff *skb, struct net_device *de
return NETDEV_TX_OK;
}
-static void flexcan_irq_bus_err(struct net_device *dev, u32 reg_esr)
+static void flexcan_irq_bus_err(struct net_device *dev,
+ struct can_rx_offload_irq *offload_irq,
+ u32 reg_esr)
{
struct flexcan_priv *priv = netdev_priv(dev);
struct flexcan_regs __iomem *regs = priv->regs;
@@ -885,12 +887,14 @@ static void flexcan_irq_bus_err(struct net_device *dev, u32 reg_esr)
if (tx_errors)
dev->stats.tx_errors++;
- err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(offload_irq, skb, timestamp);
if (err)
dev->stats.rx_fifo_errors++;
}
-static void flexcan_irq_state(struct net_device *dev, u32 reg_esr)
+static void flexcan_irq_state(struct net_device *dev,
+ struct can_rx_offload_irq *offload_irq,
+ u32 reg_esr)
{
struct flexcan_priv *priv = netdev_priv(dev);
struct flexcan_regs __iomem *regs = priv->regs;
@@ -932,7 +936,7 @@ static void flexcan_irq_state(struct net_device *dev, u32 reg_esr)
if (unlikely(new_state == CAN_STATE_BUS_OFF))
can_bus_off(dev);
- err = can_rx_offload_queue_timestamp(&priv->offload_irq, skb, timestamp);
+ err = can_rx_offload_queue_timestamp(offload_irq, skb, timestamp);
if (err)
dev->stats.rx_fifo_errors++;
}
@@ -1070,11 +1074,35 @@ static struct sk_buff *flexcan_mailbox_read(struct can_rx_offload *offload,
return skb;
}
+/* The same handler is requested on every IRQ line, so the line it was
+ * called for selects the rx-offload context to queue into.
+ */
+static struct can_rx_offload_irq *
+flexcan_get_offload_irq(struct flexcan_priv *priv, int irq)
+{
+ const u32 quirks = priv->devtype_data.quirks;
+
+ if (quirks & FLEXCAN_QUIRK_SECONDARY_MB_IRQ &&
+ irq == priv->irq_secondary_mb)
+ return &priv->offload_irq_secondary_mb;
+
+ if (quirks & FLEXCAN_QUIRK_NR_IRQ_3) {
+ if (irq == priv->irq_boff)
+ return &priv->offload_irq_boff;
+ if (irq == priv->irq_err)
+ return &priv->offload_irq_err;
+ }
+
+ return &priv->offload_irq;
+}
+
static irqreturn_t flexcan_irq(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct net_device_stats *stats = &dev->stats;
struct flexcan_priv *priv = netdev_priv(dev);
+ struct can_rx_offload_irq *offload_irq =
+ flexcan_get_offload_irq(priv, irq);
struct flexcan_regs __iomem *regs = priv->regs;
irqreturn_t handled = IRQ_NONE;
u64 reg_iflag_tx;
@@ -1088,7 +1116,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
while ((reg_iflag_rx = flexcan_read_reg_iflag_rx(priv))) {
handled = IRQ_HANDLED;
- ret = can_rx_offload_irq_offload_timestamp(&priv->offload_irq,
+ ret = can_rx_offload_irq_offload_timestamp(offload_irq,
reg_iflag_rx);
if (!ret)
break;
@@ -1099,7 +1127,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
reg_iflag1 = priv->read(®s->iflag1);
if (reg_iflag1 & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE) {
handled = IRQ_HANDLED;
- can_rx_offload_irq_offload_fifo(&priv->offload_irq);
+ can_rx_offload_irq_offload_fifo(offload_irq);
}
/* FIFO overflow interrupt */
@@ -1120,7 +1148,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
handled = IRQ_HANDLED;
stats->tx_bytes +=
- can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload_irq, 0,
+ can_rx_offload_get_echo_skb_queue_timestamp(offload_irq, 0,
reg_ctrl << 16, NULL);
stats->tx_packets++;
@@ -1143,12 +1171,12 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
if ((reg_esr & FLEXCAN_ESR_ERR_STATE) ||
(priv->devtype_data.quirks & (FLEXCAN_QUIRK_BROKEN_WERR_STATE |
FLEXCAN_QUIRK_BROKEN_PERR_STATE)))
- flexcan_irq_state(dev, reg_esr);
+ flexcan_irq_state(dev, offload_irq, reg_esr);
/* bus error IRQ - handle if bus error reporting is activated */
if ((reg_esr & FLEXCAN_ESR_ERR_BUS) &&
(priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
- flexcan_irq_bus_err(dev, reg_esr);
+ flexcan_irq_bus_err(dev, offload_irq, reg_esr);
/* availability of error interrupt among state transitions in case
* bus error reporting is de-activated and
@@ -1189,7 +1217,7 @@ static irqreturn_t flexcan_irq(int irq, void *dev_id)
}
if (handled)
- can_rx_offload_irq_finish(&priv->offload_irq);
+ can_rx_offload_irq_finish(offload_irq);
return handled;
}
@@ -1381,6 +1409,23 @@ static void flexcan_ram_init(struct net_device *dev)
priv->write(reg_ctrl2, ®s->ctrl2);
}
+static int flexcan_rx_offload_add(struct net_device *dev,
+ struct can_rx_offload_irq *offload_irq)
+{
+ struct flexcan_priv *priv = netdev_priv(dev);
+
+ if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX) {
+ offload_irq->mb_first = FLEXCAN_RX_MB_RX_MAILBOX_FIRST;
+ offload_irq->mb_last = priv->mb_count - 2;
+
+ return can_rx_offload_add_timestamp(dev, &priv->offload,
+ offload_irq);
+ }
+
+ return can_rx_offload_add_fifo(dev, &priv->offload, offload_irq,
+ FLEXCAN_NAPI_WEIGHT);
+}
+
static int flexcan_rx_offload_setup(struct net_device *dev)
{
struct flexcan_priv *priv = netdev_priv(dev);
@@ -1409,22 +1454,42 @@ static int flexcan_rx_offload_setup(struct net_device *dev)
priv->offload.mailbox_read = flexcan_mailbox_read;
- if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX) {
- priv->offload_irq.mb_first = FLEXCAN_RX_MB_RX_MAILBOX_FIRST;
- priv->offload_irq.mb_last = priv->mb_count - 2;
-
- priv->rx_mask = GENMASK_ULL(priv->offload_irq.mb_last,
- priv->offload_irq.mb_first);
- err = can_rx_offload_add_timestamp(dev, &priv->offload,
- &priv->offload_irq);
- } else {
+ if (priv->devtype_data.quirks & FLEXCAN_QUIRK_USE_RX_MAILBOX)
+ priv->rx_mask = GENMASK_ULL(priv->mb_count - 2,
+ FLEXCAN_RX_MB_RX_MAILBOX_FIRST);
+ else
priv->rx_mask = FLEXCAN_IFLAG_RX_FIFO_OVERFLOW |
FLEXCAN_IFLAG_RX_FIFO_AVAILABLE;
- err = can_rx_offload_add_fifo(dev, &priv->offload,
- &priv->offload_irq,
- FLEXCAN_NAPI_WEIGHT);
+
+ err = flexcan_rx_offload_add(dev, &priv->offload_irq);
+ if (err)
+ return err;
+
+ /* Every IRQ line queues into its own context, the shared skb_queue is
+ * only touched by can_rx_offload_irq_finish().
+ */
+ if (priv->devtype_data.quirks & FLEXCAN_QUIRK_SECONDARY_MB_IRQ) {
+ err = flexcan_rx_offload_add(dev,
+ &priv->offload_irq_secondary_mb);
+ if (err)
+ goto out_can_rx_offload_del;
}
+ if (priv->devtype_data.quirks & FLEXCAN_QUIRK_NR_IRQ_3) {
+ err = flexcan_rx_offload_add(dev, &priv->offload_irq_boff);
+ if (err)
+ goto out_can_rx_offload_del;
+
+ err = flexcan_rx_offload_add(dev, &priv->offload_irq_err);
+ if (err)
+ goto out_can_rx_offload_del;
+ }
+
+ return 0;
+
+out_can_rx_offload_del:
+ can_rx_offload_del(&priv->offload);
+
return err;
}
diff --git a/drivers/net/can/flexcan/flexcan.h b/drivers/net/can/flexcan/flexcan.h
index 90f9c2b9fed8..443ad5237ba0 100644
--- a/drivers/net/can/flexcan/flexcan.h
+++ b/drivers/net/can/flexcan/flexcan.h
@@ -89,6 +89,9 @@ struct flexcan_priv {
struct can_priv can;
struct can_rx_offload offload;
struct can_rx_offload_irq offload_irq;
+ struct can_rx_offload_irq offload_irq_secondary_mb;
+ struct can_rx_offload_irq offload_irq_boff;
+ struct can_rx_offload_irq offload_irq_err;
struct device *dev;
struct flexcan_regs __iomem *regs;
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown
2026-09-25 14:45 ` [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown Ciprian Costea
@ 2026-09-26 14:53 ` Max Staudt
0 siblings, 0 replies; 6+ messages in thread
From: Max Staudt @ 2026-09-26 14:53 UTC (permalink / raw)
To: Ciprian Costea, Marc Kleine-Budde, Vincent Mailhol,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea, Dario Binacchi,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo
This is not my driver, but the patch looks reasonable to me. While we
wait for someone else's proper review, let's call it
Acked-by: Max Staudt <max@enpas.org>
Thank you!
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context
2026-09-25 14:45 ` [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
@ 2026-09-26 15:26 ` Max Staudt
0 siblings, 0 replies; 6+ messages in thread
From: Max Staudt @ 2026-09-26 15:26 UTC (permalink / raw)
To: Ciprian Costea, Marc Kleine-Budde, Vincent Mailhol,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea, Dario Binacchi,
Markus Schneider-Pargmann, Heiko Stuebner, Manivannan Sadhasivam,
Thomas Kopp, Ming Yu
Cc: kernel, linux-can, linux-arm-kernel, linux-kernel,
linux-rockchip, NXP S32 Linux Team, imx, Haibo Chen,
Enric Balletbo
Thank you for your patch. The general idea sounds useful, and at a quick
glance, the implementation seems sound. I'm not able to do a full review
- please wait for a maintainer's reply.
Comments below, from the point of view of a simple CAN driver's
maintainer (can327).
On 9/25/26 11:45 PM, Ciprian Costea wrote:
> From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>
> The IRQ handler fills skb_irq_queue without a lock and the finish helpers
> then splice it into skb_queue under skb_queue.lock. This breaks when a
> driver uses the helpers from more than one IRQ line. On NXP S32G2, the
> flexcan handlers can run at the same time on different CPUs and corrupt
> skb_irq_queue.
Please add "Currently, " to the beginning of this paragraph.
> Add struct can_rx_offload_irq, one per IRQ line, which holds
> skb_irq_queue, skb_queue_len_max and the mailbox range. skb_queue and napi
> stay in struct can_rx_offload and are shared by all contexts.
The IRQ naming gives me a bit of a headache. It may be IRQ in the case
of flexcan, but can be something else in other drivers. Also, _irq gives
the impression that the struct stores an IRQ, or at least something
related to it, but this really does not.
Can you please rename the new struct and concept to something else?
Maybe _queue? And then the old struct can be renamed to _common or
_shared or something. The maintainers may have better naming ideas.
I think you can keep the name skb_irq_queue inside the struct, my
request is about the new struct's name itself. In the same vein,
expanding the new struct's explanatory comment would help - please
explain that *the purpose* of having this struct is so you can have one
per source, *such as* one for each unique IRQ that the same CAN hardware
may trigger.
> @@ -351,64 +378,88 @@ EXPORT_SYMBOL_GPL(can_rx_offload_threaded_irq_finish);
>
> static int can_rx_offload_init_queue(struct net_device *dev,
> struct can_rx_offload *offload,
> + struct can_rx_offload_irq *offload_irq,
> unsigned int weight)
> {
> - offload->dev = dev;
> -
> - /* Limit queue len to 4x the weight (rounded to next power of two) */
> - offload->skb_queue_len_max = 2 << fls(weight);
> - offload->skb_queue_len_max *= 4;
> - skb_queue_head_init(&offload->skb_queue);
> - __skb_queue_head_init(&offload->skb_irq_queue);
> + struct can_rx_offload_irq *pos;
> + u32 skb_queue_len_max;
> +
> + offload_irq->offload = offload;
> + __skb_queue_head_init(&offload_irq->skb_irq_queue);
> +
> + /* The first registered IRQ initializes the shared state. */
> + if (!offload->irq_cnt) {
> + offload->dev = dev;
> + skb_queue_head_init(&offload->skb_queue);
> + INIT_LIST_HEAD(&offload->irqs);
> + netif_napi_add_weight(dev, &offload->napi,
> + can_rx_offload_napi_poll, weight);
> + } else if (weight > offload->napi.weight) {
> + /* All contexts feed the same NAPI, keep the largest weight. */
> + offload->napi.weight = weight;
> + }
> + list_add_tail(&offload_irq->node, &offload->irqs);
> + offload->irq_cnt++;
>
> - netif_napi_add_weight(dev, &offload->napi, can_rx_offload_napi_poll,
> - weight);
> + /* Limit queue len to 4x the weight (rounded to next power of two).
> + * All contexts feed the same skb_queue, so they share its limit.
> + */
> + skb_queue_len_max = 2 << fls(offload->napi.weight);
> + skb_queue_len_max *= 4;
> + list_for_each_entry(pos, &offload->irqs, node)
> + pos->skb_queue_len_max = skb_queue_len_max;
>
> dev_dbg(dev->dev.parent, "%s: skb_queue_len_max=%d\n",
> - __func__, offload->skb_queue_len_max);
> + __func__, skb_queue_len_max);
>
> return 0;
> }
This API is confusing now. It's called _init_queue(), but you've really
changed it into something that acts like _init_or_add_queue(). Please
rename this, and while at it, please add an explanatory comment for the
function above it, since it has grown quite complex.
The comment you added in can_rx_offload_del() is a positive example -
having more of this stuff helps! Actually, that specific comment talks
about "contexts" - please try to align that wording with whatever you
rename the _irq struct to, such as "queue", to keep the code and
comments consistent for the next reader.
Thanks,
Max
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-26 15:27 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-25 14:45 [PATCH v6 0/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
2026-09-25 14:45 ` [PATCH v6 1/3] can: at91_can: release the rx-offload on teardown Ciprian Costea
2026-09-26 14:53 ` Max Staudt
2026-09-25 14:45 ` [PATCH v6 2/3] can: rx-offload: add a per-IRQ receive context Ciprian Costea
2026-09-26 15:26 ` Max Staudt
2026-09-25 14:45 ` [PATCH v6 3/3] can: flexcan: use one rx-offload context per IRQ line Ciprian Costea
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®