Linux USB
 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 7/8] ALSA: usb: babyfacepro: add the hardware DSP EQ
Date: Thu, 05 Nov 2026 17:19:07 +0000	[thread overview]
Message-ID: <sashiko-outbox-199596@kernel.org> (raw)
In-Reply-To: <20261105151553.223722-8-i.bahloul01@gmail.com>

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] ALSA: usb: babyfacepro: shared biquad scale overwritten in bf_eq_update_strip()
- [High] ALSA: usb: babyfacepro: inverted sign of high-shelf a1 in bf_eq_band_words()
- [Medium] ALSA: usb: babyfacepro: unrolled EQ cache in bf_eq_put() and ignored errors in bf_eq_reupload()
- [Medium] ALSA: usb: babyfacepro: missing freq_hz <= 0 guard in bf_eq_band_words()
- [Medium] ALSA: usb: babyfacepro: uninitialized boolean min and max in bf_eq_info()
- [Medium] ALSA: usb: babyfacepro: plain 64-bit signed division in bf_eq_lc_freq_raw()
- [Medium] ALSA: usb: babyfacepro: missing hardware DSP EQ reset in babyface_probe()
- [Medium] ALSA: usb: babyfacepro: ignored error return in bf_eq_reupload()
- [Low] ALSA: usb: babyfacepro: 64-bit value truncated before range check in bf_eq_put()
--

commit 100d534996c41ac8c2801f7eafc48e129593aa13
Author: Ismaïl Bahloul <i.bahloul01@gmail.com>

ALSA: usb: babyfacepro: add the hardware DSP EQ

This commit adds support for the 4-strip (AN1-AN4) 3-band and low-cut
hardware DSP parametric EQ, including fixed-point Q27 biquad coefficient
calculation, 64-byte block uploads over bulk endpoint 0x0A, and 15 ALSA
mixer controls per strip.

> diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c
> index 51cf8c7bd4357..a1b8caa281dad 100644
> --- a/sound/usb/babyfacepro/babyfacepro-ctl.c
> +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c

[ ... ]

> @@ -3336,3 +3337,610 @@ int babyface_create_panel(struct snd_usb_babyface *chip)

[ ... ]

> +void bf_eq_band_words(s32 *w, int type, s32 freq_hz, s32 q100,
> +		      s32 gain_x10, s32 fs)
> +{
> +	s64 f = freq_hz;
> +	s64 w0, c, s, u, su, cu, alpha, A, sq;
> +	s64 b0, b1, b2, a0, a1, a2;
> +	s64 pi = 0x1921FB54;	/* pi, Q27 */
> +	s64 hpi = 0xC90FDAA;	/* pi/2, Q27 */
> +	s64 t;
> +	int both = 0, cflip = 0;
> +
> +	if (type == 0 || gain_x10 == 0 || q100 <= 0) {
> +		/* Inactive band (type Off, or no gain/Q yet): identity
> +		 * words.  Also guards the alpha division below against
> +		 * the default Q=0 the controls start with - a user
> +		 * setting gain before Q used to hit a kernel
> +		 * divide-by-zero oops.
> +		 */
> +		w[0] = 0;
> +		w[1] = 0;
> +		w[2] = 0;
> +		w[3] = 0;
> +		/* w[4] is the shared scale bf_eq_update_strip() reads when the
> +		 * band has a type and a gain but no Q yet (Q defaults to 0).
> +		 * Leaving it unwritten uploaded a garbage shared scale.
> +		 */
> +		w[4] = BF_EQ_Q27;
> +		return;
> +	}

[Severity: Medium]
Should the inactive-band check in bf_eq_band_words() also guard against
freq_hz <= 0, similar to bf_eq_lc_freq_raw()?

At probe time, bf_eq_defaults() initializes band_q[] to 100 while leaving
band_freq[] at 0 Hz, and bf_eq_info() / bf_eq_put() allow frequency values
down to 0. If a user sets a band's Type and Gain before setting Freq (or
sets Freq to 0), bf_eq_band_words() runs with freq_hz == 0, which gives
w0 = 0 and u = 0. Calling bf_sincos(0, ...) produces su = -1 (alpha < 0)
and c = 0x7fffffe (~1.0 in Q27), uploading a degenerate biquad with poles
on or outside the unit circle at z = 1 instead of the identity filter.

[ ... ]

> +		if (type == 2) {	/* low shelf */
> +			b0 = (A * (ap1 - cp0 + ab) + (1 << 26)) >> 27;
> +			b1 = (2 * A * (am1 - cp1) + (1 << 26)) >> 27;
> +			b2 = (A * (ap1 - cp0 - ab) + (1 << 26)) >> 27;
> +			a0 = ap1 + cp0 + ab;
> +			a1 = -2 * (am1 + cp1);
> +			a2 = ap1 + cp0 - ab;
> +		} else {		/* high shelf */
> +			b0 = (A * (ap1 + cp0 + ab) + (1 << 26)) >> 27;
> +			b1 = (-2 * A * (am1 + cp1) + (1 << 26)) >> 27;
> +			b2 = (A * (ap1 + cp0 - ab) + (1 << 26)) >> 27;
> +			a0 = ap1 - cp0 + ab;
> +			a1 = -2 * (am1 - cp1);
> +			a2 = ap1 - cp0 - ab;
> +		}
> +	}
> +
> +	w[0] = (s32)div64_s64(a1 * BF_EQ_Q27 + a0 / 2, a0);
> +	w[1] = (s32)div64_s64(a2 * BF_EQ_Q27 + a0 / 2, a0);
> +	w[2] = (s32)div64_s64(b1 * BF_EQ_Q27 + b0 / 2, b0);
> +	w[3] = (s32)div64_s64(b2 * BF_EQ_Q27 + b0 / 2, b0);
> +	w[4] = (s32)div64_s64(b0 * BF_EQ_Q27 + a0 / 2, a0);
> +}

[Severity: High]
In the high-shelf branch (type == 3) of bf_eq_band_words(), is the sign of
a1 inverted?

With am1 = A - 1 and cp1 = (A + 1) * cos(w0), the Audio EQ Cookbook
high-shelf denominator coefficient is:

  a1 = 2 * ((A - 1) - (A + 1) * cos(w0)) = 2 * (am1 - cp1)

Using a1 = -2 * (am1 - cp1) negates w[0], so as gain approaches 0 dB (A -> 1,
am1 -> 0, cp1 -> 2 * cos(w0)), b1 approaches -4 * cos(w0) while a1
approaches +4 * cos(w0), placing the filter poles at pi - w0 instead of w0.

[ ... ]

> +static u32 bf_eq_lc_freq_raw(s32 freq_hz, s32 slope_db)
> +{
> +	s64 f, word;
> +
> +	if (freq_hz <= 0)
> +		return BF_EQ_LC_OFF;
> +	f = freq_hz;
> +	switch (slope_db) {
> +	case 6:
> +		f = f * 15267 / 10000;
> +		break;
> +
> +	case 18:
> +		f = f * 8061 / 10000;
> +		break;
> +
> +	case 24:
> +		f = f * 6977 / 10000;
> +		break;
> +	}
> +	word = div64_s64(11508 * f * 11656 + (11656 + f) / 2, 11656 + f);
> +	return (u32)word;
> +}

[Severity: Medium]
As reported by Sashiko on v7 [1], in bf_eq_lc_freq_raw(), f is declared as
s64 and divided by 10000 using the plain / operator.

Because bf_eq_reupload() -> bf_eq_update_strip() passes chip->eq[strip].lc_hz
(an s32 field in memory) as freq_hz, the compiler only knows freq_hz is in
[1, INT_MAX], so f * 15267 can exceed 32 bits. On 32-bit architectures,
this emits a libcall to __divdi3 / __aeabi_ldivmod and fails at link time.
Should this use div_s64() (or 32-bit arithmetic since lc_hz <= 20000)?

[ ... ]

> +static int bf_eq_update_strip(struct snd_usb_babyface *chip, int strip)
> +{
> +	struct bf_eq_channel *e = &chip->eq[strip];
> +	s32 fs = chip->rate ? chip->rate : 48000;
> +	s32 last_c4 = BF_EQ_Q27;
> +	int band, i;
> +
> +	lockdep_assert_held(&chip->mutex);
> +
> +	for (band = 0; band < 3; band++) {
> +		s32 w[5];
> +
> +		bf_eq_band_words(w, e->band_type[band], e->band_freq[band],
> +				 e->band_q[band], e->band_gain[band], fs);
> +		for (i = 0; i < 4; i++)
> +			e->words[band][i] = w[i];
> +		if (e->band_type[band] && e->band_gain[band])
> +			last_c4 = w[4];	/* shared scale: the last band */
> +	}
> +	e->shared = last_c4;
> +	e->lc_raw = bf_eq_lc_freq_raw(e->lc_hz, e->slope_db);
> +	e->slope = bf_eq_lc_slope_byte(e->slope_db);
> +	return bf_eq_write_strip(chip, strip);
> +}

[Severity: High]
When two or three bands are active on the same strip in
bf_eq_update_strip(), doesn't overwriting last_c4 = w[4] discard the b0/a0
scale factors of the earlier active bands?

In bf_eq_band_words(), each band's numerator coefficients w[2] = b1 / b0
and w[3] = b2 / b0 are normalized by b0 rather than a0, factoring that
band's b0 / a0 gain scale into w[4]. Since the three biquad slots in
bf_eq_build_block() only receive w[0..3] and share a single scale word at
offset 0x34, shouldn't last_c4 accumulate the Q27 product of w[4] across all
active bands rather than taking only the last active band's w[4]?

> +
> +/* Recompute + re-upload all four strips (rate change). Caller must
> + * hold chip->mutex - bf_eq_update_strip()/bf_eq_write_strip() are
> + * lock-free by convention (see bf_eq_put()) and the only caller,
> + * babyface_pcm_hw_params(), already holds the lock across the rate
> + * change; locking here too self-deadlocked it (hung-task: "blocked
> + * on a mutex likely owned by" itself, hit via regress.sh's rate
> + * sweep).
> + */
> +void bf_eq_reupload(struct snd_usb_babyface *chip)
> +{
> +	int strip;
> +
> +	for (strip = 0; strip < 4; strip++)
> +		bf_eq_update_strip(chip, strip);
> +}

[Severity: Medium]
As reported by Sashiko on v7 [1], bf_eq_reupload() ignores the error return
value of bf_eq_update_strip().

If kmemdup() or usb_bulk_msg() fails in bf_eq_upload(), bf_eq_reupload()
continues attempting up to 7 more synchronous bulk transfers (with a 1000 ms
timeout each while holding chip->mutex), and callers such as
bf_resume_locked() and babyface_pcm_hw_params() cannot detect or propagate
the failure. Should bf_eq_reupload() return an int error code and stop on
the first failing strip?

[ ... ]

> +static int bf_eq_info(struct snd_kcontrol *kctl,
> +		      struct snd_ctl_elem_info *uinfo)
> +{
> +	int param = EQ_PARAM(kctl->private_value);
> +
> +	if (param == 0) {
> +		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
> +		uinfo->count = 1;
> +		return 0;
> +	}

[Severity: Medium]
When param == 0 in bf_eq_info(), uinfo->value.integer.min and
uinfo->value.integer.max are left uninitialized.

In snd_ctl_elem_info_user(), uinfo is copied from userspace without zeroing
uinfo->value, so callers reusing the struct across controls see stale
min/max values; in 32-bit compat mode (snd_ctl_elem_info_compat()), max is
reported as 0 instead of 1. Could this set min = 0 and max = 1 (or return
snd_ctl_boolean_mono_info(uinfo)) like the other boolean controls in this
file?

[ ... ]

> +static int bf_eq_put(struct snd_kcontrol *kctl,
> +		     struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	int strip = EQ_STRIP(kctl->private_value);
> +	int param = EQ_PARAM(kctl->private_value);
> +	struct bf_eq_channel *e = &chip->eq[strip];
> +	int band = (param - 1) % 3;
> +	s32 nv;
> +	s32 *v = NULL;
> +	int ret = 0;
> +
> +	/* Read from the union member matching the control type: ENUMERATED
> +	 * params (band type 1-3, slope 14) use .enumerated.item, everything
> +	 * else (BOOL 0, INTEGER) uses .integer.value.
> +	 */
> +	if (param == 1 || param == 2 || param == 3 || param == 14)
> +		nv = (s32)ucontrol->value.enumerated.item[0];
> +	else
> +		nv = (s32)ucontrol->value.integer.value[0];

[Severity: Low]
On 64-bit architectures, ucontrol->value.integer.value[0] is a 64-bit long.
Casting it to s32 before the switch (param) range checks below truncates the
upper 32 bits when CONFIG_SND_CTL_INPUT_VALIDATION is disabled, allowing
out-of-range 64-bit values whose lower 32 bits fall within bounds to wrap
and be accepted. Could nv be kept as a long (or range-checked before
narrowing, as in bf_master_put() and bf_width_put())?

[ ... ]

> +	guard(mutex)(&chip->mutex);
> +	if (param == 0) {
> +		if (e->on != !!nv) {
> +			e->on = !!nv;
> +			ret = bf_eq_update_strip(chip, strip);
> +			if (ret < 0)
> +				return ret;
> +			ret = 1;
> +		}
> +	} else if (v && *v != nv) {
> +		*v = nv;
> +		ret = bf_eq_update_strip(chip, strip);
> +		if (ret < 0)
> +			return ret;
> +		ret = 1;
> +	}
> +	return ret;
> +}

[Severity: Medium]
As reported by Sashiko on v7 [1], in bf_eq_put(), e->on or *v is updated
before calling bf_eq_update_strip() and is not restored if
bf_eq_update_strip() fails (for example, if kmemdup() returns -ENOMEM or
usb_bulk_msg() fails in bf_eq_upload()).

Because chip->eq[strip] retains the new value nv, the host cache becomes
desynchronized from the hardware DSP, and a subsequent retry writing the
same value nv sees e->on == !!nv or *v == nv and returns 0 without
re-uploading the coefficients. In addition, while bf_eq_upload() sleeps,
concurrent lockless readers in bf_eq_get() observe the uncommitted value.
Should bf_eq_put() restore the previous value on failure (like
bf_phantom_put() and bf_reflevel_put())?

> diff --git a/sound/usb/babyfacepro/babyfacepro.c b/sound/usb/babyfacepro/babyfacepro.c
> index ece2bd0c4e63a..ed17600aba2f0 100644
> --- a/sound/usb/babyfacepro/babyfacepro.c
> +++ b/sound/usb/babyfacepro/babyfacepro.c

[ ... ]

> @@ -1638,6 +1640,8 @@ static int babyface_probe(struct usb_interface *intf,
>  			BF_REF_LEVEL_4DBU : BF_REF_LEVEL_MINUS10DBV;
>  	}
>  
> +	bf_eq_defaults(chip);
> +
>  	/* The 0x16 clear zeroed the mixer registers: apply the factory
>  	 * default routing to keep the outputs live out of the box.  alsactl
>  	 * restores the user's settings once the card is registered.

[Severity: Medium]
Does babyface_probe() also need to call bf_eq_reupload(chip) after
bf_eq_defaults(chip) to reset the hardware DSP EQ state?

As noted in the commit message and bf_resume_locked(), bf_cold_init() does
not clear the DSP EQ on bulk endpoint 0x0A. If the device already had EQ
filters active before a driver reload, warm reboot, or re-probe,
bf_eq_defaults() sets e->on = false and default parameters in chip->eq[]
without uploading identity blocks to the hardware. When alsactl subsequently
restores a state where EQ is disabled (or at default values), bf_eq_put()
sees e->on == !!nv and *v == nv and skips bf_eq_update_strip(), leaving the
stale EQ filters active in the hardware DSP while the ALSA controls report
EQ Enable as Off.

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

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

  reply	other threads:[~2026-11-05 17:19 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
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 [this message]
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-199596@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