Files
shturman/docs/specs/v0.4-mcu-thermal.md
kk0t9 c377a34c4f docs(v0.4): спека MCU/thermal fail-safe (тепловой триггер + MCU-протокол + fail-safe)
Веха v0.4 (A12/B08/B09/B10) поверх живого FSM v0.3. Решение брейнсторма: разработка без
платы (принцип #13) — MCU-копилот как reference-арх, софт+симуляция, физический выбор
B08/B09 + железо отложены в HW-bring-up-подфазу. Симметрия с v0.3: чистое ядро (ThermalPolicy/
codec) → абстракция (TempSource/Throttler/Coprocessor trait) → dev-mock.

Скоуп: тепловая политика+гистерезис → Event::ThermalTrip (реюз FSM); SoC↔MCU протокол + кодек
(CRC/replay/desync-guard) + CoprocessorClient (heartbeat/wait-for-completion/safe-to-cut);
B09 fail-safe-таймер — модель (hang/budget → Event::FailsafeCut → off); D-Bus ThermalState/
ThermalChanged (контракт сейчас, рендер v0.5). Приёмка §9.4; швы §10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Alexander <akotenev2003@gmail.com>
2026-06-25 15:00:10 +03:00

292 lines
24 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.
# Спека реализации: v0.4 — MCU/thermal fail-safe (тепловой триггер + MCU-протокол + fail-safe-таймер)
> Веха `v0.4` роадмапы: «аппаратный фундамент питания/тепла». Capabilities **A12** (тепловой мониторинг +
> базовый throttling), **B08** (MCU-копилот shutdown-протокол), **B09** (MCU аппаратный fail-safe-таймер),
> **B10** (thermal shutdown: триггер + hysteresis + UX). Поверх **v0.3** (живой `PowerFsm` + graceful shutdown).
> Источники: `docs/domains/b-power-lifecycle.md` §4/§5/§6/§1a, `docs/contracts/hardware.md` §3/§1a,
> `docs/contracts/ipc.md` §3, `docs/contracts/safety.md`, `docs/roadmap.md` §v0.4. Приёмка роадмапы:
> **«thermal-trip → graceful; MCU-таймер режет питание, если SoC завис»**.
>
> **Решение скоупа (брейнсторм):** разрабатываем без платы (принцип #13). MCU-копилот принят как
> **reference-архитектура** (доки рек.); делаем **софт + симуляцию**, физический выбор **B08/B09**
> (MCU vs supercap-only) и реальное железо — отложены в **HW-bring-up-подфазу**. Симметрия с v0.3:
> чистое ядро (политика/кодек) → абстракция (trait) → dev-mock.
---
## 1. Цель и первый артефакт
Замкнуть **тепловой** и **MCU**-швы домена B поверх живого FSM v0.3: (а) тепловой монитор с гистерезисом
кормит существующий `Event::ThermalTrip` → graceful shutdown; (б) SoC↔MCU shutdown-протокол (heartbeat /
`safe-to-cut` / wait-for-completion) с защищённым кодеком; (в) модель **независимого fail-safe-таймера**
MCU детерминированно «режет питание», если SoC завис.
**Первый артефакт (в Lima, мок-MCU/sensor):**
1. **thermal-trip:** `SetTemp ≥ critical``ThermalTrip``ShutdownImminent(thermal)` → graceful (реюз v0.3-пути,
`/data` консистентен); восстановление по гистерезису.
2. **MCU fail-safe:** `HangSoc` (heartbeat пропал в `running`) → мок-MCU детерминированно снимает питание
(в VM = форс-`off` сервиса) — «MCU режет питание, если SoC завис»; `/data` консистентен.
3. **throttling:** `SetTemp` в warn/throttle-банде → throttle-действие записано (VM `Noop`), без shutdown;
гистерезис на спаде (нет осцилляции).
**Не цель v0.4:** реальный UART/I2C-драйвер, реальный cpufreq-эффект, прошивка MCU, физический B09-чип/supercap,
полный supercap-only-путь (остаётся абстракцией-fallback), thermal-рендер в Shell (**v0.5**), sleep/wake/
battery-cutoff (**v1/v2**), числовой тюнинг порогов/hold-up — **RK3588**. Перф/тепловой вердикт — на таргете.
---
## 2. Скоуп и границы
### 2.1 В скоупе (делаем сейчас)
- **Тепловая политика (A12/B10):** чистый `ThermalPolicy` — `temp + предыдущий уровень → ThermalLevel
∈ {Normal, Warn, Throttle(n), Critical}` с **гистерезисом** (раздельные пороги вверх/вниз — нет осцилляции).
Юнит-тестируемый без I/O. Пороги — placeholder-константы (`// тюнинг на RK3588`, hardware §1a).
- **Источник температуры (`TempSource` trait):** real = sysfs `/sys/class/thermal/thermal_zone*/temp` (max по
зонам); VM = `MockTempSource` (значение из dev-D-Bus `SetTemp`). В v0.4 активен mock.
- **Throttler (`Throttler` trait):** real = cpufreq-cap (best-effort, HW); VM = `NoopThrottler` (запись уровня
для E2E/лог). Эффект — абстракция; реальное снижение частоты — HW.
- **Thermal-монитор:** периодический poll на **монотонике** → политика → throttle + на `Critical` кормит
`Event::ThermalTrip` в FSM (тот же `apply_event` из v0.3). Восстановление до PONR → `Event::ThermalCleared`
(abort thermal-shutdown, симметрия с re-power; гейт по `reason == Thermal`).
- **SoC↔MCU протокол (B08):** типы сообщений `SocToMcu`/`McuToSoc`; **чистый кодек** (framing + seq + CRC16 +
**replay/desync-guard**) — закрывает требование «защита линка от replay/мусора/десинка» (B §5, hardware §4).
Байты текут через in-memory-канал (codec исполняется по-настоящему и в integration).
- **Coprocessor (`Coprocessor` trait):** real = `SerialCoprocessor` (UART — **стаб**, HW-подфаза); VM =
`MockCoprocessor` (in-process, кормится через dev-D-Bus).
- **SoC-side `CoprocessorClient`:** heartbeat в `running`; на `ShutdownImminent` → **wait-for-completion**
(расширенный таймаут ≥ shutdown-бюджет, B §6) — не короткий keepalive посреди unmount; `safe-to-cut` после
PONR → немедленный cut. MCU — **fail-safe-авторитет** (SoC не командует cut-на-ходу — B §5).
- **Fail-safe-таймер (B09) — модель:** `MockCoprocessor` моделирует независимый таймер: heartbeat пропал
(`running`) ИЛИ бюджет истёк без `safe-to-cut` → детерминированный cut (в VM = форс-`off` power-сервиса).
- **D-Bus-контракт (ipc §3):** property `ThermalState ∈ {normal, warn, throttle, critical}` + сигнал
`ThermalChanged(state, celsius)`; `ShutdownImminent(thermal)` уже есть. **Контракт сейчас — Shell рисует в v0.5.**
- **Харнесс:** юниты (политика/кодек/клиент/таймер) + integration (session-шина) + E2E-блок v0.4 (§9).
### 2.2 Явно НЕ в скоупе (отложено, с указателем «куда»)
- **Физический выбор B08/B09** (MCU-копилот vs supercap-only), реальный MCU-чип, прошивка, реальный
hold-up cap/supercap, реальный UART/I2C-драйвер + GPIO ACC-детект — **HW-bring-up-подфаза** (hardware §3).
- **Реальный cpufreq/DDR/GPU-throttling-эффект + числовые тепловые пороги/дератинг** — **RK3588** (hardware §1a).
- **Thermal-UX рендер в Shell** («перегрев»-overlay) — **v0.5** (живой shell; v0.4 даёт только контракт).
- **Supercap-only полный путь** (SoC-таймер + разряд cap, ACC-детект в софте) — остаётся **абстракцией-fallback**
(`Coprocessor` trait), тело — HW-подфаза при выборе supercap.
- **Sleep/wake/scheduled-wake/battery-cutoff** — **v1/v2** (B §7); состояния зарезервированы (как в v0.3).
- **Реальный `/dev/watchdog` арминг + MCU-watchdog-бэкстоп железом** — **HW** (в v0.4 — дисциплина/модель, как v0.3).
- **Перф-вердикт** (время до cut, hold-time, тепловая инерция) — **RK3588** (performance §2). В VM — функционально.
### 2.3 Частично в скоупе (каркас сейчас, тело — позже)
- **`Throttler`** — trait + `Noop` (лог уровня); реальный cpufreq — HW.
- **`Coprocessor`** — trait + `MockCoprocessor`; `SerialCoprocessor` (UART) — стаб, HW-подфаза.
- **`TempSource`** — trait + `MockTempSource`; `SysfsTempSource` — каркас (читает зоны, в Lima зоны статичны).
- **MCU-watchdog/линк-fail-safe** — логика клиента + модель таймера; реальный независимый чип — HW.
### 2.4 Трассируемость ID → статус
| ID | Веха | Статус в v0.4 |
|----|------|----------------|
| A12 | Тепловой мониторинг + базовый throttling | `ThermalPolicy` + `TempSource`/`Throttler` (VM mock/noop); пороги placeholder, эффект cpufreq — HW |
| B08 | MCU-копилот shutdown-протокол | типы + кодек (CRC/replay/desync) + `CoprocessorClient` (heartbeat/wait/safe-to-cut); транспорт = in-memory (UART — HW) |
| B09 | MCU аппаратный fail-safe-таймер | **модель** в `MockCoprocessor` (hang/budget → детерминированный cut); реальный независимый чип — HW |
| B10 | Thermal shutdown (триггер + hysteresis + UX) | триггер `ThermalTrip` (реюз FSM v0.3) + гистерезис + abort `ThermalCleared`; **UX-рендер — v0.5** (контракт-property/сигнал сейчас) |
---
## 3. Красные линии, безопасность
- **MCU/Power — только питание устройства, не CAN/actuator (#1/#2):** копроцессор мониторит зажигание/напряжение
и коммутирует **рейл питания SoC** — никаких узлов авто, никаких write/actuator-путей. Протокол SoC↔MCU не
несёт автомобильных команд. (Engine-state/OBD — домен E, read-only; Power **не трогает CAN**.)
- **MCU — fail-safe-авторитет (B §5):** ни одно SoC-сообщение не может (а) снять питание на ходу, (б) держать
вечно и разрядить АКБ. В модели `MockCoprocessor` cut инициируется **только** таймером MCU (hang/budget) или
`safe-to-cut` после PONR — не произвольной SoC-командой.
- **Защита линка:** кодек отбрасывает replay (seq ≤ last)/мусор (битый CRC)/десинк (resync по SYNC) —
юнит-доказано (§9.1). Аналог защиты CAN-линка (hardware §4).
- **Durability-инвариант v0.3 сохраняется:** thermal-trip и MCU-cut идут через тот же graceful-путь до PONR
(durable-barrier `sync` до unmount); после усечённого shutdown `/data` консистентен (atomic-write A §3).
- **prod-build-gate:** `--no-default-features` (без `dev-mocks`) → `PowerMock1`/`SetTemp`/`HangSoc` не
регистрируются (как в v0.3). dev-D-Bus-policy — dev-only drop-in.
---
## 4. Раскладка (новые/изменённые артефакты)
- **Create** `crates/core/shturman-power/src/thermal.rs` — `ThermalLevel`, `ThermalPolicy` (чистая, гистерезис),
`TempSource` trait (`SysfsTempSource`/`MockTempSource`), `Throttler` trait (`Cpufreq`-стаб/`NoopThrottler`),
`ThermalMonitor` (poll → политика → throttle + `ThermalTrip`/`ThermalCleared`).
- **Create** `crates/core/shturman-power/src/protocol.rs` — `SocToMcu`/`McuToSoc` (типы сообщений).
- **Create** `crates/core/shturman-power/src/codec.rs` — кадр (SYNC/LEN/SEQ/TYPE/PAYLOAD/CRC16) + encode/decode +
replay/desync-guard. Юнит-тесты в файле.
- **Create** `crates/core/shturman-power/src/coprocessor.rs` — `Coprocessor` trait, `MockCoprocessor` (in-process +
B09-таймер-модель), `SerialCoprocessor` (UART-стаб), `CoprocessorClient` (SoC-side: heartbeat/wait/safe-to-cut).
- **Modify** `crates/core/shturman-power/src/fsm.rs` — `Event::ThermalCleared` (+ переход abort из
`ShuttingDown{Abortable, reason: Thermal}` → `Running`); `Event::FailsafeCut` (→ `off` из любого не-`off`,
необратимо — MCU-авторитет); подтвердить армы `ThermalTrip`.
- **Modify** `crates/core/shturman-power/src/service.rs` — владеть `ThermalMonitor` + `CoprocessorClient` (кормят
FSM); property `ThermalState` + сигнал `ThermalChanged`; dev-mock расширить: `SetTemp(d)`, `HangSoc()`,
`McuLinkLoss()`.
- **Modify** `crates/core/shturman-power/src/lib.rs` — `pub mod thermal; pub mod protocol; pub mod codec; pub mod coprocessor;`.
- **Modify** `crates/shturman-ipc/src/proxy.rs` — `Power1Proxy`: property `ThermalState` + сигнал `ThermalChanged`.
- **Modify** `crates/core/shturman-power/tests/integration.rs` — thermal-trip / abort / fail-safe-cut по session-шине.
- **Modify** `tests/e2e/run.sh` — блок v0.4 (thermal-trip → graceful; MCU fail-safe; throttling/гистерезис).
- **Modify (швы §10)** `docs/domains/b-power-lifecycle.md`, `docs/contracts/hardware.md`, `docs/contracts/ipc.md` §3,
`docs/capability-catalog.md` (A12/B08/B09/B10), `CLAUDE.md`.
---
## 5. Тепловая подсистема (A12/B10) — контракт
```
ThermalLevel = Normal | Warn | Throttle(level: u8) | Critical
```
**Пороги (placeholder-константы, тюнинг на RK3588 — hardware §1a; Tjmax RK3588 ~100 °C):**
| Переход | Порог вверх | Порог вниз (гистерезис) |
|---------|-------------|--------------------------|
| Normal → Warn | `WARN_C = 75` | `WARN_C HYST` |
| Warn → Throttle | `THROTTLE_C = 85` | `THROTTLE_C HYST` |
| Throttle → Critical | `CRITICAL_C = 95` | `CRITICAL_C HYST` |
| `HYST = 5 °C` | | |
- **`ThermalPolicy::next(prev: ThermalLevel, temp_c) -> ThermalLevel`** — чистая; гистерезис = переход вниз
только ниже `(порог HYST)`, иначе уровень держится (нет осцилляции на границе).
- **`ThermalMonitor`** (tokio-интервал на монотонике, ~`POLL_SECS = 1`): `temp = TempSource::read()` →
`lvl = policy.next(prev, temp)`; при смене уровня: `Throttler::apply(lvl)` + `ThermalChanged(state, temp)`;
**на входе в `Critical`** → `apply_event(Event::ThermalTrip)`; **на выходе из `Critical`** (по гистерезису),
если FSM ещё в `ShuttingDown{Abortable, reason: Thermal}` → `apply_event(Event::ThermalCleared)`.
- **`ThermalState` (D-Bus property)** = проекция текущего `ThermalLevel` (`Throttle(_)` → `"throttle"`).
---
## 6. SoC↔MCU протокол (B08) + fail-safe-таймер (B09) — контракт
### 6.1 Сообщения
```
SocToMcu = Heartbeat { seq } // периодический keepalive в running/accessory
| ShutdownImminent { budget } // вход в shutdown → MCU в wait-for-completion (таймаут ≥ budget)
| SafeToCut // после PONR → MCU снимает питание немедленно
McuToSoc = Ack { seq }
| Acc { on } // дебаунснутый ACC (источник зажигания; в VM кормит FSM AccOn/AccOff)
| Voltage { mv } // напряжение бортсети (под under-voltage backstop)
| CutWarning // бюджет почти истёк (диагностика)
```
### 6.2 Кодек (`codec.rs`)
- **Кадр:** `[SYNC=0xA5][LEN u8][SEQ u8][TYPE u8][PAYLOAD…][CRC16-CCITT]`, CRC по `LEN..=PAYLOAD`.
- **Replay/dup guard:** приёмник держит `last_seq` на направление; кадр с `seq ≤ last_seq` (в окне) — **drop**.
- **Desync/мусор:** битый CRC или нет SYNC → **resync** (скан до следующего `SYNC`), кадр отброшен.
- Юнит-тесты: round-trip всех типов; corruption (флип бита → drop); replay (повтор seq → drop); desync
(мусор перед SYNC → восстановление на следующем валидном кадре).
### 6.3 SoC-side `CoprocessorClient`
- В `running`/`accessory`: `Heartbeat{seq++}` каждые `HEARTBEAT_SECS` (монотоника); ждёт `Acc`/`Voltage` от MCU
→ кормит FSM (`AccOn`/`AccOff`/`UnderVoltage`).
- На `ShutdownImminent` (FSM вошёл в shutdown): шлёт `SocToMcu::ShutdownImminent{budget}` → переходит в
**wait-for-completion** (heartbeat останавливается, ждёт завершения секвенсинга; таймаут ≥ shutdown-бюджет, B §6).
- После PONR (commit): `SafeToCut` → MCU режет немедленно.
- **Не** шлёт power-команд с эффектом cut-на-ходу (red-line §3).
### 6.4 Fail-safe-таймер (B09) — модель в `MockCoprocessor`
- **Hang-детект:** нет `Heartbeat` дольше `FAILSAFE_MISS × HEARTBEAT_SECS` в `running` → SoC завис → cut.
- **Budget-таймер:** после `ShutdownImminent` без `SafeToCut` за `HOLDUP_BUDGET_SECS` → cut (детерминированно).
- **Cut (в VM):** мок-MCU зовёт `apply_event(Event::FailsafeCut)` → FSM → `off` (необратимо, MCU-авторитет) +
лог «MCU cut». В E2E дополнительно реюзаем v0.3 power-cut (SIGKILL до fsync) для durability-доказательства
(`fsck` clean, durable-value цел). Это **VM-модель**: реальный зависший SoC теряет питание извне, в модели
cut = событие не-реально-зависшего процесса (симметрично v0.3 «abort/PONR = stop+umount+remount»).
- Значения (`HEARTBEAT_SECS=1`, `FAILSAFE_MISS=3`, `HOLDUP_BUDGET_SECS` ~ grace+запас) — **placeholder**
(реальный hold-up sizing — hardware §3, RK3588).
---
## 7. D-Bus `ru.shturman.Power` — v0.4 расширяет
- **+ Property `ThermalState: s`** ∈ `{normal, warn, throttle, critical}` (+ `PropertiesChanged`).
- **+ Сигнал `ThermalChanged(s state, i celsius)`** — для Shell (рендер «перегрев» — **v0.5**).
- `ShutdownImminent(u seconds, s reason)` — `reason=thermal` уже объявлен (ipc §3); v0.4 его **реально эмитит**
на thermal-trip.
- **dev-mock `ru.shturman.dev.PowerMock1` (feature `dev-mocks`)** дополняется:
- `SetTemp(i celsius)` → кормит `MockTempSource` → монитор → политика.
- `HangSoc()` → останавливает heartbeat → провоцирует B09-таймер.
- `McuLinkLoss()` → тишина линка: **SoC-сторона деградирует** (лог/маркер degraded, **не** self-cut — red-line §3).
MCU-сторонняя политика cut-vs-hold при тишине — **B §12-open → HW**.
- Прод-гейт `--no-default-features` — не регистрируются (как v0.3 §3).
---
## 8. Watchdog / монотоника (реюз v0.3)
- Все новые таймеры (poll, heartbeat, wait-for-completion, fail-safe, hold-up) — на **`CLOCK_MONOTONIC`**
(`shturman_common::monotonic_secs`), НЕ wall-clock (B §8).
- MCU-watchdog/линк-fail-safe — **логика клиента + модель** (реальный независимый чип/арминг — HW). systemd
`RuntimeWatchdogSec`/`RebootWatchdogSec` — уже из v0.3 (новых юнитов не требуется; thermal/coprocessor живут
внутри `shturman-power.service`).
---
## 9. Dev-харнесс и план тестирования
### 9.1 Unit (чистые модули)
- **`thermal.rs`:** банды Normal/Warn/Throttle/Critical; **гистерезис** (нет осцилляции на границе ±HYST);
`ThermalTrip` на входе в Critical, `ThermalCleared` на выходе.
- **`codec.rs`:** round-trip всех типов; corruption→drop; replay(seq)→drop; desync→resync.
- **`coprocessor.rs`:** клиент heartbeat→wait-for-completion→safe-to-cut; B09-таймер (hang→cut, budget→cut);
MCU игнорит небезопасные SoC-команды (red-line).
- **`fsm.rs`:** `ThermalCleared` abort только из `ShuttingDown{Abortable, reason: Thermal}` (из `AccOff`-shutdown —
no-op); committed — no-op. `FailsafeCut` → `off` из любого не-`off` (необратимо).
### 9.2 Integration (session-шина, `#[ignore]`, `just test-integration`)
- `SetTemp ≥ critical` → наблюдаем `ShutdownImminent(thermal)` + `ThermalChanged(critical)`; state `shutting_down`.
- `SetTemp` recovery до grace → `ShutdownAborted` (через `ThermalCleared`) + `running`.
- `HangSoc` → наблюдаем fail-safe-cut (state → off / forced-off).
### 9.3 E2E (Lima, гибрид — расширение `run.sh`, после блока power-safe v0.3)
- **thermal-trip:** `SetTemp ≥ critical` → `ShutdownImminent(thermal)` → graceful (реюз v0.3: stop→umount(PONR)→
remount) → `/data` консистентен; затем `SetTemp` норма → `running`.
- **MCU fail-safe:** `HangSoc` (heartbeat пропал) → мок-MCU режет питание (наблюдаем forced-off /
SIGKILL-эквивалент) → `fsck` clean, durable-value цел (как v0.3 power-cut).
- **throttling/гистерезис:** `SetTemp` в warn/throttle → `ThermalState` меняется, throttle записан, **без**
shutdown; спад чуть выше нижнего порога — уровень держится (нет осцилляции).
- **Регресс v0.1/v0.2/v0.3 зелёный**; machine-id стабилен; `E2E OK ✅`.
### 9.4 Критерии приёмки (роадмапа + спека)
- [ ] thermal-trip → graceful (`ShutdownImminent(thermal)`→commit→`/data` консистентен); гистерезис — нет осцилляции.
- [ ] MCU fail-safe-таймер: SoC-hang/бюджет → **детерминированный cut** (модель); `/data` консистентен.
- [ ] Throttling-политика по бандам применена (запись в VM; числа — RK3588).
- [ ] Кодек: replay/desync/corruption отбиты (unit).
- [ ] `ThermalState`/`ThermalChanged` на шине; `Uptime`/таймеры монотонны.
- [ ] Регресс v0.1–v0.3 зелёный; `just ci` зелёный; prod-build-gate (нет `PowerMock1`/`SetTemp`); красные линии целы
(MCU/Power — только питание, нет CAN/actuator).
---
## 10. Двунаправленные швы (синхронизировать при реализации)
- **`domain B`:** §4 (thermal-trip-путь реализован), §5 (MCU shutdown-протокол + кодек + клиент — софт/модель;
физический MCU/fail-safe-чип/supercap → HW-подфаза), §6 (wait-for-completion реализован), §1a (тепловые пороги —
placeholder, тюнинг RK3588). Пометить **A12/B08/B09/B10** реализованными (VM-модель).
- **`hardware §3`/`§1a`:** **B08/B09** физический выбор (MCU vs supercap-only) остаётся **🟡 → HW-bring-up-подфаза**;
тепловой конверт/класс/охлаждение — 🟡 (числа на таргете).
- **`ipc.md §3`:** Power + `ThermalState`/`ThermalChanged`; `ShutdownImminent(thermal)` реально эмитится.
- **`capability-catalog`:** A12 ✅ (политика+абстракция), B10 ✅ (триггер+гистерезис; UX→v0.5), B08/B09 — софт/модель
реализованы, физический выбор 🟡 → HW.
- **`CLAUDE.md`:** статус v0.4 (после реализации) → следующее v0.5 shell.
---
## 11. Дальше по ритму
`v0.4` (после утверждения спеки) → **План 8** (`docs/specs/plans/08-v0.4-mcu-thermal.md`, writing-plans) → TDD →
verify в Lima → коммиты `feat/v0.4-mcu-thermal`. Затем **v0.5 — полный shell** (живой weston-shell; замкнёт
thermal-UX-рендер). Физический **HW-bring-up** (MCU/supercap выбор, реальный UART/cpufreq/B09-чип, тепловой
вердикт) — отдельной подфазой при появлении RK3588-платы.