[READ-ONLY] Mirror of https://github.com/jmrplens/kleidos. Kleidos — Hardware BLE password manager for ESP32-S3 (M5StickC Plus2, StickS3, M5Stack Gray, T-Deck, Cardputer) jmrplens.github.io/kleidos/
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 10 files
README.md

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=0x51 for the BM8563). Printed by REGS?.
  • addr=0x.. — the register address: the offset of one register inside that chip (e.g. addr=0x02 = the VL/seconds register). Printed by REGR / REGS? <chip> on every REG … line.
  • base=0x.. — the MMIO block base (ChipRegmap::mmioBase): printed by REGS? instead of dev= for bus=mmio chips (e.g. base=0x60008000 for rtccntl on the S3). Per-register addr=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:

  • REGW to a non-critical register writes normally.
  • REGW to 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 reject REGW.

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.h feature macro (HAS_BM8563_RTC, HAS_AXP2101, HAS_M5PM1, PMIC_I2C_ADDR, DISPLAY_RESET_AW9523). HAS_AXP2101=1 is 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#