All of lore.kernel.org
 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 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.