2. 编译与烧写U-Boot引导程序
任务说明
本案例实现一个基于GPIO中断的按键驱动,目标是在按键按下(下降沿触发)时,通过中断处理函数上报按键事件。驱动采用Linux字符设备框架,中断处理函数中使用底半部机制(工作队列)上报事件,避免在中断上下文执行耗时操作。用户空间程序通过读取设备节点获取按键事件。
硬件连接
按键一端连接GPIO1_13,另一端接地。GPIO内部使能上拉电阻,按键按下时引脚电平由高变低,产生下降沿中断。
驱动代码实现
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define KEY_EVENT_PRESSED 0x01
#define KEY_EVENT_RELEASED 0x00
struct key_event {
unsigned int code; /* 按键编号 */
unsigned int value; /* 0=释放 1=按下 */
};
struct key_drv_data {
int gpio;
int irq;
struct work_struct work;
struct key_event event;
struct miscdevice mdev;
wait_queue_head_t wq;
struct key_event last_event;
int available;
};
static struct key_drv_data *g_key_data;
static void key_work_handler(struct work_struct *work)
{
struct key_drv_data *kdata = container_of(work, struct key_drv_data, work);
int val = gpio_get_value(kdata->gpio);
/* 读取当前电平,判断按下或释放 */
if (val == 0) {
kdata->event.code = 1;
kdata->event.value = KEY_EVENT_PRESSED;
} else {
kdata->event.code = 1;
kdata->event.value = KEY_EVENT_RELEASED;
}
/* 保存事件并唤醒读取进程 */
kdata->last_event = kdata->event;
kdata->available = 1;
wake_up_interruptible(&kdata->wq);
}
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
struct key_drv_data *kdata = dev_id;
/* 调度工作队列处理实际事件上报 */
schedule_work(&kdata->work);
return IRQ_HANDLED;
}
static ssize_t key_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct key_drv_data *kdata = g_key_data;
int ret;
if (count < sizeof(struct key_event))
return -EINVAL;
/* 等待事件可用 */
ret = wait_event_interruptible(kdata->wq, kdata->available == 1);
if (ret)
return ret;
/* 拷贝事件到用户空间 */
if (copy_to_user(buf, &kdata->last_event, sizeof(struct key_event)))
return -EFAULT;
kdata->available = 0;
return sizeof(struct key_event);
}
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
static int key_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct key_drv_data *kdata;
int ret;
kdata = devm_kzalloc(dev, sizeof(*kdata), GFP_KERNEL);
if (!kdata)
return -ENOMEM;
g_key_data = kdata;
/* 从设备树获取GPIO */
kdata->gpio = of_get_named_gpio(np, "key-gpio", 0);
if (!gpio_is_valid(kdata->gpio)) {
dev_err(dev, "invalid GPIO\n");
return -EINVAL;
}
/* 申请GPIO并设置为输入 */
ret = devm_gpio_request(dev, kdata->gpio, "key-input");
if (ret) {
dev_err(dev, "GPIO request failed\n");
return ret;
}
ret = gpio_direction_input(kdata->gpio);
if (ret) {
dev_err(dev, "GPIO direction failed\n");
return ret;
}
/* 获取中断号 */
kdata->irq = gpio_to_irq(kdata->gpio);
if (kdata->irq < 0) {
dev_err(dev, "GPIO to IRQ failed\n");
return kdata->irq;
}
/* 初始化工作队列与等待队列 */
INIT_WORK(&kdata->work, key_work_handler);
init_waitqueue_head(&kdata->wq);
kdata->available = 0;
/* 注册中断,下降沿触发 */
ret = devm_request_irq(dev, kdata->irq, key_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_SHARED,
"key-interrupt", kdata);
if (ret) {
dev_err(dev, "IRQ request failed\n");
return ret;
}
/* 注册misc设备 */
kdata->mdev.minor = MISC_DYNAMIC_MINOR;
kdata->mdev.name = "key_event";
kdata->mdev.fops = &key_fops;
kdata->mdev.parent = dev;
ret = misc_register(&kdata->mdev);
if (ret) {
dev_err(dev, "misc register failed\n");
return ret;
}
dev_info(dev, "key driver probed, gpio=%d, irq=%d\n",
kdata->gpio, kdata->irq);
return 0;
}
static int key_remove(struct platform_device *pdev)
{
struct key_drv_data *kdata = g_key_data;
if (kdata) {
misc_deregister(&kdata->mdev);
g_key_data = NULL;
}
return 0;
}
static const struct of_device_id key_of_match[] = {
{ .compatible = "example,key-interrupt" },
{ }
};
MODULE_DEVICE_TABLE(of, key_of_match);
static struct platform_driver key_driver = {
.probe = key_probe,
.remove = key_remove,
.driver = {
.name = "key_interrupt",
.of_match_table = key_of_match,
},
};
module_platform_driver(key_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BSP Course");
MODULE_DESCRIPTION("GPIO Key Interrupt Driver");
设备树配置
/ {
key_device {
compatible = "example,key-interrupt";
key-gpio = <&gpio1 13 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
编译与加载
# 编译(内核源码树内)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# 加载驱动
insmod key_interrupt.ko
# 验证设备节点
ls -l /dev/key_event
用户空间测试程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
struct key_event {
unsigned int code;
unsigned int value;
};
int main(void)
{
int fd;
struct key_event ev;
fd = open("/dev/key_event", O_RDONLY);
if (fd < 0) {
perror("open");
return -1;
}
printf("Waiting for key events...\n");
while (1) {
if (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
printf("Key %u %s\n", ev.code,
ev.value ? "PRESSED" : "RELEASED");
}
}
close(fd);
return 0;
}
验证方法
编译并加载驱动后,确认设备节点/dev/key_event存在。运行测试程序,按下按键SW1,程序应输出Key 1 PRESSED;松开按键,输出Key 1 RELEASED。若在中断触发后3秒内无输出,检查GPIO编号是否正确、设备树是否匹配、中断是否被其他驱动占用。
判定标准
- 驱动加载无错误,
dmesg显示probe成功信息 /dev/key_event节点存在且可读- 每次按下/释放按键,测试程序均能收到对应事件
- 连续快速按键不丢失事件(工作队列保证事件顺序处理)