[PATCH can] can: gs_usb: add workarounds for HScanT USB to CAN adapter

From: Marc Kleine-Budde

Date: Mon Sep 28 2026 - 12:26:32 EST


The HScanT [1] is a RISC-V based USB to 4 channels CAN-FD adapter, which is
compatible with the gs_usb protocol.

The device is shipped with firmware version 0x00010007 and needs several
quirks to work properly.

The HScanT FW announces 5 channels, but the hardware has only 4. Workaround
the problem by changing the struct gs_device_config::icount to 3, which
corresponds to 4 channels.

The HScanT FW always sends USB In URB with length 512 bytes. Add quirk
GS_CAN_FEATURE_QUIRK_HSCANT_URB_LENGTH to allocate URBs of that length.

This driver supports up to 256 channels per USB Interface. The HScanT
device has 4 channels, but the FW requires each channels to be bound to a
USB interface using the GS_USB_BREQ_HSCANT_SET_INTERFACENUMBER_ENDPOINT USB
request. Add quirk to GS_CAN_FEATURE_QUIRK_HSCANT_BIND_CHANNEL to bind the
CAN channel to the USB Interface 0 during gs_can_open()

Link: https://github.com/cherry-embedded/HSCanT-hardware
Signed-off-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
---
drivers/net/can/usb/gs_usb.c | 110 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 106 insertions(+), 4 deletions(-)

diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c
index 3b9b2f104d86..ec8256b46646 100644
--- a/drivers/net/can/usb/gs_usb.c
+++ b/drivers/net/can/usb/gs_usb.c
@@ -72,6 +72,7 @@ enum gs_usb_breq {
GS_USB_BREQ_SET_TERMINATION,
GS_USB_BREQ_GET_TERMINATION,
GS_USB_BREQ_GET_STATE,
+ GS_USB_BREQ_HSCANT_SET_INTERFACENUMBER_ENDPOINT = 17,
};

enum gs_can_mode {
@@ -188,6 +189,18 @@ struct gs_device_termination_state {

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

+/* HScanT firmware version 0x00010007:
+ * - FW requires the binding of CAN channels to USB Interfaces
+ * - Route all CAN channels to USB Interface 0
+ */
+#define GS_CAN_FEATURE_QUIRK_HSCANT_BIND_CHANNEL BIT(29)
+
+/* HScanT firmware version 0x00010007:
+ * - FW requires bulk In URBs with >= 512 bytes.
+ * - Use struct quirk_hscant sized (512 bytes) In URBs as a workaround.
+ */
+#define GS_CAN_FEATURE_QUIRK_HSCANT_URB_LENGTH BIT(30)
+
/* CANtact Pro original firmware:
* BREQ DATA_BITTIMING overlaps with GET_USER_ID
*/
@@ -261,6 +274,10 @@ struct canfd_quirk {
u8 quirk;
} __packed;

+struct quirk_hscant {
+ u8 quirk[512];
+} __packed;
+
/* struct gs_host_frame::echo_id == GS_HOST_FRAME_ECHO_ID_RX indicates
* a regular RX'ed CAN frame
*/
@@ -284,6 +301,7 @@ struct gs_host_frame {
DECLARE_FLEX_ARRAY(struct canfd, canfd);
DECLARE_FLEX_ARRAY(struct canfd_ts, canfd_ts);
DECLARE_FLEX_ARRAY(struct canfd_quirk, canfd_quirk);
+ DECLARE_FLEX_ARRAY(struct quirk_hscant, quirk_hscant);
};
} __packed;
/* The GS USB devices make use of the same flags and masks as in
@@ -812,6 +830,18 @@ static int gs_usb_set_data_bittiming(struct gs_can *dev)
GFP_KERNEL);
}

+static int gs_usb_hscant_bind_channel_to_interface(const struct gs_can *dev)
+{
+ const u16 interface_number = 0;
+
+ /* Bind dev->channel to interface_number */
+ return usb_control_msg_send(dev->udev, 0, GS_USB_BREQ_HSCANT_SET_INTERFACENUMBER_ENDPOINT,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+ dev->channel, interface_number,
+ NULL, 0, 1000,
+ GFP_KERNEL);
+}
+
static void gs_usb_xmit_callback(struct urb *urb)
{
struct gs_tx_context *txc = urb->context;
@@ -1069,6 +1099,16 @@ static int gs_can_open(struct net_device *netdev)
}
}

+ if (dev->feature & GS_CAN_FEATURE_QUIRK_HSCANT_BIND_CHANNEL) {
+ rc = gs_usb_hscant_bind_channel_to_interface(dev);
+ if (rc) {
+ netdev_err(netdev,
+ "failed to bind Channel to Interface: %pe\n",
+ ERR_PTR(rc));
+ goto out_usb_kill_anchored_urbs;
+ }
+ }
+
/* finally start device */
dev->can.state = CAN_STATE_ERROR_ACTIVE;
dm.flags = cpu_to_le32(flags);
@@ -1314,6 +1354,49 @@ static const u16 gs_usb_termination_const[] = {
GS_USB_TERMINATION_ENABLED
};

+static bool gs_usb_is_hscant(const struct usb_device *udev,
+ const struct gs_device_config *dconf,
+ const u32 sw_version)
+{
+ if (udev->descriptor.idVendor != cpu_to_le16(USB_GS_USB_1_VENDOR_ID) ||
+ udev->descriptor.idProduct != cpu_to_le16(USB_GS_USB_1_PRODUCT_ID))
+ return false;
+
+ if (strcmp(udev->manufacturer, "HScanT") ||
+ strcmp(udev->product, "HScanT USB to CAN adapter"))
+ return false;
+
+ if (dconf->sw_version != cpu_to_le32(sw_version))
+ return false;
+
+ return true;
+}
+
+static void
+gs_usb_make_candev_get_feature(struct gs_can *dev, const struct gs_device_config *dconf,
+ const struct gs_device_bt_const *bt_const)
+{
+ const struct usb_device *udev = dev->udev;
+ const u32 feature = le32_to_cpu(bt_const->feature);
+
+ dev->feature = FIELD_GET(GS_CAN_FEATURE_MASK, feature);
+
+ if (!udev->manufacturer || !udev->product)
+ return;
+
+ /* HScanT firmware version 0x00010007:
+ * - FW doesn't advertise GS_CAN_FEATURE_BT_CONST_EXT,
+ * but implements GS_USB_BREQ_BT_CONST_EXT, fixup.
+ * - FW requires binding of CAN channel to USB Interface, add quirk.
+ * - FW requires bulk In URBs with >= 512 bytes, add quirk.
+ */
+ if (gs_usb_is_hscant(udev, dconf, 0x00010007)) {
+ dev->feature |= GS_CAN_FEATURE_BT_CONST_EXT |
+ GS_CAN_FEATURE_QUIRK_HSCANT_BIND_CHANNEL |
+ GS_CAN_FEATURE_QUIRK_HSCANT_URB_LENGTH;
+ }
+}
+
static struct gs_can *gs_make_candev(unsigned int channel,
struct usb_interface *intf,
struct gs_device_config *dconf)
@@ -1385,8 +1468,9 @@ static struct gs_can *gs_make_candev(unsigned int channel,

dev->can.ctrlmode_supported = CAN_CTRLMODE_CC_LEN8_DLC;

- feature = le32_to_cpu(bt_const.feature);
- dev->feature = FIELD_GET(GS_CAN_FEATURE_MASK, feature);
+ gs_usb_make_candev_get_feature(dev, dconf, &bt_const);
+ feature = dev->feature;
+
if (feature & GS_CAN_FEATURE_LISTEN_ONLY)
dev->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY;

@@ -1514,6 +1598,20 @@ static void gs_destroy_candev(struct gs_can *dev)
free_candev(dev->netdev);
}

+static void gs_usb_probe_quirks(const struct usb_device *udev, struct gs_device_config *dconf)
+{
+ if (!udev->manufacturer || !udev->product)
+ return;
+
+ /* HScanT firmware version 0x00010007:
+ * - FW has an icount of 4, which corresponds to 5 CAN interfaces.
+ * The hardware has only 4 interfaces, fixup.
+ */
+ if (gs_usb_is_hscant(udev, dconf, 0x00010007) &&
+ dconf->icount == 4)
+ dconf->icount = 3;
+}
+
static int gs_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
@@ -1560,6 +1658,8 @@ static int gs_usb_probe(struct usb_interface *intf,
return rc;
}

+ gs_usb_probe_quirks(udev, &dconf);
+
icount = dconf.icount + 1;
dev_info(&intf->dev, "Configuring for %u interfaces\n", icount);

@@ -1604,10 +1704,12 @@ static int gs_usb_probe(struct usb_interface *intf,
}
parent->canch[i]->parent = parent;

- /* set RX packet size based on FD and if hardware
+ /* set RX packet size based on quirks, FD and if hardware
* timestamps are supported.
*/
- if (parent->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) {
+ if (parent->canch[i]->feature & GS_CAN_FEATURE_QUIRK_HSCANT_URB_LENGTH) {
+ hf_size_rx = struct_size(hf, quirk_hscant, 1);
+ } else if (parent->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) {
if (parent->canch[i]->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
hf_size_rx = struct_size(hf, canfd_ts, 1);
else

---
base-commit: a7bfaba4823e3c165bb2004c74eff7c096672bc7
change-id: 20260924-gs_usb-hscant-bfe6987b97b2

Best regards,
--
Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>