mirror of
https://github.com/YusufOzmen01/quest-kexec.git
synced 2026-10-26 19:37:39 +00:00
Initial Quest Pro ARM64 kexec release
This commit is contained in:
26 files changed
+5031
No files matched your search
+28
@@ -0,0 +1,28 @@
|
||||
# Builds and device captures
|
||||
/out/
|
||||
/build/
|
||||
/captured/
|
||||
/runs/
|
||||
*.ko
|
||||
*.o
|
||||
*.mod
|
||||
*.mod.c
|
||||
*.cmd
|
||||
Module.symvers
|
||||
modules.order
|
||||
|
||||
# Proprietary/device-specific inputs
|
||||
boot*.img
|
||||
Image
|
||||
*.dtb
|
||||
*.fdt
|
||||
*.config
|
||||
cmdline
|
||||
iomem
|
||||
|
||||
# Editors/Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*~
|
||||
.vscode/
|
||||
.idea/
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
<https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Moe Ghoul>, 1 April 1989
|
||||
Moe Ghoul, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Convenience targets. See README.md for the full procedure.
|
||||
# make module KDIR=... [LOCALVERSION=...]
|
||||
# make initramfs
|
||||
OUT ?= out
|
||||
|
||||
.PHONY: all module initramfs clean
|
||||
all: module initramfs
|
||||
|
||||
module:
|
||||
$(MAKE) -C module KDIR=$(KDIR) LOCALVERSION=$(LOCALVERSION)
|
||||
|
||||
$(OUT)/busybox:
|
||||
initramfs/busybox.sh $@
|
||||
|
||||
initramfs: $(OUT)/busybox
|
||||
python3 initramfs/build.py --busybox $(OUT)/busybox --output $(OUT)/initramfs.gz
|
||||
|
||||
clean:
|
||||
-$(MAKE) -C module clean KDIR=$(KDIR)
|
||||
rm -rf $(OUT)
|
||||
@@ -0,0 +1,101 @@
|
||||
# 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.
|
||||
|
||||
The included initramfs exposes a BusyBox shell over USB Ethernet (ECM):
|
||||
|
||||
- target: `10.42.0.2`
|
||||
- host: `10.42.0.1` (offered by target DHCP)
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
|
||||
Do **not** use a loader module built for a different running Android kernel.
|
||||
Module ABI mismatches can crash Android during an irreversible shutdown.
|
||||
|
||||
## 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
|
||||
|
||||
## License
|
||||
|
||||
GPL-2.0. See `LICENSE`. BusyBox is downloaded from upstream and built locally;
|
||||
its binary and source are not committed.
|
||||
@@ -0,0 +1,8 @@
|
||||
# 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,50 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack a static ARM64 BusyBox and init.sh into a gzip newc initramfs."""
|
||||
import argparse
|
||||
import gzip
|
||||
import stat
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def archive(entries):
|
||||
data = bytearray()
|
||||
for ino, (name, mode, contents, major, minor) in enumerate(entries, 1):
|
||||
name = name.encode() + b"\0"
|
||||
fields = (ino, mode, 0, 0, 1, 0, len(contents), 0, 0, major, minor, len(name), 0)
|
||||
data += b"070701" + "".join(f"{x:08x}" for x in fields).encode() + name
|
||||
data += b"\0" * (-len(data) % 4)
|
||||
data += contents
|
||||
data += b"\0" * (-len(data) % 4)
|
||||
data += b"\0" * (-len(data) % 512)
|
||||
return bytes(data)
|
||||
|
||||
|
||||
def check_static_arm64(path):
|
||||
b = path.read_bytes()
|
||||
if b[:6] != b"\x7fELF\x02\x01" or struct.unpack_from("<H", b, 18)[0] != 183:
|
||||
raise SystemExit(f"{path}: not a little-endian ARM64 ELF")
|
||||
off = struct.unpack_from("<Q", b, 32)[0]
|
||||
size, count = struct.unpack_from("<HH", b, 54)
|
||||
if any(struct.unpack_from("<I", b, off + i * size)[0] == 3 for i in range(count)):
|
||||
raise SystemExit(f"{path}: must be statically linked")
|
||||
return b
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--busybox", type=Path, required=True)
|
||||
ap.add_argument("--output", type=Path, required=True)
|
||||
ap.add_argument("--init", type=Path, default=Path(__file__).with_name("init.sh"))
|
||||
ap.add_argument("--extra", action="append", default=[], metavar="SRC:DEST",
|
||||
help="add a static ARM64 binary, e.g. modeset:bin/modeset")
|
||||
a = ap.parse_args()
|
||||
dirs = ("bin", "sbin", "dev", "dev/pts", "proc", "sys", "config", "etc", "root", "tmp")
|
||||
entries = [(d, stat.S_IFDIR | 0o755, b"", 0, 0) for d in dirs]
|
||||
entries += [
|
||||
("bin/busybox", stat.S_IFREG | 0o755, check_static_arm64(a.busybox), 0, 0),
|
||||
("init", stat.S_IFREG | 0o755, a.init.read_bytes(), 0, 0),
|
||||
("dev/console", stat.S_IFCHR | 0o600, b"", 5, 1),
|
||||
("dev/kmsg", stat.S_IFCHR | 0o600, b"", 1, 11),
|
||||
("dev/null", stat.S_IFCHR | 0o666, b"", 1, 3),
|
||||
("etc/passwd", stat.S_IFREG | 0o644, b"root::0:0:root:/root:/bin/sh\n", 0, 0),
|
||||
("etc/group", stat.S_IFREG | 0o644, b"root:x:0:\n", 0, 0),
|
||||
]
|
||||
for spec in a.extra:
|
||||
src, dest = spec.split(":", 1)
|
||||
entries.append((dest, stat.S_IFREG | 0o755, check_static_arm64(Path(src)), 0, 0))
|
||||
entries.append(("TRAILER!!!", 0, b"", 0, 0))
|
||||
data = gzip.compress(archive(entries), mtime=0)
|
||||
if len(data) > 0x200000:
|
||||
raise SystemExit(f"initramfs is {len(data)} bytes; the staging layout allows 2 MiB")
|
||||
a.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
a.output.write_bytes(data)
|
||||
print(f"{a.output}: {len(data)} bytes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download, verify and build a static ARM64 BusyBox.
|
||||
# Usage: busybox.sh <output-binary>
|
||||
set -euo pipefail
|
||||
VER=1.36.1
|
||||
SHA=b8cc24c9574d809e7279c3be349795c5d5ceb6fdf19ca709f80cde50e47de314
|
||||
OUT=$(realpath -m "$1")
|
||||
WORK=$(dirname "$OUT")/busybox-build
|
||||
CROSS=${CROSS_COMPILE:-aarch64-linux-gnu-}
|
||||
mkdir -p "$WORK"
|
||||
cd "$WORK"
|
||||
[ -f busybox-$VER.tar.bz2 ] ||
|
||||
curl -fL -o busybox-$VER.tar.bz2 https://busybox.net/downloads/busybox-$VER.tar.bz2
|
||||
echo "$SHA busybox-$VER.tar.bz2" | sha256sum -c -
|
||||
rm -rf busybox-$VER
|
||||
tar xjf busybox-$VER.tar.bz2
|
||||
cd busybox-$VER
|
||||
M="make ARCH=arm64 CROSS_COMPILE=$CROSS"
|
||||
$M defconfig >/dev/null
|
||||
# Static, and drop tc: it doesn't build against current kernel headers.
|
||||
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/; s/^CONFIG_TC=y/# CONFIG_TC is not set/' .config
|
||||
$M oldconfig </dev/null >/dev/null 2>&1 || true
|
||||
grep -q '^CONFIG_STATIC=y' .config
|
||||
$M -j"$(nproc)" >/dev/null
|
||||
cp busybox "$OUT"
|
||||
echo "$OUT"
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/busybox sh
|
||||
# RAM-only init: USB Ethernet (ECM) gadget with a BusyBox telnet shell.
|
||||
# The headset is 10.42.0.2; udhcpd hands the host 10.42.0.1.
|
||||
# Nothing here mounts a block device.
|
||||
export PATH=/bin:/sbin
|
||||
export HOME=/root
|
||||
export TERM=vt100
|
||||
/bin/busybox --install -s /bin
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mount -t devtmpfs devtmpfs /dev
|
||||
mkdir -p /dev/pts /config /tmp
|
||||
mount -t devpts devpts /dev/pts
|
||||
mount -t configfs configfs /config
|
||||
mount -t tmpfs tmpfs /tmp
|
||||
exec >/dev/kmsg 2>&1
|
||||
|
||||
log() { echo "qkx-init: $*"; }
|
||||
# /proc/qkx_bootdone comes from the target kernel patches: 'd' disarms the
|
||||
# boot watchdog, 'k' is a keepalive, 'r' forces a log-preserving warm reset.
|
||||
bootdone() { [ -w /proc/qkx_bootdone ] && echo "$1" > /proc/qkx_bootdone; }
|
||||
|
||||
log "reached $(uname -r)"
|
||||
|
||||
g=/config/usb_gadget/qkx
|
||||
mkdir -p "$g/strings/0x409" "$g/configs/c.1/strings/0x409" "$g/functions/ecm.usb0"
|
||||
echo 0x1d6b > "$g/idVendor"
|
||||
echo 0x0105 > "$g/idProduct"
|
||||
echo QKX-RAM > "$g/strings/0x409/serialnumber"
|
||||
echo 'quest-pro-kexec' > "$g/strings/0x409/manufacturer"
|
||||
echo 'RAM-only USB Ethernet shell' > "$g/strings/0x409/product"
|
||||
echo 'ECM' > "$g/configs/c.1/strings/0x409/configuration"
|
||||
echo 250 > "$g/configs/c.1/MaxPower"
|
||||
# Fixed MACs keep the host interface name stable across boots.
|
||||
echo 02:51:4b:58:00:02 > "$g/functions/ecm.usb0/dev_addr"
|
||||
echo 02:51:4b:58:00:01 > "$g/functions/ecm.usb0/host_addr"
|
||||
ln -s "$g/functions/ecm.usb0" "$g/configs/c.1/ecm.usb0"
|
||||
|
||||
udc=
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
for c in /sys/class/udc/*; do
|
||||
[ -e "$c" ] || continue
|
||||
echo "${c##*/}" > "$g/UDC" && udc=$c && break 2
|
||||
done
|
||||
log "waiting for UDC"
|
||||
bootdone k
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$udc" ]; then
|
||||
log "no UDC; forcing warm reset so the log survives"
|
||||
bootdone r
|
||||
while :; do sleep 60; done
|
||||
fi
|
||||
log "bound ${udc##*/}"
|
||||
# Keep the DWC3 glue device out of runtime suspend.
|
||||
echo on > "$udc/device/../power/control" 2>/dev/null
|
||||
|
||||
ifconfig lo 127.0.0.1 up
|
||||
ifconfig usb0 10.42.0.2 netmask 255.255.255.0 up
|
||||
cat > /tmp/udhcpd.conf <<EOC
|
||||
interface usb0
|
||||
start 10.42.0.1
|
||||
end 10.42.0.1
|
||||
max_leases 1
|
||||
lease_file /tmp/udhcpd.leases
|
||||
option subnet 255.255.255.0
|
||||
option lease 86400
|
||||
EOC
|
||||
touch /tmp/udhcpd.leases
|
||||
udhcpd /tmp/udhcpd.conf
|
||||
telnetd -l /bin/sh -p 23
|
||||
|
||||
bootdone d
|
||||
log "ready: telnet 10.42.0.2"
|
||||
while :; do sleep 3600; done
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,203 @@
|
||||
From 5d4273bfeb7ad4379dbbc93f4bd0fbbe495056d3 Mon Sep 17 00:00:00 2001
|
||||
From: yusuf <[email protected]>
|
||||
Date: Sat, 26 Sep 2026 21:06:25 +0300
|
||||
Subject: [PATCH 2/3] qkx: disable stale SPMI IRQs, SMB5 skip initial-status
|
||||
handlers, probe_skip writable
|
||||
|
||||
---
|
||||
drivers/base/dd.c | 3 ++-
|
||||
drivers/power/supply/qcom/qpnp-smb5.c | 36 +++++++++++++++++++++++++++
|
||||
drivers/spmi/spmi-pmic-arb.c | 10 ++++++++
|
||||
3 files changed, 48 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
|
||||
index 2f822cdc3a..cf26ea61ff 100644
|
||||
--- a/drivers/base/dd.c
|
||||
+++ b/drivers/base/dd.c
|
||||
@@ -208,7 +208,8 @@ bool qkx_probe_trace;
|
||||
static char *qkx_probe_skip =
|
||||
"c440000.qcom,spmi:qcom,pm8150b@2:qcom,qpnp-smb5|"
|
||||
"ac4d000.qcom,cpas-cdm0";
|
||||
-core_param(qkx_probe_skip, qkx_probe_skip, charp, 0444);
|
||||
+/* Writable so a parked device can be released after USB logging is up. */
|
||||
+core_param(qkx_probe_skip, qkx_probe_skip, charp, 0644);
|
||||
|
||||
static bool qkx_probe_is_skipped(const char *name)
|
||||
{
|
||||
diff --git a/drivers/power/supply/qcom/qpnp-smb5.c b/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
index f6e66a04d1..11df10f366 100644
|
||||
--- a/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
+++ b/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
@@ -3134,6 +3134,13 @@ static int smb5_post_init(struct smb5 *chip)
|
||||
* DETERMINE INITIAL STATUS *
|
||||
****************************/
|
||||
|
||||
+/* qkx: kexec-handoff probe checkpoints; delay lets live log reach the host. */
|
||||
+#define QKX_SMB5_STEP(what) do { pr_emerg("qkxsmb5: before %s\n", what); msleep(100); } while (0)
|
||||
+
|
||||
+/* qkx: skip hand-run IRQ handlers after kexec; real IRQs report the same state. */
|
||||
+static bool qkx_skip_initial;
|
||||
+module_param(qkx_skip_initial, bool, 0444);
|
||||
+
|
||||
static int smb5_determine_initial_status(struct smb5 *chip)
|
||||
{
|
||||
struct smb_irq_data irq_data = {chip, "determine-initial-status"};
|
||||
@@ -3151,16 +3158,32 @@ static int smb5_determine_initial_status(struct smb5 *chip)
|
||||
if (chg->bms_psy)
|
||||
smblib_suspend_on_debug_battery(chg);
|
||||
|
||||
+ if (qkx_skip_initial) {
|
||||
+ pr_emerg("qkxsmb5: skipping initial-status handlers\n");
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ QKX_SMB5_STEP("usb_plugin_irq_handler");
|
||||
usb_plugin_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("dc_plugin_irq_handler");
|
||||
dc_plugin_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("typec_attach_detach_irq_handler");
|
||||
typec_attach_detach_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("typec_state_change_irq_handler");
|
||||
typec_state_change_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("usb_source_change_irq_handler");
|
||||
usb_source_change_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("chg_state_change_irq_handler");
|
||||
chg_state_change_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("icl_change_irq_handler");
|
||||
icl_change_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("batt_temp_changed_irq_handler");
|
||||
batt_temp_changed_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("wdog_bark_irq_handler");
|
||||
wdog_bark_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("typec_or_rid_detection_change_irq_handler");
|
||||
typec_or_rid_detection_change_irq_handler(0, &irq_data);
|
||||
+ QKX_SMB5_STEP("wdog_snarl_irq_handler");
|
||||
wdog_snarl_irq_handler(0, &irq_data);
|
||||
|
||||
return 0;
|
||||
@@ -3717,6 +3740,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_chg_config_init");
|
||||
rc = smb5_chg_config_init(chip);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
@@ -3724,6 +3748,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
return rc;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_parse_dt");
|
||||
rc = smb5_parse_dt(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't parse device tree rc=%d\n", rc);
|
||||
@@ -3736,6 +3761,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
else
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
+ QKX_SMB5_STEP("smblib_init");
|
||||
rc = smblib_init(chg);
|
||||
if (rc < 0) {
|
||||
pr_err("Smblib_init failed rc=%d\n", rc);
|
||||
@@ -3776,6 +3802,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_hw");
|
||||
rc = smb5_init_hw(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize hardware rc=%d\n", rc);
|
||||
@@ -3791,6 +3818,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
* only.
|
||||
*/
|
||||
if (!chg->pd_not_supported) {
|
||||
+ QKX_SMB5_STEP("smb5_init_vbus_regulator");
|
||||
rc = smb5_init_vbus_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize vbus regulator rc=%d\n",
|
||||
@@ -3798,6 +3826,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_vconn_regulator");
|
||||
rc = smb5_init_vconn_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize vconn regulator rc=%d\n",
|
||||
@@ -3805,6 +3834,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_otg_boost_regulator");
|
||||
rc = smb5_init_otg_boost_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize otg boost regulator rc=%d\n",
|
||||
@@ -3817,6 +3847,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
case PM8150B_SUBTYPE:
|
||||
case PM6150_SUBTYPE:
|
||||
case PM7250B_SUBTYPE:
|
||||
+ QKX_SMB5_STEP("smb5_init_dc_psy");
|
||||
rc = smb5_init_dc_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize dc psy rc=%d\n", rc);
|
||||
@@ -3827,6 +3858,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
break;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_usb_psy");
|
||||
rc = smb5_init_usb_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize usb psy rc=%d\n", rc);
|
||||
@@ -3845,18 +3877,21 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_batt_psy");
|
||||
rc = smb5_init_batt_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize batt psy rc=%d\n", rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_init_typec_class");
|
||||
rc = smb5_init_typec_class(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize typec class rc=%d\n", rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_determine_initial_status");
|
||||
rc = smb5_determine_initial_status(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't determine initial status rc=%d\n",
|
||||
@@ -3864,6 +3899,7 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
+ QKX_SMB5_STEP("smb5_request_interrupts");
|
||||
rc = smb5_request_interrupts(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't request interrupts rc=%d\n", rc);
|
||||
diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c
|
||||
index 14c1e38939..bfd53cee60 100644
|
||||
--- a/drivers/spmi/spmi-pmic-arb.c
|
||||
+++ b/drivers/spmi/spmi-pmic-arb.c
|
||||
@@ -491,6 +491,16 @@ static void cleanup_irq(struct spmi_pmic_arb *pmic_arb, u16 apid, int id)
|
||||
dev_err_ratelimited(&pmic_arb->spmic->dev, "%s apid=%d sid=0x%x per=0x%x irq=%d\n",
|
||||
__func__, apid, sid, per, id);
|
||||
writel_relaxed(irq_mask, pmic_arb->ver_ops->irq_clear(pmic_arb, apid));
|
||||
+ /* qkx: Android leaves interrupts enabled in peripherals whose driver
|
||||
+ * has not probed yet after kexec. Clearing alone lets a level source
|
||||
+ * refire forever, so also disable it at the peripheral; the owning
|
||||
+ * driver re-enables it through request_irq().
|
||||
+ */
|
||||
+ if (pmic_arb_write_cmd(pmic_arb->spmic, SPMI_CMD_EXT_WRITEL, sid,
|
||||
+ (per << 8) + QPNPINT_REG_EN_CLR, &irq_mask, 1))
|
||||
+ dev_err_ratelimited(&pmic_arb->spmic->dev,
|
||||
+ "failed to disable irq sid=0x%x per=0x%x irq=%d\n",
|
||||
+ sid, per, id);
|
||||
}
|
||||
|
||||
static void periph_interrupt(struct spmi_pmic_arb *pmic_arb, u16 apid)
|
||||
--
|
||||
2.55.0
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
From 3f87f8ba54d8e48d4c0ba52a9c98e0e2063f2a07 Mon Sep 17 00:00:00 2001
|
||||
From: yusuf <[email protected]>
|
||||
Date: Sat, 26 Sep 2026 21:16:55 +0300
|
||||
Subject: [PATCH 3/3] qkx: nothing parked by default; SMB5 skips initial-status
|
||||
handlers by default; drop SMB5 probe delays
|
||||
|
||||
---
|
||||
drivers/base/dd.c | 4 +---
|
||||
drivers/power/supply/qcom/qpnp-smb5.c | 29 +--------------------------
|
||||
2 files changed, 2 insertions(+), 31 deletions(-)
|
||||
|
||||
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
|
||||
index cf26ea61ff..79b88557a7 100644
|
||||
--- a/drivers/base/dd.c
|
||||
+++ b/drivers/base/dd.c
|
||||
@@ -205,9 +205,7 @@ void device_unblock_probing(void)
|
||||
/* qkx: explicit diagnostic kick enables slow per-probe tracing. */
|
||||
bool qkx_probe_trace;
|
||||
/* Devices known to be unsafe or blocking when inherited across kexec. */
|
||||
-static char *qkx_probe_skip =
|
||||
- "c440000.qcom,spmi:qcom,pm8150b@2:qcom,qpnp-smb5|"
|
||||
- "ac4d000.qcom,cpas-cdm0";
|
||||
+static char *qkx_probe_skip = "";
|
||||
/* Writable so a parked device can be released after USB logging is up. */
|
||||
core_param(qkx_probe_skip, qkx_probe_skip, charp, 0644);
|
||||
|
||||
diff --git a/drivers/power/supply/qcom/qpnp-smb5.c b/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
index 11df10f366..8deb7b4252 100644
|
||||
--- a/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
+++ b/drivers/power/supply/qcom/qpnp-smb5.c
|
||||
@@ -3134,11 +3134,8 @@ static int smb5_post_init(struct smb5 *chip)
|
||||
* DETERMINE INITIAL STATUS *
|
||||
****************************/
|
||||
|
||||
-/* qkx: kexec-handoff probe checkpoints; delay lets live log reach the host. */
|
||||
-#define QKX_SMB5_STEP(what) do { pr_emerg("qkxsmb5: before %s\n", what); msleep(100); } while (0)
|
||||
-
|
||||
/* qkx: skip hand-run IRQ handlers after kexec; real IRQs report the same state. */
|
||||
-static bool qkx_skip_initial;
|
||||
+static bool qkx_skip_initial = true;
|
||||
module_param(qkx_skip_initial, bool, 0444);
|
||||
|
||||
static int smb5_determine_initial_status(struct smb5 *chip)
|
||||
@@ -3163,27 +3160,16 @@ static int smb5_determine_initial_status(struct smb5 *chip)
|
||||
return 0;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("usb_plugin_irq_handler");
|
||||
usb_plugin_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("dc_plugin_irq_handler");
|
||||
dc_plugin_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("typec_attach_detach_irq_handler");
|
||||
typec_attach_detach_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("typec_state_change_irq_handler");
|
||||
typec_state_change_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("usb_source_change_irq_handler");
|
||||
usb_source_change_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("chg_state_change_irq_handler");
|
||||
chg_state_change_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("icl_change_irq_handler");
|
||||
icl_change_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("batt_temp_changed_irq_handler");
|
||||
batt_temp_changed_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("wdog_bark_irq_handler");
|
||||
wdog_bark_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("typec_or_rid_detection_change_irq_handler");
|
||||
typec_or_rid_detection_change_irq_handler(0, &irq_data);
|
||||
- QKX_SMB5_STEP("wdog_snarl_irq_handler");
|
||||
wdog_snarl_irq_handler(0, &irq_data);
|
||||
|
||||
return 0;
|
||||
@@ -3740,7 +3726,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_chg_config_init");
|
||||
rc = smb5_chg_config_init(chip);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
@@ -3748,7 +3733,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
return rc;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_parse_dt");
|
||||
rc = smb5_parse_dt(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't parse device tree rc=%d\n", rc);
|
||||
@@ -3761,7 +3745,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
else
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
- QKX_SMB5_STEP("smblib_init");
|
||||
rc = smblib_init(chg);
|
||||
if (rc < 0) {
|
||||
pr_err("Smblib_init failed rc=%d\n", rc);
|
||||
@@ -3802,7 +3785,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_hw");
|
||||
rc = smb5_init_hw(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize hardware rc=%d\n", rc);
|
||||
@@ -3818,7 +3800,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
* only.
|
||||
*/
|
||||
if (!chg->pd_not_supported) {
|
||||
- QKX_SMB5_STEP("smb5_init_vbus_regulator");
|
||||
rc = smb5_init_vbus_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize vbus regulator rc=%d\n",
|
||||
@@ -3826,7 +3807,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_vconn_regulator");
|
||||
rc = smb5_init_vconn_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize vconn regulator rc=%d\n",
|
||||
@@ -3834,7 +3814,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_otg_boost_regulator");
|
||||
rc = smb5_init_otg_boost_regulator(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize otg boost regulator rc=%d\n",
|
||||
@@ -3847,7 +3826,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
case PM8150B_SUBTYPE:
|
||||
case PM6150_SUBTYPE:
|
||||
case PM7250B_SUBTYPE:
|
||||
- QKX_SMB5_STEP("smb5_init_dc_psy");
|
||||
rc = smb5_init_dc_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize dc psy rc=%d\n", rc);
|
||||
@@ -3858,7 +3836,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
break;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_usb_psy");
|
||||
rc = smb5_init_usb_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize usb psy rc=%d\n", rc);
|
||||
@@ -3877,21 +3854,18 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_batt_psy");
|
||||
rc = smb5_init_batt_psy(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize batt psy rc=%d\n", rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_init_typec_class");
|
||||
rc = smb5_init_typec_class(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't initialize typec class rc=%d\n", rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_determine_initial_status");
|
||||
rc = smb5_determine_initial_status(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't determine initial status rc=%d\n",
|
||||
@@ -3899,7 +3873,6 @@ static int smb5_probe(struct platform_device *pdev)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- QKX_SMB5_STEP("smb5_request_interrupts");
|
||||
rc = smb5_request_interrupts(chip);
|
||||
if (rc < 0) {
|
||||
pr_err("Couldn't request interrupts rc=%d\n", rc);
|
||||
--
|
||||
2.55.0
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Out-of-tree build of the loader and the retained-log reader.
|
||||
#
|
||||
# make -C module KDIR=<android kernel build dir> KSRC=<android kernel source>
|
||||
#
|
||||
# KDIR must be a fully built (Image + modules) output directory of the kernel
|
||||
# the headset is running, so Module.symvers carries the right CRCs.
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
obj-m += quest_kexec.o marker_read.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
|
||||
else
|
||||
KDIR ?= $(error set KDIR to the built Android kernel output directory)
|
||||
LOCALVERSION ?=
|
||||
KMAKE = $(MAKE) -C $(abspath $(KDIR)) M=$(CURDIR) ARCH=arm64 CC=clang LD=ld.lld \
|
||||
HOSTCC=clang CROSS_COMPILE=aarch64-linux-gnu- CLANG_TRIPLE=aarch64-linux-gnu- \
|
||||
LOCALVERSION=$(LOCALVERSION)
|
||||
.PHONY: all clean
|
||||
all:
|
||||
$(KMAKE) modules
|
||||
clean:
|
||||
$(KMAKE) clean
|
||||
endif
|
||||
+1136
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Read-only diagnostic for the Quest kexec log in unused reserved ramoops RAM. */
|
||||
#include <linux/io.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#define QKX_LOG_PHYS 0x9ba80000ULL
|
||||
#define QKX_LOG_SIZE 0x10000
|
||||
#define QKX_LOG_MAGIC 0x514b584c
|
||||
#define QKX_LOG_PAGES (QKX_LOG_SIZE / PAGE_SIZE)
|
||||
|
||||
struct qkx_log_header {
|
||||
u32 magic;
|
||||
u32 write_pos;
|
||||
u32 wraps;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
static bool write_test;
|
||||
module_param(write_test, bool, 0);
|
||||
/* Invalidate a stale log before a run so it can't be mistaken for a new one. */
|
||||
static bool clear;
|
||||
module_param(clear, bool, 0);
|
||||
|
||||
static int __init qkx_marker_read_init(void)
|
||||
{
|
||||
struct page *pages[QKX_LOG_PAGES];
|
||||
struct qkx_log_header *h;
|
||||
char *ordered;
|
||||
void *base;
|
||||
u32 capacity = QKX_LOG_SIZE - sizeof(*h);
|
||||
u32 pos, wraps, used, first, out = 0;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < QKX_LOG_PAGES; i++)
|
||||
pages[i] = pfn_to_page((QKX_LOG_PHYS >> PAGE_SHIFT) + i);
|
||||
base = vmap(pages, QKX_LOG_PAGES, VM_MAP | VM_IOREMAP,
|
||||
pgprot_writecombine(PAGE_KERNEL));
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
h = base;
|
||||
if (clear) {
|
||||
WRITE_ONCE(h->magic, 0);
|
||||
wmb();
|
||||
pr_emerg("qkx_marker_read: cleared retained log\n");
|
||||
vunmap(base);
|
||||
return 0;
|
||||
}
|
||||
if (write_test) {
|
||||
static const char test[] = "QKX_WARM_RESET_RETENTION_TEST_v1\n";
|
||||
|
||||
memset(base, 0, QKX_LOG_SIZE);
|
||||
memcpy(base + sizeof(*h), test, sizeof(test) - 1);
|
||||
WRITE_ONCE(h->write_pos, sizeof(test) - 1);
|
||||
WRITE_ONCE(h->wraps, 0);
|
||||
WRITE_ONCE(h->magic, QKX_LOG_MAGIC);
|
||||
wmb();
|
||||
pr_emerg("qkx_marker_read: wrote warm-reset retention test\n");
|
||||
}
|
||||
if (READ_ONCE(h->magic) != QKX_LOG_MAGIC) {
|
||||
pr_emerg("qkx_marker_read: no retained log; magic=%08x\n",
|
||||
READ_ONCE(h->magic));
|
||||
vunmap(base);
|
||||
return 0;
|
||||
}
|
||||
pos = min_t(u32, READ_ONCE(h->write_pos), capacity - 1);
|
||||
wraps = READ_ONCE(h->wraps);
|
||||
used = wraps ? capacity : pos;
|
||||
first = wraps ? pos : 0;
|
||||
ordered = kmalloc(used + 1, GFP_KERNEL);
|
||||
if (!ordered) {
|
||||
vunmap(base);
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (used) {
|
||||
u32 tail = min(used, capacity - first);
|
||||
memcpy(ordered, base + sizeof(*h) + first, tail);
|
||||
memcpy(ordered + tail, base + sizeof(*h), used - tail);
|
||||
}
|
||||
ordered[used] = '\0';
|
||||
vunmap(base);
|
||||
|
||||
pr_emerg("qkx_marker_read: retained log bytes=%u wraps=%u pos=%u\n",
|
||||
used, wraps, pos);
|
||||
while (out < used) {
|
||||
u32 len = 0;
|
||||
|
||||
while (out + len < used && len < 220 && ordered[out + len] != '\n')
|
||||
len++;
|
||||
pr_emerg("qkxlog: %.*s\n", (int)len, ordered + out);
|
||||
out += len;
|
||||
if (out < used && ordered[out] == '\n')
|
||||
out++;
|
||||
}
|
||||
kfree(ordered);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit qkx_marker_read_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(qkx_marker_read_init);
|
||||
module_exit(qkx_marker_read_exit);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Read retained Quest kexec log from reserved RAM");
|
||||
@@ -0,0 +1,181 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Quest Pro: EL1, 4K pages, 39-bit VA. No stack use after TTBR0 switch.
|
||||
* The relocation loop follows Linux arm64's relocate_kernel.S cache rules.
|
||||
*/
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/sysreg.h>
|
||||
|
||||
.text
|
||||
.align 4
|
||||
/* x0: private identity PGD, x1: physical control page, x2: descriptor head,
|
||||
* x3: Image entry, x4: DTB. The high module mapping remains in TTBR1.
|
||||
*/
|
||||
ENTRY(qkx_enter)
|
||||
msr daifset, #0xf
|
||||
/* First invalidate the old TTBR0 translations with walks disabled. */
|
||||
mrs x9, tcr_el1
|
||||
orr x9, x9, #(1 << 7)
|
||||
msr tcr_el1, x9
|
||||
isb
|
||||
dsb ishst
|
||||
tlbi vmalle1
|
||||
dsb ish
|
||||
isb
|
||||
msr ttbr0_el1, x0
|
||||
/* T0SZ=25, TG0=4K; preserve cacheability/shareability and TTBR1. */
|
||||
bic x9, x9, #0x3f
|
||||
mov x10, #25
|
||||
orr x9, x9, x10
|
||||
bic x9, x9, #(3 << 14)
|
||||
bic x9, x9, #(1 << 37)
|
||||
bic x9, x9, #(1 << 7)
|
||||
msr tcr_el1, x9
|
||||
isb
|
||||
br x1
|
||||
ENDPROC(qkx_enter)
|
||||
|
||||
/* Copied to a separately allocated physical page. No external references. */
|
||||
.align 4
|
||||
ENTRY(qkx_reloc_start)
|
||||
mov x17, x3
|
||||
mov x18, x4
|
||||
mov x19, x2
|
||||
mrs x9, sctlr_el1
|
||||
ldr x10, =SCTLR_ELx_FLAGS
|
||||
bic x9, x9, x10
|
||||
pre_disable_mmu_workaround
|
||||
msr sctlr_el1, x9
|
||||
isb
|
||||
raw_dcache_line_size x15, x9
|
||||
1:
|
||||
cbz x19, 5f
|
||||
/* Descriptor pages: { next, count, { source, destination }[255] }. */
|
||||
ldp x20, x21, [x19]
|
||||
add x19, x19, #16
|
||||
2:
|
||||
cbz x21, 4f
|
||||
ldp x22, x23, [x19], #16
|
||||
mov x9, x23
|
||||
add x10, x23, #4096
|
||||
3:
|
||||
dc ivac, x9
|
||||
add x9, x9, x15
|
||||
cmp x9, x10
|
||||
b.lo 3b
|
||||
dsb sy
|
||||
/* PAGE_SIZE bytes, both addresses page-aligned and nonoverlapping. */
|
||||
mov x11, #256
|
||||
6:
|
||||
ldp x9, x10, [x22], #16
|
||||
stp x9, x10, [x23], #16
|
||||
subs x11, x11, #1
|
||||
b.ne 6b
|
||||
sub x21, x21, #1
|
||||
b 2b
|
||||
4:
|
||||
mov x19, x20
|
||||
b 1b
|
||||
5:
|
||||
dsb sy
|
||||
ic iallu
|
||||
dsb sy
|
||||
isb
|
||||
mov x0, x18
|
||||
mov x1, xzr
|
||||
mov x2, xzr
|
||||
mov x3, xzr
|
||||
br x17
|
||||
.ltorg
|
||||
qkx_reloc_end:
|
||||
ENDPROC(qkx_reloc_start)
|
||||
.global qkx_reloc_end
|
||||
|
||||
/* Reversible test of the very same TTBR0/physical/MMU-off transition.
|
||||
* x0..x4 are passed through to the physical code. The relocation loop uses
|
||||
* x17..x23, so preserve every callee-saved register and keep the translation
|
||||
* state in x24..x27 until the original SCTLR is restored and control returns
|
||||
* high.
|
||||
*/
|
||||
.align 4
|
||||
ENTRY(qkx_roundtrip)
|
||||
stp x19, x20, [sp, #-96]!
|
||||
stp x21, x22, [sp, #16]
|
||||
stp x23, x24, [sp, #32]
|
||||
stp x25, x26, [sp, #48]
|
||||
stp x27, x28, [sp, #64]
|
||||
str x30, [sp, #80]
|
||||
mrs x24, tcr_el1
|
||||
mrs x25, ttbr0_el1
|
||||
mrs x26, sctlr_el1
|
||||
adr x27, .Lroundtrip_return
|
||||
orr x9, x24, #(1 << 7)
|
||||
msr tcr_el1, x9
|
||||
isb
|
||||
dsb ishst
|
||||
tlbi vmalle1
|
||||
dsb ish
|
||||
isb
|
||||
msr ttbr0_el1, x0
|
||||
bic x9, x9, #0x3f
|
||||
mov x10, #25
|
||||
orr x9, x9, x10
|
||||
bic x9, x9, #(3 << 14)
|
||||
bic x9, x9, #(1 << 37)
|
||||
bic x9, x9, #(1 << 7)
|
||||
msr tcr_el1, x9
|
||||
isb
|
||||
br x1
|
||||
.Lroundtrip_return:
|
||||
/* Disable TTBR0 walks before restoring the saved translation context. */
|
||||
mrs x9, tcr_el1
|
||||
orr x9, x9, #(1 << 7)
|
||||
msr tcr_el1, x9
|
||||
isb
|
||||
msr ttbr0_el1, x25
|
||||
dsb ishst
|
||||
tlbi vmalle1
|
||||
dsb ish
|
||||
isb
|
||||
msr tcr_el1, x24
|
||||
isb
|
||||
ldr x30, [sp, #80]
|
||||
ldp x27, x28, [sp, #64]
|
||||
ldp x25, x26, [sp, #48]
|
||||
ldp x23, x24, [sp, #32]
|
||||
ldp x21, x22, [sp, #16]
|
||||
ldp x19, x20, [sp], #96
|
||||
ret
|
||||
ENDPROC(qkx_roundtrip)
|
||||
|
||||
.align 4
|
||||
ENTRY(qkx_probe_phys_start)
|
||||
mrs x9, sctlr_el1
|
||||
ldr x10, =SCTLR_ELx_FLAGS
|
||||
bic x9, x9, x10
|
||||
pre_disable_mmu_workaround
|
||||
msr sctlr_el1, x9
|
||||
isb
|
||||
mrs x0, sctlr_el1
|
||||
and x0, x0, x10
|
||||
/* Result zero proves the cleared flags were read back while physical. */
|
||||
msr sctlr_el1, x26
|
||||
isb
|
||||
br x27
|
||||
.ltorg
|
||||
.global qkx_probe_phys_end
|
||||
qkx_probe_phys_end:
|
||||
ENDPROC(qkx_probe_phys_start)
|
||||
|
||||
/* Appended to the copied relocation code for its reversible scratch test.
|
||||
* qkx_reloc_start leaves the completion value in x0, restores SCTLR here,
|
||||
* then returns through the high mapping saved by qkx_roundtrip.
|
||||
*/
|
||||
.align 4
|
||||
ENTRY(qkx_reloc_return_start)
|
||||
msr sctlr_el1, x26
|
||||
isb
|
||||
br x27
|
||||
.global qkx_reloc_return_end
|
||||
qkx_reloc_return_end:
|
||||
ENDPROC(qkx_reloc_return_start)
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Capture the device-specific inputs needed by prepare.py (read-only on device).
|
||||
# Usage: capture.sh <output-dir>
|
||||
set -euo pipefail
|
||||
O=$1; mkdir -p "$O"
|
||||
su() { adb shell "su -c \"$1\""; }
|
||||
# /sys/firmware/fdt is the original boot FDT. Kexec needs the kernel's
|
||||
# runtime tree after overlays/fixups; using the boot FDT caused a cold reset.
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
adb exec-out 'su -c "tar -C /proc/device-tree -cf - ."' |
|
||||
tar --warning=no-timestamp -C "$tmp" -xf -
|
||||
dtc -q -I fs -O dtb "$tmp" -o "$O/runtime.dtb"
|
||||
su 'cat /proc/iomem' > "$O/iomem"
|
||||
su 'cat /proc/cmdline' > "$O/cmdline"
|
||||
adb exec-out 'su -c "zcat /proc/config.gz"' > "$O/android.config"
|
||||
adb shell uname -r > "$O/android-release"
|
||||
echo "captured into $O: runtime.dtb iomem cmdline android.config android-release"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a payload directory from the captured inputs and a target Image.
|
||||
# Usage: prep.sh <captured-dir> <Image> <initramfs> <out-dir> [extra cmdline...]
|
||||
set -euo pipefail
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
C=$1 IMG=$2 RD=$3 OUT=$4; shift 4
|
||||
# Android's cmdline minus the boot-time watchdog bits, plus what the target needs.
|
||||
BASE=$(sed -e 's/softdog.soft_panic=1//' "$C/cmdline")
|
||||
python3 "$HERE/prepare.py" --live-dtb "$C/runtime.dtb" --iomem "$C/iomem" \
|
||||
--kernel-image "$IMG" --initramfs "$RD" --output "$OUT" \
|
||||
--cmdline "$BASE rdinit=/init nokaslr printk.devkmsg=on qkx_qmp_adopt=1 initcall_blacklist=virtual_sensor_driver_init $*" \
|
||||
> "$OUT.manifest.log"
|
||||
echo "prepared $OUT"
|
||||
Executable
+143
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Prepare Image + initramfs + DTB for the Quest Pro kexec staging layout.
|
||||
|
||||
The Android boot image is optional; it is only needed when the kernel or
|
||||
initramfs is taken from it.
|
||||
|
||||
Only writes host files. Does not contact the headset or execute a boot.
|
||||
"""
|
||||
import argparse
|
||||
import gzip
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def align(n, a):
|
||||
return (n + a - 1) & -a
|
||||
|
||||
|
||||
def unpack(data):
|
||||
if len(data) < 1660 or data[:8] != b"ANDROID!":
|
||||
raise ValueError("not an Android boot image")
|
||||
ksize, _, rsize, _, ssize, _, _, page, version, _ = struct.unpack_from("<10I", data, 8)
|
||||
if version not in (0, 1, 2):
|
||||
raise ValueError("only boot header versions 0, 1 and 2 are supported")
|
||||
if page not in (2048, 4096, 8192, 16384) or not ksize:
|
||||
raise ValueError("invalid page or kernel size")
|
||||
if ssize:
|
||||
raise ValueError("second-stage bootloader payloads are not supported")
|
||||
roff = align(page + ksize, page)
|
||||
soff = align(roff + rsize, page)
|
||||
if soff + ssize > len(data) or roff + rsize > len(data):
|
||||
raise ValueError("truncated boot image")
|
||||
kernel = data[page:page + ksize]
|
||||
if kernel.startswith(b"\x1f\x8b"):
|
||||
# Gzip streams with appended DTBs need separate handling, not guesses.
|
||||
kernel = gzip.decompress(kernel)
|
||||
if len(kernel) < 64 or kernel[56:60] != b"ARM\x64":
|
||||
raise ValueError("kernel is not an uncompressed/gzip ARM64 Image")
|
||||
text_offset, image_size, flags = struct.unpack_from("<QQQ", kernel, 8)
|
||||
if not image_size or image_size < len(kernel) or flags & 1:
|
||||
raise ValueError("unsupported ARM64 image size/endianness (appended data must be separated)")
|
||||
if flags & 6 not in (0, 2):
|
||||
raise ValueError("this loader requires a 4K-page target")
|
||||
if not flags & 8:
|
||||
raise ValueError("target does not permit placement away from the base of RAM")
|
||||
cmdline = (data[64:576].split(b"\0", 1)[0] +
|
||||
data[608:1632].split(b"\0", 1)[0]).decode("ascii")
|
||||
return kernel, data[roff:roff + rsize], dict(
|
||||
boot_header_version=version, text_offset=text_offset,
|
||||
image_size=image_size, image_file_size=len(kernel), flags=flags,
|
||||
cmdline=cmdline, boot_sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
|
||||
def check_ram(iomem, start, size):
|
||||
"""Require one System RAM extent and reject every nested reserved extent."""
|
||||
end = start + size
|
||||
ranges = []
|
||||
for line in iomem.splitlines():
|
||||
span, sep, label = line.strip().partition(" : ")
|
||||
if not sep:
|
||||
continue
|
||||
lo, hi = (int(x, 16) for x in span.split("-"))
|
||||
ranges.append((lo, hi + 1, label))
|
||||
if not any(lo <= start < end <= hi and label == "System RAM" for lo, hi, label in ranges):
|
||||
raise ValueError("destination does not fit in a captured System RAM extent")
|
||||
for lo, hi, label in ranges:
|
||||
if label != "System RAM" and start < hi and lo < end:
|
||||
raise ValueError(f"destination overlaps {label}: {lo:#x}-{hi:#x}")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--boot", type=Path, help="Android boot.img (v0-v2) to take missing parts from")
|
||||
ap.add_argument("--live-dtb", type=Path, required=True)
|
||||
ap.add_argument("--iomem", type=Path, required=True)
|
||||
ap.add_argument("--output", type=Path, required=True)
|
||||
ap.add_argument("--initramfs", type=Path, help="optional replacement; original is retained separately")
|
||||
ap.add_argument("--cmdline", help="explicit replacement command line")
|
||||
ap.add_argument("--kernel-image", type=Path, help="replacement raw ARM64 Image")
|
||||
ap.add_argument("--keep-watchdog", action="store_true",
|
||||
help="leave /soc/qcom,wdt@17c10000 enabled in the DTB")
|
||||
a = ap.parse_args()
|
||||
if a.boot:
|
||||
kernel, original, report = unpack(a.boot.read_bytes())
|
||||
elif a.kernel_image and a.initramfs:
|
||||
kernel, original, report = b"", b"", dict(cmdline="")
|
||||
else:
|
||||
ap.error("--boot is required unless --kernel-image and --initramfs are both given")
|
||||
if a.kernel_image:
|
||||
kernel = a.kernel_image.read_bytes()
|
||||
if len(kernel) < 64 or kernel[56:60] != b"ARM\x64":
|
||||
raise ValueError("replacement is not a raw ARM64 Image")
|
||||
offset, size, flags = struct.unpack_from("<QQQ", kernel, 8)
|
||||
if size < len(kernel) or flags & 1 or flags & 6 not in (0, 2) or not flags & 8:
|
||||
raise ValueError("unsupported replacement Image")
|
||||
report.update(text_offset=offset, image_size=size, flags=flags,
|
||||
image_file_size=len(kernel), replacement_kernel=str(a.kernel_image),
|
||||
kernel_sha256=hashlib.sha256(kernel).hexdigest())
|
||||
if report["text_offset"] != 0x80000 or report["image_size"] > 0x2F80000:
|
||||
raise ValueError("image does not fit this Quest Pro staging layout")
|
||||
initrd = a.initramfs.read_bytes() if a.initramfs else original
|
||||
dtb = a.live_dtb.read_bytes()
|
||||
if len(dtb) < 40 or struct.unpack_from(">I", dtb)[0] != 0xD00DFEED:
|
||||
raise ValueError("invalid live DTB")
|
||||
if len(initrd) > 0x200000 or len(dtb) > 0x200000:
|
||||
raise ValueError("initramfs/DTB exceeds staging limits")
|
||||
# Leave a single exclusion window for source, descriptor and control pages.
|
||||
check_ram(a.iomem.read_text(), 0x90000000, 0x3400000)
|
||||
a.output.mkdir(parents=True, exist_ok=True)
|
||||
(a.output / "Image").write_bytes(kernel)
|
||||
if original:
|
||||
(a.output / "original-ramdisk.gz").write_bytes(original)
|
||||
(a.output / "initramfs").write_bytes(initrd)
|
||||
dtpath = a.output / "boot.dtb"
|
||||
dtpath.write_bytes(dtb)
|
||||
args = a.cmdline if a.cmdline is not None else report["cmdline"]
|
||||
subprocess.run(["fdtput", "-t", "s", str(dtpath), "/chosen", "bootargs", args], check=True)
|
||||
# The target kernel takes over the apps watchdog from the loader instead of
|
||||
# probing it again; this matches every validated boot.
|
||||
if not a.keep_watchdog:
|
||||
subprocess.run(["fdtput", "-t", "s", str(dtpath), "/soc/qcom,wdt@17c10000",
|
||||
"status", "disabled"], check=True)
|
||||
for name, address in (("linux,initrd-start", 0x93000000),
|
||||
("linux,initrd-end", 0x93000000 + len(initrd))):
|
||||
subprocess.run(["fdtput", "-t", "x", str(dtpath), "/chosen", name,
|
||||
"0", f"{address:x}"], check=True)
|
||||
report.update(kernel_address=0x90080000, initrd_address=0x93000000,
|
||||
dtb_address=0x93200000, initrd_size=len(initrd),
|
||||
dtb_size=dtpath.stat().st_size, effective_cmdline=args,
|
||||
replacement_initramfs=a.initramfs is not None,
|
||||
crc32={name: f"{zlib.crc32(content):08x}" for name, content in
|
||||
(("Image", kernel), ("initramfs", initrd), ("boot.dtb", dtpath.read_bytes()))},
|
||||
status="prepared only; no boot attempted")
|
||||
(a.output / "manifest.json").write_text(json.dumps(report, indent=2) + "\n")
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run a shell command on the kexec target via its ECM telnetd (10.42.0.2:23).
|
||||
|
||||
Usage: qkx_sh.py 'COMMAND' [timeout_seconds] (env QKX_HOST overrides 10.42.0.2)
|
||||
"""
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
cmd = sys.argv[1]
|
||||
limit = float(sys.argv[2]) if len(sys.argv) > 2 else 15
|
||||
marker = "QKX%dDONE" % os.getpid()
|
||||
s = socket.create_connection((os.environ.get("QKX_HOST", "10.42.0.2"), 23), timeout=5)
|
||||
s.settimeout(0.2)
|
||||
# Split the marker in the command so only real output contains it whole.
|
||||
s.sendall(('%s; echo QKX%d""DONE\n' % (cmd, os.getpid())).encode())
|
||||
buf = b""
|
||||
end = time.time() + limit
|
||||
while time.time() < end and marker.encode() not in buf:
|
||||
try:
|
||||
chunk = s.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
buf += chunk
|
||||
except socket.timeout:
|
||||
pass
|
||||
s.close()
|
||||
out = buf.replace(b"\xff\xfb\x01", b"").replace(b"\xff\xfb\x03", b"")
|
||||
sys.stdout.write(out.decode(errors="replace").split(marker)[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# After the device is back in Android, print the log the target kernel kept
|
||||
# in RAM across a warm reset, plus any Android pstore panic record.
|
||||
set -uo pipefail
|
||||
adb shell 'su -c "insmod /data/local/tmp/qkx_marker.ko; rmmod marker_read; dmesg"' |
|
||||
awk '/qkx_marker_read:/{f=1} f'
|
||||
adb shell 'su -c "cat /sys/fs/pstore/dmesg-ramoops-0 2>/dev/null"' | grep -a 'quest_kexec\|Kernel panic' | tail -5
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/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=$HERE/module/quest_kexec.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" "$HERE/module/marker_read.ko" "$P/Image" "$P/initramfs" "$P/boot.dtb"; do
|
||||
[ -f "$f" ] || { echo "missing $f"; exit 1; }
|
||||
done
|
||||
adb push "$MOD" $T/qkx.ko >/dev/null
|
||||
adb push "$HERE/module/marker_read.ko" $T/qkx_marker.ko >/dev/null
|
||||
adb push "$P/Image" $T/qkx-Image >/dev/null
|
||||
adb push "$P/initramfs" $T/qkx-initramfs >/dev/null
|
||||
adb push "$P/boot.dtb" $T/qkx-boot.dtb >/dev/null
|
||||
adb shell 'su -c "sync; sync"'
|
||||
for pair in "$MOD:qkx.ko" "$P/Image:qkx-Image" "$P/initramfs:qkx-initramfs" "$P/boot.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
|
||||
# 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
|
||||
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"
|
||||
Reference in new issue
Block a user