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

From: Marc Kleine-Budde

Date: Mon Sep 28 2026 - 14:11:48 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 requires a USB High Speed Hub, bail out if device is
connected to slower USB Hub.

The HScanT FW always sends USB In URB with length 512 bytes and seems to
have broken Zero Packet Length handling. Add quirk
GS_CAN_FEATURE_QUIRK_HSCANT_URB_SIZE to allocate URBs of 513 bytes to work
around these issues.

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>
---
Changes in v2:
- drop struct quirk_hscant and use length of 513 bytes for USB In URB
(detected by sashiko)
- bail out on USB Full Speed eor slower Hubs
- Link to v1: https://patch.msgid.link/20260928-gs_usb-hscant-v1-1-42638ca0f85f@xxxxxxxxxxxxxx
---
drivers/net/can/usb/gs_usb.c | 125 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 121 insertions(+), 4 deletions(-)

diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c
index 3b9b2f104d86..60a58dcea897 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,21 @@ 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 sends bulk In URBs with length of 512 bytes.
+ * - When using In URBs with 512 bytes it will send a second in URB with length 0
+ * It seems the ZLP handling is broken.
+ * - Use In URBs of length GS_USB_QUIRK_HSCANT_IN_URB_SIZE as a workaround.
+ */
+#define GS_CAN_FEATURE_QUIRK_HSCANT_URB_SIZE BIT(30)
+#define GS_USB_QUIRK_HSCANT_IN_URB_SIZE (513)
+
/* CANtact Pro original firmware:
* BREQ DATA_BITTIMING overlaps with GET_USER_ID
*/
@@ -812,6 +828,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 +1097,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 +1352,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_SIZE;
+ }
+}
+
static struct gs_can *gs_make_candev(unsigned int channel,
struct usb_interface *intf,
struct gs_device_config *dconf)
@@ -1385,8 +1466,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 +1596,34 @@ static void gs_destroy_candev(struct gs_can *dev)
free_candev(dev->netdev);
}

+static int gs_usb_probe_quirks(const struct usb_interface *intf, struct gs_device_config *dconf)
+{
+ const struct usb_device *udev = interface_to_usbdev(intf);
+
+ if (!udev->manufacturer || !udev->product)
+ return 0;
+
+ /* HScanT firmware version 0x00010007:
+ * - FW has an icount of 4, which corresponds to 5 CAN interfaces.
+ * The hardware has only 4 interfaces, fixup.
+ * - FW provides broken Endpoint Descriptors on USB Full Speed Hubs:
+ * config 1 interface 0 altsetting 0 endpoint 0x4 has invalid maxpacket 512, setting to 64
+ * Probably related to GS_CAN_FEATURE_QUIRK_HSCANT_URB_SIZE,
+ * FW only works on USB High Speed Hubs, detect and bail out.
+ */
+ if (gs_usb_is_hscant(udev, dconf, 0x00010007)) {
+ if (dconf->icount == 4)
+ dconf->icount = 3;
+
+ if (udev->speed < USB_SPEED_HIGH) {
+ dev_err(&intf->dev, "Device only works with USB High Speed Hubs\n");
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
static int gs_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
@@ -1560,6 +1670,10 @@ static int gs_usb_probe(struct usb_interface *intf,
return rc;
}

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

@@ -1604,10 +1718,13 @@ 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_SIZE) {
+ hf_size_rx = GS_USB_QUIRK_HSCANT_IN_URB_SIZE;
+ BUILD_BUG_ON(struct_size(hf, canfd, 1) > GS_USB_QUIRK_HSCANT_IN_URB_SIZE);
+ } 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>