RTC 唤醒注入 KEY_POWER — 代码路径分析
背景
rtk-parker-aosp-squash-25q2.02-mirakulo项目
概述
STB 待机时 RTC 定时唤醒,IR 驱动在 Resume 时从寄存器读到上一个按键(POWER)的 NEC 帧残留,因 KEY_E 不在键值表中导致查表失败,旧代码 fallback 注入 KEY_POWER,经 Android → CEC 触发电视开机。
根因是两层问题叠加: ① 键值表缺少 KEY_E 映射;② 旧代码在 KEY_E 查不到时无条件使用寄存器残留值调用 rc_keydown()。
一、待机前:POWER 键的 NEC 帧锁存在寄存器中
用户按遥控器 POWER 让 STB 待机。IR 硬件收到 NEC 帧后存入 ISO 寄存器 0x65c。
正常 ISR 路径(rtk-ir-hw.c:100-139)读出并解码,发送按键。Suspend 时 ISR 已不再触发,但硬件寄存器继续锁存上次收到的数据。
rtk-ir-hw.c:76-86 的 suspend 路径只 drain FIFO,不清零数据寄存器,残留值保留。
二、RTC 唤醒:PM notifier 读取寄存器
RTC 到期 → SoC 唤醒 → PM notifier 回调 rtk_ir_key_event()。
rtk-ir-core.c:108-112:
regmap_read(ir->iso, 0x65c, &val); // 读 RX FIFO → 残留值 0xf50a4987
regmap_read(ir->iso, 0x650, &nf_code); // 噪声检测 → 0(无真实 IR 信号)
pr_err("Wakeup entire android src %d, 0x%x, %d\n", wakeup_event, val, nf_code);
// 输出: Wakeup entire android src 3, 0xf50a4987, 0
dec->scancode(&request, val); // 解码
唤醒事件类型定义在 rtk_pm_pcpu.h:25-31:
enum rtk_wakeup_event {
LAN_EVENT = 0,
IR_EVENT, // 1 — 遥控器按键唤醒
GPIO_EVENT, // 2 — GPIO 唤醒
ALARM_EVENT, // 3 — RTC 定时器唤醒 ← 本次
// ...
};
三、NEC 解码:0xf50a4987 → 0x87490a
rtk-ir-nec.c:11-37:
static int rtk_ir_nec_scancode(struct rtk_ir_scancode_req *request, unsigned int raw)
{
addr = (raw >> 0) & 0xff; // 0x87
addr_inv = (raw >> 8) & 0xff; // 0x49
data = (raw >> 16) & 0xff; // 0x0a
data_inv = (raw >> 24) & 0xff; // 0xf5
// (data_inv ^ data) = 0xff → 不走 NEC32
// (addr_inv ^ addr) = 0xCE ≠ 0xff → 走 NECX
request->scancode = addr << 16 | addr_inv << 8 | data;
= 0x87 << 16 | 0x49 << 8 | 0x0a;
= 0x87490a; // ← 恰是 KEY_POWER 的 scancode
request->protocol = RC_PROTO_NECX;
}
解码出的 0x87490a 就是用户在待机前按下 POWER 键的 scancode。
四、查找 KEY_E 失败 — scancode 未被覆盖
rtk-ir-core.c:114-141 根据唤醒类型决定使用什么 scancode:
if (wakeup_event == IR_EVENT) {
// 在 map 中匹配当前 scancode
} else if (wakeup_event == GPIO_EVENT) {
// 查 KEY_PROG1(Netflix 按钮)
} else if (wakeup_event == ALARM_EVENT) {
// ★ 走这里 — 查找 KEY_E
for (k = 0; k < map->len; k++)
if (KEY_E == (map->scan + k)->keycode) {
request.scancode = (map->scan + k)->scancode; // 用 KEY_E 的 scancode 覆盖
break;
}
// GIEC 的 rc-realtek-dhc 中没有 KEY_E → k >= map->len
// request.scancode 未被覆盖 → 仍是解码出来的 0x87490a
} else {
// 其他事件 — 查 KEY_POWER
}
GIEC 的键值表 rc-realtek-dhc.c 中没有 KEY_E 条目:
static struct rc_map_table realtek_dhc[] = {
// ...
{ 0x87490A, KEY_POWER }, // ← KEY_POWER 在表中
{ 0x8869, KEY_Y },
{ 0x8870, KEY_U },
// ... 没有 KEY_E
};
五、旧代码无条件注入 → 命中 KEY_POWER
修复前的 rtk-ir-core.c(注入阶段):
} else if (wakeup_event == ALARM_EVENT) {
pr_err("[ALARM_RESUME][%s] Check Wi-Fi connection, scan code: 0x%x\n",
__func__, request.scancode);
// 输出: [ALARM_RESUME][rtk_ir_key_event] Check Wi-Fi connection, scan code: 0x87490a
rc_keydown(rc, map->rc_proto, request.scancode, 0); // 无条件注入
// → rc_keydown(NECX, 0x87490a, 0)
}
rc-main.c:848-855 的 rc_keydown():
void rc_keydown(struct rc_dev *dev, enum rc_proto protocol, u64 scancode, u8 toggle)
{
u32 keycode = rc_g_keycode_from_table(dev, scancode); // 查表
ir_do_keydown(dev, protocol, scancode, keycode, toggle);
}
rc_g_keycode_from_table()(rc-main.c:605-625)对 0x87490a 二分查找 → 命中 rc-realtek-dhc 中的 { 0x87490A, KEY_POWER } → 返回 KEY_POWER (116) → Android 收到电源键事件。
六、Android 到 CEC 到电视开机
input_report_key(KEY_POWER, 1)
→ /vendor/usr/keylayout/rtk-ir-hw.kl → key 116 POWER WAKE
→ PhoneWindowManager → 检测系统从待机恢复 → 触发 HDMI-CEC
→ HdmiControlService → <Active Source> + <One Touch Play>
→ 电视收到 CEC → 开机
七、完整链路
待机前用户按 POWER
→ IR 硬件捕获 NEC 帧 → ISO 0x65c = 0xf50a4987
→ STB 进入 Suspend(寄存器不清零)
│
RTC 到期
→ SoC 唤醒
→ PM notifier: rtk_ir_key_event()
→ regmap_read(0x65c) → val = 0xf50a4987(残留值)
→ NECX 解码 → scancode = 0x87490a
→ ALARM_EVENT → 查 map 找 KEY_E → 找不到
→ request.scancode 未被覆盖 → 仍是 0x87490a
→ 旧代码无条件 rc_keydown(0x87490a)
→ rc_g_keycode_from_table() → 命中 KEY_POWER
→ input_report_key(KEY_POWER, 1)
→ Android → CEC One Touch Play → TV ON
八、修复
ps: 最终采用了直接将
switch (wakeup_event) { case ALARM_EVENT: sendKeyEvent = false;ALARM_EVENT的sendKeyEvent置为false,直接不注入按键到上层
8.1 解析层 — KEY_E 找不到时 scancode 置 0
rtk-ir-core.c:127-133
} else if (wakeup_event == ALARM_EVENT) {
for (k = 0; k < map->len; k++)
if (KEY_E == (map->scan + k)->keycode) {
request.scancode = (map->scan + k)->scancode;
break;
}
if (k == map->len)
request.scancode = 0; // KEY_E 找不到 → 显式清零
}
scancode = 0 永远不会命中键值表中的任何条目,确保 rc_keydown() 即使被调用也不会注入有效按键。
8.2 注入层 — KEY_E 找不到时跳过注入
rtk-ir-core.c:152-160
} else if (wakeup_event == ALARM_EVENT) {
if (k < map->len) {
pr_err("[ALARM_RESUME]... scan code: 0x%x\n", __func__, request.scancode);
rc_keydown(rc, map->rc_proto, request.scancode, 0);
} else {
pr_err("KEY_E not in rc_map, skip ALARM inject\n"); // 找不到 → 跳过
}
}
8.3 键值表 — 添加 KEY_E 映射(可选)
rc-realtek-dhc.c:
{ 0x8749FF, KEY_E }, // KEY_E for wifi connected
添加后,RTC 唤醒时 KEY_E 被注入到 Android → GlobalKeyReceiver 收到 BUTTON_8 → 后台 WiFi 重连 → 释放 WakeLock → 系统睡回。
涉及文件
| 文件 | 改动 |
|---|---|
drivers/media/rc/rtk-ir/rtk-ir-core.c |
解析层 scancode 置 0 + 注入层 skip |
drivers/media/rc/rtk-ir/rtk-ir-nec.c |
NEC 解码器(未改,链路中说明) |
drivers/media/rc/keymaps/rc-realtek-dhc.c |
添加 KEY_E 映射(可选) |
drivers/media/rc/rc-main.c |
rc_keydown + 查表逻辑(未改,链路中说明) |
include/soc/realtek/uapi/rtk_pm_pcpu.h |
唤醒事件枚举 |