aboutsummaryrefslogtreecommitdiff
path: root/scripts/zz-bluetooth-resume
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/zz-bluetooth-resume')
-rwxr-xr-xscripts/zz-bluetooth-resume86
1 files changed, 86 insertions, 0 deletions
diff --git a/scripts/zz-bluetooth-resume b/scripts/zz-bluetooth-resume
new file mode 100755
index 0000000..4273339
--- /dev/null
+++ b/scripts/zz-bluetooth-resume
@@ -0,0 +1,86 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-3.0-or-later
+# zz-bluetooth-resume - put bluetooth back after a sleep cycle.
+#
+# A systemd-sleep hook. Two things break bluetooth across sleep on a TLP
+# laptop, and nothing else on the machine fixes either one.
+#
+# 1. The rfkill soft-block is not restored. systemd-rfkill would do it, and
+# it is masked here deliberately -- it fights TLP's radio handling, so
+# configure_tlp_power masks it and TLP owns radios instead. TLP's own
+# sleep hook runs `tlp resume`, but its setting is
+# DEVICES_TO_ENABLE_ON_STARTUP: startup, not resume. TLP has no ON_RESUME
+# at all, so the resume edge has no owner. WiFi survives only because
+# NetworkManager unblocks itself; bluetooth has no equivalent.
+#
+# 2. The controller comes back wedged from a hibernate. It reports powered
+# and unblocked while scanning finds nothing whatever -- zero devices
+# where the same room gave seventeen a minute later -- and bluetoothd
+# logs "Failed to set mode" and "Failed to add device <mac>" at the
+# instant of resume. Reloading btusb clears it.
+#
+# Both observed on velox 2026-08-21, on the first suspend-then-hibernate cycle
+# after hibernate was switched back on. The second symptom is why unblocking
+# alone is not enough: rfkill was cleared by hand and scanning still returned
+# nothing until the driver was reloaded.
+#
+# The hook re-asserts TLP's own declared intent rather than inventing a policy.
+# A machine whose TLP config does not ask for bluetooth keeps it off, which is
+# what stops this from overriding a deliberate block at every wakeup.
+#
+# The zz- prefix orders it after TLP's own hook, so `tlp resume` has finished
+# before this runs.
+#
+# Test seams: BTR_RFKILL, BTR_MODPROBE, BTR_TLP_CONF, BTR_TLP_CONF_DIR,
+# BTR_SETTLE (seconds to wait between driver unload and load).
+
+set -u
+
+RFKILL="${BTR_RFKILL:-rfkill}"
+MODPROBE="${BTR_MODPROBE:-modprobe}"
+TLP_CONF="${BTR_TLP_CONF:-/etc/tlp.conf}"
+TLP_CONF_DIR="${BTR_TLP_CONF_DIR:-/etc/tlp.d}"
+SETTLE="${BTR_SETTLE:-1}"
+
+# post only. The pre phase has nothing to do, and acting there would fight the
+# suspend it is about to run.
+[ "${1:-}" = "post" ] || exit 0
+
+# Does TLP ask for bluetooth on this machine? Comments are stripped first, so a
+# commented-out example in the stock config cannot be read as a policy. Both
+# the main file and any drop-in count, and the last assignment wins the same
+# way TLP itself resolves them.
+wants_bluetooth() {
+ cat "$TLP_CONF" "$TLP_CONF_DIR"/*.conf 2>/dev/null \
+ | sed 's/#.*//' \
+ | awk -F= '/DEVICES_TO_ENABLE_ON_STARTUP/ { v = $2 } END { print v }' \
+ | tr -d '"' \
+ | tr ' ' '\n' \
+ | grep -qx "bluetooth"
+}
+
+wants_bluetooth || exit 0
+
+# The wedge follows a hibernate, which reinitialises the controller from a
+# saved image. A plain suspend brings USB back intact, so reloading there would
+# tear down a working adapter for nothing.
+#
+# suspend-then-hibernate reports that name whether or not it reached the
+# hibernate stage, so this reloads on a cycle that only suspended. That is the
+# cheap side of the trade: a couple of seconds against an adapter that answers
+# nothing until someone notices and reloads it by hand.
+case "${2:-}" in
+ hibernate|suspend-then-hibernate)
+ "$MODPROBE" -r btusb 2>/dev/null || true
+ [ "$SETTLE" = "0" ] || sleep "$SETTLE"
+ "$MODPROBE" btusb 2>/dev/null || true
+ ;;
+esac
+
+# After the reload, not before: a freshly loaded btusb can come up soft-blocked
+# and would undo an earlier unblock.
+"$RFKILL" unblock bluetooth 2>/dev/null || true
+
+# Never fail. systemd-sleep logs a failing hook, and that noise outlives the
+# cause it describes; nothing here is worth alarming a resume over.
+exit 0