[PATCH can-next 13/13] can: gs_usb: implement CAN bus off recovery

From: Marc Kleine-Budde

Date: Mon Jul 20 2026 - 09:58:13 EST


Traditionally gs_usb compatible CAN devices implement automatic CAN bus off
recovery. Either supported by the CAN IP core in hardware or in the device
firmware.

This turned out to be inflexible, the USB host and thus the user space has
no control over the CAN bus recovery of behaviour. The gs_usb has not
implemented the struct can_priv::do_set_mode that's used to restart the CAN
controller after a CAN bus off.

Recent candleLight firmware closes the gap by implementing the
GS_CAN_FEATURE_BUS_OFF_RECOVERY feature. If the host sets
GS_CAN_FEATURE_BUS_OFF_RECOVERY flag is set device disables automatic CAN
bus off recovery, a manual recovery can be started with the USB request
GS_USB_BREQ_BUS_OFF_RECOVERY.

Implement gs_usb_set_mode() to recover the device via the
GS_USB_BREQ_BUS_OFF_RECOVERY USB request.

Extend gs_make_candev(): if the device signals
GS_CAN_FEATURE_BUS_OFF_RECOVERY support, set the struct
can_priv::do_set_mode callback. In gs_can_open() activate
GS_CAN_FEATURE_BUS_OFF_RECOVERY if supported.

Update gs_update_state() to use the default can_bus_off() handler if the
device goes bus off and GS_CAN_FEATURE_BUS_OFF_RECOVERY.

Link: https://github.com/candle-usb/candleLight_fw/pull/317
Signed-off-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
---
drivers/net/can/usb/gs_usb.c | 47 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 44 insertions(+), 3 deletions(-)

diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c
index 6355d05e4a16..e4ee9a1ef3f6 100644
--- a/drivers/net/can/usb/gs_usb.c
+++ b/drivers/net/can/usb/gs_usb.c
@@ -89,6 +89,7 @@ enum gs_usb_breq {
__GS_USB_BREQ_ELM_PLACEHOLDER_29,
__GS_USB_BREQ_ELM_PLACEHOLDER_30,
__GS_USB_BREQ_ELM_PLACEHOLDER_31,
+ GS_USB_BREQ_BUS_OFF_RECOVERY = 32,
};

enum gs_can_mode {
@@ -217,6 +218,10 @@ struct gs_device_tdc {
__le32 mode;
} __packed __aligned(4);

+struct gs_device_bus_off_recovery {
+ u32 unused;
+} __packed __aligned(4);
+
#define GS_CAN_FEATURE_LISTEN_ONLY BIT(0)
#define GS_CAN_FEATURE_LOOP_BACK BIT(1)
#define GS_CAN_FEATURE_TRIPLE_SAMPLE BIT(2)
@@ -240,7 +245,8 @@ struct gs_device_tdc {
#define GS_CAN_FEATURE_ELM_DEV_FLAG_SEND_USB_BLOBS_260528 BIT(16)
#define GS_CAN_FEATURE_FILTER BIT(16)
#define GS_CAN_FEATURE_TDC BIT(17)
-#define GS_CAN_FEATURE_MASK GENMASK(17, 0)
+#define GS_CAN_FEATURE_BUS_OFF_RECOVERY BIT(18)
+#define GS_CAN_FEATURE_MASK GENMASK(18, 0)

/* internal quirks - keep in GS_CAN_FEATURE space for now */

@@ -573,14 +579,22 @@ static void gs_update_state(struct gs_can *dev, struct can_frame *cf,
return;

/* some firmware does automatically CAN bus off recovery, account for this */
- if (cf->can_id & CAN_ERR_RESTARTED ||
- (dev->can.state == CAN_STATE_BUS_OFF && new_state < CAN_STATE_BUS_OFF)) {
+ if (!(dev->feature & GS_CAN_FEATURE_BUS_OFF_RECOVERY) &&
+ (cf->can_id & CAN_ERR_RESTARTED ||
+ (dev->can.state == CAN_STATE_BUS_OFF && new_state < CAN_STATE_BUS_OFF))) {
can_stats->restarts++;
/* some firmware doesn't set CAN_ERR_RESTARTED, fixup */
cf->can_id |= CAN_ERR_RESTARTED;
}

can_change_state(dev->can.dev, cf, tx_state, rx_state);
+
+ /* If device supports explicit CAN bus off recovery by the host, use default CAN bus off
+ * handler. Otherwise the device will recover itself.
+ */
+ if (new_state == CAN_STATE_BUS_OFF &&
+ dev->feature & GS_CAN_FEATURE_BUS_OFF_RECOVERY)
+ can_bus_off(dev->netdev);
}

static u32 gs_usb_set_timestamp(struct gs_can *dev, struct sk_buff *skb,
@@ -1243,6 +1257,9 @@ static int gs_can_open(struct net_device *netdev)
if (dev->feature & GS_CAN_FEATURE_TDC)
flags |= GS_CAN_FEATURE_TDC;

+ if (dev->feature & GS_CAN_FEATURE_BUS_OFF_RECOVERY)
+ flags |= GS_CAN_FEATURE_BUS_OFF_RECOVERY;
+
rc = gs_usb_set_bittiming(dev);
if (rc) {
netdev_err(netdev, "failed to set bittiming: %pe\n", ERR_PTR(rc));
@@ -1330,6 +1347,27 @@ static int gs_usb_get_state(const struct net_device *netdev,
return 0;
}

+static int gs_usb_set_mode(struct net_device *netdev, enum can_mode mode)
+{
+ struct gs_can *dev = netdev_priv(netdev);
+
+ switch (mode) {
+ case CAN_MODE_START: {
+ struct gs_device_bus_off_recovery bus_off_recovery = { 0 };
+
+ return usb_control_msg_send(dev->udev, 0, GS_USB_BREQ_BUS_OFF_RECOVERY,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+ dev->channel, 0,
+ &bus_off_recovery, sizeof(bus_off_recovery),
+ USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
static int gs_usb_can_get_berr_counter(const struct net_device *netdev,
struct can_berr_counter *bec)
{
@@ -1735,6 +1773,9 @@ static struct gs_can *gs_make_candev(unsigned int channel,
dev->can.fd.do_get_auto_tdcv = gs_usb_get_auto_tdcv;
}

+ if (feature & GS_CAN_FEATURE_BUS_OFF_RECOVERY)
+ dev->can.do_set_mode = gs_usb_set_mode;
+
can_rx_offload_add_manual(netdev, &dev->offload, GS_NAPI_WEIGHT);
SET_NETDEV_DEV(netdev, &intf->dev);


--
2.53.0