Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 65 additions & 20 deletions skeleton/SYSTEM/tg5040/bin/suspend
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,64 @@ set -uo pipefail
set +e
exec 0<&-

#logfile="/mnt/SDCARD/.userdata/tg5040/logs/suspend_$(date +%Y%m%d_%H%M%S).log"
#exec >> "$logfile" 2>&1
logfile="/mnt/SDCARD/.userdata/tg5040/logs/suspend_$(date +%Y%m%d_%H%M%S).log"
exec >> "$logfile" 2>&1
Comment thread
frysee marked this conversation as resolved.

wifid_running=
bluetoothd_running=

sleep_retval=
sleep_retval=1

asound_state_dir=/tmp/asound-suspend

log_wakeup_sources() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be removed before we merge.

local label="$1"
>&2 echo " [$label] wakeup_count: $(cat /sys/power/wakeup_count 2>/dev/null)"
if [ -f /sys/power/pm_wakeup_irq ]; then
>&2 echo " [$label] last wakeup IRQ: $(cat /sys/power/pm_wakeup_irq 2>/dev/null)"
fi
if [ -f /sys/power/wake_lock ]; then
local locks
locks=$(cat /sys/power/wake_lock 2>/dev/null)
[ -n "$locks" ] && >&2 echo " [$label] active wakelocks: $locks"
fi
if [ -r /sys/kernel/debug/wakeup_sources ]; then
>&2 echo " [$label] wakeup sources (wakeup_count>0 | active | prevent_suspend>0):"
awk 'NR==1 || $4 != "0" || $6 != "0" || $10 != "0"' \
/sys/kernel/debug/wakeup_sources 2>/dev/null | head -20 >&2
fi
>&2 echo " [$label] dmesg (last 10 lines):"
dmesg 2>/dev/null | tail -10 >&2
}

before() {
>&2 echo "Preparing for suspend..."

# Run pre-sleep hooks synchronously before services are stopped.
# Subshell ensures a crashing hook cannot block suspend.
( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true

>&2 echo "Saving mixer state..."
mkdir -p "$asound_state_dir"
alsactl --file "$asound_state_dir/asound.state.pre" store || true
log_wakeup_sources "before"

if pgrep bluetoothd; then
bluetoothd_running=1
>&2 echo "Stopping bluetoothd..."
/etc/bluetooth/bt_init.sh stop || true
fi
# Enable kernel PM debug messages so driver-level suspend rejections
# appear in dmesg.
echo 1 >/sys/power/pm_debug_messages 2>/dev/null || true

#>&2 echo "Saving mixer state..."
#mkdir -p "$asound_state_dir"
#alsactl --file "$asound_state_dir/asound.state.pre" store || true

#if pgrep bluetoothd; then
# bluetoothd_running=1
# >&2 echo "Stopping bluetoothd..."
# /etc/bluetooth/bt_init.sh stop || true
#fi

if pgrep wpa_supplicant; then
wifid_running=1
>&2 echo "Stopping wpa_supplicant..."
/etc/wifi/wifi_init.sh stop || true
rmmod xradio_wlan > /dev/null 2>&1 || true
fi
}

Expand All @@ -45,14 +72,21 @@ after() {

if [ -n "$wifid_running" ]; then
>&2 echo "Starting wpa_supplicant..."
modprobe xradio_wlan > /dev/null 2>&1 || true
# wait for wlan0 to appear before starting wpa_supplicant
for i in $(seq 1 10); do
if ip link show wlan0 > /dev/null 2>&1; then
break
fi
sleep 1
done
/etc/wifi/wifi_init.sh start || true
fi

if [ -n "$bluetoothd_running" ]; then
>&2 echo "Starting bluetoothd..."
/etc/bluetooth/bt_init.sh start || true
fi

#if [ -n "$bluetoothd_running" ]; then
# >&2 echo "Starting bluetoothd..."
# /etc/bluetooth/bt_init.sh start || true
#fi
# >&2 echo "Restoring mixer state..."
# alsactl --file "$asound_state_dir/asound.state.post" store || true
# alsactl --file "$asound_state_dir/asound.state.pre" restore || true
Expand All @@ -61,10 +95,21 @@ after() {
before

>&2 echo "Suspending..."
sleep_max_tries=5
sleep_max_tries=15
for i in $(seq 1 $sleep_max_tries); do
>&2 echo "Deep sleep attempt $i of $sleep_max_tries"

# Synchronize with wakeup events: read wakeup_count and write it back.
# If a wakeup event occurred since reading, the write fails and we retry.
wakeup_count=$(cat /sys/power/wakeup_count 2>/dev/null)
if [ -n "$wakeup_count" ]; then
if ! echo "$wakeup_count" >/sys/power/wakeup_count 2>/dev/null; then
>&2 echo "Deep sleep: wakeup event detected before sleep (wakeup_count=$wakeup_count), retrying in 1 second"
sleep 1
continue
fi
fi

sleep_time=$(date +%s)
echo mem >/sys/power/state
sleep_retval=$?
Expand All @@ -89,10 +134,10 @@ for i in $(seq 1 $sleep_max_tries); do
sleep_retval=0
break
fi


>&2 echo "Deep sleep failed: retrying in 3 seconds"
sleep 3
>&2 echo "Deep sleep failed: retrying in 1 second"
log_wakeup_sources "attempt $i"
sleep 1
done

# Resume services in background to reduce UI latency
Expand Down
24 changes: 24 additions & 0 deletions skeleton/SYSTEM/tg5040/etc/bluetooth/bt_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ start_bt() {
start_hci_attach
fi

# Allow headsets to auto-reconnect without user re-pairing.
# XRadio BT firmware sets store_hint=0, so link keys are never
# persisted; JustWorksRepairing=always lets earbuds re-initiate
# the bond from their side after a reboot.
if ! grep -q 'JustWorksRepairing = always' /etc/bluetooth/main.conf 2>/dev/null; then
sed -i 's/#JustWorksRepairing = never/JustWorksRepairing = always/' /etc/bluetooth/main.conf 2>/dev/null
fi

# Start bluetooth daemon if not running
d=`ps | grep bluetoothd | grep -v grep`
[ -z "$d" ] && {
Expand All @@ -69,6 +77,22 @@ start_bt() {

# Set adapter name
bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null

# Proactively reconnect trusted A2DP audio devices after BT restart.
# Some headphones (e.g. Sony) won't fall back to JustWorks re-pairing
# when authentication fails; they expect the host to initiate using the
# stored link key. Runs in background to avoid blocking startup.
{
sleep 5
for dev_dir in /var/lib/bluetooth/*/; do
for paired_dir in "${dev_dir}"*/; do
[ -f "${paired_dir}info" ] || continue
grep -q "0000110b" "${paired_dir}info" || continue
mac=$(basename "${paired_dir%/}")
bluetoothctl connect "$mac" >/dev/null 2>&1
done
done
} &
}

}
Expand Down
66 changes: 52 additions & 14 deletions skeleton/SYSTEM/tg5050/bin/suspend
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ set -uo pipefail
set +e
exec 0<&-

#logfile="/mnt/SDCARD/.userdata/tg5040/logs/suspend_$(date +%Y%m%d_%H%M%S).log"
#exec >> "$logfile" 2>&1
logfile="/mnt/SDCARD/.userdata/tg5050/logs/suspend_$(date +%Y%m%d_%H%M%S).log"
exec >> "$logfile" 2>&1

wifid_running=
bluetoothd_running=
Expand All @@ -13,22 +13,48 @@ sleep_retval=

asound_state_dir=/tmp/asound-suspend

log_wakeup_sources() {
local label="$1"
>&2 echo " [$label] wakeup_count: $(cat /sys/power/wakeup_count 2>/dev/null)"
if [ -f /sys/power/pm_wakeup_irq ]; then
>&2 echo " [$label] last wakeup IRQ: $(cat /sys/power/pm_wakeup_irq 2>/dev/null)"
fi
if [ -f /sys/power/wake_lock ]; then
local locks
locks=$(cat /sys/power/wake_lock 2>/dev/null)
[ -n "$locks" ] && >&2 echo " [$label] active wakelocks: $locks"
fi
if [ -r /sys/kernel/debug/wakeup_sources ]; then
>&2 echo " [$label] wakeup sources (wakeup_count>0 | active | prevent_suspend>0):"
awk 'NR==1 || $4 != "0" || $6 != "0" || $10 != "0"' \
/sys/kernel/debug/wakeup_sources 2>/dev/null | head -20 >&2
fi
>&2 echo " [$label] dmesg (last 10 lines):"
dmesg 2>/dev/null | tail -10 >&2
}

before() {
>&2 echo "Preparing for suspend..."

# Run pre-sleep hooks synchronously before services are stopped.
# Subshell ensures a crashing hook cannot block suspend.
( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true

>&2 echo "Saving mixer state..."
mkdir -p "$asound_state_dir"
alsactl --file "$asound_state_dir/asound.state.pre" store || true
log_wakeup_sources "before"

if pgrep bluetoothd; then
bluetoothd_running=1
>&2 echo "Stopping bluetoothd..."
$SYSTEM_PATH/etc/bluetooth/bt_init.sh stop || true
fi
# Enable kernel PM debug messages so driver-level suspend rejections
# appear in dmesg.
echo 1 >/sys/power/pm_debug_messages 2>/dev/null || true

#>&2 echo "Saving mixer state..."
#mkdir -p "$asound_state_dir"
#alsactl --file "$asound_state_dir/asound.state.pre" store || true

#if pgrep bluetoothd; then
# bluetoothd_running=1
# >&2 echo "Stopping bluetoothd..."
# $SYSTEM_PATH/etc/bluetooth/bt_init.sh stop || true
#fi

if pgrep wpa_supplicant; then
wifid_running=1
Expand All @@ -48,10 +74,10 @@ after() {
$SYSTEM_PATH/etc/wifi/wifi_init.sh start || true
fi

if [ -n "$bluetoothd_running" ]; then
>&2 echo "Starting bluetoothd..."
/etc/bluetooth/bt_init.sh start || true
fi
#if [ -n "$bluetoothd_running" ]; then
# >&2 echo "Starting bluetoothd..."
# /etc/bluetooth/bt_init.sh start || true
#fi

# >&2 echo "Restoring mixer state..."
# alsactl --file "$asound_state_dir/asound.state.post" store || true
Expand All @@ -65,6 +91,17 @@ sleep_max_tries=5
for i in $(seq 1 $sleep_max_tries); do
Comment thread
frysee marked this conversation as resolved.
>&2 echo "Deep sleep attempt $i of $sleep_max_tries"

# Synchronize with wakeup events: read wakeup_count and write it back.
# If a wakeup event occurred since reading, the write fails and we retry.
wakeup_count=$(cat /sys/power/wakeup_count 2>/dev/null)
if [ -n "$wakeup_count" ]; then
if ! echo "$wakeup_count" >/sys/power/wakeup_count 2>/dev/null; then
>&2 echo "Deep sleep: wakeup event detected before sleep (wakeup_count=$wakeup_count), retrying in 1 second"
sleep 1
continue
fi
fi

echo mem >/sys/power/state
sleep_retval=$?

Expand All @@ -74,6 +111,7 @@ for i in $(seq 1 $sleep_max_tries); do
fi

>&2 echo "Deep sleep failed: retrying in 3 seconds"
log_wakeup_sources "attempt $i"
sleep 3
done

Expand Down
24 changes: 24 additions & 0 deletions skeleton/SYSTEM/tg5050/etc/bluetooth/bt_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ start_bt() {
start_hci_attach
fi

# Allow headsets to auto-reconnect without user re-pairing.
# Some BT controller firmware never persists link keys to disk;
# JustWorksRepairing=always lets earbuds re-initiate the bond
# from their side after a reboot without user interaction.
if ! grep -q 'JustWorksRepairing = always' /etc/bluetooth/main.conf 2>/dev/null; then
sed -i 's/#JustWorksRepairing = never/JustWorksRepairing = always/' /etc/bluetooth/main.conf 2>/dev/null
fi

# Start bluetooth daemon if not running
d=`ps | grep bluetoothd | grep -v grep`
[ -z "$d" ] && {
Expand All @@ -77,6 +85,22 @@ start_bt() {

# Set adapter name
bluetoothctl system-alias "$DEVICE_NAME" 2>/dev/null

# Proactively reconnect trusted A2DP audio devices after BT restart.
# Some headphones (e.g. Sony) won't fall back to JustWorks re-pairing
# when authentication fails; they expect the host to initiate using the
# stored link key. Runs in background to avoid blocking startup.
{
sleep 5
for dev_dir in /var/lib/bluetooth/*/; do
for paired_dir in "${dev_dir}"*/; do
[ -f "${paired_dir}info" ] || continue
grep -q "0000110b" "${paired_dir}info" || continue
mac=$(basename "${paired_dir%/}")
bluetoothctl connect "$mac" >/dev/null 2>&1
done
done
} &
}

}
Expand Down
38 changes: 37 additions & 1 deletion workspace/all/audiomon/audiomon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum DeviceType {

bool use_syslog = false;
bool running = true;
static std::string connected_a2dp_mac;

void log(const std::string& msg) {
if (use_syslog) syslog(LOG_INFO, "%s", msg.c_str());
Expand Down Expand Up @@ -102,6 +103,22 @@ void clearAudioFile() {
}
}

void recoverPreviousConfig() {
std::ifstream f(AUDIO_FILE);
if (!f) return;

std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
auto pos = content.find("defaults.bluealsa.device \"");
if (pos == std::string::npos) return;

pos += strlen("defaults.bluealsa.device \"");
auto end = content.find("\"", pos);
if (end == std::string::npos) return;

connected_a2dp_mac = content.substr(pos, end - pos);
log("Restored BT state from .asoundrc: " + connected_a2dp_mac);
}

std::string pathToMac(const std::string& path) {
auto pos = path.find("dev_");
if (pos == std::string::npos) return "";
Expand Down Expand Up @@ -203,6 +220,8 @@ void handleDeviceConnected(DBusConnection* conn, const std::string& path) {
std::string mac = pathToMac(path);
if (hasUUID(conn, path, UUID_A2DP)) {
log("Audio device connected: " + mac);
connected_a2dp_mac = mac;
usleep(500000); // Wait a bit for the device to be fully ready before writing config
writeAudioFile(mac, DEVICE_BLUETOOTH);
SetAudioSink(AUDIO_SINK_BLUETOOTH);
} else {
Expand All @@ -212,9 +231,22 @@ void handleDeviceConnected(DBusConnection* conn, const std::string& path) {

void handleDeviceDisconnected(DBusConnection* conn, const std::string& path) {
std::string mac = pathToMac(path);
if (hasUUID(conn, path, UUID_A2DP)) {
// Use cached MAC rather than querying BlueZ: after an abrupt power-off the
// device's service cache may already be gone, causing hasUUID to return false
// and silently skip the audio switch-back.
if (hasUUID(conn, path, UUID_A2DP) || (!connected_a2dp_mac.empty() && mac == connected_a2dp_mac)) {
log("Audio device disconnected: " + mac);
connected_a2dp_mac.clear();
clearAudioFile();
// In case BT just disconnected, chances are that bluealsa is throwing weird PCM errors
// on recovering the connection. It seems the only way to avoid this is to straight up kill and restart bluealsa...‚
system("killall bluealsa");
for (int i = 0; i < 20; i++) {
if (system("pidof bluealsa > /dev/null 2>&1") != 0) break;
usleep(100000); // 100ms
}
system("bluealsa -p a2dp-source &");

// TODO: we could maintain a stack here, if USBC was connected before and restore that instead
SetAudioSink(AUDIO_SINK_DEFAULT);
}
Expand Down Expand Up @@ -286,6 +318,10 @@ int main(int argc, char* argv[]) {
// This will be updated as soon as something connects
SetAudioSink(AUDIO_SINK_DEFAULT);

// Recover any existing audio config from previous run (e.g. after a restart)
// This ensures we don't lose audio output if the daemon crashes or is restarted while a device is connected
recoverPreviousConfig();

signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);

Expand Down
Loading
Loading