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 3/8] ALSA: usb: babyfacepro: add mic preamp, phantom/pad and input trim
Date: Thu, 05 Nov 2026 15:32:00 +0000	[thread overview]
Message-ID: <sashiko-outbox-199312@kernel.org> (raw)
In-Reply-To: <20261105151553.223722-4-i.bahloul01@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] ALSA: usb: babyfacepro: bf_trim_apply() and bf_phase_apply() clobber xpoints
- [Medium] ALSA: usb: babyfacepro: hardcoded Mic 1 names in babyface_create_preamp()
- [Low] ALSA: usb: babyfacepro: unchecked snd_ctl_new1() in babyface_create_preamp()
- [Low] ALSA: usb: babyfacepro: bf_phase_info() and bf_bool_info() duplicate code
--

commit 82ef805ee77606164dc3814bf736734ca60721d5
Author: Ismaïl Bahloul <i.bahloul01@gmail.com>

ALSA: usb: babyfacepro: add mic preamp, phantom/pad and input trim

This commit adds ALSA mixer controls for the input side of the RME Babyface
Pro, including phantom power, pad, instrument reference level, mic/instrument
preamp gains, phase invert, stereo split, and input trim.

> diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c
> index 942d4732cbe07..fe6266ea118e6 100644
> --- a/sound/usb/babyfacepro/babyfacepro-ctl.c
> +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c
> @@ -654,3 +654,627 @@ int babyface_create_masters(struct snd_usb_babyface *chip)

[ ... ]

> +int bf_phase_apply(struct snd_usb_babyface *chip, int mic, bool invert)
> +{
> +	const struct bf_source *s = &bf_sources[mic];
> +	int out, ret;
> +	u16 flag;
> +
> +	for (out = 0; out < 6; out++) {
> +		u16 plain = chip->xpoint[out][mic][0];
> +		u16 value = invert ? (u16)~plain : plain;
> +
> +		/* The AN1/2 block is written without the rotating bits, as
> +		 * bf_xpoint_write() does.
> +		 */
> +		flag = 0;
> +		if (out) {
> +			flag = bf_flag_cycle[chip->flag_cnt];
> +			chip->flag_cnt = (chip->flag_cnt + 1) & 3;
> +		}
> +		ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, value,
> +				      (BF_REG_XPOINT_BASE_L +
> +				       BF_REG_XPOINT_STRIDE * out + s->idx_l) |
> +				      flag);
> +		if (ret < 0)
> +			return ret;
> +	}
> +	return 0;
> +}

[ ... ]

> +int bf_split_apply(struct snd_usb_babyface *chip, int pb, bool split)
> +{
> +	const struct bf_source *s = &bf_sources[8 + pb];
> +	u16 l = split ? 0x2000 : chip->xpoint[0][8 + pb][0];
> +	u16 r = split ? 0x0000 : chip->xpoint[0][8 + pb][1];
> +	int ret;
> +
> +	/* The AN1/2 block.  The same values also went to a "standard map"
> +	 * half at 0x0068, which is AS1/2's submix, not part of AN1/2: it
> +	 * overwrote AS1/2's own crosspoint for this playback pair.
> +	 */
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l,
> +			      BF_REG_XPOINT_BASE_L + s->idx_l);
> +	if (ret < 0)
> +		return ret;
> +	return bf_vendor_write(chip, BF_REQ_CROSSPOINT, r,
> +			       BF_REG_XPOINT_BASE_R + s->idx_r);
> +}

[ ... ]

> +int bf_trim_apply(struct snd_usb_babyface *chip, int mic, int trim_db2)
> +{
> +	int base = mic & ~1;
> +	int sib = base + 1;
> +	const struct bf_source *sb = &bf_sources[base];
> +	const struct bf_source *ss = &bf_sources[sib];
> +	u16 trim_raw = bf_master_16bit(trim_db2);
> +	int ret;
> +
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, trim_raw,
> +			      BF_REG_LOWMAP_BASE_L + sb->idx_l);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, trim_raw,
> +			      BF_REG_LOWMAP_BASE_R + sb->idx_r);
> +	if (ret < 0)
> +		return ret;
> +	ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, trim_raw,
> +			      BF_REG_LOWMAP_BASE_L + ss->idx_l);
> +	if (ret < 0)
> +		return ret;
> +	return bf_vendor_write(chip, BF_REQ_CROSSPOINT, trim_raw,
> +			       BF_REG_LOWMAP_BASE_R + ss->idx_r);
> +}

[ ... ]

> +static int bf_trim_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl);
> +	int mic = kctl->private_value;
> +	int sib = mic ^ 1;
> +	int db = ucontrol->value.integer.value[0];
> +	int ret = 0;
> +
> +	if (db < -65 || db > 6)
> +		return -EINVAL;
> +
> +	scoped_guard(mutex, &chip->mutex) {
> +		if (db == chip->trim[mic])
> +			return 0;
> +		ret = bf_trim_apply(chip, mic, db * 2);
> +		if (ret < 0)
> +			return ret;

[Severity: High]
As reported by Sashiko on v7 [1], does bf_trim_apply() clobber the AN1/2
crosspoint state and unmute live analog inputs into the main outputs?

In sound/usb/babyfacepro/babyfacepro.h, BF_REG_LOWMAP_BASE_L (0x0000) and
BF_REG_LOWMAP_BASE_R (0x001a) are the exact same register addresses as
BF_REG_XPOINT_BASE_L and BF_REG_XPOINT_BASE_R for output pair 0 (the AN1/2
main output). In babyface_write_default_mixer(), analog inputs AN1-4 have
chip->xpoint[0][0..3][0/1] initialized to 0 so live microphones are not
routed directly into the speakers.

When bf_trim_put() calls bf_trim_apply(), it converts trim_db2 via
bf_master_16bit() (clamped to [1, 0x4000] where 0 dB is 0x2000, compared to
BF_FADER_0DB = 0x16a0 on the crosspoint curve) and writes trim_raw directly
to all four L and R crosspoint registers of the input pair (base and sib) on
AN1/2, ignoring chip->xpoint[0][base/sib][0/1]. Touching Trim Volume at all
(even at -65 dB, or returning it to 0 dB which writes 0x2000 instead of
restoring chip->xpoint[0][base/sib][0/1]) unconditionally routes both inputs
of the pair to both L and R of AN1/2, overriding any muted state, fader
level, or panning.

Because chip->xpoint[0][base/sib][0/1] remains 0 in the cache while the
hardware holds 0x2000, a subsequent write of 0 to the crosspoint control hits
the early return in bf_xpoint_put():

sound/usb/babyfacepro/babyfacepro-ctl.c:bf_xpoint_put() {
	guard(mutex)(&chip->mutex);
	if (l == chip->xpoint[out][src][0] && r == chip->xpoint[out][src][1])
		return 0;

	ret = bf_xpoint_write(chip, out, src, l, r);
}

and cannot re-mute the hardware.

Additionally, because bf_phase_apply(), bf_split_apply(), bf_trim_apply(),
and bf_xpoint_put() write to the shared crosspoint registers without
combining their state:
- bf_trim_apply() (both in bf_trim_put() and in bf_state_apply_flags() where
  the trim loop runs after the phase loop) overwrites the negated out = 0 L
  register written by bf_phase_apply().
- bf_phase_apply() only negates BF_REG_XPOINT_BASE_L (leaving R un-inverted,
  and writing ~0 = 0xffff when plain == 0).
- Any subsequent bf_xpoint_put() writes plain l/r values, silently wiping out
  active phase, stereo split, or trim settings in hardware while their cached
  control states remain active.

[ ... ]

> +static int bf_bool_info(struct snd_kcontrol *kctl,
> +			struct snd_ctl_elem_info *uinfo)
> +{
> +	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
> +	uinfo->count = 1;
> +	uinfo->value.integer.min = 0;
> +	uinfo->value.integer.max = 1;
> +	return 0;
> +}

[Severity: Low]
This isn't a bug, but bf_bool_info() is identical to bf_phase_info() added
earlier in sound/usb/babyfacepro/babyfacepro-ctl.c (and to the existing
bf_mute_info()). Could these controls use the standard ALSA helper
snd_ctl_boolean_mono_info() from include/sound/control.h instead of
duplicating the callback?

[ ... ]

> +int babyface_create_preamp(struct snd_usb_babyface *chip)
> +{
> +	struct snd_kcontrol *kctl;
> +	int i, err;
> +
> +	/* The control name is the type and .index is the channel (0 =
> +	 * Mic 1/AN1, 1 = Mic 2/AN2), the convention the companion mixer
> +	 * and existing asound.state files use.
> +	 */
> +	for (i = 0; i < 2; i++) {
> +		u16 bit = i == 0 ? BF_PREAMP_48V_MIC1 : BF_PREAMP_48V_MIC2;
> +
> +		kctl = snd_ctl_new1(&(struct snd_kcontrol_new){
> +			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +			.name = "Phantom Power Mic 1",
> +			.index = i,
> +			.info = bf_bool_info,
> +			.get = bf_phantom_get,
> +			.put = bf_phantom_put,
> +			.private_value = bit,
> +		}, chip);
> +		if (!kctl)
> +			return -ENOMEM;
> +		err = snd_ctl_add(chip->card, kctl);
> +		if (err < 0)
> +			return err;
> +	}
> +
> +	for (i = 0; i < 2; i++) {
> +		u16 bit = i == 0 ? BF_PREAMP_PAD_MIC1 : BF_PREAMP_PAD_MIC2;
> +
> +		kctl = snd_ctl_new1(&(struct snd_kcontrol_new){
> +			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +			.name = "Pad Mic 1",
> +			.index = i,
> +			.info = bf_bool_info,
> +			.get = bf_phantom_get,
> +			.put = bf_phantom_put,
> +			.private_value = bit,
> +		}, chip);
> +		if (!kctl)
> +			return -ENOMEM;
> +		err = snd_ctl_add(chip->card, kctl);
> +		if (err < 0)
> +			return err;
> +	}
> +
> +	kctl = snd_ctl_new1(&(struct snd_kcontrol_new){
> +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +		.name = "Instrument Ref Level",
> +		.info = bf_reflevel_info,
> +		.get = bf_reflevel_get,
> +		.put = bf_reflevel_put,
> +	}, chip);
> +	err = snd_ctl_add(chip->card, kctl);
> +	if (err < 0)
> +		return err;

[Severity: Low]
If snd_ctl_new1() returns NULL for the "Instrument Ref Level" control in
babyface_create_preamp(), passing NULL to snd_ctl_add() will return -EINVAL
instead of -ENOMEM. Should this check if (!kctl) return -ENOMEM; before
calling snd_ctl_add() like the other control creation calls in this function?

> +
> +	for (i = 0; i < 4; i++) {
> +		kctl = snd_ctl_new1(&(struct snd_kcontrol_new){
> +			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +			.name = "Mic 1 Capture Volume",
> +			.index = i,
> +			.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
> +				  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
> +			.info = bf_gain_info,
> +			.get = bf_gain_get,
> +			.put = bf_gain_put,
> +			.tlv.p = bf_gain_tlv,
> +			.private_value = i,
> +		}, chip);

[Severity: Medium]
As reported by Sashiko on v7 [1], why do all three loops in
babyface_create_preamp() hardcode "Mic 1" in .name ("Phantom Power Mic 1",
"Pad Mic 1", and "Mic 1 Capture Volume") across all loop iterations?

When i > 0, this registers Mic 2 phantom power and pad as "Phantom Power
Mic 1",1 and "Pad Mic 1",1, and registers Mic 2, Instrument 3, and Instrument
4 capture gains as "Mic 1 Capture Volume" with indices 1, 2, and 3. Should
these either use a channel-neutral name when indexing by i, or format the
per-channel name as babyface_create_trim() does?

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

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

  reply	other threads:[~2026-11-05 15:32 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
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 [this message]
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-199312@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