文章目录
阻塞和非阻塞 IO 是 Linux 驱动开发中常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。下面来学习一下阻塞和非阻塞 IO,及如何在驱动程序中处理阻塞与非阻塞,如何在驱动程序使用等待队列和 poll 机制。
1. 阻塞和非阻塞 IO
1.1 阻塞IO
应用程序对驱动进行操作,如果获取不到驱动中的设备资源,会将应用程序对应的线程挂起,直到驱动中设备资源可用,下面图示,应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序
应用程序使用阻塞IO方式访问驱动
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
1.2 非阻塞IO
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。
应用程序通过非阻塞IO方式访问驱动
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
2.等待队列
2.1 等待队列头
Linux中提供等待队列(wait queue)来实现对阻塞进程进行唤醒的工作,在驱动中使用等待队列,必须先定义并初始化等待队列头,头文件位置为include/linux/wait.h
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化。
2.2 等待队列项
等待队列头就相当于一个等待队列的头部,每一个访问设备的进程都是一个等待队列项,当设备不可以用时将这些进程对应的队列项添加到等待队列头中,即添加到等待队列中,结构体 wait_queue_t 表示等待队列项,结构体内容如下:
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
使用宏 DECLARE_WAITQUEUE
定义并初始化一个等待队列项,宏的内容如下:
DECLARE_WAITQUEUE(name, tsk)
name是等待队列项的名字,tsk表示这个任务属于哪个进程,一般设置为current,表示当前进程
2.3 从等待队列头添加/删除队列项
当设备不可以访问时,需要将等待队列项添加到创建的等待队列头中,只有添加到等待队列头之后进程才可以进行休眠状态,当设备可以访问时将进程对应的队列项从等待队列头中移除即可,
2.3.1 等待队列头添加队列项的API函数
void add_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
-
q:等待队列项要加入的等待队列头
-
wait:要加入的等待队列项
-
返回值:无
2.3.2 移除等待队列项
void remove_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
-
q: 要删除的等待队列项所处的等待队列头。
-
wait:要删除的等待队列项。
-
返回值:无。
3.等待唤醒
当设备可以使用的时候要唤醒进入休眠状态的进程,唤醒函数:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
参数 q 就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。
4.等待事件
主动唤醒的同时,也可以设置等待队列等待某个事件,当被 wake_up_interruptible唤醒之后,自己再检查condition条件是否满足,满足则唤醒,否则继续休眠
函数 | 作用 |
---|---|
wait_event(wq, condition) | 等待以 wq 为等待队列头的等待队列被唤醒,前 提是 condition 条件必须满足(为真),否则一直阻 塞 。 此 函 数 会 将 进 程 设 置 为 TASK_UNINTERRUPTIBLE 状态 |
wait_event_timeout(wq, condition, timeout) | 功能和 wait_event 类似,但是此函数可以添加超 时时间,以 jiffies 为单位。此函数有返回值,果返回 0 的话表示超时时间到,而且 condition 为假。为 1 的话表示 condition 为真,也就是条 件满足了。 |
wait_event_interruptible(wq, condition) | 与 wait_event 函数类似,但是此函数将进程设置 为 TASK_INTERRUPTIBLE,就是可以被信号打 断。 |
wait_event_interruptible_timeout(wq, condition, timeout) | 与 wait_event_timeout 函数类似,此函数也将进 程设置为 TASK_INTERRUPTIBLE,可以被信号 打断。 |
文章推荐:https://blog.csdn.net/qingkongyeyue/article/details/76302536
5.轮询
应用程序使用轮询的方式对于驱动进行非阻塞访问,应用程序的poll,epoll和select函数用来处理轮询,通过这三个函数查询设备是否可以操作,如果可以操作则向设备中写入或者读取数据,当应用程序调用这三个函数会调用驱动中的poll函数,所以需要在驱动中实现poll函数
5.1 select函数
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
-
nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1
-
readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,这三个参数都是 fd_set 类型的, fd_set 类型变量的每一个位都代表了一个文件描述符。
readfds
用于监视指定描述符集的读变化,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取
。如果没有文件可以读取,那么就会根据timeout 参数来判断是否超时
。可以将readfs设置为 NULL
,表示不关心任何文件的读变化。 writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。 exceptfds 用于监视这些文件的异常。 -
fd_set变量的操作函数
void FD_ZERO(fd_set *set) /*将set的所有位的清0*/
void FD_SET(int fd, fd_set *set)/*将set变量某一个位置1*/
void FD_CLR(int fd, fd_set *set)/*将set变量某一个位置0*/
int FD_ISSET(int fd, fd_set *set)/*测试文件fd是否属于某个集合*/
-
timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示:
struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* 微妙 */ };
当 timeout 为 NULL 的时候就表示无限期的等待。
-
返回值: 0,表示的话就表示超时发生,没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。
5.1.1 伪代码
void main(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
break;
}
}
5.2 poll函数
在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制, Linux 应用程序中 poll 函数原型如下所示:
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
函数参数和返回值含义如下:
-
fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的, pollfd 结构体如下所示:
struct pollfd { int fd; /* 文件描述符 */ short events; /* 请求的事件 */ short revents; /* 返回的事件 */ };
fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents 返回 0。 events 是要监视的事件,可监视的事件类型如下所示:
POLLIN 有数据可以读取。 POLLPRI 有紧急的数据需要读取。 POLLOUT 可以写数据。 POLLERR 指定的文件描述符发生错误。 POLLHUP 指定的文件描述符挂起。 POLLNVAL 无效的请求。 POLLRDNORM 等同于 POLLIN
revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件。
-
nfds:poll函数要监视的文件描述符的数量
-
timeout:超时时间,单位为ms
-
返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型
5.2.1 伪代码
void main(void)
{
int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */
......
/* 读取数据 */
......
} else if (ret == 0) { /* 超时 */
......
} else if (ret < 0) { /* 错误 */
......
}
}
5.3 epoll函数
selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄, epoll_create 函数原型如下:
int epoll_create(int size)
-
size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
-
返回值: epoll 句柄,如果为-1 的话表示创建失败。
epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事件, epoll_ctl 函数原型如下所示:
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
-
epfd:要操作的epoll句柄,由epoll_create创建
-
op:表示要对于epfd进行的操作
EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。 EPOLL_CTL_MOD 修改参数 fd 的 event 事件。 EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。
-
fd:要监视的文件描述符
-
event:要监视的事件类型,为epoll_event结构体类型指针,epoll_event结构体类型如下所示
struct epoll_event { uint32_t events; /* epoll 事件 */ epoll_data_t data; /* 用户数据 */ };
结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示
EPOLLIN 有数据可以读取。 EPOLLOUT 可以写数据。 EPOLLPRI 有紧急的数据需要读取。 EPOLLERR 指定的文件描述符发生错误。 EPOLLHUP 指定的文件描述符挂起。 EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。 EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将 fd 重新添加到 epoll 里面。
上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件
-
返回值:0,成功,-1:失败,并且设置 errno 的值为相应的错误码
5.3.1 epoll_wait
一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生,类似 select 函数。 epoll_wait 函数原型如下所示:
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
-
epfd:前面创建的epfd
-
events:指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件
-
maxevents: events 数组大小,必须大于 0。
-
timeout: 超时时间,单位为 ms。
-
返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量。
6.Linux驱动下的poll函数
应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序
file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函数, poll 函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
- filp: 要打开的设备文件(文件描述符)。-
- wait: 结构体 poll_table_struct 类型指针, 由应用程序传递进来的。一般将此参数传递给
- poll_wait 函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN,普通数据可读
需要在驱动程序的 poll 函数中调用 poll_wait 函数, poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中, poll_wait 函数原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
参数 wait_address 是要添加到 poll_table 中的等待队列头,参数 p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数。
7.阻塞IO实验
在probe函数中初始化等待队列头,在中断处理程序中使用wake_up唤醒等待队列,应用程序调用read函数时,如果驱动还在休眠状态,则阻塞,等中断程序唤醒,且驱动中read函数wait_event_interruptible的第二个值为真时,则解除休眠,处于运行状态
7.1 驱动程序
1.修改设备树,在根结点下添加
btn-gpio {
#address-cells = <1>;
#size-cells = <1>;
compatible = "imx-btn";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
2.在pinctrl结点下添加
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
执行make dtbs
之后拷贝到tftboot
下
驱动代码
blockio.h
#ifndef _BLOCK_IO_H
#define _BLOCK_IO_H
#include <linux/types.h> /*设备号所在头文件*/
#include <linux/module.h> /*内核模块声明的相关函数*/
#include <linux/init.h> /*module_init和module_exit*/
#include <linux/kernel.h> /*内核的各种函数*/
#include <asm/io.h> /*readl函数*/
#include <linux/cdev.h> /*cdev*/
#include <linux/device.h> /*class & device*/
#include <linux/fs.h>
#include <asm/uaccess.h> /*copy_from_user*/
#include <linux/gpio.h> /*gpio fileoperation*/
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/signal.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <linux/compat.h>
#include <linux/sched.h>
#define DEVICE_NAME "btn_driver"
#define DEVICE_CNT 1
#define LED_ON 1
#define LED_OFF 0
struct btn_data
{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
int irq;
int gpio_num;
struct device_node* dev_node;
struct timer_list timer; /*定时器*/
wait_queue_head_t wait_queue_head;
};
struct btn_data *data;
#endif
blockio.c
#include "blockio.h"
static irqreturn_t btn_interrupt(int irq, void *handle)
{
//硬件消抖
mod_timer(&data->timer, jiffies +msecs_to_jiffies(10));
wake_up(&(data->wait_queue_head));
return IRQ_HANDLED;
}
static void btn_timer_func(unsigned long handle)
{
//printk("%s\n",__func__);
}
//cat
static ssize_t btn_status_show(struct device *dev,struct device_attribute *attr, char *buf)
{
int ret;
ret = gpio_get_value(data->gpio_num);
printk("%s:ret=%d\n",__func__,ret);
return sprintf(buf, "%d\n", ret);
}
//声明btn_status文件节点
static DEVICE_ATTR(btn_status, S_IRUSR, btn_status_show,NULL);
static struct attribute *atk_imx6ul_btn_sysfs_attrs[] = {
&dev_attr_btn_status.attr,
NULL,
};
static const struct attribute_group dev_attr_grp = {
.attrs = atk_imx6ul_btn_sysfs_attrs,
NULL,
};
static int imx6ul_btn_open(struct inode *inode, struct file *file)
{
file->private_data = data;
return 0;
}
static ssize_t imx6ul_btn_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
int ret;
int val;
char *readbuf = kzalloc(sizeof(char), GFP_KERNEL);
//加入等待队列,等待按键按下才读
ret = wait_event_interruptible(data->wait_queue_head,!gpio_get_value(data->gpio_num));
if(ret)
{
printk("%s:wait_event_interruptible failed\n",__func__);
}
val = gpio_get_value(data->gpio_num);
//将val格式化为字符串
sprintf(readbuf,"%d\n",val);
ret = copy_to_user(buf,readbuf,1);
if(ret == 0)
{
}else
{
}
return 0;
}
static ssize_t imx6ul_btn_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
return 0;
}
static int imx6ul_btn_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "imx6ul_btn_close\n");
//删除异步通知
return 0;
}
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.read = imx6ul_btn_read,
.write = imx6ul_btn_write,
.open = imx6ul_btn_open,
.release = imx6ul_btn_close,
};
static int btn_parse_dt(void)
{
int ret;
/*1.获取设备树中compatible属性的字符串值imx-btn*/
data->dev_node = of_find_compatible_node(NULL,NULL,"imx-btn");
if(data->dev_node == NULL)
{
printk("led device node find failed\n");
return -1;
}
/*2.获取gpio编号,将节点中的“key-gpio”属性值转换为对应的gpio编号。*/
data->gpio_num = of_get_named_gpio(data->dev_node, "key-gpio", 0);
if(data->gpio_num < 0)
{
printk("failed to get gpio\n");
return -1;
}
/*3.申请gpio管脚*/
ret = gpio_request(data->gpio_num, "btn-gpio");
if(ret != 0)
{
printk("gpio request failed\n");
return -1;
}
/*4.设置gpio为输入*/
ret = gpio_direction_input(data->gpio_num);
if(ret != 0)
{
printk("gpio direction input failed\n");
return -1;
}
/*5.设置gpio为中断gpio,获取gpio的中断号*/
data->irq = gpio_to_irq(data->gpio_num);
ret = devm_request_threaded_irq(data->device, data->irq, NULL,
btn_interrupt,IRQ_TYPE_EDGE_BOTH| IRQF_ONESHOT,"btn-irq", data);
if (ret) {
printk("Failed to register interrupt\n");
return -1;
}
disable_irq(data->irq);
return 0;
}
static int btn_driver_probe(struct platform_device *pdev)
{
u32 ret;
data = devm_kzalloc(&pdev->dev,sizeof(struct btn_data), GFP_KERNEL);
if(data == NULL)
{
printk(KERN_ERR"kzalloc data failed\n");
return -ENOMEM;
}
/*2.字符设备驱动框架那一套*/
/*2.1 之前定义了主设备号*/
if(data->major)
{
/*选择次设备号*/
data->devid = MKDEV(data->major,0);
/*注册设备号*/
ret = register_chrdev_region(data->devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0)
{
printk("register_chrdev_region faibtn\n");
return ret;
}
}else
{
/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
alloc_chrdev_region(&data->devid, 0, DEVICE_CNT, DEVICE_NAME); /* 申请设备号 */
data->major = MAJOR(data->devid); /* 获取分配号的主设备号 */
data->minor = MINOR(data->devid); /* 获取分配号的次设备号 */
}
data->cdev.owner = THIS_MODULE;
cdev_init(&data->cdev,&btn_fops);
/*自动创建设备结点,在/dev目录下体现*/
ret = cdev_add(&data->cdev,data->devid,DEVICE_CNT);
if(ret < 0)
{
printk("cdev_add device faibtn\n");
goto fail_cdev_add;
}
data->class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(data->class))
{
printk("class creat faibtn\n");
goto fail_class_create;
}
/*生成dev/DEVICE_NAME文件*/
data->device = device_create(data->class,NULL,data->devid,NULL,DEVICE_NAME);
if(IS_ERR(data->device))
{
printk("device class faibtn\n");
goto fail_device_create;
}
/*创建led_enable结点,直接通过系统调用来操作驱动*/
ret = sysfs_create_group(&data->device->kobj,&dev_attr_grp);
if(ret)
{
printk("failed to create sys files\n");
goto fail_sys_create;
}
/*1.设备树解析*/
ret = btn_parse_dt();
if (ret < 0) {
printk("btn parse error");
return ret;
}
//struct data *device = dev_get_drvdata(dev);
/*初始化定时器,工作队列*/
//INIT_WORK(&data.btn_work,btn_exchange_work);
setup_timer(&data->timer,btn_timer_func,(unsigned long)data);//最后一个值可以传指针
init_waitqueue_head(&(data->wait_queue_head));
enable_irq(data->irq);
/*激活定时器,add_timer不激活定时器*/
mod_timer(&data->timer, jiffies +msecs_to_jiffies(0));
printk("%s:probe success\n",__func__);
return 0;
fail_cdev_add:
unregister_chrdev_region(data->devid,DEVICE_CNT);
return -1;
fail_class_create:
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
return -1;
fail_device_create:
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
class_destroy(data->class);
return -1;
fail_sys_create:
sysfs_remove_group(&data->device->kobj,&dev_attr_grp);
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
class_destroy(data->class);
return -1;
}
static int btn_driver_remove(struct platform_device *pdev)
{
//依赖device,先删除
sysfs_remove_group(&data->device->kobj, &dev_attr_grp);
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
/*依赖于class所以先删除*/
device_destroy(data->class, data->devid);
class_destroy(data->class);
//删除定时器
del_timer_sync(&data->timer);
gpio_free(data->gpio_num);
return 0;
}
/* 匹配列表,btn_of_match中的compatible与设备树中的
* compatible匹配,匹配成功则跑probe函数
*/
static const struct of_device_id btn_of_match[] = {
{ .compatible = "imx-btn" },
{ /* Sentinel */ }
};
/*
* platform 平台驱动结构体
*/
static struct platform_driver btn_driver = {
.driver = {
.name = "imx-btn",
.of_match_table = btn_of_match,
},
.probe = btn_driver_probe,
.remove = btn_driver_remove,
};
module_platform_driver(btn_driver);
MODULE_AUTHOR("fib");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("btn driver of atk imx6ull");
Makefile:
KERNELDIR := /home/klz/linux/linux-4.1.15
CURRENT_PATH := $(shell pwd)
obj-m := noblockio.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
sudo cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
执行make && make copy
拷贝到根文件系统下
应用程序:
blockIoApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"
static int fd = 0; /* 文件描述符 */
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int flags = 0;
char *filename;
int ret = 0;
unsigned char buf[1];
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while(1) {
ret = read(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("read buf is unvailed\n");
}
else
{
buf[0] = atoi((const char*)&buf[0]);
printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
}
}
close(fd);
return 0;
}
8.非阻塞IO实验
非阻塞IO需要在驱动中实现poll函数,在应用程序中调用poll,select函数实际上都会调用到底层驱动的poll函数,当没有数据可读时,返回错误码
8.1驱动代码
设备树同上
驱动代码:
noblockio.h
#ifndef _BLOCK_IO_H
#define _BLOCK_IO_H
#include <linux/types.h> /*设备号所在头文件*/
#include <linux/module.h> /*内核模块声明的相关函数*/
#include <linux/init.h> /*module_init和module_exit*/
#include <linux/kernel.h> /*内核的各种函数*/
#include <asm/io.h> /*readl函数*/
#include <linux/cdev.h> /*cdev*/
#include <linux/device.h> /*class & device*/
#include <linux/fs.h>
#include <asm/uaccess.h> /*copy_from_user*/
#include <linux/gpio.h> /*gpio fileoperation*/
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/signal.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <linux/compat.h>
#include <linux/sched.h>
#include <linux/poll.h>
#define DEVICE_NAME "no_blockio"
#define DEVICE_CNT 1
struct btn_data
{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
int irq;
int gpio_num;
struct device_node* dev_node;
struct timer_list timer; /*定时器*/
wait_queue_head_t wait_queue_head;
};
struct btn_data *data;
#endif
noblockio.c
#include "noblockio.h"
static irqreturn_t btn_interrupt(int irq, void *handle)
{
//硬件消抖
mod_timer(&data->timer, jiffies +msecs_to_jiffies(10));
if(!gpio_get_value(data->gpio_num))
{
wake_up(&(data->wait_queue_head));
}
return IRQ_HANDLED;
}
static void btn_timer_func(unsigned long handle)
{
//printk("%s\n",__func__);
}
//cat
static ssize_t btn_status_show(struct device *dev,struct device_attribute *attr, char *buf)
{
int ret;
ret = gpio_get_value(data->gpio_num);
printk("%s:ret=%d\n",__func__,ret);
return sprintf(buf, "%d\n", ret);
}
//声明btn_status文件节点
static DEVICE_ATTR(btn_status, S_IRUSR, btn_status_show,NULL);
static struct attribute *atk_imx6ul_btn_sysfs_attrs[] = {
&dev_attr_btn_status.attr,
NULL,
};
static const struct attribute_group dev_attr_grp = {
.attrs = atk_imx6ul_btn_sysfs_attrs,
NULL,
};
static int imx6ul_btn_open(struct inode *inode, struct file *file)
{
file->private_data = data;
return 0;
}
static ssize_t imx6ul_btn_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
int ret;
int val;
char *readbuf = kzalloc(sizeof(char), GFP_KERNEL);
if(file->f_flags & O_NONBLOCK)//如果为非阻塞访问
{
if(!gpio_get_value(data->gpio_num))
{
//按键没有按下则返回其他值
return -EAGAIN;
}
}
else//阻塞访问
{
//加入等待队列,等待按键按下才读
ret = wait_event_interruptible(data->wait_queue_head,!gpio_get_value(data->gpio_num));
if(ret)
{
printk("%s:wait_event_interruptible failed\n",__func__);
}
}
val = gpio_get_value(data->gpio_num);
//将val格式化为字符串
sprintf(readbuf,"%d\n",val);
ret = copy_to_user(buf,readbuf,1);
if(ret == 0)
{
}else
{
}
return 0;
}
static ssize_t imx6ul_btn_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
return 0;
}
static int imx6ul_btn_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "imx6ul_btn_close\n");
return 0;
}
/*
* @description : poll 函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
*/
static unsigned int imx6ul_btn_poll(struct file *filp,struct poll_table_struct *wait)
{
unsigned int mask = 0;//返回的资源状态
//将等待队列头添加到 poll_table 中
poll_wait(filp,&data->wait_queue_head,wait);
//按键按下表示有数据可读
if(!gpio_get_value(data->gpio_num))
{
mask = POLLIN | POLLRDNORM;//返回资源状态
}
return mask;
}
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.read = imx6ul_btn_read,
.write = imx6ul_btn_write,
.open = imx6ul_btn_open,
.poll = imx6ul_btn_poll,
.release = imx6ul_btn_close,
};
static int btn_parse_dt(void)
{
int ret;
/*1.获取设备树中compatible属性的字符串值imx-btn*/
data->dev_node = of_find_compatible_node(NULL,NULL,"imx-btn");
if(data->dev_node == NULL)
{
printk("led device node find failed\n");
return -1;
}
/*2.获取gpio编号,将节点中的“key-gpio”属性值转换为对应的gpio编号。*/
data->gpio_num = of_get_named_gpio(data->dev_node, "key-gpio", 0);
if(data->gpio_num < 0)
{
printk("failed to get gpio\n");
return -1;
}
/*3.申请gpio管脚*/
ret = gpio_request(data->gpio_num, "btn-gpio");
if(ret != 0)
{
printk("gpio request failed\n");
return -1;
}
/*4.设置gpio为输入*/
ret = gpio_direction_input(data->gpio_num);
if(ret != 0)
{
printk("gpio direction input failed\n");
return -1;
}
/*5.设置gpio为中断gpio,获取gpio的中断号*/
data->irq = gpio_to_irq(data->gpio_num);
ret = devm_request_threaded_irq(data->device, data->irq, NULL,
btn_interrupt,IRQ_TYPE_EDGE_BOTH| IRQF_ONESHOT,"btn-irq", data);
if (ret) {
printk("Failed to register interrupt\n");
return -1;
}
disable_irq(data->irq);
return 0;
}
static int btn_driver_probe(struct platform_device *pdev)
{
u32 ret;
data = devm_kzalloc(&pdev->dev,sizeof(struct btn_data), GFP_KERNEL);
if(data == NULL)
{
printk(KERN_ERR"kzalloc data failed\n");
return -ENOMEM;
}
/*2.字符设备驱动框架那一套*/
/*2.1 之前定义了主设备号*/
if(data->major)
{
/*选择次设备号*/
data->devid = MKDEV(data->major,0);
/*注册设备号*/
ret = register_chrdev_region(data->devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0)
{
printk("register_chrdev_region faibtn\n");
return ret;
}
}else
{
/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
alloc_chrdev_region(&data->devid, 0, DEVICE_CNT, DEVICE_NAME); /* 申请设备号 */
data->major = MAJOR(data->devid); /* 获取分配号的主设备号 */
data->minor = MINOR(data->devid); /* 获取分配号的次设备号 */
}
data->cdev.owner = THIS_MODULE;
cdev_init(&data->cdev,&btn_fops);
/*自动创建设备结点,在/dev目录下体现*/
ret = cdev_add(&data->cdev,data->devid,DEVICE_CNT);
if(ret < 0)
{
printk("cdev_add device faibtn\n");
goto fail_cdev_add;
}
data->class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(data->class))
{
printk("class creat faibtn\n");
goto fail_class_create;
}
/*生成dev/DEVICE_NAME文件*/
data->device = device_create(data->class,NULL,data->devid,NULL,DEVICE_NAME);
if(IS_ERR(data->device))
{
printk("device class faibtn\n");
goto fail_device_create;
}
/*创建led_enable结点,直接通过系统调用来操作驱动*/
ret = sysfs_create_group(&data->device->kobj,&dev_attr_grp);
if(ret)
{
printk("failed to create sys files\n");
goto fail_sys_create;
}
/*1.设备树解析*/
ret = btn_parse_dt();
if (ret < 0) {
printk("btn parse error");
return ret;
}
//struct data *device = dev_get_drvdata(dev);
/*初始化定时器,工作队列*/
//INIT_WORK(&data.btn_work,btn_exchange_work);
setup_timer(&data->timer,btn_timer_func,(unsigned long)data);//最后一个值可以传指针
init_waitqueue_head(&(data->wait_queue_head));
enable_irq(data->irq);
/*激活定时器,add_timer不激活定时器*/
mod_timer(&data->timer, jiffies +msecs_to_jiffies(0));
printk("%s:probe success\n",__func__);
return 0;
fail_cdev_add:
unregister_chrdev_region(data->devid,DEVICE_CNT);
return -1;
fail_class_create:
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
return -1;
fail_device_create:
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
class_destroy(data->class);
return -1;
fail_sys_create:
sysfs_remove_group(&data->device->kobj,&dev_attr_grp);
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
class_destroy(data->class);
return -1;
}
static int btn_driver_remove(struct platform_device *pdev)
{
//依赖device,先删除
sysfs_remove_group(&data->device->kobj, &dev_attr_grp);
cdev_del(&data->cdev);
unregister_chrdev_region(data->devid,DEVICE_CNT);
/*依赖于class所以先删除*/
device_destroy(data->class, data->devid);
class_destroy(data->class);
//删除定时器
del_timer_sync(&data->timer);
gpio_free(data->gpio_num);
return 0;
}
/* 匹配列表,btn_of_match中的compatible与设备树中的
* compatible匹配,匹配成功则跑probe函数
*/
static const struct of_device_id btn_of_match[] = {
{ .compatible = "imx-btn" },
{ /* Sentinel */ }
};
/*
* platform 平台驱动结构体
*/
static struct platform_driver btn_driver = {
.driver = {
.name = "imx-btn",
.of_match_table = btn_of_match,
},
.probe = btn_driver_probe,
.remove = btn_driver_remove,
};
module_platform_driver(btn_driver);
MODULE_AUTHOR("fib");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("btn driver of atk imx6ull");
应用程序为:no_blockApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"
static int fd = 0; /* 文件描述符 */
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int flags = 0;
char *filename;
int ret = 0;
fd_set readfds; /*读操作文件描述符*/
struct pollfd fds;
struct timeval timeout; /* 超时结构体 */
unsigned char buf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
//传入第三个参数为0,调用select
if(atoi(argv[2]) == 0)
{
while(1)
{
FD_ZERO(&readfds);//清空读文件操作符使用位为0
FD_SET(fd,&readfds);//将fd添加到读文件操作符中
/*构建超时时间*/
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd+1,&readfds,NULL,NULL,&timeout);
switch(ret)
{
case 0://超时
{
printf("select timeout\n");
break;
}
case -1://错误
{
printf("select error\n");
break;
}
default:
{
if(FD_ISSET(fd,&readfds))//判断fd是否需要读集合
{
ret = read(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("read buf is unvailed\n");
}
else
{
buf[0] = atoi((const char*)&buf[0]);
printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
}
}
break;
}
}
}
}
else if(atoi(argv[2]) == 1)
{
fds.fd = fd;//要检测的文件描述符
fds.events = POLLIN;
while(1)
{
ret = poll(&fds,1,500);//监视的文件描述符数量为1,超时时间为500ms
switch(ret)
{
case 0://超时
{
printf("poll timeout\n");
break;
}
case -1://错误
{
printf("poll error\n");
break;
}
default:
{
ret = read(fd,buf,sizeof(buf));
if(ret < 0)
{
printf("read buf is unvailed\n");
}
else
{
buf[0] = atoi((const char*)&buf[0]);
printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
}
break;
}
}
}
}
close(fd);
return 0;
}
9.测试
./no_blockApp /dev/no_blockio 0 //调用select
./no_blockApp /dev/no_blockio 1 //调用poll