HAL Register Introspection#
Kleidos ships an in-house register-introspection engine: every I2C chip the
firmware drives (PMICs, the RTC, the I/O expander) carries a compile-time
register-map descriptor table (*_regmap.h) that names each register, its
access mode, its bit fields, and — critically — whether writing it can drop a
power rail, stop the clock, or disable a protection. The engine turns those
tables into decoded register dumps, named-field reads, a policed write path, and
a live telemetry sampler, all reachable from the serial debug console. Since
Phase 3.5 the same engine also describes one MMIO SoC peripheral — the RTC
controller (RTC_CNTL) on the internal-RTC boards — via BusKind::Mmio,
strictly read-only (see rtc-cntl.md). The pattern also extends
to a UART device: the u-blox M10 GNSS receiver on the T-Deck carries the same
compile-time, ///-documented descriptor table over its UBX-CFG configuration
space (gps_config_map.h), consumed by the GPSCFG/GPSMON/GPSPROBE console
commands (see gps-ublox-m10.md).
This subtree documents the engine and one page per chip. The chip pages are
generated from the *_regmap.h headers themselves — the header /// doc
lines are the source of truth; nothing here is invented beyond them.
What the engine is#
| Layer | Where | In which builds |
|---|---|---|
Register-map tables (*_regmap.h) |
src/drivers/** (I2C chips + the MMIO rtc_cntl_*_regmap.h pair) |
All builds (linked where the chip probes) |
Introspection engine (dumpChip, formatRegLine, findReg, write gate; MmioRegIo for BusKind::Mmio reads) |
src/drivers/introspection/ |
All builds |
Registry (registerChip on driver begin()) |
src/drivers/introspection/regmap_registry.h |
All builds |
Curated typed accessors (IPmic / IRtc methods) |
src/drivers/**, src/hal/common/ |
All builds, including production |
Console command surface (REGS?, REGR, REGW, TELEM, …) |
src/ui/debug/debug_regmap.cpp |
*_debug only (DEBUG_SERIAL_BUTTONS) |
A driver registers its table on the success path of begin() (e.g.
axp192.cpp:62, bm8563.cpp:54), so REGS? lists exactly the chips that
actually probed on this board — never a chip that is merely compiled in.
Console command surface#
The commands below are exposed by *_debug builds over the serial console
(DEBUG_SERIAL_BUTTONS). Output is key=value for machine parsing by the qa/
suite.
| Command | Purpose | Sample output |
|---|---|---|
REGS? |
Enumerate every registered chip | REGMAP chip=bm8563 bus=i2c dev=0x51 regs=16 · MMIO: REGMAP chip=rtccntl bus=mmio base=0x60008000 regs=21 |
REGS? <chip> |
Decode the chip's full register map | one REG … line per register (see below) |
REGR <chip> <reg|name> |
Read one register (numeric addr or case-insensitive name) | REG chip=bm8563 addr=0x02 name=VL_SECONDS val=0x25 vl=0 seconds=37 |
REGW <chip> <reg|name> <value> |
Write one register — refused on a critical register; MMIO chips refuse every write | REGW_ERR: critical … (gate) · MMIO: REGW_ERR: mmio is read-only |
REGW! <chip> <reg|name> <value> |
Bang override: write a critical register after printing the decoded target | prints target, then writes |
I2CDUMP <dev> <start> <count> |
Raw hex dump of an I2C device's register space | hex rows |
SPIRD <…> |
Raw SPI read (SPI-bus chips) | hex bytes |
TELEM? |
Telemetry sampler status + channel list | TELEM running=0 … |
TELEM START [period_ms] [ch,…] / STOP / DUMP / STREAM ON|OFF |
Sample registered telemetry channels | TELEM t=… ch=… v=… |
RES? |
Resource / heap + battery snapshot | RES role=target uptime_ms=… batt_mv=… … |
PMIC? |
PMIC model + battery + die temperature | PMIC model=axp2101 batt_pct=87 batt_mv=4012 charging=1 temp_c=31.5 |
RTC? |
RTC date/time + introspection fields | RTC ok=1 date=2026-07-08 time=14:03:37 vl=0 alarm_en=0 alarm_fired=0 timer_en=0 clkout=off trust=backed |
dev= vs addr= key convention#
Two distinct address keys appear in the output, and they must not be confused:
dev=0x..— the device address: the chip's 7-bit I2C bus address (e.g.dev=0x51for the BM8563). Printed byREGS?.addr=0x..— the register address: the offset of one register inside that chip (e.g.addr=0x02= the VL/seconds register). Printed byREGR/REGS? <chip>on everyREG …line.base=0x..— the MMIO block base (ChipRegmap::mmioBase): printed byREGS?instead ofdev=forbus=mmiochips (e.g.base=0x60008000forrtccntlon the S3). Per-registeraddr=0x..is then the byte offset added to that base.
A decoded register line is:
REG chip=<name> addr=0x<reg> name=<REG_NAME> val=0x<hex> [<field>=<v>[(label)] …]
Enum-decoded fields carry a parenthesized label, e.g. fd=3(1Hz).
Critical-write blocklist + bang override#
Each RegDesc carries a critical flag. A register is critical=true when a
write to it can drop a rail, power off / reset the device, stop the timekeeping
clock, change charger behavior, or disable a detection/protection. The write
path enforces a policy:
REGWto a non-critical register writes normally.REGWto a critical register is refused by the gate.REGW!(bang) forces the write, first printing the decoded target (chip, register, critical flag) so the operator sees exactly what they override.- Write-only registers reject
REGR(REGR_ERR: write-only); read-only registers rejectREGW.
QA write-back-safe rule. Automated HIL tests never send bang-writes. When
a test must exercise a critical register it performs a read-back write: it
reads the current value and writes back that exact value, so the chip state is
unchanged. Plain writes to critical registers are refused by design; only a human
operator, deliberately, uses REGW!.
Curated accessor layer#
Above the raw register tables sits a typed accessor layer on the driver
contracts IPmic (src/drivers/power/i_pmic.h) and IRtc
(src/drivers/rtc/i_rtc.h), surfaced device-agnostically through the HAL facades
(PowerManager, rtc::). These return decoded, unit-bearing values — battery
percent/millivolts, PMIC die temperature (°C), coulomb charge (mAh), the RTC VL
flag, alarm, countdown timer and CLKOUT — computed by the pure conversion helpers
in pmic_common.h / the BM8563 codec.
Unlike the REGS?/REGR console surface (debug builds only), the curated
accessors are compiled into every build, including production. Optional
capabilities default to return false so a chip that lacks a feature (no
die-temp ADC, no coulomb counter, no RTC backend) needs no override and the
caller can render na.
Documented chips#
| Chip | Page | Bus / addr | Regs | Board(s) |
|---|---|---|---|---|
| BM8563 / PCF8563 RTC | bm8563-rtc.md |
I2C 0x51 |
16 | m5stickc_plus1, m5stickc_plus2, cores3_se, core2_v13, m5core_ink |
| X-Powers AXP2101 PMIC | axp2101.md |
I2C 0x34 |
87 | cores3_se |
| X-Powers AXP192 PMIC | axp192.md |
I2C 0x34 |
105 | m5stickc_plus1, core2_v13 |
| M5PM1 (PY32) PMIC | m5pm1-py32.md |
I2C 0x6E |
24 | sticks3 |
| Injoinic IP5306 PMIC | ip5306.md |
I2C 0x75 |
13 | m5stack_gray |
| AWINIC AW9523B I/O expander | aw9523.md |
I2C 0x58 |
29 | cores3_se |
| ESP32/-S3 RTC controller (RTC_CNTL) | rtc-cntl.md |
MMIO 0x60008000 (S3) / 0x3FF48000 (classic) |
21 / 17 | sticks3, cardputer, tdeck, m5stack_gray (internal-RTC boards; read-only) |
| u-blox M10 GNSS receiver | gps-ublox-m10.md |
UART1 GPIO43/44 @ 38400 |
108 cfg keys | tdeck (T-Deck Plus) |
Board presence is verified against each
variants/<board>/variant.hfeature macro (HAS_BM8563_RTC,HAS_AXP2101,HAS_M5PM1,PMIC_I2C_ADDR,DISPLAY_RESET_AW9523).HAS_AXP2101=1is a code-path selector, not a literal AXP2101 — on m5stickc_plus1 and core2_v13 it drives an AXP192, and on m5stack_gray an IP5306. Each page states the real silicon.
RTC backends#
The rtc:: facade has two mutually-exclusive backends, selected per board by
HAS_BM8563_RTC, each with its own page:
| Backend | Boards | RTC? trust= |
Page |
|---|---|---|---|
| BM8563 / PCF8563 (battery-backed) | m5stickc_plus1, m5stickc_plus2, cores3_se, core2_v13, m5core_ink | backed |
bm8563-rtc.md |
| ESP32 internal RTC (volatile) | sticks3, cardputer, tdeck, m5stack_gray | volatile / none |
rtc-internal.md |
The RTC? snapshot carries a trust= field derived from rtc::clockTrust()
(none | volatile | backed), emitted on both the ok=1 and ok=0
lines so an unset internal clock is still observable as trust=none. It is the
single discriminator the RTC HIL suite asserts against. See
rtc-internal.md for the ClockTrust model and the
fail-closed volatile-clock lockout policy.
See also#
bm8563-rtc.md— the battery-backed RTC backend register map.rtc-internal.md— the ESP32 internal-RTC (volatile) backend,ClockTrustmodel, and fail-closed lockout policy.rtc-cntl.md— the SoC RTC controller (RTC_CNTL) MMIO register map behind the internal-RTC backend (read-only introspection).../devices/README.md— per-device hardware reference (specs, pin maps, chip inventory).../../testing/serial-debug-commands.md— every serial command in*_debugbuilds.../../testing/qa-system.md— theqa/pytest HIL system that drives these commands.