固件 0.1.3-c5:硬重启开机校验 + 重按(0.1.2)、status 上报上游电脑在不在(host)

- 0.1.2:power_cycle 按完电源键后看电流,没开就重按,最多 3 次;新事件 19 BOOT_CHECK
- 0.1.3:UP_VBUS_DET(IO6)启动时配成输入,status 加 "host":true|false|null
- rack:单板页显示「上游:已连电脑 / 未连电脑」
- HANDOFF:损耗两档的来历(Hub 工作 ≈1.1 W)、板 B 第 2 槽 PRSNT2# 卡低

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-28 08:59:12 +08:00
共同撰写人 Claude Opus 5.5
父节点 830b4cdff8
当前提交 ef91daf07d
共修改 13 个文件,包含 834 行新增和 366 行删除
+112 -16
查看文件
@@ -20,12 +20,19 @@ typedef struct {
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:最近一个读到的样本 */
} act_rail_t;
static act_rail_t s_rail[PHONERAIL_RAIL_COUNT];
static act_pg_fn s_pg;
static void *s_pg_ctx;
static bool s_inited;
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;
/* ---- 小工具 -------------------------------------------------------------- */
@@ -67,12 +74,46 @@ static bool pg_now(int rail, bool *pg)
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)
@@ -122,18 +163,8 @@ static uint32_t state_step(act_rail_t *r, int rail, uint32_t now)
case ACT_ST_HR_PG: {
bool pg = false;
if (pg_now(rail, &pg) && pg) {
/* 电好了 ⇒ 长按电源键开机(ACT_HR_BOOT_KEY_MS 的注释)。
和 KEY 序列一样:先记 release_mask 再看写没写成 —— 写失败时器件里那个字节
是未知的,照样走 CLEANUP 把它显式清一遍。 */
const uint8_t mask = key_out_mask(ACT_KEYS_PWR);
r->release_mask = mask;
r->hold_ms = ACT_HR_BOOT_KEY_MS;
if (out_write(rail, mask, mask) != HAL_OK) {
finish(r, ACT_RES_IO, now); /* 电是开着的,只是没按上键 */
return ACT_CLEANUP_RETRY_MS;
}
enter(r, ACT_ST_HR_BOOT, now);
return ACT_HR_BOOT_KEY_MS;
/* 电好了 ⇒ 长按电源键开机(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 的注释:把「看不见」当成
@@ -146,6 +177,57 @@ static uint32_t state_step(act_rail_t *r, int rail, uint32_t now)
}
case ACT_ST_HR_BOOT:
if (el < r->hold_ms) {
return r->hold_ms - el;
}
if (s_ua == NULL) {
finish(r, ACT_RES_OK, 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);
finish(r, ACT_RES_OK, 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 那段注释)。 */
finish(r, ACT_RES_OK, 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;
@@ -213,9 +295,19 @@ 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) {
@@ -244,6 +336,8 @@ hal_err_t t_actuator_hard_reset(int rail)
}
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;
@@ -368,6 +462,7 @@ const char *t_actuator_state_str(act_state_t st)
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";
@@ -384,6 +479,7 @@ const char *t_actuator_result_str(act_result_t r)
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 "?";
}