mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Vadim Fedorenko <vadim.fedorenko@linux.dev>
To: Shuangpeng <shuangpeng.kernel@gmail.com>
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 11:05:38 +0100	[thread overview]
Message-ID: <7ced0697-e968-4e4f-b529-e333eae764f9@linux.dev> (raw)
In-Reply-To: <97232020-922B-42BE-B050-A42B6FF40B85@gmail.com>

On 23/07/2026 06:17, Shuangpeng wrote:
> 
> 
>> 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?

Ok, fair, but we are slowly moving towards per-device locks. May need to
revisit this part later.

Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>

> 
>>
>>>   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;
> 
> 

      reply	other threads:[~2026-07-23 10:05 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
2026-07-23 10:05     ` Vadim Fedorenko [this message]

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=7ced0697-e968-4e4f-b529-e333eae764f9@linux.dev \
    --to=vadim.fedorenko@linux.dev \
    --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=shuangpeng.kernel@gmail.com \
    --cc=stable@vger.kernel.org \
    --cc=uwu@coelacanthus.name \
    /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

Powered by JetHome