Files
HC_APTBS/docs/kline_eeprom_spec.md
LucianoDev 827b811b39 feat: developer tools page, auto-test orchestrator, BIP display, tests redesign
Bundles several feature streams that have been iterating on the working tree:

- Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the
  identification card, manual KWP write + transaction log, ROM/EEPROM dump
  card with progress banner and completion message, persisted custom-commands
  library, persisted EEPROM passwords library. New service primitives:
  IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync.
  Persistence mirrors the Clients XML pattern in two new files
  (custom_commands.xml, eeprom_passwords.xml).
- Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear
  K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and
  progress dialog VM, gated on dashboard alarms.
- BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at
  0x0106 via IKwpService.ReadBipStatusAsync; status definitions in
  BipStatusDefinition.
- Tests page redesign: TestSectionCard + PhaseTileView replacing the old
  TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/
  TestSectionView controls and their VMs.
- Pump command sliders: Fluent thick-track style with overhang thumb,
  click-anywhere-and-drag, mouse-wheel adjustment.
- Window startup: app.manifest declares PerMonitorV2 DPI awareness,
  MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and
  maximizes there (after the hook is in place) so the app fits the work
  area exactly on any display configuration.
- Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias
  importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and
  dump-functions reference docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 13:59:50 +02:00

207 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 5. EEPROM memory map (8-bit address, 0x000xFF)
The on-board serial EEPROM has 256 bytes addressed `0x000xFF`. The ECU's internal mirror after boot covers a sub-range only (`FUN_7568` loads EEPROM 0x400x8B into RAM 0x4000x44B); the upper area is read on demand.
Observed structure (from cross-dump comparison + disassembly):
| EEPROM range | Purpose | Notes |
|---|---|---|
| 0x000x3F | Pump cal record A — operating parameters | Read at boot via `FUN_4A79` (record-based, 8-byte chunks). Content is largely zero in current dumps. Erasable via cmd 0x05. |
| 0x400x8B | Pump cal record B — primary | Block-loaded into RAM 0x4000x44B at boot by `FUN_7568`. Contains: temp offset (0x42), dphi seed (0x44) + checksum (0x45), accel-comp seed (0x48), angle-table seed (0x4C), CKP loop seeds (0x500x56), redundant copies (0x790x7A), serial number ASCII (0x800x88) |
| 0x8C0xBF | Pump cal record C — extension | Not loaded into RAM at boot. Readable only via K-line cmd 0x19 after Zone 3 unlock. **Per user observation 2026-05-07: present in physical EEPROM, never consumed by running ECU code.** Likely OEM/factory metadata. |
| 0xC00xCF | Reserved / unused | Outside any zone bounds in observed configs |
| 0xD0 | Lockout backoff counter | Written by `FUN_29D4` failure path, read at boot by `FUN_3AD1`. Persists failed-auth state across power cycles. |
| 0xD70xDF | Zone 0 seed (9 bytes) | Read into RAM 0xB7+ as part of cmd 0x18 success response |
| 0xE10xE9 | Zone 1 seed (9 bytes) | Same |
| 0xEA0xF2 | Zone 2 seed / Zone 8 magic seed (9 bytes) | Same. Shared between Zone 2 and Zone 8. |
| 0xF30xFB | Zone 3 seed (9 bytes) | Same |
| 0xFC | Zone 0 use counter | Incremented on each cmd 0x18 + RB0=0x10 success |
| 0xFD | Zone 1 use counter | Same |
| 0xFE | Zone 2 / Zone 8 use counter | **Magic-zone access leaves this trace** |
| 0xFF | Zone 3 use counter | Same |
The "cal record" boundaries (A/B/C) are inferred from access patterns; the EEPROM does not have explicit headers separating them.
---
## 6. Security architecture
### 6.1 Zone descriptor table
A 4-entry × 12-byte table at **ROM 0x5FA0** drives zone authentication. Each entry has:
| Offset | Field | Type | Purpose |
|---|---|---|---|
| +0 | `alt_key` | u16-LE | Alternative key (used by cmd 0x18 with RB0 ≠ 0x10) |
| +2 | `exp_key` | u16-LE | Primary expected key |
| +4 | `zone_start` | u16-LE | First EEPROM address the zone covers |
| +6 | `zone_end` | u16-LE | Last EEPROM address (inclusive) |
| +8 | `flag_byte` | u8 | RE6/RE7 bit pattern set on success |
| +9 | `seed_off` | u8 | EEPROM offset of the 9-byte seed read on success |
| +10 | `cnt_off` | u8 | EEPROM offset of the per-zone use counter |
| +11 | reserved | u8 | usually 0xFF (unused) |
A **fifth implicit "magic zone 8"** is hard-coded in `FUN_29D4`:
- Key: **0x4453** (ASCII "DS")
- Range: 0x00000xFFFE (effectively the whole 64KB MCU address space)
- On success: `RE7 = 0xFF` (every gate-bit set, including the cmd 0x1A write gate)
- Seed/counter: shares Zone 2's offsets (0xEA / 0xFE)
### 6.3 Authentication flow (`FUN_29D4`)
Tester side:
1. **Build cmd 0x18 block** with:
- `RB3` = 0x03 (request length / sub-count)
- `RB4` = zone selector (0, 1, 2, 3, or 8)
- `RB5` = key high byte
- `RB6` = key low byte
- `RB7` = any non-zero byte (acts as continuation flag — without it, the ECU's `RWC4 = 0` reset short-circuits the success path)
- `RB0` = 0x10 if you also want write access enabled (sets the upper-nibble flag bits AND increments the use counter)
- Otherwise `RB0` = block length (4 + data bytes)
2. **TX block, observe response.** ECU returns success block (`RB2 = 0xF0`, payload = the 9-byte seed + zone bounds) or error (`RB2` = 0xE5/error pattern).
3. **On success**, internal flags are set:
- Zone 0 → `RE6 |= 0x01` (read) or `|= 0x11` if RB0=0x10 (read+write)
- Zone 1 → `RE6 |= 0x02` or `|= 0x22`
- Zone 2 → `RE6 |= 0x04` or `|= 0x44`
- Zone 3 → `RE7 |= 0x08`
- Zone 8 → `RE7 = 0xFF` (and cmd 0x1A becomes available)
4. The flags persist for the rest of the K-line session; they reset on session end / power cycle.
ECU-side details an implementer should be aware of:
- The 9-byte seed read at `EEPROM[seed_off..seed_off+8]` is **echoed back to the tester in the response**. It does not gate access — pure transport, presumably so the tool can fingerprint the unit.
- `RE8` (lockout state) must be 0 to attempt auth. If non-zero (set by previous failed attempts), the ECU silently fails until the lockout timer (`FUN_38BE`) decrements RE8 to 0.
- Wildcard match: if `exp_key == 0xFFFF` in any zone descriptor, ANY tester key is accepted. **None of the observed dumps use this**, but a future ROM variant might.
- Sentinel: if `exp_key == 0x5555`, the zone is permanently disabled (cannot be unlocked). None observed yet either.
### 6.4 Lockout mechanism
Implemented in `FUN_29D4` failure path + `FUN_38BE` timer.
```
fail #1 -> RE9 = 1, RE8 = 1, stored to EEPROM 0xD0
fail #2 -> RE9 = 2, RE8 = 2
fail #3 -> RE9 = 4
...
fail #8 -> RE9 = 128
fail #9+ -> RE9 = 240 (saturated)
```
`RE8` decrements once per ~30000 ticks (active session) or ~120000 ticks (idle). Each decrement re-writes EEPROM 0xD0 — the lockout therefore **persists across power cycles**. A wedged unit can require minutes to hours of waiting to retry.
**Recommendation for the reader software:** read EEPROM 0xD0 before any auth attempt (via cmd 0x19 after Zone 3 unlock — the only safe path that doesn't risk further lockout).
## 7. Read recipes
### 7.1 Recipe A — public read of 0x000xBF (Zone 3)
This is the safest operation: it requires a static key (0x00FF) that is identical across every variant we have dumps for, has no destructive side-effects, does not increment the magic-zone counter, and covers the full "general data" portion of the EEPROM.
```
# Step 1 — unlock Zone 3
TX: [ 06 NN 18 03 03 00 FF FF 03 ]
len=06, seq=NN, cmd=0x18, RB3=03, RB4=03 (Zone 3),
RB5=00, RB6=FF (key 0x00FF), RB7=FF (continuation flag),
end=03
ECU response (success): RB2 = 0xF0, plus 9-byte seed @ 0xF3..0xFB.
ECU now has RE7 |= 0x08.
# Step 2 — read EEPROM in 13-byte chunks
for offset in 0x00, 0x0D, 0x1A, 0x27, 0x34, 0x41, 0x4E, 0x5B,
0x68, 0x75, 0x82, 0x8F, 0x9C, 0xA9, 0xB6:
TX: [ 04 NN 19 0D 00 offset 03 ]
cmd=0x19 (read EEPROM), RB3=0x0D (13 bytes), RB5=offset
RX: 13 bytes from EEPROM[offset..offset+12] in the response payload
# 15 chunks * 13 bytes = 195 bytes, covering 0x00..0xC2
# Trim or adjust the last chunk's RB3 to 0x0A so it stops at 0xBF inclusive.
```
### 7.2 Recipe B — full read of 0x000xFF (Zone 0)
Reads bytes that Zone 3 cannot see (0xC00xFF — counters, seeds, lockout). Requires the OEM key 0x00A6.
```
# Step 1 — unlock Zone 0
TX: [ 06 NN 18 03 00 00 A6 01 03 ]
RB4=00 (Zone 0), RB5=00, RB6=A6, RB7=01
# Step 2 — read EEPROM via cmd 0x03 (per-zone path)
for offset in 0x00, 0x0A, 0x14, ..., 0xF6:
TX: [ 06 NN 03 0A 00 offset 03 ]
cmd=0x03, RB3=0x0A (10 bytes), RB4:RB5 = 0x00:offset (16-bit address)
# 26 chunks * 10 bytes = 260 bytes, slightly over-reads;
# adjust the last chunk's RB3 to stop at 0xFF.
```
### 7.3 Recipe C — magic full access (Zone 8) — read AND write
```
# Step 1 — unlock Zone 8 with the master key
TX: [ 06 NN 18 03 08 44 53 01 03 ]
RB4=08 (magic zone), RB5=44, RB6=53 (key "DS" = 0x4453), RB7=01
# After: RE7 = 0xFF (every gate-bit set).
# All read commands work; cmd 0x1A is also available for writing.
# WARNING: this access path increments EEPROM[0xFE] (the use counter).
# The increment is non-destructive but forensically observable — service
# tools at the OEM can read 0xFE to see how many times Zone 8 was accessed.
```
### 7.4 Recipe D — fast-path RAM-mirror dump (no auth)
For EEPROM 0x400x8B specifically, you don't need to talk to the EEPROM driver at all — the data is mirrored into RAM 0x4000x44B at boot and accessible via cmd 0x01 (read RAM, no auth required).
```
for offset in 0x400, 0x40D, 0x41A, ..., 0x444:
TX: [ 04 NN 01 0D high_addr low_addr 03 ]
cmd=0x01 (read RAM/ROM), RB3=0x0D, RB4=high, RB5=low
# 6 chunks covering 0x400..0x44B = exact mirror of EEPROM 0x40..0x8B.
```
This recipe is ideal for monitoring tools that want to observe the live EEPROM cache without authentication overhead.
---
## 8. Write recipes — DANGER ZONE
Writes require either Zone 0/1/2 unlock with `RB0 = 0x10` (cmd 0x0C path) or Zone 8 (cmd 0x1A path).
### 8.1 Cmd 0x0C — write via OEM zone (with RB0 = 0x10 unlock)
```
# Step 1 — unlock Zone 0 with write enabled
TX: [ 06 NN 18 03 00 00 A6 01 03 ] # NOTE: RB0 must be 0x10, not 0x06,
# to set the read+write flag pattern
# (RC8 << 4 | RC8) and increment 0xFC.
```
(Implementer task: verify the RB0=0x10 vs RB0=0x06 distinction by experiment — it's set in `FUN_29D4 @ 0x29D4` based on `RB0 == 0x10` test, but the call path that actually populates `RB0` differs between the dispatcher's path-A and path-B.)
```
# Step 2 — cmd 0x0C write with verify
TX: [ 0E NN 0C NN 00 offset b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 03 ]
cmd=0x0C, RB3=count<11, RB4:RB5 = address, RB6+ = bytes to write
```
### 8.2 Cmd 0x1A — write via Zone 8 (after master unlock)
After Zone 8 is unlocked (RE7 = 0xFF), cmd 0x1A becomes available:
```
TX: [ ... NN 1A count rb4 offset b1..bN 03 ]
cmd=0x1A, RB3=count<11, RB5=EEPROM offset (8-bit), data bytes from RAM 0xB6+
```
Each byte is written and immediately read-back-verified internally by `FUN_22EA`. On verify failure, the response indicates failure and the partial write is left in place.
---