Complete crash when charging

@mal, thanks for the reply.

On the chargers: this was already asked by @pherjung in the first post, and I answered it in the second post. One is an Aukey PA-T2, the other a Xiaomi MDY-11-EZ. I don’t know if those count as “fast” or “slow” chargers. There are dozens of fast-charging protocols at this point, so I’m not sure what the distinction is meant to be.

On battery level: I usually start charging once the level dips below 40%, sometimes when below 60%, it depends a bit on the situation and how much I expect to use the phone until the next charge. But as I noted in the original report, the battery percentage indicator is stuck way below the actual charge level, so it’s often impossible to tell how full the battery really is. It’s more based on vibes than a reliable reading.

On “very low”: I don’t know what you count as very low, so I can’t answer that. I don’t think it’s limited to very low charge, but if you define the threshold I can tell you whether it matches what I see. I try to always start charging before the battery dips below 10%.

Could you also still answer the rough timeline question, or at least confirm whether this is actively being worked on (rather than only responding when I ask)? And confirm you will continue posting updates in this thread. I’m not asking for a fix date; I’d just like to know what the next steps are. Do you want me to send more logs?

So the battery level indicator is always wrong on your device? Could you send me via email output of “ls -l /sys/class/power_supply/” and “cat /sys/class/power_supply/*/uevent” also mention what you see in the battery charge indicator when you got those so we could see what value is being shown. By low charge I mean less than 10% or something like that.
As for timeline I hope we can at some point find out what actually is happening on the device, at the moment the logs haven’t contained any hints of what is happening. Would be good to see from journal log if the charging is actually starting, maybe start charging and take a journal log (journalctl -b --no-pager) after half an hour and send it to me so I can see if the device is even charging.
If the outputs and logs still don’t contain any hints I will ask some more information.

4 Likes

Would it help if I do the same? Looking at the behaviour our bugs appear similar (but there are differences too).

It wouldn’t hurt if Matti see more logs.

1 Like

It’s been a while, there were quite a few charge cycles without issue somehow. It happened again last night but given the earlier rate I’d expected a crash sooner. I have updated to 5.2.0.18 at some point in between, but since the uneventful charges started before that, I don’t think the update changed anything.

@mal, replying to your questions properly now:

Is the battery indicator always wrong?
No, not always. What I was getting at is that when a crash occurs, then the indicator is off (so in that sense, yes, after a crash the indicator is always wrong). That makes it ~difficult~ impossible to tell the charge level starting at the next cycle. If the indicator stays at e.g. 11% all the time for that cycle, then I just have to guess when it’s time to plug in to charge again. After a succesful recharge the battery indicator seems to recover, and then I can tell when to plug in normally again. So that also means that it’s possible some session started charging from a much lower point than I’d normally do, simply because I cannot tell. I’m going on the fact that the phone can seemingly run for the normal duration (whole day of normal usage) even when in the morning it’s only at 11% to conclude that it’s just the indicator that is not updating correctly.

ls -l + cat */uevent + UI value
All in the zip I’m emailing you separately (see power_supply_uevent_all.txt, battery_vs_ui.txt and charge_state.txt). For this crash the UI read 9% at charge-start, matching the kernel’s 9%.

“Low charge <10%”
This session started at exactly 9%, so it qualifies. Looking back at the charge attempts since the last message, it’s the only captured session that started that low:

┌───────────────────┬───────────────────────────┬─────────────────────┬──────────────────┐
│session            │start kernel/UI            │snapshots            │outcome           │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Sep 23             │32 / 32                    │8/8                  │ok                │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Sep 26             │37 / 37                    │8/8                  │ok                │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Sep 27             │59 / 59                    │8/8                  │ok                │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Sep 29             │47 / 47                    │8/8                  │ok                │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Sep 30             │36 / 36                    │8/8                  │ok                │
├───────────────────┼───────────────────────────┼─────────────────────┼──────────────────┤
│Oct 4              │9 / 9                      │2/8                  │crash             │
└───────────────────┴───────────────────────────┴─────────────────────┴──────────────────┘

Same charger (Xiaomi MDY-11-EZ) every time, so that’s controlled for. One crash is only a
correlation, but the thing to watch is whether the next sub-10% charge crashes too.

Did charging actually start / journal after half an hour
Charging verifiably started; status flipped Discharging→Charging ~1 min after plug-in, current went −58 mA → +2.0 A, voltage climbing, 9% → 11%, and snap_01’s dmesg shows the TCPC/PD negotiation happening. But I can’t give you the half-hour journal: the phone died ~4 minutes after plug-in (last curve point 01:17:44, last journal line 01:18:07), which is also why there are only two snapshots.

The most informative part: with persistent journals on and pstore cleared beforehand, the previous-boot journal ends at 01:18:07 with the charger driver still enabled and charging, then PM: suspend entry (s2idle) — and nothing after. The pstore ramoops likewise ends in routine modem chatter with no panic or oops. So from the logs’ perspective the phone charged normally, suspended, and never woke up. Post-reboot the battery still read 11%.

Helper script for capturing logs
Since doing all these captures by hand was going to be tedious and error prone, I had an LLM write a shell script to automate the process. I used it for the capture I’m sharing, so it’s tested and seems to be working well. You run it in Fingerterm before plugging in, it asks for the charger model and then automaticallly records the baseline battery/UI state, waits for plug-in, and then takes snapshots every 5 min for half an hour plus a 30s charge curve. Everything is stored all into a sub-directory charge_crash_investigation/<timestamp>/ next to the script. After a crash + reboot, you run ./charge-capture.sh --after-crash to collect the pstore and previous-boot journal. It also has a --journal-persistent on|off toggle for the Storage setting. This is what I used to take the capture in the .zip I’ve emailed you.

I hope this script will make it easier for people to provide all the information you’re requesting.

Please let us know what you find and what next steps you’d like to take.

3 Likes

Second time asking:
Please fix your forum to allow proper sharing of files

charge-capture.sh
#!/usr/bin/env bash
# charge-capture.sh — pre-charge + charging-window evidence collector for
# Sailfish OS "crash/dying while charging" investigations
# (built for jp2601 forum thread #32018, works on any Sailfish phone:
# MTK-specific nodes are read opportunistically, everything else is generic
# sysfs + dbus, and unreadable nodes degrade to UNREADABLE, never abort).
#
# Runs from anywhere writable, e.g. SD card root:
#   /run/media/defaultuser/<uuid>/charge-capture.sh
# Each run creates a sibling folder:
#   charge_crash_investigation/<YYYYMMDDHHMMSS>/...
#
# No root needed for the main flow. Pricileged captures (pstore, full
# journal) are attempted only in --after-crash mode via interactive devel-su
# (you type your developer-mode password when prompted), and degrade
# gracefully to unprivileged captures otherwise.
#
# Usage (run in Fingerterm BEFORE plugging in the charger):
#   cd to the folder holding charge-capture.sh
#   ./charge-capture.sh                    # interactive, 30s settle + 6x5min
#   ./charge-capture.sh --yes              # skip prompts (defaults)
#   ./charge-capture.sh --after-crash      # after a crash+reboot: pstore+journals
#   ./charge-capture.sh --help             # all options
# If the filesystem is mounted noexec, run:  bash ./charge-capture.sh ...
set -u

VERSION="1.4.0"
# NOTE: on Sailfish /bin/bash is busybox sh — keep this script POSIX-sh clean
# (no arrays, no BASH_SOURCE, no [[ ]]).
SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
ROOT="${SCRIPT_DIR}/charge_crash_investigation"

SETTLE=30
INTERVAL=300
COUNT=6
WAIT_TIMEOUT=600
CURVE_EVERY=30
ASSUME_YES=0
MODE="precharge"
AFTER_RUN=""
SKIPROOT=1
JWANT=""

log()  { echo "[$(date +%H:%M:%S) charge-capture] $*"; }
warn() { echo "[$(date +%H:%M:%S) charge-capture] WARNING: $*" >&2; }

# Progress for multi-second steps: begin_step "label" ... end_step
# prints "... label" then "    done (Ns): label". Uses SNAP_NAME for context.
begin_step() { STEP_LABEL="$1"; STEP_T0="$(date +%s)"; log "… $1"; }
end_step() {
  log "    done ($(( $(date +%s) - STEP_T0 ))s): $STEP_LABEL"
  STEP_LABEL=""; STEP_T0=""
}

# Privilege handling. Preferred: run as defaultuser; privileged steps elevate
# via devel-su (password prompt stays visible on the terminal). Running the
# whole script as root also works: inner commands run directly.
IS_ROOT=0
[ "$(id -u 2>/dev/null)" = "0" ] && IS_ROOT=1
as_root() { # $1 = shell command string; stderr stays on terminal on purpose
  if [ "$IS_ROOT" = "1" ]; then sh -c "$1"
  else devel-su -c "$1"; fi
}

# Unmissable banner before a devel-su password prompt: WHAT password, WHY,
# and what the prompt itself looks like. Call right before as_root.
# Silent when already root — no password will be asked then.
# (Measured: devel-su's prompt bypasses stdout/stderr redirection and goes
# straight to the terminal, so without this banner it appears as a bare
# unexplained "Password:" — or is missed entirely while scrolled.)
need_root_pw() { # $1 = why root is needed
  if [ "$IS_ROOT" = "1" ]; then return 0; fi
  echo "=========================== ROOT NEEDED ==========================="
  log "Why: $1"
  log "What: your DEVELOPER-MODE password (set in Settings > Developer mode)."
  log "How: a 'Password:' prompt follows on the next line — that is devel-su"
  log "     asking for it. Typing is invisible; press Enter when done."
  log "     A wrong password just fails this step, the script continues."
  echo "==================================================================="
}

usage() {
  cat <<'EOF'
charge-capture.sh — charging-crash evidence collector (jp2601 #32018)

Run in Fingerterm BEFORE plugging in the charger. Each run saves to
charge_crash_investigation/<YYYYMMDDHHMMSS>/ next to this script.

  ./charge-capture.sh                    interactive, 30s settle + 6x5min
  ./charge-capture.sh --yes              skip prompts (defaults)
  ./charge-capture.sh --after-crash      post-mortem into latest run (no timestamp needed)
  ./charge-capture.sh --after-crash RUN  post-mortem into a specific run
  ./charge-capture.sh --journal-persistent status|on|off
                                     check / flip persistent journals
                                     (on|off needs root, e.g.
                                      devel-su -c './charge-capture.sh --journal-persistent on')
  ./charge-capture.sh --help             this text

Options:
  --settle SEC        settle delay after plug detect (default 30)
  --interval SEC      spacing of the periodic snapshots (default 300)
  --count N           number of periodic snapshots (default 6)
  --wait-timeout SEC  how long to wait for plug-in (default 600)
  --curve-every SEC   charge_curve.csv sampling (default 30, must be >0)
  --yes               non-interactive, accept defaults
  --after-crash [RUN] post-mortem collect (default: latest run, else new)
  --journal-persistent status|on|off
                      status needs no privilege; on|off must run as root
  --help              this text
EOF
}

need_num() { # $1 name $2 value [$3 min] — validates non-negative integer
  case "$2" in ''|*[!0-9]*) echo "Option $1 needs a non-negative integer, got: $2" >&2; exit 1;; esac
  if [ -n "${3:-}" ] && [ "$2" -lt "$3" ]; then
    echo "Option $1 must be >= $3, got: $2" >&2; exit 1
  fi
}
need_arg() { # $1 flag $#remaining $2orEmpty
  if [ "$2" -lt 2 ]; then echo "Option $1 needs a value" >&2; exit 1; fi
  case "$3" in -*) echo "Option $1 needs a value, got flag: $3" >&2; exit 1;; esac
}

while [ $# -gt 0 ]; do
  case "$1" in
    --settle) need_arg "$1" $# "${2:-}"; SETTLE="$2"; need_num "$1" "$SETTLE"; shift 2;;
    --interval) need_arg "$1" $# "${2:-}"; INTERVAL="$2"; need_num "$1" "$INTERVAL" 1; shift 2;;
    --count) need_arg "$1" $# "${2:-}"; COUNT="$2"; need_num "$1" "$COUNT"; shift 2;;
    --wait-timeout) need_arg "$1" $# "${2:-}"; WAIT_TIMEOUT="$2"; need_num "$1" "$WAIT_TIMEOUT" 1; shift 2;;
    --curve-every) need_arg "$1" $# "${2:-}"; CURVE_EVERY="$2"; need_num "$1" "$CURVE_EVERY" 1; shift 2;;
    --yes) ASSUME_YES=1; shift;;
    --after-crash)
      MODE="aftercrash"
      if [ $# -ge 2 ]; then case "${2:-}" in -*) ;; *) AFTER_RUN="$2"; shift;; esac; fi
      # Sanitize: run names are timestamps; never allow path traversal.
      case "$AFTER_RUN" in
        ""|*[!A-Za-z0-9._-]*) 
          case "$AFTER_RUN" in "" ) ;; *)
            warn "ignoring unsafe --after-crash value: $AFTER_RUN (using latest run)"
            AFTER_RUN="";;
          esac;;
      esac
      shift;;
    --after-crash=*) AFTER_RUN="${1#--after-crash=}"; MODE="aftercrash"
      case "$AFTER_RUN" in ""|*[!A-Za-z0-9._-]*) warn "ignoring unsafe value; using latest run"; AFTER_RUN="";; esac
      shift;;
    --journal-persistent) need_arg "$1" $# "${2:-}"; JWANT="$2"
      case "$JWANT" in on|off|status) ;; *) echo "Option $1 needs on|off|status, got: $JWANT" >&2; exit 1;; esac
      MODE="journal"; shift 2;;
    --journal-persistent=*) JWANT="${1#--journal-persistent=}"; MODE="journal"
      case "$JWANT" in on|off|status) ;; *) echo "Option --journal-persistent needs on|off|status" >&2; exit 1;; esac
      shift;;
    --help|-h) usage; exit 0;;
    *) echo "Unknown arg: $1" >&2; usage >&2; exit 1;;
  esac
done

# --- helpers ---------------------------------------------------------------
# NOTE: Sailfish has no `timeout(1)`, so blocking probes are bounded with
# with_timeout() (watchdog kill) and server-side limits (journalctl -n).

# Safe file read: prints value or "UNREADABLE".
rd() { cat "$1" 2>/dev/null || echo "UNREADABLE"; }

# with_timeout SECS CMD... — run CMD, kill after SECS (rc 124 on timeout).
# IMPORTANT (busybox sh): the watchdog inherits stdout, so NEVER pipe
# with_timeout's output (the pipe stays open until the watchdog sleeps out,
# adding SECS of delay per call). Redirect to a tmp file instead.
with_timeout() {
  secs="$1"; shift
  "$@" &
  pid=$!
  ( sleep "$secs"; kill -9 "$pid" 2>/dev/null ) &
  wd=$!
  wait "$pid" 2>/dev/null; rc=$?
  kill "$wd" 2>/dev/null; wait "$wd" 2>/dev/null
  return "$rc"
}

snap_tmp() { mktemp /tmp/ccap.XXXXXX 2>/dev/null || echo "/tmp/ccap.$$.${RANDOM:-0}"; }

mce_raw() { # $1 = mce request name; prints raw dbus-send reply (bounded)
  t="$(snap_tmp)"
  with_timeout 10 dbus-send --system --print-reply --dest=com.nokia.mce \
    /com/nokia/mce/request "com.nokia.mce.request.$1" > "$t" 2>&1
  rc=$?
  tail -n 8 "$t" 2>/dev/null; rm -f "$t"
  return "$rc"
}

mce_level() { # numeric UI battery level, or "?" on error
  out="$(mce_raw get_battery_level)"; rc=$?
  [ "$rc" -eq 0 ] || { echo "?"; return; }
  echo "$out" | grep -E "(int32|uint32)" | grep -oE '[0-9]+' | tail -n 1 | grep -E '.+' || echo "?"
}

mce_string() { # $1 request; prints trailing string value or "?"
  out="$(mce_raw "$1")"; rc=$?
  [ "$rc" -eq 0 ] || { echo "?"; return; }
  echo "$out" | grep -E "string" | tail -n 1 | sed 's/.*string *"//; s/".*//' | grep -E '.+' || echo "?"
}

# Generic charger presence for ANY Sailfish phone (not just MTK): 1 if any
# power supply reports online=1, else 0. Unknown/unreadable nodes are skipped.
any_online() {
  for ps in /sys/class/power_supply/*; do
    [ -f "$ps/online" ] || continue
    [ "$(rd "$ps/online" | head -n 1)" = "1" ] && { echo 1; return 0; }
  done
  echo 0
}
# usb_type of the first online supply: "none" if unplugged, "unknown" if the
# supply has no usb_type node (e.g. plain AC), else the reported type(s).
online_usb_type() {
  for ps in /sys/class/power_supply/*; do
    [ -f "$ps/online" ] || continue
    if [ "$(rd "$ps/online" | head -n 1)" = "1" ]; then
      if [ -f "$ps/usb_type" ]; then rd "$ps/usb_type" | head -n 1; else echo "unknown"; fi
      return 0
    fi
  done
  echo "none"
}

# --- charger picker (multiple choice) ----------------------------------------
# Built-in list (from forum thread #32018) + user additions in
# charge-capture-chargers.txt next to the script. Last choice is remembered
# in .charge-capture-last and offered as default. Sets charger_label.
CHARGERS_FILE="${SCRIPT_DIR}/charge-capture-chargers.txt"
LAST_CHARGER_FILE="${SCRIPT_DIR}/.charge-capture-last"
DEFAULT_CHARGERS="Aukey PA-T2
Xiaomi MDY-11-EZ
Aukey PA-T9 (QC3.0)
UGreen PD charger
Other USB-PD charger
Laptop USB port"

choose_charger() {
  charger_label=""
  last=""
  [ -f "$LAST_CHARGER_FILE" ] && last="$(head -n 1 "$LAST_CHARGER_FILE" 2>/dev/null)"
  if [ "$ASSUME_YES" = "1" ]; then
    charger_label="${last:-unspecified}"
    return 0
  fi
  # Build numbered menu: defaults + custom file entries.
  menu="$(snap_tmp)"
  printf '%s\n' "$DEFAULT_CHARGERS" > "$menu"
  if [ -f "$CHARGERS_FILE" ]; then
    grep -v -E '^[[:space:]]*(#|$)' "$CHARGERS_FILE" 2>/dev/null >> "$menu" || true
  fi
  n=0; def_n=""
  echo "Which charger are you about to use?"
  while IFS= read -r cl || [ -n "$cl" ]; do
    [ -n "$cl" ] || continue
    n=$((n + 1))
    mark=""; [ -n "$last" ] && [ "$cl" = "$last" ] && { mark="  (last used)"; def_n="$n"; }
    echo "  $n) $cl$mark"
  done < "$menu"
  n=$((n + 1))
  echo "  $n) Other (type manually)"
  if [ -n "$def_n" ]; then printf "choice [${def_n}]> "
  else printf "choice [$n]> "; def_n="$n"; fi
  read -t 120 -r ans || ans=""
  [ -z "$ans" ] && ans="$def_n"
  case "$ans" in ''|*[!0-9]*|"0") ans="$n";; esac
  if [ "$ans" -ge 1 ] && [ "$ans" -lt "$n" ]; then
    charger_label="$(sed -n "${ans}p" "$menu")"
  else
    printf "charger name> "; read -t 120 -r charger_label || charger_label=""
    [ -z "$charger_label" ] && charger_label="unspecified"
    if [ "$charger_label" != "unspecified" ] && [ -n "$charger_label" ]; then
      if ! grep -Fxq "$charger_label" "$menu" 2>/dev/null; then
        echo "Remember '$charger_label' for next time? [Y/n]"
        read -t 60 -r yn || yn=""
        case "$yn" in n|N|no|No|NO*) ;; *)
          echo "$charger_label" >> "$CHARGERS_FILE" 2>/dev/null || true;;
        esac
      fi
    fi
  fi
  rm -f "$menu"
  [ -z "$charger_label" ] && charger_label="unspecified"
  echo "$charger_label" > "$LAST_CHARGER_FILE" 2>/dev/null || true
}

# --- journal Storage toggle ---------------------------------------------------
# status needs no privilege; on|off must run as root, e.g.:
#   devel-su -c './charge-capture.sh --journal-persistent on'
journal_persistent() { # $1 = on|off|status
  mode="$1"
  conf="/etc/systemd/journald.conf"
  cur="$(grep -Ei '^[[:space:]]*Storage[[:space:]]*=' "$conf" \
    /etc/systemd/journald.conf.d/*.conf 2>/dev/null | tail -n 1)"
  if [ "$mode" = "status" ]; then
    echo "journald Storage: ${cur:-"(not set anywhere; compiled default applies)"}"
    if [ -d /var/log/journal ]; then echo "/var/log/journal exists (persistent logs active after reboot)"
    else echo "/var/log/journal missing (logs are volatile across reboots)"; fi
    return 0
  fi
  want=""; [ "$mode" = "on" ] && want="persistent"; [ "$mode" = "off" ] && want="volatile"
  if [ "$(id -u 2>/dev/null)" != "0" ]; then
    echo "Not root — rerun as root (you'll be asked for your developer-mode password):" >&2
    echo "  devel-su -c './charge-capture.sh --journal-persistent $mode'" >&2
    exit 1
  fi
  [ -f "$conf.ccap-bak" ] || cp "$conf" "$conf.ccap-bak" 2>/dev/null || true
  if grep -Eqi '^[[:space:]#]*Storage[[:space:]]*=' "$conf"; then
    sed -i "s/^[[:space:]#]*Storage[[:space:]]*=.*/Storage=$want/" "$conf" || exit 1
  else
    printf '\nStorage=%s\n' "$want" >> "$conf" || exit 1
  fi
  echo "Set: $(grep -Ei '^[[:space:]]*Storage' "$conf")"
  drop="$(grep -Ei '^[[:space:]]*Storage[[:space:]]*=' \
    /etc/systemd/journald.conf.d/*.conf 2>/dev/null | tail -n 1)"
  [ -n "$drop" ] && warn "a drop-in also sets Storage and overrides the main file: $drop"
  echo "Done. You MUST REBOOT for this to take effect,"
  echo "then run ./charge-capture.sh (and revert with --journal-persistent off afterwards)."
  if [ -t 0 ]; then
    echo "Reboot now? [y/N]"
    read -t 60 -r rb || rb=""
    case "$rb" in y|Y|yes|Yes|YES*) reboot;;
      *) echo "Not rebooting. Remember to reboot before charging.";; esac
  fi
}

snap_one() { # $1 = dest dir
  d="$1"; mkdir -p "$d" || { warn "cannot create $d"; return 1; }
  SNAP_NAME="$(basename "$d")"
  start_ep="$(date +%s)"
  log "snapshot $SNAP_NAME: starting"
  date -Iseconds > "$d/timestamp_iso.txt" 2>&1
  date +%s > "$d/timestamp_epoch.txt" 2>&1
  rd /etc/sailfish-release > "$d/sailfish_release.txt" 2>&1
  uname -a > "$d/uname.txt" 2>&1
  uptime > "$d/uptime.txt" 2>&1

  begin_step "[$SNAP_NAME] power supply uevents"
  ls -l /sys/class/power_supply/ > "$d/power_supply_ls.txt" 2>&1
  # Full concatenated uevent dump (what Jolla asked for in #32018):
  cat /sys/class/power_supply/*/uevent > "$d/power_supply_uevent_all.txt" 2>&1
  { for ps in /sys/class/power_supply/*; do
      echo "===== $ps ====="; rd "$ps/uevent"; echo
    done; } > "$d/power_supply_uevent_split.txt" 2>&1
  end_step

  begin_step "[$SNAP_NAME] battery kernel vs UI (mce dbus)"
  { echo "kernel_capacity=$(rd /sys/class/power_supply/battery/capacity)"
    echo "kernel_status=$(rd /sys/class/power_supply/battery/status)"
    echo "kernel_current_now=$(rd /sys/class/power_supply/battery/current_now)"
    echo "kernel_current_avg=$(rd /sys/class/power_supply/battery/current_avg)"
    echo "kernel_voltage_now=$(rd /sys/class/power_supply/battery/voltage_now)"
    echo "kernel_temp=$(rd /sys/class/power_supply/battery/temp)"
    echo "--- mce (UI-side, system bus) ---"
    echo "mce_get_battery_level: $(mce_level)"
    echo "mce_get_battery_state: $(mce_string get_battery_state)"
    echo "mce_get_charger_state: $(mce_string get_charger_state)"
  } > "$d/battery_vs_ui.txt" 2>&1
  end_step

  begin_step "[$SNAP_NAME] typec port + charger state"
  { echo "--- /sys/class/typec/port0 listing ---"
    ls -l /sys/class/typec/port0/ 2>&1
    for f in /sys/class/typec/port0/*; do
      [ -f "$f" ] || continue
      echo "--- $f: $(rd "$f" | head -c 300)"
    done
  } > "$d/typec_port0.txt" 2>&1

  { echo "any_online=$(any_online) (1 if any supply reports online=1)"
    echo "online_usb_type=$(online_usb_type)"
    echo "mtk-master-charger/online=$(rd /sys/class/power_supply/mtk-master-charger/online)"
    echo "mtk-master-charger/usb_type=$(rd /sys/class/power_supply/mtk-master-charger/usb_type)"
    echo "battery/status=$(rd /sys/class/power_supply/battery/status)"
    echo "--- all supplies (generic; MTK rows above are jp2601 detail) ---"
    for ps in /sys/class/power_supply/*; do
      [ -d "$ps" ] || continue
      echo "== $(basename "$ps"): type=$(rd "$ps/type" | head -n 1) online=$(rd "$ps/online" | head -n 1) usb_type=$(rd "$ps/usb_type" | head -n 1) status=$(rd "$ps/status" | head -n 1)"
    done
  } > "$d/charger_online.txt" 2>&1
  end_step

  # One-line charge state summary for this snapshot (machine + human friendly).
  { cs_iso="$(date -Iseconds)"
    cs_kc="$(rd /sys/class/power_supply/battery/capacity | head -n 1)"
    cs_st="$(rd /sys/class/power_supply/battery/status | head -n 1)"
    cs_ci="$(rd /sys/class/power_supply/battery/current_now | head -n 1)"
    cs_vo="$(rd /sys/class/power_supply/battery/voltage_now | head -n 1)"
    cs_tp="$(rd /sys/class/power_supply/battery/temp | head -n 1)"
    cs_ui="$(mce_level)"
    cs_mb="$(mce_string get_battery_state)"
    cs_mc="$(mce_string get_charger_state)"
    cs_on="$(any_online)"
    cs_ut="$(online_usb_type)"
    cs_tm="$(rd /sys/class/typec/port0/power_operation_mode | head -n 1)"
    echo "$cs_iso kernel=${cs_kc}% ${cs_st} (${cs_ci}uA, ${cs_vo}uV, ${cs_tp}dC) ui=${cs_ui}% mce_batt=${cs_mb} mce_chg=${cs_mc} online=${cs_on} usb=(${cs_ut}) typec=${cs_tm}"
    # plug context if this snapshot lives inside a run dir
    for pd in "$(dirname "$d")/plug_event.txt" \
              "$(dirname "$(dirname "$d")")/plug_event.txt"; do
      if [ -f "$pd" ]; then echo "plug: $(cat "$pd" 2>/dev/null)"; break; fi
    done
  } > "$d/charge_state.txt" 2>&1
  log "[$SNAP_NAME] charge state: $(cat "$d/charge_state.txt" 2>/dev/null | head -n 1)"

  begin_step "[$SNAP_NAME] bluetooth"
  { echo "bluetooth_service=$(with_timeout 10 systemctl is-active bluetooth 2>&1)"
    echo "--- bluez (bounded dump, filtered) ---"
    bt="$(snap_tmp)"
    with_timeout 15 dbus-send --system --print-reply --dest=org.bluez / \
      org.freedesktop.DBus.ObjectManager.GetManagedObjects > "$bt" 2>&1
    grep -E "Adapter1|Device1|Connected|Address|Name|/org/bluez" "$bt" 2>/dev/null | head -n 40
    rm -f "$bt"
  } > "$d/bluetooth.txt" 2>&1
  end_step

  # dmesg buffer is huge on this device (~30s to dump) — bound it, then tail.
  begin_step "[$SNAP_NAME] dmesg (takes ~30s, kernel log is huge)"
  dm="$(snap_tmp)"
  with_timeout 60 dmesg > "$dm" 2>&1
  if [ $? -eq 0 ]; then tail -n 300 "$dm" 2>/dev/null > "$d/dmesg_tail.txt" 2>&1
  else echo "DMESG_BOUNDED_AT_60S_OR_ERROR" > "$d/dmesg_tail.txt" 2>&1; fi
  rm -f "$dm"
  end_step
  # Unprivileged journal (defaultuser is NOT in systemd-journal, so this is
  # user-scope only; full/system journal needs devel-su, see --after-crash).
  # -n 500 limits server-side: no full-boot scan (was the main slowdown).
  begin_step "[$SNAP_NAME] journal (user scope)"
  jt="$(snap_tmp)"
  with_timeout 25 journalctl -b --no-pager -n 500 > "$jt" 2>&1
  tail -n 500 "$jt" 2>/dev/null > "$d/journal_user_current.txt" 2>&1
  rm -f "$jt"
  end_step
  { grep -Ei '^\s*Storage\s*=' /etc/systemd/journald.conf 2>/dev/null
    grep -Ei '^\s*Storage\s*=' /etc/systemd/journald.conf.d/*.conf 2>/dev/null
  } > "$d/journald_storage.txt" 2>&1
  ls -l /sys/fs/pstore/ > "$d/pstore_ls_user.txt" 2>&1
  # shellcheck disable=SC3039
  { free -h 2>&1 || free 2>&1; echo ---; df -h; } > "$d/mem_disk.txt" 2>&1
  echo "$(( $(date +%s) - start_ep ))" > "$d/snap_duration_s.txt" 2>&1
  log "snapshot $SNAP_NAME: done ($(( $(date +%s) - start_ep ))s)"
}

curve_line() { # $1 = csv file; appends one sample row
  csv="$1"
  ep="$(date +%s)"; iso="$(date -Iseconds)"
  kc="$(rd /sys/class/power_supply/battery/capacity | head -n 1)"
  st="$(rd /sys/class/power_supply/battery/status | head -n 1)"
  ci="$(rd /sys/class/power_supply/battery/current_now | head -n 1)"
  vo="$(rd /sys/class/power_supply/battery/voltage_now | head -n 1)"
  tp="$(rd /sys/class/power_supply/battery/temp | head -n 1)"
  on="$(any_online)"
  ut="$(online_usb_type | tr ' ' '_')"
  tm="$(rd /sys/class/typec/port0/power_operation_mode | head -n 1)"
  echo "${ep},${iso},${kc},${st},${ci},${vo},${tp},${on},${ut},${tm}" >> "$csv"
}

charging_now() { # exit 0 if charger appears connected (conservative)
  st="$(rd /sys/class/power_supply/battery/status)"
  [ "$(any_online)" = "1" ] && return 0
  case "$st" in Charging|Full) return 0;; *) return 1;; esac
}

sleep_with_curve() { # $1 seconds, $2 csv — sleep in CURVE_EVERY chunks
  total="$1"; csv="$2"; done_s=0
  while [ "$done_s" -lt "$total" ]; do
    step="$CURVE_EVERY"
    [ $((done_s + step)) -gt "$total" ] && step=$((total - done_s))
    sleep "$step"; done_s=$((done_s + step))
    curve_line "$csv" || true
    # heartbeat so long waits are visibly alive
    hb_kc="$(rd /sys/class/power_supply/battery/capacity | head -n 1)"
    hb_st="$(rd /sys/class/power_supply/battery/status | head -n 1)"
    log "… watching ($((total - done_s))s left) | battery ${hb_kc}% ${hb_st}"
  done
}

pack_run() { # $1 = run dir; packs sibling tar.gz, keeps tar stderr in log
  run="$1"
  # Lesson from 2026-10-04: a root-made run must stay fetchable as defaultuser
  # over ssh (cp -a preserves restrictive perms, e.g. pstore trees).
  if [ "$IS_ROOT" = "1" ]; then chmod -R a+rX "$run" 2>/dev/null || true; fi
  ( cd "$ROOT" && tar -czf "$(basename "$run")-bundle.tar.gz" \
    "$(basename "$run")" >"$run/tar.log" 2>&1 ) \
    && log "packed $ROOT/$(basename "$run")-bundle.tar.gz" \
    || warn "tar pack had issues (see $run/tar.log)"
}

# --- after-crash mode --------------------------------------------------------
after_crash() {
  run=""
  if [ -n "$AFTER_RUN" ]; then
    run="$ROOT/$AFTER_RUN"
  else
    # latest existing run, else fresh timestamped dir (timestamp picked by
    # the script — no need to pass it manually)
    run="$(ls -1dt "$ROOT"/*/ 2>/dev/null | head -n 1)"
    run="${run%/}"
    [ -z "$run" ] && run="$ROOT/$(date +%Y%m%d%H%M%S)-aftercrash"
    n_runs="$(ls -1d "$ROOT"/*/ 2>/dev/null | wc -l)"
    if [ -t 0 ] && [ "$ASSUME_YES" != "1" ] && [ "$n_runs" -gt 1 ]; then
      echo "Multiple runs exist; using latest: $(basename "$run"). OK? [Y/n]"
      read -t 60 -r okrun || okrun=""
      case "$okrun" in n|N|no|No|NO*)
        echo "Runs:"; ls -1 "$ROOT"
        echo "Rerun with an explicit timestamp: ./charge-capture.sh --after-crash <run>"
        exit 1;;
      esac
    else
      log "using run: $(basename "$run")"
    fi
  fi
  if [ -e "$run" ] && [ ! -w "$run" ]; then
    warn "cannot write to $run (root-owned?) — rerun as root so captures land in the session:"
    warn "  devel-su"
    warn "  cd /run/media/defaultuser/*/ && ./charge-capture.sh --after-crash $(basename "$run")"
    exit 1
  fi
  mkdir -p "$run" || { warn "cannot create $run"; exit 1; }
  ac="$run/after_crash_$(date +%Y%m%d%H%M%S)"; mkdir -p "$ac" || exit 1
  log "after-crash collect -> $ac"
  if [ "$IS_ROOT" = "1" ]; then log "running as ROOT: privileged captures run directly"
  else log "running as non-root: privileged captures will ask for devel-su"; fi
  log "… snapshot now (unprivileged part)"
  snap_one "$ac/snap_now"

  if [ -t 0 ] && [ "$ASSUME_YES" != "1" ]; then
    echo "This will try privileged captures via devel-su (pstore + full journals)."
    echo "Type your developer-mode password when prompted (typing is invisible). Continue? [Y/n]"
    read -t 60 -r ans || ans=""
    case "$ans" in n|N|no|No|NO*) SKIPROOT=1;; *) SKIPROOT=0;; esac
  elif [ -t 0 ]; then
    warn "--yes on a tty: will prompt for devel-su password without further notice"
    SKIPROOT=0
  else
    SKIPROOT=1
    warn "non-interactive shell: skipping devel-su captures (run in Fingerterm instead)"
  fi

  if [ "$SKIPROOT" = "0" ]; then
    need_root_pw "read /sys/fs/pstore (crash logs) + full system journals (current + previous boot)"
    # Full previous-boot journal: matti asked for the WHOLE boot, not a
    # capped tail (2026-10-04: -n 50000 started 01:17:54, missing the 01:13
    # plug event). Can be tens of MB — disk space is logged first.
    # stdout to log, stderr stays on the terminal so the password prompt is visible.
    if as_root "ls -lR /sys/fs/pstore/ > '$ac/pstore_ls_root.txt' 2>&1; \
      mkdir -p '$ac/pstore_root' && cp -a /sys/fs/pstore/. '$ac/pstore_root/' 2>&1; \
      chmod -R a+rX '$ac/pstore_root' 2>/dev/null; \
      journalctl --disk-usage > '$ac/journal_disk_usage.txt' 2>&1; \
      df -h '$ac' | tail -n 1 > '$ac/disk_space.txt' 2>&1; \
      echo '… root: previous-boot journal, FULL boot (this is the big one)'; \
      journalctl -b -1 --no-pager > '$ac/journal_prev_boot_root.txt' 2>&1; \
      echo '… root: current-boot journal, FULL boot'; \
      journalctl -b --no-pager > '$ac/journal_cur_root.txt' 2>&1; \
      echo '… root: dmesg'; \
      dmesg > '$ac/dmesg_root.txt' 2>&1; \
      chmod -R a+rX '$ac' 2>/dev/null; echo ROOT_CAPTURE_DONE" \
      > "$ac/devel_su_attempt.log"; then
      log "    done: privileged captures finished (pstore_root + journal_*_root.txt)"
    else
      warn "devel-su captures failed — wrong password or no root access."
      warn "Unprivileged snap_now* was still collected (see devel_su_attempt.log)."
    fi
  else
    log "privileged captures skipped; unprivileged snap_now* still collected"
  fi

  pack_run "$run"
  log "done. Send the tar.gz + $(basename "$ac")/battery_vs_ui.txt with your bug report."
  log "To revert persistent journals: devel-su -c './charge-capture.sh --journal-persistent off' + reboot."
}

# --- pre-charge mode ---------------------------------------------------------
precharge() {
  ts="$(date +%Y%m%d%H%M%S)"
  run="$ROOT/$ts"; mkdir -p "$run" || { warn "cannot create $run"; exit 1; }
  log "run dir: $run"
  if [ "$IS_ROOT" = "1" ]; then log "running as ROOT: privileged steps run directly"
  else log "running as non-root: privileged steps (pstore) will ask for devel-su when reached"; fi

  { echo "charge-capture $VERSION at $(date -Iseconds)"
    uname -a; rd /etc/sailfish-release; uptime
    echo "settle=$SETTLE interval=$INTERVAL count=$COUNT wait_timeout=$WAIT_TIMEOUT curve_every=$CURVE_EVERY"
  } > "$run/run_meta.txt" 2>&1

  if grep -Eqi '^\s*Storage\s*=\s*volatile' /etc/systemd/journald.conf 2>/dev/null; then
    warn "journald Storage=volatile: previous-boot journal will NOT survive a crash."
    warn "For crash debugging, persistent logs are recommended. Enable with:"
    warn "  devel-su -c './charge-capture.sh --journal-persistent on'"
    warn "  (devel-su will ask for your developer-mode password first)"
    warn "then reboot (revert afterwards with --journal-persistent off + reboot)."
    echo "volatile (previous-boot journal will be lost on crash)" > "$run/journald_mode.txt"
  else
    { grep -Ei '^\s*Storage' /etc/systemd/journald.conf 2>/dev/null
      grep -Ei '^\s*Storage' /etc/systemd/journald.conf.d/*.conf 2>/dev/null
    } > "$run/journald_mode.txt" 2>&1
    log "journald mode: $(cat "$run/journald_mode.txt" 2>/dev/null | tr '\n' ' ')"
  fi

  # Identify charger (multiple choice) + UI reading (a bug report needs
  # charger model + whether the UI % matches the kernel or not).
  auto_ui="$(mce_level)"
  auto_kc="$(rd /sys/class/power_supply/battery/capacity)"
  charger_label=""; ui_note=""
  choose_charger
  if [ "$ASSUME_YES" != "1" ]; then
    echo "What does the Sailfish battery indicator show RIGHT NOW? [auto: ${auto_ui:-?}, kernel: ${auto_kc:-?}]"
    printf "ui%%> "; read -t 120 -r ui_note || ui_note=""
    [ -z "${ui_note:-}" ] && ui_note="auto ui=${auto_ui:-?} kernel=${auto_kc:-?}"
  else
    ui_note="auto ui=${auto_ui:-?} kernel=${auto_kc:-?}"
  fi
  { echo "charger=${charger_label:-unspecified}"
    echo "ui_note=${ui_note:-?}"
    echo "auto_ui=${auto_ui:-?} auto_kernel=${auto_kc:-?} at=$(date -Iseconds)"
  } > "$run/user_notes.txt"

  snap_one "$run/snap_00_preplug"
  log "baseline snap_00_preplug done (charging NOT yet connected expected)"

  csv="$run/charge_curve.csv"
  echo "epoch,iso,kernel_capacity,status,current_now,voltage_now,temp,charger_online,usb_type,typec_mode" > "$csv"
  curve_line "$csv" || true

  # Optional: preserve + clear stale pstore so a new crash is unambiguous.
  # Needs root (pstore is only readable as root) — elevates via devel-su.
  if [ "$IS_ROOT" = "1" ]; then
    log "Running as root: backing up + clearing old /sys/fs/pstore automatically."
    log "… backing up pstore to $run/pstore_backup_root, then clearing"
    if as_root "mkdir -p '$run/pstore_backup_root' && \
        cp -a /sys/fs/pstore/. '$run/pstore_backup_root/' 2>&1 && \
        chmod -R a+rX '$run/pstore_backup_root' 2>/dev/null; \
        rm -f /sys/fs/pstore/* 2>&1; ls -l /sys/fs/pstore/" \
        > "$run/pstore_clear.log" 2>&1; then
      log "    done: pstore backed up + cleared (see pstore_clear.log)"
    else
      warn "pstore backup/clear failed (see pstore_clear.log)"
    fi
  elif [ -t 0 ] && [ "$ASSUME_YES" != "1" ]; then
    echo "Backup + CLEAR old /sys/fs/pstore so a fresh crash is unambiguous?"
    echo "(needs root — a devel-su password prompt follows if you say yes) [y/N]"
    printf "pstore> "; read -t 60 -r pans || pans=""
    if case "$pans" in y|Y|yes|Yes|YES*) true;; *) false;; esac; then
      need_root_pw "back up /sys/fs/pstore to this run dir, then clear it so a fresh crash is unambiguous"
      # stdout to log, stderr stays on the terminal so the password prompt is visible
      if as_root "mkdir -p '$run/pstore_backup_root' && \
        cp -a /sys/fs/pstore/. '$run/pstore_backup_root/' 2>&1 && \
        chmod -R a+rX '$run/pstore_backup_root' 2>/dev/null; \
        rm -f /sys/fs/pstore/* 2>&1; ls -l /sys/fs/pstore/" \
        > "$run/pstore_clear.log"; then
        log "    done: pstore backed up + cleared (result in pstore_clear.log)"
      else
        warn "pstore step failed — wrong password or no pstore access."
        warn "Continuing WITHOUT pstore clear (old crash logs stay; see pstore_clear.log)."
      fi
    else
      log "pstore left untouched"
    fi
  else
    log "pstore left untouched (needs root; rerun interactive to clear it)"
  fi

  log "NOW PLUG IN the charger (waiting up to ${WAIT_TIMEOUT}s)..."
  waited=0; plugged=0
  while [ "$waited" -lt "$WAIT_TIMEOUT" ]; do
    if charging_now; then plugged=1; break; fi
    sleep 2; waited=$((waited + 2))
    [ $((waited % 20)) -eq 0 ] && log "... still waiting (${waited}s, status=$(rd /sys/class/power_supply/battery/status | head -n 1))"
  done
  plug_iso="$(date -Iseconds)"
  echo "plug_detected=${plugged} waited_s=${waited} at=${plug_iso}" > "$run/plug_event.txt"
  if [ "$plugged" = "1" ]; then log "charger detected at $plug_iso"
  else warn "no charger detected within timeout; continuing anyway (snapshots still useful)"; fi
  curve_line "$csv" || true

  log "settling ${SETTLE}s..."
  sleep_with_curve "$SETTLE" "$csv"
  snap_dir="$run/snap_01_plus${SETTLE}s"; snap_one "$snap_dir"; curve_line "$csv" || true
  log "$(basename "$snap_dir") done — charging-window baseline"

  i=1
  while [ "$i" -le "$COUNT" ]; do
    mins=$((i * INTERVAL / 60))
    log "waiting ${INTERVAL}s for snapshot $((i + 1)) (~${mins} min after settle)..."
    sleep_with_curve "$INTERVAL" "$csv"
    snap_one "$run/snap_$(printf '%02d' $((i + 1)))_plus$((mins))min"
    curve_line "$csv" || true
    i=$((i + 1))
  done

  { echo "Charge-crash investigation run $(basename "$run")"
    echo "charger: ${charger_label:-unspecified} | ui: ${ui_note:-?} | plug: $plug_iso (detected=$plugged)"
    echo "Contents: snap_00_preplug, $(basename "$snap_dir"), $COUNT periodic snaps, charge_curve.csv"
    echo "Key files for a bug report:"
    echo "  power_supply_uevent_all.txt (= cat /sys/class/power_supply/*/uevent)"
    echo "  battery_vs_ui.txt (kernel capacity vs MCE UI level)"
    echo "  charge_state.txt (one-line charge state per snapshot)"
    echo "  journal_user_current.txt (user-scope; full journal needs devel-su, Storage=persistent)"
    echo "  pstore_* (needs devel-su; use --after-crash after a crash+reboot)"
    echo "If it crashes overnight: reboot, then run ./charge-capture.sh --after-crash"
  } > "$run/README.txt"

  pack_run "$run"
  log "done: $run"
  log "leave phone on charger; if it dies (white LED), force-reboot, then run:"
  log "  ./charge-capture.sh --after-crash"
}

if [ "$MODE" = "journal" ]; then journal_persistent "$JWANT"
elif [ "$MODE" = "aftercrash" ]; then after_crash; else precharge; fi
1 Like

@mal, I prefer to keep the conversation visible so I’m replying to your questions here rather than by email.

It seems the journal log from previous boot doesn’t contain what
happened after taking the last charge curve point which it seems you
already noted in the script file. Hopefully next time you can make sure
you get enough information from the previous boot so nothing is lost,
might be better that you just in case take the whole journal from
previous boot in case the 50000 lines is not enough.

In the next script version it will capture the full uncapped log.

I do see one possibly strange thing in the log, the device seems to
attempting to suspend while it’s supposed to be charging, I’m not quite
sure if device should suspend when charging, I will check the logs more.
I did a quick test on my own Jolla Phone and I did not see any
suspending when device was connected to USB.

From my side there’s nothing unusual: the phone sits untouched on the night stand, screen off, so it suspending overnight is what I’d expect. One thing I did purposely do differently in the last two charge cycles was to turn off the screen after the script reached the “plug in charger now” message before plugging in the charger, to better match what I would otherwise do. I.e. go to bed, take phone from pocket and plug in charger, usually directly without first turning it on to run a script.

After comparing with my five good sessions: suspending while charging is normal. Every good session’s dmesg shows constant suspend/resume cycling mid-charge, e.g. from the Sep 26 session while charging at ~2A:

PM: suspend entry 2026-09-25 22:43:19.940961 UTC
PM: suspend exit 2026-09-25 22:43:19.952564 UTC
PM: suspend entry (s2idle)
PM: suspend exit ... (12 ms later)

These entry→exit pairs show up in the dmesg tails of snapshots 02 through 07 — i.e. at every 5-minute checkpoint across the charging window, the preceding kernel log shows suspend cycling while charging at ~2A.

You mentioned you had always used the same charger in these tests, have
you tried different chargers?

First line in the OP: This is the second time this has happened out of three charging cycles using two different chargers.

On the chargers: this was already asked by @pherjung in the first post, and I answered it in the second post. One is an Aukey PA-T2, the other a Xiaomi MDY-11-EZ. I don’t know if those count as “fast” or “slow” chargers. There are dozens of fast-charging protocols at this point, so I’m not sure what the distinction is meant to be.

Since both chargers work flawlessly with every other device I have ever used them with, and since the phone crashed with both, it is an issue with the phone. As such, I’m opting to continue to charge with the newer / more powerful Xiaomi charger.

Also about your note about the usb_type,
it’s normal that only primary_chg contains the correct USB type value.

Noted.

1 Like

I sometimes get this behavior when connecting the phone to a regular USB 2.0 port on a computer.

Specific details:

  • In the USB settings, I have “Ask every time” set for the connection type.
  • The phone is locked at this point.
  • The last time it happened, the battery charge level was around 55%.
  • After connecting the USB cable there is no reaction whatsoever from the phone, even no LED. The host does not see it either.
  • Tapping the screen or pressing the power button also has no reaction.
  • Finally, when I long-press the power button, the phone boots.

I observed this on both OS versions 5.2.0.17 and 5.2.0.18.

1 Like

@matti, last Friday I captured another instance, I’m sending you the capture by email.

Timeline (CEST):

  • 23:26 — capture run started, 31%, discharging
  • 23:40:56 — plugged in at 30%, current ramps 0.45A → ~2A within ~30s
  • 23:41:27 — last snapshot, charging normally at ~2A
  • 23:42:10 — prev-boot journal ends with PM: suspend entry (s2idle); nothing after
  • Phone dead overnight, found dead in the morning; rebooted ~10:52, post-mortem collected
    10:54

Ramoops shows the same loop as Oct 4, repeating ~every 15s until the log ends abruptly
mid-traffic (reset, no panic/oops anywhere):

[name:spm&][SPM] mcusys was not off
[name:spm&] Pending Wakeup Sources: NETLINK [timerfd] battery_manager_wakelock charger
suspend wakelock 11282000.i2c:mt6720@34:mt6720-tcpc ...

The pending-wakeup list is character-identical to the Oct 4 crash (which died at 01:18:07,
4 min after plugging at 9%). This time it died ~75s after plugging at 30%. So: same
charger, suspend entry shortly after plug-in, SPM mcusys was not off loop with charger/
battery wakelocks pending, silent hang, watchdog reset. State of charge looks incidental.

1 Like