Skip to content

Repository files navigation

scripts

Setup scripts for MacBook Pro 13" 2017 (A1708) running Debian Testing.

Prerequisites — fresh Debian Testing install

Before running this script:

  1. Install Debian Testing with non-free firmware enabled — the Debian installer must include firmware-iwlwifi, firmware-brcm80211 and friends. The graphical installer offers "non-free-firmware" as a checkbox; the netinst ISO from cdimage.debian.org/cdimage/unofficial/non-free/firmware/ has them bundled. Without these, WiFi/Bluetooth will not work even after this script runs.
  2. Boot into GNOME, open a terminal, then install git:
    sudo apt update
    sudo apt install -y git
  3. (Optional) If migrating from macOS: perform an SMC Reset once (see Bluetooth section below) — otherwise Bluetooth will stay broken even after the script runs.

Usage

git clone https://github.com/vrilutza/scripts.git
cd scripts
chmod +x macbook-debian-setup.sh
./macbook-debian-setup.sh
sudo reboot

After reboot — quick stability check

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 statussnd_hda_macbookpro and facetimehd should show installed

Diagnostics & monitoring per subsystem

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.

Audio (Cirrus CS8409)

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.10 briefly broke the CS8409 codec (no sound card; an in-tree HDA generic-parser change, not a DKMS rebuild failure). It was fixed upstream in 7.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.

Camera (FaceTime HD)

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.

System fixes (backlight, suspend, lid behavior)

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 false

Video acceleration (VA-API)

LIBVA_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.

Touchpad UX

# 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 touchpad

To 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".

Thermal management

# 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 (change 22000000 to e.g. 18000000), then sudo 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:

  1. v1 — tmpfiles.d: wrote RAPL limits at early boot. Failed because intel_rapl_msr loads via udev later in boot and overwrote our values with Apple EFI defaults (100W / 125W).
  2. v2 — .service with ConditionPathExists: 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, thermald itself initialised without RAPL on the same failed boots (logged NO RAPL sysfs present) — and its 4-second polling does not rediscover cooling devices.
  3. v3 — .path unit + .service: also failed. .path units use inotify, and sysfs does not reliably emit inotify creation events — so the trigger only fired when the file already existed at .path start time (still a race). Plus After=thermald.service on a .path unit created an ordering cycle (paths.target is ordered before basic.target, thermald after).
  4. v4 (current) — udev rule: ACTION=="add", SUBSYSTEM=="powercap", KERNEL=="intel-rapl:0" writes the limits directly via ATTR{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 that try-restarts thermald so it reinitialises and discovers RAPL. Verified empirically: udevadm trigger --action=add re-applies 100W→22W, and thermald restarted after RAPL is set no longer logs NO 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 floorfan1_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.

General health — anything failing this boot?

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 avg

If systemctl --failed lists anything, drill in with systemctl status <unit> and journalctl -u <unit> for that specific unit.

Scripts

macbook-debian-setup.sh

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.

macbook-debian-optimize.sh

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.

Touchpad — no patch needed

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.

Hardware

  • 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)

Suspend / sleep / hibernation — why it's blocked, not fixed

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.

Why S3 (suspend-to-RAM) can't be fixed

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.

Why hibernation (S4, suspend-to-disk) won't save you either

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:

  1. It inherits the same Apple resume path — restoring from hibernation still goes through the Apple EFI + NVMe bring-up that's unreliable.
  2. It needs swap ≥ RAM set up as a resume device, plus a resume= kernel param — extra config that this minimal setup doesn't ship.
  3. 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.

What the script does instead (Stage 5)

Since sleep is unfixable, the script prevents it so the machine can never hang trying:

  • GNOME sleep-inactive-ac-type / sleep-inactive-battery-typenothing (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: a suspend-then-hibernate still 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=pci in GRUB — the default reset method does not reliably reset Apple hardware (reboot hangs at "Rebooting."); reboot=pci forces a reset via PCI port 0xcf9. Tested working on MacBookPro14,1. (If a future kernel breaks it, alternatives are reboot=efi / reboot=acpi.)
  • Firmware auto power-on off (optimize step 17: binary AutoBoot=%00 — the switch non-T2 Intel Macs honor — plus ASCII auto-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

Bluetooth

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):

  1. Shut down completely: sudo systemctl poweroff -i (on this logind setup shutdown -h now may be refused; -i ignores inhibitors)
  2. Hold Shift left + Control left + Option left + Power simultaneously for 10 seconds
  3. 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.

"Bluetooth won't turn on at all" — rfkill soft-block persisted across boots (fixed by Stage 5f)

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 = blocked

Trade-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: no with a clean adapter; hardware = hci0 timing out or DOWN.

Warm reboot can leave WiFi/Bluetooth unresponsive

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: 0xffffffffbrcmf_pcie_probe: failed (chip returns all-ones on PCIe = not responding)
  • Bluetooth: command 0xfc18 tx timeoutBCM: Reset failed (-110) (chip times out on UART), hci0 stays DOWN

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 on

A 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.)

WiFi (BCM4350) — chronic firmware desync & the July 2026 kernel panic (mitigated by Stage 5g)

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/):

  1. 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.
  2. brcmfmac: brcmf_msgbuf_get_pktid: Invalid packet id 48 (not in use) — the firmware↔driver message ring desynced.
  3. 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).
  4. memcpy dereferenced 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 offwifi.powersave = 2 in /etc/NetworkManager/conf.d/wifi-powersave-off.conf (fewer firmware state transitions; the laptop always runs on AC, so this costs nothing).
  • kernel.panic = 10 in /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=off to 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 happened

The 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.

Tested on

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 in 7.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.

About

Setup scripts for MacBook on Linux (Debian) — drivers, optimization, dev-tools

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages