NetBSD Problem Report #59712

From www@netbsd.org  Sun Oct 19 13:13:06 2025
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits)
	 client-signature RSA-PSS (2048 bits))
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 67CDD1A9239
	for <gnats-bugs@gnats.NetBSD.org>; Sun, 19 Oct 2025 13:13:06 +0000 (UTC)
Message-Id: <20251019131305.358511A923A@mollari.NetBSD.org>
Date: Sun, 19 Oct 2025 13:13:05 +0000 (UTC)
From: nia@pkgsrc.org
Reply-To: nia@pkgsrc.org
To: gnats-bugs@NetBSD.org
Subject: hdaudio driver exposes nonexistent volume controls
X-Send-Pr-Version: www-1.0

>Number:         59712
>Category:       kern
>Synopsis:       hdaudio driver exposes nonexistent volume controls
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          analyzed
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Oct 19 13:15:00 +0000 2025
>Closed-Date:    
>Last-Modified:  Tue Oct 28 13:35:01 +0000 2025
>Originator:     nia
>Release:        any with hdaudio
>Organization:
The SliderBSD Foundation
>Environment:
>Description:
My audio chipset (and indeed any realtek chipset that i've seen)
has mute-only output amplifier controls:

[     1.044129] hdaudio1 at pci7 dev 0 function 4: HD Audio Controller
[     1.044129] hdaudio1: interrupting at msi6 vec 0
[     1.044129] hdaudio1: HDA ver. 1.0, OSS 4, ISS 4, BSS 0, SDO 1, 64-bit
[     1.044129] hdafg1 at hdaudio1: Realtek ALC897
[     1.044129] add ctrl outamp mute=0 step=87 size=2 offset=87
[     1.044129] add ctrl outamp mute=0 step=87 size=2 offset=87
[     1.044129] add ctrl outamp mute=0 step=87 size=2 offset=87
[     1.044129] add ctrl outamp mute=0 step=87 size=2 offset=87
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=1 step=0 size=0 offset=0
[     1.044129] add ctrl outamp mute=0 step=87 size=2 offset=87

These are the ones with step=0 and size=0.

Currently the way the driver tries to handle these is quite odd.
It creates a normal mute control, but also creates a volume control
with delta=256 (i.e. not possible to modify). This clutters up
mixer interfaces and i've had to put a workaround for this in aiomixer.

The code contains some hacky workarounds for these fake controls,
e.g.:

if (ctl->ctl_step == 0)
        divisor = 128; /* ??? - just avoid div by 0 */
else
        divisor = 255 / ctl->ctl_step;

hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE,
  mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] / divisor,
  mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] / divisor);
>How-To-Repeat:
run mixerctl -la on a machine with realtek audio hardware
>Fix:
The way that `hdafg_build_mixers` is written means that volume-only
controls (with no mute) are possible, but mute-only controls with
no volume are not possible. This restriction does not come from any
particular API or specification, it's just a quirk of the driver.

The driver should be modified to build only mute enums for those
mute-only controls.

I should get around to this.

>Release-Note:

>Audit-Trail:
From: nia <nia@NetBSD.org>
To: gnats-bugs@netbsd.org
Cc: 
Subject: Re: kern/59712: hdaudio driver exposes nonexistent volume controls
Date: Sun, 19 Oct 2025 16:23:03 +0000

 --gTKzjOIvDZugVxqG
 Content-Type: text/plain; charset=us-ascii
 Content-Disposition: inline

 And here is a patch.

 --gTKzjOIvDZugVxqG
 Content-Type: text/x-diff; charset=us-ascii
 Content-Disposition: attachment; filename=hdafg.patch

 hdaudio(4): Do not expose non-functional volume controls.

 The old code supported "volume without mute", but not "mute
 without volume", which are widely exposed by hdaudio devices.
 This resulted in redundant volume controls with delta=256
 (not allowing them to be set).

 While here, classify PC speaker and phone out controls as
 output controls.

 Index: sys/dev/hdaudio/hdafg.c
 ===================================================================
 RCS file: /cvsroot/src/sys/dev/hdaudio/hdafg.c,v
 retrieving revision 1.32
 diff -u -p -r1.32 hdafg.c
 --- sys/dev/hdaudio/hdafg.c	29 Jan 2024 18:58:54 -0000	1.32
 +++ sys/dev/hdaudio/hdafg.c	19 Oct 2025 16:19:36 -0000
 @@ -2820,7 +2820,8 @@ hdafg_build_mixers(struct hdafg_softc *s
  		    ctl->ctl_audiomask == 0)
  			continue;
  		audiomask |= ctl->ctl_audiomask;
 -		++nmixers;
 +		if (ctl->ctl_step > 0)
 +			++nmixers;
  		if (ctl->ctl_mute)
  			++nmixers;
  	}
 @@ -2831,7 +2832,8 @@ hdafg_build_mixers(struct hdafg_softc *s
  	    HDAUDIO_MASK(PCM)) {
  		audiomask |= HDAUDIO_MASK(VOLUME);
  		for (i = 0; i < sc->sc_nctls; i++) {
 -			if (sc->sc_ctls[i].ctl_audiomask == HDAUDIO_MASK(PCM)) {
 +			if (sc->sc_ctls[i].ctl_audiomask == HDAUDIO_MASK(PCM) &&
 +			    sc->sc_ctls[i].ctl_step > 0) {
  				masterctl = &sc->sc_ctls[i];
  				++nmixers;
  				if (masterctl->ctl_mute)
 @@ -2874,7 +2876,8 @@ hdafg_build_mixers(struct hdafg_softc *s
  		mx[index].mx_di.index = index;
  		mx[index].mx_di.type = AUDIO_MIXER_CLASS;
  		mx[index].mx_di.mixer_class = i;
 -		mx[index].mx_di.next = mx[index].mx_di.prev = AUDIO_MIXER_LAST;
 +		mx[index].mx_di.prev = index > 0 ? index - 1 : 0;
 +		mx[index].mx_di.next = index + 1;
  		switch (i) {
  		case HDAUDIO_MIXER_CLASS_OUTPUTS:
  			strcpy(mx[index].mx_di.label.name, AudioCoutputs);
 @@ -2891,24 +2894,32 @@ hdafg_build_mixers(struct hdafg_softc *s

  	/* Shadow master control */
  	if (masterctl != NULL) {
 -		mx[index].mx_ctl = masterctl;
 -		mx[index].mx_di.index = index;
 -		mx[index].mx_di.type = AUDIO_MIXER_VALUE;
 -		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
 -		mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
 -		mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
 -		mx[index].mx_di.un.v.delta = 256 / 
 -		    (masterctl->ctl_step ? masterctl->ctl_step : 1);
 -		strcpy(mx[index].mx_di.label.name, AudioNmaster);
 -		strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
 -		hda_trace(sc, "  adding outputs.%s\n",
 -		    mx[index].mx_di.label.name);
 -		++index;
 +		if (masterctl->ctl_step > 0) {
 +			mx[index].mx_ctl = masterctl;
 +			mx[index].mx_di.index = index;
 +			mx[index].mx_di.type = AUDIO_MIXER_VALUE;
 +			mx[index].mx_di.prev = index > 0 ? index - 1 : 0;
 +			mx[index].mx_di.next = index + 1;
 +			mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
 +			mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
 +			mx[index].mx_di.un.v.delta = 256 / masterctl->ctl_step;
 +			strcpy(mx[index].mx_di.label.name, AudioNmaster);
 +			strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
 +			hda_trace(sc, "  adding outputs.%s\n",
 +			    mx[index].mx_di.label.name);
 +			++index;
 +		}
  		if (masterctl->ctl_mute) {
 -			mx[index] = mx[index - 1];
 +			if (masterctl->ctl_step > 0) {
 +				mx[index - 1].mx_di.next = index;
 +				mx[index] = mx[index - 1];
 +				mx[index].mx_di.prev = index;
 +			}
 +			mx[index].mx_ctl = masterctl;
  			mx[index].mx_di.index = index;
  			mx[index].mx_di.type = AUDIO_MIXER_ENUM;
 -			mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
 +			mx[index].mx_di.prev = index > 0 ? index - 1 : 0;
 +			mx[index].mx_di.next = index + 1;
  			strcpy(mx[index].mx_di.label.name, AudioNmaster "." AudioNmute);
  			mx[index].mx_di.un.e.num_mem = 2;
  			strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
 @@ -2928,66 +2939,84 @@ hdafg_build_mixers(struct hdafg_softc *s
  		    ctl->ctl_audiomask == 0)
  			continue;
  		audiodev = ffs(ctl->ctl_audiomask) - 1;
 -		mx[index].mx_ctl = ctl;
 -		mx[index].mx_di.index = index;
 -		mx[index].mx_di.type = AUDIO_MIXER_VALUE;
 -		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
 -		mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
 -		mx[index].mx_di.un.v.delta = 256 /
 -		    (ctl->ctl_step ? ctl->ctl_step : 1);
 -		if (ctrlcnt[audiodev] > 0)
 -			snprintf(mx[index].mx_di.label.name,
 -			    sizeof(mx[index].mx_di.label.name),
 -			    "%s%d",
 -			    hdafg_mixer_names[audiodev],
 -			    ctrlcnt[audiodev] + 1);
 -		else
 -			strcpy(mx[index].mx_di.label.name,
 -			    hdafg_mixer_names[audiodev]);
 -		ctrlcnt[audiodev]++;

  		switch (audiodev) {
  		case HDAUDIO_MIXER_VOLUME:
  		case HDAUDIO_MIXER_BASS:
  		case HDAUDIO_MIXER_TREBLE:
  		case HDAUDIO_MIXER_OGAIN:
 +		case HDAUDIO_MIXER_SPEAKER:
 +		case HDAUDIO_MIXER_PHONEOUT:
  			mx[index].mx_di.mixer_class =
  			    HDAUDIO_MIXER_CLASS_OUTPUTS;
 -			hda_trace(sc, "  adding outputs.%s\n",
 -			    mx[index].mx_di.label.name);
  			break;
  		case HDAUDIO_MIXER_MIC:
  		case HDAUDIO_MIXER_MONITOR:
  			mx[index].mx_di.mixer_class =
  			    HDAUDIO_MIXER_CLASS_RECORD;
 -			hda_trace(sc, "  adding record.%s\n",
 -			    mx[index].mx_di.label.name);
  			break;
  		default:
  			mx[index].mx_di.mixer_class =
  			    HDAUDIO_MIXER_CLASS_INPUTS;
 -			hda_trace(sc, "  adding inputs.%s\n",
 -			    mx[index].mx_di.label.name);
  			break;
  		}
 -		strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);

 -		++index;
 +		if (ctl->ctl_step > 0) {
 +			if (ctrlcnt[audiodev] > 0)
 +				snprintf(mx[index].mx_di.label.name,
 +				    sizeof(mx[index].mx_di.label.name),
 +				    "%s%d",
 +				    hdafg_mixer_names[audiodev],
 +				    ctrlcnt[audiodev] + 1);
 +			else
 +				strcpy(mx[index].mx_di.label.name,
 +				    hdafg_mixer_names[audiodev]);
 +			ctrlcnt[audiodev]++;
 +			mx[index].mx_ctl = ctl;
 +			mx[index].mx_di.index = index;
 +			mx[index].mx_di.type = AUDIO_MIXER_VALUE;
 +			mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
 +			mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
 +			mx[index].mx_di.un.v.delta = 256 / ctl->ctl_step;
 +			strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
 +			hda_trace(sc, "  adding vol control %s idx %d\n",
 +			    mx[index].mx_di.label.name, index);
 +			++index;
 +		}

  		if (ctl->ctl_mute) {
 -			mx[index] = mx[index - 1];
 +			if (ctl->ctl_step > 0) {
 +				/* there is a parent vol control */
 +				mx[index] = mx[index - 1];
 +				snprintf(mx[index].mx_di.label.name,
 +				    sizeof(mx[index].mx_di.label.name),
 +				    "%s." AudioNmute,
 +				    mx[index - 1].mx_di.label.name);
 +			} else {
 +				if (ctrlcnt[audiodev] > 0)
 +					snprintf(mx[index].mx_di.label.name,
 +					    sizeof(mx[index].mx_di.label.name),
 +					    "%s%d." AudioNmute,
 +					    hdafg_mixer_names[audiodev],
 +					    ctrlcnt[audiodev] + 1);
 +				else
 +					snprintf(mx[index].mx_di.label.name,
 +					    sizeof(mx[index].mx_di.label.name),
 +					    "%s." AudioNmute,
 +					    hdafg_mixer_names[audiodev]);
 +				ctrlcnt[audiodev]++;
 +			}
 +			mx[index].mx_ctl = ctl;
  			mx[index].mx_di.index = index;
  			mx[index].mx_di.type = AUDIO_MIXER_ENUM;
  			mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
 -			snprintf(mx[index].mx_di.label.name,
 -			    sizeof(mx[index].mx_di.label.name),
 -			    "%s." AudioNmute,
 -			    mx[index - 1].mx_di.label.name);
  			mx[index].mx_di.un.e.num_mem = 2;
  			strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
  			mx[index].mx_di.un.e.member[0].ord = 0;
  			strcpy(mx[index].mx_di.un.e.member[1].label.name, AudioNon);
  			mx[index].mx_di.un.e.member[1].ord = 1;
 +			hda_trace(sc, "  adding mute control %s idx %d\n",
 +			    mx[index].mx_di.label.name, index);
  			++index;
  		}
  	}
 @@ -4161,11 +4190,7 @@ hdafg_set_port(void *opaque, mixer_ctrl_

  	switch (mx->mx_di.type) {
  	case AUDIO_MIXER_VALUE:
 -		if (ctl->ctl_step == 0)
 -			divisor = 128; /* ??? - just avoid div by 0 */
 -		else
 -			divisor = 255 / ctl->ctl_step;
 -
 +		divisor = 255 / ctl->ctl_step;
  		hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE,
  		  mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] / divisor,
  		  mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] / divisor);
 @@ -4222,11 +4247,7 @@ hdafg_get_port(void *opaque, mixer_ctrl_

  	switch (mx->mx_di.type) {
  	case AUDIO_MIXER_VALUE:
 -		if (ctl->ctl_step == 0)
 -			factor = 128; /* ??? - just avoid div by 0 */
 -		else
 -			factor = 255 / ctl->ctl_step;
 -
 +		factor = 255 / ctl->ctl_step;
  		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = ctl->ctl_left * factor;
  		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = ctl->ctl_right * factor;
  		break;

 --gTKzjOIvDZugVxqG--

State-Changed-From-To: open->analyzed
State-Changed-By: nia@NetBSD.org
State-Changed-When: Sun, 19 Oct 2025 16:42:48 +0000
State-Changed-Why:


From: "Nia Alarie" <nia@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59712 CVS commit: src/sys/dev/hdaudio
Date: Sun, 19 Oct 2025 17:08:55 +0000

 Module Name:	src
 Committed By:	nia
 Date:		Sun Oct 19 17:08:55 UTC 2025

 Modified Files:
 	src/sys/dev/hdaudio: hdafg.c

 Log Message:
 hdaudio(4): Do not expose non-functional volume controls.

 The old code supported "volume without mute", but not "mute
 without volume", which are widely exposed by hdaudio devices.
 This resulted in redundant volume controls with delta=256
 (not allowing them to be set).

 While here, classify PC speaker and phone out controls as
 output controls.

 "ship it" jmcneill@

 PR kern/59712 hdaudio driver exposes nonexistent volume controls


 To generate a diff of this commit:
 cvs rdiff -u -r1.32 -r1.33 src/sys/dev/hdaudio/hdafg.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc: 
Subject: PR/59712 CVS commit: [netbsd-11] src/sys/dev/hdaudio
Date: Tue, 28 Oct 2025 13:33:07 +0000

 Module Name:	src
 Committed By:	martin
 Date:		Tue Oct 28 13:33:07 UTC 2025

 Modified Files:
 	src/sys/dev/hdaudio [netbsd-11]: hdafg.c

 Log Message:
 Pull up following revision(s) (requested by nia in ticket #76):

 	sys/dev/hdaudio/hdafg.c: revision 1.33

 hdaudio(4): Do not expose non-functional volume controls.

 The old code supported "volume without mute", but not "mute
 without volume", which are widely exposed by hdaudio devices.

 This resulted in redundant volume controls with delta=256
 (not allowing them to be set).

 While here, classify PC speaker and phone out controls as
 output controls.
 "ship it" jmcneill@

 PR kern/59712 hdaudio driver exposes nonexistent volume controls


 To generate a diff of this commit:
 cvs rdiff -u -r1.32 -r1.32.4.1 src/sys/dev/hdaudio/hdafg.c

 Please note that diffs are not public domain; they are subject to the
 copyright notices on the relevant files.

>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.