* [PATCH] ALSA: hda/conexant: Fix swapped speaker/headphone outputs on Huawei MateBook 16s
@ 2026-10-24 18:40 Xie Youtian
2026-10-24 19:06 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Xie Youtian @ 2026-10-24 18:40 UTC (permalink / raw)
To: linux-sound; +Cc: tiwai, perex, linux-kernel, xiedada
On the Huawei MateBook 16s (2023) with a Conexant SN6140 codec (codec
SSID 0x19e5:0x3291), the speaker amplifier and the headphone jack are
both fed from the analog output line of pin 0x16: the jack through a
tap that needs GPIO1 enabled and driven low, and the speaker amp
switched by the EAPD of pin 0x17 - none of which matches the BIOS pin
config. The generic parser therefore routes audio to the wrong
physical output and never powers the jack:
* headphones plugged: the headphone path (DAC 0x10 -> pin 0x16)
drives the speakers while the jack stays silent
* headphones unplugged: the speaker path (DAC 0x11 -> pin 0x17) feeds
no transducer at all and the machine stays silent
Route the shared line to whichever DAC the parser leaves unmuted for
the current jack state, keep the jack tap open, and gate the speaker
amp through EAPD. The verbs are applied from a private jack callback
(the generic unsol->automute path never fires on this machine while a
stream keeps the codec awake) with a debounced presence sample, and a
delayed recheck re-applies everything from a settled sample, since any
reader of the pin sense can see stale data for a short while after a
codec power transition. The 600 ms recheck delay matches the factory
Windows driver's plug-detection restart window.
The factory Windows driver groups codec SSIDs 0x19e5:0x3291 through
0x3294 into a single audio configuration, so the siblings very likely
share this wiring; only 0x3291 could be tested.
Tested on the machine: headphone/speaker switching in both directions,
during playback, over the bare PCM path and through PipeWire, and
across suspend/resume.
Problem detection and the verb-level hardware experiments were
performed with the help of an AI coding assistant, with all audible
verification done by ear on the actual hardware by the submitter; the
fix was drafted by the assistant and then reviewed, understood,
adjusted and tested by the submitter.
Link: https://github.com/geniusinsanity/huawei-matebook16s-speaker-fix
Signed-off-by: Xie Youtian <xiedada@xiedada.net>
---
sound/hda/codecs/conexant.c | 150 ++++++++++++++++++++++++++++++++++++
1 file changed, 150 insertions(+)
diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c
index 9bfdbf5c9032..62a41c764ccb 100644
--- a/sound/hda/codecs/conexant.c
+++ b/sound/hda/codecs/conexant.c
@@ -43,6 +43,10 @@ struct conexant_spec {
unsigned int gpio_mute_led_mask;
unsigned int gpio_mic_led_mask;
bool is_cx11880_sn6140;
+
+ /* quirk for Huawei MateBooks with Raptor Lake */
+ struct hda_codec *rpl_recheck_codec;
+ struct delayed_work rpl_recheck_work;
};
@@ -312,6 +316,7 @@ enum {
CXT_FIXUP_HP_A_U,
CXT_FIXUP_ACER_SWIFT_HP,
CXT_FIXUP_HUAWEI_MATEBOOK_HP,
+ CXT_FIXUP_HUAWEI_MATEBOOK_RPL,
};
/* for hda_fixup_thinkpad_acpi() */
@@ -863,6 +868,143 @@ static const struct hda_pintbl cxt_pincfg_sws_js201d[] = {
{}
};
+/*
+ * Huawei MateBooks with Raptor Lake and SN6140 (e.g. the MateBook 16s
+ * 2023): the speaker amp and the headphone jack are both fed from the
+ * pin 0x16 line. The speaker amp is gated by the EAPD of pin 0x17, the
+ * jack tap by GPIO1 - none of which matches the BIOS pin config. Point
+ * pin 0x16 at the DAC the parser leaves unmuted for the current jack
+ * state, keep the tap open, and gate the speaker amp via EAPD.
+ */
+static void cxt_matebook_raptor_lake_coerce(struct hda_codec *codec, bool hp)
+{
+ snd_hda_codec_write_cache(codec, 0x16, 0,
+ AC_VERB_SET_CONNECT_SEL, hp ? 0x0 : 0x1);
+ snd_hda_codec_write_cache(codec, 0x17, 0,
+ AC_VERB_SET_EAPD_BTLENABLE, hp ? 0x0 : 0x2);
+ /* keep the jack tap on the shared line open; with the speaker amp
+ * gated separately this is correct in both jack states
+ */
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_MASK, 0x02);
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DIRECTION, 0x02);
+ snd_hda_codec_write_cache(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, 0x00);
+}
+
+/* jack presence right at a plug event bounces for a few milliseconds;
+ * sample it once, debounced, and use that one value everywhere below
+ */
+static bool cxt_matebook_raptor_lake_jack_present(struct hda_codec *codec)
+{
+ bool a = snd_hda_jack_detect(codec, 0x16);
+
+ if (snd_hda_jack_detect(codec, 0x16) != a) {
+ msleep(100);
+ return snd_hda_jack_detect(codec, 0x16);
+ }
+ return a;
+}
+
+static void cxt_matebook_raptor_lake_apply(struct hda_codec *codec, bool hp)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ spec->gen.hp_jack_present = hp;
+ snd_hda_gen_update_outputs(codec);
+ cxt_matebook_raptor_lake_coerce(codec, hp);
+}
+
+/*
+ * Right after the codec powers back up every reader of the pin sense -
+ * ours and the parser's alike - can see garbage. Re-apply everything
+ * from a settled read a bit later, which heals any misguess made at
+ * init or during a jack event.
+ */
+static void cxt_matebook_raptor_lake_recheck(struct work_struct *work)
+{
+ struct conexant_spec *spec = container_of(work, struct conexant_spec,
+ rpl_recheck_work.work);
+ struct hda_codec *codec = spec->rpl_recheck_codec;
+
+ if (codec) {
+ bool hp = snd_hda_jack_detect(codec, 0x16);
+
+ codec_dbg(codec, "matebook-rpl: recheck hp=%d\n", hp);
+ cxt_matebook_raptor_lake_apply(codec, hp);
+ }
+}
+
+/* the parser reprograms pin 0x17 and the DAC amps on jack events; the
+ * recheck heals a stale presence sample taken while the contact still
+ * bounces
+ */
+static void cxt_matebook_raptor_lake_update_outputs(struct hda_codec *codec)
+{
+ struct conexant_spec *spec = codec->spec;
+ bool hp = cxt_matebook_raptor_lake_jack_present(codec);
+
+ codec_dbg(codec, "matebook-rpl: hook hp=%d\n", hp);
+ cxt_matebook_raptor_lake_apply(codec, hp);
+ mod_delayed_work(system_wq, &spec->rpl_recheck_work,
+ msecs_to_jiffies(600));
+}
+
+/* our own jack callback: the unsol -> automute_hook chain is
+ * unreliable on this machine (stays silent while a stream keeps the
+ * codec awake); apply immediately and keep the delayed recheck as the
+ * safety net for anything the debounced sample got wrong
+ */
+static void cxt_matebook_raptor_lake_jack_event(struct hda_codec *codec,
+ struct hda_jack_callback *jack)
+{
+ struct conexant_spec *spec = codec->spec;
+ bool hp = cxt_matebook_raptor_lake_jack_present(codec);
+
+ codec_dbg(codec, "matebook-rpl: jack event\n");
+ cxt_matebook_raptor_lake_apply(codec, hp);
+ mod_delayed_work(system_wq, &spec->rpl_recheck_work,
+ msecs_to_jiffies(600));
+}
+
+static void cxt_fixup_matebook_raptor_lake(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ switch (action) {
+ case HDA_FIXUP_ACT_PRE_PROBE:
+ spec->gen.automute_hook = cxt_matebook_raptor_lake_update_outputs;
+ INIT_DELAYED_WORK(&spec->rpl_recheck_work,
+ cxt_matebook_raptor_lake_recheck);
+ spec->rpl_recheck_codec = codec;
+ /* pin 0x17 drives the tap line; enabled once, like the
+ * Windows driver does
+ */
+ snd_hda_codec_write_cache(codec, 0x17, 0,
+ AC_VERB_SET_CONNECT_SEL, 0x0);
+ snd_hda_codec_write_cache(codec, 0x17, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ 0x40);
+ break;
+ case HDA_FIXUP_ACT_INIT:
+ /* the parser's own view is the best guess at init; the
+ * delayed recheck above heals whatever it gets wrong
+ */
+ snd_hda_jack_detect_enable_callback(codec, 0x16,
+ cxt_matebook_raptor_lake_jack_event);
+ cxt_matebook_raptor_lake_apply(codec, spec->gen.hp_jack_present);
+ schedule_delayed_work(&spec->rpl_recheck_work,
+ msecs_to_jiffies(600));
+ break;
+ case HDA_FIXUP_ACT_FREE:
+ cancel_delayed_work_sync(&spec->rpl_recheck_work);
+ spec->rpl_recheck_codec = NULL;
+ break;
+ }
+}
+
static const struct hda_fixup cxt_fixups[] = {
[CXT_PINCFG_LENOVO_X200] = {
.type = HDA_FIXUP_PINS,
@@ -1062,6 +1204,10 @@ static const struct hda_fixup cxt_fixups[] = {
{ }
},
},
+ [CXT_FIXUP_HUAWEI_MATEBOOK_RPL] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_matebook_raptor_lake,
+ },
};
static const struct hda_quirk cxt5045_fixups[] = {
@@ -1164,6 +1310,10 @@ static const struct hda_quirk cxt5066_fixups[] = {
SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI),
SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP),
+ SND_PCI_QUIRK(0x19e5, 0x3291, "Huawei MateBook 16s (RPL)", CXT_FIXUP_HUAWEI_MATEBOOK_RPL),
+ SND_PCI_QUIRK(0x19e5, 0x3292, "Huawei MateBook 16s (RPL)", CXT_FIXUP_HUAWEI_MATEBOOK_RPL),
+ SND_PCI_QUIRK(0x19e5, 0x3293, "Huawei MateBook 16s (RPL)", CXT_FIXUP_HUAWEI_MATEBOOK_RPL),
+ SND_PCI_QUIRK(0x19e5, 0x3294, "Huawei MateBook 16s (RPL)", CXT_FIXUP_HUAWEI_MATEBOOK_RPL),
SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004),
SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205),
SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC),
--
2.56.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] ALSA: hda/conexant: Fix swapped speaker/headphone outputs on Huawei MateBook 16s
2026-10-24 18:40 [PATCH] ALSA: hda/conexant: Fix swapped speaker/headphone outputs on Huawei MateBook 16s Xie Youtian
@ 2026-10-24 19:06 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-24 19:06 UTC (permalink / raw)
To: Xie Youtian, linux-sound
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] ALSA: hda/conexant: snd_hda_jack_detect() reads stale cached pin sense
- [High] ALSA: hda/conexant: rpl_recheck_work not cancelled in cx_suspend()
- [High] ALSA: hda/conexant: use-after-free via rpl_recheck_work re-arm on unbind
- [Medium] ALSA: hda/conexant: rpl_recheck_work queued on deprecated system_wq
- [Medium] ALSA: hda/conexant: pin 0x17 setup and EAPD state clobbered in cx_init()
--
commit ea041495d0aee419faa09bb80155d129a155625c
Author: Xie Youtian <xiedada@xiedada.net>
ALSA: hda/conexant: Fix swapped speaker/headphone outputs on Huawei MateBook 16s
This commit adds a fixup for Huawei MateBook 16s (Raptor Lake) machines with
a Conexant SN6140 codec where the speaker amplifier and headphone jack share
the analog output line of pin 0x16. It routes pin 0x16 to the active DAC,
opens the headphone tap via GPIO1, gates the speaker amplifier via pin 0x17
EAPD, and schedules a delayed work to recheck pin sense after 600 ms.
Link: https://github.com/geniusinsanity/huawei-matebook16s-speaker-fix
> diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c
> index 9bfdbf5c90325..62a41c764ccbb 100644
> --- a/sound/hda/codecs/conexant.c
> +++ b/sound/hda/codecs/conexant.c
[ ... ]
> @@ -863,6 +868,143 @@ static const struct hda_pintbl cxt_pincfg_sws_js201d[] = {
> {}
> };
>
> +/*
> + * Huawei MateBooks with Raptor Lake and SN6140 (e.g. the MateBook 16s
> + * 2023): the speaker amp and the headphone jack are both fed from the
> + * pin 0x16 line. The speaker amp is gated by the EAPD of pin 0x17, the
> + * jack tap by GPIO1 - none of which matches the BIOS pin config. Point
> + * pin 0x16 at the DAC the parser leaves unmuted for the current jack
> + * state, keep the tap open, and gate the speaker amp via EAPD.
> + */
> +static void cxt_matebook_raptor_lake_coerce(struct hda_codec *codec, bool hp)
> +{
> + snd_hda_codec_write_cache(codec, 0x16, 0,
> + AC_VERB_SET_CONNECT_SEL, hp ? 0x0 : 0x1);
> + snd_hda_codec_write_cache(codec, 0x17, 0,
> + AC_VERB_SET_EAPD_BTLENABLE, hp ? 0x0 : 0x2);
> + /* keep the jack tap on the shared line open; with the speaker amp
> + * gated separately this is correct in both jack states
> + */
> + snd_hda_codec_write_cache(codec, 0x01, 0,
> + AC_VERB_SET_GPIO_MASK, 0x02);
> + snd_hda_codec_write_cache(codec, 0x01, 0,
> + AC_VERB_SET_GPIO_DIRECTION, 0x02);
> + snd_hda_codec_write_cache(codec, 0x01, 0,
> + AC_VERB_SET_GPIO_DATA, 0x00);
> +}
> +
> +/* jack presence right at a plug event bounces for a few milliseconds;
> + * sample it once, debounced, and use that one value everywhere below
> + */
> +static bool cxt_matebook_raptor_lake_jack_present(struct hda_codec *codec)
> +{
> + bool a = snd_hda_jack_detect(codec, 0x16);
> +
> + if (snd_hda_jack_detect(codec, 0x16) != a) {
> + msleep(100);
> + return snd_hda_jack_detect(codec, 0x16);
> + }
> + return a;
> +}
> +
> +static void cxt_matebook_raptor_lake_apply(struct hda_codec *codec, bool hp)
> +{
> + struct conexant_spec *spec = codec->spec;
> +
> + spec->gen.hp_jack_present = hp;
> + snd_hda_gen_update_outputs(codec);
> + cxt_matebook_raptor_lake_coerce(codec, hp);
> +}
> +
> +/*
> + * Right after the codec powers back up every reader of the pin sense -
> + * ours and the parser's alike - can see garbage. Re-apply everything
> + * from a settled read a bit later, which heals any misguess made at
> + * init or during a jack event.
> + */
> +static void cxt_matebook_raptor_lake_recheck(struct work_struct *work)
> +{
> + struct conexant_spec *spec = container_of(work, struct conexant_spec,
> + rpl_recheck_work.work);
> + struct hda_codec *codec = spec->rpl_recheck_codec;
> +
> + if (codec) {
> + bool hp = snd_hda_jack_detect(codec, 0x16);
> +
> + codec_dbg(codec, "matebook-rpl: recheck hp=%d\n", hp);
> + cxt_matebook_raptor_lake_apply(codec, hp);
> + }
> +}
[Severity: High]
Will snd_hda_jack_detect(codec, 0x16) actually query the hardware on the
second call in cxt_matebook_raptor_lake_jack_present() or when
cxt_matebook_raptor_lake_recheck() runs 600 ms later?
Pin 0x16 is registered in codec->jacktbl during
snd_hda_gen_parse_auto_config() -> check_auto_mute_availability() and via
snd_hda_jack_detect_enable_callback(). When snd_hda_jack_detect() calls
snd_hda_jack_detect_mst() -> snd_hda_jack_detect_state_mst() ->
snd_hda_jack_pin_sense(), it updates the cached state through
jack_detect_update():
sound/hda/common/jack.c:jack_detect_update() {
if (!jack->jack_dirty)
return;
...
jack->pin_sense = read_pin_sense(codec, jack->nid,
jack->dev_id);
...
jack->jack_dirty = 0;
}
In cxt_matebook_raptor_lake_jack_present(), the first call to
snd_hda_jack_detect(codec, 0x16) clears jack->jack_dirty to 0 (or finds it
already 0 when called via snd_hda_gen_hp_automute() -> detect_jacks() ->
call_update_outputs()). The subsequent call in the condition:
if (snd_hda_jack_detect(codec, 0x16) != a)
sees jack->jack_dirty == 0, skips read_pin_sense(), and returns the cached
jack->pin_sense equal to a, making the msleep(100) debounce branch
unreachable.
Similarly, when cxt_matebook_raptor_lake_recheck() executes 600 ms after
HDA_FIXUP_ACT_INIT, cxt_matebook_raptor_lake_update_outputs(), or
cxt_matebook_raptor_lake_jack_event(), jack->jack_dirty is still 0 from the
earlier read. Calling snd_hda_jack_detect(codec, 0x16) without setting
jack->jack_dirty = 1 (or reading pin sense directly and calling
snd_hda_jack_report_sync()) returns the 600 ms-old cached pin_sense without
issuing AC_VERB_GET_PIN_SENSE to the codec, and never notifies userspace jack
controls if the settled state changed.
Also, since pin 0x16 already has call_hp_automute registered on its callback
list by check_auto_mute_availability(), registering
cxt_matebook_raptor_lake_jack_event on pin 0x16 causes each unsolicited event
to run both callbacks back-to-back, applying the verbs and rescheduling
rpl_recheck_work twice per event.
> +
> +/* the parser reprograms pin 0x17 and the DAC amps on jack events; the
> + * recheck heals a stale presence sample taken while the contact still
> + * bounces
> + */
> +static void cxt_matebook_raptor_lake_update_outputs(struct hda_codec *codec)
> +{
> + struct conexant_spec *spec = codec->spec;
> + bool hp = cxt_matebook_raptor_lake_jack_present(codec);
> +
> + codec_dbg(codec, "matebook-rpl: hook hp=%d\n", hp);
> + cxt_matebook_raptor_lake_apply(codec, hp);
> + mod_delayed_work(system_wq, &spec->rpl_recheck_work,
> + msecs_to_jiffies(600));
> +}
> +
> +/* our own jack callback: the unsol -> automute_hook chain is
> + * unreliable on this machine (stays silent while a stream keeps the
> + * codec awake); apply immediately and keep the delayed recheck as the
> + * safety net for anything the debounced sample got wrong
> + */
> +static void cxt_matebook_raptor_lake_jack_event(struct hda_codec *codec,
> + struct hda_jack_callback *jack)
> +{
> + struct conexant_spec *spec = codec->spec;
> + bool hp = cxt_matebook_raptor_lake_jack_present(codec);
> +
> + codec_dbg(codec, "matebook-rpl: jack event\n");
> + cxt_matebook_raptor_lake_apply(codec, hp);
> + mod_delayed_work(system_wq, &spec->rpl_recheck_work,
> + msecs_to_jiffies(600));
> +}
[Severity: Medium]
Does queueing spec->rpl_recheck_work onto system_wq here trigger a runtime
deprecation warning and mix two different workqueues for the same work item?
In kernel/workqueue.c:__queue_work():
if (unlikely(wq->flags & __WQ_DEPRECATED))
pr_warn_once("workqueue: work func %ps enqueued on deprecated workqueue. "
"Use system_{percpu|dfl}_wq instead.\n",
work->func);
Because system_wq is allocated with __WQ_DEPRECATED, calling
mod_delayed_work(system_wq, &spec->rpl_recheck_work, ...) triggers this
warning when the timer expires.
In addition, HDA_FIXUP_ACT_INIT below uses schedule_delayed_work(), which
queues onto system_percpu_wq rather than system_wq. Mixing two distinct
workqueue_struct instances for spec->rpl_recheck_work breaks workqueue
non-reentrancy in __queue_work() (which checks worker->current_pwq->wq == wq),
allowing cxt_matebook_raptor_lake_recheck() to execute concurrently on two
CPUs if re-queued while sleeping on HDA verb I/O. Should these callers use
mod_delayed_work(system_percpu_wq, ...) or schedule_delayed_work()
consistently?
> +
> +static void cxt_fixup_matebook_raptor_lake(struct hda_codec *codec,
> + const struct hda_fixup *fix, int action)
> +{
> + struct conexant_spec *spec = codec->spec;
> +
> + switch (action) {
> + case HDA_FIXUP_ACT_PRE_PROBE:
> + spec->gen.automute_hook = cxt_matebook_raptor_lake_update_outputs;
> + INIT_DELAYED_WORK(&spec->rpl_recheck_work,
> + cxt_matebook_raptor_lake_recheck);
> + spec->rpl_recheck_codec = codec;
> + /* pin 0x17 drives the tap line; enabled once, like the
> + * Windows driver does
> + */
> + snd_hda_codec_write_cache(codec, 0x17, 0,
> + AC_VERB_SET_CONNECT_SEL, 0x0);
> + snd_hda_codec_write_cache(codec, 0x17, 0,
> + AC_VERB_SET_PIN_WIDGET_CONTROL,
> + 0x40);
> + break;
[Severity: Medium]
Aren't the pin 0x17 settings written in HDA_FIXUP_ACT_PRE_PROBE overwritten
during snd_hda_gen_init(), and doesn't cx_init() clobber pin 0x17's EAPD
state?
Because HDA_FIXUP_ACT_PRE_PROBE runs in cx_probe() before
snd_hda_gen_parse_auto_config() and cx_init() -> snd_hda_gen_init(), the
generic parser's init_extra_out() -> set_output_and_unmute() ->
snd_hda_activate_path() overwrites pin 0x17 AC_VERB_SET_CONNECT_SEL to 0x1
(DAC 0x11), and snd_hda_gen_update_outputs() -> do_automute() overwrites pin
0x17 AC_VERB_SET_PIN_WIDGET_CONTROL to 0x0 whenever headphones are present or
master_mute is active. (The comment here also says pin 0x17 drives the tap
line, whereas the commit description and top comment state pin 0x16 drives
the shared line.)
Furthermore, cx_probe() calls cx_auto_parse_eapd(), which adds pin 0x17 to
spec->eapds[]. During cx_init() (on probe and every resume):
sound/hda/codecs/conexant.c:cx_init() {
struct conexant_spec *spec = codec->spec;
snd_hda_gen_init(codec);
if (!spec->dynamic_eapd)
cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true);
cxt_init_gpio_led(codec);
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
}
When !spec->dynamic_eapd, cx_auto_turn_eapd() sends an uncached
AC_VERB_SET_EAPD_BTLENABLE = 0x02 to pin 0x17 after snd_hda_gen_init() has
already unmuted DAC 0x10 on pin 0x16 for headphones, transiently enabling the
speaker amplifier until HDA_FIXUP_ACT_INIT turns it back off. If
spec->dynamic_eapd is set, cx_auto_vmaster_hook() turns pin 0x17 EAPD on
whenever Master Playback Switch is unmuted, enabling the speaker amplifier
even while headphones are plugged in.
> + case HDA_FIXUP_ACT_INIT:
> + /* the parser's own view is the best guess at init; the
> + * delayed recheck above heals whatever it gets wrong
> + */
> + snd_hda_jack_detect_enable_callback(codec, 0x16,
> + cxt_matebook_raptor_lake_jack_event);
> + cxt_matebook_raptor_lake_apply(codec, spec->gen.hp_jack_present);
> + schedule_delayed_work(&spec->rpl_recheck_work,
> + msecs_to_jiffies(600));
> + break;
> + case HDA_FIXUP_ACT_FREE:
> + cancel_delayed_work_sync(&spec->rpl_recheck_work);
> + spec->rpl_recheck_codec = NULL;
> + break;
> + }
> +}
[Severity: High]
What happens if the codec suspends or shuts down while spec->rpl_recheck_work
is pending?
spec->rpl_recheck_work is armed for 600 ms in HDA_FIXUP_ACT_INIT,
cxt_matebook_raptor_lake_update_outputs(), and
cxt_matebook_raptor_lake_jack_event(), but it is never cancelled in
cx_suspend() or cx_auto_shutdown() ( and in cx_remove(), cx_auto_shutdown()
runs before snd_hda_gen_remove() invokes HDA_FIXUP_ACT_FREE):
sound/hda/common/codec.c:hda_call_codec_suspend() {
snd_hdac_enter_pm(&codec->core);
if (driver->ops->suspend)
driver->ops->suspend(codec);
if (!codec->no_stream_clean_at_suspend)
hda_cleanup_all_streams(codec);
state = hda_set_power_state(codec, AC_PWRST_D3);
update_power_acct(codec, true);
snd_hdac_leave_pm(&codec->core);
return state;
}
If spec->rpl_recheck_work fires while hda_call_codec_suspend() is running
(for example, during the depop_delay sleep in hda_set_power_state() after
cx_auto_shutdown() has cleared EAPD on pin 0x17), snd_hdac_keep_power_up()
sees codec->core.in_pm > 0 and allows cxt_matebook_raptor_lake_coerce() to
write AC_VERB_SET_EAPD_BTLENABLE = 0x2 to pin 0x17, re-enabling the speaker
amplifier as the codec enters D3.
If spec->rpl_recheck_work fires after runtime suspend completes,
snd_hda_codec_write_cache() triggers snd_hdac_power_up_pm(), forcing an
immediate runtime resume that runs cx_init() -> HDA_FIXUP_ACT_INIT and
re-schedules spec->rpl_recheck_work for another 600 ms. And if it fires during
system suspend or shutdown after runtime PM is disabled,
pm_runtime_get_if_active() returns -EINVAL (which snd_hdac_keep_power_up()
treats as 0), issuing raw HDA verbs while the codec is in D3.
Additionally, can spec->rpl_recheck_work be re-armed after
cancel_delayed_work_sync() during driver unbind, causing a use-after-free on
struct conexant_spec?
In sound/hda/common/bind.c:hda_codec_driver_remove():
if (driver->ops->remove)
driver->ops->remove(codec);
snd_hda_codec_cleanup_for_unbind(codec);
driver->ops->remove(codec) calls cx_remove() -> snd_hda_gen_remove(), which
invokes HDA_FIXUP_ACT_FREE and immediately calls kfree(codec->spec). However,
codec->jackpoll_work, bus->unsol_work, mixer controls (such as
automute_mode_put()), and codec->jacktbl callbacks are not stopped or freed
until snd_hda_codec_cleanup_for_unbind(codec) runs afterward.
If cxt_matebook_raptor_lake_jack_event() (which can sleep for 100 ms in
cxt_matebook_raptor_lake_jack_present()) or
cxt_matebook_raptor_lake_update_outputs() is running concurrently during
cx_remove(), it can call mod_delayed_work() after cancel_delayed_work_sync()
has returned, re-queueing spec->rpl_recheck_work on the freed
struct conexant_spec (or dereferencing codec->spec after it is set to NULL).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261024184049.107280-1-xiedada@xiedada.net?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-24 19:06 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-24 18:40 [PATCH] ALSA: hda/conexant: Fix swapped speaker/headphone outputs on Huawei MateBook 16s Xie Youtian
2026-10-24 19:06 ` sashiko-bot
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.