* [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck
@ 2026-10-26 12:57 Stjepan Poljak
0 siblings, 0 replies; only message in thread
From: Stjepan Poljak @ 2026-10-26 12:57 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela; +Cc: Clemens Ladisch, linux-sound, linux-kernel
Switching a bus-powered USB keyboard off and on can be too quick for the
hub to report a disconnect. The device comes back unconfigured at the old
address, and its input endpoint then fails forever:
- with -EPIPE, which snd_usbmidi_urb_error() logs and treats as
"continue", so the URBs are resubmitted at once and the log fills
with "urb status -32" every few milliseconds;
- or with -EPROTO/-ETIME/-EILSEQ, retried silently every 100 ms.
Either way the rawmidi device stays, reads never fail and no data ever
arrives, so the instrument goes silent until the cable is replugged.
Retry a stalled input endpoint through the error timer like the other
errors, and when input stalls, or keeps failing for 2 s with no
successful URB, queue a USB reset. snd-usb-audio has no pre_reset or
post_reset, so the reset unbinds and re-probes the card, and userspace
sees an ordinary unplug and replug. A real unplug normally reaches
disconnect() long before 2 s. If the host misses that as well, as
dwc_otg does behind a hub, the input stalls and the reset finds no
device, which ends in the same disconnect.
The queued reset is dropped without a word if usb_lock_device_for_reset()
fails (e.g. -EBUSY when the device lock is held for over a second), so
while input keeps failing, queue another one every 2 s.
Output endpoints are left as they are.
Tested on a Raspberry Pi 3B+ (dwc_otg, Raspberry Pi 6.12.61 kernel) with
a Nektar Impact LX49+ (2467:2015) behind the on-board hub. Without this
patch, switching the keyboard off and on left it silent, with "urb
status -32" logged every few milliseconds. With it, each off/on cycle
logged a single "MIDI input not recovering (urb status -32), resetting
device", and the keyboard was re-enumerated and playing again 2.8-4.0 s
later, including two cycles 9 s apart. Left switched off for 3 minutes,
it was reset, disconnected cleanly and enumerated normally when switched
back on. Unplugging the cable went the same way: the input stalled, the
reset found no device and disconnected it, and plugging it back in
enumerated it normally. No warnings while playing normally over about
three hours.
Also tested on x86_64 (xHCI, 7.0 kernel): there the host sees both the
off/on cycle and an unplug as ordinary disconnects, so no reset is
queued and input resumes after re-enumeration as before.
Assisted-by: LLM
Signed-off-by: Stjepan Poljak <stjepan.poljak@gmail.com>
---
sound/usb/midi.c | 45 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index e3d157554061..7f93df66e55b 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -70,6 +70,12 @@
*/
#define ERROR_DELAY_JIFFIES (HZ / 10)
+/*
+ * how long input may keep failing before we reset the device (a real unplug
+ * reaches disconnect() well before this), and the least time between resets
+ */
+#define RESET_DELAY_JIFFIES (2 * HZ)
+
#define OUTPUT_URBS 7
#define INPUT_URBS 7
@@ -162,6 +168,10 @@ struct snd_usb_midi_in_endpoint {
bool in_sysex;
u8 last_cin;
u8 error_resubmit;
+ u8 failing; /* no successful URB since the last error */
+ u8 reset_queued; /* at reset_at */
+ unsigned long failing_since;
+ unsigned long reset_at;
int current_port;
};
@@ -234,6 +244,36 @@ static void dump_urb(const char *type, const u8 *data, int length)
#define dump_urb(type, data, length) /* nothing */
#endif
+/*
+ * A device that was power cycled without the hub noticing (e.g. a bus
+ * powered keyboard switched off and on again) has lost its configuration:
+ * it stalls the input endpoint or stops answering, and resubmitting the
+ * URBs never recovers it. Reset it instead; snd-usb-audio has no
+ * pre_reset/post_reset, so this unbinds and re-probes the card.
+ * A queued reset is skipped if usb_lock_device_for_reset() fails, so if
+ * we're still here a while later, ask again.
+ */
+static void snd_usbmidi_in_error(struct snd_usb_midi_in_endpoint *ep,
+ int status)
+{
+ if (!ep->failing) {
+ ep->failing = 1;
+ ep->failing_since = jiffies;
+ }
+ if (ep->reset_queued &&
+ time_before(jiffies, ep->reset_at + RESET_DELAY_JIFFIES))
+ return;
+ if (status != -EPIPE &&
+ time_before(jiffies, ep->failing_since + RESET_DELAY_JIFFIES))
+ return;
+ ep->reset_queued = 1;
+ ep->reset_at = jiffies;
+ dev_warn(&ep->umidi->dev->dev,
+ "MIDI input not recovering (urb status %d), resetting device\n",
+ status);
+ usb_queue_reset_device(ep->umidi->iface);
+}
+
/*
* Processes the data read from the device.
*/
@@ -242,13 +282,16 @@ static void snd_usbmidi_in_urb_complete(struct urb *urb)
struct snd_usb_midi_in_endpoint *ep = urb->context;
if (urb->status == 0) {
+ ep->failing = 0;
dump_urb("received", urb->transfer_buffer, urb->actual_length);
ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
urb->actual_length);
} else {
- int err = snd_usbmidi_urb_error(urb);
+ /* a stalled endpoint stays stalled: don't resubmit at once */
+ int err = urb->status == -EPIPE ? -EPIPE : snd_usbmidi_urb_error(urb);
if (err < 0) {
if (err != -ENODEV) {
+ snd_usbmidi_in_error(ep, urb->status);
ep->error_resubmit = 1;
mod_timer(&ep->umidi->error_timer,
jiffies + ERROR_DELAY_JIFFIES);
base-commit: 5deb9c57ba93d28a52871a96f3d8794d2cbed035
--
2.53.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-26 12:58 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-26 12:57 [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck Stjepan Poljak
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®