Skip to content

ASoC: apple: macaudio: Set the speaker volumes again after a system sleep - #580

Closed
erikwb wants to merge 1 commit into
aurora-silicon:integration/aurorafrom
erikwb:j713/macaudio-pm-volumes
Closed

erikwb wants to merge 1 commit into
aurora-silicon:integration/aurorafrom
erikwb:j713/macaudio-pm-volumes

Conversation

@erikwb

@erikwb erikwb commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Since 1cbce20 ("ASoC: apple: macaudio: lock the speaker volumes before userspace freezes"), the speakers are silent after the first system sleep. They stay silent until speakersafetyd is restarted, and also across reboots: alsactl stores the muted volumes at shutdown and restores them at the next boot.

Cause

Locking the speaker volumes clips every speaker volume control to the lock's ceiling. On the SN012776 that is 1, or -100 dB. Unlocking only lifts the ceiling again; it doesn't set the volumes back.

After a lease the daemon lost, that is fine: speakersafetyd gets -ETIMEDOUT on its next ping, restarts and sets the volumes itself. The system-sleep lock is deliberately not reported as a lost lease, so that the daemon keeps its speaker temperatures. As a result, nothing ever sets the volumes again: the daemon still assumes the values it wrote before the sleep.

Fix

At suspend-prepare, before the lock clips them, keep the speaker volumes. At the first unlock after that, set them again. That unlock is the daemon's first ping after the resume, which can come only when playback next starts. These are the values the daemon set and still assumes, so its state and the hardware agree again.

  • A second sleep before the daemon pings keeps what the first one saved: a suspend-prepare that finds the volumes locked already doesn't save or drop anything.
  • No race with the daemon's volume writes. The card's controls are held for reading from keeping the volumes until they are clipped, and in the work that can unlock after a back-end starts or stops. So an older, higher volume can't overwrite one the daemon set meanwhile. Control writes hold the controls for writing and take volume_lock_mutex inside, in the same order.
  • A new owner of the lock, such as a restarted daemon, sets the volumes itself: what was kept is dropped then, and when the card is removed. Nothing is kept for a card that is shutting down.
  • The value buffer for setting them again is allocated with them, so the restore itself can't fail.

The lock itself is unchanged: the volumes are still clipped from suspend-prepare until the daemon's first ping after the resume.

Testing

On a J713 (MacBook Air 13" M4), with this fix applied to our J713 tree (which has the J713 speaker support):

Before After
Speaker volumes after a suspend-to-idle all four at 1 (-100 dB), for good 1 until the daemon pings, then all four back at 201 (0 dB)
Two suspends in a row, the second with no ping before playback — still locked at 1 after the second resume; back at 201 when playback starts
Playback PCM runs, nothing audible audible
speakersafetyd restart needed yes no

KUnit: a new apple-speaker-pm-volumes suite drives the real lock control callbacks (slk_lock, slk_put) and the PM notifier, with volume controls backed by soc_mixer_control, so the actual clip runs. It covers one sleep, two sleeps before the daemon pings, and a new owner after a sleep. The two-sleep case fails without the keep-while-locked change. It runs with the existing admission suite under arm64 QEMU with KASAN and lockdep: 4/4 pass, no reports.

checkpatch --strict: no errors. Two notes remain: the MAINTAINERS warning is covered by the existing sound/soc/apple/* entry, and the blank-line check before kunit_test_suite() follows the existing admission test header. W=1 builds of macaudio.o are clean, built in and as a module.

This fix was developed and tested by Claude (Anthropic), working with me on the J713.

…leep

Since the speaker volumes are locked from suspend-prepare, the speakers
stay silent after the first system sleep. Locking clips every speaker
volume to the lock's ceiling (1, -100 dB on the SN012776), and unlocking
only lifts the ceiling again. After a lost lease that is fine:
speakersafetyd gets -ETIMEDOUT, restarts and sets the volumes itself. The
system-sleep lock is deliberately not reported as a lost lease, so the
daemon keeps its state and never writes the volumes again. They stay at
-100 dB until the daemon restarts. alsactl then stores them that way at
shutdown, and restores them on the next boot.

Keep the speaker volumes at suspend-prepare, before the lock clips them,
and set them again at the first unlock after it. That is the daemon's
first ping after the resume, which in practice comes when playback next
starts. These are the values the daemon set and still assumes.

The daemon may not ping between two sleeps. A suspend-prepare that finds
the volumes locked already keeps what an earlier one saved, so a second
sleep before any playback doesn't lose them. Hold the card's controls
for reading from keeping the volumes until they are clipped, so a volume
the daemon writes meanwhile can't be overwritten by an older, higher one
at the unlock. Hold them as well in the work that can unlock after a
back-end starts or stops, so setting the volumes again there doesn't race
a volume write either. Control writes hold them for writing and take
volume_lock_mutex inside, in the same order.

A new owner of the lock, such as a restarted daemon, sets the volumes
itself: drop what was kept then, and when the card is removed. Nothing
is kept for a card that is shutting down.

Tested on a J713: after a suspend-to-idle the four speaker volumes are
back at 0 dB and the speakers play, without restarting speakersafetyd.
The KUnit cases go through the lock control and the PM notifier: one
sleep, two sleeps before the daemon pings, and a new owner after a sleep.

Fixes: 1cbce20 ("ASoC: apple: macaudio: lock the speaker volumes before userspace freezes")
Signed-off-by: Erik Bourget <erik@ebourget.net>
@jgoyvaerts

Copy link
Copy Markdown
Contributor

Confirmed on a J616s (MacBook Pro 16" M4 Pro, T6040), and agreed on the cause. 1cbce20 is my commit; thanks for catching this.

Reproduced on 7.3.0-m4.75, which carries 1cbce20 (no fix):

  • Before the sleep, all six J616s speaker volumes were at 201.
  • After one s2idle cycle (14:10:13 → 14:10:24), all six were at 1, and Speaker Volume Unlock read 1: the daemon had pinged and the ceiling was lifted, but the values stayed clipped.
  • speakersafetyd kept the same PID across the sleep. It logged Suspend detected! and reinitialized its PCM, but never wrote a volume again.

Why the daemon never writes them back: speakersafetyd writes a volume only when its computed gain changes (if gain != group.gain, src/main.rs). After the resume it still assumes 0 dB, so the clipped values stay until its gain changes or it restarts.

On "also across reboots": here a reboot clears it. speakersafetyd's warm-boot flag is under /run/speakersafetyd/, so every boot is a cold start, and it presets every gain to 0 dB after alsactl restores the saved 1s. Our asound.state holds value 1 for all six, yet the live controls were at 201 after boot. Whether it survives a reboot probably depends on the order alsa-restore and speakersafetyd start in.

On the fix (read only, not built or run here):

  • Every path that can unlock and so restore holds controls_rwsem (slk_put via the control write, the PM notifier and lock_update_work with this patch). The paths that only lock (timeout work, slk_unlock) never restore.
  • Lock order: controls_rwsem is taken before volume_lock_mutex everywhere.
  • A daemon volume write while locked gets -EINVAL from soc_mixer_valid_ctl() (above platform_max). speakersafetyd panics on that and restarts, and the new owner drops the kept values. So an older, higher volume can't overwrite a reduction the daemon applied after the resume.

Analysis done with Claude (Anthropic).

@erikwb

erikwb commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #820 (commit 17). Two changes:

  • The card's remove now cancels the lock update work while the snd_card still exists. That work takes controls_rwsem, and a back-end stop during teardown can queue it.
  • The commit message no longer says the clipped volumes always come back after a reboot, per @jgoyvaerts' J616s result above.

@erikwb erikwb closed this Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants