- 0.1.5/0.1.6:刷机模式(flash_mode,最多 2 路,60 分钟可加钟)——窗口内顶高按 4450 mV 绝对值判、不断 VBUS - 0.1.7/0.1.8:反灌断 VBUS 阈值放宽到 -300 mA(adb reboot bootloader 过渡实测 -208 mA) - 0.1.9:WiFi 最多存 4 组,连不上轮换;NVS v1 自动迁移并双写(OTA 回滚仍能上网) - 0.1.10:热插拔进来的刀片重新初始化 PCA9555 + INA226(否则 VBUS 常开、电流恒读 0) - 0.1.11:i2cbus 嵌套检测改看本任务持锁(原全局标志让 key 随机回 busy、vbus 随机回 io) - 0.1.12:硬重启先断 VBUS,开机校验通过 60 s 后再接回(否则假重启 / 误判没开机 / 进关机充电) - 0.1.13:这一路被安全判据断电时一并退出刷机模式(事件 21 arg 254) 26 ctest 全绿;新增用例均先在旧代码上确认变红。板 B 已 OTA 到 0.1.13-c5 并实测。 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
594 行
20 KiB
C
594 行
20 KiB
C
/* 三个执行器状态机 + 互斥。契约、时间常量的出处、三条规矩都在 t_actuator.h。 */
|
||
#include "t_actuator.h"
|
||
|
||
#include <assert.h>
|
||
#include <stdarg.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "hal_time.h"
|
||
#include "rail_cfg.h"
|
||
#include "slot.h"
|
||
|
||
typedef struct {
|
||
act_state_t state;
|
||
act_seq_t seq;
|
||
uint32_t t0_ms; /* 进入当前状态的时刻(绝对,回绕安全) */
|
||
uint32_t keys; /* KEY 序列:逻辑键位(ACT_KEYS_*) */
|
||
uint32_t hold_ms;
|
||
uint8_t release_mask; /* 收尾时要在 Port0 上清掉的位。0 = 这个序列没按下任何东西 */
|
||
act_result_t result;
|
||
uint32_t done;
|
||
uint32_t cleanup_retries;
|
||
uint8_t presses; /* HR:这一次硬重启已经按了几次电源键 */
|
||
uint8_t good_n; /* HR_VERIFY:连续几个样本电流达标 */
|
||
bool ua_seen; /* HR_VERIFY:这一轮窗口里读到过电流没有 */
|
||
int32_t last_ua; /* HR_VERIFY:最近一个读到的样本 */
|
||
bool vbus_back; /* HR:开始时断了 VBUS,开机校验通过后要接回(0.1.11) */
|
||
bool vbus_pending; /* 已开机,等到 vbus_at 再接回 VBUS(ACT_HR_VBUS_BACK_MS) */
|
||
uint32_t vbus_at;
|
||
} act_rail_t;
|
||
|
||
static act_rail_t s_rail[PHONERAIL_RAIL_COUNT];
|
||
static act_pg_fn s_pg;
|
||
static void *s_pg_ctx;
|
||
static act_ua_fn s_ua;
|
||
static act_boot_fn s_boot_done;
|
||
static void *s_boot_ctx;
|
||
static bool s_inited;
|
||
|
||
/* ---- 小工具 -------------------------------------------------------------- */
|
||
|
||
static bool rail_valid(int rail) { return rail >= 1 && rail <= PHONERAIL_RAIL_COUNT; }
|
||
|
||
static act_rail_t *at(int rail)
|
||
{
|
||
assert(rail_valid(rail));
|
||
return &s_rail[rail - 1];
|
||
}
|
||
|
||
/* 逻辑键位 → PCA9555 Port0 位号。查 `pca9555_key_bit()`,**不算**。
|
||
⚠️ R2 里位号与路号无关(五片刀片同构),所以这个函数不收 rail。 */
|
||
static uint8_t key_out_mask(uint32_t keys)
|
||
{
|
||
uint8_t m = 0;
|
||
for (int k = 0; k < PCA9555_KEY_COUNT; k++) {
|
||
if ((keys & ((uint32_t)1u << k)) != 0) {
|
||
m |= (uint8_t)(1u << pca9555_key_bit((pca9555_key_t)k));
|
||
}
|
||
}
|
||
return m;
|
||
}
|
||
|
||
/* 动这一路 Port0 的**唯一**入口:`slot_out0_write()` 自己开一次 `i2cbus_xact`
|
||
(拿锁 → 选通道 → 读改写一个字节 → 放锁)。
|
||
一次写就是一个字节 ⇒ 组合键的两位天然落在同一次写里(§10.6)。
|
||
⚠️ 这一步会拿 I²C 锁,所以**不许**在持锁的上下文里调它(R2 的新前提,见头文件)。 */
|
||
static hal_err_t out_write(int rail, uint8_t mask, uint8_t value)
|
||
{
|
||
return slot_out0_write(rail, mask, value);
|
||
}
|
||
|
||
static bool pg_now(int rail, bool *pg)
|
||
{
|
||
if (s_pg == NULL) {
|
||
return false; /* 「读不到」,不是「没好」 */
|
||
}
|
||
return s_pg(s_pg_ctx, rail, pg);
|
||
}
|
||
|
||
static bool ua_now(int rail, int32_t *ua)
|
||
{
|
||
if (s_ua == NULL) {
|
||
return false;
|
||
}
|
||
return s_ua(s_boot_ctx, rail, ua);
|
||
}
|
||
|
||
static void enter(act_rail_t *r, act_state_t st, uint32_t now)
|
||
{
|
||
r->state = st;
|
||
r->t0_ms = now;
|
||
}
|
||
|
||
/* HR:按下电源键开机(HR_PG 第一次、HR_VERIFY 重按走同一个口)。
|
||
和 KEY 序列一样:先记 release_mask 再看写没写成 —— 写失败时器件里那个字节
|
||
是未知的,照样走 CLEANUP 把它显式清一遍。返回 false = 写失败,已 finish(IO)。 */
|
||
static void finish(act_rail_t *r, act_result_t res, uint32_t now);
|
||
|
||
static bool hr_press(act_rail_t *r, int rail, uint32_t now)
|
||
{
|
||
const uint8_t mask = key_out_mask(ACT_KEYS_PWR);
|
||
r->release_mask = mask;
|
||
r->hold_ms = ACT_HR_BOOT_KEY_MS;
|
||
r->presses++;
|
||
if (out_write(rail, mask, mask) != HAL_OK) {
|
||
finish(r, ACT_RES_IO, now); /* 电是开着的,只是没按上键 */
|
||
return false;
|
||
}
|
||
enter(r, ACT_ST_HR_BOOT, now);
|
||
return true;
|
||
}
|
||
|
||
static void boot_report(act_rail_t *r, int rail, bool booted)
|
||
{
|
||
if (s_boot_done != NULL) {
|
||
s_boot_done(s_boot_ctx, rail, booted, r->presses, r->last_ua);
|
||
}
|
||
}
|
||
|
||
/* 序列结束(成功、超时、出错、被打断走的是同一条路)。
|
||
按下去过东西就必须经 CLEANUP,那是「键一定会松开」的唯一保证。 */
|
||
static void finish(act_rail_t *r, act_result_t res, uint32_t now)
|
||
{
|
||
r->vbus_back = false; /* 只有 hr_ok() 会接回 VBUS;别的结局一律让它断着 */
|
||
r->result = res;
|
||
r->done++;
|
||
if (r->release_mask != 0) {
|
||
enter(r, ACT_ST_CLEANUP, now);
|
||
} else {
|
||
r->seq = ACT_SEQ_NONE;
|
||
enter(r, ACT_ST_IDLE, now);
|
||
}
|
||
}
|
||
|
||
/* HR 成功收尾:开始时断过 VBUS 的,排一个 ACT_HR_VBUS_BACK_MS 之后的「接回」
|
||
(t_actuator_step 里做,那时执行器已空闲)。 */
|
||
static void hr_ok(act_rail_t *r, int rail, uint32_t now)
|
||
{
|
||
(void)rail;
|
||
if (r->vbus_back) {
|
||
r->vbus_pending = true;
|
||
r->vbus_at = now + ACT_HR_VBUS_BACK_MS;
|
||
}
|
||
finish(r, ACT_RES_OK, now);
|
||
}
|
||
|
||
/* 到点了就接回 VBUS。返回下次最晚多久再来。
|
||
这一路被关了电(安全判据 / 拔刀片)就作废;写失败下一拍再试(影子如实留在 off)。 */
|
||
static uint32_t vbus_back_step(act_rail_t *r, int rail, uint32_t now)
|
||
{
|
||
if (!r->vbus_pending || r->state != ACT_ST_IDLE) {
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
const int32_t left = (int32_t)(r->vbus_at - now);
|
||
if (left > 0) {
|
||
return (uint32_t)left;
|
||
}
|
||
if (!rail_efuse_is_on(rail)) {
|
||
r->vbus_pending = false;
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
if (slot_vbus_enable(rail, true) == HAL_OK) {
|
||
r->vbus_pending = false;
|
||
}
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
|
||
/* ---- 状态机本体:一个状态一段,返回「还要多久再来」 ---------------------- */
|
||
|
||
static uint32_t state_step(act_rail_t *r, int rail, uint32_t now)
|
||
{
|
||
const uint32_t el = now - r->t0_ms; /* 无符号减法自然回绕 */
|
||
|
||
switch (r->state) {
|
||
case ACT_ST_IDLE:
|
||
return ACT_IDLE_WAIT_MS;
|
||
|
||
case ACT_ST_HR_OFF:
|
||
if (el < ACT_HR_OFF_MS) {
|
||
return ACT_HR_OFF_MS - el;
|
||
}
|
||
/* 放电这时早就由硬件做完了(τ 22 ms,5τ ≈ 110 ms),固件什么也不用管。 */
|
||
if (rail_efuse_enable(rail, true) != HAL_OK) {
|
||
/* 开不回来是安全关键的失败:这一路现在是关着的。记 IO,让上层看见。 */
|
||
finish(r, ACT_RES_IO, now);
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
enter(r, ACT_ST_HR_RAMP, now);
|
||
return ACT_HR_RAMP_MS;
|
||
|
||
case ACT_ST_HR_RAMP:
|
||
/* tON 5.1 ms + 缓启动斜坡 11.5 ms。这段时间里 PG 本来就不该置位,
|
||
所以**根本不看它** —— 提前去看只会看到「还没好」。 */
|
||
if (el < ACT_HR_RAMP_MS) {
|
||
return ACT_HR_RAMP_MS - el;
|
||
}
|
||
enter(r, ACT_ST_HR_PG, now);
|
||
return ACT_HR_PG_POLL_MS;
|
||
|
||
case ACT_ST_HR_PG: {
|
||
bool pg = false;
|
||
if (pg_now(rail, &pg) && pg) {
|
||
/* 电好了 ⇒ 长按电源键开机(ACT_HR_BOOT_KEY_MS 的注释)。 */
|
||
return hr_press(r, rail, now) ? ACT_HR_BOOT_KEY_MS : ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
if (el >= ACT_HR_PG_TIMEOUT_MS) {
|
||
/* ⚠️ 电**继续开着**。见 ACT_RES_NO_PG 的注释:把「看不见」当成
|
||
「坏了」去断电,等于一片 PCA9555 失联就让五台手机一起停机。 */
|
||
finish(r, ACT_RES_NO_PG, now);
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
const uint32_t left = ACT_HR_PG_TIMEOUT_MS - el;
|
||
return left < ACT_HR_PG_POLL_MS ? left : ACT_HR_PG_POLL_MS;
|
||
}
|
||
|
||
case ACT_ST_HR_BOOT:
|
||
if (el < r->hold_ms) {
|
||
return r->hold_ms - el;
|
||
}
|
||
if (s_ua == NULL) {
|
||
hr_ok(r, rail, now); /* 不校验(0.1.1 行为):CLEANUP 松键 */
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
/* 要校验:先在这里松键(松不掉就走 CLEANUP 的「不放弃」,不再校验)。 */
|
||
if (out_write(rail, r->release_mask, 0) != HAL_OK) {
|
||
finish(r, ACT_RES_IO, now);
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
r->release_mask = 0;
|
||
r->good_n = 0;
|
||
r->ua_seen = false;
|
||
enter(r, ACT_ST_HR_VERIFY, now);
|
||
return ACT_HR_VERIFY_DELAY_MS;
|
||
|
||
case ACT_ST_HR_VERIFY: {
|
||
if (el < ACT_HR_VERIFY_DELAY_MS) {
|
||
return ACT_HR_VERIFY_DELAY_MS - el;
|
||
}
|
||
int32_t ua = 0;
|
||
if (ua_now(rail, &ua)) {
|
||
r->ua_seen = true;
|
||
r->last_ua = ua;
|
||
r->good_n = ua >= ACT_HR_BOOT_UA_MIN ? (uint8_t)(r->good_n + 1) : 0;
|
||
if (r->good_n >= ACT_HR_VERIFY_GOOD_N) {
|
||
boot_report(r, rail, true);
|
||
hr_ok(r, rail, now);
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
}
|
||
if (el < ACT_HR_VERIFY_DELAY_MS + ACT_HR_VERIFY_WINDOW_MS) {
|
||
const uint32_t left = ACT_HR_VERIFY_DELAY_MS + ACT_HR_VERIFY_WINDOW_MS - el;
|
||
return left < ACT_HR_VERIFY_POLL_MS ? left : ACT_HR_VERIFY_POLL_MS;
|
||
}
|
||
if (!r->ua_seen) {
|
||
/* 整个窗口都读不到电流:判不了,**不重按**(ACT_HR_BOOT_TRIES 那段注释)。 */
|
||
hr_ok(r, rail, now);
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
if (r->presses < ACT_HR_BOOT_TRIES) {
|
||
return hr_press(r, rail, now) ? ACT_HR_BOOT_KEY_MS : ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
/* ⚠️ 电**继续开着**:关掉也不会让弹片压上,反而丢了人工到场前的现场。 */
|
||
boot_report(r, rail, false);
|
||
finish(r, ACT_RES_NO_BOOT, now);
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
|
||
case ACT_ST_KEY_HOLD:
|
||
if (el < r->hold_ms) {
|
||
return r->hold_ms - el;
|
||
}
|
||
finish(r, ACT_RES_OK, now); /* release_mask 非 0 ⇒ 一定进 CLEANUP 松键 */
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
|
||
case ACT_ST_EDL_SHORT:
|
||
/* SW-6:先短接、再插 USB。这两句的先后就是这条需求的全部内容。 */
|
||
if (el < ACT_EDL_SHORT_MS) {
|
||
return ACT_EDL_SHORT_MS - el;
|
||
}
|
||
if (slot_vbus_enable(rail, true) != HAL_OK) {
|
||
finish(r, ACT_RES_IO, now); /* CLEANUP 会把 EDL_EN 清掉 */
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
enter(r, ACT_ST_EDL_ENUM, now);
|
||
return ACT_EDL_ENUM_MS;
|
||
|
||
case ACT_ST_EDL_ENUM:
|
||
if (el < ACT_EDL_ENUM_MS) {
|
||
return ACT_EDL_ENUM_MS - el;
|
||
}
|
||
/* `EDL_EN=0`(收尾那一步)。**VBUS 留着不动** —— 手机现在正挂在 9008 上。 */
|
||
finish(r, ACT_RES_OK, now);
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
|
||
case ACT_ST_CLEANUP:
|
||
if (out_write(rail, r->release_mask, 0) == HAL_OK) {
|
||
r->release_mask = 0;
|
||
r->seq = ACT_SEQ_NONE;
|
||
enter(r, ACT_ST_IDLE, now);
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
/* ⚠️ **不放弃。** 放弃 = 电源键留在按下状态,手机再也起不来,
|
||
而五路电压电流看上去一切正常。这一路一直算 busy,重试次数报出去。 */
|
||
r->cleanup_retries++;
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
|
||
assert(0 && "unknown state");
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
|
||
/* 换了状态就当场再走一步:CLEANUP 该立刻试一次,不该先睡 5 ms。
|
||
上限 8 是防呆 —— 状态机里任何一个环都会在这里被截住,而不是把任务钉死。 */
|
||
static uint32_t rail_step(int rail, uint32_t now)
|
||
{
|
||
act_rail_t *r = at(rail);
|
||
|
||
for (int guard = 0; guard < 8; guard++) {
|
||
const act_state_t before = r->state;
|
||
const uint32_t wait = state_step(r, rail, now);
|
||
if (r->state == before) {
|
||
return wait;
|
||
}
|
||
}
|
||
return ACT_CLEANUP_RETRY_MS;
|
||
}
|
||
|
||
/* ---- 接口 ---------------------------------------------------------------- */
|
||
|
||
void t_actuator_init(act_pg_fn pg, void *ctx)
|
||
{
|
||
memset(s_rail, 0, sizeof(s_rail));
|
||
s_pg = pg;
|
||
s_pg_ctx = ctx;
|
||
s_ua = NULL;
|
||
s_boot_done = NULL;
|
||
s_boot_ctx = NULL;
|
||
s_inited = true;
|
||
}
|
||
|
||
void t_actuator_set_boot_check(act_ua_fn ua, act_boot_fn done, void *ctx)
|
||
{
|
||
s_ua = ua;
|
||
s_boot_done = done;
|
||
s_boot_ctx = ctx;
|
||
}
|
||
|
||
hal_err_t t_actuator_hard_reset(int rail)
|
||
{
|
||
if (!s_inited) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
if (!rail_valid(rail)) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
act_rail_t *r = at(rail);
|
||
if (r->state != ACT_ST_IDLE) {
|
||
return HAL_ERR_STATE; /* §10.6:不可重入。要抢占先 abort,写成显式的 */
|
||
}
|
||
|
||
/* 防御性松开这一路的三个键:断电重启期间有键被按着,手机会开进 Recovery /
|
||
Fastboot 而不是正常起来。正常情况下这里一定已经是 0(有序列在跑的话
|
||
上面那一句就把请求挡了),所以影子不变 ⇒ 不脏 ⇒ 一个时钟都不会发。
|
||
只有上一次收尾没落地时才真的发一帧。 */
|
||
const uint8_t km = pca9555_key_mask();
|
||
if ((slot_shadow(rail) & km) != 0) {
|
||
(void)out_write(rail, km, 0); /* 失败也照断电:断电永远比不断电安全 */
|
||
}
|
||
|
||
/* 0.1.11(2026-09-28 板 B):VBUS 通着就**先断**,开机校验通过后再接回(hr_ok)。
|
||
不断的三个后果,现场全见过:
|
||
· 手机靠 USB 撑着,根本没断电 —— 硬重启是假的;
|
||
· 手机吃 USB 的电,直供电流 ≈ 0 ⇒ 开机校验判「没开起来」、再按两下电源键;
|
||
· 断电那一刻 USB 还在 ⇒ 被当成「插充电器开机」,进关机充电模式,一拔 USB 就关机。
|
||
断不掉也照样断电(断电永远比不断电安全),只是事后不接回。 */
|
||
r->vbus_back = false;
|
||
r->vbus_pending = false; /* 上一次硬重启排的接回作废 */
|
||
const bool had_vbus = slot_vbus_is_on(rail);
|
||
if (had_vbus && slot_vbus_enable(rail, false) == HAL_OK) {
|
||
r->vbus_back = true;
|
||
}
|
||
|
||
const hal_err_t err = rail_efuse_enable(rail, false);
|
||
if (err != HAL_OK) {
|
||
if (r->vbus_back) {
|
||
(void)slot_vbus_enable(rail, true); /* 序列没开始:恢复原样 */
|
||
r->vbus_back = false;
|
||
}
|
||
return err; /* 序列没开始 */
|
||
}
|
||
r->seq = ACT_SEQ_HARD_RESET;
|
||
r->release_mask = 0; /* 断电这一段没按任何东西;开机那一下在 HR_PG 里才记 */
|
||
r->presses = 0;
|
||
r->last_ua = 0;
|
||
r->result = ACT_RES_NONE;
|
||
enter(r, ACT_ST_HR_OFF, hal_time_now_ms());
|
||
return HAL_OK;
|
||
}
|
||
|
||
hal_err_t t_actuator_key(int rail, uint32_t keys, uint32_t hold_ms)
|
||
{
|
||
if (!s_inited) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
if (!rail_valid(rail) || keys == 0 || (keys & ~(uint32_t)ACT_KEYS_ALL) != 0) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
if (hold_ms == 0 || hold_ms > ACT_KEY_MAX_MS) {
|
||
return HAL_ERR_ARG; /* 拦手误,不是保护 */
|
||
}
|
||
act_rail_t *r = at(rail);
|
||
if (r->state != ACT_ST_IDLE) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
|
||
const uint8_t mask = key_out_mask(keys);
|
||
|
||
/* 组合键的两位在**同一次写**里 ⇒ 输出锁存器一次更新。
|
||
分两次写会先单按一个键(§10.6)。 */
|
||
const hal_err_t err = out_write(rail, mask, mask);
|
||
|
||
r->seq = ACT_SEQ_KEY;
|
||
r->keys = keys;
|
||
r->hold_ms = hold_ms;
|
||
r->result = ACT_RES_NONE;
|
||
r->release_mask = mask;
|
||
|
||
if (err != HAL_OK) {
|
||
/* 写失败时驱动**不更新影子**,但器件里那个字节的状态是未知的
|
||
(事务可能发出去一半)。所以照样进 CLEANUP 把这几位显式清一遍 ——
|
||
「按下去的东西一定要松开」这条规矩不看我们以为发生了什么。 */
|
||
finish(r, ACT_RES_IO, hal_time_now_ms());
|
||
return err;
|
||
}
|
||
|
||
enter(r, ACT_ST_KEY_HOLD, hal_time_now_ms());
|
||
return HAL_OK;
|
||
}
|
||
|
||
hal_err_t t_actuator_edl(int rail)
|
||
{
|
||
if (!s_inited) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
if (!rail_valid(rail)) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
act_rail_t *r = at(rail);
|
||
if (r->state != ACT_ST_IDLE) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
|
||
r->vbus_pending = false; /* EDL 自己管 VBUS */
|
||
const uint8_t mask = (uint8_t)(1u << PCA9555_BIT_EDL_EN);
|
||
const hal_err_t err = out_write(rail, mask, mask);
|
||
|
||
r->seq = ACT_SEQ_EDL;
|
||
r->result = ACT_RES_NONE;
|
||
r->release_mask = mask;
|
||
|
||
if (err != HAL_OK) {
|
||
finish(r, ACT_RES_IO, hal_time_now_ms());
|
||
return err;
|
||
}
|
||
enter(r, ACT_ST_EDL_SHORT, hal_time_now_ms());
|
||
return HAL_OK;
|
||
}
|
||
|
||
void t_actuator_abort(int rail)
|
||
{
|
||
if (!s_inited || !rail_valid(rail)) {
|
||
return;
|
||
}
|
||
act_rail_t *r = at(rail);
|
||
r->vbus_pending = false; /* 被打断的路(多半是安全判据关了电)不再接回 VBUS */
|
||
if (r->state == ACT_ST_IDLE || r->state == ACT_ST_CLEANUP) {
|
||
return; /* CLEANUP 已经在收尾了,别把它的结果覆盖掉 */
|
||
}
|
||
finish(r, ACT_RES_ABORTED, hal_time_now_ms());
|
||
(void)rail_step(rail, hal_time_now_ms()); /* 立刻试一次收尾,别等下一拍 */
|
||
}
|
||
|
||
void t_actuator_cancel_vbus_back(int rail)
|
||
{
|
||
if (s_inited && rail_valid(rail)) {
|
||
at(rail)->vbus_pending = false;
|
||
}
|
||
}
|
||
|
||
bool t_actuator_vbus_back_pending(int rail)
|
||
{
|
||
return s_inited && rail_valid(rail) && at(rail)->vbus_pending;
|
||
}
|
||
|
||
uint32_t t_actuator_step(void)
|
||
{
|
||
if (!s_inited) {
|
||
return ACT_IDLE_WAIT_MS;
|
||
}
|
||
const uint32_t now = hal_time_now_ms();
|
||
uint32_t wait = ACT_IDLE_WAIT_MS;
|
||
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
uint32_t w = rail_step(rail, now);
|
||
const uint32_t v = vbus_back_step(at(rail), rail, now);
|
||
if (v < w) {
|
||
w = v;
|
||
}
|
||
if (w < wait) {
|
||
wait = w;
|
||
}
|
||
}
|
||
return wait;
|
||
}
|
||
|
||
/* ---- 观测 ---------------------------------------------------------------- */
|
||
|
||
bool t_actuator_busy(int rail)
|
||
{
|
||
return rail_valid(rail) && at(rail)->state != ACT_ST_IDLE;
|
||
}
|
||
|
||
act_state_t t_actuator_state(int rail) { return at(rail)->state; }
|
||
act_seq_t t_actuator_seq(int rail) { return at(rail)->seq; }
|
||
act_result_t t_actuator_last_result(int rail) { return at(rail)->result; }
|
||
uint32_t t_actuator_done(int rail) { return at(rail)->done; }
|
||
uint32_t t_actuator_cleanup_retries(int rail) { return at(rail)->cleanup_retries; }
|
||
|
||
const char *t_actuator_state_str(act_state_t st)
|
||
{
|
||
switch (st) {
|
||
case ACT_ST_IDLE: return "idle";
|
||
case ACT_ST_HR_OFF: return "hr-off";
|
||
case ACT_ST_HR_RAMP: return "hr-ramp";
|
||
case ACT_ST_HR_PG: return "hr-pg";
|
||
case ACT_ST_HR_BOOT: return "hr-boot";
|
||
case ACT_ST_HR_VERIFY: return "hr-verify";
|
||
case ACT_ST_KEY_HOLD: return "key-hold";
|
||
case ACT_ST_EDL_SHORT: return "edl-short";
|
||
case ACT_ST_EDL_ENUM: return "edl-enum";
|
||
case ACT_ST_CLEANUP: return "cleanup";
|
||
}
|
||
return "?";
|
||
}
|
||
|
||
const char *t_actuator_result_str(act_result_t r)
|
||
{
|
||
switch (r) {
|
||
case ACT_RES_NONE: return "-";
|
||
case ACT_RES_OK: return "ok";
|
||
case ACT_RES_NO_PG: return "no-pg";
|
||
case ACT_RES_IO: return "io";
|
||
case ACT_RES_ABORTED: return "aborted";
|
||
case ACT_RES_NO_BOOT: return "no-boot";
|
||
}
|
||
return "?";
|
||
}
|
||
|
||
static void app_fmt(char *buf, size_t len, size_t *off, const char *fmt, ...)
|
||
{
|
||
if (*off + 1 >= len) {
|
||
return;
|
||
}
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
const int k = vsnprintf(buf + *off, len - *off, fmt, ap);
|
||
va_end(ap);
|
||
if (k < 0) {
|
||
return;
|
||
}
|
||
*off += (size_t)k;
|
||
if (*off >= len) {
|
||
*off = len - 1; /* 截断了 */
|
||
}
|
||
}
|
||
|
||
int t_actuator_format(char *buf, size_t len)
|
||
{
|
||
assert(buf != NULL && len > 0);
|
||
size_t off = 0;
|
||
uint32_t done = 0;
|
||
uint32_t retries = 0;
|
||
|
||
app_fmt(buf, len, &off, "t_actuator:");
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
const act_rail_t *r = at(rail);
|
||
app_fmt(buf, len, &off, " ch%d=%s", rail, t_actuator_state_str(r->state));
|
||
done += r->done;
|
||
retries += r->cleanup_retries;
|
||
}
|
||
app_fmt(buf, len, &off, " done=%u retry=%u", (unsigned)done, (unsigned)retries);
|
||
return (int)off;
|
||
}
|