Re: [PATCH can v3] can: gs_usb: fix hardware timestamp state for mixed channels
Shuangpeng <[email protected]> Thu, 23 Jul 2026 01:17:14 -0400
| Newsgroups | org.kernel.vger.linux-can,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
> On Jul 20, 2026, at 18:35, Vadim Fedorenko <[email protected]> wrote: > > On 20.07.2026 19:40, Shuangpeng Bai wrote: >> The hardware timestamp state is shared by struct gs_usb, but >> gs_can_open() and gs_can_close() tie its initialization and teardown to >> active_channels and to the feature bits of the channel being opened or >> closed. >> This is wrong for mixed-channel devices in both directions. If a >> non-timestamp channel opens first, a later timestamp-capable channel does >> not initialize the shared cyclecounter/timecounter because active_channels >> is already non-zero. Timestamp RX then calls timecounter_cyc2time() with >> parent->tc.cc unset. >> Conversely, if a timestamp-capable channel opens first and starts the >> shared delayed work, then closes while a non-timestamp channel remains >> active, disconnect may close the non-timestamp channel last. The old >> teardown check skips gs_usb_timestamp_stop() in that case and frees >> struct gs_usb while the delayed work timer is still queued. >> Count the number of active timestamp-capable channels instead. Start the >> shared timestamp state when the first such channel opens, stop it when the >> last such channel closes, and unwind the count if open fails. >> Initialize the shared timestamp lock and delayed work once during probe. >> The first timestamp-capable channel may be opened while RX URBs are >> already active, so guard the count and timecounter access with the same >> lock. Keep the count zero until timecounter_init() completes, and make RX >> and delayed work skip the timecounter while the count is zero. >> Fixes: 45dfa45f52e6 ("can: gs_usb: add RX and TX hardware timestamp support") >> Cc: [email protected] >> Signed-off-by: Shuangpeng Bai <[email protected]> >> --- >> Changes in v3: >> - Drop Suggested-by tag. >> - Move active_timestamp_channels management into the timestamp init/stop >> helpers. >> - Protect active_timestamp_channels with tc_lock, and keep it zero until >> timecounter_init() has completed. >> - Make RX timestamp conversion and the delayed work skip the timecounter >> while no timestamp-capable channel is active. >> - Initialize the shared timestamp lock and delayed work once during probe. >> Changes in v2: >> - Count active timestamp-capable channels instead of tracking only whether >> the shared timestamp worker has been started. >> - Stop the shared timestamp worker when the last timestamp-capable channel >> closes, even if non-timestamp channels remain open. >> - Unwind the timestamp-capable channel count on gs_can_open() failures. >> drivers/net/can/usb/gs_usb.c | 81 ++++++++++++++++++++++++------------ >> 1 file changed, 54 insertions(+), 27 deletions(-) >> diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c >> index ec9a7cbbbc69..be25d82cedcb 100644 >> --- a/drivers/net/can/usb/gs_usb.c >> +++ b/drivers/net/can/usb/gs_usb.c >> @@ -337,6 +337,7 @@ struct gs_usb { >> unsigned int hf_size_rx; >> u8 active_channels; >> + u8 active_timestamp_channels; >> u8 channel_cnt; >> unsigned int pipe_in; >> @@ -447,14 +448,20 @@ static void gs_usb_timestamp_work(struct work_struct *work) >> { >> struct delayed_work *delayed_work = to_delayed_work(work); >> struct gs_usb *parent; >> + bool active; >> parent = container_of(delayed_work, struct gs_usb, timestamp); >> spin_lock_bh(&parent->tc_lock); >> - timecounter_read(&parent->tc); >> + active = parent->active_timestamp_channels; >> + if (active) { >> + timecounter_read(&parent->tc); >> + active = parent->active_timestamp_channels; >> + } >> spin_unlock_bh(&parent->tc_lock); >> - schedule_delayed_work(&parent->timestamp, >> - GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); >> + if (active) >> + schedule_delayed_work(&parent->timestamp, >> + GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); >> } >> static void gs_usb_skb_set_timestamp(struct gs_can *dev, >> @@ -465,6 +472,11 @@ static void gs_usb_skb_set_timestamp(struct gs_can *dev, >> u64 ns; >> spin_lock_bh(&parent->tc_lock); >> + if (!parent->active_timestamp_channels) { >> + spin_unlock_bh(&parent->tc_lock); >> + return; >> + } >> + >> ns = timecounter_cyc2time(&parent->tc, timestamp); >> spin_unlock_bh(&parent->tc_lock); >> @@ -474,25 +486,40 @@ static void gs_usb_skb_set_timestamp(struct gs_can *dev, >> static void gs_usb_timestamp_init(struct gs_usb *parent) >> { >> struct cyclecounter *cc = &parent->cc; >> + bool first = false; >> - cc->read = gs_usb_timestamp_read; >> - cc->mask = CYCLECOUNTER_MASK(32); >> - cc->shift = 32 - bits_per(NSEC_PER_SEC / GS_USB_TIMESTAMP_TIMER_HZ); >> - cc->mult = clocksource_hz2mult(GS_USB_TIMESTAMP_TIMER_HZ, cc->shift); >> - >> - spin_lock_init(&parent->tc_lock); >> spin_lock_bh(&parent->tc_lock); >> - timecounter_init(&parent->tc, &parent->cc, ktime_get_real_ns()); >> + if (!parent->active_timestamp_channels) { >> + cc->read = gs_usb_timestamp_read; >> + cc->mask = CYCLECOUNTER_MASK(32); >> + cc->shift = 32 - bits_per(NSEC_PER_SEC / >> + GS_USB_TIMESTAMP_TIMER_HZ); >> + cc->mult = clocksource_hz2mult(GS_USB_TIMESTAMP_TIMER_HZ, >> + cc->shift); >> + >> + timecounter_init(&parent->tc, &parent->cc, >> + ktime_get_real_ns()); >> + first = true; >> + } >> + parent->active_timestamp_channels++; >> spin_unlock_bh(&parent->tc_lock); >> - INIT_DELAYED_WORK(&parent->timestamp, gs_usb_timestamp_work); >> - schedule_delayed_work(&parent->timestamp, >> - GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); >> + if (first) >> + schedule_delayed_work(&parent->timestamp, >> + GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); >> } >> static void gs_usb_timestamp_stop(struct gs_usb *parent) >> { >> - cancel_delayed_work_sync(&parent->timestamp); >> + bool last; >> + >> + spin_lock_bh(&parent->tc_lock); >> + parent->active_timestamp_channels--; >> + last = !parent->active_timestamp_channels; >> + spin_unlock_bh(&parent->tc_lock); >> + >> + if (last) >> + cancel_delayed_work_sync(&parent->timestamp); >> } >> > > tc_lock serializes active_timestamp_channels updates, but this work manipulation > is not protected. Imagine CPU0 doing gs_usb_timestamp_stop while CPU1 is doing > gs_can_open: > > CPU0 CPU1 > > gs_usb_timestamp_stop() gs_usb_timestamp_init() > spin_lock_bh(&tc_lock); > active_timestamp_channels-- > last = true > spin_unlock_bh(&tc_lock); > ... spin_lock_bh(&tc_lock) > ... timecounter_init() > ... first = true; > ... spin_unlock_bh(&tc_lock) > ... schedule_delayed_work() > cancel_delayed_work_sync() > > And the device will have no worker while active_timestamp_channels is not 0. > > I think we have to find another way of synchronization here. > Thanks for your check. I thought these paths were already serialized by RTNL through ndo_open/ndo_stop. If additional synchronization is needed, do you have any suggestions? > >> static void gs_update_state(struct gs_can *dev, struct can_frame *cf) >> @@ -980,10 +1007,10 @@ static int gs_can_open(struct net_device *netdev) >> can_rx_offload_enable(&dev->offload); >> - if (!parent->active_channels) { >> - if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> - gs_usb_timestamp_init(parent); >> + if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> + gs_usb_timestamp_init(parent); >> + if (!parent->active_channels) { >> for (i = 0; i < GS_MAX_RX_URBS; i++) { >> u8 *buf; >> @@ -1094,12 +1121,11 @@ static int gs_can_open(struct net_device *netdev) >> out_usb_free_urb: >> usb_free_urb(urb); >> out_usb_kill_anchored_urbs: >> - if (!parent->active_channels) { >> - usb_kill_anchored_urbs(&parent->rx_submitted); >> + if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> + gs_usb_timestamp_stop(parent); >> - if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> - gs_usb_timestamp_stop(parent); >> - } >> + if (!parent->active_channels) >> + usb_kill_anchored_urbs(&parent->rx_submitted); >> can_rx_offload_disable(&dev->offload); >> close_candev(netdev); >> @@ -1152,12 +1178,11 @@ static int gs_can_close(struct net_device *netdev) >> /* Stop polling */ >> parent->active_channels--; >> - if (!parent->active_channels) { >> - usb_kill_anchored_urbs(&parent->rx_submitted); >> + if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> + gs_usb_timestamp_stop(parent); >> - if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) >> - gs_usb_timestamp_stop(parent); >> - } >> + if (!parent->active_channels) >> + usb_kill_anchored_urbs(&parent->rx_submitted); >> /* Stop sending URBs */ >> usb_kill_anchored_urbs(&dev->tx_submitted); >> @@ -1577,6 +1602,8 @@ static int gs_usb_probe(struct usb_interface *intf, >> parent->channel_cnt = icount; >> init_usb_anchor(&parent->rx_submitted); >> + spin_lock_init(&parent->tc_lock); >> + INIT_DELAYED_WORK(&parent->timestamp, gs_usb_timestamp_work); >> usb_set_intfdata(intf, parent); >> parent->udev = udev;