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 v7 4/8] ALSA: usb: babyfacepro: add routing flags and varispeed pitch
Date: Tue, 27 Oct 2026 11:47:01 +0000	[thread overview]
Message-ID: <sashiko-outbox-186473@kernel.org> (raw)
In-Reply-To: <20261027113140.134257-5-i.bahloul01@gmail.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] ALSA: usb: babyfacepro: bf_state_apply_flags() unmutes AN1/2 inputs on probe
- [High] ALSA: usb: babyfacepro: bf_ms_put() restores wrong crosspoints to AN1/2
- [High] ALSA: usb: babyfacepro: DIM and Phones master/mute states clobber each other
- [Medium] ALSA: usb: babyfacepro: preamp flag commits clear Boost ref level state
- [Medium] ALSA: usb: babyfacepro: bf_loopback_put() omits notify on cleared outputs
- [Low] ALSA: usb: babyfacepro: missing enum bounds and long checks in flag puts
- [Low] ALSA: usb: babyfacepro: unused dim_kctl pointer in babyface_create_flags()
--

commit 43e9e598414a501c1d77575b85841ffbf7ea280c
Author: Ismaïl Bahloul <i.bahloul01@gmail.com>

ALSA: usb: babyfacepro: add routing flags and varispeed pitch

This commit adds ALSA mixer controls and state restoration for loopback,
AN 1>2, sample clock source, optical output format, AN1/2 input link, MS
processor, DIM, stereo width, FX send, and varispeed pitch.

> diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c
> index 60604242be883..39584ec719b23 100644
> --- a/sound/usb/babyfacepro/babyfacepro-ctl.c
> +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c

[ ... ]

> @@ -377,6 +381,11 @@ static int bf_master_put(struct snd_kcontrol *kctl,
>  
>  	chip->master[out][0] = l;
>  	chip->master[out][1] = r;
> +	/* A Phones change while DIM is engaged re-bases the restore point. */
> +	if (chip->dim && out == 1) {
> +		chip->dim_saved[0] = l;
> +		chip->dim_saved[1] = r;
> +	}
>  	ret = 1;
>  out:
>  	mutex_unlock(&chip->mutex);

[Severity: High]
Does changing the Phones master volume while DIM is engaged cancel the DIM
attenuation on the hardware?

In bf_master_put(), when chip->dim is true and out == 1, the code updates
chip->dim_saved[0..1], but only after writing wire_l and wire_r (the full
undimmed volume) to BF_REG_MASTER_8 + 2 * out and BF_REG_MASTER_16 + 2 * out.
That drives the Phones output to the undimmed level while chip->dim remains
true.

Similarly, in bf_mute_put(), unmuting out == 1 while chip->dim is true writes
chip->master[1][0..1] to the Phones master registers instead of the dimmed
BF_MASTER_MINUS20_8 and BF_MASTER_MINUS20_16 levels.

> @@ -1280,3 +1289,699 @@ int babyface_create_preamp(struct snd_usb_babyface *chip)

[ ... ]

> +static int bf_pitch_put(struct snd_kcontrol *kctl,
> +			struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	int p = ucontrol->value.integer.value[0];
> +	int ret = 0;
> +
> +	if (p < -50 || p > 50)
> +		return -EINVAL;

[Severity: Low]
Can ucontrol->value.integer.value[0] wrap when narrowed from long to int here
(and in bf_width_put() below) before the range check, unlike bf_master_put()
and bf_fx_send_put() which validate the long value prior to narrowing?

[ ... ]

> +static int bf_loopback_put(struct snd_kcontrol *kctl,
> +			   struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	int out = kctl->private_value;
> +	bool on = ucontrol->value.integer.value[0];
> +	int ret = 0;
> +
> +	mutex_lock(&chip->mutex);
> +	if (on == chip->loopback[out])
> +		goto out;
> +	ret = bf_loopback_write_map(chip, out, on);
> +	if (ret < 0)
> +		goto out;
> +	/* Single-active model (TotalMix writes one pair at 0x0001, the
> +	 * rest 0x0000): toggling one output clears the others.
> +	 */
> +	memset(chip->loopback, 0, sizeof(chip->loopback));
> +	chip->loopback[out] = on;
> +	ret = 1;
> +out:
> +	mutex_unlock(&chip->mutex);
> +	return ret;
> +}

[Severity: Medium]
When enabling Loopback Switch on one output clears a previously active
chip->loopback[] entry via memset(), should this notify user space with
snd_ctl_notify() for the cleared control (similar to how bf_trim_put()
notifies its sibling control)?

Also, babyface_create_flags() registers Loopback Switch using bf_mute_info
(which declares count = 2 channels), whereas bf_loopback_put() only inspects
ucontrol->value.integer.value[0]. Should Loopback Switch use bf_switch_info
(count = 1) or handle both channels?

[ ... ]

> +static int bf_an12_put(struct snd_kcontrol *kctl,
> +		       struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	bool an12 = ucontrol->value.integer.value[0];
> +	u16 v;
> +	int ret = 0;
> +
> +	mutex_lock(&chip->mutex);
> +	if (an12 == chip->an12)
> +		goto out;
> +	v = (chip->linked ? 0x0400 : 0x0000) | (an12 ? 0x1000 : 0x0000);
> +	ret = bf_vendor_write(chip, BF_REQ_PREAMP, v, 0x1000);
> +	if (ret < 0)
> +		goto out;
> +	ret = bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000);

[Severity: Medium]
Does sending BF_REQ_PREAMP_COMMIT with a hardcoded 0x0000 here (as well as in
bf_link_put() and bf_state_apply_flags()) clear the Instrument 3/4 Boost
reference level on the hardware?

In bf_preamp_state_write(), Boost is distinguished from -10dBV by passing
wValue = 0x0003 instead of 0x0000 to BF_REQ_PREAMP_COMMIT:

    u16 commit = chip->ref_level == BF_REF_LEVEL_BOOST ? 0x0003 : 0x0000;

When chip->ref_level == BF_REF_LEVEL_BOOST, toggling AN 1>2 Switch or AN1/2
Link Switch, or running bf_state_apply_flags() after bf_preamp_state_write(),
sends 0x0000 and drops the hardware back to -10dBV while chip->ref_level still
reports Boost.

[ ... ]

> +static int bf_clock_put(struct snd_kcontrol *kctl,
> +			struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	bool optical = ucontrol->value.enumerated.item[0] != 0;
> +	int ret = 0;

[ ... ]

> +static int bf_optical_put(struct snd_kcontrol *kctl,
> +			  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	bool spdif = ucontrol->value.enumerated.item[0] != 0;
> +	int ret = 0;

[Severity: Low]
Should bf_clock_put() and bf_optical_put() reject ucontrol->value.enumerated.item[0] >= 2
with -EINVAL when CONFIG_SND_CTL_INPUT_VALIDATION is not enabled, matching
bf_reflevel_put()?

[ ... ]

> +static int bf_ms_put(struct snd_kcontrol *kctl,
> +		     struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	bool on = ucontrol->value.integer.value[0];
> +	int ret = 0;
> +
> +	mutex_lock(&chip->mutex);
> +	if (on == chip->ms_proc)
> +		goto out;
> +	if (on) {
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x0035);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x004f);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x0001);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x001b);
> +		if (ret < 0)
> +			goto out;
> +	} else {
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      chip->xpoint[1][1][0], 0x0001);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      chip->xpoint[1][1][0], 0x0035);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      chip->xpoint[1][1][1], 0x001b);
> +		if (ret < 0)
> +			goto out;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      chip->xpoint[1][1][1], 0x004f);
> +		if (ret < 0)
> +			goto out;
> +	}

[Severity: High]
When disengaging MS Processor Switch (on == false), does this write output 1's
(PH3/4) cached AN2 crosspoints into output 0's (AN1/2) registers?

Registers 0x0001 (L) and 0x001b (R) belong to output pair 0 (AN1/2), whose
cached AN2 levels are in chip->xpoint[0][1][0] and chip->xpoint[0][1][1],
whereas chip->xpoint[1][1][0..1] holds output pair 1 (PH3/4, registers 0x0035
and 0x004f). Writing chip->xpoint[1][1][0..1] to 0x0001 and 0x001b overwrites
output 0's AN2 routing with output 1's fader values and leaves hardware out of
sync with chip->xpoint[0][1].

[ ... ]

> +static int bf_dim_apply(struct snd_usb_babyface *chip, bool on)
> +{
> +	u16 flag;
> +	int ret;
> +
> +	lockdep_assert_held(&chip->mutex);
> +	if (on) {
> +		chip->dim_saved[0] = chip->master[1][0];
> +		chip->dim_saved[1] = chip->master[1][1];
> +		ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_MINUS20_8,
> +				      BF_REG_MASTER_8 + 2 * 1);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_MINUS20_8,
> +				      BF_REG_MASTER_8 + 2 * 1 + 1);
> +		if (ret < 0)
> +			return ret;
> +		flag = bf_flag_cycle[chip->flag_cnt];
> +		chip->flag_cnt = (chip->flag_cnt + 1) & 3;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      BF_MASTER_MINUS20_16,
> +				      (BF_REG_MASTER_16 + 2 * 1) | flag);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      BF_MASTER_MINUS20_16,
> +				      (BF_REG_MASTER_16 + 2 * 1 + 1) | flag);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_PREAMP, 0x2000, 0x2000);
> +		if (ret < 0)
> +			return ret;
> +	} else {
> +		ret = bf_vendor_write(chip, BF_REQ_GAIN,
> +				      bf_master_reg8(chip->dim_saved[0]),
> +				      BF_REG_MASTER_8 + 2 * 1);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_GAIN,
> +				      bf_master_reg8(chip->dim_saved[1]),
> +				      BF_REG_MASTER_8 + 2 * 1 + 1);
> +		if (ret < 0)
> +			return ret;
> +		flag = bf_flag_cycle[chip->flag_cnt];
> +		chip->flag_cnt = (chip->flag_cnt + 1) & 3;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      bf_master_reg16(chip->dim_saved[0]),
> +				      (BF_REG_MASTER_16 + 2 * 1) | flag);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT,
> +				      bf_master_reg16(chip->dim_saved[1]),
> +				      (BF_REG_MASTER_16 + 2 * 1 + 1) | flag);
> +		if (ret < 0)
> +			return ret;
> +		ret = bf_vendor_write(chip, BF_REQ_PREAMP, 0x0000, 0x2000);
> +		if (ret < 0)
> +			return ret;
> +	}
> +	chip->dim = on;
> +	return 0;
> +}

[Severity: High]
What happens in bf_dim_apply() (and in the chip->dim restore block in
bf_state_apply_flags()) if the Phones output is muted (chip->muted[1] == true)?

Both paths write non-muted gain values (BF_MASTER_MINUS20_8 /
BF_MASTER_MINUS20_16 on engage, or chip->dim_saved[0..1] on release) to the
Phones master registers without checking chip->muted[1]. If Phones was muted,
toggling Dim Switch or running bf_state_apply_flags() unmutes the headphones
on the hardware while chip->muted[1] remains true.

[ ... ]

> +	kctl = snd_ctl_new1(&(struct snd_kcontrol_new){
> +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +		.name = "Dim Switch",
> +		.info = bf_switch_info,
> +		.get = bf_dim_get,
> +		.put = bf_dim_put,
> +	}, chip);
> +	chip->dim_kctl = kctl;
> +	err = snd_ctl_add(chip->card, kctl);
> +	if (err < 0)
> +		return err;

[Severity: Low]
This isn't a bug, but chip->dim_kctl is assigned here in
babyface_create_flags() and never read anywhere in the driver (a later patch
adds a separate chip->dim_press_kctl for the front-panel button). Can
dim_kctl be dropped from struct snd_usb_babyface?

> diff --git a/sound/usb/babyfacepro/babyfacepro.c b/sound/usb/babyfacepro/babyfacepro.c
> index c2835e2d64851..ed4555c711064 100644
> --- a/sound/usb/babyfacepro/babyfacepro.c
> +++ b/sound/usb/babyfacepro/babyfacepro.c

[ ... ]

> @@ -300,14 +317,111 @@ int babyface_restore_state(struct snd_usb_babyface *chip)
>  	return bf_pitch_write(chip, chip->pitch);
>  }
>  
> -/* Re-apply the per-input state that sits on top of the crosspoints
> - * (phase invert, stereo split, trim) after a state restore.  The write
> - * patterns mirror the corresponding _put() handlers.  Caller holds
> - * chip->mutex.
> +/* Re-apply the state that sits on top of the masters and crosspoints
> + * (the routing flags, DIM, phase invert, stereo split, trim) after a
> + * state restore.  The write patterns mirror the corresponding _put()
> + * handlers.  Caller holds chip->mutex.
>   */
>  int bf_state_apply_flags(struct snd_usb_babyface *chip)
>  {
> -	int ret;
> +	int out, ret, on_out = -1;
> +	u16 l, r;
> +
> +	/* Loopback: the full 30-channel map from the cached state (the
> +	 * single-active invariant keeps at most one pair at 0x0001).
> +	 */
> +	for (out = 0; out < 6; out++) {
> +		if (chip->loopback[out]) {
> +			on_out = out;
> +			break;
> +		}
> +	}
> +	ret = bf_loopback_write_map(chip, on_out, on_out >= 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = bf_vendor_write(chip, BF_REQ_PREAMP,
> +			      (chip->linked ? 0x0400 : 0x0000) |
> +			      (chip->an12 ? 0x1000 : 0x0000), 0x1000);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000);
> +	if (ret < 0)
> +		return ret;
> +
> +	l = (u16)(((0x2000 * (100 + chip->width) / 2) + 50) / 100);
> +	r = 0x2000 - l;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x0000);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x001a);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x0001);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x001b);
> +	if (ret < 0)
> +		return ret;

[Severity: High]
Does running bf_state_apply_flags() unconditionally unmute hardware inputs AN1
and AN2 into the main outputs (AN1/2) on probe and state restore?

In babyface_probe(), babyface_write_default_mixer() intentionally writes 0 to
all hardware input crosspoints (src < BF_SRC_PB1, which includes AN1 at
0x0000/0x001a and AN2 at 0x0001/0x001b on output 0) and caches 0 in
chip->xpoint[0][0..1][0..1] so live inputs are not routed out of the box.

Immediately afterward, babyface_probe() calls bf_state_apply_flags(). Even
when chip->width == 0, this block computes l = 0x1000 and r = 0x1000 and
writes them to registers 0x0000, 0x001a, 0x0001, and 0x001b, routing AN1 and
AN2 at 0 dB into output 0 while chip->xpoint[0][0..1][0..1] remains 0. Because
the cache still holds 0, a subsequent alsactl restore or mixer write of 0 to
those crosspoints hits the early return in bf_xpoint_put() and cannot mute
them.

Also, when chip->width is non-zero, bf_width_put() writes both the low map
(0x0000, 0x001a, 0x0001, 0x001b) and the block-0 map (0x0034, 0x004e, 0x0035,
0x004f), whereas bf_state_apply_flags() only writes the low map. Should the
width writes in bf_state_apply_flags() be guarded by if (chip->width) and
include both maps?

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

  reply	other threads:[~2026-10-27 11:47 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 11:31 [PATCH v7 0/8] ALSA: usb: add RME Babyface Pro driver (proprietary mode) Ismaïl Bahloul
2026-10-27 11:31 ` [PATCH v7 1/8] ALSA: usb: add RME Babyface Pro driver core (probe, PCM stream) Ismaïl Bahloul
2026-10-27 12:02   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 2/8] ALSA: usb: babyfacepro: add output masters and crosspoint routing Ismaïl Bahloul
2026-10-27 11:48   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 3/8] ALSA: usb: babyfacepro: add mic preamp, phantom/pad and input trim Ismaïl Bahloul
2026-10-27 11:48   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 4/8] ALSA: usb: babyfacepro: add routing flags and varispeed pitch Ismaïl Bahloul
2026-10-27 11:47   ` sashiko-bot [this message]
2026-10-27 11:31 ` [PATCH v7 5/8] ALSA: usb: babyfacepro: add S3 suspend/resume Ismaïl Bahloul
2026-10-27 11:53   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 6/8] ALSA: usb: babyfacepro: add the front-panel poll and controls Ismaïl Bahloul
2026-10-27 11:58   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 7/8] ALSA: usb: babyfacepro: add the hardware DSP EQ Ismaïl Bahloul
2026-10-27 11:46   ` sashiko-bot
2026-10-27 11:31 ` [PATCH v7 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-186473@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