[PATCH can-next v3] can: gs_usb: add workarounds for HScanT USB to CAN adapter
From: Marc Kleine-Budde
Date: Fri Oct 02 2026 - 05:05:48 EST
The HScanT is a RISC-V-based USB-to-4-channel CAN-FD adapter that is
compatible with the gs_usb protocol. The device ships with firmware
version 0x00010007 and requires several quirks to function properly.
The HScanT firmware specifies 5 channels, but the µC contains only 4
m_can IP cores. To work around this issue, change the value of `struct
gs_device_config::icount` to 3, which corresponds to 4 channels.
The HScanT supports CAN-FD with different bit-timing constants for
arbitration and data rate. The firmware reports that it supports
CAN-FD (GS_CAN_FEATURE_FD), but does not support querying the data
bit-timing constants (GS_CAN_FEATURE_BT_CONST_EXT). As a result, the driver
uses the arbitration bit-timing constants for the data rate.
According to EcuBus-Pro (a cross-platform CAN analyzer), if the
firmware supports GS_CAN_FEATURE_FD, it also supports querying the
data bit timing constants. Fix: Add GS_CAN_FEATURE_BT_CONST_EXT if
GS_CAN_FEATURE_FD is set.
The HScanT firmware requires a USB High Speed Hub, bail out if device is
connected to slower USB Hub.
The HScanT firmware requires In-URBs with a length of at least 512 bytes.
If In-URBs with a length of less than 512 bytes are used, the firmware does
not send any In-URBs.
When using In-URBs with exactly 512 bytes (= maximum packet length of the
endpoint), 512-byte In-URBs are sent, immediately followed by a 0-byte
In-URB. This results in a "short read" error in
gs_usb_receive_bulk_callback(). This can be avoided by using In-URBs with a
length of 513 bytes.
Add the GS_CAN_FEATURE_QUIRK_HSCANT_URB_SIZE quirk to allocate In-URBs that
are 513 bytes in size and work around these issues.
This driver supports up to 255 channels per USB interface. The HScanT
device has 4 channels, but the firmware requires that each channel be bound
to a USB interface via the USB request
"GS_USB_BREQ_HSCANT_SET_INTERFACENUMBER_ENDPOINT." Add a quirk to
"GS_CAN_FEATURE_QUIRK_HSCANT_BIND_CHANNEL" to bind the CAN channel to USB
interface 0 during "gs_can_open()".
Link: https://github.com/cherry-embedded/HSCanT-hardware
Link: https://github.com/ecubus/EcuBus-Pro/blob/86f6e1bab0de/src/main/docan/candle/api/candle.c#L307-L313
Signed-off-by: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
---
Changes in v3:
- Link to v2: https://patch.msgid.link/20260928-gs_usb-hscant-v2-1-a7c1c02460e9@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 or slower Hubs
- Link to v1: https://patch.msgid.link/20260928-gs_usb-hscant-v1-1-42638ca0f85f@xxxxxxxxxxxxxx
To: Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>
To: Vincent Mailhol <mailhol@xxxxxxxxxx>
Cc: kernel@xxxxxxxxxxxxxx
Cc: linux-can@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
---
drivers/net/can/usb/gs_usb.c | 128 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 124 insertions(+), 4 deletions(-)
diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c
index 3b9b2f104d86..67e57ccbcdec 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,50 @@ 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:
+ * - According to EcuBus-Pro: if FW support GS_CAN_FEATURE_FD,
+ * it also supports the GS_CAN_FEATURE_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)) {
+ if (dev->feature & GS_CAN_FEATURE_FD)
+ dev->feature |= GS_CAN_FEATURE_BT_CONST_EXT;
+ dev->feature |= 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 +1467,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 +1597,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 +1671,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 +1719,15 @@ 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) {
+ BUILD_BUG_ON(struct_size(hf, canfd_ts, 1) >
+ GS_USB_QUIRK_HSCANT_IN_URB_SIZE);
+
+ hf_size_rx = 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: d24e8ac715de2e16a53c144005b1863660a5fbea
change-id: 20260924-gs_usb-hscant-bfe6987b97b2
Best regards,
--
Marc Kleine-Budde <mkl@xxxxxxxxxxxxxx>