Linux Sound subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ismaïl Bahloul" <i.bahloul01@gmail.com>, linux-sound@vger.kernel.org
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH v8 1/8] ALSA: usb: add RME Babyface Pro driver core (probe, PCM stream)
Date: Thu, 05 Nov 2026 16:24:40 +0000	[thread overview]
Message-ID: <sashiko-outbox-199481@kernel.org> (raw)
In-Reply-To: <20261105151553.223722-2-i.bahloul01@gmail.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] ALSA: usb: babyfacepro: URB and coherent DMA teardown deferred past disconnect
- [Medium] ALSA: usb: babyfacepro: validate endpoint types in babyface_stream_start()
- [Medium] ALSA: usb: babyfacepro: stop running substreams on babyface_stream_start() error
- [Medium] ALSA: usb: babyfacepro: constrain period_size and rate by packet floor in open()
- [Medium] ALSA: usb: babyfacepro: unconditional usb_enable_autosuspend() on teardown
- [Low] ALSA: usb: babyfacepro: document module parameters in kernel-parameters.txt
- [Low] ALSA: usb: babyfacepro: use per-card slot for index and id in babyface_probe()
--

commit 6f9408f458b3d9ee7e3ba9156ec7fd66f87dee6c
Author: Ismaïl Bahloul <i.bahloul01@gmail.com>

ALSA: usb: add RME Babyface Pro driver core (probe, PCM stream)

This commit adds a standalone ALSA USB audio driver for the RME Babyface
Pro and Babyface Pro FS in proprietary mode, handling cold-start
initialization, card lifecycle, and full-duplex interrupt-URB PCM
streaming.

> diff --git a/sound/usb/babyfacepro/babyfacepro.c b/sound/usb/babyfacepro/babyfacepro.c
> new file mode 100644
> index 0000000000000..ac1414adc73c6
> --- /dev/null
> +++ b/sound/usb/babyfacepro/babyfacepro.c

[ ... ]

> +static int babyface_stream_start(struct snd_usb_babyface *chip)
> +{
> +	unsigned int urbsize = chip->frame_bytes * chip->urb_frames;
> +	s64 since;
> +	int i, ret;

[ ... ]

> +	for (i = 0; i < chip->urbs_active; i++) {
> +		/* Do not replay data from the previous stream/format. */
> +		memset(chip->buf_out[i], 0, urbsize);
> +		usb_fill_int_urb(chip->urbs_in[i], chip->dev,
> +				 usb_rcvintpipe(chip->dev, BF_EP_IN),
> +				 chip->buf_in[i], urbsize,
> +				 babyface_complete_in, chip, 1);
> +		usb_fill_int_urb(chip->urbs_out[i], chip->dev,
> +				 usb_sndintpipe(chip->dev, BF_EP_OUT),
> +				 chip->buf_out[i], urbsize,
> +				 babyface_complete_out, chip, 1);

[ ... ]

> +	WRITE_ONCE(chip->streaming, true);
> +	for (i = 0; i < chip->urbs_active; i++) {
> +		ret = usb_submit_urb(chip->urbs_in[i], GFP_KERNEL);
> +		if (ret < 0)
> +			goto err;
> +		ret = usb_submit_urb(chip->urbs_out[i], GFP_KERNEL);
> +		if (ret < 0)
> +			goto err;
> +	}

[Severity: Medium]
As reported by Sashiko on v7 [1], babyface_stream_start() initializes
chip->urbs_in[i] and chip->urbs_out[i] with usb_fill_int_urb() using
hardcoded interrupt pipes for BF_EP_IN and BF_EP_OUT and submits them via
usb_submit_urb() without validating the endpoint descriptors in the active
alternate setting.

If a malformed or emulated USB device with VID 0x2a39 and PID 0x3fc0
exposes non-interrupt endpoints (or omits them) on interface 5 alternate
settings BF_ALT_1, BF_ALT_2, or BF_ALT_3, usb_submit_urb() logs a bogus
URB transfer warning and forwards the interrupt URB with a mismatched
endpoint descriptor to the host controller driver.

Should babyface_stream_start() check usb_urb_ep_type_check() on the
initialized URBs (or validate the endpoints after usb_set_interface())
before submitting them?

> +	/* Session arm (cap_audio frame 5829, after the URBs). */
> +	ret = bf_vendor_write(chip, BF_REQ_SESSION_ARM, 0x0000, 0xc000);
> +	if (ret < 0)
> +		goto err;
> +
> +	dev_dbg(&chip->dev->dev, "stream started (%u frames/URB, %u URBs)\n",
> +		chip->urb_frames, chip->urbs_active);
> +	return 0;
> +
> +err:
> +	dev_err_ratelimited(&chip->dev->dev, "failed to start stream: %d\n",
> +			    ret);
> +	babyface_stream_kill(chip);
> +	return ret;
> +}

[Severity: Medium]
As reported by Sashiko on v7 [2], babyface_stream_start() sets
chip->streaming to true before the usb_submit_urb() loop and the blocking
bf_vendor_write(chip, BF_REQ_SESSION_ARM, ...) control transfer.

Because babyface_pcm_trigger() handles SNDRV_PCM_TRIGGER_START under the
PCM stream lock without taking chip->mutex:

	case SNDRV_PCM_TRIGGER_START:
		if (!READ_ONCE(chip->streaming))
			return -EPIPE;

if the other direction's substream is already in SNDRV_PCM_STATE_PREPARED
and triggers start while babyface_stream_start() is in flight, it sees
chip->streaming == true and enters SNDRV_PCM_STATE_RUNNING.

If usb_submit_urb() or bf_vendor_write() then fails, could the err path
leave that other substream stuck in SNDRV_PCM_STATE_RUNNING with no URBs
in flight because it calls babyface_stream_kill(chip) without calling
babyface_pcm_stop_both(chip, SNDRV_PCM_STATE_XRUN)?

[ ... ]

> +static unsigned int bf_urb_frames(struct snd_usb_babyface *chip,
> +				  const struct bf_rate *r,
> +				  unsigned int period)
> +{
> +	unsigned int pkt = r->min_fpu;
> +	unsigned int hi = rounddown(chip->frames_per_urb, pkt);
> +	unsigned int lo = roundup(max(chip->urb_frames_min, pkt), pkt);
> +	unsigned int urb;
> +
> +	if (!hi || lo > hi || period < pkt)
> +		return 0;
> +	urb = rounddown(period / DIV_ROUND_UP(period, hi), pkt);
> +	if (bf_split_period(r, period))
> +		urb = rounddown(urb / 2, pkt);
> +	urb = clamp(urb, lo, hi);
> +	return urb <= period ? urb : 0;
> +}

[ ... ]

> +static int babyface_pcm_open(struct snd_pcm_substream *subs)
> +{
> +	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
> +	struct snd_pcm_runtime *rt = subs->runtime;
> +	int ret;
> +
> +	rt->hw = babyface_pcm_hw;
> +	ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
> +					 &bf_rates_constraint);
> +	if (ret < 0)
> +		return ret;
> +	/* The stream is 24-bit audio in a 32-bit container, left-justified
> +	 * (the device word carries the sample in bits 31-8).  Declare the
> +	 * valid-bit width so user-space knows to ignore the low 8 bits.
> +	 */
> +	ret = snd_pcm_hw_constraint_msbits(rt, 0, 32, 24);
> +	if (ret < 0)
> +		return ret;
> +	/* A period must span at least one URB, so a completion crosses at
> +	 * most one period boundary.  The URB size follows the period down to
> +	 * urb_frames_min; while another application holds the session, its
> +	 * period (in whole URBs) is the floor.  Constrain in frames (not
> +	 * bytes) so the minimum period does not balloon at low channel
> +	 * counts.
> +	 */
> +	ret = snd_pcm_hw_constraint_minmax(rt, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
> +					   chip->urb_frames_min, 1 << 18);
> +	if (ret < 0)
> +		return ret;

[Severity: Medium]
In babyface_probe(), chip->frames_per_urb and chip->urb_frames_min can be
configured down to 8 (such as urb_frames_min=16, which is documented as
the low-latency floor):

	chip->frames_per_urb = clamp(frames_per_urb, 8, 1024) & ~7;
	chip->urb_frames_min = min_t(unsigned int,
				     clamp(urb_frames_min, 8, 1024) & ~7,
				     chip->frames_per_urb);

However, bf_urb_frames() requires the period and URB size to be at least
r->min_fpu frames (16 frames at BF_ALT_2 and 32 frames at BF_ALT_3) and
requires rounddown(chip->frames_per_urb, r->min_fpu) >=
roundup(max(chip->urb_frames_min, r->min_fpu), r->min_fpu), returning 0
otherwise.

Because babyface_pcm_open() only bounds SNDRV_PCM_HW_PARAM_PERIOD_SIZE by
chip->urb_frames_min and offers all rates in bf_rates_constraint, won't
ALSA parameter refinement allow combinations (for example period_size=16
at 192 kHz when urb_frames_min=16) that babyface_pcm_hw_params() then
rejects with -EINVAL?

[ ... ]

> +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
> +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
> +static int frames_per_urb = BF_FRAMES_PER_URB_DEFAULT;
> +static int urb_frames_min = 32;
> +static int nurbs = BF_NURBS_DEFAULT;
> +
> +module_param_array(index, int, NULL, 0444);
> +MODULE_PARM_DESC(index, "Index value for the Babyface Pro sound card.");
> +module_param_array(id, charp, NULL, 0444);
> +MODULE_PARM_DESC(id, "ID string for the Babyface Pro sound card.");
> +module_param(frames_per_urb, int, 0444);
> +MODULE_PARM_DESC(frames_per_urb, "Largest URB in audio frames, 8..1024 (256 = default); the period chooses the size below it.");
> +module_param(urb_frames_min, int, 0444);
> +MODULE_PARM_DESC(urb_frames_min, "Smallest URB in audio frames, 8..1024 (32 = default, 16 = low-latency floor).");
> +module_param(nurbs, int, 0444);
> +MODULE_PARM_DESC(nurbs, "Most URBs in flight per direction, 2..16 (8 = default).");

[Severity: Low]
As reported by Sashiko on v7 [3], this adds five module parameters
(index, id, frames_per_urb, urb_frames_min, and nurbs) that can also be
set on the kernel command line as snd-usb-babyface-pro.<param>=. Should
these parameters be documented in
Documentation/admin-guide/kernel-parameters.txt?

> +static void babyface_private_free(struct snd_card *card)
> +{
> +	struct snd_usb_babyface *chip = card->private_data;
> +	unsigned int urbsize;
> +	int i;
> +
> +	if (!chip)
> +		return;
> +
> +	/* The URB arrays are NULL when the probe failed before allocating
> +	 * them (snd_card_free runs private_free on any probe error).
> +	 */
> +	if (chip->urbs_in) {
> +		urbsize = BF_WORDS_PER_FRAME * sizeof(u32) * chip->frames_per_urb;
> +		for (i = 0; i < chip->nurbs; i++) {
> +			if (chip->urbs_in[i]) {
> +				usb_kill_urb(chip->urbs_in[i]);
> +				usb_free_urb(chip->urbs_in[i]);
> +			}
> +			if (chip->urbs_out && chip->urbs_out[i]) {
> +				usb_kill_urb(chip->urbs_out[i]);
> +				usb_free_urb(chip->urbs_out[i]);
> +			}
> +			/* probe() allocates the six arrays separately and can
> +			 * fail between them, so each may be NULL here - this runs
> +			 * on the probe error path via snd_card_free().
> +			 */
> +			if (chip->buf_in && chip->dma_in)
> +				usb_free_coherent(chip->dev, urbsize,
> +						  chip->buf_in[i], chip->dma_in[i]);
> +			if (chip->buf_out && chip->dma_out)
> +				usb_free_coherent(chip->dev, urbsize,
> +						  chip->buf_out[i], chip->dma_out[i]);
> +		}
> +	}

[Severity: High]
In babyface_disconnect(), the driver stops the stream and calls
snd_card_free_when_closed(chip->card), deferring babyface_private_free()
until user-space closes the last ALSA file descriptor.

If the USB host controller is unbound or hot-unplugged while an audio
application holds the ALSA PCM device open, usb_remove_hcd() runs
babyface_disconnect() and then calls hcd_buffer_destroy(), which destroys
hcd->pool[] and sets the pool pointers to NULL.

When user-space later closes the ALSA file descriptor and
babyface_private_free() runs after disconnect has returned, won't calling
usb_kill_urb() and usb_free_coherent() violate the USB lifecycle contract
and trigger a NULL pointer dereference in dma_pool_free() (when urbsize <=
2048) or free DMA memory on a torn-down HCD device?

> +	kfree(chip->urbs_in);
> +	kfree(chip->urbs_out);
> +	kfree(chip->buf_in);
> +	kfree(chip->buf_out);
> +	kfree(chip->dma_in);
> +	kfree(chip->dma_out);
> +	usb_put_dev(chip->dev);
> +}
> +
> +static int babyface_probe(struct usb_interface *intf,
> +			  const struct usb_device_id *usb_id)
> +{
> +	struct usb_device *dev = interface_to_usbdev(intf);
> +	struct snd_usb_babyface *chip;
> +	struct snd_card *card;
> +	struct snd_pcm *pcm;
> +	unsigned int urbsize;
> +	int i, err;
> +
> +	if (intf->cur_altsetting->desc.bInterfaceNumber != BF_IFACE) {
> +		/* Only the proprietary audio interface is ours; the MIDI
> +		 * (standard class) and bulk interfaces stay unclaimed so
> +		 * snd-usb-audio can take the MIDI one.
> +		 */
> +		return -ENODEV;
> +	}
> +
> +	err = snd_card_new(&intf->dev, index[0], id[0], THIS_MODULE,
> +			   sizeof(*chip), &card);

[Severity: Low]
As reported by Sashiko on v7 [4], index and id are declared as
SNDRV_CARDS-element module_param_array arrays, but babyface_probe()
always passes index[0] and id[0] to snd_card_new().

If two devices are connected (or a device is re-probed before
snd_card_free_when_closed() finishes releasing the previous card) when a
non-default index or id parameter is specified, won't snd_card_new() fail
trying to claim the same slot or ID, while ignoring elements 1 through
SNDRV_CARDS - 1?

> +	if (err < 0) {
> +		dev_err(&intf->dev, "snd_card_new failed: %d\n", err);
> +		return err;
> +	}
> +	chip = card->private_data;
> +	chip->card = card;
> +
> +	chip->dev = usb_get_dev(dev);
> +	/* USB autosuspend is untested: nothing in this driver holds a PM
> +	 * reference while streaming, so an autosuspend request could race
> +	 * a live stream.  Disable it explicitly rather than ship an
> +	 * untested code path - full autosuspend support (correct
> +	 * autopm_get/put pairing around the stream) is a deliberate
> +	 * follow-up, not an oversight.
> +	 */
> +	usb_disable_autosuspend(chip->dev);

[ ... ]

> +error:
> +	usb_set_intfdata(intf, NULL);
> +	/* Balance the probe()-time usb_disable_autosuspend(): disconnect()
> +	 * is never called for a failed probe, so the disable would leak and
> +	 * leave autosuspend off on this usb_device until a physical unplug.
> +	 */
> +	usb_enable_autosuspend(chip->dev);
> +	snd_card_free(chip->card);
> +	return err;
> +}
> +
> +static void babyface_disconnect(struct usb_interface *intf)
> +{

[ ... ]

> +	cancel_work_sync(&chip->stream_work);
> +	/* Balance the probe()-time usb_disable_autosuspend(): the usb_device
> +	 * outlives this interface claim (a usbfs detach re-probes without
> +	 * the physical device ever disconnecting), so leaving autosuspend
> +	 * disabled here would wrongly affect whatever claims the device next.
> +	 */
> +	usb_enable_autosuspend(chip->dev);

[Severity: Medium]
usb_disable_autosuspend() and usb_enable_autosuspend() wrap
pm_runtime_forbid() and pm_runtime_allow() on struct usb_device and are
not reference-counted; USB core initializes non-hub USB devices with
autosuspend disabled (power/control set to on) by default, and because
babyface_driver does not set supports_autosuspend, USB core already keeps
the bound interface active while claimed.

When babyface_probe() fails or babyface_disconnect() unbinds interface 5,
won't calling usb_enable_autosuspend(chip->dev) unconditionally flip the
parent usb_device's power/control setting to auto and allow the composite
device (including its MIDI interface claimed by snd-usb-audio) to
autosuspend unexpectedly?

[1] https://lore.kernel.org/r/20261027113140.134257-2-i.bahloul01@gmail.com
[2] https://lore.kernel.org/r/20261027113140.134257-2-i.bahloul01@gmail.com
[3] https://lore.kernel.org/r/20261027113140.134257-2-i.bahloul01@gmail.com
[4] https://lore.kernel.org/r/20261027113140.134257-2-i.bahloul01@gmail.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261105151553.223722-1-i.bahloul01@gmail.com?part=1

  reply	other threads:[~2026-11-05 16:24 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 15:15 [PATCH v8 0/8] ALSA: usb: add RME Babyface Pro driver (proprietary mode) Ismaïl Bahloul
2026-11-05 15:15 ` [PATCH v8 1/8] ALSA: usb: add RME Babyface Pro driver core (probe, PCM stream) Ismaïl Bahloul
2026-11-05 16:24   ` sashiko-bot [this message]
2026-11-05 15:15 ` [PATCH v8 2/8] ALSA: usb: babyfacepro: add output masters and crosspoint routing Ismaïl Bahloul
2026-11-05 15:31   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 3/8] ALSA: usb: babyfacepro: add mic preamp, phantom/pad and input trim Ismaïl Bahloul
2026-11-05 15:32   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 4/8] ALSA: usb: babyfacepro: add routing flags and varispeed pitch Ismaïl Bahloul
2026-11-05 15:32   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 5/8] ALSA: usb: babyfacepro: add S3 suspend/resume Ismaïl Bahloul
2026-11-05 16:45   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 6/8] ALSA: usb: babyfacepro: add the front-panel poll and controls Ismaïl Bahloul
2026-11-05 17:03   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 7/8] ALSA: usb: babyfacepro: add the hardware DSP EQ Ismaïl Bahloul
2026-11-05 17:19   ` sashiko-bot
2026-11-05 15:15 ` [PATCH v8 8/8] Documentation: sound: add the Babyface Pro proprietary-mode design doc Ismaïl Bahloul

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-199481@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=i.bahloul01@gmail.com \
    --cc=linux-sound@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox