title: MISC与INPUT子系统 tags: [Linux驱动, misc, input, 嵌入式, 字符设备] created: 2026-09-16 updated: 2026-09-17 pdf_ref:
💡 关联知识: [[03-Linux驱动开发核心/01-字符设备驱动框架]] | [[05-Linux外设驱动实战/02-按键输入驱动]] | [[03-Linux驱动开发核心/04-platform总线驱动]]
MISC(杂项设备)是Linux内核中一类特殊的字符设备,其主设备号固定为10,不同设备使用不同的次设备号。MISC设备的核心价值在于简化字符设备驱动的编写——传统字符设备需要手动完成设备号申请、cdev初始化/添加、类创建、设备创建等5个步骤,而MISC设备只需调用一个 misc_register() 函数即可全部完成。
MISC设备驱动的典型应用场景:
| 特性 | MISC设备 | 普通字符设备 |
|---|---|---|
| 主设备号 | 固定为10 | 动态或静态分配 |
| 次设备号 | 用户指定或动态分配 | 用户指定或动态分配 |
| 设备创建 | misc_register() 一步完成 |
需要5步:alloc→init→add→class→device |
| 设备注销 | misc_deregister() 一步完成 |
需要4步:del→unregister→destroy→destroy |
| 自动创建节点 | ✅ 是 | 需手动创建 |
| 适用场景 | 简单/杂项设备 | 复杂/分类设备 |
flowchart TB
accTitle: MISC设备驱动框架
accDescr: 展示MISC设备从用户空间到内核空间的完整调用链路
subgraph 用户空间
APP["用户应用程序<br/>open/read/write"]
DEV["/dev/miscbeep<br/>设备节点"]
end
subgraph 内核空间
MISC["MISC核心层<br/>misc_register()"]
CDEV["字符设备层<br/>自动创建cdev"]
FOPS["file_operations<br/>用户提供的操作集"]
end
subgraph 硬件层
HW["硬件设备<br/>GPIO/I2C/SPI"]
end
APP -->|"系统调用"| DEV
DEV --> MISC
MISC --> CDEV
CDEV --> FOPS
FOPS -->|"gpio_set_value等"| HW
flowchart LR
accTitle: MISC驱动编写三步法
accDescr: MISC设备驱动的简化编写流程
A["① 定义miscdevice<br/>设置minor/name/fops"] --> B["② 实现file_operations<br/>open/read/write等"]
B --> C["③ 注册/注销<br/>misc_register/deregister"]
/* 文件:include/linux/miscdevice.h */
struct miscdevice {
int minor; /* 次设备号 */
const char *name; /* 设备名字,注册后出现在/dev/下 */
const struct file_operations *fops; /* 设备操作集 */
struct list_head list; /* 内核链表,用于管理misc设备 */
struct device *parent; /* 父设备 */
struct device *this_device; /* 当前设备 */
const struct attribute_group **groups; /* 设备属性组 */
const char *nodename; /* 节点名 */
umode_t mode; /* 设备权限 */
};
关键成员说明:
| 成员 | 类型 | 说明 |
|---|---|---|
minor |
int |
次设备号。可用预定义值或 MISC_DYNAMIC_MINOR(255)动态分配 |
name |
const char * |
设备名,注册后在 /dev/ 下创建同名设备文件 |
fops |
const struct file_operations * |
字符设备操作函数集 |
mode |
umode_t |
设备文件权限,如 0666 |
/* 文件:include/linux/miscdevice.h */
#define PSMOUSE_MINOR 1
#define MS_BUSMOUSE_MINOR 2 /* unused */
#define ATIXL_BUSMOUSE_MINOR 3 /* unused */
#define ATARIMOUSE_MINOR 5 /* unused */
#define SUN_MOUSE_MINOR 6 /* unused */
/* ... 省略中间定义 ... */
#define MISC_DYNAMIC_MINOR 255 /* 动态分配次设备号 */
使用
MISC_DYNAMIC_MINOR(255) 让内核自动分配可用的次设备号,避免冲突。
flowchart TB
accTitle: 字符设备注册方式对比
accDescr: 对比传统字符设备5步注册与MISC设备1步注册
subgraph 传统方式["传统字符设备注册(5步)"]
T1["alloc_chrdev_region()<br/>申请设备号"] --> T2["cdev_init()<br/>初始化cdev"]
T2 --> T3["cdev_add()<br/>添加cdev"]
T3 --> T4["class_create()<br/>创建类"]
T4 --> T5["device_create()<br/>创建设备"]
end
subgraph MISC方式["MISC设备注册(1步)"]
M1["misc_register()<br/>一步完成全部"]
end
| 函数 | 原型 | 说明 |
|---|---|---|
misc_register |
int misc_register(struct miscdevice *misc) |
注册MISC设备,成功返回0 |
misc_deregister |
int misc_deregister(struct miscdevice *misc) |
注销MISC设备,成功返回0 |
int misc_register(struct miscdevice *misc)
| 参数 | 方向 | 说明 |
|---|---|---|
misc |
输入 | 指向已初始化的 miscdevice 结构体 |
| 返回值 | 输出 | 0成功,负值失败(如 -EINVAL 参数错误) |
int misc_deregister(struct miscdevice *misc)
| 参数 | 方向 | 说明 |
|---|---|---|
misc |
输入 | 指向要注销的 miscdevice 结构体 |
| 返回值 | 输出 | 0成功,负值失败 |
sequenceDiagram
accTitle: MISC设备注册注销时序
accDescr: 展示MISC设备从注册到注销的完整生命周期
participant 驱动 as 驱动模块
participant MISC as MISC核心层
participant VFS as VFS层
participant DEV as /dev节点
驱动->>MISC: misc_register(&my_miscdev)
MISC->>MISC: 分配/校验次设备号
MISC->>VFS: 注册字符设备(主设备号10)
MISC->>DEV: 创建 /dev/name 设备节点
MISC-->>驱动: 返回0(成功)
Note over 驱动,DEV: 设备运行中...
驱动->>MISC: misc_deregister(&my_miscdev)
MISC->>DEV: 删除 /dev/name 设备节点
MISC->>VFS: 注销字符设备
MISC-->>驱动: 返回0(成功)
以下代码来自正点原子 I.MX6ULL 开发板例程 19_misc,采用 platform + MISC 框架实现蜂鸣器驱动。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h> /* MISC设备头文件 */
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MISCBEEP_NAME "miscbeep" /* 设备名 */
#define MISCBEEP_MINOR 144 /* 次设备号 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */
/* 设备结构体 */
struct miscbeep_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int beep_gpio;
};
struct miscbeep_dev miscbeep;
/* 打开设备 */
static int miscbeep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &miscbeep; /* 设置私有数据 */
return 0;
}
/* 写数据:控制蜂鸣器开关 */
static ssize_t miscbeep_write(struct file *filp,
const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char beepstat;
struct miscbeep_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0];
if (beepstat == BEEPON)
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
else if (beepstat == BEEPOFF)
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
return 0;
}
/* 文件操作集 */
static struct file_operations miscbeep_fops = {
.owner = THIS_MODULE,
.open = miscbeep_open,
.write = miscbeep_write,
};
/* MISC设备结构体 */
static struct miscdevice beep_miscdev = {
.minor = MISCBEEP_MINOR, /* 次设备号144 */
.name = MISCBEEP_NAME, /* 设备名"miscbeep" */
.fops = &miscbeep_fops, /* 操作集 */
};
/* platform驱动probe函数 */
static int miscbeep_probe(struct platform_device *dev)
{
int ret = 0;
printk("beep driver and device was matched!\r\n");
/* 1. 获取设备节点 */
miscbeep.nd = of_find_node_by_path("/beep");
if (miscbeep.nd == NULL) {
printk("beep node not find!\r\n");
return -EINVAL;
}
/* 2. 获取GPIO编号 */
miscbeep.beep_gpio = of_get_named_gpio(miscbeep.nd, "beep-gpio", 0);
if (miscbeep.beep_gpio < 0) {
printk("can't get beep-gpio");
return -EINVAL;
}
/* 3. 申请并初始化GPIO */
gpio_request(miscbeep.beep_gpio, "beep");
ret = gpio_direction_output(miscbeep.beep_gpio, 1);
if (ret < 0)
printk("can't set gpio!\r\n");
/* 4. 注册MISC设备(一步代替传统5步) */
ret = misc_register(&beep_miscdev);
if (ret < 0) {
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
/* platform驱动remove函数 */
static int miscbeep_remove(struct platform_device *dev)
{
gpio_set_value(miscbeep.beep_gpio, 1);
gpio_free(miscbeep.beep_gpio);
misc_deregister(&beep_miscdev); /* 注销MISC设备 */
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "atkalpha-beep" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver beep_driver = {
.driver = {
.name = "imx6ul-beep",
.of_match_table = beep_of_match,
},
.probe = miscbeep_probe,
.remove = miscbeep_remove,
};
static int __init miscbeep_init(void)
{
return platform_driver_register(&beep_driver);
}
static void __exit miscbeep_exit(void)
{
platform_driver_unregister(&beep_driver);
}
module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
| 代码行 | 功能说明 |
|---|---|
第29行 #define MISCBEEP_NAME "miscbeep" |
定义设备名,注册后在 /dev/miscbeep 出现 |
第30行 #define MISCBEEP_MINOR 144 |
指定次设备号为144 |
第53~57行 miscbeep_open() |
打开设备,将私有数据指向设备结构体 |
第67~87行 miscbeep_write() |
写操作:从用户空间拷贝数据,根据值控制GPIO |
第90~94行 miscbeep_fops |
文件操作集,包含open和write |
第97~101行 beep_miscdev |
MISC设备核心结构体,定义次设备号、名称、操作集 |
第109~145行 miscbeep_probe() |
platform匹配成功后执行:获取GPIO→申请→注册MISC |
第138行 misc_register(&beep_miscdev) |
核心调用:一行完成设备号+cdev+class+device |
第152~160行 miscbeep_remove() |
卸载时:关闭GPIO→释放→注销MISC |
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define BEEPOFF 0
#define BEEPON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR); /* 打开beep驱动 */
if (fd < 0) {
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 0关 1开 */
retvalue = write(fd, databuf, sizeof(databuf));
if (retvalue < 0) {
printf("BEEP Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd);
return 0;
}
INPUT子系统是Linux内核专门为输入设备(按键、键盘、鼠标、触摸屏等)设计的框架。输入设备本质上是字符设备,但INPUT子系统在其上封装了标准化的事件上报机制,驱动开发者只需关注"上报事件",无需关心用户空间如何处理。
flowchart LR
accTitle: Linux INPUT子系统三层架构
accDescr: 展示INPUT子系统的驱动层、核心层、事件层分工
subgraph 硬件["硬件输入设备"]
KBD["USB键盘"]
MOUSE["USB鼠标"]
TS["触摸屏"]
KEY["GPIO按键"]
end
subgraph 驱动层["驱动层(开发者编写)"]
KH["Keyboard Handler"]
MH["Mouse Handler"]
TH["Touch Handler"]
end
subgraph 核心层["核心层(内核管理)"]
INPUT_CORE["input核心层<br/>input.c<br/>设备注册/事件分发"]
end
subgraph 事件层["事件层(用户接口)"]
EVDEV["/dev/input/eventX<br/>事件设备节点"]
end
subgraph 用户空间["用户空间"]
APP["应用程序<br/>read(input_event)"]
EVTEST["evtest<br/>测试工具"]
end
KBD --> KH
MOUSE --> MH
TS --> TH
KEY --> KH
KH --> INPUT_CORE
MH --> INPUT_CORE
TH --> INPUT_CORE
INPUT_CORE --> EVDEV
EVDEV --> APP
EVDEV --> EVTEST
| 层次 | 职责 | 开发者关注 |
|---|---|---|
| 驱动层 | 输入设备的具体驱动,向核心层上报原始数据 | ✅ 需要编写 |
| 核心层 | 承上启下,管理设备注册,分发事件到事件层 | ❌ 内核提供 |
| 事件层 | 与用户空间交互,创建 /dev/input/eventX 节点 |
❌ 内核提供 |
sequenceDiagram
accTitle: INPUT事件上报流程
accDescr: 从硬件中断到用户空间读取的完整事件传递链
participant HW as 硬件(GPIO)
participant DRV as 驱动层
participant CORE as 核心层
participant EVT as 事件层(/dev/input/eventX)
participant APP as 用户APP
HW->>DRV: 中断触发
DRV->>DRV: 读取硬件状态(如gpio_get_value)
DRV->>CORE: input_report_key(dev, KEY_0, 1)
DRV->>CORE: input_sync(dev)
CORE->>EVT: 封装为input_event结构体
EVT->>APP: read()返回input_event
APP->>APP: 解析事件类型和按键值
/* 文件:include/linux/input.h */
struct input_dev {
const char *name; /* 设备名称 */
const char *phys; /* 设备物理路径 */
const char *uniq; /* 设备唯一ID */
struct input_id id; /* 设备ID(总线类型/厂商/产品/版本) */
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; /* 属性位图 */
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型位图 */
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值位图 */
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标位图 */
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标位图 */
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件位图 */
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /* LED位图 */
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* 声音位图 */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 力反馈位图 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /* 开关状态位图 */
/* ... 省略其他成员 ... */
};
/* 文件:include/uapi/linux/input.h */
#define EV_SYN 0x00 /* 同步事件(每次上报后必须发送) */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件(如鼠标移动) */
#define EV_ABS 0x03 /* 绝对坐标事件(如触摸屏) */
#define EV_MSC 0x04 /* 杂项事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED事件 */
#define EV_SND 0x12 /* 声音事件 */
#define EV_REP 0x14 /* 重复事件(连按功能) */
#define EV_FF 0x15 /* 力反馈事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 力反馈状态事件 */
/* 文件:include/uapi/linux/input.h */
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
/* ... */
#define KEY_0 11 /* 数字0键 */
#define KEY_ENTER 28 /* 回车键 */
/* ... */
#define KEY_A 30
#define KEY_B 48
/* ... 共数百个按键定义 ... */
/* 文件:include/uapi/linux/input.h */
struct input_event {
struct timeval time; /* 事件发生时间 */
__u16 type; /* 事件类型(EV_KEY等) */
__u16 code; /* 事件码(KEY_0等) */
__s32 value; /* 事件值(1按下/0松开) */
};
input_event内存布局(16字节):
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0 | 4 | tv_sec | 秒(32位) |
| 4 | 4 | tv_usec | 微秒(32位) |
| 8 | 2 | type | 事件类型 |
| 10 | 2 | code | 事件码 |
| 12 | 4 | value | 事件值 |
用户空间通过
read("/dev/input/eventX")获取input_event结构体来解析输入事件。
以下代码来自正点原子 I.MX6ULL 开发板例程 20_input,实现GPIO按键的INPUT子系统驱动。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h> /* INPUT子系统头文件 */
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEYINPUT_CNT 1
#define KEYINPUT_NAME "keyinput"
#define KEY0VALUE 0X01
#define INVAKEY 0XFF
#define KEY_NUM 1
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*handler)(int, void *);
};
/* 设备结构体 */
struct keyinput_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
struct input_dev *inputdev; /* input_dev指针 */
};
struct keyinput_dev keyinputdev;
/* 中断服务函数:启动消抖定时器 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数:消抖后上报按键事件 */
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct keyinput_dev *dev = (struct keyinput_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if (value == 0) { /* 按下按键 */
input_report_key(dev->inputdev, keydesc->value, 1); /* 上报按下 */
input_sync(dev->inputdev); /* 同步事件 */
} else { /* 按键松开 */
input_report_key(dev->inputdev, keydesc->value, 0); /* 上报松开 */
input_sync(dev->inputdev); /* 同步事件 */
}
}
/* 按键IO初始化 + input_dev注册 */
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
/* 1. 获取设备节点 */
keyinputdev.nd = of_find_node_by_path("/key");
if (keyinputdev.nd == NULL) {
printk("key node not find!\r\n");
return -EINVAL;
}
/* 2. 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
keyinputdev.irqkeydesc[i].gpio =
of_get_named_gpio(keyinputdev.nd, "key-gpio", i);
}
/* 3. 初始化GPIO并申请中断 */
for (i = 0; i < KEY_NUM; i++) {
memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(keyinputdev.irqkeydesc[i].name));
sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);
gpio_request(keyinputdev.irqkeydesc[i].gpio, keyinputdev.irqkeydesc[i].name);
gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);
keyinputdev.irqkeydesc[i].irqnum =
irq_of_parse_and_map(keyinputdev.nd, i);
}
keyinputdev.irqkeydesc[0].handler = key0_handler;
keyinputdev.irqkeydesc[0].value = KEY_0;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(keyinputdev.irqkeydesc[i].irqnum,
keyinputdev.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
keyinputdev.irqkeydesc[i].name, &keyinputdev);
}
/* 4. 创建消抖定时器 */
init_timer(&keyinputdev.timer);
keyinputdev.timer.function = timer_function;
/* 5. 申请并注册input_dev */
keyinputdev.inputdev = input_allocate_device();
keyinputdev.inputdev->name = KEYINPUT_NAME;
/* 设置事件类型 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/* 注册input_dev */
ret = input_register_device(keyinputdev.inputdev);
if (ret) {
printk("register input device failed!\r\n");
return ret;
}
return 0;
}
static int __init keyinput_init(void)
{
keyio_init();
return 0;
}
static void __exit keyinput_exit(void)
{
unsigned int i = 0;
del_timer_sync(&keyinputdev.timer);
for (i = 0; i < KEY_NUM; i++)
free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
for (i = 0; i < KEY_NUM; i++)
gpio_free(keyinputdev.irqkeydesc[i].gpio);
input_unregister_device(keyinputdev.inputdev);
input_free_device(keyinputdev.inputdev);
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
| 代码行 | 功能说明 |
|---|---|
第57行 struct input_dev *inputdev |
在设备结构体中定义input_dev指针 |
第93~102行 timer_function() |
消抖定时器回调:读取GPIO值,上报按键事件 |
第96行 input_report_key(dev, KEY_0, 1) |
上报KEY_0按键按下事件 |
第97行 input_sync(dev) |
上报同步事件,必须调用 |
第156行 input_allocate_device() |
申请input_dev结构体 |
| 第172~173行 | 设置事件类型为EV_KEY和EV_REP,注册KEY_0按键 |
第176行 input_register_device() |
向内核注册input_dev |
第215行 input_unregister_device() |
注销input_dev |
第216行 input_free_device() |
释放input_dev内存 |
/* 方法一:使用__set_bit(推荐初学者) */
__set_bit(EV_KEY, inputdev->evbit);
__set_bit(EV_REP, inputdev->evbit);
__set_bit(KEY_0, inputdev->keybit);
/* 方法二:直接操作位图数组 */
inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
/* 方法三:使用input_set_capability(推荐) */
inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(inputdev, EV_KEY, KEY_0);
/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if (value == 0) { /* 按下按键 */
input_report_key(inputdev, KEY_0, 1); /* 上报按下 */
input_sync(inputdev); /* 同步 */
} else { /* 按键松开 */
input_report_key(inputdev, KEY_0, 0); /* 上报松开 */
input_sync(inputdev); /* 同步 */
}
}
# 编译MISC驱动模块
obj-m := miscbeep.o
make -j32 # 生成 miscbeep.ko
# 编译INPUT驱动模块
obj-m := keyinput.o
make -j32 # 生成 keyinput.ko
# 编译测试APP(交叉编译)
arm-linux-gnueabihf-gcc miscbeepApp.c -o miscbeepApp
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
# 加载驱动
depmod # 首次加载需要
modprobe miscbeep.ko # 加载模块
# 查看设备节点
ls /dev/miscbeep -l # 主设备号10,次设备号144
# 测试蜂鸣器
./miscbeepApp /dev/miscbeep 1 # 打开蜂鸣器
./miscbeepApp /dev/miscbeep 0 # 关闭蜂鸣器
# 卸载驱动
rmmod miscbeep.ko
# 加载前查看/dev/input目录
ls /dev/input/ # 通常有event0, mice
# 加载驱动
depmod
modprobe keyinput.ko
# 加载后查看
ls /dev/input/ # 新增event1
# 方法一:使用测试APP
./keyinputApp /dev/input/event1
# 按下KEY0输出:key 11 press
# 松开KEY0输出:key 11 release
# 方法二:使用hexdump查看原始数据
hexdump /dev/input/event1
# 按下按键输出:
# 0000000 0c41 0000 d7cd 000c 0001 000b 0001 0000
# 0000010 0c41 0000 d7cd 000c 0000 0000 0000 0000
/* input_event原始数据格式(16字节一行) */
/* tv_sec(4B) tv_usec(4B) type(2B) code(2B) value(4B) */
第1行: 0c41 0000 d7cd 000c 0001 000b 0001 0000
tv_sec=0x00000c41 tv_usec=0x000cd7cd
type=0x0001(EV_KEY) code=0x000b(KEY_0=11) value=1(按下)
第2行: 0c41 0000 d7cd 000c 0000 0000 0000 0000
type=0x0000(EV_SYN) ← 同步事件
第3行: 0c42 0000 54bb 0000 0001 000b 0000 0000
type=0x0001(EV_KEY) code=0x000b(KEY_0) value=0(松开)
第4行: 0c42 0000 54bb 0000 0000 0000 0000 0000
type=0x0000(EV_SYN) ← 同步事件
| 工具 | 用途 | 安装 |
|---|---|---|
evtest |
交互式查看输入事件 | apt install evtest |
hexdump |
查看原始event数据 | 系统自带 |
od |
查看原始event数据 | 系统自带 |
cat /proc/bus/input/devices |
查看已注册的input设备 | 系统自带 |
| 特性 | IMX6ULL | STM32 HAL | RK3568 |
|---|---|---|---|
| 设备框架 | platform + device tree | HAL句柄 + CubeMX | platform + device tree |
| MISC支持 | ✅ 完整支持 | ❌ 不支持 | ✅ 完整支持 |
| INPUT子系统 | ✅ 完整支持 | ❌ 轮询/中断回调 | ✅ 完整支持 |
| 设备树 | ✅ 必须 | ❌ 无 | ✅ 必须 |
| GPIO操作 | of_get_named_gpio |
HAL_GPIO_ReadPin |
of_get_named_gpio |
| 中断处理 | request_irq |
HAL_NVIC_SetPriority |
request_irq |
| 事件上报 | input_report_key |
直接读取寄存器 | input_report_key |
| 用户空间接口 | /dev/input/eventX |
无标准接口 | /dev/input/eventX |
| 驱动加载 | insmod/modprobe | 编译时链接 | insmod/modprobe |
flowchart TB
accTitle: 不同平台MISC设备使用方式
accDescr: 对比IMX6ULL、STM32、RK3568三种平台的MISC设备使用方式
subgraph IMX6ULL["IMX6ULL (Linux)"]
I1["设备树描述硬件"] --> I2["platform驱动匹配"]
I2 --> I3["misc_register()"]
I3 --> I4["/dev/miscbeep"]
end
subgraph STM32["STM32 (HAL)"]
S1["CubeMX配置引脚"] --> S2["HAL_GPIO_Init()"]
S2 --> S3["HAL_GPIO_WritePin()"]
S3 --> S4["直接寄存器操作"]
end
subgraph RK3568["RK3568 (Linux)"]
R1["设备树描述硬件"] --> R2["platform驱动匹配"]
R2 --> R3["misc_register()"]
R3 --> R4["/dev/miscbeep"]
end
考察点: MISC设备机制
参考答案:
misc_register() 一步完成,普通字符设备需要 alloc_chrdev_region() → cdev_init() → cdev_add() → class_create() → device_create() 共5步misc_deregister() 一步,普通字符设备需要4步考察点: INPUT子系统架构
参考答案:
drivers/input/input.c),为驱动层提供设备注册/操作接口,通知事件层处理输入事件。注册主设备号13的字符设备input_event 结构体,通过 /dev/input/eventX 设备节点供用户空间读取考察点: INPUT事件上报机制
参考答案:
input_report_key(dev, code, value):上报按键事件,本质是调用 input_event(dev, EV_KEY, code, !!value)input_sync(dev):上报同步事件(EV_SYN),告诉内核一次完整的事件上报结束EV_SYN 后,事件层才会将这一组事件统一分发给用户空间。如果不调用 input_sync,事件会滞留在缓冲区,用户空间无法读取考察点: input_event数据结构
参考答案:
struct input_event {
struct timeval time; /* 8字节:秒+微秒 */
__u16 type; /* 2字节:事件类型 */
__u16 code; /* 2字节:事件码 */
__s32 value; /* 4字节:事件值 */
}; /* 共16字节 */
read("/dev/input/eventX", &ev, sizeof(ev)) 读取input_event 结构体ev.type 判断事件类型(EV_KEY/EV_REL/EV_ABS等)ev.code 获取具体按键码或轴号ev.value 获取事件值(按键的按下/松开)考察点: 内核自带驱动使用
参考答案:
gpio-keys,compatible 属性设置为 "gpio-keys"gpios、label、linux,code 属性描述autorepeat 属性CONFIG_KEYBOARD_GPIO=ylinux,code 的值对应 include/uapi/linux/input.h 中的按键码定义代码来源: Linux驱动例程 19_miscbeep(MISC蜂鸣器驱动)、20_input(INPUT按键驱动)
最后更新: 2026-09-17