三层实现改动清单

一、内核层(断电能力 + 死机兜底检测)

文件改动
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.crk_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