author: hjjdebug
date: 2026年 05月 27日 星期三 14:11:15 CST
descrip: 手提电脑开盖,关盖的驱动
文章目录
- 1.笔记本的合盖,开盖设备.
- [2. 驱动代码](#2. 驱动代码)
- [3. 去掉原有驱动,加载自己驱动是实验的关键.](#3. 去掉原有驱动,加载自己驱动是实验的关键.)
-
- [3.1. 收到合盖事件不要休眠.](#3.1. 收到合盖事件不要休眠.)
- [3.2. 解除原来的驱动绑定.](#3.2. 解除原来的驱动绑定.)
-
- [3.2.1 先要找到盖子设备](#3.2.1 先要找到盖子设备)
- [3.2.2 由这个设备找到驱动.](#3.2.2 由这个设备找到驱动.)
- [3.2.3 解除这个驱动和设备的binding](#3.2.3 解除这个驱动和设备的binding)
- 4.安装测试我们的驱动.
目的: 写一个实际设备的最简单的驱动.
1.笔记本的合盖,开盖设备.
据说这个盖子上有霍尔传感器,传感器信号连接到一个单片机上.
专门监视着这个开合信号. 一但有变,要通过ACPI 协议通知内核.
具体过程, EC(embeded control) ->(上报) pch(periperal controler hub) 南桥->
产生SCI中断 (system control interrupt), 一般是IRQ9
然后是中断响应及处理阶段, ACPI核心首先识别到是PNPC0D0 设备事件,遍历设备上
注册的所有Notify 回调链表.
驱动就在这个回调函数链上. 系统会通知你状态,就是这么简单. 你不用实时监测状态的变化.
就是说,驱动要完成的功能是,当盖子打开时,驱动能收到一条打开的消息.
盖子闭合时,驱动收到一条闭合的消息.
这不是传统意义上的驱动,获取或写入设备数据,而是监测设备数据变化.
- 首先,它要匹配上设备,让驱动独占这个设备,完成自己的probe 函数,这是驱动的典型行为.
- 注册一个回调函数,就能够收到状态改变信息.
可能硬件层面很复杂,甚至是其它类型, 但它应该符合ACPI协议,是ACPI设备,
盖子在ACPI 协议中的名称是"PNP0C0D"
我们也应该在标准中享受到便利之处,那就是忽略它的硬件复杂性,只面向系统接口编程
2. 驱动代码
下面看看代码,很简单.60行代码.
cpp
$cat lid_driver.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/acpi.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux 5.4 Exact Match LID Driver");
MODULE_AUTHOR("DIY");
// 匹配 LID 标准设备, 是ACPI 协议,规定盖子的名称就是PNP0C0D
// 这就是标准的好处
static const struct acpi_device_id lid_acpi_ids[] = {
{"PNP0C0D", 0},
{ /* 终止 */ }
};
MODULE_DEVICE_TABLE(acpi, lid_acpi_ids);
// 事件回调
static void lid_notify(acpi_handle handle, u32 event, void *priv)
{
if (event == 0x80) {
pr_info("[DIY LID DRV] >>> 盖子关闭\n");
} else if (event == 0x81) {
pr_info("[DIY LID DRV] >>> 盖子打开\n");
}
}
// Probe 设备探测
static int lid_probe(struct acpi_device *adev)
{
pr_info("[DIY LID DRV] ✅ 成功接管 PNP0C0D LID 设备!\n");
//acpi 子系统会依次调用注册的回调函数
acpi_install_notify_handler(adev->handle,
ACPI_DEVICE_NOTIFY,
lid_notify,
NULL);
return 0;
}
// Remove 设备移除
static int lid_remove(struct acpi_device *adev)
{
acpi_remove_notify_handler(adev->handle,
ACPI_DEVICE_NOTIFY,
lid_notify);
pr_info("[DIY LID DRV] 设备解绑\n");
return 0;
}
// 【完全严格对齐内核结构体顺序】
static struct acpi_driver my_lid_driver = {
.name = "my_custom_lid",
.class = "lid",
.ids = lid_acpi_ids,
.flags = 0,
.ops = {
.add = lid_probe, //probe 要放到ops 结构体中
.remove = lid_remove,
},
.owner = THIS_MODULE,
};
module_acpi_driver(my_lid_driver);
3. 去掉原有驱动,加载自己驱动是实验的关键.
原驱动收到合盖事件会使系统休眠, 先解决这个问题.
3.1. 收到合盖事件不要休眠.
经查, 这是systemd-logind 服务的行为.
$ systemctl status systemd-logind
● systemd-logind.service - Login Service
Loaded: loaded (/lib/systemd/system/systemd-logind.service; static; vendor preset: enabled)
Active: active (running) since Wed 2026-05-27 10:10:17 CST; 3h 21min ago
Docs: man:systemd-logind.service(8)
man:logind.conf(5)
https://www.freedesktop.org/wiki/Software/systemd/logind
https://www.freedesktop.org/wiki/Software/systemd/multiseat
Main PID: 4262 (systemd-logind)
Status: "Processing requests..."
CGroup: /system.slice/systemd-logind.service
└─4262 /lib/systemd/systemd-logind
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: New seat seat0.
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event3 (Power Button)
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event2 (Power Button)
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event1 (Lid Switch)
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event0 (Sleep Button)
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event13 (SEMICO USB Keyboard)
5月 27 10:10:17 hjj-laptop systemd-logind[4262]: Watching system buttons on /dev/input/event6 (AT Translated Set 2 keyboard)
5月 27 10:10:17 hjj-laptop systemd[1]: Started Login Service.
5月 27 10:10:27 hjj-laptop systemd-logind[4262]: New session c1 of user gdm.
5月 27 10:20:16 hjj-laptop systemd-logind[4262]: New session 3 of user hjj.
它的服务脚本是/lib/systemd/system/systemd-logind.service, 执行文件是/lib/systemd/systemd-logind.
后者是一个elf 文件.功能:
监视着Power Button, Lid Switch, PS2 keyboard, USB keyboard 硬件.
我们不想关闭这个服务,因为影响太多, 但想改一下这个服务的行为,让合盖不要suspend, 而是ignore该信号.
打开/etc/systemd/logind.conf
#HandleLidSwitch=suspend
改为
HandleLidSwitch=ignore
意思是原来默认的行为是suspend, 现在显式指定为ignore
然后重启logind 服务:
$ sudo systemctl restart systemd-logind.service
再测关闭盖子,果然不再suspend了
3.2. 解除原来的驱动绑定.
3.2.1 先要找到盖子设备
$ find /sys/devices -name "PNP0C0D*"
输出: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0D:00
3.2.2 由这个设备找到驱动.
$ ls -l /sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0D:00
总用量 0
lrwxrwxrwx 1 root root 0 5月 27 13:49 driver -> .../.../.../.../bus/acpi/drivers/button/
-r--r--r-- 1 root root 4096 5月 27 10:21 hid
-r--r--r-- 1 root root 4096 5月 27 10:21 modalias
-r--r--r-- 1 root root 4096 5月 27 10:21 path
lrwxrwxrwx 1 root root 0 5月 27 10:21 physical_node -> .../.../.../platform/PNP0C0D:00
drwxr-xr-x 2 root root 0 5月 27 10:20 power
lrwxrwxrwx 1 root root 0 5月 27 10:09 subsystem -> .../.../.../.../bus/acpi
-rw-r--r-- 1 root root 4096 5月 27 10:09 uevent
drwxr-xr-x 3 root root 0 5月 27 10:09 wakeup
3.2.3 解除这个驱动和设备的binding
先进入到驱动目录.
$cd /sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0D:00
$cd .../.../.../.../bus/acpi/drivers/button/
hjj@hjj-laptop:/sys/bus/acpi/drivers/button$ ll
总用量 0
drwxr-xr-x 2 root root 0 5月 27 13:55 ./
drwxr-xr-x 9 root root 0 5月 27 13:55 .../
--w------- 1 root root 4096 5月 27 13:58 bind
lrwxrwxrwx 1 root root 0 5月 27 13:58 LNXPWRBN:00 -> .../.../.../.../devices/LNXSYSTM:00/LNXPWRBN:00/
lrwxrwxrwx 1 root root 0 5月 27 13:58 PNP0C0C:00 -> .../.../.../.../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0C:00/
lrwxrwxrwx 1 root root 0 5月 27 13:58 PNP0C0D:00 -> .../.../.../.../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0D:00/
lrwxrwxrwx 1 root root 0 5月 27 13:58 PNP0C0E:00 -> .../.../.../.../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0E:00/
--w------- 1 root root 4096 5月 27 13:55 uevent
--w------- 1 root root 4096 5月 27 13:58 unbind
解除命令
$ echo PNP0C0D:00 |sudo tee unbind
于是,相关链接,/dev/input下事件均消失.
这种操作, 针对的是就算驱动编译到内核,也可以用该方法解除binding.
4.安装测试我们的驱动.
insmod lid_driver.ko
开盖,关盖可以查看打印信息.