PCS alertLog (0x424) decode — 63/84 alerts from firmware layouts #12

Merged
outlandnish merged 2 commits from feat/pcs-alertlog into feat/odin-support 2026-08-28 18:06:58 -05:00
Owner

Reverse-engineers the PCS (variant-411, F28377D dual-core) alertLog packing and
recovers per-alert log-field bit layouts — the PCS analog of the drive-unit work.

What lands

  • 63/84 PCS alerts decode through the existing alert_log.py framework, from
    firmware-recovered bit layouts (docs/private/alerts/alertlog_layouts_2022.45.15.json,
    a new "PCS" node alongside DI/DIR).
  • Anchors hand-verified: a030 = {canRxErrorType:[0,3], canID:[8,16]} (matches Damien
    Maguire's handle424), plus a029, a034; 3 hand-traced bench alerts (a018/a044/a076).
  • Fixes a latent drive-unit bug: the CAN-rationality decoder paired offending-id /
    errorType / badValues positionally, but their catalog order varies (DIR a094 vs a066,
    PCS a030). Now matched by field name — fixes PCS and the DU a066 family.
  • tests/test_alert_log.py: PCS node allowed + test_pcs_layouts_anchor. 24 pass.

Mechanism

Each PCS alert builds a 4-word record = the 0x424 frame (word0 code/state, words 1-3
= the 48-bit payload over bytes 2-7, same LE view the framework decodes) then calls the
setter with the alert id as an explicit immediate. A dataflow interpreter over the
mask/shift/OR + FPU-clamp packing idiom recovers each field; counts checked against
libQtCarAlerts log_signals, non-overlap/<=48b gated. Extractor tooling stays
local/gitignored (docs/private/alertlog-extractor/), matching the DU convention.

Not covered (documented)

~11 alerts have no published field names in any rev; MIA alerts pack only a source bit;
~6 hard packers (a013/a053/a054/a073/a014/a038) have unverifiable internal splits left to
the reason-only fallback rather than guessed.

Ghidra: ~80 functions renamed on both PCS cores (saved to the programs, not in this PR).

Reverse-engineers the PCS (variant-411, F28377D dual-core) alertLog packing and recovers per-alert log-field bit layouts — the PCS analog of the drive-unit work. ## What lands - **63/84 PCS alerts** decode through the existing `alert_log.py` framework, from firmware-recovered bit layouts (`docs/private/alerts/alertlog_layouts_2022.45.15.json`, a new `"PCS"` node alongside DI/DIR). - Anchors hand-verified: `a030 = {canRxErrorType:[0,3], canID:[8,16]}` (matches Damien Maguire's `handle424`), plus `a029`, `a034`; 3 hand-traced bench alerts (a018/a044/a076). - **Fixes a latent drive-unit bug**: the CAN-rationality decoder paired offending-id / errorType / badValues *positionally*, but their catalog order varies (DIR a094 vs a066, PCS a030). Now matched by field name — fixes PCS **and** the DU a066 family. - `tests/test_alert_log.py`: PCS node allowed + `test_pcs_layouts_anchor`. 24 pass. ## Mechanism Each PCS alert builds a 4-word record = the 0x424 frame (word0 code/state, words 1-3 = the 48-bit payload over bytes 2-7, same LE view the framework decodes) then calls the setter with the alert id as an explicit immediate. A dataflow interpreter over the mask/shift/OR + FPU-clamp packing idiom recovers each field; counts checked against `libQtCarAlerts` log_signals, non-overlap/<=48b gated. Extractor tooling stays local/gitignored (`docs/private/alertlog-extractor/`), matching the DU convention. ## Not covered (documented) ~11 alerts have no published field names in any rev; MIA alerts pack only a source bit; ~6 hard packers (a013/a053/a054/a073/a014/a038) have unverifiable internal splits left to the reason-only fallback rather than guessed. Ghidra: ~80 functions renamed on both PCS cores (saved to the programs, not in this PR).
Reverse-engineered the PCS (variant-411, F28377D dual-core) alertLog packing and
recovered per-alert log-field bit layouts, the PCS analog of the drive-unit work.

Each PCS alert builds a 4-word record = the 0x424 frame (word0 code/state, words
1-3 = the 48-bit payload over bytes 2-7, same little-endian view the framework
already decodes) then calls the setter with the alert id as an explicit immediate.
A dataflow interpreter over the mask/shift/OR + FPU-clamp packing idiom (local
tooling in docs/private/alertlog-extractor) recovers each field; field counts are
checked against libQtCarAlerts log_signals, non-overlap/<=48b gated, and anchored
on a030 = {canRxErrorType:[0,3], canID:[8,16]} (matches Damien Maguire's handle424)
plus a029. 60/84 alerts emitted into the shared, rev-tagged layouts file under a
"PCS" node (cross-rev names borrowed from 2024/2026 where the 2022 catalog is
silent; tail flag groups split into 1-bit fields). The remaining alerts either
have no published field names, are MIA source-bit markers, or are register-built
packers left to the reason-only fallback.

Also fixes a latent drive-unit bug: the CAN-rationality decoder paired the
offending-id / errorType / badValues positionally, but their catalog order varies
(DIR a094 = [canID, errorType, ...] vs a066 = [badValue1, badValue2, canID,
errorType]); now matched by field name, which fixes PCS a030 and the DU a066 family.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three bench-relevant charge/DC-DC alerts the interpreter couldn't cleanly
resolve, traced by hand from the CPU1/CPU2 integer store/shift/mask path:
- a018 chgUnknownGridConfig: recovers the lost L1L2Locked flag (bit 39), set via
  the final AND #0x1f7f + OR rather than a per-flag clearmask.
- a044 12vSupportRegulation: FaultReasonCode is one 2-bit field (was split into
  two 1-bit), plus shortTime_us / dcdcLvBusCurrent_A / vTankPeak_abs_V.
- a076 dcdcEnDeassertedErr: the DAT_15484[] table byte at bits 8-15 splits into
  dcdcEnableLine[8,1] + dcdcPwmEnableDeassertedCount[9,7] by the ascending-bit ==
  catalog-order convention.

Recorded as verified overrides in the (local) extractor. The remaining bench
alerts (a013/a053/a054/a073/a014/a038) have unverifiable internal field splits
(pre-combined source regs, scratch-staged overwrites, catalog gaps) and are left
to the reason-only fallback rather than guessed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
outlandnish changed target branch from feat/odin-support to main 2026-08-28 18:06:39 -05:00
outlandnish changed target branch from main to feat/odin-support 2026-08-28 18:06:51 -05:00
outlandnish merged commit aa62496306 into feat/odin-support 2026-08-28 18:06:58 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
outlandnish/tm3diag!12
No description provided.