mirror of
https://github.com/YusufOzmen01/quest-kexec.git
synced 2026-10-26 19:37:39 +00:00
151 lines
6.8 KiB
Bash
Executable File
151 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Push a prepared payload plus the loader, verify hashes, and execute the kexec.
|
|
# Usage: run.sh <prepared-dir> [extra loader params...]
|
|
# Needs adb with a working `su`. Files go only to /data/local/tmp.
|
|
set -uo pipefail
|
|
HERE=$(cd "$(dirname "$0")/.." && pwd)
|
|
P=$1; shift
|
|
T=/data/local/tmp
|
|
MOD=${QKX_LOADER_MODULE:-$HERE/module/quest_kexec.ko}
|
|
MARKER=${QKX_MARKER_MODULE:-$HERE/module/marker_read.ko}
|
|
PARAMS="execute=1 preserve_watchdog=1 watchdog_recovery=0 core_hang_control=2 \
|
|
flush_rpmh=1 suspend_syncboss=1 disconnect_qmp=0 phase_delay_ms=300 $*"
|
|
|
|
for f in "$MOD" "$MARKER" "$P/Image" "$P/initramfs" "$P/boot.dtb"; do
|
|
[ -f "$f" ] || { echo "missing $f"; exit 1; }
|
|
done
|
|
INITRD=$P/initramfs
|
|
PRIVATE_INITRD=
|
|
if [ -f "$P/require-stock-calibration" ] || [ "${QKX_INHERIT_CALIBRATION:-0}" = 1 ]; then
|
|
PRIVATE_INITRD=$(mktemp)
|
|
trap 'rm -f "$PRIVATE_INITRD"' EXIT
|
|
python3 "$HERE/tools/stock-calibration-initramfs.py" "$INITRD" "$PRIVATE_INITRD" || exit 1
|
|
INITRD=$PRIVATE_INITRD
|
|
fi
|
|
# Stock hyp-assigns secure ION pages to other VMs; they stay locked after
|
|
# kexec and any access from the next kernel hangs the CPU. Collect them as
|
|
# late as possible and hand them over as one no-map reserved-memory node.
|
|
DTB=$P/boot.dtb
|
|
SECMAP=${QKX_SECMAP_MODULE:-$HERE/module/ion_secmap.ko}
|
|
if [ -f "$SECMAP" ]; then
|
|
adb push "$SECMAP" $T/ion_secmap.ko >/dev/null
|
|
# Preserve Android's live hardware state by default. Stopping services
|
|
# drops hardware/firmware votes and prevents this target from starting.
|
|
if [ "${QKX_STOP_ANDROID:-0}" = 1 ]; then
|
|
adb shell "su -c 'stop; for s in cameraserver virtual_camera bootanim \
|
|
vendor.oculus.hardware.composer-service vendor.qti.hardware.display.composer \
|
|
vendor.qti.hardware.display.allocator; do stop \$s; done; sleep 8; \
|
|
echo 3 > /proc/sys/vm/drop_caches; sleep 2'"
|
|
fi
|
|
if [ "${GIVEBACK:-0}" = 1 ]; then
|
|
# Return the pages to HLOS with the stock unassign calls; only
|
|
# what the hypervisor refuses still needs reserving.
|
|
adb push "$HERE/module/qkx_giveback.ko" $T/qkx_giveback.ko >/dev/null
|
|
adb shell "su -c 'insmod $T/qkx_giveback.ko; dmesg'" | tac |
|
|
sed '/giveback: begin/q' > "$P/giveback.txt"
|
|
grep -o 'giveback: returned.*' "$P/giveback.txt" ||
|
|
{ echo "giveback did not run"; exit 1; }
|
|
grep -o 'giveback: kept 0x[0-9a-f]*-0x[0-9a-f]*' "$P/giveback.txt" |
|
|
sed 's/giveback: kept/ionsec:/' > "$P/ionsec.txt"
|
|
else
|
|
adb shell "su -c 'insmod $T/ion_secmap.ko; dmesg'" | tac | sed '/ionsec: begin/q' |
|
|
grep -o 'ionsec: 0x[0-9a-f]*-0x[0-9a-f]*' > "$P/ionsec.txt"
|
|
[ -s "$P/ionsec.txt" ] || { echo "ion_secmap returned no ranges"; exit 1; }
|
|
fi
|
|
fi
|
|
if [ -s "$P/ionsec.txt" ] && [ -f "$SECMAP" ]; then
|
|
DTB=$(mktemp --suffix=.dtb)
|
|
cp "$P/boot.dtb" "$DTB"
|
|
# Early memblock can't grow its region array, so every no-map range
|
|
# costs a split; merge into a few 2 MiB-aligned blocks.
|
|
REG=$(python3 - "$P/ionsec.txt" <<'EOF'
|
|
import re, sys
|
|
A = 2 << 20
|
|
m = []
|
|
for a, b in sorted(tuple(int(x, 16) for x in re.findall(r'0x[0-9a-f]+', l))
|
|
for l in open(sys.argv[1])):
|
|
a &= ~(A - 1)
|
|
b = (b + A - 1) & ~(A - 1)
|
|
if m and a <= m[-1][1]:
|
|
m[-1][1] = max(m[-1][1], b)
|
|
else:
|
|
m.append([a, b])
|
|
print(' '.join('0x%x 0x%x 0x%x 0x%x' % (a >> 32, a & 0xffffffff,
|
|
(b - a) >> 32, (b - a) & 0xffffffff) for a, b in m))
|
|
EOF
|
|
)
|
|
fdtput -c "$DTB" /reserved-memory/qkx-ionsec &&
|
|
fdtput -t x "$DTB" /reserved-memory/qkx-ionsec reg $REG &&
|
|
fdtput -t s "$DTB" /reserved-memory/qkx-ionsec no-map "" ||
|
|
{ echo "failed to add ionsec reservation"; exit 1; }
|
|
echo "reserved $(wc -l < "$P/ionsec.txt") secure ranges as $(($(echo $REG | wc -w) / 4)) blocks"
|
|
fi
|
|
|
|
# The appended calibration archive changes the initrd length. Keep /chosen
|
|
# consistent with the verified bytes staged by the loader.
|
|
if [ -n "$PRIVATE_INITRD" ]; then
|
|
if [ "$DTB" = "$P/boot.dtb" ]; then
|
|
DTB=$(mktemp --suffix=.dtb)
|
|
cp "$P/boot.dtb" "$DTB"
|
|
fi
|
|
END=$(python3 - "$DTB" "$INITRD" <<'EOF'
|
|
import pathlib, subprocess, sys
|
|
cells = subprocess.check_output(['fdtget', '-t', 'x', sys.argv[1], '/chosen', 'linux,initrd-start'], text=True).split()
|
|
start = 0
|
|
for c in cells:
|
|
start = (start << 32) | int(c, 16)
|
|
end = start + pathlib.Path(sys.argv[2]).stat().st_size
|
|
print(hex(end >> 32), hex(end & 0xffffffff))
|
|
EOF
|
|
) || exit 1
|
|
fdtput -t x "$DTB" /chosen linux,initrd-end $END || exit 1
|
|
fi
|
|
|
|
adb push "$MOD" $T/qkx.ko >/dev/null
|
|
adb push "$MARKER" $T/qkx_marker.ko >/dev/null
|
|
adb push "$P/Image" $T/qkx-Image >/dev/null
|
|
adb push "$INITRD" $T/qkx-initramfs >/dev/null
|
|
adb push "$DTB" $T/qkx-boot.dtb >/dev/null
|
|
adb shell 'su -c "sync; sync"'
|
|
for pair in "$MOD:qkx.ko" "$P/Image:qkx-Image" "$INITRD:qkx-initramfs" "$DTB:qkx-boot.dtb"; do
|
|
l=${pair%%:*}; r=${pair##*:}
|
|
[ "$(md5sum < "$l" | cut -d' ' -f1)" = "$(adb shell "su -c 'md5sum $T/$r'" | awk '{print $1}')" ] ||
|
|
{ echo "hash mismatch: $r"; exit 1; }
|
|
done
|
|
# The PMIC latches why the SoC last went down. Read it here, before this run
|
|
# overwrites it, so a failed attempt can be attributed afterwards.
|
|
adb shell "su -c 'dmesg | grep -iE \"Power-on reason|Power-off reason\"'" 2>/dev/null |
|
|
sed 's/^\[[^]]*\] *//' | tr -d '\r' > "$P/prev-reset-reason.txt"
|
|
[ -s "$P/prev-reset-reason.txt" ] &&
|
|
echo "previous shutdown: $(grep -m1 -i 'Power-off reason' "$P/prev-reset-reason.txt" | sed 's/.*Power-off reason: //')"
|
|
|
|
# QSEE listeners are registered in TrustZone against stock-kernel buffers.
|
|
# Freezing their owners preserves those stale registrations across kexec, and
|
|
# the target then hangs in __qseecom_scm_call2_locked. Stop only TEE clients;
|
|
# do not use Android's broad `stop`, which drops required hardware votes.
|
|
if [ "${QKX_STOP_QSEE:-0}" = 1 ]; then
|
|
adb shell "su -c 'for s in vendor.oculus.hardware.attestation-service \
|
|
vendor.oculus.devicecert-hal-1-0 gatekeeperd gatekeeper-1-0 \
|
|
vendor.keymaster-4-1 vendor.spdaemon vendor.qseecomd; do \
|
|
setprop ctl.stop \$s; done; sleep 2; \
|
|
echo qsee_services_stopped; \
|
|
ps -A | grep -E \"qseecomd|spdaemon|keymaster|gatekeeper|attestation|devicecert\" || true'" | tr -d '\r'
|
|
fi
|
|
|
|
# Invalidate the previous retained log so a reset can't reuse it.
|
|
adb shell "su -c 'insmod $T/qkx_marker.ko clear=1; rmmod marker_read'" >/dev/null 2>&1
|
|
# Assignments made after the ion_secmap snapshot would be missing from the
|
|
# reservation; hypwatch (if loaded) shows whether that window saw any.
|
|
adb shell "su -c 'cat /proc/qkx_hypwatch 2>/dev/null'" > "$P/hypwatch.txt" &&
|
|
[ -s "$P/hypwatch.txt" ] && echo "hypwatch before jump: $(head -1 "$P/hypwatch.txt")"
|
|
echo "verified; executing: $PARAMS"
|
|
adb shell "su -c 'insmod $T/qkx.ko image=$T/qkx-Image initrd=$T/qkx-initramfs dtb=$T/qkx-boot.dtb $PARAMS'"
|
|
sleep 2
|
|
if adb shell 'su -c true' >/dev/null 2>&1; then
|
|
echo "loader returned without jumping:"
|
|
adb shell 'su -c "dmesg | grep quest_kexec | tail -5"'
|
|
adb shell 'su -c "rmmod quest_kexec"' >/dev/null 2>&1
|
|
exit 1
|
|
fi
|
|
echo "jumped. Expect USB gadget 1d6b:0105; then: telnet 10.42.0.2"
|