summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorMikhail Zubenko <misha.zubenko.01@yandex.ru>2026-08-24 15:54:28 +0000
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-10-18 10:13:48 +0200
commitab248e0b900ea2e30bec5c71fd56d8f0fa87a67a (patch)
treeef60f9dd7ba6a92851c0e7bd51a4c244d83e64c1 /drivers/usb
parent8d22d6da17d9612963a08965c3784a191dc66cec (diff)
usb: gadget: dummy_hcd: add tick_us module parameter for timer-rate tuning
dummy_hcd services URB completions from an hrtimer callback that rearms itself with a fixed 125 us delay (one high-speed microframe) for as long as any URBs are queued. Against a permanently polled HID interrupt endpoint the queue never drains, so the timer runs at ~8 kHz regardless of actual traffic. On timer-rate-sensitive platforms (observed on one consumer AM4 board with a current BIOS and a stock kernel) this timer rate co-existing with a live Bluetooth session on the machine's real xHCI controller resulted in a hard, below-software platform hang: printk, SysRq, NMI and both watchdogs died in the same instant, with no panic, no MCE, and empty pstore. 13 occurrences over 5 days, reliably reproducible with that configuration (hangs within 20 min to 2.5 h of uptime). The same workload with the emulated microframe lengthened to 1000 us (this parameter) ran ~8 hours with zero hangs, and USB-cable sessions (identical gadget traffic, no Bluetooth co-existence) never hung. Note that lengthening the emulated microframe makes the emulation run slower than a physical device would; users of the parameter trade that emulation fidelity for interrupt-rate headroom. The per-frame bandwidth budget in dummy_timer() does not scale with the emulated microframe length; making that accounting microframe- correct is planned as a follow-up patch. Otherwise nothing in the driver's behaviour depends on the emulated microframe length. Default keeps the current 125 us behaviour bit-for-bit (and defaults to 1000 us for full-speed emulation, where the natural unit is the 1-ms frame). Values below 125 are rejected. Signed-off-by: Mikhail Zubenko <misha.zubenko.01@yandex.ru> Reviewed-by: Alan Stern <stern@rowland.harvard.edu> Link: https://patch.msgid.link/178758686883.288478.6637939361965647417@yandex.ru Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/gadget/udc/dummy_hcd.c48
1 files changed, 44 insertions, 4 deletions
diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c
index 27cb3b25e481..4da008bb39f8 100644
--- a/drivers/usb/gadget/udc/dummy_hcd.c
+++ b/drivers/usb/gadget/udc/dummy_hcd.c
@@ -51,8 +51,6 @@
#define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */
#define POWER_BUDGET_3 900 /* in mA */
-#define DUMMY_TIMER_INT_NSECS 125000 /* 1 microframe */
-
static const char driver_name[] = "dummy_hcd";
static const char driver_desc[] = "USB Host+Gadget Emulator";
@@ -66,6 +64,31 @@ struct dummy_hcd_module_parameters {
bool is_super_speed;
bool is_high_speed;
unsigned int num;
+ unsigned int tick_us;
+};
+
+/*
+ * Length in microseconds of an emulated microframe. The default (125,
+ * one high-speed microframe) preserves the historical timer rate; larger
+ * values slow the emulated controller down and reduce its interrupt-rate
+ * pressure on the host platform. Values below 125 are rejected.
+ */
+static int tick_us_set(const char *val,
+ const struct kernel_param *kp)
+{
+ unsigned int v;
+ int ret = kstrtouint(val, 0, &v);
+
+ if (ret < 0 || v < 125) {
+ pr_err("dummy_hcd: tick_us must be >= 125\n");
+ return -EINVAL;
+ }
+ return param_set_uint(val, kp);
+}
+
+static const struct kernel_param_ops tick_us_ops = {
+ .set = tick_us_set,
+ .get = param_get_uint,
};
static struct dummy_hcd_module_parameters mod_data = {
@@ -79,6 +102,9 @@ module_param_named(is_high_speed, mod_data.is_high_speed, bool, S_IRUGO);
MODULE_PARM_DESC(is_high_speed, "true to simulate HighSpeed connection");
module_param_named(num, mod_data.num, uint, S_IRUGO);
MODULE_PARM_DESC(num, "number of emulated controllers");
+module_param_cb(tick_us, &tick_us_ops, &mod_data.tick_us, 0444);
+MODULE_PARM_DESC(tick_us,
+ "Length in microseconds of an emulated (micro)frame (default 125 for high/super-speed emulation, 1000 for full-speed); larger values reduce the interrupt-rate pressure on the host platform");
/*-------------------------------------------------------------------------*/
/* gadget side driver data structures */
@@ -244,6 +270,7 @@ struct dummy_hcd {
struct dummy *dum;
enum dummy_rh_state rh_state;
struct hrtimer timer;
+ ktime_t timer_interval; /* emulated microframe length */
u32 port_status;
u32 old_status;
unsigned long re_timeout;
@@ -1333,7 +1360,7 @@ static int dummy_urb_enqueue(
/* kick the scheduler, it'll do the rest */
if (!dum_hcd->timer_pending) {
dum_hcd->timer_pending = 1;
- hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS),
+ hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval,
HRTIMER_MODE_REL_SOFT);
}
@@ -2033,7 +2060,7 @@ return_urb:
dum_hcd->rh_state == DUMMY_RH_RUNNING) {
/* want a 1 msec delay here */
dum_hcd->timer_pending = 1;
- hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS),
+ hrtimer_start(&dum_hcd->timer, dum_hcd->timer_interval,
HRTIMER_MODE_REL_SOFT);
}
@@ -2513,6 +2540,7 @@ static DEVICE_ATTR_RO(urbs);
static int dummy_start_ss(struct dummy_hcd *dum_hcd)
{
hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC);
dum_hcd->rh_state = DUMMY_RH_RUNNING;
dum_hcd->stream_en_ep = 0;
INIT_LIST_HEAD(&dum_hcd->urbp_list);
@@ -2542,6 +2570,7 @@ static int dummy_start(struct usb_hcd *hcd)
spin_lock_init(&dum_hcd->dum->lock);
hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ dum_hcd->timer_interval = ns_to_ktime((u64)mod_data.tick_us * NSEC_PER_USEC);
dum_hcd->rh_state = DUMMY_RH_RUNNING;
INIT_LIST_HEAD(&dum_hcd->urbp_list);
@@ -2841,6 +2870,17 @@ static int __init dummy_hcd_init(void)
return -EINVAL;
}
+ /*
+ * The emulated microframe length defaults to one high-speed
+ * microframe (125 us). When neither is_high_speed nor
+ * is_super_speed is set the emulation is full-speed, where the
+ * natural scheduling unit is the 1-ms frame, so default to
+ * 1000 us instead.
+ */
+ if (!mod_data.tick_us)
+ mod_data.tick_us = (mod_data.is_super_speed ||
+ mod_data.is_high_speed) ? 125 : 1000;
+
for (i = 0; i < mod_data.num; i++) {
the_hcd_pdev[i] = platform_device_alloc(driver_name, i);
if (!the_hcd_pdev[i]) {