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 (qcom,qpnp-power-on@800)

Pre-boot, by XBL ButtonsDxe / ABL

KPDPWRKEY_POWER (0x74), RESINKEY_VOLUMEDOWN (0x72); no gpio-keys node in this path, no VOL+ line at all

Android gpio_keys node

At runtime, by the booted kernel

vol_up alone in fragment@28; both vol_up/vol_down re-declared together in an override fragment, fragment@79 — two DT fragments for the same logical device, not a discrepancy

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.3. Key input and code-to-button map

ABL reads keys via ReadKeyStroke on EFI_SIMPLE_TEXT_INPUT_EX (GUID DD9E7534-7762-4698-8C14-F58517A625AA at RVA 0x4dcb4; helper 0x1d9a8). The returned values are EFI EFI_INPUT_KEY.ScanCode, not PMIC PON register bits.

The physical-key-to-scancode mapper is XBL ButtonsDxe, not ABL: it supports PWR/VOL+/VOL- via PMIC PON (KPDPWR/RESIN) plus PMIC GPIO (VOL+), and installs the SimpleTextInputEx that ABL consumes. ABL contains no scancode assignment (only a printf uses 0x102).

Code

EFI key

Physical button (this device)

ABL action

0x01

SCAN_UP

VOL+ — generic; likely not wired, DTB has none

set recovery flag

0x02

SCAN_DOWN

Volume-Down (RESIN)

BootIntoMode

0x08

SCAN_DELETE

none on this 2-button device

BootIntoMode (dead path)

0x17

SCAN_ESC

none

direct EDL (RESET_PARAM="EDL")

0x102

vendor

Power (KPDPWR)

GetBootIntoModeRecovery (5-tap)

  • Power = 0x102 is confirmed (consumed as power everywhere; “Pressed down Power key[0x102]”).

  • VolDown = SCAN_DOWN (0x02) is inferred (UEFI standard plus RESIN is the only non-power button); ButtonsDxe’s keymap is data-driven, so the exact value was not byte-extractable.

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:

  1. software cookie: reboot-reason 0x17 sets flag[5] directly (0x1690) → 0x10708;

  2. hardware: BootIntoMode dload 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 0x102GetBootIntoModeRecovery

blue LED is driven by XBL ("not power_key_press, blue led on!", QcomChargerApp) → PBL done, XBL running, ABL about to load. Confirms power-on. Exact XBL branch is a dead end (GOT-indirected, see Charger-type detection and the unreachable fedl/LED branch).

release, hold ~8 s

stage 1: hold ≥30 cyc (~3 s), release, <100 cyc (~10 s)

must release; loop advances only on release (0x1ea0)

release, tap ×5

stage 2: 5 press/release edges, cmp #5 at 0x1ef8/0x1fc0 → recovery flag at 0x1fd4

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 assumes gBS->Stall microseconds (~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).