mirror of
https://github.com/YusufOzmen01/quest-kexec.git
synced 2026-10-26 19:37:39 +00:00
Simplify documentation and add USB kernel log module
This commit is contained in:
15 files changed
+548
-278
No files matched your search
@@ -1,9 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Standalone Quest Pro ARM64 kexec loader repository.
|
||||
- Reproducible ECM-only BusyBox initramfs with DHCP and telnet shell.
|
||||
- Device capture, payload preparation, hash-verified run and retained-log tools.
|
||||
- Kernel patch series matching the maintained `qkx-kexec` branch.
|
||||
- Validated with 8 CPUs, DRM, USB, QMP, syncboss, camera CPAS/CDM and SMB5.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
Keep hardware/OS specifics explicit in commit messages. Do not commit boot
|
||||
images, firmware, device captures, serial numbers, vbmeta digests, proprietary
|
||||
binaries, or files pulled from partitions. Generated payloads belong under
|
||||
`out/` and are ignored.
|
||||
|
||||
A hardware test report should state the Android build/kernel, target commit,
|
||||
loader parameters, target command line additions, power/cable state, and whether
|
||||
retained/pstore evidence survived.
|
||||
@@ -0,0 +1,94 @@
|
||||
# Build
|
||||
|
||||
There are two kernel builds:
|
||||
|
||||
- **Android kernel:** used to compile `quest_kexec.ko`
|
||||
- **Target kernel:** the patched kernel booted by kexec
|
||||
|
||||
## Requirements
|
||||
|
||||
Install:
|
||||
|
||||
- Clang and LLD
|
||||
- `aarch64-linux-gnu-` GCC/binutils
|
||||
- `adb`, Python 3, `dtc`, `fdtput`, `curl`, `make`
|
||||
|
||||
## 1. Build the Android kernel tree
|
||||
|
||||
The loader must match the kernel reported by:
|
||||
|
||||
```sh
|
||||
adb shell uname -r
|
||||
```
|
||||
|
||||
For OS build `51503870024400340`, use Meta kernel commit:
|
||||
|
||||
```text
|
||||
fa2e480a85c6adbcc73dfc4f0ab0728781e14584
|
||||
```
|
||||
|
||||
Save the running config:
|
||||
|
||||
```sh
|
||||
mkdir -p android-out
|
||||
adb exec-out 'su -c "zcat /proc/config.gz"' > android-out/.config
|
||||
```
|
||||
|
||||
Disable the automatic git suffix, then build `Image modules`. A full modules
|
||||
build is needed for a correct `Module.symvers`.
|
||||
|
||||
```sh
|
||||
SRC=/path/to/android-kernel-source
|
||||
OUT=$PWD/android-out
|
||||
$SRC/scripts/config --file "$OUT/.config" --disable LOCALVERSION_AUTO
|
||||
|
||||
make -C "$SRC" O="$OUT" ARCH=arm64 CC=clang LD=ld.lld HOSTCC=clang \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- CLANG_TRIPLE=aarch64-linux-gnu- \
|
||||
LOCALVERSION=-g49638c7a8637 olddefconfig
|
||||
|
||||
make -C "$SRC" O="$OUT" ARCH=arm64 CC=clang LD=ld.lld HOSTCC=clang \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- CLANG_TRIPLE=aarch64-linux-gnu- \
|
||||
LOCALVERSION=-g49638c7a8637 KCFLAGS=-I$SRC/drivers/pinctrl \
|
||||
-j"$(nproc)" Image modules
|
||||
```
|
||||
|
||||
## 2. Build the kexec and log modules
|
||||
|
||||
```sh
|
||||
make module KDIR="$OUT" LOCALVERSION=-g49638c7a8637
|
||||
modinfo -F vermagic module/quest_kexec.ko
|
||||
```
|
||||
|
||||
The vermagic must start with the output of `adb shell uname -r`.
|
||||
|
||||
## 3. Build the target kernel
|
||||
|
||||
Use the patched kernel fork:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/YusufOzmen01/oculus-linux-kernel
|
||||
cd oculus-linux-kernel
|
||||
git checkout oculus-quest-pro-kernel-master
|
||||
```
|
||||
|
||||
Build it using the Quest Pro config. The target needs at least:
|
||||
|
||||
```text
|
||||
CONFIG_LOCALVERSION_AUTO=n
|
||||
CONFIG_ION_POOL_AUTO_REFILL=n
|
||||
CONFIG_USB_CONFIGFS_ECM=y
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_PRINTK_TIME=y
|
||||
CONFIG_LOG_BUF_SHIFT=20
|
||||
```
|
||||
|
||||
The result must be a raw ARM64 `Image`.
|
||||
|
||||
## 4. Build the initramfs
|
||||
|
||||
```sh
|
||||
make initramfs
|
||||
```
|
||||
|
||||
This downloads and verifies BusyBox 1.36.1, builds it statically for ARM64 and
|
||||
creates `out/initramfs.gz`.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Port a custom Quest Pro kernel
|
||||
|
||||
This project is for Quest Pro only. Porting here means adapting another Quest
|
||||
Pro 4.19 kernel release, not another headset or SoC.
|
||||
|
||||
## 1. Start from the matching Meta source
|
||||
|
||||
Find the Meta source commit for the headset OS version. Keep a clean copy for
|
||||
building the Android loader module.
|
||||
|
||||
## 2. Apply the target patches
|
||||
|
||||
The maintained kernel fork already contains the changes. For another source
|
||||
revision, apply the patches in order:
|
||||
|
||||
```sh
|
||||
git am /path/to/quest-pro-kexec/kernel/patches/*.patch
|
||||
```
|
||||
|
||||
Resolve conflicts by behavior, not by blindly choosing one side. The important
|
||||
areas are:
|
||||
|
||||
- ARM64 entry and SMP/GIC state
|
||||
- apps SMMU and msm_bus/RPMh
|
||||
- display clocks/GDSCs/SDE
|
||||
- DWC3 and USB PHY
|
||||
- AOP QMP adoption
|
||||
- SPMI stale IRQ cleanup
|
||||
- camera CPAS/CDM
|
||||
- SMB5 charger handoff
|
||||
- retained log and boot watchdog
|
||||
|
||||
## 3. Keep the required target command line
|
||||
|
||||
`tools/prep.sh` adds:
|
||||
|
||||
```text
|
||||
rdinit=/init nokaslr qkx_qmp_adopt=1
|
||||
initcall_blacklist=virtual_sensor_driver_init
|
||||
```
|
||||
|
||||
Do not use loader-side QMP disconnect. Taking the AP/AOP link down caused a PMIC
|
||||
reset. The target must adopt the inherited link instead.
|
||||
|
||||
## 4. Keep the staging layout
|
||||
|
||||
```text
|
||||
Image 0x90080000
|
||||
initramfs 0x93000000
|
||||
DTB 0x93200000
|
||||
retained log 0x9ba80000 (64 KiB)
|
||||
```
|
||||
|
||||
The Image memory size must be at most `0x2f80000`. Initramfs and DTB must each
|
||||
fit in 2 MiB.
|
||||
|
||||
## 5. Test in small steps
|
||||
|
||||
First boot with the supplied RAM-only initramfs. Check:
|
||||
|
||||
```sh
|
||||
getconf _NPROCESSORS_ONLN
|
||||
ls /dev/dri
|
||||
ip addr show usb0
|
||||
dmesg | grep -E 'qkxqmp|QPNP SMB5|cam_hw_cdm_init'
|
||||
```
|
||||
|
||||
Only move to a larger userspace after the basic target is repeatable.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Use
|
||||
|
||||
## 1. Capture the running device state
|
||||
|
||||
Boot rooted Android and connect ADB:
|
||||
|
||||
```sh
|
||||
tools/capture.sh out/captured
|
||||
```
|
||||
|
||||
This reads the runtime device tree, iomem, command line and kernel config. It
|
||||
does not modify the device.
|
||||
|
||||
Use the runtime tree from `/proc/device-tree`. The original
|
||||
`/sys/firmware/fdt` blob is not safe for this handoff.
|
||||
|
||||
## 2. Prepare the payload
|
||||
|
||||
```sh
|
||||
tools/prep.sh out/captured /path/to/target/Image \
|
||||
out/initramfs.gz out/payload
|
||||
```
|
||||
|
||||
## 3. Boot it
|
||||
|
||||
```sh
|
||||
tools/run.sh out/payload
|
||||
```
|
||||
|
||||
The script freshly pushes and verifies the module, Image, initramfs and DTB.
|
||||
All device files are placed under `/data/local/tmp`.
|
||||
|
||||
## 4. Connect to the shell
|
||||
|
||||
Find the new Ethernet interface with `ip link`. Then:
|
||||
|
||||
```sh
|
||||
nmcli device connect <interface>
|
||||
telnet 10.42.0.2
|
||||
```
|
||||
|
||||
If DHCP is unavailable:
|
||||
|
||||
```sh
|
||||
sudo ip addr add 10.42.0.1/24 dev <interface>
|
||||
telnet 10.42.0.2
|
||||
```
|
||||
|
||||
Run one command without telnet:
|
||||
|
||||
```sh
|
||||
python3 tools/qkx_sh.py 'uname -a'
|
||||
```
|
||||
|
||||
## 5. Return to Android
|
||||
|
||||
```sh
|
||||
python3 tools/qkx_sh.py 'echo r > /proc/qkx_bootdone'
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
After Android returns:
|
||||
|
||||
```sh
|
||||
tools/read-log.sh
|
||||
```
|
||||
|
||||
This loads `marker_read.ko` and prints the target's retained RAM log.
|
||||
|
||||
To capture Android kernel messages over the existing USB gadget before kexec:
|
||||
|
||||
```sh
|
||||
tools/load-usb-log.sh 60
|
||||
```
|
||||
|
||||
`usb_log.ko` hooks the active gadget's EP0 callback and intentionally cannot be
|
||||
unloaded safely. Reboot Android to remove it.
|
||||
@@ -1,101 +1,62 @@
|
||||
# Quest Pro kexec
|
||||
|
||||
Experimental ARM64 kexec loader and RAM-only Linux environment for Meta Quest Pro
|
||||
(Seacliff/Kona, Linux 4.19). It hands execution from the rooted Android kernel to
|
||||
a patched Quest kernel entirely from RAM. No partition is flashed or mounted.
|
||||
A RAM-only ARM64 kexec loader for Meta Quest Pro.
|
||||
|
||||
The included initramfs exposes a BusyBox shell over USB Ethernet (ECM):
|
||||
It boots a patched Linux kernel from rooted Android without flashing a
|
||||
partition. The included initramfs starts a BusyBox shell over USB Ethernet.
|
||||
|
||||
- target: `10.42.0.2`
|
||||
- host: `10.42.0.1` (offered by target DHCP)
|
||||
## Current status
|
||||
|
||||
Tested on Quest Pro OS build `51503870024400340`.
|
||||
|
||||
Working:
|
||||
|
||||
- all 8 CPUs
|
||||
- DRM and both panels
|
||||
- USB Ethernet
|
||||
- AOP/QMP handoff
|
||||
- syncboss
|
||||
- camera CPAS/CDM
|
||||
- SPMI and SMB5 charging
|
||||
|
||||
The patched kernel is in the `oculus-quest-pro-kernel-master` branch of:
|
||||
|
||||
https://github.com/YusufOzmen01/oculus-linux-kernel
|
||||
|
||||
The same changes are also included as patch files in `kernel/patches/`.
|
||||
|
||||
## Layout
|
||||
|
||||
- `module/` — kexec module and log modules
|
||||
- `initramfs/` — BusyBox USB-network initramfs
|
||||
- `tools/` — capture, prepare, run and shell scripts
|
||||
- `kernel/patches/` — Quest Pro target-kernel changes
|
||||
- `HOW_TO_BUILD.md` — build instructions
|
||||
- `HOW_TO_USE.md` — boot and shell instructions
|
||||
- `HOW_TO_PORT.md` — adapting another Quest Pro kernel
|
||||
- `TROUBLESHOOTING.md` — common failures
|
||||
|
||||
## Network shell
|
||||
|
||||
The initramfs exposes one ECM USB function:
|
||||
|
||||
- headset: `10.42.0.2`
|
||||
- host: `10.42.0.1`
|
||||
- shell: `telnet 10.42.0.2`
|
||||
- no USB serial functions and no block-device mounts
|
||||
|
||||
## Status
|
||||
|
||||
Validated on Quest Pro OS build `51503870024400340`, Android kernel
|
||||
`4.19.325-cip128-st12-g49638c7a8637`, Meta source commit
|
||||
`fa2e480a85c6adbcc73dfc4f0ab0728781e14584`.
|
||||
|
||||
Working in the RAM-only target: 8 CPUs, DRM/panels, USB ECM, AOP QMP handoff,
|
||||
syncboss, camera CPAS/CDM, SPMI and SMB5 charging. The target kernel patches are
|
||||
also maintained on the `qkx-kexec` branch of
|
||||
[`YusufOzmen01/oculus-linux-kernel`](https://github.com/YusufOzmen01/oculus-linux-kernel).
|
||||
Patch files are mirrored under `kernel/patches/` for review and portability.
|
||||
The target runs DHCP, so NetworkManager can normally configure the host
|
||||
interface automatically.
|
||||
|
||||
## Safety
|
||||
|
||||
This is research software. A failed handoff can freeze or reboot the headset.
|
||||
Keep the stock boot image available and use only on a rooted device you can
|
||||
recover. The supplied scripts only write temporary files under
|
||||
`/data/local/tmp`; they do not flash, format or mount partitions.
|
||||
This is experimental. A bad handoff can reboot or lock the headset.
|
||||
|
||||
Do **not** use a loader module built for a different running Android kernel.
|
||||
Module ABI mismatches can crash Android during an irreversible shutdown.
|
||||
The included scripts only copy files to `/data/local/tmp`. They do not flash,
|
||||
format or mount partitions.
|
||||
|
||||
## Host requirements
|
||||
|
||||
Linux host with:
|
||||
|
||||
- `adb`, root access through `adb shell 'su -c ...'`
|
||||
- Clang/LLD and an `aarch64-linux-gnu-` toolchain
|
||||
- Python 3, `dtc`, `fdtput`, `curl`, `make`, `cpio`, `gzip`
|
||||
- NetworkManager or another way to configure the ECM host interface
|
||||
|
||||
## Quick start
|
||||
|
||||
See [docs/build.md](docs/build.md) for complete kernel/module build steps.
|
||||
Assuming the patched target `Image` and matching modules already exist:
|
||||
|
||||
```sh
|
||||
# Build static BusyBox and the ECM-only initramfs
|
||||
make initramfs
|
||||
|
||||
# Build loader against the *running Android kernel's* completed build tree
|
||||
make module KDIR=/path/to/android-kernel-out \
|
||||
LOCALVERSION=-g49638c7a8637
|
||||
|
||||
# Capture runtime DT, iomem, cmdline, config (read-only on device)
|
||||
tools/capture.sh out/captured
|
||||
|
||||
# Prepare a payload using the patched target kernel
|
||||
tools/prep.sh out/captured /path/to/target/Image \
|
||||
out/initramfs.gz out/payload
|
||||
|
||||
# Freshly pushes and verifies every file before jumping
|
||||
tools/run.sh out/payload
|
||||
```
|
||||
|
||||
After USB re-enumerates, NetworkManager should obtain `10.42.0.1` from the
|
||||
target. If not:
|
||||
|
||||
```sh
|
||||
nmcli device connect <ECM-interface>
|
||||
# or: sudo ip addr add 10.42.0.1/24 dev <ECM-interface>
|
||||
telnet 10.42.0.2
|
||||
```
|
||||
|
||||
Return to Android while the target is running:
|
||||
|
||||
```sh
|
||||
python3 tools/qkx_sh.py 'echo r > /proc/qkx_bootdone'
|
||||
```
|
||||
|
||||
After Android returns, retrieve a retained target log:
|
||||
|
||||
```sh
|
||||
tools/read-log.sh
|
||||
```
|
||||
|
||||
## Repository layout
|
||||
|
||||
- `module/` — kexec loader, ARM64 transition code, retained-log reader
|
||||
- `initramfs/` — ECM-only PID 1 and reproducible BusyBox/initramfs builders
|
||||
- `tools/` — device capture, payload preparation, run and shell helpers
|
||||
- `kernel/patches/` — patches required by the target kernel
|
||||
- `docs/` — detailed build, usage and architecture notes
|
||||
Always build `quest_kexec.ko` against the exact Android kernel currently running
|
||||
on the headset. Never force-load a module with the wrong ABI.
|
||||
|
||||
## License
|
||||
|
||||
GPL-2.0. See `LICENSE`. BusyBox is downloaded from upstream and built locally;
|
||||
its binary and source are not committed.
|
||||
GPL-2.0. See `LICENSE`.
|
||||
@@ -1,8 +0,0 @@
|
||||
# Security
|
||||
|
||||
The loader deliberately crosses a kernel trust boundary and executes a second
|
||||
kernel without a bootloader verification step. Use it only on hardware you own.
|
||||
Do not distribute boot images, firmware, device keys or captures.
|
||||
|
||||
Report security-sensitive issues privately to the repository owner rather than
|
||||
posting device identifiers or logs containing secrets in a public issue.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Module refuses to load
|
||||
|
||||
Check:
|
||||
|
||||
```sh
|
||||
adb shell uname -r
|
||||
modinfo -F vermagic module/quest_kexec.ko
|
||||
```
|
||||
|
||||
Rebuild against the exact Android source, config, local version and
|
||||
`Module.symvers`. Do not force-load it.
|
||||
|
||||
## USB appears but the shell is unreachable
|
||||
|
||||
The ECM-only interface may have a different name than older ACM+ECM payloads.
|
||||
Find it with `ip link`, then use NetworkManager or assign `10.42.0.1/24`.
|
||||
|
||||
## Android returns immediately
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
tools/read-log.sh
|
||||
```
|
||||
|
||||
A retained log means the target reached the log ring. Pstore may instead show a
|
||||
loader panic inside Android.
|
||||
|
||||
## No retained log
|
||||
|
||||
A zero marker can mean a PMIC hard reset or a failure before the target log was
|
||||
initialized. Do not assume the last target driver caused it without a logged
|
||||
checkpoint.
|
||||
|
||||
## Device locks up
|
||||
|
||||
Force reboot only when necessary; it usually erases the useful RAM log. Do not
|
||||
repeat the same payload blindly. Add a checkpoint or narrow the boundary first.
|
||||
@@ -1,50 +0,0 @@
|
||||
# Architecture and handoff notes
|
||||
|
||||
## Memory layout
|
||||
|
||||
| Payload | Physical address |
|
||||
|---|---:|
|
||||
| target Image | `0x90080000` |
|
||||
| initramfs | `0x93000000` |
|
||||
| DTB | `0x93200000` |
|
||||
| retained ring | `0x9ba80000`, 64 KiB |
|
||||
|
||||
The loader stages segments in ordinary pages, freezes Android userspace,
|
||||
quiesces GPU, syncboss and DWC3, calls the normal device shutdown path, offlines
|
||||
secondary CPUs, flushes RPMh, copies the payload through an identity-mapped
|
||||
ARM64 transition and enters the target at EL1.
|
||||
|
||||
## Important inherited-state fixes
|
||||
|
||||
The target patch series contains the fixes established during bring-up:
|
||||
|
||||
- SMP/GIC and apps-SMMU inherited-state tolerance
|
||||
- msm_bus/RPMh QoS handling (Quest's nonexistent UFS-card master is skipped)
|
||||
- display GDSCs, Display CC, SDE RSC and DRM handoff
|
||||
- DWC3/USB PHY gadget handoff
|
||||
- AOP QMP link adoption and inherited QDSS vote adoption
|
||||
- syncboss quiesce through Android's PM callback
|
||||
- SPMI stale interrupt disable
|
||||
- CPAS/CCI child-node lookups and camera/CDM diagnostics
|
||||
- SMB5 probe with unsafe synthetic initial IRQ callbacks skipped
|
||||
- retained log plus boot watchdog diagnostics
|
||||
|
||||
## Runtime tree requirement
|
||||
|
||||
Use the runtime tree reconstructed from `/proc/device-tree`, not the original
|
||||
blob in `/sys/firmware/fdt`. The original boot FDT caused a cold reset in a
|
||||
validated comparison test. `tools/capture.sh` handles the correct conversion.
|
||||
|
||||
## QMP
|
||||
|
||||
Never take the AP-to-AOP link down during handoff. Loader-side QMP disconnect
|
||||
caused an immediate PMIC reset. The target adopts the inherited physical link,
|
||||
recycles only the logical channel and imports the negotiated mailbox metadata.
|
||||
The required target command line option is `qkx_qmp_adopt=1`.
|
||||
|
||||
## SMB5
|
||||
|
||||
Android's normal `device_shutdown()` already calls `smb5_shutdown()`. The target
|
||||
must not manually call the 11 synthetic initial-status interrupt handlers after
|
||||
kexec; they reset or lock the PMIC/USB-C path. The patched driver defaults to
|
||||
skipping those calls. Charging and real plug interrupts remain functional.
|
||||
@@ -1,70 +0,0 @@
|
||||
# Building
|
||||
|
||||
There are two kernels involved:
|
||||
|
||||
1. **Android kernel** currently running on the headset. It is used only to build
|
||||
`quest_kexec.ko`; source, configuration, release suffix and `Module.symvers`
|
||||
must match the device exactly.
|
||||
2. **Target kernel** entered by kexec. It is the Meta Quest Pro kernel with the
|
||||
patches in `kernel/patches/` (or branch `qkx-kexec` in the companion fork).
|
||||
|
||||
## Android loader ABI tree
|
||||
|
||||
For OS build `51503870024400340`, use Meta commit `fa2e480a85...`, copy the
|
||||
headset's `/proc/config.gz` to the output `.config`, disable
|
||||
`CONFIG_LOCALVERSION_AUTO`, and build `Image modules` with local version
|
||||
`-g49638c7a8637`. A full module build is required to produce correct symbol
|
||||
CRCs in `Module.symvers`.
|
||||
|
||||
The vendor audio tree needs its pinctrl include directory in `KCFLAGS`:
|
||||
|
||||
```sh
|
||||
SRC=/path/to/meta-kernel-at-fa2e480a85
|
||||
OUT=/path/to/android-out
|
||||
adb exec-out 'su -c "zcat /proc/config.gz"' > "$OUT/.config"
|
||||
$SRC/scripts/config --file "$OUT/.config" --disable LOCALVERSION_AUTO
|
||||
make -C "$SRC" O="$OUT" ARCH=arm64 CC=clang LD=ld.lld HOSTCC=clang \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- CLANG_TRIPLE=aarch64-linux-gnu- \
|
||||
LOCALVERSION=-g49638c7a8637 olddefconfig
|
||||
make -C "$SRC" O="$OUT" ARCH=arm64 CC=clang LD=ld.lld HOSTCC=clang \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- CLANG_TRIPLE=aarch64-linux-gnu- \
|
||||
LOCALVERSION=-g49638c7a8637 KCFLAGS=-I$SRC/drivers/pinctrl -j8 Image modules
|
||||
make module KDIR="$OUT" LOCALVERSION=-g49638c7a8637
|
||||
modinfo -F vermagic module/quest_kexec.ko
|
||||
```
|
||||
|
||||
The reported vermagic must begin with the output of `adb shell uname -r`.
|
||||
|
||||
## Target kernel
|
||||
|
||||
Clone `YusufOzmen01/oculus-linux-kernel`, then check out `qkx-kexec`. Or apply
|
||||
all patches in order to upstream Quest Pro commit `42dc87a978`:
|
||||
|
||||
```sh
|
||||
git am /path/to/quest-pro-kexec/kernel/patches/*.patch
|
||||
```
|
||||
|
||||
Start from the device config and make these configuration changes:
|
||||
|
||||
```text
|
||||
CONFIG_LOCALVERSION_AUTO=n
|
||||
CONFIG_ION_POOL_AUTO_REFILL=n
|
||||
CONFIG_USB_CONFIGFS_ECM=y
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_PRINTK_TIME=y
|
||||
CONFIG_LOG_BUF_SHIFT=20
|
||||
```
|
||||
|
||||
Build a raw ARM64 `Image`. The repository's staging layout requires text offset
|
||||
`0x80000`, a relocatable little-endian 4 KiB-page image, maximum memory size
|
||||
`0x2f80000`, and maximum initramfs/DTB size 2 MiB each.
|
||||
|
||||
## BusyBox/initramfs
|
||||
|
||||
```sh
|
||||
make initramfs
|
||||
```
|
||||
|
||||
`initramfs/busybox.sh` downloads BusyBox 1.36.1 from busybox.net, verifies its
|
||||
published SHA-256, then cross-builds it static. `initramfs/build.py` verifies the
|
||||
binary is static ARM64 and generates deterministic gzip/newc output.
|
||||
@@ -1,44 +0,0 @@
|
||||
# Usage and troubleshooting
|
||||
|
||||
## Normal run
|
||||
|
||||
1. Charge the headset and boot rooted Android.
|
||||
2. Build loader for that exact Android kernel.
|
||||
3. Build patched target kernel and initramfs.
|
||||
4. Run `tools/capture.sh out/captured` after every OS/device-tree change.
|
||||
5. Prepare and execute as shown in the README.
|
||||
|
||||
Each execution freshly pushes Image, initramfs and DTB, runs `sync` twice and
|
||||
verifies MD5 hashes before inserting the loader.
|
||||
|
||||
## Target shell
|
||||
|
||||
The initramfs advertises one USB ECM function (VID:PID `1d6b:0105`) with fixed
|
||||
MAC addresses. The interface name differs from old ACM+ECM payloads because ECM
|
||||
is now interface zero. Prefer finding it by MAC or `ip link` rather than hard
|
||||
coding its name.
|
||||
|
||||
`udhcpd` offers only `10.42.0.1/24`; it advertises no gateway or DNS, so the host
|
||||
should not route Internet traffic through the headset.
|
||||
|
||||
## Reset classification
|
||||
|
||||
- Retained marker exists: target reached far enough to initialize the ring and
|
||||
returned via a DRAM-preserving warm reset.
|
||||
- Marker is zero and pstore contains `quest_kexec: native CPU shutdown failed`:
|
||||
loader panic inside Android.
|
||||
- Marker is zero with no pstore evidence: PMIC hard/cold reset or very early
|
||||
failure; do not attribute it to the last target probe without a checkpoint.
|
||||
|
||||
`tools/read-log.sh` reads both retained-ring and pstore evidence after Android
|
||||
returns. A stale marker is cleared before every run.
|
||||
|
||||
## Common failures
|
||||
|
||||
- **Module won't load / disagrees about symbol version:** rebuild against the
|
||||
exact running Android source/config/release; never force-load it.
|
||||
- **Gadget exists but target is unreachable:** ask NetworkManager to connect or
|
||||
manually assign `10.42.0.1/24` to the new ECM interface.
|
||||
- **No gadget and Android returns:** retrieve retained logs immediately.
|
||||
- **Hard lockup:** force reboot may erase all evidence. Avoid repeating an
|
||||
unchanged payload; add a checkpoint or narrow the boundary first.
|
||||
@@ -6,6 +6,7 @@
|
||||
# the headset is running, so Module.symvers carries the right CRCs.
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
obj-m += quest_kexec.o marker_read.o
|
||||
obj-m += usb_log.o
|
||||
quest_kexec-y := loader.o transition.o
|
||||
# Without this define the module can only stage (execute=1 is refused).
|
||||
ccflags-y += -DQKX_ENABLE_EXPERIMENTAL_JUMP
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Diagnostic console via vendor EP0 reads on the existing USB gadget.
|
||||
* No exit function: the hooked callback must remain resident until reboot.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
|
||||
#define RING_SIZE 65536
|
||||
#define TRANSFER_SIZE 1024
|
||||
/* Private UDC layout from this board's drivers/usb/gadget/udc/core.c. */
|
||||
struct qlog_udc {
|
||||
struct usb_gadget_driver *driver;
|
||||
struct usb_gadget *gadget;
|
||||
struct device dev;
|
||||
struct list_head list;
|
||||
bool vbus;
|
||||
};
|
||||
static unsigned long (*lookup)(const char *);
|
||||
static int (*original_setup)(struct usb_gadget *, const struct usb_ctrlrequest *);
|
||||
static struct usb_request *request;
|
||||
static unsigned char ring[RING_SIZE];
|
||||
static unsigned int head, tail;
|
||||
static DEFINE_SPINLOCK(ring_lock);
|
||||
static atomic_t dropped = ATOMIC_INIT(0);
|
||||
|
||||
static void log_write(struct console *con, const char *s, unsigned int count)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int i;
|
||||
if (!spin_trylock_irqsave(&ring_lock, flags)) {
|
||||
atomic_add(count, &dropped);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
if (head - tail == RING_SIZE) {
|
||||
tail++;
|
||||
atomic_inc(&dropped);
|
||||
}
|
||||
ring[head++ & (RING_SIZE - 1)] = s[i];
|
||||
}
|
||||
spin_unlock_irqrestore(&ring_lock, flags);
|
||||
}
|
||||
static struct console log_console = {
|
||||
.name = "qkxusb", .write = log_write, .flags = CON_ENABLED, .index = -1,
|
||||
};
|
||||
static void log_complete(struct usb_ep *ep, struct usb_request *req) { }
|
||||
|
||||
static int log_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int n = 0, size;
|
||||
u8 *out = request->buf;
|
||||
/* Magic request namespace: vendor/device/IN, request 0x5a,
|
||||
* value 0x514b, index 0x584c. All other requests pass through unchanged.
|
||||
* Reply is LE32 cumulative dropped bytes followed by console text.
|
||||
*/
|
||||
if (ctrl->bRequestType != 0xc0 || ctrl->bRequest != 0x5a ||
|
||||
le16_to_cpu(ctrl->wValue) != 0x514b ||
|
||||
le16_to_cpu(ctrl->wIndex) != 0x584c)
|
||||
return original_setup(gadget, ctrl);
|
||||
size = min_t(unsigned int, le16_to_cpu(ctrl->wLength), TRANSFER_SIZE);
|
||||
if (size < 4)
|
||||
return -EINVAL;
|
||||
spin_lock_irqsave(&ring_lock, flags);
|
||||
while (head != tail && n < size - 4)
|
||||
out[4 + n++] = ring[tail++ & (RING_SIZE - 1)];
|
||||
spin_unlock_irqrestore(&ring_lock, flags);
|
||||
out[0] = atomic_read(&dropped);
|
||||
out[1] = atomic_read(&dropped) >> 8;
|
||||
out[2] = atomic_read(&dropped) >> 16;
|
||||
out[3] = atomic_read(&dropped) >> 24;
|
||||
request->length = n + 4;
|
||||
request->zero = request->length < le16_to_cpu(ctrl->wLength);
|
||||
return usb_ep_queue(gadget->ep0, request, GFP_ATOMIC);
|
||||
}
|
||||
static int match(struct device *dev, const void *name)
|
||||
{
|
||||
return !strcmp(dev_name(dev), name);
|
||||
}
|
||||
static int __init log_init(void)
|
||||
{
|
||||
struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
|
||||
struct class **class;
|
||||
struct device *dev;
|
||||
struct qlog_udc *udc;
|
||||
struct device *(*find)(struct class *, struct device *, const void *,
|
||||
int (*)(struct device *, const void *));
|
||||
void (*register_con)(struct console *);
|
||||
struct mutex *udc_lock;
|
||||
int ret = register_kprobe(&kp);
|
||||
if (ret)
|
||||
return ret;
|
||||
lookup = (void *)kp.addr;
|
||||
unregister_kprobe(&kp);
|
||||
class = (void *)lookup("udc_class");
|
||||
udc_lock = (void *)lookup("udc_lock");
|
||||
find = (void *)lookup("class_find_device");
|
||||
register_con = (void *)lookup("register_console");
|
||||
if (!class || !*class || !find || !register_con || !udc_lock)
|
||||
return -ENOENT;
|
||||
dev = find(*class, NULL, "a600000.dwc3", match);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
udc = container_of(dev, struct qlog_udc, dev);
|
||||
mutex_lock(udc_lock);
|
||||
if (!udc->driver || !udc->gadget || !udc->driver->setup ||
|
||||
udc->gadget->dev.driver != &udc->driver->driver) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
request = usb_ep_alloc_request(udc->gadget->ep0, GFP_KERNEL);
|
||||
if (!request) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
request->buf = kmalloc(TRANSFER_SIZE, GFP_KERNEL);
|
||||
if (!request->buf) {
|
||||
usb_ep_free_request(udc->gadget->ep0, request);
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
request->complete = log_complete;
|
||||
original_setup = udc->driver->setup;
|
||||
/* Publish fully initialized state before USB IRQs can enter our code. */
|
||||
smp_wmb();
|
||||
WRITE_ONCE(udc->driver->setup, log_setup);
|
||||
mutex_unlock(udc_lock);
|
||||
/* Keep the UDC device reference for the lifetime of this resident module. */
|
||||
register_con(&log_console);
|
||||
pr_info("qkx_usb_log: resident EP0 console ready; ring=%u\n", RING_SIZE);
|
||||
return 0;
|
||||
out:
|
||||
mutex_unlock(udc_lock);
|
||||
put_device(dev);
|
||||
return ret;
|
||||
}
|
||||
module_init(log_init);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Resident Quest kernel console over USB vendor control reads");
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load the Android-side USB console logger and read it from the host.
|
||||
# This module intentionally cannot be unloaded; reboot Android to remove it.
|
||||
set -euo pipefail
|
||||
HERE=$(cd "$(dirname "$0")/.." && pwd)
|
||||
SECONDS=${1:-60}
|
||||
adb push "$HERE/module/usb_log.ko" /data/local/tmp/qkx_usb_log.ko >/dev/null
|
||||
adb shell 'su -c "insmod /data/local/tmp/qkx_usb_log.ko"'
|
||||
exec python3 "$HERE/tools/usb_log_host.py" --seconds "$SECONDS"
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read the Quest's resident EP0 console; no ADB, interface claim or detach.
|
||||
|
||||
Uses libusb through ctypes. Output includes host timestamps and drop counters.
|
||||
"""
|
||||
import argparse
|
||||
import ctypes as c
|
||||
import ctypes.util
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument('--seconds', type=float, default=60)
|
||||
ap.add_argument('--output', help='Host log file (defaults to stdout)')
|
||||
ap.add_argument('--vid', type=lambda s: int(s, 0), default=0x2833)
|
||||
ap.add_argument('--pid', type=lambda s: int(s, 0), default=0x5013)
|
||||
a = ap.parse_args()
|
||||
if a.output:
|
||||
sys.stdout = open(a.output, 'w', buffering=1)
|
||||
lib = c.CDLL(ctypes.util.find_library('usb-1.0'))
|
||||
lib.libusb_init.argtypes = [c.POINTER(c.c_void_p)]
|
||||
lib.libusb_init.restype = c.c_int
|
||||
lib.libusb_exit.argtypes = [c.c_void_p]
|
||||
lib.libusb_open_device_with_vid_pid.argtypes = [c.c_void_p, c.c_uint16, c.c_uint16]
|
||||
lib.libusb_open_device_with_vid_pid.restype = c.c_void_p
|
||||
lib.libusb_close.argtypes = [c.c_void_p]
|
||||
lib.libusb_control_transfer.argtypes = [c.c_void_p, c.c_uint8, c.c_uint8,
|
||||
c.c_uint16, c.c_uint16, c.c_void_p,
|
||||
c.c_uint16, c.c_uint]
|
||||
lib.libusb_control_transfer.restype = c.c_int
|
||||
context = c.c_void_p()
|
||||
if lib.libusb_init(c.byref(context)):
|
||||
raise RuntimeError('libusb initialization failed')
|
||||
handle = None
|
||||
try:
|
||||
handle = lib.libusb_open_device_with_vid_pid(context, a.vid, a.pid)
|
||||
if not handle:
|
||||
raise RuntimeError('Quest USB device unavailable or permission denied')
|
||||
buf = c.create_string_buffer(1024)
|
||||
deadline = time.monotonic() + a.seconds
|
||||
while time.monotonic() < deadline:
|
||||
n = lib.libusb_control_transfer(handle, 0xc0, 0x5a, 0x514b,
|
||||
0x584c, buf, 1024, 1000)
|
||||
if n < 0:
|
||||
print(f'{time.time():.6f} USB error {n}', flush=True)
|
||||
if n == -7: # Timeout is not proof of a disconnect/reset.
|
||||
continue
|
||||
return 1
|
||||
if n < 4:
|
||||
raise RuntimeError(f'short console header: {n}')
|
||||
dropped = int.from_bytes(buf.raw[:4], 'little')
|
||||
if n > 4:
|
||||
print(f'[{time.time():.6f} dropped={dropped}]', flush=True)
|
||||
sys.stdout.write(buf.raw[4:n].decode('utf-8', errors='replace'))
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
finally:
|
||||
if handle:
|
||||
lib.libusb_close(handle)
|
||||
lib.libusb_exit(context)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user