2025-03-24
struct input_dev
是Linux内核中用于表示输入设备的数据结构,定义在<linux/input.h>
头文件中。它用于管理和描述输入设备(如键盘、鼠标、触摸屏等)及其事件。
name: 设备名称。
phys: 设备的物理地址。
uniq: 设备的唯一标识符。
id: 设备的 ID 信息(如厂商、产品 ID 等)。
evbit: 事件类型位图(如按键、相对坐标等)。
keybit: 按键位图,表示支持的按键。
relbit: 相对坐标位图(如鼠标移动)。
absbit: 绝对坐标位图(如触摸屏坐标)。
mscbit: 杂项事件位图。
ledbit: LED状态位图。
sndbit: 声音事件位图。
ffbit: 力反馈事件位图。
swbit: 开关事件位图。
propbit: 设备属性位图(如是否支持多点触控)。
// test.dts
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
// 模拟数据
gpioe:gpio@50006000 {
#gpio-cells = <0x2>;
reg = <0x50006000 0x4 0x50006014 0x4 0x50000a28 0x4>;
};
// 模拟数据
gpiof:gpio@50008000 {
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
reg = <0x50008000 0x4 0x50008014 0x4 0x50008a28 0x4>;
gpio-ranges = <0x55 0x0 0x50 0x10>;
status = "okay";
};
key {
model = "This is key";
dev-type = "KEY";
interrupt-parent = <&gpiof>;
interrupts = <9 0>,<7 0>,<8 0>;
key-gpios = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// 与驱动匹配的关键设置
compatible = "my_device_002";
// 表示启用设备节点
status = "okay";
};
};
// input-device.c
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#define PLATFORM_DEVICE_BASENAME "my_device"
#define PLATFORM_DEVICE_NAME "my_device_002"
typedef struct {
u32 key1_interrupts;
u32 key1_gpios;
volatile int current_irq;
struct input_dev *input_device;
} simple_cdev_t;
static simple_cdev_t my_cdev = {0};
// 中断上文处理函数
static irqreturn_t _key_ISR(int irq, void *dev) {
pr_info("key_ISR irq = %d\n", irq);
my_cdev.current_irq = irq;
// 表示还有中断下文要处理
return IRQ_WAKE_THREAD;
}
// 中断下文处理函数
static irqreturn_t _thread_irq(int irq, void *dev) {
pr_info("thread_irq\n");
msleep(100); // 阻塞延时100毫秒, 会放弃cpu的使用权
if (irq == my_cdev.key1_interrupts) {
if (gpio_get_value(my_cdev.key1_gpios) == 0) {
pr_info("key1 pressed\n");
input_event(my_cdev.input_device, EV_KEY, KEY_A, 1);
input_sync(my_cdev.input_device);
} else {
pr_info("key1 released\n");
input_event(my_cdev.input_device, EV_KEY, KEY_A, 0);
input_sync(my_cdev.input_device);
}
}
// 表示中断处理完成
return IRQ_HANDLED;
}
// 当安装该驱动并名称匹配成功后,平台设备驱动会自动调用该函数
static int _probe(struct platform_device *device) {
pr_info("input_device_probe\n");
struct device_node *key_node = device->dev.of_node;
// 初始化gpio资源, 第二个参数要与testdtree.dts中的资源名称要一致
my_cdev.key1_gpios = of_get_named_gpio(key_node, "key-gpios", 0);
gpio_request(my_cdev.key1_gpios, "key1-gpios");
gpio_direction_input(my_cdev.key1_gpios);
// 获取按键中断资源
my_cdev.key1_interrupts = of_irq_get(key_node, 0);
// 会创建`/proc/irq/key1-interrupts`目录
// 中断下文运行在不同的线程,实现并发处理
int key1_irq_ret = request_threaded_irq(
my_cdev.key1_interrupts, _key_ISR, _thread_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key1-interrupts", NULL);
if (key1_irq_ret) {
pr_err("request_irq for key1 failed\n");
return key1_irq_ret;
}
// 初始化输入设备
// 输入设备的用途是将按键的信息通过设备文件暴露到用户层
my_cdev.input_device = input_allocate_device();
if (!my_cdev.input_device) {
pr_err("input_allocate_device failed\n");
return -ENOMEM;
}
// 对输入设备匹配方式和事件相关进行设置
// 设备匹配成功后会自动创建设备号,设备文件等
// 注册成功后可以在`/proc/bus/input/devices`文件查看相应的信息
// 设备文件在`/dev/input`目录下
my_cdev.input_device->id.bustype = ID_BUS;
// 注册按键事件
set_bit(EV_KEY, my_cdev.input_device->evbit);
// 分别为每个按键注册事件
set_bit(KEY_A, my_cdev.input_device->keybit);
// set_bit(KEY_B, my_cdev_v1.input_device->keybit);
// set_bit(KEY_C, my_cdev_v1.input_device->keybit);
my_cdev.input_device->name = "KEY";
my_cdev.input_device->uniq = "KEY";
my_cdev.input_device->phys = "soc/pf9/key";
// 注册到输入子系统中
int ird_ret = input_register_device(my_cdev.input_device);
if (ird_ret) {
input_free_device(my_cdev.input_device);
pr_err("input_register_device failed\n");
return ird_ret;
}
return 0;
}
// 当平台设备驱动卸载时,会自动调用该函数
static int _remove(struct platform_device *device) {
pr_info("input_device_remove\n");
input_unregister_device(my_cdev.input_device);
input_free_device(my_cdev.input_device);
free_irq(my_cdev.key1_interrupts, NULL);
gpio_free(my_cdev.key1_gpios);
return 0;
}
// 会与设备树安装的platform_device进行匹配
struct of_device_id of_node_match_table[] = {
[0] =
{
.compatible = PLATFORM_DEVICE_NAME,
},
[1] = {}, // 空元素代表结束
};
struct platform_driver my_input_driver = {
.probe = _probe,
.remove = _remove,
// 通过id_table匹配, 实现一个驱动对多个设备
.driver =
{
.name = PLATFORM_DEVICE_BASENAME,
// 设备树的匹配方式
.of_match_table = of_node_match_table,
},
};
static int __init input_device_init(void) {
pr_info("input_device_init\n");
int ret = platform_driver_register(&my_input_driver);
if (ret < 0) {
pr_info(KERN_WARNING "input_device_register failed\n");
return -1;
}
return 0;
}
static void __exit input_device_exit(void) {
pr_info("input_device_exit\n");
platform_driver_unregister(&my_input_driver);
}
module_init(input_device_init);
module_exit(input_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple input_device kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m += input-device.o
all: dt driver
driver:
make -C $(kernel-dir) modules M=$(top-dir)
dt:
dtc -@ -I dts -O dtb -o test.dtbo test.dts
clean:
rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
make -C $(kernel-dir) clean m=$(top-dir)
// reader.c
// 编译:gcc -g -o reader main.c
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/input.h>
struct input_event event;
int main(int argc, char *argv[]) {
if (argc != 2) {
perror("please input a device to read and write");
return -1;
}
FILE *fp = fopen(argv[1], "r");
assert(fp);
while (true) {
size_t rlen = fread(&event, 1, sizeof(struct input_event), fp);
assert(rlen >= 0);
if (rlen == 0)
break;
printf("len = %ld, code = %d, value = %d\n", rlen, event.code,
event.value);
if (event.code == KEY_A && event.value == 1) {
printf("A\n");
} else if (event.code == KEY_B && event.value == 1) {
printf("B\n");
} else if (event.code == KEY_C && event.value == 1) {
printf("C\n");
}
sleep(1);
}
fclose(fp);
return 0;
}