Setup scripts for MacBook Pro 13" 2017 (A1708) running Debian Testing.
Before running this script:
- Install Debian Testing with non-free firmware enabled — the Debian installer must include
firmware-iwlwifi,firmware-brcm80211and friends. The graphical installer offers "non-free-firmware" as a checkbox; the netinst ISO fromcdimage.debian.org/cdimage/unofficial/non-free/firmware/has them bundled. Without these, WiFi/Bluetooth will not work even after this script runs. - Boot into GNOME, open a terminal, then install
git:sudo apt update sudo apt install -y git
- (Optional) If migrating from macOS: perform an SMC Reset once (see Bluetooth section below) — otherwise Bluetooth will stay broken even after the script runs.
git clone https://github.com/vrilutza/scripts.git
cd scripts
chmod +x macbook-debian-setup.sh
./macbook-debian-setup.sh
sudo reboot| What to test | How |
|---|---|
| Audio (speakers + headphones jack) | Play any sound; toggle output in GNOME Settings → Sound |
| Camera | cheese or any video-call app — /dev/video0 should exist |
| Backlight | Fn+F1 / Fn+F2 — slider should respond smoothly; brightness must then stay where you set it (auto-brightness + idle-dim disabled by Stage 5h) |
| WiFi | Connect to your network from GNOME |
| Bluetooth | Pair a device from GNOME Settings → Bluetooth (SMC reset first if needed) |
| Touchpad | Tap, swipe, two-finger scroll — cursor should be smooth |
| VA-API (video accel) | vainfo should print supported profiles |
| Suspend | Fully blocked by design (sleep targets masked — S3 unreliable on this hardware). Lid close only locks the screen; lid open no longer powers the machine on by itself (firmware AutoBoot=%00, optimize step 17). See "Suspend / sleep / hibernation" below. |
| Reboot | sudo systemctl reboot should reset cleanly (reboot=pci). If it ever hangs at "Rebooting.", see the reboot note below. |
| DKMS rebuild | sudo dkms status — snd_hda_macbookpro and facetimehd should show installed |
Commands you can come back to without remembering anything — open this section, copy what you need. All read-only unless noted. Some commands need extra tools; the apt install hint is shown where relevant.
sudo dkms status snd_hda_macbookpro # DKMS module installed + built for current kernel?
lsmod | grep cs8409 # Codec module loaded right now?
cat /proc/asound/cards # ALSA sees the card?
aplay -l # Playback devices visible to ALSA?Quick live test: from GNOME Settings → Sound, toggle output between Speakers and Headphones; both should respond. If audio dies after a kernel upgrade, run sudo dkms status first — DKMS rebuilds the module automatically against the new headers and a missing rebuild is the usual culprit.
✅ Audio regression on kernel 7.0.10 — RESOLVED in 7.0.12. Kernel
7.0.10briefly broke the CS8409 codec (no sound card; an in-tree HDA generic-parser change, not a DKMS rebuild failure). It was fixed upstream in7.0.12— audio registers normally again (verified: 0 UBSAN, card OK). 7.0.10 is no longer shipped. If you're still on it for any reason,sudo apt full-upgrade(or boot 7.0.9). Background in TODO.md.
ls -la /dev/video* # Device node present after boot?
lsmod | grep facetimehd # Driver loaded right now?
sudo dkms status facetimehd # DKMS state (installed against current kernel)?
ls -la /usr/lib/firmware/facetimehd/firmware.bin # ~1.4 MB firmware in place?
sudo modinfo facetimehd | head -10 # Driver metadata (version, deps)Quick live test: sudo apt install cheese && cheese — webcam preview should appear in 1-2 seconds. If /dev/video0 is missing right after boot, try sudo modprobe facetimehd manually.
cat /proc/cmdline # Kernel boot params (expect acpi_backlight=native, reboot=pci, mem_sleep_default=deep, nvme.noacpi=1, i915.enable_dc=0, nvme_core...)
grep -o reboot=pci /proc/cmdline # reboot fix active? (prevents "Rebooting." hang)
systemctl is-enabled sleep.target suspend.target hibernate.target suspend-then-hibernate.target # all should be 'masked' (suspend fully blocked)
ls -la /usr/lib/systemd/system-sleep/ # Sleep hooks (facetimehd, brcmfmac) present (moot while masked)
gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type # Should be 'nothing'
gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type # Should be 'nothing'
cat /etc/systemd/logind.conf.d/macbook-no-suspend.conf # Lid switch override (HandleLidSwitch=lock)
od -An -tx1 -j4 /sys/firmware/efi/efivars/AutoBoot-7c436110-ab2a-4bbb-a880-fe41995c9f82 # '00' = no self power-on at lid-open/power-connect — the switch non-T2 Intel Macs honor (optimize step 17)
tail -c +5 /sys/firmware/efi/efivars/auto-boot-7c436110-ab2a-4bbb-a880-fe41995c9f82; echo # 'false' = same, ASCII T1/T2 variant (also step 17; inert on this model but kept in sync)
gsettings get org.gnome.settings-daemon.plugins.power ambient-enabled # Should be 'false' (Stage 5h — no auto-brightness)
gsettings get org.gnome.settings-daemon.plugins.power idle-dim # Should be 'false' (Stage 5h — no dimming on idle)To test the backlight keys: Fn+F1 / Fn+F2 — slider in GNOME panel should slide smoothly without jumps.
Brightness stays where you set it (Stage 5h). This MacBook has an ambient light sensor
(acpi-als, next to the camera) read by iio-sensor-proxy; with GNOME's "Automatic Screen
Brightness" enabled, the backlight drifts darker/brighter on its own as room light changes — it can
also look like the colors shift warmer/cooler, but that's just the backlight (the 2017 panel has
no True Tone). The script disables both auto-brightness and dimming-on-idle. To toggle manually:
# re-enable / disable auto-brightness (GNOME Settings → Power → Automatic Screen Brightness)
gsettings set org.gnome.settings-daemon.plugins.power ambient-enabled true # or false
# re-enable / disable screen dimming after inactivity
gsettings set org.gnome.settings-daemon.plugins.power idle-dim true # or falseLIBVA_DRIVER_NAME=iHD vainfo 2>&1 | head -10 # iHD driver (Kaby Lake native) profiles
vainfo 2>&1 | grep VAProfile # All profiles available (any driver)
dpkg -l intel-media-va-driver i965-va-driver # Both VA-API packages installed?Browser-side check: open chrome://gpu in Chrome/Chromium and look for Video Decode: Hardware accelerated. In Firefox, about:support → search "Decoder" → should show VAAPI for H.264/HEVC.
# Current GNOME settings — should all return 'true'
gsettings get org.gnome.desktop.peripherals.touchpad tap-to-click
gsettings get org.gnome.desktop.peripherals.touchpad natural-scroll
gsettings get org.gnome.desktop.peripherals.touchpad disable-while-typing
# Touchpad device known to libinput (apt install libinput-tools if missing)
sudo libinput list-devices | grep -A 6 -i touchpadTo live-debug events (e.g., "did my tap register as click?"): sudo libinput debug-events then touch the trackpad; Ctrl+C to stop. To check libinput discarding bad touchpad events (the "Touch jump detected" messages): journalctl -k --since "1 hour ago" | grep -i "touch jump".
# Live temps + fan speed (Ctrl+C to exit)
watch -n 1 'sensors | grep -E "Core|fan"'
# Fan floor active? (should be 3500; SMC auto-ramps above it under load)
cat /sys/devices/platform/applesmc.768/fan1_min
cat /sys/devices/platform/applesmc.768/fan1_input # current RPM
cat /etc/udev/rules.d/99-macbook-fan.rules # the persistent floor rule
# One-shot read of all sensors (CPU, battery, ambient)
sensors
# RAPL limits currently active (constraint_0 = PL1, constraint_1 = PL2)
grep . /sys/class/powercap/intel-rapl:0/constraint_*_power_limit_uw
# Recent thermal throttle events
journalctl --since "1 hour ago" | grep -iE "throttle|thermal"
# Is thermald running + did the udev rule fire its reinit service?
systemctl is-active thermald macbook-rapl-thermald.service
# The udev rule that sets RAPL on device appearance
cat /etc/udev/rules.d/99-macbook-rapl.rules
# How thermald picks up RAPL each boot: the first thermald instance starts
# before the powercap device and logs "NO RAPL sysfs present" (2 lines, expected),
# then the udev rule sets RAPL and restarts thermald. Confirm the restart happened
# *after* RAPL was set — the running thermald instance is the one that sees RAPL:
journalctl -u thermald -u macbook-rapl-thermald.service -b 0 | tail -8
# Expected tail: "...reinit... Finished", immediately followed by thermald "Started".Expected ranges on i5-7360U with PL1=22W / PL2=30W:
| Scenario | Temperature |
|---|---|
| Idle / light browsing | 40-60°C |
| YouTube / Netflix HD (VA-API active) | 60-75°C |
| Sustained heavy load (build, video encode) | 80-92°C — PL1 22W cap engages |
| Brief burst (app launch, short compile) | up to ~95°C — PL2 30W, transient |
Red flags worth investigating:
- Idle persistent over 70°C — RAPL may not be applied (
grep .command above) or thermald is down (systemctl is-active thermald). - Sustained over 95°C with fan maxed — PL1 is too aggressive for this thermal solution. To lower it, edit the two
ATTR{constraint_*_power_limit_uw}values in/etc/udev/rules.d/99-macbook-rapl.rules(change22000000to e.g.18000000), thensudo udevadm control --reload && sudo udevadm trigger --action=add /sys/class/powercap/intel-rapl:0. No reboot needed.
Why a udev rule (not tmpfiles / .service / .path)? This repo went through four iterations:
- v1 —
tmpfiles.d: wrote RAPL limits at early boot. Failed becauseintel_rapl_msrloads via udev later in boot and overwrote our values with Apple EFI defaults (100W / 125W). - v2 —
.servicewithConditionPathExists: worked on kernel 7.0.7 (100% of boots) but failed on ~37.5% of boots on kernel 7.0.9 due to a ~110 ms race condition: systemd evaluated the condition just before the kernel's udev probe exposed the sysfs file. Worse,thermalditself initialised without RAPL on the same failed boots (loggedNO RAPL sysfs present) — and its 4-second polling does not rediscover cooling devices. - v3 —
.pathunit +.service: also failed..pathunits use inotify, and sysfs does not reliably emit inotify creation events — so the trigger only fired when the file already existed at.pathstart time (still a race). PlusAfter=thermald.serviceon a.pathunit created an ordering cycle (paths.target is ordered before basic.target, thermald after). - v4 (current) — udev rule:
ACTION=="add", SUBSYSTEM=="powercap", KERNEL=="intel-rapl:0"writes the limits directly viaATTR{constraint_*_power_limit_uw}=. udev fires on the kernel's real device-add uevent (reliable, unlike inotify on sysfs), so the values are set deterministically the moment the device appears — on any kernel.TAG+="systemd"+ENV{SYSTEMD_WANTS}+="macbook-rapl-thermald.service"pulls in a tiny oneshot thattry-restarts thermald so it reinitialises and discovers RAPL. Verified empirically:udevadm trigger --action=addre-applies 100W→22W, and thermald restarted after RAPL is set no longer logsNO RAPL sysfs present.
Fan floor (fan1_min=3500). The Apple SMC's stock fan curve is silence-first to a fault — it
keeps the single exhaust fan at its floor (~1200 RPM) even at 80°C. The script raises the floor to
3500 RPM (the noise sweet spot on the A1708) via a udev rule:
ACTION=="add", SUBSYSTEM=="platform", KERNEL=="applesmc.768", ATTR{fan1_min}="3500"
Crucially this only raises the floor — fan1_manual stays 0, so the SMC keeps full automatic
control and still ramps above 3500 (up to 7200) under load. It is not a fixed-speed override
(fan1_manual=1 would pin the fan and disable the safety ramp — never do that). Measured on
MacBookPro14,1:
| Phase | Fan | Package temp |
|---|---|---|
| Baseline (stock floor 1200) | 1197 RPM | 80°C |
| Floor 3500, idle | ~3500 RPM | 74°C (−6°C) |
| Floor 3500, 4-core load | ramps 3500 → 4525 → 5400+ | peaks ~94°C, no throttle |
So the floor keeps the common idle/light case noticeably cooler (and reduces heat soak into the
worn battery — heat is the #1 killer of lithium cells), while the SMC's auto-ramp above the floor
keeps heavy load safe. One honest caveat: the SMC reacts slowly, so a sudden 100% load still
spikes to ~94°C for ~20-30 s before the fan ramps meaningfully — that lag is inherent to the SMC,
not the floor, and it stays under TJmax (100°C). To change the floor, edit
/etc/udev/rules.d/99-macbook-fan.rules then sudo udevadm control --reload && sudo udevadm trigger --action=add /sys/devices/platform/applesmc.768. To revert to stock: set it back to
1200 (or just echo 1200 | sudo tee /sys/devices/platform/applesmc.768/fan1_min — resets on
reboot anyway).
Hot-weather fingerprint (measured 2026-07-19, ~3h PSI/thermal monitoring, Chrome+Firefox). With high summer ambient the chassis heat-soaks: the fan sat at max (≥7000 RPM) ~85% of a 3-hour browsing session even at ~60°C package — that is poor heat evacuation (time to clean the fan/heatsink), not a control problem. The SMC-lag caveat above bites harder in summer: one 2-minute burst caught the fan near its floor (~3900 RPM) and cost ~115 throttle events at 89-95°C. Note the UI stutter with two browsers open is primarily 2C/4T CPU saturation (PSI-CPU spiking to 54%, GPU pinned at 1000 MHz), not thermal and not memory (PSI-mem ≈0; swap on the NVMe absorbs cleanly) — so a turbo cap or zswap would not help; a fan/heatsink cleaning and closing the one permanently-busy Chrome tab do.
lsmod | grep brcmfmac # WiFi driver loaded?
nmcli device wifi # Visible networks + current connection
nmcli device status # All network interfaces state
systemctl status bluetooth # Bluetooth daemon running?
bluetoothctl show # Adapter info; expect "Powered: yes"
rfkill list bluetooth # Blocked? expect "Soft blocked: no" (Stage 5f keeps it unblocked)
systemctl status bluetooth-rfkill-unblock.service # Boot unblock ran? expect status=0/SUCCESS
iw dev wlp2s0 get power_save # expect "Power save: off" (Stage 5g — BCM4350 firmware stability)
cat /proc/sys/kernel/panic # expect 10 — auto-reboot 10s after a panic (Stage 5g)
journalctl -g 'Invalid packet id' --no-pager # brcmfmac firmware desync counter (see WiFi section)Live BT debug: journalctl -u bluetooth -f, then try to pair from GNOME Settings — watch for BCM: Reset failed (means you need the SMC Reset described in Bluetooth section below). If bluetoothctl show reports Powered: no and rfkill shows it soft-blocked, that's the rfkill soft-block trap — see Bluetooth.
journalctl -p err -b --no-pager # All errors since boot
systemctl --failed # Any failed unit?
sudo dkms status # All DKMS modules state (both snd_hda_macbookpro and facetimehd should show 'installed')
uptime # Boot time + load avgIf systemctl --failed lists anything, drill in with systemctl status <unit> and journalctl -u <unit> for that specific unit.
Full setup for MacBook Pro 13" 2017 on a fresh Debian Testing install. Runs in 9 stages, each with auto-verification before proceeding.
| Stage | What it does |
|---|---|
| 1 — Dependencies | build-essential, linux-headers-amd64, linux-source, dkms, git, patch, wget, curl, cpio, xz-utils, libssl-dev, rfkill, iw |
| 2 — Audio driver | davidjo/snd_hda_macbookpro — Cirrus CS8409 patched driver via DKMS |
| 3 — Camera firmware | patjak/facetimehd-firmware — extracted from Apple OS X driver |
| 4 — Camera driver | patjak/facetimehd — kernel module via DKMS |
| 5 — System fixes | Backlight (acpi_backlight=native) + reboot=pci + sleep targets masked (suspend/hibernate blocked — see below) + Bluetooth rfkill unblock at boot + AutoEnable (see below) + WiFi stability: power-save off + kernel.panic=10 (see below) + fixed brightness: ALS auto-brightness + idle-dim off (5h) |
| 6 — VA-API | intel-media-va-driver + i965-va-driver — hardware video acceleration for Intel Iris Plus 640 |
| 7 — Touchpad UX | tap-to-click + natural-scroll + disable-while-typing via gsettings — macOS-like out of the box |
| 8 — Thermal management | thermald + lm-sensors + RAPL PL1=22W / PL2=30W (Apple-like) via udev rule + thermald reinit + fan floor (fan1_min=3500 RPM) |
| 9 — Cosmetic / journal | GNOME media-keys (hibernate/playback-repeat) + usb-protection off + applespi fnmode=1 — silences benign per-boot log errors |
The two DKMS modules (audio, camera) auto-rebuild on kernel updates. The script is idempotent — safe to re-run, skips already completed stages.
Companion script — turns off services that do nothing on this hardware/usage. Measured after a day of real use (2026-07-13): boot 16s → 9.9s (kernel 3.9→1.15s, userspace 12.1→8.8s), system services (system.slice) 862 → 223 MB, swap 3.7 GB → 0, plus ~280 MB freed from the GNOME session by steps 13-16. Every step was verified individually against the live system (apt removal simulations, package dependencies, docker restart policies, socket units) before being included — nothing here affects stability. Idempotent, safe to re-run.
| Step | What | Gain | Revert |
|---|---|---|---|
| 1 | Docker on-demand: docker.service + containerd disabled, docker.socket stays — daemon + containerd + dev stacks start at the first docker command (drop-in Wants=containerd, since the stock unit only orders After= it); orphaned csmbraila_db stopped |
~770 MB + 3.2s | systemctl enable docker containerd |
| 2 | NetworkManager-wait-online off (nothing needs network-online at boot after steps 1/4/8) |
3.6s | systemctl enable it back |
| 3 | Plymouth removed + quiet dropped from GRUB — service messages visible at boot instead of a splash |
~4s wait | apt install plymouth |
| 4 | gnome-software masked (user unit) — not apt-removed, which would drag the gnome/gnome-core metapackages away; updates via apt |
~30 MB | systemctl --user unmask |
| 5 | localsearch-3 masked + autostart hidden — file indexing off (Files search becomes limited) |
~55 MB | unmask + delete override |
| 6 | ModemManager disabled (no WWAN modem; package kept — NM references it) |
2-10 MB | systemctl enable --now |
| 7 | fwupd masked + refresh timer off (LVFS ships nothing for 2017 Macs) |
6-15 MB + 1.2s | unmask + enable timer |
| 8 | CUPS on-demand for real: service+browsed+path disabled, only the socket stays — printing auto-starts when used. (cups.path watches /var/cache/cups/org.cups.cupsd, which persists across reboots, so it was starting cupsd at every boot) |
~5 MB | systemctl enable --now cups cups.path |
| 9-12 | switcheroo-control (single GPU), iio-sensor-proxy (its only consumer, auto-brightness, is off per Stage 5h), networking.service (ifupdown legacy, only lo), e2scrub (LVM-only, no LVM here) |
~2 MB + ~1s | enable/unmask each |
| 13 | Evolution Data Server masked (3 user units + alarm-notify autostart hidden) — no online accounts configured (GOA empty), so it served nothing. Shell calendar shows no events; GNOME Contacts/Calendar need an unmask | ~207 MB | systemctl --user unmask evolution-source-registry evolution-calendar-factory evolution-addressbook-factory + delete the autostart override |
| 14 | packagekit masked + its apt hook (20packagekit) dpkg-diverted. The hook gdbus-poked the daemon after every apt update/dpkg run (that's what started packagekitd at every boot, via apt-daily) and, with the unit masked, printed a harmless Error: ... UnitMasked after each apt update. Its only real client (gnome-software) is masked by step 4; apt doesn't use PackageKit |
~21 MB | systemctl unmask packagekit && dpkg-divert --rename --remove /etc/apt/apt.conf.d/20packagekit |
| 15 | Remmina tray applet autostart off (Hidden=true) — the app itself stays installed, launch it when needed |
~53 MB | set Hidden=false back in ~/.config/autostart/remmina-applet.desktop |
| 16 | gvfs monitors masked (user): afc (iPhone mounting — no iPhone) + goa (online accounts — none). mtp and gphoto2 stay — the Samsung phone over USB shows up via MTP (files) or PTP (photos) |
~16 MB | systemctl --user unmask both |
| 17 | Firmware AutoBoot=%00 + auto-boot=false — 2016+ MacBooks power themselves on at lid-open and at power-connect, hence unwanted boots while carried with the lid closed. On non-T2 Intel Macs the switch the SMC honors is the binary AutoBoot variable (macOS nvram AutoBoot=%00); the ASCII auto-boot is the T1/T2 variant — verified live 2026-07-19: auto-boot=false alone persisted in NVRAM but did not stop lid-open boot on MacBookPro14,1. The step writes both (harmless overlap). After this the machine starts only from the power button |
no surprise power-ons | reverse writes printed by step 17 (true / %03) |
Deliberately untouched (in active use): bluetooth + mpris-proxy (Apple/Sony headphones, Logitech
mouse), avahi-daemon (SSH/.local to a second PC), gvfs-mtp/gphoto2 (Samsung phone over USB),
bolt/udisks2 (needed, and parallel to the boot critical path anyway).
Options at the top of the script: OPT_DOCKER=0 skips step 1 (keep dev stacks up right after boot),
OPT_LOCALSEARCH=0 skips step 5 (keep file search), OPT_EDS=0 skips step 13 (keep Shell calendar
events). Run it, then sudo reboot; measure with systemd-analyze && free -h.
Previous versions of this script shipped an out-of-tree applespi-fix/ DKMS patch with a velocity
filter to catch SPI-bus coordinate glitches before they reached libinput. Real-world testing showed
the patch reduced touch-jump frequency only marginally (it only caught the case where finger count
stayed constant between frames), and libinput already discards these events in userspace —
the cursor does not visibly jump.
The patch has been removed. libinput's own tp_motion_history detection handles all glitch cases
cleanly. The kernel bug: Touch jump detected and discarded messages still appear in the journal
when libinput discards a bad event, but that is the protection mechanism working as designed, not
a defect.
Root cause is hardware-level SPI bus instability on Apple T1/T2 systems — there is no software-only cure. libinput's secondary filtering is sufficient.
- MacBook Pro 13" 2017 — Model A1708 (no Touch Bar)
- CPU: Intel Core i5-7360U (Kaby Lake)
- GPU: Intel Iris Plus Graphics 640
- Audio: Cirrus Logic CS8409 / CS42L83
- Camera: Broadcom 720p FaceTime HD [14e4:1570]
- WiFi/Bluetooth: Broadcom BCM4350 (WiFi) / BCM4350C0 (Bluetooth, UART on serial0/ttyS4)
Short version: S3 sleep and hibernation cannot be made reliable on this MacBook, so the script blocks all sleep entirely. The laptop is meant to run always-on; closing the lid only locks the screen.
S3 "deep" suspend does not wake reliably on MacBookPro14,1 (Apple proprietary NVMe + Apple EFI).
The kernel enters PM: suspend entry (deep) fine, but the hardware often never generates a wake
event — the machine stays frozen and only a forced power-off recovers it (data-loss risk). This
was tested with every standard fix and none make it dependable:
mem_sleep_default=deep(S3 instead of the s2idle that crashes Apple NVMe)nvme.noacpi=1,nvme_core.default_ps_max_latency_us=0(Apple NVMe power-state quirks)i915.enable_dc=0(Intel display C-states crash on resume)- brcmfmac PCI-unbind sleep hook
Result: short suspends sometimes wake, longer ones (>20 min) freeze. It is a firmware/hardware limitation of how Apple implements power management — there is no software-only cure.
Hibernation is a different mechanism (write RAM to swap, power off, restore on next boot), so it's tempting to think it sidesteps the S3 wake problem. In practice it doesn't, on this hardware:
- It inherits the same Apple resume path — restoring from hibernation still goes through the Apple EFI + NVMe bring-up that's unreliable.
- It needs swap ≥ RAM set up as a resume device, plus a
resume=kernel param — extra config that this minimal setup doesn't ship. - The Apple NVMe quirks (
nvme.noacpi, PS0 pinning) that we apply for stability also work against the clean low-power state hibernation expects.
So chasing hibernation here is a dead end. That's the honest answer: don't try to "enable hibernation" on this MacBook — it will cost you hours and still not be dependable.
Since sleep is unfixable, the script prevents it so the machine can never hang trying:
- GNOME
sleep-inactive-ac-type/sleep-inactive-battery-type→nothing(no idle auto-suspend) - logind override
/etc/systemd/logind.conf.d/macbook-no-suspend.conf:HandleLidSwitch=lock(+ ExternalPower=lock, Docked=ignore) — lid close locks the screen, does not suspend systemctl mask sleep.target suspend.target hibernate.target suspend-then-hibernate.target— the bulletproof layer. gsettings + lid alone proved insufficient: asuspend-then-hibernatestill fired once on long idle (lid closed, on battery) and hung S3. Masking the sleep targets makes any suspend/hibernate impossible — nothing can trigger it.reboot=pciin GRUB — the default reset method does not reliably reset Apple hardware (reboot hangs at "Rebooting.");reboot=pciforces a reset via PCI port 0xcf9. Tested working on MacBookPro14,1. (If a future kernel breaks it, alternatives arereboot=efi/reboot=acpi.)- Firmware auto power-on off (optimize step 17: binary
AutoBoot=%00— the switch non-T2 Intel Macs honor — plus ASCIIauto-boot=false) — since the machine never sleeps, the way to carry it around is a plain shutdown; with the factory default it would then power itself back on at lid-open or power-connect (even inside a bag). Now it stays off until the power button.
The screen still blanks/locks after idle; the laptop just stays running. The sleep hooks
(facetimehd, brcmfmac) remain installed but are now moot while sleep is masked — they only matter
if you ever unmask the targets to experiment.
To experiment with suspend anyway (knowing it may hang):
sudo systemctl unmask sleep.target suspend.target hibernate.target suspend-then-hibernate.target
The Bluetooth chip (BCM4350C0) communicates over UART, not USB. Linux initializes it at 115200 baud,
but macOS leaves it at 3 Mbaud at shutdown — so after migrating from macOS the chip doesn't respond
and hci0 fails to initialize:
Bluetooth: hci0: command 0xfc18 tx timeout
Bluetooth: hci0: BCM: Reset failed (-110)
Fix — SMC Reset (one time only, after first boot from macOS):
- Shut down completely:
sudo systemctl poweroff -i(on this logind setupshutdown -h nowmay be refused;-iignores inhibitors) - Hold Shift left + Control left + Option left + Power simultaneously for 10 seconds
- Release all keys, then press Power to boot normally
The SMC Reset power-cycles the chip back to 115200 baud. After that Linux initializes it correctly and resets it to 115200 on every shutdown — so subsequent boots work without SMC Reset.
The most common everyday failure here isn't hardware — it's a stuck rfkill soft-block. If you ever
disable Bluetooth from GNOME (a natural reaction when a device won't pair), systemd-rfkill saves
that "blocked" state to /var/lib/systemd/rfkill/ and restores it on every boot. Result:
bluetoothctl show reports Powered: no, GNOME shows Bluetooth off, and it stays dead across
reboots.
AutoEnable=true in /etc/bluetooth/main.conf does not fix this on its own — verified with a
controlled A/B test on this hardware. systemd-rfkill restores the block before bluetoothd
starts, and a soft-blocked adapter cannot be powered on:
systemd-rfkill.service → restores soft=1 (from the saved "blocked" state)
bluetooth.service → bluetoothd starts; AutoEnable can't power a blocked adapter → Powered: no
Fix (Stage 5f, automatic): a oneshot unit bluetooth-rfkill-unblock.service runs
rfkill unblock bluetooth, ordered After=systemd-rfkill.service and Before=bluetooth.service,
so it clears the block in the window between the restore and bluetoothd. Together with
AutoEnable=true, Bluetooth comes up Powered: yes every boot regardless of any saved block.
Manual commands, if you ever need them:
rfkill list bluetooth # "Soft blocked: yes" = the trap
sudo rfkill unblock bluetooth # clear it now
bluetoothctl power on # power the adapter on
systemctl status bluetooth-rfkill-unblock.service # did the boot unblock run? (status=0/SUCCESS)
cat /var/lib/systemd/rfkill/*bluetooth # persisted state: 0 = unblocked, 1 = blockedTrade-off: Bluetooth now always comes up available at boot — a deliberate persistent disable
won't survive a reboot (disabling it within a session still works). On this hardware there's no
working suspend and the laptop runs on AC, so this costs nothing. To opt out:
sudo systemctl disable bluetooth-rfkill-unblock.service.
Note: this rfkill soft-block is a software state and is distinct from the two hardware issues above (UART baudrate / SMC reset, and the warm-reboot
Reset failed (-110)). Soft-block =Powered: nowith a clean adapter; hardware =hci0timing out orDOWN.
WiFi (BCM4350 on PCIe) and Bluetooth (BCM4350C0 on UART) are the same physical Broadcom combo
chip. A warm reboot (sudo reboot) does not fully power-cycle this chip, and after several
rapid successive reboots it can land in an unresponsive state:
- WiFi:
brcmfmac: brcmf_chip_recognition: MMIO read failed: 0xffffffff→brcmf_pcie_probe: failed(chip returns all-ones on PCIe = not responding) - Bluetooth:
command 0xfc18 tx timeout→BCM: Reset failed (-110)(chip times out on UART),hci0staysDOWN
This is a hardware-level limitation, not a software bug — none of this repo's hooks run on reboot. Observed empirically: across 8 rapid reboots, WiFi failed once (self-recovered next boot) and Bluetooth went unresponsive after the burst.
Recovery: a full power-off, not a warm reboot.
sudo systemctl poweroff -i # 'shutdown -h now' may be refused on this logind setup; -i ignores inhibitors
# wait ~10 seconds, then power onA complete power-off de-powers the Broadcom chip so it comes up clean. Re-running the setup script
does not help — there is no software fix for an unresponsive chip. (Tip: the distinction shows in
the log — error -16 / EBUSY means the chip still answered and BT worked; error -110 / timeout
means the chip is fully unresponsive and needs the power cycle.)
On 2026-07-07 this machine kernel-panicked (Kernel panic - not syncing: Fatal exception in interrupt) after ~63 minutes of uptime under normal WiFi load (streaming + Docker). The full chain
was captured in the EFI pstore crash dump (archived to /var/lib/systemd/pstore/):
DMAR: [DMA Write] Request device [02:00.0] ... PTE Write access is not set— the WiFi chip attempted a DMA write to memory it doesn't own; the IOMMU (VT-d) blocked it.brcmfmac: brcmf_msgbuf_get_pktid: Invalid packet id 48 (not in use)— the firmware↔driver message ring desynced.- A corrupted skb escaped into the network stack claiming 125 fragments in a 17-slot array
(two UBSAN out-of-bounds warnings in
skbuff.h). memcpydereferenced a garbage frag pointer → general protection fault in softirq context (irq/65-brcmf_pc) — unrecoverable in interrupt context → panic.
This is not a kernel regression: the precursor (Invalid packet id, usually paired with a DMAR
fault from the same device 02:00.0) fired 23 times between May 20 and Jul 8, across both
7.0.x and 7.1.x kernels. The driver normally recovers silently; on Jul 7 the corruption reached the
network stack before recovery. Root cause: the generic Broadcom firmware (Nov 2015, from
firmware-brcm80211) running without Apple's board-specific NVRAM/CLM data — the same gap behind
the failed to load ...MacBookPro14,1.txt/.clm_blob boot messages and the 2.4 GHz channel limit.
Mitigations (Stage 5g, automatic):
- WiFi power management off —
wifi.powersave = 2in/etc/NetworkManager/conf.d/wifi-powersave-off.conf(fewer firmware state transitions; the laptop always runs on AC, so this costs nothing). kernel.panic = 10in/etc/sysctl.d/99-panic-reboot.conf— if it ever panics again, the machine reboots itself after 10 s instead of freezing until someone holds the power button.- The IOMMU stays on. Do not use
intel_iommu=offto silence the DMAR faults — the IOMMU is exactly what blocks the chip's rogue DMA writes; without it they become silent memory corruption.
Manual checks:
iw dev wlp2s0 get power_save # expect "Power save: off"
cat /proc/sys/kernel/panic # expect 10
journalctl -g 'Invalid packet id' --no-pager # desync frequency — should drop with power save off
sudo ls -la /var/lib/systemd/pstore/ # archived EFI crash dumps, if any new panic happenedThe Apple firmware files do not exist — investigated Jul 2026 (see TODO.md): upstream
linux-firmware and every community source only carry the generic .bin; on non-T2 Macs, macOS
itself has no separate nvram/CLM files either — calibration lives in the chip's OTP (which brcmfmac
already reads) and regulatory data is embedded in Apple's own "bmac" firmware, which is
architecturally incompatible with brcmfmac. The practical impact is near zero: despite the
"limited channels" boot message, the full 5 GHz channel set (36–165) is enabled and 5 GHz networks
are visible in scans — if you're stuck on 2.4 GHz, check the router's 5 GHz band, not this
machine. The chronic desync remains a generic-firmware quirk, handled by the Stage 5g mitigations.
Debian Testing/forky — kernels 7.0.13+deb14-amd64 (forky) + 7.1.2-1~exp1 (experimental, GRUB
default) — July 2026 (full hardware stack working, audio included).
Kernel note: the audio regression on
7.0.10+deb14-amd64(CS8409 codec failed to register a sound card — in-tree HDA generic-parser change) was fixed upstream in7.0.12; audio works again. Kernels currently installed:7.0.13(forky, fallback) +7.1.2(experimental, default). Everything else (camera, WiFi, VA-API, RAPL/thermal, touchpad) works across both.