speakerguardd is a userspace daemon, written in Rust, that protects the
speakers of machines whose amplifier has no current or voltage sense lines.
The first such machine is the MacBook Neo (J700): a fixed-gain MAX98360A per
channel on the AOP's serializer, and a "Speaker Sense" capture stream
(hw:AppleJ700,2) that returns the words the amplifier receives.
It is the sibling of Asahi Linux's speakersafetyd and speaks the same
interlock to the kernel: the card (snd-soc-macaudio) holds the speaker
volume at a safe level until a daemon locks the volume controls and the
"Speaker Volume Unlock" control and keeps writing to it while the speakers
play; if the daemon stops for any reason, the card locks the volume again
within 250 ms. The configuration files use the same keys, so a speaker
description reads the same way in both projects.
Every period (4096 frames) the sense words of each channel are scaled to the
amplifier's output voltage (vs_scale volts at full scale) and squared into
the power in the coil (z_nominal). That power drives a two-stage lumped
thermal model - voice coil into magnet (tr_coil K/W, tau_coil s), magnet
into ambient (tr_magnet, tau_magnet) - and the coil temperature drives a
governor: gain reduction starts t_window below the working limit
(t_limit - t_headroom), grows linearly across the window to
t_reduction_max dB at the limit (and on beyond it), is released only once
the coil has cooled t_hysteresis below where reduction began, and is
released slowly. A windowed power budget (p_limit_1s, p_limit_60s: the
mean power over the last second and the last minute) reduces the gain at
once when a burst would exceed what the pair is rated for, before the coil
has warmed up. The larger of the two reductions is written to the card's
speaker volume control. If it would exceed --max-reduction, the daemon
exits and leaves the card locked: the safe volume is then the kernel's, not
the model's (20 dB by default: a model asking for more is wrong).
There is no feedback: the model can only be as good as its constants. The J700's constants - thermal resistances and time constants, coil temperature limits, volts at full scale, coil resistance and the power budgets - are the machine's own loudspeaker-manager tuning, read back through the public AudioUnit API and recorded in the bring-up provenance record. Without the daemon the kernel locks the J700's speakers 20 dB below the amplifier's full scale; with it, the model governs up to full scale.
Static, from any host:
cargo build --release --target aarch64-unknown-linux-musl
The binary talks to the ALSA kernel interface directly (no alsa-lib).
make install
installs the binary, conf/apple/*.conf, the systemd unit and the udev
rule that starts it when a supported card's sense PCM appears.
speakerguardd -c /usr/share/speakerguardd -v
-C <card id> selects a card, -m <dB> the reduction at which the daemon
gives up.