三层实现改动清单
一、内核层(断电能力 + 死机兜底检测)
| 文件 | 改动 |
|---|---|
| sysdrv/source/kernel/drivers/power/reset/jw_poweroff.c 新增 | ① 注册 pm_power_off:regulator_force_disable (power_hold) → 绕过 always‑on 拉低 GPIO0_A2 → 真正断电;② 注册 input handler 监听 KEY_POWER,持续按下 12s → kernel_power_off () 兜底(用户态死掉时仍生效) |
| .../drivers/power/reset/Makefile | 加 obj‑$(CONFIG_JW_POWEROFF) += jw_poweroff.o |
| .../drivers/power/reset/Kconfig | 加 CONFIG_JW_POWEROFF(默认 y,depends on POWER_RESET && INPUT) |
| .../arch/arm/configs/rv1106‑evb.config | 加 CONFIG_JW_POWEROFF=y |
| .../boot/dts/jw_rv1106‑adc‑btn.dtsi | 删除 restart‑poweroff 节点(否则它抢占 pm_power_off,断电变重启) |
| .../boot/dts/jw_borad_v1.dts | 加 poweroff‑ctrl {compatible = "jw,poweroff"; power‑supply = <&power_hold>; } |
gpio‑keys 的 bell‑key (KEY_POWER/wakeup) 保留不动 —— 用户态 daemon 和 kernel handler 都靠它的输入事件,AOV 唤醒功能不破坏。&wdt 已使能无需改。
二、用户态守护进程(优雅关机)
| 文件 | 改动 |
|---|---|
| project/app/powerkeyd/powerkeyd.c 新增 | 扫 /proc/bus/input/devices 找 KEY_POWER 设备,select 监听;长按 10s → 执行 poweroff(busybox 优雅关机,sync FS) |
| project/app/powerkeyd/Makefile 新增 | 交叉编译后拷到 overlay/jw/usr/bin/powerkeyd(进 rootfs /usr/bin) |
| project/cfg/BoardConfig_IPC/overlay/jw/usr/bin/powerkeyd | 编译产物 |
| .../overlay/jw/etc/init.d/S97powerkeyd 启用 / 改写 | 启动 powerkeyd(现在整段注释,改为可用脚本) |
| .../overlay/jw/etc/init.d/S98watchdog 新增 | watchdog -T 15 /dev/watchdog & 喂狗(busybox CONFIG_WATCHDOG=y 已具备,CONFIG_DW_WATCHDOG=y 内核已具备) |
三、U‑Boot 兜底层(死机复位后拦截)
| 文件 | 改动 |
|---|---|
| sysdrv/source/ukernel‑board/.../evb_rv1106.c | rk_board_late_init () 电池路径:power_gpio_init () 后、power_hold_on () 前,若 power_key_pressed () 持续 10s → 跳过 power_hold_on ()(A2 保持低:直接掉电) |
配合链路:真死机 → WDT 15s 复位 → 用户仍按着键 → U‑Boot 检测长按 → 断电。
时序协调
- 正常路径:10s 用户态 poweroff(sync FS)→ 内核 pm_power_off → A2 低 → 断电
- 兜底路径:用户态死 → 12s 内核 handler → kernel_power_off → 断电(不 sync,但状态本就不可靠)
记得改
/root/sdk/rv1106/sysdrv/source/kernel/drivers/power/reset/Kconfig
/root/sdk/rv1106/sysdrv/source/kernel/drivers/power/reset/Makefile
config JW_POWEROFF
bool "JW board power-off control"
depends on OF
help
JW battery board: provide a real power-off by force disabling the
power_hold regulator (GPIO0_A2) so the power latch is released, and
watch KEY_POWER input events to force a power-off when the power
button is held for ~15s (fallback when userspace is hung).
obj-$(CONFIG_JW_POWEROFF) += jw_poweroff.o
jw_poweroff.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* JW board power-off control
*
* Provides real power-off for the battery powered JW board based on
* RV1106. Power is latched by the power_hold (GPIO0_A2) line: lowering it
* cuts the main power supply, so "poweroff" really switches the board off
* instead of just rebooting (the default restart-poweroff behaviour). The
* kernel_power_off()/poweroff path ends up in ->pm_power_off, which force
* disables the power_hold regulator (it is regulator-always-on, so a plain
* disable is not enough).
*
* The driver also monitors KEY_POWER input events: if the power button is
* kept pressed for ~12s it forces kernel_power_off() itself. This is a
* fallback for the case where the userspace powerkeyd daemon is dead or
* hung (userspace freeze), so a long press on the power key always manages
* to shut the board down.
*
* Copyright (C) 2026 JW
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/regulator/consumer.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#define JW_POWERKEY_HOLD_MS 15000
struct jw_power {
struct platform_device *pdev;
struct regulator *power_hold;
struct input_handler handler;
struct input_handle handle;
struct delayed_work hold_work;
bool key_pressed;
};
static struct jw_power *g_jw;
static void jw_cut_power(struct jw_power *jw)
{
int ret;
pr_emerg("jw_poweroff: cutting power (power_hold OFF)\n");
ret = regulator_force_disable(jw->power_hold);
if (ret)
pr_emerg("jw_poweroff: regulator_force_disable failed (%d)\n",
ret);
}
/* pm_power_off: called at the end of any kernel_power_off()/poweroff. */
static void jw_do_power_off(void)
{
struct jw_power *jw = g_jw;
if (!jw)
return;
jw_cut_power(jw);
for (;;)
cpu_relax();
}
/* Emergency fallback when userspace (powerkeyd) is unresponsive. */
static void jw_hold_work(struct work_struct *work)
{
pr_emerg("jw_poweroff: power key held %dms, forcing power off\n",
JW_POWERKEY_HOLD_MS);
kernel_power_off();
}
static void jw_powerkey_handle(struct input_handle *handle,
unsigned int type, unsigned int code,
int value)
{
struct jw_power *jw = handle->private;
if (type != EV_KEY || code != KEY_POWER)
return;
if (value == 1) {
if (jw->key_pressed)
return;
jw->key_pressed = true;
pr_info("jw_poweroff: power key pressed, hold %dms to power off\n",
JW_POWERKEY_HOLD_MS);
queue_delayed_work(system_power_efficient_wq,
&jw->hold_work,
msecs_to_jiffies(JW_POWERKEY_HOLD_MS));
} else if (value == 0) {
if (!jw->key_pressed)
return;
jw->key_pressed = false;
cancel_delayed_work(&jw->hold_work);
pr_info("jw_poweroff: power key released, hold cancelled\n");
}
}
static int jw_powerkey_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
struct jw_power *jw = handler->private;
int ret;
jw->handle.private = jw;
jw->handle.dev = dev;
jw->handle.handler = handler;
jw->handle.name = "jw-powerkey";
ret = input_register_handle(&jw->handle);
if (ret)
return ret;
return input_open_device(&jw->handle);
}
static void jw_powerkey_disconnect(struct input_handle *handle)
{
struct jw_power *jw = handle->private;
cancel_delayed_work_sync(&jw->hold_work);
input_close_device(handle);
input_unregister_handle(handle);
}
static const struct input_device_id jw_powerkey_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_KEYBIT,
.evbit = { BIT_MASK(EV_KEY) },
.keybit = { BIT_MASK(KEY_POWER) },
},
{ },
};
MODULE_DEVICE_TABLE(input, jw_powerkey_ids);
static int jw_poweroff_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct jw_power *jw;
int ret;
jw = devm_kzalloc(dev, sizeof(*jw), GFP_KERNEL);
if (!jw)
return -ENOMEM;
jw->pdev = pdev;
jw->power_hold = devm_regulator_get_optional(dev, "power");
if (IS_ERR(jw->power_hold)) {
dev_err(dev, "failed to get power_hold regulator (%ld)\n",
PTR_ERR(jw->power_hold));
return PTR_ERR(jw->power_hold);
}
INIT_DELAYED_WORK(&jw->hold_work, jw_hold_work);
jw->handler.private = jw;
jw->handler.id_table = jw_powerkey_ids;
jw->handler.event = jw_powerkey_handle;
jw->handler.connect = jw_powerkey_connect;
jw->handler.disconnect = jw_powerkey_disconnect;
jw->handler.name = "jw-powerkey";
ret = input_register_handler(&jw->handler);
if (ret) {
dev_err(dev, "failed to register input handler (%d)\n", ret);
return ret;
}
platform_set_drvdata(pdev, jw);
g_jw = jw;
pm_power_off = jw_do_power_off;
dev_info(dev, "JW poweroff: real power-off + %dms power-key fallback ready\n",
JW_POWERKEY_HOLD_MS);
return 0;
}
static int jw_poweroff_remove(struct platform_device *pdev)
{
struct jw_power *jw = platform_get_drvdata(pdev);
if (pm_power_off == jw_do_power_off)
pm_power_off = NULL;
if (g_jw == jw)
g_jw = NULL;
input_unregister_handler(&jw->handler);
return 0;
}
static const struct of_device_id jw_poweroff_of_match[] = {
{ .compatible = "jw,poweroff" },
{ },
};
MODULE_DEVICE_TABLE(of, jw_poweroff_of_match);
static struct platform_driver jw_poweroff_driver = {
.probe = jw_poweroff_probe,
.remove = jw_poweroff_remove,
.driver = {
.name = "jw-poweroff",
.of_match_table = jw_poweroff_of_match,
},
};
module_platform_driver(jw_poweroff_driver);
MODULE_AUTHOR("JW");
MODULE_DESCRIPTION("JW board power-off control and long-press power key fallback");
MODULE_LICENSE("GPL v2");
设备树
/ {
model = "JW Board V1";
compatible = "jw,board-v1", "rockchip,rv1106";
/* POWER_HOLD - 系统电源保持引脚,必须拉高以维持供电 */
power_hold: power-hold-regulator {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&power_hold_pins>;
regulator-name = "power_hold";
regulator-always-on;
regulator-boot-on;
enable-active-high;
gpio = <&gpio0 RK_PA2 GPIO_ACTIVE_HIGH>;
};
/* 长按电源键关机 + 真正断电 (拉低 power_hold)
* 依赖 /drivers/power/reset/jw_poweroff.c */
poweroff-ctrl {
compatible = "jw,poweroff";
power-supply = <&power_hold>;
};
};
用户态脚本
/rv1106/project/app/powerkeyd/powerkeyd.c
/*
* powerkeyd - JW board power key long-press power-off daemon
*
* Watches the gpio-keys input device that reports KEY_POWER (GPIO0_A3).
* If the power button is held continuously for HOLD_MS it triggers a
* graceful poweroff: busybox "poweroff" syncs the filesystems and asks
* init to shut the system down, which ends in the kernel pm_power_off
* handler (drivers/power/reset/jw_poweroff.c) that drops the power_hold
* (GPIO0_A2) line and really cuts the board power.
*
* Copyright (C) 2026 JW
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <dirent.h>
#include <signal.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/input.h>
#define HOLD_MS 10000
#define POLL_TIMEOUT_MS 200
#define PID_FILE "/var/run/powerkeyd.pid"
static volatile sig_atomic_t g_stop;
static void on_signal(int sig)
{
(void)sig;
g_stop = 1;
}
static void write_pid(void)
{
FILE *f;
f = fopen(PID_FILE, "w");
if (f) {
fprintf(f, "%d\n", getpid());
fclose(f);
}
}
static long long now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int has_key_power(int fd)
{
unsigned char bits[KEY_MAX / 8 + 1];
int i, ret;
memset(bits, 0, sizeof(bits));
ret = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bits)), bits);
if (ret < 0)
return 0;
for (i = 0; i < KEY_MAX; i++) {
if (i == KEY_POWER && (bits[i >> 3] & (1 << (i & 7))))
return 1;
}
return 0;
}
static int open_powerkey_device(void)
{
DIR *dir;
struct dirent *ent;
char path[64];
int fd;
dir = opendir("/dev/input");
if (!dir)
return -1;
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, "event", 5) != 0)
continue;
snprintf(path, sizeof(path), "/dev/input/%.*s",
(int)(sizeof(path) - sizeof("/dev/input/") - 1),
ent->d_name);
fd = open(path, O_RDONLY | O_NONBLOCK);
if (fd < 0)
continue;
if (has_key_power(fd)) {
closedir(dir);
printf("powerkeyd: using %s for KEY_POWER\n", path);
return fd;
}
close(fd);
}
closedir(dir);
return -1;
}
static void trigger_poweroff(void)
{
int ret;
sync();
printf("powerkeyd: power key held %dms, shutting down\n", HOLD_MS);
/* Graceful path: let busybox init run shutdown scripts. */
ret = system("poweroff >/dev/null 2>&1");
if (ret != 0)
ret = system("poweroff -f >/dev/null 2>&1");
if (ret != 0)
execl("/sbin/poweroff", "poweroff", "-f", (char *)NULL);
/* Should never get here; let the kernel handler take over. */
while (!g_stop)
sleep(1000);
}
int main(int argc, char *argv[])
{
struct pollfd p;
struct input_event ev;
int fd = -1;
int i, foreground = 0;
long long press_ms = 0;
int pressed = 0;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--foreground") == 0 ||
strcmp(argv[i], "-F") == 0)
foreground = 1;
}
if (!foreground) {
fflush(NULL);
i = fork();
if (i < 0)
exit(1);
if (i > 0)
_exit(0);
setsid();
i = fork();
if (i < 0)
exit(1);
if (i > 0)
_exit(0);
chdir("/");
umask(0);
}
write_pid();
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGHUP, SIG_IGN);
while (!g_stop) {
if (fd < 0) {
fd = open_powerkey_device();
if (fd < 0) {
printf("powerkeyd: no KEY_POWER device yet, "
"retrying...\n");
sleep(2);
continue;
}
p.fd = fd;
p.events = POLLIN;
p.revents = 0;
}
if (pressed && now_ms() - press_ms >= HOLD_MS) {
trigger_poweroff();
break;
}
p.revents = 0;
i = poll(&p, 1, POLL_TIMEOUT_MS);
if (i < 0) {
if (errno == EINTR)
continue;
perror("powerkeyd: poll");
close(fd);
fd = -1;
continue;
}
if (i == 0 || !(p.revents & POLLIN))
continue;
while (read(fd, &ev, sizeof(ev)) == (ssize_t)sizeof(ev)) {
if (ev.type != EV_KEY || ev.code != KEY_POWER)
continue;
if (ev.value == 1) {
if (!pressed)
printf("powerkeyd: power key down, "
"hold %dms to power off\n",
HOLD_MS);
pressed = 1;
press_ms = now_ms();
} else if (ev.value == 0) {
if (pressed)
printf("powerkeyd: power key up\n");
pressed = 0;
}
}
}
if (fd >= 0)
close(fd);
remove(PID_FILE);
return 0;
}
/rv1106/project/app/powerkeyd/Makefile
ifeq ($(APP_PARAM), )
APP_PARAM:=../Makefile.param
include $(APP_PARAM)
endif
export LC_ALL=C
SHELL:=/bin/bash
CC := $(RK_APP_CROSS)-gcc
PKG_NAME := powerkeyd
# 二进制直接放进 overlay,随 rootfs 打包到 /usr/bin
OVERLAY_BIN := $(RK_APP_TOP_DIR)/../cfg/BoardConfig_IPC/overlay/jw/usr/bin
CFLAGS := -O2 -Wall -Wextra
all: $(PKG_NAME)
$(PKG_NAME): powerkeyd.c
$(CC) $(CFLAGS) -o $@ $^
@mkdir -p $(OVERLAY_BIN)
@install -m 0755 $@ $(OVERLAY_BIN)/$@
@echo "powerkeyd installed to $(OVERLAY_BIN)/$(PKG_NAME)"
clean:
rm -f $(PKG_NAME)
.PHONY: all clean