/* 三个执行器状态机 + 互斥。契约、时间常量的出处、三条规矩都在 t_actuator.h。 */ #include "t_actuator.h" #include #include #include #include #include "hal_lock.h" #include "hal_time.h" #include "rail_cfg.h" #include "sr595.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; uint32_t release_mask; /* 收尾时要在链上清掉的位。0 = 这个序列没按下任何东西 */ act_result_t result; uint32_t done; uint32_t cleanup_retries; } 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 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]; } /* 逻辑键位 → 链上位号。查 `sr595_key_bit()`,**不算**(sr595.h 的禁令)。 */ static uint32_t key_chain_mask(int rail, uint32_t keys) { uint32_t m = 0; for (int k = 0; k < SR595_KEY_COUNT; k++) { if ((keys & ((uint32_t)1u << k)) != 0) { m |= (uint32_t)1u << sr595_key_bit(rail, (sr595_key_t)k); } } return m; } /* 动 24 位链的**唯一**入口:拿 595 那把锁 → 改影子 + 整帧发下去 → 放锁。 一帧原子(RCLK 之前输出不变)⇒ 组合键的两位天然落在同一帧里。 ⚠️ 这里绝不会去拿 I²C 那把锁,两把锁因此永远不会嵌套(hal_lock.h)。 */ static hal_err_t chain_write(uint32_t mask, uint32_t value) { hal_err_t err = hal_lock_take(HAL_LOCK_SR595, ACT_LOCK_TIMEOUT_MS); if (err != HAL_OK) { return err; /* 影子没动过,下一拍重来 */ } err = sr595_write(mask, value); hal_lock_give(HAL_LOCK_SR595); return err; } static bool pg_now(int rail, bool *pg) { if (s_pg == NULL) { return false; /* 「读不到」,不是「没好」 */ } return s_pg(s_pg_ctx, rail, pg); } static void enter(act_rail_t *r, act_state_t st, uint32_t now) { r->state = st; r->t0_ms = now; } /* 序列结束(成功、超时、出错、被打断走的是同一条路)。 按下去过东西就必须经 CLEANUP,那是「键一定会松开」的唯一保证。 */ static void finish(act_rail_t *r, act_result_t res, uint32_t now) { 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); } } /* ---- 状态机本体:一个状态一段,返回「还要多久再来」 ---------------------- */ 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) { finish(r, ACT_RES_OK, now); return ACT_IDLE_WAIT_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_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 (rail_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 (chain_write(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_inited = true; } 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 uint32_t km = sr595_key_mask(rail); if ((sr595_shadow() & km) != 0) { (void)chain_write(km, 0); /* 失败也照断电:断电永远比不断电安全 */ } const hal_err_t err = rail_efuse_enable(rail, false); if (err != HAL_OK) { return err; /* 序列没开始 */ } r->seq = ACT_SEQ_HARD_RESET; r->release_mask = 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 uint32_t mask = key_chain_mask(rail, keys); /* 组合键的两位在**同一次 `chain_write()`** 里 ⇒ 同一帧 ⇒ 同一个 RCLK 沿。 分两次写会先单按一个键(§10.6)。 */ const hal_err_t err = chain_write(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) { /* 影子可能已经改成「按下」了(拿到锁但整帧没发出去)。直接返回错误 就等于把一个「按下」的脏影子留在那儿,谁下一次 flush 谁就替我们把键 按下去 —— 而没有人会来松开它。所以照样进 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; } const uint32_t mask = (uint32_t)1u << sr595_edl_bit(rail); /* 查表,不算 */ const hal_err_t err = chain_write(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); 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()); /* 立刻试一次收尾,别等下一拍 */ } 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++) { const uint32_t w = rail_step(rail, now); 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_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"; } 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; }