From: Shuangpeng <shuangpeng.kernel@gmail.com>
To: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Cc: mkl@pengutronix.de, mailhol@kernel.org, uwu@coelacanthus.name,
kees@kernel.org, linux-can@vger.kernel.org,
linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH can v3] can: gs_usb: fix hardware timestamp state for mixed channels
Date: Thu, 23 Jul 2026 01:17:14 -0400 [thread overview]
Message-ID: <97232020-922B-42BE-B050-A42B6FF40B85@gmail.com> (raw)
In-Reply-To: <da89a51e-3e42-4bcb-8e80-c992b1887ac8@linux.dev>
> On Jul 20, 2026, at 18:35, Vadim Fedorenko <vadim.fedorenko@linux.dev> 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: stable@vger.kernel.org
>> Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
>> ---
>> 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;
next prev parent reply other threads:[~2026-07-23 5:17 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 18:40 Shuangpeng Bai
2026-07-20 22:35 ` Vadim Fedorenko
2026-07-23 5:17 ` Shuangpeng [this message]
2026-07-23 10:05 ` Vadim Fedorenko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=97232020-922B-42BE-B050-A42B6FF40B85@gmail.com \
--to=shuangpeng.kernel@gmail.com \
--cc=kees@kernel.org \
--cc=linux-can@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mailhol@kernel.org \
--cc=mkl@pengutronix.de \
--cc=stable@vger.kernel.org \
--cc=uwu@coelacanthus.name \
--cc=vadim.fedorenko@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®