From: Cunhao Lu <1579567540@qq.com> The close path cancels restart_work through close_candev() only after freeing the IRQ, disabling RX offload and stopping the controller. A running restart can re-enable the controller and its interrupts after IRQ teardown, or restart timestamp work after it has been stopped and before runtime suspend removes the controller clocks. Call close_candev_cancel_restart_sync() at the start of ndo_stop(), while the resources needed by a running restart are still available. The core netif_running() check prevents a late restart from entering the driver, while this cancellation waits for one that has already passed the check. Keep close_candev() after IRQ teardown for the final cancellation and echo cleanup, including work queued by an IRQ racing with the first cancellation. Mask this controller's interrupt sources and read back the mask before setting irq_suspended. Have the IRQ handler check this flag before any register access, then use synchronize_irq() to drain handlers that have already passed the check. This excludes the handler during hardware reinitialization without disabling the shared interrupt line. Publish the initialized state with release/acquire ordering before resuming IRQ handling and unmasking the controller interrupts. The bus-off IRQ only cancels timestamp work asynchronously. Drain that work after the IRQ handler and before reinitializing the timecounter. Initialize the delayed_work once at probe instead of reinitializing a potentially active work item on every start. Keep the non-synchronous cancellation in the bus-off IRQ path. Also balance RX offload enablement if request_irq() fails during open, before stopping the controller and releasing the runtime PM reference. Fixes: ff60bfbaf67f ("can: rockchip_canfd: add driver for Rockchip CAN-FD controller") Cc: stable@vger.kernel.org Signed-off-by: Cunhao Lu <1579567540@qq.com> --- Changes in v3: - Replace disable_irq()/enable_irq() with device interrupt masking, mask readback, an irq_suspended flag and synchronize_irq(). - Pair release/acquire accesses to publish reinitialized state before the IRQ handler resumes, without disabling other shared IRQ users. - Initialize timestamp delayed work once at probe and synchronously cancel it before reinitializing the timestamp state during restart. - Disable RX offload when request_irq() fails during open. --- drivers/net/can/rockchip/rockchip_canfd-core.c | 20 ++++++++++++++++++++ drivers/net/can/rockchip/rockchip_canfd-timestamp.c | 6 +++++- drivers/net/can/rockchip/rockchip_canfd.h | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/rockchip/rockchip_canfd-core.c b/drivers/net/can/rockchip/rockchip_canfd-core.c index 37c1c22c40c9f..4b3db07242395 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-core.c +++ b/drivers/net/can/rockchip/rockchip_canfd-core.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "rockchip_canfd.h" @@ -356,7 +357,18 @@ static int rkcanfd_set_mode(struct net_device *ndev, switch (mode) { case CAN_MODE_START: + rkcanfd_chip_interrupts_disable(priv); + /* Flush the interrupt mask write before suspending the handler. */ + rkcanfd_read(priv, RKCANFD_REG_INT_MASK); + /* Keep shared IRQs from accessing the device during restart. */ + WRITE_ONCE(priv->irq_suspended, true); + /* Wait for handlers that already passed the suspended check. */ + synchronize_irq(ndev->irq); + rkcanfd_timestamp_stop_sync(priv); + rkcanfd_chip_start(priv); + /* Publish the initialized state before resuming IRQ handling. */ + smp_store_release(&priv->irq_suspended, false); rkcanfd_chip_interrupts_enable(priv); netif_wake_queue(ndev); break; @@ -666,6 +678,10 @@ static irqreturn_t rkcanfd_irq(int irq, void *dev_id) struct rkcanfd_priv *priv = dev_id; u32 reg_int_unmasked, reg_int; + /* Pairs with the release after restart has initialized the device. */ + if (smp_load_acquire(&priv->irq_suspended)) + return IRQ_NONE; + reg_int_unmasked = rkcanfd_read(priv, RKCANFD_REG_INT); reg_int = reg_int_unmasked & ~priv->reg_int_mask_default; @@ -747,6 +763,7 @@ static int rkcanfd_open(struct net_device *ndev) return 0; out_rkcanfd_chip_stop: + can_rx_offload_disable(&priv->offload); rkcanfd_chip_stop_sync(priv, CAN_STATE_STOPPED); pm_runtime_put(ndev->dev.parent); out_close_candev: @@ -758,6 +775,7 @@ static int rkcanfd_stop(struct net_device *ndev) { struct rkcanfd_priv *priv = netdev_priv(ndev); + close_candev_cancel_restart_sync(ndev); netif_stop_queue(ndev); rkcanfd_chip_interrupts_disable(priv); @@ -934,6 +952,8 @@ static int rkcanfd_probe(struct platform_device *pdev) if (err) goto out_free_candev; + rkcanfd_timestamp_work_init(priv); + err = rkcanfd_register(priv); if (err) goto out_can_rx_offload_del; diff --git a/drivers/net/can/rockchip/rockchip_canfd-timestamp.c b/drivers/net/can/rockchip/rockchip_canfd-timestamp.c index 72774cd2f94b7..ad582afbbd682 100644 --- a/drivers/net/can/rockchip/rockchip_canfd-timestamp.c +++ b/drivers/net/can/rockchip/rockchip_canfd-timestamp.c @@ -37,6 +37,11 @@ static void rkcanfd_timestamp_work(struct work_struct *work) schedule_delayed_work(&priv->timestamp, priv->work_delay_jiffies); } +void rkcanfd_timestamp_work_init(struct rkcanfd_priv *priv) +{ + INIT_DELAYED_WORK(&priv->timestamp, rkcanfd_timestamp_work); +} + void rkcanfd_timestamp_init(struct rkcanfd_priv *priv) { const struct can_bittiming *dbt = &priv->can.fd.data_bittiming; @@ -73,7 +78,6 @@ void rkcanfd_timestamp_init(struct rkcanfd_priv *priv) max_cycles = min(max_cycles, cc->mask); work_delay_ns = clocksource_cyc2ns(max_cycles, cc->mult, cc->shift); priv->work_delay_jiffies = div_u64(work_delay_ns, 3u * NSEC_PER_SEC / HZ); - INIT_DELAYED_WORK(&priv->timestamp, rkcanfd_timestamp_work); netdev_dbg(priv->ndev, "clock=%lu.%02luMHz bitrate=%lu.%02luMBit/s div=%u rate=%lu.%02luMHz mult=%u shift=%u delay=%lus\n", priv->can.clock.freq / MEGA, diff --git a/drivers/net/can/rockchip/rockchip_canfd.h b/drivers/net/can/rockchip/rockchip_canfd.h index 95bea9bfd8a2f..e1debab54534e 100644 --- a/drivers/net/can/rockchip/rockchip_canfd.h +++ b/drivers/net/can/rockchip/rockchip_canfd.h @@ -480,6 +480,7 @@ struct rkcanfd_priv { u32 reg_mode_default; u32 reg_int_mask_default; + bool irq_suspended; struct rkcanfd_devtype_data devtype_data; struct cyclecounter cc; @@ -551,6 +552,7 @@ int rkcanfd_handle_rx_int(struct rkcanfd_priv *priv); void rkcanfd_skb_set_timestamp(const struct rkcanfd_priv *priv, struct sk_buff *skb, const u32 timestamp); +void rkcanfd_timestamp_work_init(struct rkcanfd_priv *priv); void rkcanfd_timestamp_init(struct rkcanfd_priv *priv); void rkcanfd_timestamp_start(struct rkcanfd_priv *priv); void rkcanfd_timestamp_stop(struct rkcanfd_priv *priv); -- 2.34.1