当前位置: 首页 > news >正文

嵌入式Linux驱动开发:i.MX6ULL按键中断驱动(非阻塞IO)

嵌入式Linux驱动开发:i.MX6ULL按键中断驱动(非阻塞IO)

概述

本文档详细介绍了在i.MX6ULL开发板上实现按键中断驱动的完整过程。该驱动程序实现了非阻塞IO操作,允许用户空间应用程序通过poll系统调用高效地监控按键状态变化,而无需进行忙等待。本文档结合了提供的源代码和设备树文件,详细解释了驱动程序的各个组成部分及其工作原理。

源码仓库

  • 仓库地址: https://gitee.com/dream-cometrue/linux_driver_imx6ull

理论基础

1. 非阻塞IO (Non-blocking I/O)

在传统的阻塞IO模型中,当应用程序调用read等系统调用时,如果数据不可用,进程会进入睡眠状态,直到数据就绪。这在某些场景下是高效的,但在需要同时监控多个文件描述符或进行其他工作的场景下,会导致资源浪费。

非阻塞IO通过在open系统调用时指定O_NONBLOCK标志来实现。在这种模式下,如果read调用时没有数据可读,系统调用会立即返回一个-EAGAIN错误,而不是让进程睡眠。这允许应用程序立即处理其他任务,或者使用pollselect系统调用来监控多个文件描述符的状态。

2. poll系统调用

poll系统调用是实现非阻塞IO的核心机制。它允许应用程序在一个系统调用中监控多个文件描述符的读、写和异常事件。poll会阻塞直到任何一个被监控的文件描述符就绪,或者超时。

在驱动程序中,poll操作通过file_operations结构体中的.poll成员函数实现。驱动程序需要调用poll_wait函数将当前进程添加到一个等待队列中,并返回一个描述当前文件描述符状态的掩码。

3. 等待队列 (Wait Queue)

等待队列是Linux内核中用于进程同步的机制。它允许一个或多个进程在某个条件满足之前进入睡眠状态。当条件满足时,另一个进程或中断处理程序可以唤醒等待队列中的所有进程。

在本驱动程序中,我们使用等待队列来实现poll功能。当应用程序调用poll时,驱动程序会将当前进程添加到等待队列中。当按键状态发生变化时,中断处理程序会通过定时器唤醒等待队列中的进程。

4. 定时器 (Timer)

定时器用于在指定的时间后执行一段代码。在本驱动程序中,定时器用于实现按键消抖。当按键中断发生时,我们启动一个20ms的定时器。在定时器超时后,我们读取按键的实际状态,以避免由于机械按键的抖动导致的误触发。

5. 原子变量 (Atomic Variables)

原子变量是内核中用于在多处理器系统中实现无锁同步的机制。对原子变量的操作是不可分割的,保证了在并发访问时的数据一致性。在本驱动程序中,我们使用原子变量keyvaluerelease来在中断上下文和进程上下文之间安全地传递按键值和释放状态。

设备树 (Device Tree)

设备树文件imx6ull-alientek-emmc.dts定义了开发板上的硬件配置。与本驱动程序相关的部分是/key节点:

key{compatible = "alientek,key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;states = "okay";key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio1>;interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
  • compatible = "alientek,key": 指定了该节点的兼容性字符串,驱动程序会根据这个字符串来匹配设备。
  • pinctrl-0 = <&pinctrl_key>: 引用了iomuxc节点中的pinctrl_key子节点,用于配置GPIO1_IO18引脚的复用功能和电气特性。
  • key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>: 指定了按键连接到gpio1控制器的第18号引脚,且为高电平有效。
  • interrupt-parent = <&gpio1>: 指定了中断控制器为gpio1
  • interrupts = <18 IRQ_TYPE_EDGE_BOTH>: 指定了中断号为18,触发类型为上升沿和下降沿(双边沿触发)。

在驱动程序中,我们通过of_find_node_by_path("/key")找到这个节点,并通过of_get_named_gpio函数获取按键的GPIO号。

驱动程序分析

1. 数据结构

struct key_desc

该结构体用于描述一个按键设备:

struct key_desc
{char name[10];        // 按键名称int gpio;             // GPIO号int irqnum;           // 中断号unsigned char value;  // 按键值irqreturn_t (*handler)(int, void *); // 中断处理函数
};
struct imx6uirq_dev

该结构体是驱动程序的核心,包含了所有必要的状态信息:

struct imx6uirq_dev
{dev_t devid;                    // 设备号int major;                      // 主设备号int minor;                      // 次设备号struct cdev cdev;               // 字符设备struct class *class;            // 设备类struct device *device;          // 设备struct device_node *key_nd;     // 设备树节点struct key_desc key[KEY_NUM];   // 按键描述数组struct timer_list timer;        // 定时器atomic_t keyvalue;              // 按键值(原子变量)atomic_t release;               // 释放状态(原子变量)wait_queue_head_t r_wait;       // 读等待队列
};

2. 字符设备操作

imx6uirq_open

该函数在应用程序打开设备文件时被调用。它将private_data指针指向imx6uirq全局变量,以便后续操作可以访问驱动程序的状态。

static int imx6uirq_open(struct inode *inode, struct file *filp)
{filp->private_data = &imx6uirq;return 0;
}
imx6uirq_release

该函数在应用程序关闭设备文件时被调用。在本驱动程序中,它不执行任何特定操作。

static int imx6uirq_release(struct inode *inode, struct file *filp)
{return 0;
}
imx6uirq_read

该函数实现了read系统调用。它根据O_NONBLOCK标志决定是阻塞还是非阻塞读取。

  • 如果指定了O_NONBLOCK标志,且没有按键释放事件,则立即返回-EAGAIN
  • 否则,调用wait_event_interruptible将进程添加到等待队列中,直到有按键释放事件发生。
  • 一旦有事件发生,将按键值复制到用户空间缓冲区,并重置释放状态。
ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{struct imx6uirq_dev *dev = filp->private_data;u8 keyvalue, release;int ret = 0;if (filp->f_flags & O_NONBLOCK){if (atomic_read(&dev->release) == 0){return -EAGAIN;}}else{wait_event_interruptible(dev->r_wait, atomic_read(&dev->release));}keyvalue = atomic_read(&dev->keyvalue);release = atomic_read(&dev->release);if (release){ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));if (ret){ret = -EFAULT;goto fail_copy_user;}atomic_set(&dev->release, 0);}else{ret = -EAGAIN;goto fail_key_unrelease;}return sizeof(keyvalue);
}
imx6uirq_poll

该函数实现了poll系统调用。它调用poll_wait将当前进程添加到r_wait等待队列中,并检查release原子变量。如果release为真,则返回POLLIN | POLLRDNORM,表示文件描述符可读。

static unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{int mask = 0;struct imx6uirq_dev *dev = filp->private_data;poll_wait(filp, &dev->r_wait, wait);if (atomic_read(&dev->release)){mask = POLLIN | POLLRDNORM;}return mask;
}

3. 中断处理

key0_handler

该函数是按键中断的处理程序。它在按键状态发生变化时被调用。由于中断处理程序执行在中断上下文中,不能进行睡眠操作,因此我们不能在这里直接读取GPIO状态。相反,我们启动一个定时器,在定时器的回调函数中读取GPIO状态。

static irqreturn_t key0_handler(int irq, void *filp)
{struct imx6uirq_dev *dev = filp;dev->timer.data = (volatile long)filp;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));return IRQ_HANDLED;
}
timer_func

该函数是定时器的回调函数。它在定时器超时后被调用。在本函数中,我们读取按键的GPIO状态:

  • 如果按键被按下(GPIO为低电平),则设置keyvalueKEY0VALUE
  • 如果按键被释放(GPIO为高电平),则设置release为1,并唤醒等待队列中的所有进程。
static void timer_func(unsigned long arg)
{struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;int value = 0;value = gpio_get_value(dev->key[0].gpio);if (value == 0){atomic_set(&dev->keyvalue, dev->key[0].value);}else{atomic_set(&dev->release, 1);}if (atomic_read(&dev->release)){wake_up_interruptible(&dev->r_wait);}
}

4. 驱动程序初始化和退出

imx6uirq_init

该函数在模块加载时被调用。它完成了以下初始化工作:

  1. 分配设备号(动态或静态)。
  2. 初始化字符设备,并将其添加到系统中。
  3. 创建设备类和设备文件。
  4. 调用key_init1函数初始化按键。
  5. 初始化定时器。
  6. 初始化原子变量和等待队列。
static int __init imx6uirq_init(void)
{// ... (设备号分配、字符设备注册、设备类和设备创建)ret = key_init1(&imx6uirq);if (ret < 0){goto fail_key_init;}timer1_init(&imx6uirq);atomic_set(&imx6uirq.keyvalue, KEYINVA);atomic_set(&imx6uirq.release, 0);init_waitqueue_head(&imx6uirq.r_wait);return 0;
}
key_init1

该函数负责初始化按键硬件。它通过设备树API获取按键的GPIO号,并请求GPIO、配置为输入模式,然后获取中断号并注册中断处理程序。

int key_init1(struct imx6uirq_dev *dev)
{u8 ret = 0, i = 0;dev->key_nd = of_find_node_by_path("/key");if (dev->key_nd == NULL){ret = -EFAULT;goto fail_find_nd;}dev->key[i].handler = key0_handler;dev->key[i].value = KEY0VALUE;for (i = 0; i < KEY_NUM; i++){dev->key[i].gpio = of_get_named_gpio(dev->key_nd, "key-gpios", i);if (dev->key[i].gpio < 0){ret = -EFAULT;goto fail_get_gpio;}memset(dev->key[i].name, 0, sizeof(dev->key[i].name));sprintf(dev->key[i].name, "KEY%d", i);ret = gpio_request(dev->key[i].gpio, dev->key[i].name);if (ret){ret = -EFAULT;goto fail_gpio_req;}ret = gpio_direction_input(dev->key[i].gpio);if (ret){ret = -EFAULT;goto fail_gpio_dir;}dev->key[i].irqnum = gpio_to_irq(dev->key[i].gpio);ret = request_irq(dev->key[i].irqnum, dev->key[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, dev->key[i].name, &imx6uirq);if (ret){ret = -EFAULT;goto fail_req_irq;}}return 0;
}
timer1_init

该函数初始化定时器,设置其回调函数为timer_func

void timer1_init(struct imx6uirq_dev *dev)
{init_timer(&dev->timer);dev->timer.function = timer_func;
}
imx6uirq_exit

该函数在模块卸载时被调用。它负责释放所有分配的资源,包括中断、GPIO、设备文件、字符设备、设备类和设备号。

static void __exit imx6uirq_exit(void)
{u8 i = 0;for (i = 0; i < KEY_NUM; i++){free_irq(imx6uirq.key[i].irqnum, &imx6uirq);gpio_free(imx6uirq.key[i].gpio);}del_timer(&imx6uirq.timer);device_destroy(imx6uirq.class, imx6uirq.devid);class_destroy(imx6uirq.class);cdev_del(&imx6uirq.cdev);unregister_chrdev(imx6uirq.major, IMX6UIRQ_NAME);
}

用户空间应用程序

用户空间应用程序imx6uirqAPP.c演示了如何使用poll系统调用来监控按键状态。

1. poll的使用

应用程序创建一个pollfd结构体,指定要监控的文件描述符、感兴趣的事件(POLLIN)和返回的事件。然后调用poll函数,指定超时时间为500毫秒。

struct pollfd fds;
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500);

2. 事件处理

poll返回后,应用程序检查revents字段以确定发生了什么事件。如果POLLIN事件发生,则调用read函数读取按键值。

if (ret > 0)
{if (fds.revents | POLLIN){unsigned char ch = 0;int ret = read(fd, &ch, sizeof(ch));if (ret >= 0){if (ch == KEY0VALUE){printf("User: key is pressing, ret is: %d\r\n", ret);}}}
}
http://www.xdnf.cn/news/1390105.html

相关文章:

  • PostgreSQL15——子查询
  • 基于SQL大型数据库的智能问答系统优化
  • Emacs 多个方便查看函数列表的功能
  • QML QQuickImage: Cannot open: qrc:/images/shrink.png(已解决)
  • 前端-初识Vue实例
  • Spring Boot Redis序列化全解析(7种策略)
  • 2024年06月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • leetcode 461 汉明距离
  • 如何在FastAPI中玩转全链路追踪,让分布式系统故障无处遁形?
  • 基于MCP工具的开发-部署-上线与维护全流程技术实现与应用研究
  • 北斗导航 | PPP-RTK算法核心原理与实现机制深度解析
  • AI助力PPT创作:秒出PPT与豆包AI谁更高效?
  • TypeScript:map和set函数
  • 【前端教程】从基础到专业:诗哩诗哩网HTML视频页面重构解析
  • Java试题-选择题(21)
  • new/delete 和 malloc/free 区别
  • 小杰机器视觉(five day)——直方图均衡化
  • linux系统学习(13.系统管理)
  • 基于orin系列的刷写支持笔记
  • 30分钟入门实战速成Cursor IDE(1)
  • 【拍摄学习记录】04-拍摄模式/曝光组合
  • Nginx的主要配置文件nginx.conf详细解读——及其不间断重启nginx服务等操作
  • 数据结构—第五章 树与二叉树
  • 机器学习算法全景解析:从理论到实践
  • vue3 鼠标移上去 显示勾选框 选中之后保持高亮
  • 自然语言提取PDF表格数据
  • 马斯克杀入AI编程!xAI新模型Grok Code Fast 1发布,深度评测:速度、价格与API上手指南
  • Vue3 + Spring Boot 项目中跨域问题的排查与解决
  • CS144 lab3 tcp_sender
  • 自动驾驶中的传感器技术36——Lidar(11)