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
next prev parent reply other threads:[~2026-11-05 16:24 UTC|newest]
Thread overview: 17+ 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
2026-11-05 17:21 ` sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.