Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
From 62ef2098bc6b0776cfd83fda4c44c482b4d4425c Mon Sep 17 00:00:00 2001
From cbc0164b35559447f19841c7e2924e0ba89217bf Mon Sep 17 00:00:00 2001
From: "Michael C. Ferguson" <michael.christopher.ferguson@gmail.com>
Date: Fri, 25 Sep 2026 20:31:45 -0500
Subject: [PATCH 2/2] usb: dwc2: host: acknowledge a SOF with nothing due in
Expand Down Expand Up @@ -44,10 +44,17 @@ suspended or hibernated port and device mode are passed through. While
the hold-off is on, held SOFs are not reported as missed ones by
dwc2_track_missed_sofs().

The hold-off is on by default and can be switched off per controller at
The hold-off is off by default and can be switched on per controller at
run time through a "sof_holdoff" debugfs file. Its decisions are counted
in "hcd_stats".

On a PREEMPT_RT 7.2 kernel with a hub and a full-speed pad idle, it cut
the IRQ thread's CPU time from 13.2% to 7.7%. On a non-RT 6.18 kernel it
gained nothing when idle, and with about 195 Mbit/s of receive traffic
through a USB WiFi adapter on the same host port, a fixed CPU0 workload
ran slower in six of six runs and complete-split misses rose by about
10-15%. It stays off until that is understood.

This needs "usb: dwc2: host: serve the HCD from the common interrupt
handler", which leaves one action on the line: with the HCD's own
usb_hcd_irq() action also there, its forced thread would still wake on
Expand All @@ -58,18 +65,17 @@ Signed-off-by: Michael C. Ferguson <michael.christopher.ferguson@gmail.com>
drivers/usb/dwc2/core.h | 40 +++++++++++++++++-
drivers/usb/dwc2/core_intr.c | 50 ++++++++++++++++++++++
drivers/usb/dwc2/debugfs.c | 19 +++++++--
drivers/usb/dwc2/hcd.c | 1 +
drivers/usb/dwc2/hcd.h | 1 +
drivers/usb/dwc2/hcd_intr.c | 80 +++++++++++++++++++++++++++++++++++-
drivers/usb/dwc2/hcd_queue.c | 42 +++++++++++++++++++
drivers/usb/dwc2/platform.c | 18 ++++++--
8 files changed, 243 insertions(+), 8 deletions(-)
7 files changed, 242 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index eabdf12..92d1c7d 100644
index df6c49c..5f4a2b4 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -859,8 +859,18 @@ struct dwc2_hregs_backup {
@@ -855,8 +855,18 @@ struct dwc2_hregs_backup {
* @sel_np: QHs given a channel from the non-periodic schedule
* @halts: Channel interrupts, per host channel
* @halts_by: Channel interrupts by endpoint type, split and direction
Expand All @@ -89,7 +95,7 @@ index eabdf12..92d1c7d 100644
*/
struct dwc2_hcd_stats {
u32 hcd_passes;
@@ -872,6 +882,15 @@ struct dwc2_hcd_stats {
@@ -868,6 +878,15 @@ struct dwc2_hcd_stats {
u32 sel_np;
u32 halts[MAX_EPS_CHANNELS];
u32 halts_by[4][2][2];
Expand All @@ -105,7 +111,7 @@ index eabdf12..92d1c7d 100644
};

/**
@@ -1004,6 +1023,11 @@ struct dwc2_hcd_stats {
@@ -1000,6 +1019,11 @@ struct dwc2_hcd_stats {
* SOF enable/disable.
* @hcd_stats: Host interrupt counters, see struct dwc2_hcd_stats
* @hcd_stats_base: Counter values at the last debugfs reset
Expand All @@ -117,7 +123,7 @@ index eabdf12..92d1c7d 100644
* @free_hc_list: Free host channels in the controller. This is a list of
* struct dwc2_host_chan items.
* @periodic_channels: Number of host channels assigned to periodic transfers.
@@ -1198,6 +1222,14 @@ struct dwc2_hsotg {
@@ -1192,6 +1216,14 @@ struct dwc2_hsotg {
bool new_connection;
struct dwc2_hcd_stats hcd_stats;
struct dwc2_hcd_stats hcd_stats_base;
Expand All @@ -132,7 +138,7 @@ index eabdf12..92d1c7d 100644

u16 last_frame_num;

@@ -1393,6 +1425,9 @@ void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup);
@@ -1386,6 +1418,9 @@ void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup);

/* This function should be called on every hardware interrupt. */
irqreturn_t dwc2_handle_common_intr(int irq, void *dev);
Expand All @@ -142,15 +148,15 @@ index eabdf12..92d1c7d 100644

/* The device ID match table */
extern const struct of_device_id dwc2_of_match_table[];
@@ -1545,6 +1580,7 @@ void dwc2_hcd_connect(struct dwc2_hsotg *hsotg);
@@ -1538,6 +1573,7 @@ void dwc2_hcd_connect(struct dwc2_hsotg *hsotg);
void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force);
void dwc2_hcd_start(struct dwc2_hsotg *hsotg);
irqreturn_t dwc2_hcd_handle_irq(struct dwc2_hsotg *hsotg);
+irqreturn_t dwc2_hcd_hold_sof(struct dwc2_hsotg *hsotg);
int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup);
int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex);
int dwc2_port_resume(struct dwc2_hsotg *hsotg);
@@ -1574,6 +1610,8 @@ static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {}
@@ -1567,6 +1603,8 @@ static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {}
static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {}
static inline irqreturn_t dwc2_hcd_handle_irq(struct dwc2_hsotg *hsotg)
{ return IRQ_NONE; }
Expand All @@ -163,7 +169,7 @@ diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c
index f9f8f08..5112329 100644
--- a/drivers/usb/dwc2/core_intr.c
+++ b/drivers/usb/dwc2/core_intr.c
@@ -914,3 +914,53 @@ out:
@@ -914,3 +914,53 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev)

return retval;
}
Expand Down Expand Up @@ -218,10 +224,10 @@ index f9f8f08..5112329 100644
+ return retval;
+}
diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c
index faadb48..20c833e 100644
index 8396a27..43c6bde 100644
--- a/drivers/usb/dwc2/debugfs.c
+++ b/drivers/usb/dwc2/debugfs.c
@@ -799,6 +799,16 @@ static int hcd_stats_show(struct seq_file *seq, void *v)
@@ -798,6 +798,16 @@ static int hcd_stats_show(struct seq_file *seq, void *v)
seq_printf(seq, "split_nak %u\n", st.split_nak);
seq_printf(seq, "sel_periodic %u\n", st.sel_periodic);
seq_printf(seq, "sel_np %u\n", st.sel_np);
Expand All @@ -238,7 +244,7 @@ index faadb48..20c833e 100644
seq_puts(seq, "halts");
for (i = 0; i < hsotg->params.host_channels; i++)
seq_printf(seq, " %u", st.halts[i]);
@@ -842,9 +852,12 @@ static const struct file_operations hcd_stats_fops = {
@@ -841,9 +851,12 @@ static const struct file_operations hcd_stats_fops = {

static void dwc2_hcd_create_debug(struct dwc2_hsotg *hsotg)
{
Expand All @@ -254,23 +260,11 @@ index faadb48..20c833e 100644
}
#else
static inline void dwc2_hcd_create_debug(struct dwc2_hsotg *hsotg) {}
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index e2e81fb..56b3381 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -5158,6 +5158,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
return -ENODEV;

dev_dbg(hsotg->dev, "DWC OTG HCD INIT\n");
+ hsotg->sof_holdoff = true;

retval = -ENOMEM;

diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h
index eb2ab64..0b832e4 100644
index b7254d9..36e7f27 100644
--- a/drivers/usb/dwc2/hcd.h
+++ b/drivers/usb/dwc2/hcd.h
@@ -549,6 +549,7 @@ struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg,
@@ -538,6 +538,7 @@ struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg,
gfp_t mem_flags);
void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh);
int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh);
Expand All @@ -279,7 +273,7 @@ index eb2ab64..0b832e4 100644
void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
int sched_csplit);
diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c
index 5bd81d4..97ada27 100644
index d172dfa..60e62df 100644
--- a/drivers/usb/dwc2/hcd_intr.c
+++ b/drivers/usb/dwc2/hcd_intr.c
@@ -124,7 +124,11 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg)
Expand Down Expand Up @@ -369,7 +363,7 @@ index 5bd81d4..97ada27 100644
}

/*
@@ -2265,6 +2336,12 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg)
@@ -2249,6 +2320,12 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg)
/* Check if HOST Mode */
if (dwc2_is_host_mode(hsotg)) {
gintsts = dwc2_read_core_intr(hsotg);
Expand All @@ -382,7 +376,7 @@ index 5bd81d4..97ada27 100644
if (!gintsts) {
spin_unlock(&hsotg->lock);
return retval;
@@ -2299,6 +2376,7 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg)
@@ -2283,6 +2360,7 @@ irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg)
dwc2_hc_intr(hsotg);
if (gintsts & GINTSTS_PTXFEMP)
dwc2_perio_tx_fifo_empty_intr(hsotg);
Expand All @@ -391,7 +385,7 @@ index 5bd81d4..97ada27 100644
if (dbg_gintsts) {
dev_vdbg(hsotg->dev,
diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c
index ad5137c..cf873b4 100644
index 5aec13c..17d3af1 100644
--- a/drivers/usb/dwc2/hcd_queue.c
+++ b/drivers/usb/dwc2/hcd_queue.c
@@ -1427,6 +1427,7 @@ static enum hrtimer_restart dwc2_wait_timer_fn(struct hrtimer *t)
Expand Down
19 changes: 15 additions & 4 deletions docs/dwc2-usb-irq.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dwc2 host: interrupt load, descriptor DMA and split transactions

Tracking issue: #205. Status as of 2026-09-25: patches `0054`–`0066` carried, M1 and the `fs_ddma` fixes verified on hardware (RT 7.2.7),
Tracking issue: #205. Status as of 2026-09-28: patches `0054`–`0066` carried (the `0062` SOF hold-off ships **off** by default), M1 and the `fs_ddma` fixes verified on hardware (RT 7.2.7),
the `fs_ddma` interrupt-channel chaining feature parked. Research record, parked patches and lab tools:
[`dwc2-usb-irq/`](dwc2-usb-irq/README.md).

Expand Down Expand Up @@ -37,7 +37,7 @@ exists: 8,000 interrupts/s on the high-speed root port, whatever is plugged in.
| `0059-dwc2-fs-ddma-param` | MiSTer-local | `dwc2.fs_ddma=1` opt-in: caps the port to full speed and enables descriptor DMA. High speed stays the default |
| `0060-dwc2-host-keep-periodic-qh-cadence` | yes (Fixes: fb616e3f837e) | a 1 kHz FS interrupt endpoint was polled every 2 ms in buffer mode; rig-measured 500 → 984 reports/s |
| `0061-dwc2-host-debugfs-hcd-stats` | with 0062 | M0: `hcd_stats` debugfs counters (SOF passes with and without work, complete-split window misses, split NAKs, halts per channel and type). Behaviour unchanged |
| `0062-dwc2-host-sof-holdoff-in-hardirq` | yes (needs 0058) | M1: the primary handler acks a SOF with nothing due and does not wake the IRQ thread. Buffer DMA only. Off switch: `echo 0 > /sys/kernel/debug/usb/ffb40000.usb/sof_holdoff` |
| `0062-dwc2-host-sof-holdoff-in-hardirq` | yes (needs 0058) | M1: the primary handler acks a SOF with nothing due and does not wake the IRQ thread. Buffer DMA only. **Off by default** since 2026-09-28 (regresses under bulk USB receive on 6.18, see Plan 3). On switch: `echo 1 > /sys/kernel/debug/usb/ffb40000.usb/sof_holdoff` |
| `0063-dwc2-ddma-desc-list-bidirectional` | yes (Fixes: 95105a998dff) | the descriptor list was mapped `DMA_TO_DEVICE` but unmapped and synced as `DMA_FROM_DEVICE` |
| `0064-dwc2-ddma-giveback-on-dequeue-halt` | yes (Fixes: dc4c76e7b22c) | a dequeue halt dropped descriptors the core had already completed, and the data toggle went stale, with more than one URB queued |
| `0065-dwc2-ddma-halt-before-freeing-desc-list` | yes (Fixes: dc4c76e7b22c) | a QH's descriptor list was freed while its channel could still be enabled; now halted and waited for (bounded) first |
Expand Down Expand Up @@ -119,6 +119,15 @@ Reopen only with new evidence (for example the licensed databook describing an S
- Report rates and gaps for the pad and mouse are the same with M1 on and off. `cs_miss`, safety-net and race counters stayed 0; no warnings.
- An HS mouse with five interrupt endpoints keeps a channel busy almost every microframe, so M1 cannot help there (M4b in the ledger).
- The rig module also carried the in-progress `fs_ddma` chain patches (inert in buffer mode). `0061`/`0062` apply at `-F0` on `0054`–`0060` for 6.18.53 and 7.2.7.
- **Non-RT 6.18.54, 2026-09-28** (production image with the series; hold-off switched live through debugfs; hub + wired pad + BT dongle + an `rtw88_8822bu` USB WiFi adapter; iperf3 from a PC over WiFi; CPU0 cost read as the median time of a fixed CPU0 workload, since that kernel has no IRQ time accounting):

| Load | CPU0 workload, hold-off on vs off | `cs_miss`, on vs off |
|---|---|---|
| idle | 1,380 vs 1,384 ms | 0 vs 0 |
| WiFi send, 126 Mbit/s | 4.7–5.0 vs 5.2 s | same |
| WiFi receive, ~195 Mbit/s | 8.6–13.8 vs 5.1–9.5 s (worse in 6 of 6 pairs) | 673–730 vs 553–660 per 40 s |

- So off RT the hold-off gains nothing idle and **regresses under heavy USB receive**, and RT was never tested under bulk load. It now ships **off by default**; the debugfs switch stays for testing. Test notes: with Ethernet and WiFi on one subnet, traffic for the WiFi address arrives on `eth0` (ARP flux) unless `arp_ignore=1`/`arp_announce=2` and a gratuitous ARP on `wlan0`; replies leave by the default route unless the rig's iperf3 server uses `--bind-dev wlan0`. Check `/sys/class/net/*/statistics` before trusting a number.
4. **Gate for anything bigger:** on the M1 kernel with several devices on the hub, compare
user-visible work (CD-streaming stutter, CHD/ROM load time, `update_all` duration) against
"no USB load" (`fs_ddma=1` or devices unplugged). No visible difference → stop.
Expand All @@ -141,7 +150,8 @@ Parked work, with the trigger that would reopen each. Update this table instead
| **usbhid: two interrupt-IN URBs in flight** | small usbhid patch; upstream-sensitive | makes the `fs_ddma` channel chaining pay off for every HID device: 500 → ~1,000/s | owner wants 1 kHz HID under `fs_ddma`; needs the chaining patches carried first |
| **`fs_ddma` interrupt-channel chaining** (parked patches in `dwc2-usb-irq/parked/`) | 3 patches, ~1,000 lines; medium | rig: 2 URBs in flight 500 → ~964/s, 4 URBs 666 → 1,000/s; 1 URB unchanged (hardware prefetch) | a driver with several interrupt-IN URBs in flight matters under `fs_ddma`: USB MIDI on interrupt endpoints (`snd-usb-midi` keeps 7) or the usbhid option below. Retest on the rig before carrying |
| **Automatic `fs_ddma`** (userspace: udev + unbind/rebind) | ~100 lines + testing; low–medium | ~7 points of CPU0 over M1 for all-full-speed setups (7.7% → ~0.7%) | CPU0 still matters after M1. Switch only at boot or in the menu: every switch drops all USB for 1–2 s. Detect high-speed-capable devices plugged in while capped with a `DEVICE_QUALIFIER` request (full-speed-only devices stall it) or by class (storage, network) |
| **Measure on 6.18 (non-RT)** | one lab-kernel boot | tells whether `0058`/`0062` matter off RT | before offering them to the MiSTer 6.18 kernel |
| **Hold-off under bulk receive** (why `0062` costs CPU0 and split completions under WiFi receive) | investigation, then a fix | makes `0062` default-on material again | before re-enabling it or offering it upstream. First suspect: the rule that wakes the thread for pending non-periodic work, which constantly re-triggers under bulk traffic. Then retest RT and non-RT under WiFi receive with a Bluetooth pad paired |
| **Measure `0058` on 6.18 (non-RT)** | one boot of a kernel without `0058` | whether the single IRQ action pays off off RT | before offering it to the MiSTer 6.18 kernel. About a day of normal use on 6.18.54 with the series showed no problems, but no gain was measured |
| FS cap by default | — | — | **declined**: high speed stays the default, `fs_ddma` is opt-in |

## Side issues found along the way
Expand All @@ -160,4 +170,5 @@ Nothing has been sent; every submission needs the owner's approval.
- **MiSTer 6.18 kernel (Linux-Kernel_MiSTer)**, which is non-RT and runs high-speed buffer DMA by default:
- `0060`: small, generic, and helps any 1 kHz full-speed device. Measured only on a full-speed root port so far; confirm behind the high-speed hub on 6.18 first.
- The `fs_ddma` bundle (`0054`, `0055`, `0057`, `0059`, `0063`–`0066`): an opt-in that removes the SOF storm on any kernel. The DDMA fixes are only reachable through `0059`, so they go together or not at all. Offer it after a 6.18 measurement.
- Not `0058`, `0061`, `0062` until 6.18 numbers show they matter off RT. Not `0056` on its own.
- Not `0062`/`0061`: measured on 6.18.54 (2026-09-28), no gain idle and a regression under bulk USB receive. Not `0058` until a 6.18 A/B shows a gain. Not `0056` on its own.
- Nothing is ready to offer yet (owner decision 2026-09-28: fix our own tree first).
Loading