Re: [PATCH v7 2/2] ASoC: codecs: nau8360: Add support for NAU83G60 amplifier

[email protected] Tue, 04 Aug 2026 03:44:22 +0000
Newsgroups org.kernel.vger.linux-devicetree
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 11 potential issue=
(s) to consider:
- [High] DSP initialization fails unconditionally during resume due to regm=
ap `cache_only` mode.
- [High] Concurrent asynchronous firmware loading requests cause a Use-Afte=
r-Free during device unbind.
- [High] Unkillable task deadlock on module unload (remove) if the device i=
s removed after a suspend/resume cycle.
- [High] Ignored `regmap_read()` return values result in uninitialized stac=
k variables driving hardware logic.
- [Medium] Silent failure when rejecting DAC mux path changes during playba=
ck.
- [Medium] Unprotected access to `nau8360->anc_enable` and hardware registe=
rs creates a data race in `nau8360_dsp_switch`.
- [Medium] Short fragments from the DSP can cause uninitialized stack memor=
y to be used in `nau8360_dsp_kcs_setup`.
- [Medium] Out-of-bounds `tdm_tx_func_slot` configurations cause undefined =
behavior via bit shifts.
- [Medium] Data race on the `dsp_created` flag between firmware callback an=
d ALSA startup.
- [Medium] Manual DAPM pin toggling of the 'Sense' widget breaks I/V captur=
e streams when the DSP is bypassed.
- [Low] Incompatible pointer type warning in `nau8360_validate_tdm_slots`.
--

--- Patch [2]: [PATCH v7 2/2] ASoC: codecs: nau8360: Add support for NAU83G=
60 amplifier ---
commit 498f738336b985a294d2d2e4f2b2064ec352e710
Author: Neo Chang <[email protected]>

ASoC: codecs: nau8360: Add support for NAU83G60 amplifier

Add support for the Nuvoton NAU83G60 audio codec. The NAU83G60 is a
stereo 30W+30W smart amplifier with an integrated low-latency
Advanced Audio DSP.

> diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-ds=
p.c
> index 14ff6c5705d14.....
> --- /dev/null
> +++ b/sound/soc/codecs/nau8360-dsp.c
[ ... ]
> +static int nau8360_read_data_payload(struct snd_soc_component *cp,
> +	unsigned short dsp_addr, int frag_len, bool msg_param,
> +	void *data, int data_size, int *data_count)
> +{
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(cp);
> +	struct device *dev =3D cp->dev;
> +	unsigned int payload;
> +	u32 *data_buf =3D data;
> +	int i, j, ret;
> +
> +	*data_count =3D (msg_param) ? data_size : 0;
> +	for (i =3D 0; i < frag_len - 1; i++) {

[Severity: Medium]
What happens if the DSP hardware returns a trailing fragment with a frag_len
of 1?=20

The loop extracting payload bytes wouldn't execute, leaving the data buffer=
=20
untouched without returning an error.

[ ... ]
> +static int nau8360_dsp_kcs_setup(struct snd_soc_component *cp, int offse=
t, int size,
> +	const void *data, unsigned short dsp_addr)
> +{
[ ... ]
> +		/* checking KCS result */
> +		ret =3D nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_GET_KCS_RSLTS,
> +			0, NAU8360_DSP_DATA_BYTE, NULL,
> +			NAU8360_DSP_DATA_BYTE, &kcs_rst, dsp_addr);
> +		if (ret)
> +			return ret;
> +		if (kcs_rst !=3D NAU8360_DSP_KCS_RSLTS_SUCCESS)
> +			return -EINVAL;

[Severity: Medium]
Could this evaluate uninitialized stack memory?

If nau8360_read_data_payload() encounters a frag_len of 1, kcs_rst is left=
=20
uninitialized but the reply validation still returns success. Evaluating=20
kcs_rst here could then randomly fail or pass based on whatever was on the=
=20
stack.

[ ... ]
> +static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp,
> +	const char *fw_name, int dsp_addr)
> +{
[ ... ]
> +	ctx =3D kmalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx)
> +		return -ENOMEM;
> +
> +	ctx->cp =3D cp;
> +	ctx->dsp_addr =3D dsp_addr;
> +
> +	reinit_completion(&nau8360->fw_done[core_id]);
> +
> +	ret =3D request_firmware_nowait(THIS_MODULE, true,
> +		fw_name, dev, GFP_KERNEL, ctx, nau8360_dsp_fw_cb);

[Severity: High]
Does this create a race condition if called multiple times before the previ=
ous=20
firmware request finishes?

Reinitializing the completion and launching a duplicate request could leave=
=20
multiple asynchronous callbacks pending, which might bypass later unbind
synchronizations.

> diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c
> index 4a5fe315850fd.....
> --- /dev/null
> +++ b/sound/soc/codecs/nau8360.c
[ ... ]
> +static int nau8360_get_tdm_chan_len(struct nau8360 *nau8360)
> +{
> +	int val;
> +
> +	regmap_read(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, &val);
> +	val =3D (val & NAU8360_TDM_CLEN_MASK) >> NAU8360_TDM_CLEN_SFT;
> +
> +	return (val << 3) + 16;
> +}

[Severity: High]
Can regmap_read() fail here?

If an I2C error occurs, the return value is ignored and the uninitialized=20
stack variable val is used to compute the channel length. This garbage=20
value then propagates into the mathematical bounds for TDM slots.

[ ... ]
> +static void nau8360_dsp_switch(struct snd_soc_component *component, bool=
 enable)
> +{
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(component);
> +	struct regmap *regmap =3D nau8360->regmap;
> +	int value =3D NAU8360_PEQ_BAND_8;
> +
> +	/* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware,
> +	 * and configure PEQ after dsp reset.
> +	 */
> +	if (enable) {
> +		value =3D nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND=
_12;
> +		nau8360_dsp_enable(regmap, true);
> +	} else {
> +		dev_dbg(nau8360->dev, "Bypass DSP path");
> +		nau8360_dsp_enable(regmap, false);
> +	}
> +	regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK,
> +		value << NAU8360_PEQ_BAND_SFT);
> +}

[Severity: Medium]
Is it safe to read nau8360->anc_enable and update the PEQ register without
holding nau8360->lock?=20

Looking at nau8360_anc_put(), it protects these variables with a mutex.
Concurrent execution from ALSA control put handlers might result in a stale
hardware state here.

[ ... ]
> +static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_dapm_context *dapm =3D snd_soc_dapm_kcontrol_to_dapm(kco=
ntrol);
> +	struct snd_soc_component *component =3D snd_soc_dapm_to_component(dapm);
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(component);
> +	struct soc_enum *e =3D (struct soc_enum *)kcontrol->private_value;
> +	unsigned int *item =3D ucontrol->value.enumerated.item;
> +	int ret =3D 0;
> +
> +	if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) {
> +		dev_warn(nau8360->dev, "changing path is not allowed during playback");
> +		return ret;
> +	}

[Severity: Medium]
Should this return an error code like -EBUSY?=20

Returning 0 when rejecting the change silently fails and tricks userspace=20
into thinking the hardware path was updated.

[ ... ]
> +static int nau8360_startup(struct snd_pcm_substream *substream, struct s=
nd_soc_dai *dai)
> +{
> +	struct snd_soc_component *component =3D dai->component;
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(component);
> +	unsigned int i2s_mask =3D NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_=
MASK;
> +	unsigned int i2s_fmt =3D NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2=
S;
> +	int val;
> +
> +	if (nau8360->dsp_created !=3D NAU8360_DSP_FW_ALL_DONE) {
> +		dev_warn(nau8360->dev, "DSP firmware is not ready yet!");
> +		return -EBUSY;
> +	}

[Severity: Medium]
Is a lock needed here when checking the dsp_created flag?=20

The firmware callback nau8360_dsp_fw_cb() modifies this flag under=20
nau8360->lock. Reading it unlocked might cause a torn read and spuriously=20
reject playback streams.

> +
> +	if (substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK) {
> +		regmap_read(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, &val);
> +		if ((val & i2s_mask) =3D=3D i2s_fmt)
> +			regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1,
> +				NAU8360_EN_TDM_RX, NAU8360_EN_TDM_RX);

[Severity: High]
Does regmap_read() need its return value checked here?=20

Similar to nau8360_get_tdm_chan_len(), if this read fails due to bus issues=
,=20
an uninitialized stack variable will be evaluated to determine if the recei=
ver=20
should be enabled.

> +
> +		if (nau8360_dsp_active(component))
> +			snd_soc_dapm_enable_pin(nau8360->dapm, "Sense");
> +	}

[Severity: Medium]
Will this break I/V capture streams when the DSP is bypassed?=20

The Sense widget is the DAPM source for ADC_I and ADC_V. If it's never enab=
led=20
when nau8360_dsp_active() evaluates to false, DAPM won't power up the ADCs,=
=20
leaving the host recording silence over TDM.

[ ... ]
> +static int nau8360_validate_tdm_slots(struct device *dev, unsigned int m=
ask,
> +	const int *func_slots, const char * const *func_names,
> +	int num_funcs, const char *dir,
> +	unsigned int *slot_used)

[Severity: Low]
Does the func_slots parameter need its type updated?=20

The struct fields tdm_tx_func_slot and tdm_rx_func_slot were updated to u32=
,=20
which results in passing an unsigned int array to a const int pointer here.

> +{
> +	int i;
> +	unsigned int func_slot;
> +	*slot_used =3D 0;
> +
> +	if (!mask)
> +		return 0;
> +
> +	for (i =3D 0; i < num_funcs; i++) {
> +		func_slot =3D func_slots[i];
[ ... ]
> +		if (func_slot >=3D NAU8360_TDM_MAX_CHAN) {
> +			dev_warn(dev, "Invalid %s %s slot index: %d",
> +				dir, func_names[i], func_slot);
> +			continue;
> +		}

[Severity: Medium]
Should this clear the invalid slot from the array?=20

It logs a warning and continues, but leaves the out-of-bounds value intact =
in
the original array for later consumption.

[ ... ]
> +static void nau8360_enable_tdm_channels(struct snd_soc_component *cp,
> +	int rx_slot_used, int tx_slot_used)
> +{
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(cp);
> +	int i, slot;
> +	unsigned int val =3D 0;
> +	bool enable;
> +
> +	for (i =3D 0; i < NAU8360_TDM_TXN; i++) {
> +		slot =3D nau8360->tdm_tx_func_slot[i];
> +		enable =3D (slot !=3D TDM_SLOT_NONE) && (tx_slot_used & BIT(slot));

[Severity: Medium]
Can this trigger undefined behavior?=20

If the invalid slot value from earlier (e.g. >=3D 32) remains in the array,=
=20
evaluating BIT(slot) will bit-shift out of bounds.

[ ... ]
> +static int __maybe_unused nau8360_suspend(struct snd_soc_component *comp=
onent)
> +{
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(component);
> +	int i;
> +
> +	for (i =3D 0; i < NAU8360_DSP_CORE_NUM; i++)
> +		wait_for_completion(&nau8360->fw_done[i]);

[Severity: High]
Does this consume the completion token without replacing it if the resume
fails?=20

If nau8360_resume() doesn't properly replenish the token, a subsequent modu=
le=20
unload will deadlock forever when it waits on this completion.

[ ... ]
> +static int __maybe_unused nau8360_resume(struct snd_soc_component *compo=
nent)
> +{
> +	struct nau8360 *nau8360 =3D snd_soc_component_get_drvdata(component);
> +	struct regmap *regmap =3D nau8360->regmap;
> +	int ret;
> +
> +	/* disable Sense at standby */
> +	snd_soc_dapm_disable_pin(nau8360->dapm, "Sense");
> +	snd_soc_dapm_sync(nau8360->dapm);
> +
> +	ret =3D nau8360_dsp_setup(component);
> +
> +	regcache_cache_only(regmap, false);

[Severity: High]
Can the DSP initialization succeed here?=20

nau8360_dsp_setup() is called before clearing the cache_only flag. Since=20
nau8360_dsp_setup() relies on polling hardware registers over the bus, it=20
will silently fail every time during resume, skipping firmware loads and=20
leaving the hardware unprotected.

[ ... ]
> +static void nau8360_i2c_remove(struct i2c_client *client)
> +{
> +	struct nau8360 *nau8360 =3D i2c_get_clientdata(client);
> +	int i;
> +
> +	for (i =3D 0; i < NAU8360_DSP_CORE_NUM; i++)
> +		wait_for_completion(&nau8360->fw_done[i]);
> +}

[Severity: High]
If nau8360_suspend() consumed the token and nau8360_resume() failed to repl=
ace
it, won't this cause an unkillable deadlock?

Additionally, if duplicate firmware requests were launched by=20
nau8360_dsp_chan_kcs_setup(), this wait_for_completion() will only synchron=
ize=20
against the first one. The second pending callback could execute after the=
=20
device unbinds, triggering a use-after-free on the nau8360 struct.

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260804032951.1069=
[email protected]?part=3D2