From: Vadim Fedorenko <vadim.fedorenko@linux.dev>
To: Shuangpeng Bai <shuangpeng.kernel@gmail.com>,
mkl@pengutronix.de, mailhol@kernel.org
Cc: 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: Mon, 20 Jul 2026 23:35:07 +0100 [thread overview]
Message-ID: <da89a51e-3e42-4bcb-8e80-c992b1887ac8@linux.dev> (raw)
In-Reply-To: <20260720184042.1712217-1-shuangpeng.kernel@gmail.com>
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.
> 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;
prev parent reply other threads:[~2026-07-20 22:35 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 18:40 [PATCH can v3] can: gs_usb: fix hardware timestamp state for mixed channels Shuangpeng Bai
2026-07-20 22:35 ` 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=da89a51e-3e42-4bcb-8e80-c992b1887ac8@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