From b8cea4e97e80bfc7535b9e1dba2ad00adc1b0b94 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 26 Sep 2026 21:45:28 +0300 Subject: [PATCH] Generalize documentation for Meta Quest headsets --- HOW_TO_BUILD.md | 79 ++++++++++++++++++++++------------- HOW_TO_PORT.md | 96 +++++++++++++++++++------------------------ HOW_TO_USE.md | 62 +++++++++++++++++----------- README.md | 107 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 205 insertions(+), 139 deletions(-) diff --git a/HOW_TO_BUILD.md b/HOW_TO_BUILD.md index faa9e7c..34b97da 100644 --- a/HOW_TO_BUILD.md +++ b/HOW_TO_BUILD.md @@ -1,9 +1,12 @@ -# Build +# Build guide -There are two kernel builds: +> This project currently has tested settings only for Meta Quest Pro. -- **Android kernel:** used to compile `quest_kexec.ko` -- **Target kernel:** the patched kernel booted by kexec +Two kernel trees are used: + +1. The **Android kernel tree** builds `quest_kexec.ko` for the kernel currently + running on the headset. +2. The **target kernel tree** builds the custom kernel entered by kexec. ## Requirements @@ -11,59 +14,70 @@ Install: - Clang and LLD - `aarch64-linux-gnu-` GCC/binutils -- `adb`, Python 3, `dtc`, `fdtput`, `curl`, `make` +- `adb` +- Python 3 +- `dtc` and `fdtput` +- `curl`, `make` and standard build tools -## 1. Build the Android kernel tree +## Android kernel and loader module -The loader must match the kernel reported by: +Check the running version: ```sh adb shell uname -r ``` -For OS build `51503870024400340`, use Meta kernel commit: - -```text -fa2e480a85c6adbcc73dfc4f0ab0728781e14584 -``` - -Save the running config: +Use matching source and save the device 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`. +For the tested Quest Pro release, the source commit is: + +```text +fa2e480a85c6adbcc73dfc4f0ab0728781e14584 +``` + +Build the kernel and modules so that `Module.symvers` contains the correct +symbol CRCs: ```sh SRC=/path/to/android-kernel-source OUT=$PWD/android-out +LOCAL=-g49638c7a8637 + $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 + LOCALVERSION="$LOCAL" 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 \ + LOCALVERSION="$LOCAL" KCFLAGS=-I$SRC/drivers/pinctrl \ -j"$(nproc)" Image modules ``` -## 2. Build the kexec and log modules +Build the kexec and log modules: ```sh -make module KDIR="$OUT" LOCALVERSION=-g49638c7a8637 -modinfo -F vermagic module/quest_kexec.ko +make module KDIR="$OUT" LOCALVERSION="$LOCAL" ``` -The vermagic must start with the output of `adb shell uname -r`. +Verify the result: -## 3. Build the target kernel +```sh +modinfo -F vermagic module/quest_kexec.ko +adb shell uname -r +``` -Use the patched kernel fork: +The release strings must match. + +## Target kernel + +For the tested Quest Pro kernel: ```sh git clone https://github.com/YusufOzmen01/oculus-linux-kernel @@ -71,7 +85,7 @@ cd oculus-linux-kernel git checkout oculus-quest-pro-kernel-master ``` -Build it using the Quest Pro config. The target needs at least: +Build a raw ARM64 `Image` using the headset config. Required options include: ```text CONFIG_LOCALVERSION_AUTO=n @@ -82,13 +96,20 @@ CONFIG_PRINTK_TIME=y CONFIG_LOG_BUF_SHIFT=20 ``` -The result must be a raw ARM64 `Image`. +For another Quest model or kernel release, see +[HOW_TO_PORT.md](HOW_TO_PORT.md). -## 4. Build the initramfs +## Initramfs + +From this repository: ```sh make initramfs ``` -This downloads and verifies BusyBox 1.36.1, builds it statically for ARM64 and -creates `out/initramfs.gz`. +This downloads and verifies BusyBox 1.36.1, cross-builds it statically for +ARM64 and writes: + +```text +out/initramfs.gz +``` diff --git a/HOW_TO_PORT.md b/HOW_TO_PORT.md index b4d7b9e..8b485fd 100644 --- a/HOW_TO_PORT.md +++ b/HOW_TO_PORT.md @@ -1,68 +1,56 @@ -# Port a custom Quest Pro kernel +# Porting to another Meta Quest kernel -This project is for Quest Pro only. Porting here means adapting another Quest -Pro 4.19 kernel release, not another headset or SoC. +This implementation has only been tested on Meta Quest Pro. Other Quest models +use different SoCs, device trees, memory maps and hardware handoff states. -## 1. Start from the matching Meta source +## Same headset, newer kernel -Find the Meta source commit for the headset OS version. Keep a clean copy for -building the Android loader module. +1. Get the source matching the new Android release. +2. Build `quest_kexec.ko` against its exact config, local version and + `Module.symvers`. +3. Apply the patches under `kernel/patches/` to the new target source. +4. Resolve conflicts carefully in the affected drivers. +5. Capture a fresh runtime device tree with `tools/capture.sh`. +6. Test first with the supplied RAM-only initramfs. -## 2. Apply the target patches +Important target areas include: -The maintained kernel fork already contains the changes. For another source -revision, apply the patches in order: +- ARM64 entry and CPU startup +- GIC and SMMU inherited state +- RPMh and interconnect voting +- display clocks and power domains +- USB controller and PHY handoff +- AOP/QMP mailbox adoption +- SPMI and charger state +- retained logging and watchdog recovery -```sh -git am /path/to/quest-pro-kexec/kernel/patches/*.patch -``` +## Another Quest headset -Resolve conflicts by behavior, not by blindly choosing one side. The important -areas are: +Do not reuse the Quest Pro physical addresses or device names without checking +them. At minimum, determine: -- 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 +- target Image, initramfs and DTB staging addresses +- safe retained-log RAM +- target kernel text offset and maximum image size +- CPU, GIC and SMMU setup +- USB controller and UDC name +- watchdog behavior +- platform-device names used by loader shutdown callbacks +- AOP/mailbox protocol used by that SoC -## 3. Keep the required target command line +Update `module/loader.c`, `module/transition.S`, `tools/prepare.py` and the target +kernel patches for the new board. -`tools/prep.sh` adds: +## First test -```text -rdinit=/init nokaslr qkx_qmp_adopt=1 -initcall_blacklist=virtual_sensor_driver_init -``` +Keep the first target small: -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. +- static BusyBox initramfs +- no block-device mounts +- USB Ethernet only +- retained kernel log enabled -## 4. Keep the staging layout +Verify CPUs, USB and basic device probing before adding a larger userspace. -```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. +Do not disconnect the AOP QMP link on Quest Pro. The tested implementation +adopts the inherited link with `qkx_qmp_adopt=1`. diff --git a/HOW_TO_USE.md b/HOW_TO_USE.md index 7fc1011..88b78b7 100644 --- a/HOW_TO_USE.md +++ b/HOW_TO_USE.md @@ -1,64 +1,83 @@ -# Use +# Usage guide -## 1. Capture the running device state +> These steps are tested on Meta Quest Pro with rooted Android. -Boot rooted Android and connect ADB: +## Prepare + +Build the modules, target `Image` and initramfs first. See +[HOW_TO_BUILD.md](HOW_TO_BUILD.md). + +Boot Android, enable ADB and confirm root: + +```sh +adb shell 'su -c id' +``` + +Capture the current device information: ```sh tools/capture.sh out/captured ``` -This reads the runtime device tree, iomem, command line and kernel config. It -does not modify the device. +This is read-only. It captures the runtime device tree, iomem, command line and +kernel configuration. -Use the runtime tree from `/proc/device-tree`. The original -`/sys/firmware/fdt` blob is not safe for this handoff. - -## 2. Prepare the payload +## Create the payload ```sh tools/prep.sh out/captured /path/to/target/Image \ out/initramfs.gz out/payload ``` -## 3. Boot it +## Boot ```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`. +The script copies the module, Image, initramfs and DTB to `/data/local/tmp`, +runs `sync`, verifies their hashes and starts kexec. -## 4. Connect to the shell +## Connect to BusyBox -Find the new Ethernet interface with `ip link`. Then: +The initramfs exposes USB Ethernet with: + +- headset: `10.42.0.2` +- host: `10.42.0.1` + +Find the new interface: + +```sh +ip link +``` + +NetworkManager can normally obtain the address from the headset: ```sh nmcli device connect tools/shell.sh ``` -If DHCP is unavailable: +If DHCP does not work: ```sh sudo ip addr add 10.42.0.1/24 dev tools/shell.sh ``` -Run one command without telnet: +Run one command without an interactive shell: ```sh python3 tools/qkx_sh.py 'uname -a' ``` -## 5. Return to Android +## Return to Android ```sh tools/return-android.sh ``` -## Logs +## Read logs After Android returns: @@ -66,13 +85,10 @@ After Android returns: 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: +To capture Android kernel logs over its existing USB gadget before a run: ```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. +`usb_log.ko` intentionally remains installed until Android reboots. diff --git a/README.md b/README.md index c304ea5..c6efb14 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,102 @@ -# Quest Pro kexec +# Meta Quest kexec -A RAM-only ARM64 kexec loader for Meta Quest Pro. +An experimental ARM64 kexec implementation for Meta Quest headsets. -It boots a patched Linux kernel from rooted Android without flashing a -partition. The included initramfs starts a BusyBox shell over USB Ethernet. +It starts a custom Linux kernel from rooted Android without flashing a +partition. The included RAM-only initramfs provides a BusyBox shell over USB +Ethernet. -## Current status +> **Note:** This project has only been developed and tested on Meta Quest Pro. +> Other Quest headsets require device-specific kernel and loader work. -Tested on Quest Pro OS build `51503870024400340`. - -Working: +## What works on Quest Pro - all 8 CPUs - DRM and both panels -- USB Ethernet +- USB Ethernet shell - AOP/QMP handoff - syncboss - camera CPAS/CDM - SPMI and SMB5 charging -The patched kernel is in the `oculus-quest-pro-kernel-master` branch of: +The tested kernel changes are available in: https://github.com/YusufOzmen01/oculus-linux-kernel -The same changes are also included as patch files in `kernel/patches/`. +Patch copies are also included under `kernel/patches/`. -## Layout +## Basic build -- `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 +Install Clang/LLD, an `aarch64-linux-gnu-` toolchain, Python 3, `adb`, `dtc`, +`fdtput`, `curl` and `make`. -## Network shell +Build the kexec modules against the exact Android kernel running on the headset: -The initramfs exposes one ECM USB function: +```sh +make module KDIR=/path/to/android-kernel-out \ + LOCALVERSION=-g49638c7a8637 +``` -- headset: `10.42.0.2` -- host: `10.42.0.1` -- shell: `telnet 10.42.0.2` +Build the BusyBox USB-network initramfs: -The target runs DHCP, so NetworkManager can normally configure the host -interface automatically. +```sh +make initramfs +``` + +See [HOW_TO_BUILD.md](HOW_TO_BUILD.md) for preparing the Android and target +kernel trees. + +## Basic usage + +With rooted Android running: + +```sh +# Capture the current runtime device tree and kernel information +tools/capture.sh out/captured + +# Prepare the target payload +tools/prep.sh out/captured /path/to/target/Image \ + out/initramfs.gz out/payload + +# Push, verify and boot it +tools/run.sh out/payload +``` + +Connect to the shell after USB Ethernet appears: + +```sh +nmcli device connect +tools/shell.sh +``` + +Return to Android: + +```sh +tools/return-android.sh +``` + +See [HOW_TO_USE.md](HOW_TO_USE.md) for more information. + +## Logs + +Read a retained target-kernel log after Android returns: + +```sh +tools/read-log.sh +``` + +Capture Android kernel logs through its current USB gadget: + +```sh +tools/load-usb-log.sh 60 +``` ## Safety -This is experimental. A bad handoff can reboot or lock the headset. +A failed handoff can reboot or lock the headset. The supplied scripts only copy +files to `/data/local/tmp`; they do not flash, format or mount partitions. -The included scripts only copy files to `/data/local/tmp`. They do not flash, -format or mount partitions. - -Always build `quest_kexec.ko` against the exact Android kernel currently running -on the headset. Never force-load a module with the wrong ABI. +Never force-load a module built for a different Android kernel. ## License