Linux Sound subsystem development
 help / color / mirror / Atom feed
* [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck
@ 2026-10-26 12:57 Stjepan Poljak
  2026-10-26 13:18 ` sashiko-bot
  2026-10-27  7:38 ` Takashi Iwai
  0 siblings, 2 replies; 4+ messages 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 related	[flat|nested] 4+ messages in thread

* Re: [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck
  2026-10-26 12:57 [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck Stjepan Poljak
@ 2026-10-26 13:18 ` sashiko-bot
  2026-10-27  7:38 ` Takashi Iwai
  1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-26 13:18 UTC (permalink / raw)
  To: Stjepan Poljak; +Cc: linux-sound

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] ALSA: usb-audio: stale failing state in snd_usbmidi_in_error() resets device
- [Medium] ALSA: usb-audio: duplicate device resets from per-endpoint reset_queued state
--

commit c2e2b9a4ef7034463197fae2778c946cbde9e7fb
Author: Stjepan Poljak <stjepan.poljak@gmail.com>

ALSA: usb-audio: reset a MIDI device whose input is stuck

This commit delays resubmission of stalled (-EPIPE) USB MIDI input URBs via
the error timer and queues a USB device reset when an input endpoint stalls or
fails without a successful URB for 2 seconds.

> diff --git a/sound/usb/midi.c b/sound/usb/midi.c
> index e3d1575540613..7f93df66e55b7 100644
> --- a/sound/usb/midi.c
> +++ b/sound/usb/midi.c

[ ... ]

> @@ -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;
>  };

[Severity: Medium]
Should reset_queued and reset_at be stored on struct snd_usb_midi rather than
struct snd_usb_midi_in_endpoint?

In __snd_usbmidi_create(), a single struct snd_usb_midi bound to umidi->iface
can have multiple input endpoints in umidi->endpoints[i].in (for example, via
snd_usbmidi_get_ms_info() or snd_usbmidi_detect_per_port_endpoints()).

When multiple input endpoints on the same interface fail,
snd_usbmidi_in_urb_complete() -> snd_usbmidi_in_error() checks
ep->reset_queued on each endpoint independently:

    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);

Once system_wq starts running __usb_queue_reset_device() for the first
endpoint, WORK_STRUCT_PENDING_BIT on iface->reset_ws is cleared while
usb_reset_device() is in progress. Before usb_reset_device() finishes
unbinding interfaces and killing URBs in snd_usbmidi_disconnect(), a failing
URB on a second endpoint (where ep->reset_queued is still 0) will log a
duplicate warning and call usb_queue_reset_device(ep->umidi->iface) again.

Because neither usb_reset_device() nor usb_unbind_interface() cancels
iface->reset_ws, can that re-queued work run after the first reset finishes
and immediately reset the newly re-probed device a second time?

> @@ -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;

[Severity: High]
Can ep->failing and ep->failing_since stay latched across idle periods and
trigger a false-positive device reset on the next isolated error?

Unlike isochronous audio endpoints, USB MIDI input endpoints created in
snd_usbmidi_in_endpoint_create() are bulk or interrupt IN endpoints. When a
healthy MIDI device is idle and not transmitting MIDI data, it NAKs IN tokens
and submitted IN URBs remain pending without completing.

If a single transient bus error (-EPROTO, -ETIME, or -EILSEQ) occurs while the
device is idle:

1. snd_usbmidi_in_urb_complete() -> snd_usbmidi_in_error() sets
   ep->failing = 1 and latches ep->failing_since = jiffies.
2. snd_usbmidi_error_timer() resubmits the URB after ERROR_DELAY_JIFFIES
   (100 ms).
3. Because the device is healthy and idle, the resubmitted URB stays pending
   on the bus without completing. Since ep->failing is only cleared in
   snd_usbmidi_in_urb_complete() when an URB completes with urb->status == 0
   (and is also not cleared in snd_usbmidi_input_stop() or
   snd_usbmidi_input_start() when usb_kill_urb() completes URBs with
   -ENOENT), ep->failing remains 1 and ep->failing_since stays latched at the
   old timestamp indefinitely.

If another single transient error (or the first -EPROTO of a normal cable
unplug) occurs more than 2 seconds later without any MIDI notes having been
received in between, time_before(jiffies, ep->failing_since +
RESET_DELAY_JIFFIES) in snd_usbmidi_in_error() evaluates to false on the very
first failing URB.

Won't this immediately call usb_queue_reset_device(ep->umidi->iface) and
unbind the entire ALSA card (including any active PCM audio or MIDI output
streams on a composite device) without input having continuously failed for 2
seconds?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026125751.1700779-1-stjepan.poljak@gmail.com?part=1

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck
  2026-10-26 12:57 [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck Stjepan Poljak
  2026-10-26 13:18 ` sashiko-bot
@ 2026-10-27  7:38 ` Takashi Iwai
  2026-10-28 10:38   ` Stjepan Poljak
  1 sibling, 1 reply; 4+ messages in thread
From: Takashi Iwai @ 2026-10-27  7:38 UTC (permalink / raw)
  To: Stjepan Poljak
  Cc: Takashi Iwai, Jaroslav Kysela, Clemens Ladisch, linux-sound,
	linux-kernel

On Mon, 26 Oct 2026 13:57:51 +0100,
Stjepan Poljak wrote:
> 
> 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>

This doesn't look like a problem to be worked around in the USB audio
driver side, but better to be addressed in the USB core, IMO.

Please try that, or ask USB guys.  If they find this should be rather
fixed in that way, I can reconsider and review again.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] ALSA: usb-audio: reset a MIDI device whose input is stuck
  2026-10-27  7:38 ` Takashi Iwai
@ 2026-10-28 10:38   ` Stjepan Poljak
  0 siblings, 0 replies; 4+ messages in thread
From: Stjepan Poljak @ 2026-10-28 10:38 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Takashi Iwai, Jaroslav Kysela, Clemens Ladisch, linux-sound,
	linux-kernel

On Tue, Oct 27, 2026 at 8:38 AM Takashi Iwai <tiwai@suse.de> wrote:
>
> This doesn't look like a problem to be worked around in the USB audio
> driver side, but better to be addressed in the USB core, IMO.
>
> Please try that, or ask USB guys.  If they find this should be rather
> fixed in that way, I can reconsider and review again.

Hi Takashi,

Thanks for your time, and agreed. The host should have seen the
disconnect and on xHCI it does. The Pi uses the downstream dwc_otg
host driver, so I'll first check whether mainline dwc2 has the same
problem and take it to the Raspberry Pi kernel or to linux-usb
depending on that.

Sorry for the noise: I sent a v2 yesterday, fixing the review bot's
findings, before I had seen your reply. Please disregard it.

Regards,
Stjepan

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-28 10:38 UTC | newest]

Thread overview: 4+ messages (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
2026-10-26 13:18 ` sashiko-bot
2026-10-27  7:38 ` Takashi Iwai
2026-10-28 10:38   ` Stjepan Poljak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox