Compare commits
12 changed files with 7 additions and 539 deletions
|
|
@ -1,91 +0,0 @@
|
||||||
# Offline charging mode for the FP6 (Android-style charger boot), built
|
|
||||||
# 2026-08-24 — source of truth + design + test evidence: fp6 repo
|
|
||||||
# work/charging-mode/ and journal/power/.
|
|
||||||
#
|
|
||||||
# The FP6 ABL appends androidboot.mode=charger when a charger insertion
|
|
||||||
# powers the phone on. Without this package, that boot goes all the way to
|
|
||||||
# the full UI and at low battery sags back into shutdown — the dead-battery
|
|
||||||
# bootloop. With it, a systemd generator diverts charger boots to a minimal
|
|
||||||
# charging.target: ADSP-owned charging runs (incl. USB-PD), panel dark
|
|
||||||
# (splash killed ~6-10 s in via the initramfs hook), modem/cdsp remoteprocs
|
|
||||||
# stopped, radios/sensors suppressed, CPU capped. Verified net >= breakeven
|
|
||||||
# even on a 100 mA-classified workstation USB port; strongly positive on
|
|
||||||
# real chargers.
|
|
||||||
#
|
|
||||||
# User interactions in charging mode:
|
|
||||||
# power key reboot into a normal boot (warm reboot drops the ABL flag)
|
|
||||||
# volume-up toggle on-screen battery status (%/rate/input/ETA)
|
|
||||||
# volume-down enable ssh over the USB cable (debug; static 172.16.42.1)
|
|
||||||
# unplug orderly poweroff
|
|
||||||
#
|
|
||||||
# Suspend duty-cycling exists behind /etc/charging-mode/enable-suspend but
|
|
||||||
# is NOT shipped enabled: resume from deep suspend intermittently kills the
|
|
||||||
# UFS link (tracked in journal/power, track 3). Do not enable until fixed.
|
|
||||||
# Escape hatch: touch /etc/charging-mode/disable to make the generator
|
|
||||||
# inert. Units install under /etc/systemd/system (same paths the dev phone
|
|
||||||
# runs, byte-identical to the tested deployment).
|
|
||||||
maintainer="Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>"
|
|
||||||
pkgname=fp6-charging-mode
|
|
||||||
pkgver=1
|
|
||||||
pkgrel=0
|
|
||||||
pkgdesc="Fairphone 6 offline charging mode (charger-boot detection + minimal charging target)"
|
|
||||||
url="https://forgejo.catcrafts.net/Catcrafts/fp6-img"
|
|
||||||
arch="noarch"
|
|
||||||
license="MIT"
|
|
||||||
options="!check"
|
|
||||||
source="
|
|
||||||
charging-mode-generator
|
|
||||||
charging.target
|
|
||||||
charging-monitor.service
|
|
||||||
charging-monitor.sh
|
|
||||||
charging-usbnet.service
|
|
||||||
charging-usbnet.sh
|
|
||||||
charging-keys.service
|
|
||||||
charging-keys.sh
|
|
||||||
charging-mode-suppress.conf
|
|
||||||
charging-mode-initfs-hook.sh
|
|
||||||
"
|
|
||||||
|
|
||||||
package() {
|
|
||||||
install -Dm755 "$srcdir"/charging-mode-generator \
|
|
||||||
"$pkgdir"/usr/lib/systemd/system-generators/charging-mode-generator
|
|
||||||
install -Dm644 "$srcdir"/charging.target \
|
|
||||||
"$pkgdir"/etc/systemd/system/charging.target
|
|
||||||
install -Dm644 "$srcdir"/charging-monitor.service \
|
|
||||||
"$pkgdir"/etc/systemd/system/charging-monitor.service
|
|
||||||
install -Dm755 "$srcdir"/charging-monitor.sh \
|
|
||||||
"$pkgdir"/usr/lib/charging-mode/charging-monitor.sh
|
|
||||||
install -Dm644 "$srcdir"/charging-usbnet.service \
|
|
||||||
"$pkgdir"/etc/systemd/system/charging-usbnet.service
|
|
||||||
install -Dm755 "$srcdir"/charging-usbnet.sh \
|
|
||||||
"$pkgdir"/usr/lib/charging-mode/charging-usbnet.sh
|
|
||||||
install -Dm644 "$srcdir"/charging-keys.service \
|
|
||||||
"$pkgdir"/etc/systemd/system/charging-keys.service
|
|
||||||
install -Dm755 "$srcdir"/charging-keys.sh \
|
|
||||||
"$pkgdir"/usr/lib/charging-mode/charging-keys.sh
|
|
||||||
# radios/sensors out of charging mode: conditional drop-ins, inert in
|
|
||||||
# normal boots (the generator creates the /run/charging-mode marker
|
|
||||||
# only on charger boots). Drop-ins for units a variant image may not
|
|
||||||
# ship are harmless.
|
|
||||||
for u in bluetooth iio-sensor-proxy hkdm; do
|
|
||||||
install -Dm644 "$srcdir"/charging-mode-suppress.conf \
|
|
||||||
"$pkgdir"/etc/systemd/system/$u.service.d/zz-charging-mode.conf
|
|
||||||
done
|
|
||||||
# early splash/panel kill — lands in the initramfs via the
|
|
||||||
# postmarketos-initramfs trigger on /usr/share/mkinitfs
|
|
||||||
install -Dm755 "$srcdir"/charging-mode-initfs-hook.sh \
|
|
||||||
"$pkgdir"/usr/share/mkinitfs/hooks/20-charging-mode.sh
|
|
||||||
}
|
|
||||||
|
|
||||||
sha512sums="
|
|
||||||
28027696cb1f5d2b1f5d1e09d7f381e89c223b7f10e54f7f8f6e1d75de2b0640b4db81a14263deda69c9c1d992614df0449f9078fec654e56c67c659649301c6 charging-mode-generator
|
|
||||||
e0640c43c7900269000fb0854f8a113258572ee35d6072e96c5e919329a785de9a17308002099343046d2c5657638780708c34728d1445f82a27505e329fd55a charging.target
|
|
||||||
1b330faa2dbd9e4d1505f58db7883f52de656c51aac93012c3e91aed7797fccada9eb67b116a9d932754588475e282fa6fb57287bb6681431e50b4a51aa7003a charging-monitor.service
|
|
||||||
d05b1a5e3a13de4cfa0d09fb87842da740d13d4f557894adab4d9f0d9d3ea47d61d17fd75beee3fddf2297a64b7d9d0e0ebdefa76952df4445daf169946428e5 charging-monitor.sh
|
|
||||||
504c9cf3bb0d511851bc82ae5cf715b9be6c289ff9611c7387b787d68404254a702af1fc705376f6e72314bf17094acb61a5d66279a3bcc5fbda2f2e9d1f2454 charging-usbnet.service
|
|
||||||
89f3cd78969787cfccc4da26dadacc395d51f5d2e0f4e9565988eb4a34bb98c2b3ca55550d21229e43bc1abe0191394ea0c9b0b68f7f39f5dc1a5e05ca8340e7 charging-usbnet.sh
|
|
||||||
24cda032576251a7ac2ebc67a4bb1e40b8e245a6280cba083a7d8a2b76513fbb728f1f78ad62fc95f77b12880c80d963226b9ffc049997487e4fd7619b7dc8a4 charging-keys.service
|
|
||||||
8905a1dfb7a73a846fe6b9f4c530a28b326dded44e992c4f6ca1976250b966f231d67639190570a055628f01637debd951dfb7bf937640364424ed97afe517e2 charging-keys.sh
|
|
||||||
127cbcd35a581c048eb9e9c31df9044a3a62c3223914032f413e58da58422c3871faa09b4ac5ecd8f4237b33a448c43a91b181830faa62eb753d7111d1efa66b charging-mode-suppress.conf
|
|
||||||
1e9ab3c137cda062de1cbb7eea59677337a6b59eeed7e7f74bc7826146af8c6d26e0ac394984d4ccec3b0a1017f196f09f3fdba1728218e3c28022892c06685d charging-mode-initfs-hook.sh
|
|
||||||
"
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Charging-mode key handler (volume-down enables ssh)
|
|
||||||
# Only ever pulled by charging.target.
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=exec
|
|
||||||
ExecStart=/usr/lib/charging-mode/charging-keys.sh
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# Charging-mode key handler (r4):
|
|
||||||
# volume-down (pmic_resin) -> enable ssh debug mode (inhibits suspend)
|
|
||||||
# volume-up (gpio-keys) -> toggle the on-screen battery status display
|
|
||||||
# The display costs nothing while off: panel + pipeline stay powered down
|
|
||||||
# and suspend cycling continues. While visible, /run/charging-mode-display
|
|
||||||
# inhibits suspend (the monitor checks it); toggling off (or the safety
|
|
||||||
# timeout) blanks everything again and cycling resumes.
|
|
||||||
# The power key is taken (logind: reboot to normal boot).
|
|
||||||
|
|
||||||
log() { echo "charging-keys: $*"; }
|
|
||||||
|
|
||||||
BAT=/sys/class/power_supply/qcom-battmgr-bat
|
|
||||||
USB=/sys/class/power_supply/qcom-battmgr-usb
|
|
||||||
DISPLAY_MARKER=/run/charging-mode-display
|
|
||||||
DISPLAY_TIMEOUT=600 # safety auto-off, seconds
|
|
||||||
|
|
||||||
find_dev() {
|
|
||||||
for e in /sys/class/input/event*; do
|
|
||||||
n=$(cat "$e/device/name" 2>/dev/null)
|
|
||||||
case "$n" in
|
|
||||||
$1) echo "/dev/input/${e##*/}"; return 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
read_key() {
|
|
||||||
# echoes the key code of a key RELEASE event (or nothing). Release, not
|
|
||||||
# press: when a key wakes the phone from suspend, the press can be
|
|
||||||
# consumed as the wake event while the release is still delivered — so
|
|
||||||
# triggering on release works both awake and as a wake key.
|
|
||||||
# input_event on arm64: u64 sec, u64 usec, u16 type, u16 code, s32 value
|
|
||||||
# od -tu2 words: 1-8 time, 9 type, 10 code, 11-12 value (lo, hi)
|
|
||||||
_d="$1"
|
|
||||||
set -- $(dd if="$_d" bs=24 count=1 2>/dev/null | od -An -v -tu2)
|
|
||||||
[ $# -ge 11 ] || return 1
|
|
||||||
[ "$9" = "1" ] && [ "${11}" = "0" ] && echo "${10}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
enable_ssh() {
|
|
||||||
[ -e /run/charging-mode-ssh ] && return
|
|
||||||
: > /run/charging-mode-ssh
|
|
||||||
log "volume-down pressed: enabling usbnet+sshd, suspend cycling inhibited"
|
|
||||||
systemctl start systemd-user-sessions.service charging-usbnet.service sshd.service
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_status() {
|
|
||||||
cap=$(cat "$BAT/capacity" 2>/dev/null)
|
|
||||||
cur=$(cat "$BAT/current_now" 2>/dev/null)
|
|
||||||
cc=$(cat "$BAT/charge_counter" 2>/dev/null)
|
|
||||||
full=$(cat "$BAT/charge_full" 2>/dev/null)
|
|
||||||
[ -n "$full" ] || full=4415000
|
|
||||||
uv=$(cat "$USB/voltage_now" 2>/dev/null)
|
|
||||||
ui=$(cat "$USB/current_now" 2>/dev/null)
|
|
||||||
eta=$(awk -v f="$full" -v c="$cc" -v i="$cur" 'BEGIN {
|
|
||||||
if (i > 10000) { h = (f - c) / i; printf "%dh%02dm", int(h), (h - int(h)) * 60 }
|
|
||||||
else printf "--"
|
|
||||||
}' 2>/dev/null)
|
|
||||||
{
|
|
||||||
printf '\033[2J\033[H\n\n'
|
|
||||||
printf ' CHARGING %s%%\n\n' "$cap"
|
|
||||||
printf ' battery %d mA\n' "$(( ${cur:-0} / 1000 ))"
|
|
||||||
printf ' input %d mV %d mA\n' "$(( ${uv:-0} / 1000 ))" "$(( ${ui:-0} / 1000 ))"
|
|
||||||
printf ' full in %s\n' "$eta"
|
|
||||||
} > /dev/tty0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
display_off() {
|
|
||||||
rm -f "$DISPLAY_MARKER"
|
|
||||||
printf '\033[2J\033[H' > /dev/tty0 2>/dev/null
|
|
||||||
for b in /sys/class/backlight/*/brightness; do echo 0 > "$b" 2>/dev/null; done
|
|
||||||
for f in /sys/class/graphics/fb*/blank; do { echo 4 > "$f" || echo 1 > "$f"; } 2>/dev/null; done
|
|
||||||
log "status display OFF"
|
|
||||||
}
|
|
||||||
|
|
||||||
display_on() {
|
|
||||||
: > "$DISPLAY_MARKER"
|
|
||||||
log "volume-up pressed: status display ON"
|
|
||||||
for f in /sys/class/graphics/fb*/blank; do echo 0 > "$f" 2>/dev/null; done
|
|
||||||
for b in /sys/class/backlight/*/brightness; do echo 400 > "$b" 2>/dev/null; done
|
|
||||||
(
|
|
||||||
shown=0
|
|
||||||
while [ -e "$DISPLAY_MARKER" ] && [ "$shown" -lt "$DISPLAY_TIMEOUT" ]; do
|
|
||||||
draw_status
|
|
||||||
sleep 5
|
|
||||||
shown=$((shown + 5))
|
|
||||||
done
|
|
||||||
display_off
|
|
||||||
) &
|
|
||||||
}
|
|
||||||
|
|
||||||
VOLDOWN_DEV=$(find_dev "*resin*")
|
|
||||||
VOLUP_DEV=$(find_dev "gpio-keys")
|
|
||||||
|
|
||||||
[ -n "$VOLDOWN_DEV" ] && log "volume-down (ssh) on $VOLDOWN_DEV" \
|
|
||||||
|| log "no resin device - ssh enable unavailable this boot"
|
|
||||||
[ -n "$VOLUP_DEV" ] && log "volume-up (display) on $VOLUP_DEV" \
|
|
||||||
|| log "no gpio-keys device - status display unavailable this boot"
|
|
||||||
|
|
||||||
if [ -n "$VOLDOWN_DEV" ]; then
|
|
||||||
( while :; do [ "$(read_key "$VOLDOWN_DEV")" = "114" ] && enable_ssh; done ) &
|
|
||||||
fi
|
|
||||||
if [ -n "$VOLUP_DEV" ]; then
|
|
||||||
( while :; do
|
|
||||||
[ "$(read_key "$VOLUP_DEV")" = "115" ] || continue
|
|
||||||
if [ -e "$DISPLAY_MARKER" ]; then display_off; else display_on; fi
|
|
||||||
done ) &
|
|
||||||
fi
|
|
||||||
wait
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# systemd system generator: divert a charger-triggered boot to charging.target.
|
|
||||||
#
|
|
||||||
# The FP6 ABL appends "androidboot.mode=charger" to the kernel cmdline when
|
|
||||||
# the PMIC PON reason is a charger insertion (confirmed on-device 2026-08-24,
|
|
||||||
# journal/power). On such boots we swap the default target for the minimal
|
|
||||||
# charging.target instead of booting to the full UI.
|
|
||||||
#
|
|
||||||
# This must NEVER break a normal boot: every failure path falls through to
|
|
||||||
# exit 0 and the stock default.target.
|
|
||||||
#
|
|
||||||
# Escape hatch: /etc/charging-mode/disable — while it exists this generator
|
|
||||||
# is inert (deploy.sh creates it; arming = removing it).
|
|
||||||
#
|
|
||||||
# The CHARGING_MODE_* env overrides exist only for side-effect-free dry-runs
|
|
||||||
# on a live system; systemd invokes generators with a clean environment.
|
|
||||||
|
|
||||||
[ -e "${CHARGING_MODE_DISABLE_FILE:-/etc/charging-mode/disable}" ] && exit 0
|
|
||||||
|
|
||||||
grep -Eq '(^| )androidboot\.mode=charger( |$)' /proc/cmdline || exit 0
|
|
||||||
|
|
||||||
# $2 = early generator dir (highest priority in the unit search path that a
|
|
||||||
# generator can write to — beats /etc/systemd/system). Old single-arg
|
|
||||||
# invocation falls back to $1.
|
|
||||||
EARLY_DIR="${2:-$1}"
|
|
||||||
[ -n "$EARLY_DIR" ] && [ -d "$EARLY_DIR" ] || exit 0
|
|
||||||
|
|
||||||
ln -sf /etc/systemd/system/charging.target "$EARLY_DIR/default.target" || exit 0
|
|
||||||
|
|
||||||
# Marker for the ConditionPathExists=!/run/charging-mode drop-ins that keep
|
|
||||||
# radios/sensors (bluetooth, iio-sensor-proxy, hkdm) out of charging mode.
|
|
||||||
# Only created after the default.target diversion succeeded, so the
|
|
||||||
# suppressions can never leak into a normal boot.
|
|
||||||
: > "${CHARGING_MODE_MARKER:-/run/charging-mode}" 2>/dev/null
|
|
||||||
|
|
||||||
# Power key exits charging mode via reboot: a warm reboot PONs as HARD_RESET,
|
|
||||||
# so ABL does not re-append mode=charger and the next boot is a normal boot.
|
|
||||||
LOGIND_DIR="${CHARGING_MODE_LOGIND_DIR:-/run/systemd/logind.conf.d}"
|
|
||||||
mkdir -p "$LOGIND_DIR" 2>/dev/null && printf '[Login]\nHandlePowerKey=reboot\nHandlePowerKeyLongPress=reboot\n' > "$LOGIND_DIR/10-charging-mode.conf"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# FP6 offline charging (work/charging-mode): on charger-triggered boots,
|
|
||||||
# kill the boot splash and panel as early as software can — this hook runs
|
|
||||||
# right after splash_start in the initramfs (~6-10 s after power-on), vs
|
|
||||||
# the charging monitor's blank at ~16-20 s. The 0-5 s bootloader logo is
|
|
||||||
# ABL's and untouchable. Executed via `sh` by run_hooks (subshell, exit is
|
|
||||||
# safe); written to fall through harmlessly on any failure — a normal boot
|
|
||||||
# must never be affected.
|
|
||||||
|
|
||||||
if grep -Eq '(^| )androidboot\.mode=charger( |$)' /proc/cmdline 2>/dev/null; then
|
|
||||||
command -v plymouth >/dev/null 2>&1 && plymouth quit 2>/dev/null
|
|
||||||
for b in /sys/class/backlight/*/brightness; do
|
|
||||||
[ -w "$b" ] && echo 0 > "$b" 2>/dev/null
|
|
||||||
done
|
|
||||||
for f in /sys/class/graphics/fb*/blank; do
|
|
||||||
[ -w "$f" ] && { echo 4 > "$f" || echo 1 > "$f"; } 2>/dev/null
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Installed by work/charging-mode (FP6 offline charging) as a drop-in for
|
|
||||||
# units that are pointless while charging (radios, sensors, hotkeys).
|
|
||||||
# Inert in normal boots: the generator creates /run/charging-mode only on
|
|
||||||
# charger-triggered boots, and only after the default.target diversion
|
|
||||||
# succeeded.
|
|
||||||
[Unit]
|
|
||||||
ConditionPathExists=!/run/charging-mode
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Offline charging monitor
|
|
||||||
After=basic.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=exec
|
|
||||||
ExecStart=/usr/lib/charging-mode/charging-monitor.sh
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# Offline charging monitor (prototype r3). Runs only inside charging.target.
|
|
||||||
#
|
|
||||||
# - waits for the ADSP-backed battmgr supplies (charging + USB-PD live on
|
|
||||||
# the ADSP, which never sleeps — AP suspend does not touch the PD contract)
|
|
||||||
# - blanks + powers down the display pipeline, stops modem/cdsp remoteprocs,
|
|
||||||
# caps CPU frequency, nudges UFS auto-hibernate
|
|
||||||
# - unplug -> orderly poweroff (debounced ~6 s; a poweroff while plugged
|
|
||||||
# re-wakes via the USB_CHARGER PON trigger)
|
|
||||||
# - power key -> logind reboots to a NORMAL boot (generator drop-in)
|
|
||||||
# - suspend cycling (rock-bottom mode): only when /etc/charging-mode/
|
|
||||||
# enable-suspend exists AND ssh debug mode is off (/run/charging-mode-ssh,
|
|
||||||
# volume-down) AND the status display is off (/run/charging-mode-display,
|
|
||||||
# volume-up toggle). Arms an RTC wakealarm before every suspend and never
|
|
||||||
# suspends without a verified alarm.
|
|
||||||
#
|
|
||||||
# Deliberately NO rfkill anywhere: systemd-rfkill would persist a soft-block
|
|
||||||
# into normal boots (the 2026-08-23 wifi trap).
|
|
||||||
#
|
|
||||||
# Failsafe: if battmgr never appears, reboot — that PONs as HARD_RESET, so
|
|
||||||
# the phone comes back in a NORMAL boot and is never stuck dark.
|
|
||||||
|
|
||||||
BAT=/sys/class/power_supply/qcom-battmgr-bat
|
|
||||||
USB=/sys/class/power_supply/qcom-battmgr-usb
|
|
||||||
WAKEALARM=/sys/class/rtc/rtc0/wakealarm
|
|
||||||
SUSPEND_SECS=45
|
|
||||||
|
|
||||||
log() { echo "charging-monitor: $*"; }
|
|
||||||
|
|
||||||
log "offline charging mode active (prototype r5)"
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ ! -r "$USB/online" ]; do
|
|
||||||
i=$((i + 1))
|
|
||||||
if [ "$i" -gt 180 ]; then
|
|
||||||
log "battmgr never appeared after ${i}s - failsafe reboot to normal boot"
|
|
||||||
exec systemctl reboot
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
log "battmgr up after ${i}s"
|
|
||||||
|
|
||||||
for b in /sys/class/backlight/*/brightness; do
|
|
||||||
[ -w "$b" ] && echo 0 > "$b" 2>/dev/null
|
|
||||||
done
|
|
||||||
log "panel blanked"
|
|
||||||
|
|
||||||
# release the boot splash and power the display pipeline down (best effort —
|
|
||||||
# brightness 0 alone leaves the DSI/DPU scanning out a black frame)
|
|
||||||
if command -v plymouth >/dev/null 2>&1 && plymouth --ping 2>/dev/null; then
|
|
||||||
plymouth quit 2>/dev/null && log "plymouth quit"
|
|
||||||
fi
|
|
||||||
for f in /sys/class/graphics/fb*/blank; do
|
|
||||||
[ -w "$f" ] || continue
|
|
||||||
{ echo 4 > "$f" || echo 1 > "$f"; } 2>/dev/null && log "fb powerdown via $f"
|
|
||||||
done
|
|
||||||
|
|
||||||
# radios/compute are pointless while charging — stop modem and cdsp
|
|
||||||
# remoteprocs (adsp stays: it IS the charger; wpss left alone near the
|
|
||||||
# WCN-combo trap). The stop write can block past a short timeout yet still
|
|
||||||
# succeed — judge by the resulting state, not the write's exit code.
|
|
||||||
for r in /sys/class/remoteproc/remoteproc*; do
|
|
||||||
name=$(cat "$r/name" 2>/dev/null)
|
|
||||||
case "$name" in
|
|
||||||
*mpss*|*modem*|*mss*|*cdsp*)
|
|
||||||
timeout 25 sh -c "echo stop > '$r/state'" 2>/dev/null
|
|
||||||
if [ "$(cat "$r/state" 2>/dev/null)" = "offline" ]; then
|
|
||||||
log "stopped remoteproc $name"
|
|
||||||
else
|
|
||||||
log "remoteproc $name did not stop (state=$(cat "$r/state" 2>/dev/null)) - continuing"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# cap CPU frequency to the minimum, then offline every core except cpu0
|
|
||||||
# (a little @ 441.6 MHz) — emptying the two big clusters (cpu4-6, cpu7)
|
|
||||||
# lets their cluster rails power-collapse entirely. Charging mode always
|
|
||||||
# ends in a reboot, so no undo needed.
|
|
||||||
for p in /sys/devices/system/cpu/cpufreq/policy*; do
|
|
||||||
cat "$p/cpuinfo_min_freq" > "$p/scaling_max_freq" 2>/dev/null
|
|
||||||
done
|
|
||||||
for c in /sys/devices/system/cpu/cpu[1-9]*; do
|
|
||||||
[ -w "$c/online" ] && echo 0 > "$c/online" 2>/dev/null
|
|
||||||
done
|
|
||||||
log "cpu freq capped to min, online cpus: $(cat /sys/devices/system/cpu/online 2>/dev/null)"
|
|
||||||
# NOTE: do NOT touch UFS auto_hibern8 — the 2026-08-24 PD-boot crash
|
|
||||||
# (ufshcd hibern8 exit failed, dead rootfs at t=34s) happened with our
|
|
||||||
# auto_hibern8=5000 write + suspend-thrash; the write is the prime suspect
|
|
||||||
# and its benefit was never measured.
|
|
||||||
|
|
||||||
# battmgr power_supply events are wakeup sources, and a PPS charger streams
|
|
||||||
# telemetry ~1/s — that turned suspend cycling into 1.2 s suspend-thrash on
|
|
||||||
# the PD brick. Drop the psy wakeup flags: the RTC alarm is our only
|
|
||||||
# intended waker (unplug is then detected at the next wake, <=45 s).
|
|
||||||
for w in /sys/class/power_supply/qcom-battmgr-*/device/power/wakeup \
|
|
||||||
/sys/class/power_supply/qcom-battmgr-*/power/wakeup; do
|
|
||||||
[ -w "$w" ] && echo disabled > "$w" 2>/dev/null && log "wakeup disabled: $w"
|
|
||||||
done
|
|
||||||
|
|
||||||
suspend_enabled=false
|
|
||||||
[ -e /etc/charging-mode/enable-suspend ] && suspend_enabled=true
|
|
||||||
log "suspend cycling: $suspend_enabled (flag /etc/charging-mode/enable-suspend)"
|
|
||||||
|
|
||||||
check_unplug() {
|
|
||||||
# debounced: three reads ~3 s apart must all say offline
|
|
||||||
n=0
|
|
||||||
while [ "$(cat "$USB/online" 2>/dev/null || echo 1)" = "0" ]; do
|
|
||||||
n=$((n + 1))
|
|
||||||
if [ "$n" -ge 3 ]; then
|
|
||||||
log "charger removed (capacity $(cat "$BAT/capacity" 2>/dev/null)%) - powering off"
|
|
||||||
exec systemctl poweroff
|
|
||||||
fi
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
sample() {
|
|
||||||
log "sample: cap $(cat "$BAT/capacity" 2>/dev/null)% status $(cat "$BAT/status" 2>/dev/null) bat $(cat "$BAT/current_now" 2>/dev/null)uA cc $(cat "$BAT/charge_counter" 2>/dev/null)uAh usb $(cat "$USB/voltage_now" 2>/dev/null)uV $(cat "$USB/current_now" 2>/dev/null)uA icl $(cat "$USB/input_current_limit" 2>/dev/null)uA online $(cat "$USB/online" 2>/dev/null)"
|
|
||||||
}
|
|
||||||
|
|
||||||
suspending() {
|
|
||||||
$suspend_enabled && [ ! -e /run/charging-mode-ssh ] && [ ! -e /run/charging-mode-display ]
|
|
||||||
}
|
|
||||||
|
|
||||||
tick=0
|
|
||||||
thrash=0
|
|
||||||
while :; do
|
|
||||||
check_unplug
|
|
||||||
# when suspend-cycling, log a full sample at EVERY wake — the persistent
|
|
||||||
# journal is the offline logger for charger tests where no ssh path
|
|
||||||
# exists (e.g. the PD brick occupying the USB port); in awake mode log
|
|
||||||
# once a minute
|
|
||||||
if suspending || [ $((tick % 12)) -eq 0 ]; then
|
|
||||||
sample
|
|
||||||
fi
|
|
||||||
tick=$((tick + 1))
|
|
||||||
|
|
||||||
if suspending && [ -w "$WAKEALARM" ]; then
|
|
||||||
echo 0 > "$WAKEALARM" 2>/dev/null
|
|
||||||
echo "+$SUSPEND_SECS" > "$WAKEALARM" 2>/dev/null
|
|
||||||
if [ -n "$(cat "$WAKEALARM" 2>/dev/null)" ]; then
|
|
||||||
t0=$(date +%s)
|
|
||||||
echo mem > /sys/power/state 2>/dev/null || { log "suspend write failed - falling back to sleep"; sleep 30; }
|
|
||||||
dur=$(( $(date +%s) - t0 ))
|
|
||||||
log "slept ${dur}s"
|
|
||||||
# thrash guard: instant resumes back-to-back nearly killed UFS
|
|
||||||
# on the PD brick (2026-08-24) — three short cycles in a row
|
|
||||||
# means something wakes us constantly; stop suspending and stay
|
|
||||||
# in awake mode (still fine: any real charger out-supplies it)
|
|
||||||
if [ "$dur" -lt 10 ]; then
|
|
||||||
thrash=$((thrash + 1))
|
|
||||||
if [ "$thrash" -ge 3 ]; then
|
|
||||||
log "suspend thrash (3 consecutive <10s cycles) - disabling suspend cycling this session"
|
|
||||||
suspend_enabled=false
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
thrash=0
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "could not arm RTC wakealarm - NOT suspending (sleep fallback)"
|
|
||||||
sleep 30
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Static USB gadget network for charging mode
|
|
||||||
# Only ever pulled by charging.target — replaces NetworkManager's
|
|
||||||
# "Developer Mode" profile there so no wifi stack starts in charging mode.
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
RemainAfterExit=yes
|
|
||||||
ExecStart=/usr/lib/charging-mode/charging-usbnet.sh
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# Give the USB gadget interface its usual static address (the one the NM
|
|
||||||
# "Developer Mode" profile would assign) without NetworkManager. The
|
|
||||||
# workstation side self-configures 172.16.42.2 via its udev rule, so
|
|
||||||
# `ssh fp6usb4` works in charging mode.
|
|
||||||
|
|
||||||
IF=""
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
IF=$(ls /sys/class/net 2>/dev/null | grep '^usb' | head -1)
|
|
||||||
[ -n "$IF" ] && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
[ -n "$IF" ] || exit 0
|
|
||||||
|
|
||||||
ip addr replace 172.16.42.1/16 dev "$IF"
|
|
||||||
ip link set "$IF" up
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# Offline charging mode: minimal boot target for charger-triggered boots.
|
|
||||||
# Reached only via charging-mode-generator (never enabled, never the static
|
|
||||||
# default). basic.target gives us udev coldplug -> qcom_q6v5_pas module ->
|
|
||||||
# ADSP boot -> pmic_glink/battmgr (all builtin), i.e. battery telemetry and
|
|
||||||
# the ADSP-owned charging state machine, with no UI stack and no greeter.
|
|
||||||
#
|
|
||||||
# systemd-logind handles the power key (generator drops HandlePowerKey=reboot
|
|
||||||
# into /run for this boot only).
|
|
||||||
|
|
||||||
[Unit]
|
|
||||||
Description=Offline charging mode
|
|
||||||
Requires=basic.target
|
|
||||||
After=basic.target rescue.service rescue.target
|
|
||||||
Conflicts=rescue.service rescue.target
|
|
||||||
Wants=charging-monitor.service systemd-logind.service
|
|
||||||
AllowIsolate=yes
|
|
||||||
|
|
||||||
# Observability (r3): ssh is OFF by default — volume-down enables it
|
|
||||||
# (charging-keys starts systemd-user-sessions + charging-usbnet + sshd, USB
|
|
||||||
# cable only, no NetworkManager/wifi ever). systemd-user-sessions is needed
|
|
||||||
# at that moment because we bypass multi-user.target and sshd would
|
|
||||||
# otherwise refuse unprivileged logins via pam_nologin.
|
|
||||||
# Radios/sensors are kept out via ConditionPathExists drop-ins on
|
|
||||||
# bluetooth/iio-sensor-proxy/hkdm (marker: /run/charging-mode) and the
|
|
||||||
# monitor stops the modem + cdsp remoteprocs.
|
|
||||||
Wants=charging-keys.service
|
|
||||||
39
build.sh
39
build.sh
|
|
@ -36,20 +36,8 @@ if [ "$(id -u)" = 0 ]; then
|
||||||
# (pmbootstrap's host-tool checks + image mounting need both)
|
# (pmbootstrap's host-tool checks + image mounting need both)
|
||||||
apk add -q git sudo openssl python3 py3-pip multipath-tools util-linux \
|
apk add -q git sudo openssl python3 py3-pip multipath-tools util-linux \
|
||||||
tar xz
|
tar xz
|
||||||
# the pmOS gitlab hiccups under crawler load and truncates clones
|
|
||||||
# ("early EOF"); that should cost a retry, not the run — same reasoning
|
|
||||||
# as clone_retry below, which isn't defined yet in this root branch
|
|
||||||
for _i in 1 2 3; do
|
|
||||||
pip install -q --break-system-packages \
|
pip install -q --break-system-packages \
|
||||||
git+https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git@3.11.1 \
|
git+https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git@3.11.1
|
||||||
&& break
|
|
||||||
if [ "$_i" = 3 ]; then
|
|
||||||
echo "pmbootstrap pip install failed after 3 attempts" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "pmbootstrap pip install failed (attempt $_i/3), retrying in 15s..." >&2
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
# The container cannot modprobe (no /lib/modules in here), but it doesn't
|
# The container cannot modprobe (no /lib/modules in here), but it doesn't
|
||||||
# need to: the host kernel autoloads the loop driver when losetup opens
|
# need to: the host kernel autoloads the loop driver when losetup opens
|
||||||
# the static /dev/loop-control node. Make pmbootstrap's explicit
|
# the static /dev/loop-control node. Make pmbootstrap's explicit
|
||||||
|
|
@ -72,8 +60,7 @@ if [ "$(id -u)" = 0 ]; then
|
||||||
# would poison the next podman invocation's cwd)
|
# would poison the next podman invocation's cwd)
|
||||||
install -d -o build dist
|
install -d -o build dist
|
||||||
find dist -mindepth 1 -delete
|
find dist -mindepth 1 -delete
|
||||||
# su scrubs the environment — carry the knobs that matter across it
|
exec su build -c "sh -eu '$PWD/build.sh'"
|
||||||
exec su build -c "FP6IMG_NO_CROSSDIRECT='${FP6IMG_NO_CROSSDIRECT:-}' sh -eu '$PWD/build.sh'"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# git hosts occasionally hiccup (our forgejo lives on a small VM that also
|
# git hosts occasionally hiccup (our forgejo lives on a small VM that also
|
||||||
|
|
@ -131,7 +118,6 @@ cp -r aports/device/linux-postmarketos-qcom-milos "$WORK/pmaports/device/testing
|
||||||
cp -r aports/device/soc-fairphone-fp6-audio "$WORK/pmaports/device/"
|
cp -r aports/device/soc-fairphone-fp6-audio "$WORK/pmaports/device/"
|
||||||
cp -r aports/device/callaudioshim "$WORK/pmaports/device/"
|
cp -r aports/device/callaudioshim "$WORK/pmaports/device/"
|
||||||
cp -r aports/device/fp6-device-tweaks "$WORK/pmaports/device/"
|
cp -r aports/device/fp6-device-tweaks "$WORK/pmaports/device/"
|
||||||
cp -r aports/device/fp6-charging-mode "$WORK/pmaports/device/"
|
|
||||||
cp -r aports/device/catcrafts-fp6-repo "$WORK/pmaports/device/"
|
cp -r aports/device/catcrafts-fp6-repo "$WORK/pmaports/device/"
|
||||||
cp -r aports/main/postmarketos-config-nftables "$WORK/pmaports/main/"
|
cp -r aports/main/postmarketos-config-nftables "$WORK/pmaports/main/"
|
||||||
cp -r aports/modem/imsd "$WORK/pmaports/modem/"
|
cp -r aports/modem/imsd "$WORK/pmaports/modem/"
|
||||||
|
|
@ -214,7 +200,7 @@ aports = $WORK/pmaports
|
||||||
device = fairphone-fp6
|
device = fairphone-fp6
|
||||||
ui = plasma-mobile
|
ui = plasma-mobile
|
||||||
systemd = always
|
systemd = always
|
||||||
extra_packages = soc-fairphone-fp6-audio,callaudioshim,imsd,fp6-device-tweaks,fp6-charging-mode,catcrafts-fp6-repo,postmarketos-base-ui-audio-backend-pipewire,pipewire-pulse,pipewire-echo-cancel
|
extra_packages = soc-fairphone-fp6-audio,callaudioshim,imsd,fp6-device-tweaks,catcrafts-fp6-repo,postmarketos-base-ui-audio-backend-pipewire,pipewire-pulse,pipewire-echo-cancel
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# All five source tarballs are generated locally above, so every checksum
|
# All five source tarballs are generated locally above, so every checksum
|
||||||
|
|
@ -227,29 +213,16 @@ pmbootstrap checksum libqmi
|
||||||
pmbootstrap checksum modemmanager
|
pmbootstrap checksum modemmanager
|
||||||
pmbootstrap checksum libcamera
|
pmbootstrap checksum libcamera
|
||||||
|
|
||||||
# FP6IMG_NO_CROSSDIRECT=1: for local (podman) environments where
|
|
||||||
# crossdirect's /native bridge breaks ("cc: cannot execute cc1:
|
|
||||||
# posix_spawnp: No such file or directory" during meson setup); the
|
|
||||||
# affected packages then build qemu-only — slower, identical output. The
|
|
||||||
# kernel is unaffected either way (cross-native, no crossdirect).
|
|
||||||
NOCROSS=${FP6IMG_NO_CROSSDIRECT:+--no-cross}
|
|
||||||
|
|
||||||
# pmbootstrap's install-time build plan is not dependency-ordered (it tried
|
# pmbootstrap's install-time build plan is not dependency-ordered (it tried
|
||||||
# building modemmanager before the libqmi its makedepends require); build
|
# building modemmanager before the libqmi its makedepends require); build
|
||||||
# the GNSS stack bottom-up explicitly. 'pmbootstrap build' is a no-op when
|
# the GNSS stack bottom-up explicitly. 'pmbootstrap build' is a no-op when
|
||||||
# the package is already current.
|
# the package is already current.
|
||||||
retry "build libqmi" pmbootstrap $NOCROSS build --arch aarch64 libqmi
|
retry "build libqmi" pmbootstrap build --arch aarch64 libqmi
|
||||||
retry "build modemmanager" pmbootstrap $NOCROSS build --arch aarch64 modemmanager
|
retry "build modemmanager" pmbootstrap build --arch aarch64 modemmanager
|
||||||
# libcamera is no makedepend of anything here - build it explicitly so the
|
# libcamera is no makedepend of anything here - build it explicitly so the
|
||||||
# patched -r2 exists for the publish step even if the install set resolves
|
# patched -r2 exists for the publish step even if the install set resolves
|
||||||
# it before the overlay is considered.
|
# it before the overlay is considered.
|
||||||
retry "build libcamera" pmbootstrap $NOCROSS build --arch aarch64 libcamera
|
retry "build libcamera" pmbootstrap build --arch aarch64 libcamera
|
||||||
# imsd is the one crossdirect build left to the install phase, which has no
|
|
||||||
# per-package flag — pre-build it here in no-crossdirect mode so install
|
|
||||||
# finds it current.
|
|
||||||
if [ -n "$NOCROSS" ]; then
|
|
||||||
retry "build imsd" pmbootstrap $NOCROSS build --arch aarch64 imsd
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 4. build the image -------------------------------------------------------
|
# --- 4. build the image -------------------------------------------------------
|
||||||
# Same default credentials as the official postmarketOS images.
|
# Same default credentials as the official postmarketOS images.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue