嵌入式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.pswp.cn/bicheng/95508.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95508.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95508.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从 @Schedule 到 XXL-JOB:分布式定时任务的演进与实践

从Schedule到XXL-JOB&#xff1a;分布式定时任务的演进与实践 在分布式系统中&#xff0c;定时任务是常见需求&#xff08;如数据备份、报表生成、缓存刷新等&#xff09;。Spring框架的Schedule注解虽简单易用&#xff0c;但在集群环境下存在明显局限&#xff1b;而XXL-JOB作为…

阿里云营业执照OCR接口的PHP实现与技术解析:从签名机制到企业级应用

一、阿里云营业执照OCR接口的核心技术架构 阿里云OCR服务基于深度学习模型和大规模数据训练,针对中国营业执照的版式特征(如统一社会信用代码位置、企业名称排版、经营范围换行规则等)进行了专项优化,识别准确率可达98%以上。其接口调用遵循RESTful API设计规范,采用HMAC…

AI人工智能大模型应用如何落地

AI人工智能大模型应用落地需要经过以下步骤&#xff1a; 明确应用场景和目标&#xff1a;首先需要明确AI大模型在哪个领域、解决什么问题。例如&#xff0c;在智能客服领域&#xff0c;AI大模型可以用于提高客户服务的效率和质量&#xff1b;在医学领域&#xff0c;AI大模型可以…

手写Muduo网络库核心代码2--Poller、EPollPoller详细讲解

Poller抽象层代码Muduo 网络库中的 Poller 抽象层是其事件驱动模型的核心组件之一&#xff0c;负责统一封装不同 I/O 复用机制&#xff08;如 epoll、poll&#xff09;&#xff0c;实现事件监听与分发。Poller 抽象层的作用统一 I/O 复用接口Poller 作为抽象基类&#xff0c;定…

基于MCP架构的OpenWeather API服务端设计与实现

随着微服务和模块化架构的发展&#xff0c;越来越多的系统倾向于采用可插拔、高内聚的设计模式。MCP(Modular, Collaborative,Pluggable)架构正是这样一种强调模块化、协作性和扩展性的设计思想。它允许开发者以“组件”方式组合功能&#xff0c;提升系统的灵活性与可维护性。 …

从“叠加”到“重叠”:Overlay 与 Overlap 双引擎驱动技术性能优化

在技术领域&#xff0c;“Overlay”和“Overlap”常因拼写相似被混淆&#xff0c;但二者实则代表两种截然不同的优化逻辑&#xff1a;Overlay 是“主动构建分层结构”&#xff0c;通过资源复用与隔离提升效率&#xff1b;Overlap 是“让耗时环节时间交叉”&#xff0c;通过并行…

【Vue2 ✨】 Vue2 入门之旅(六):指令与过滤器

前一篇我们学习了组件化开发。本篇将介绍 指令与过滤器&#xff0c;这是 Vue 模板语法的重要扩展&#xff0c;让页面渲染更加灵活。 目录 常见内置指令自定义指令过滤器小结 常见内置指令 Vue 提供了丰富的内置指令&#xff0c;常见的有&#xff1a; <div id"app&qu…

【随笔】【Debian】【ArchLinux】基于Debian和ArchLinux的ISO镜像和虚拟机VM的系统镜像获取安装

一、Debian Debian -- Debian 全球镜像站 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 debian-cd-current-amd64-iso-cd安装包下载_开源镜像站-阿里云 清华源&#xff1a; 清华大学开源软件镜像站 | Tsinghua Open Source Mirror USTC Open Source Software Mirror 二、…

如何用 Kotlin 在 Android 手机开发一个文字游戏,并加入付费机制?

Kotlin 开发 Android 文字游戏基础框架使用 Android Studio 创建项目&#xff0c;选择 Kotlin 作为主要语言。基础游戏逻辑可通过状态机和文本解析实现&#xff1a;class GameEngine {private var currentScene: Scene loadStartingScene()fun processCommand(input: String):…

安卓开发---BaseAdapter(定制ListView的界面)

概念&#xff1a;BaseAdapter 是 Android 中最基础的适配器类&#xff0c;它是所有其他适配器&#xff08;如 ArrayAdapter、SimpleAdapter&#xff09;的父类。方法签名&#xff1a;public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { // 获取数据…

Conda配置完全指南:Windows系统Anaconda/Miniconda的安装、配置、基础使用、清理缓存空间和Pycharm/VSCode配置指南

本文同步发布在个人博客&#xff1a; Conda配置完全指南Conda 是一个开源的跨平台包管理与环境管理工具&#xff0c;广泛应用于数据科学、机器学习及 Python 开发领域。它不仅能帮助用户快速安装、更新和卸载第三方库&#xff0c;还能创建相互隔离的虚拟环境&#xff0c;解决不…

什么是账号矩阵?如何避免账号IP关联风险

账号矩阵是指在同一平台或多个平台上&#xff0c;围绕同一品牌、业务或个人 IP 构建的多个相互关联、协同运作的账号体系。这些账号通过差异化的内容定位和运营策略形成互补&#xff0c;共同实现流量聚合、品牌曝光或业务拓展的目标。协同效应&#xff1a;账号间通过内容互推、…

解析ELK(filebeat+logstash+elasticsearch+kibana)日志系统原理以及k8s集群日志采集过程

ELK日志系统解析 ELK 日志系统&#xff08;现常称为 Elastic Stack&#xff0c;由 Filebeat、Logstash、Elasticsearch、Kibana 组成&#xff09;是一套用于 日志收集、清洗、存储、检索和可视化 的开源解决方案。 它的核心价值是将分散在多台服务器 / 应用中的日志 “汇聚成池…

python 内置函数 sort() 复杂度分析笔记

在做 280. 摆动排序 时&#xff0c;有一版 python 题解&#xff0c;里面直接用了sort() &#xff0c;又用了一个简单的 for 循环&#xff0c;但整体时间复杂度为 O(n⋅log(n)) &#xff0c;那么问题就出自这个 sort() &#xff0c;所以在这分析一下 sort() 的复杂度。Python 的…

【光照】Unity中的[经验模型]

【从UnityURP开始探索游戏渲染】专栏-直达 图形学第一定律&#xff1a;“看起来对就对” URP光照模型发展史 ‌2018年‌&#xff1a;URP首次发布&#xff08;原LWRP&#xff09;&#xff0c;继承传统前向渲染的Blinn-Phong简化版‌2019年‌&#xff1a;URP 7.x引入Basic Shade…

uniapp小程序使用自定义底部tabbar,并根据用户类型动态切换tabbar数据

1.注意点 在pages.json中配置tabbar如下字段&#xff1a;custom&#xff1a;true &#xff0c;会自动隐藏原生tabbar&#xff0c;使用自定义的tabbar2.如何自定义呢 可以使用第三方组件库的tabbar组件&#xff0c;然后二次封装下内部封装逻辑&#xff1a; 1.点击切换逻辑 2.根据…

Redis 哨兵 (基于 Docker)

目录 1. 基本概念 2. 安装部署 (基于 Docker) 2.1 使用 docker 获取 redis 镜像 2.2 编排 主从节点 2.3 编排 redis-sentinel 节点 3. 重新选举 4. 选举原理 5. 总结 1. 基本概念 名词 逻辑结构物理结构主节点Reids 主服务一个独立的 redis-server 进程从节点Redis 从…

Python学习-day4

Python 语言的运算符: 算术运算符比较&#xff08;关系&#xff09;运算符赋值运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级 算术运算符 定义变量a 21&#xff0c;变量b 10。运算符描述实例加 - 两个对象相加a b 输出结果 31-减 - 得到负数或是一个数减去另一…

Vite 插件 @vitejs/plugin-legacy 深度解析:旧浏览器兼容指南

&#x1f4d6; 简介 vitejs/plugin-legacy 是 Vite 官方提供的兼容性插件&#xff0c;专门用于为现代浏览器构建的应用程序提供对旧版浏览器的支持。该插件通过自动生成兼容性代码和 polyfill&#xff0c;确保您的应用能够在 IE 11 等旧版浏览器中正常运行。 核心价值 向后兼…

数据质检之springboot通过yarn调用spark作业实现数据质量检测

Spring Boot 应用中通过 YARN 来调用 Spark 作业的来执行数据质检。这是一个非常经典的数据质量检测、数据优化的常用架构,将Web服务/业务处理(Spring Boot)与大数据质检(Spark on YARN)解耦。 核心架构图 首先,通过一张图来理解整个流程的架构: 整个流程的核心在于,…