3.5. Physical keys: power, volume, and the boot-mode dispatcher¶
This device has exactly two physical buttons: Power (KPDPWR) and Volume-Down (RESIN). There is no Volume-Up button wired at the hardware level. Everything that is a physical key you actually press — including how ABL reads them at power-on to choose a boot mode — lives on this page. Everything else that merely uses a GPIO pin without being something you press (the hall-effect cover switch, the keyboard-cover connector pins) is not a key and stays in Misc sensors (fingerprint, hall, keyboard cover).
3.5.1. Two independent bindings¶
The same two physical buttons are wired through two separate, unrelated DT paths, read by two different pieces of software at two different times:
Path |
When it’s read |
Binding |
|---|---|---|
PMIC PON registers ( |
Pre-boot, by XBL |
|
Android |
At runtime, by the booted kernel |
|
The boot-mode state machine below is entirely about the first path
(PMIC PON, read by XBL/ABL before Android ever starts) — the gpio_keys
node has nothing to do with boot-mode selection; it’s how the running
Android kernel later reports volume presses to apps.
ABL selects its boot mode from the buttons read at power-on. It reads keystrokes
as EFI scancodes from an EFI_SIMPLE_TEXT_INPUT_EX protocol installed by XBL
ButtonsDxe, dispatches on the first keystroke, and runs timed debounce loops
that decode recovery, fastboot and download entry. RVAs are LinuxLoader-relative
(= ABL DXE file offset − 0xb8); runtime = load base + RVA.
3.5.2. Timing basis¶
Every debounce cycle calls gBS->Stall(0x186A0 = 100000 µs) via gBS at RVA
0x51e38 ([gBS+0xF8]=Stall, [+0x118]=OpenProtocol confirm it is Boot
Services), giving approximately 100 ms per cycle. Cycle counts are code-exact;
wall-clock assumes gBS->Stall microseconds (~100 ms/cycle).
3.5.4. PMIC PON reason register¶
Distinct from the keystroke codes. GetPonReason (0x1968) reads the PON
reason register; per ABL’s own log its bits are: bit3=USB, bit4=CBLPWR, bit5=PON1,
bit6=DC, bit7=KPDPWR. This register is used only as the gate inside BootIntoMode
(arg & 0x10 = CBLPWR), not as the 2/8 keystroke codes. XBL vocabulary matches
(KPDPWR/RESIN/PON1/SMPL/RTC/USB_CHG/DC_CHG/CBLPWR/PS_HOLD).
3.5.5. Top-level dispatch¶
Switch on first ReadKeyStroke ([sp+0x10]):
==2 -> BootIntoMode(PON_word) (0x15d0 -> 0x1bfc)
==8 -> BootIntoMode(PON_word)
==0x102 -> GetBootIntoModeRecovery() (0x1638 -> 0x1e0c) [5-tap]
==0x17 -> dload flag[5]=1 ; 0x10708 -> reboot into EDL
==1 -> recovery flag[4]=1
default -> "Boot reason 0x%x not handled" -> normal boot
g_mode_flags at RVA 0x51e18: [0]=fastboot [4]=recovery [5]=dload
[8]=ChgPresent. 0x10708 = UEFI var RESET_PARAM="EDL" +
gRT->ResetSystem(EfiResetPlatformSpecific,"EDL").
3.5.6. BootIntoMode (0x1bfc)¶
Handles dload / fastboot / hold-recovery. Gate (0x1c70): dload/recovery-hold
branch if (PON & 0x10 CBLPWR) | ChgPresent; else power-key (fastboot) branch.
Then a debounce poll of the held key:
Path |
requirement (≈100 ms/cycle) |
outcome |
|---|---|---|
EDL/Download (hold) |
combo held ≥50 cyc (~5 s) then release |
dload flag |
Recovery (hold) |
combo held full 100 cyc (~10 s) |
recovery flag |
Fastboot |
Power held full 100 cyc (~10 s) |
fastboot flag |
3.5.6.1. BootIntoDload deep-dive¶
Commit block at 0x1cd0 sets g_mode_flags[5] and logs “BootIntoDload
detected”. Two ways in:
software cookie: reboot-reason
0x17sets flag[5] directly (0x1690) →0x10708;hardware:
BootIntoModedload branch (gate above) held 50–99 cycles then released.
The poll loop re-reads the key (0x218c``→ReadKeyStroke) each ~100 ms; 50–99 cyc
→ dload, ≥100 cyc → recovery, <50 → abort. Function A ``GetPonReason
(0x1968) supplies the PON word (gate); Function B is BootIntoMode
(0x1bfc).
3.5.7. GetBootIntoModeRecovery (0x1e0c) — the 5-tap recovery¶
Entered on power-key boot (reason 0x102). Debug strings:
"GetBootIntoModeRecovery detect begin. Power key Down/Up",
"BootIntoMode detect seg[%d] Power key Down/Up",
"BootIntoRecovery detected".
Stage 1 — hold-then-release (0x1e78):
0x1e88 cmp w22,#0x64(100); b.eq abort ; held ≥10 s -> abort
0x1e98 read key ; 0x1ea0 cbz -> exit on RELEASE
0x1ea8 Stall(100000)=~100ms ; w22++
0x1eb4 cmp w22,#0x1e(30); b.lo abort ; must have been held ≥30 cyc (~3 s)
Stage 2 — count 5 power taps (0x1ee8):
0x1ef0 per-tap timeout = 0x14 (20 cyc ~2 s)
0x1ef8 cmp w21,#5 ; b.eq success ; TAP COUNTER w21
(wait press==0x102, then release==0, each ≤~2 s; increment @0x1fb8)
0x1fc0 cmp w21,#5 ; b.ne abort
0x1fd4 strb w8,[g_mode_flags+4] ; RECOVERY
3.5.8. The documented recovery sequence, mapped¶
Documented user sequence: (1) power off; (2) hold Power until blue LED; (3) release, hold ~8 s; (4) release, tap Power ×5.
Step |
Code |
Why |
|---|---|---|
off → hold until blue LED |
power-key boot ⇒ reason |
blue LED is driven by XBL ( |
release, hold ~8 s |
stage 1: hold ≥30 cyc (~3 s), release, <100 cyc (~10 s) |
must release; loop advances only on release ( |
release, tap ×5 |
stage 2: 5 press/release edges, |
count-to-5 |
Why ~8 s: the lower bound is ABL’s ~3 s counted hold plus the XBL→ABL boot
latency after the LED (ABL only starts counting once it reaches 0x1e78); the
upper bound is the PMIC KPDPWR S2 reset ~8 s (see
PMIC reset timers, charging and reachability) — release before that or the SoC
power-cycles. “~8 s” is the PMIC ceiling, floored by ABL’s 3 s plus latency.
Why release/re-press (not just UX): stage 1 advances only on the release edge; stage 2 counts 5 tap edges; and a single continuous hold would hit the PMIC S2 reset (~8 s), so a continuous hold cannot reach recovery. Code-required, twice over.
3.5.9. Reachability¶
Which of these code paths a human finger can actually reach is bounded by the PMIC’s own hold-to-reset ceiling, not by this state machine — see PMIC reset timers, charging and reachability for the mode-by-mode reachability table.
3.5.10. Caveats¶
~100 ms/cycle assumes standard EFI Stall µs; cycle counts (30/50/100/20, taps=5) are exact.
3.5.11. Provenance¶
- Source:
../artifacts/abl_a/abl_dxe_fv.bin,../artifacts/boot_a/board.dts,../_READONLY/lun1/xbl_a.bin.- Method:
RVAs LinuxLoader-relative (= ABL DXE file offset −
0xb8); runtime = load base + RVA. Cycle counts are code-exact from disassembly; wall-clock assumesgBS->Stallmicroseconds (~100 ms/cycle).- Cross-refs:
ABL overview, key handling, fastboot, PMIC reset timers, charging and reachability, EDL / 9008 entry and the deep-flash cable, Charger-type detection and the unreachable fedl/LED branch, Misc sensors (fingerprint, hall, keyboard cover) (non-key GPIO accessories).