抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1.阻塞与非阻塞IO

1.1阻塞与非阻塞简介

这里的“IO”并不是我们学习 STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。

阻塞:

  • 当资源不可用的时候,应用程序就会挂起。

  • 当资源可用的时候,唤醒任务。

  • 应用程序使用open打开驱动文件,默认是阻塞方式打开。

1
2
3
4
5
6
//应用程序阻塞读取数据
int fd;
int data = 0;

fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

非阻塞:

  • 当资源不可用的时候,应用程序轮询查看,或放弃。
  • 会有超时处理机制。
  • 程旭在使用open打开驱动文件的时候,使用O_NONBLOCK。
1
2
3
4
5
6
//应用程序非阻塞读取数据
int fd;
int data = 0;

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

1.2等待队列

1.2.1等待队列头

当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。

Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头。

1
2
3
4
5
6
>//wait_queue_head_t 结构体
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

定义以后使用init_waitqueue_head(wait_queue_head_t *q )函数或者使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化。

1.2.2等待对列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。

1
2
3
4
5
6
7
8
//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 定义并初始化一个等待队列项,宏的内容如下:

1
DECLARE_WAITQUEUE(name, tsk)  
  • name 就是等待队列项的名字。
  • tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current , 在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。

1.2.3将队列项添加/移除等待队列头

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可,等待队列项添加 API 函数如下:

1
2
void add_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
  • q: 等待队列项要加入的等待队列头。wait:要加入的等待队列项。

等待队列项移除 API 函数如下:

1
2
void remove_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
  • q: 要删除的等待队列项所处的等待队列头。wait:要删除的等待队列项。

1.2.4等待唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:

1
2
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
  • 参数 q 就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。

1.2.5等待事件

除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。

函数 描述
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,可以被信号打断。

1.3轮询

  • 如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。

  • poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。

  • 当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。

select函数

1
2
3
4
5
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
  • nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1。

  • readfds、 writefds 和 exceptfds:这三个指针指向描述符集合 。readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。可以将 readfs设置为 NULL,表示不关心任何文件的读变化。 writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。 exceptfds 用于监视这些文件的异常。

现在要从一个设备文件中读取数据,那么就可以定义一个 fd_set 变量,这个变量要传递给参数 readfds。当我们定义好一个 fd_set 变量以后可以使用如下所示几个宏进行操作:

1
2
3
4
void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)

FD_ZERO 用于将 fd_set 变量的所有位都清零, FD_SET 用于将 fd_set 变量的某个位置 1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。 FD_CLR 用于将 fd_set变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。 FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。

  • timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示:
1
2
3
4
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
}

当 timeout 为 NULL 的时候就表示无限期的等待。

poll函数

在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数, poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制, Linux 应用程序中 poll 函数原型如下所示:

1
2
3
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
  • fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的, pollfd 结构体如下所示:
1
2
3
4
5
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
}
  • fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents返回 0。 events 是要监视的事件,可监视的事件类型如下所示:
1
2
3
4
5
6
7
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN
  • revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件

  • nfds:poll函数要监视的文件描述符数量

  • timeout:超时时间,单位为 ms

  • 返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型

epoll 函数

传统的 selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄, epoll_create 函数原型如下:

1
int epoll_create(int size)
  • size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。返回值: epoll 句柄,如果为-1 的话表示创建失败。

epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事件, epoll_ctl 函数原型如下所示:

1
2
3
4
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
  • epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。

  • op: 表示要对 epfd(epoll 句柄)进行的操作,可以设置为:

1
2
3
EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。
  • fd:要监视的文件描述符。

  • event: 要监视的事件类型,为 epoll_event 结构体类型指针, epoll_event 结构体类型如下所示:

1
2
3
4
struct epoll_event {
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户数据 */
}

结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示:

1
2
3
4
5
6
7
8
EPOLLIN 有数据可以读取。
EPOLLOUT 可以写数据。
EPOLLPRI 有紧急的数据需要读取。
EPOLLERR 指定的文件描述符发生错误。
EPOLLHUP 指定的文件描述符挂起。
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将
fd 重新添加到 epoll 里面。

上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件。返回值: 0,成功; -1,失败,并且设置 errno 的值为相应的错误码。

一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生,类似 select 函数。 epoll_wait 函数原型如下所示:

1
2
3
4
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
  • epfd: 要等待的 epoll。

  • events: 指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件。

  • maxevents: events 数组大小,必须大于 0。timeout: 超时时间,单位为 ms。

  • 返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量

epoll 更多的是用在大规模的并发服务器上,因为在这种场合下 select 和 poll 并不适合。当设计到的文件描述符(fd)比较少的时候就适合用 selcet 和 poll,本章我们就使用 sellect 和 poll 这两个函数。

1.4驱动里面的poll函数

当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函数, poll 函数原型如下所示:

1
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
  • filp: 要打开的设备文件(文件描述符)。

  • wait: 结构体 poll_table_struct 类型指针, 由应用程序传递进来的。一般将此参数传递给poll_wait

函数。

  • 返回值:向应用程序返回设备或者资源状态。

需要在驱动程序的 poll 函数中调用 poll_wait 函数, poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中, poll_wait 函数原型如下:

1
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 参数。

2.阻塞IO实验

在irq代码里面修改:

2.1驱动代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>

#define im6uirq_CNT 1 /* 设备号个数 */
#define im6uirq_NAME "imx6uirq" /* 名字 */

#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0XFF


struct irq_key_dev
{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[10]; //名字

irqreturn_t (*handler)(int,void *); //中断处理函数


struct tasklet_struct tasklet;
};


struct im6uirq_dev
{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */

struct irq_key_dev irq_key[KEY_NUM];

struct timer_list timer;

atomic_t keyvalue;
atomic_t releasekey;

wait_queue_head_t r_wait; //读等待队列头

};

struct im6uirq_dev im6uirq;

static int im6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &im6uirq;
return 0;
}
static int im6uirq_release(struct inode *inode,struct file *filp)
{

return 0;
}

static ssize_t im6uirq_write(struct file *filp,const char __user *buf,size_t count,loff_t *offt)
{


return 0;
}

static ssize_t im6uirq_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int ret;
unsigned char keyvalue;
unsigned char releasekey;
struct im6uirq_dev *dev = filp->private_data;

#if 0
//等待事件
wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey)); //等待按键有效
#endif

DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */
add_wait_queue(&dev->r_wait, &wait); /* 将等待队列添加到等待队列头 */
__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
schedule(); /* 进行一次任务切换 */
if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING); /* 将当前任务设置为运行状态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将对应的队列项从等待队列头删除 */

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) //有效按键
{
if (keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else{
goto data_error;
}

atomic_set(&dev->releasekey,0);
}
else {
goto data_error;
}

return 0;

wait_error:
set_current_state(TASK_RUNNING); /* 设置任务为运行态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */
return ret;

data_error:
return -EINVAL;

}

/* 1.操作集 */
static const struct file_operations im6uirq_fops =
{
.owner = THIS_MODULE,
.open = im6uirq_open,
.release = im6uirq_release,
.write = im6uirq_write,
.read = im6uirq_read,
};


//中断处理函数
static irqreturn_t key0_irq_handler(int irq,void *dev_id)
{
struct im6uirq_dev *dev = dev_id;



// dev->timer.data = (volatile unsigned long)dev_id;
// mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms

tasklet_schedule(&dev->irq_key[0].tasklet);

return IRQ_HANDLED;
}


/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct im6uirq_dev *dev = (struct im6uirq_dev *)arg;

value = gpio_get_value(dev->irq_key[0].gpio);

if (value == 0) //按下
{
atomic_set(&dev->keyvalue, dev->irq_key[0].value);
}else if (value == 1)
{
atomic_set(&dev->keyvalue, 0x80 | dev->irq_key[0].value);
atomic_set(&dev->releasekey,1);
}

//唤醒进程
if(atomic_read(&dev->releasekey))
{
wake_up(&dev->r_wait);
}
}

static void key_tasklet(unsigned long data)
{
struct im6uirq_dev *dev = (struct im6uirq_dev *)data;

dev->timer.data = (volatile unsigned long)data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
}

static int keyio_init(struct im6uirq_dev *dev)
{
int ret;
int i;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL)
{
printk("fail find nd\r\n");
ret = -EINVAL;
goto fail_nd;
}

for (i = 0; i < KEY_NUM; i++)
{
ret = dev->irq_key[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if (ret < 0){
printk("can't get key%d\r\n",i);
goto fail_get_gpio;
}
}

for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irq_key[i].name,0,sizeof(dev->irq_key[i].name)); /* 缓冲区清零 */
sprintf(dev->irq_key[i].name,"key%d",i);
gpio_request(dev->irq_key[i].gpio,dev->irq_key[i].name);
gpio_direction_input(dev->irq_key[i].gpio);

dev->irq_key[i].irqnum = gpio_to_irq(dev->irq_key[i].gpio); /* 获取中断号 */
#if 0
dev->irq_key[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
}

dev->irq_key[0].handler = key0_irq_handler;
dev->irq_key[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irq_key[i].irqnum,dev->irq_key[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irq_key[i].name,&im6uirq);

if (ret)
{
printk("request %d irq failed\r\n",dev->irq_key[i].irqnum);
ret = -EINVAL;
goto fail_requese_irq;
}
tasklet_init(&dev->irq_key[0].tasklet,key_tasklet,(unsigned long)dev);
}


init_timer(&dev->timer);
dev->timer.function = timer_func;

return 0;
fail_requese_irq:
for (i = 0; i < KEY_NUM; i++){
gpio_free(dev->irq_key[i].gpio);
}
fail_get_gpio:
fail_nd:
return ret;
}


static int __init im6uirq_init(void)
{
int ret;

/* 1.注册字符设备驱动*/
im6uirq.major = 0;
if (im6uirq.major) /* 定义了设备号 */
{
im6uirq.devid = MKDEV(im6uirq.major, 0);
register_chrdev_region(im6uirq.devid,im6uirq_CNT,im6uirq_NAME);
}
else{ /* 没有定义设备号 */
ret = alloc_chrdev_region(&im6uirq.devid,0,im6uirq_CNT,im6uirq_NAME);
im6uirq.major = MAJOR(im6uirq.devid); /* 获取分配号的主设备号 */
im6uirq.minor = MINOR(im6uirq.devid); /* 获取分配号的次设备号 */
}
printk("major = %d,minor = %d\r\n",im6uirq.major,im6uirq.minor);

if(ret < 0)
{
printk("fail devid\r\n");
goto fail_devid;
}

/* 2.初始化cdev*/
im6uirq.cdev.owner = THIS_MODULE;
cdev_init(&im6uirq.cdev,&im6uirq_fops);

/* 3.添加设备 */
ret = cdev_add(&im6uirq.cdev,im6uirq.devid,im6uirq_CNT);
if (ret < 0)
{
printk("fail cdevadd\r\n");
goto fail_cdevadd;
}

/* 4.创建类 */
im6uirq.class = class_create(THIS_MODULE,im6uirq_NAME);
if (IS_ERR(im6uirq.class))
{
printk("fail class\r\n");
ret = PTR_ERR(im6uirq.class);
goto fail_class;
}

/* 5.创建设备 */
im6uirq.device = device_create(im6uirq.class,NULL,im6uirq.devid,NULL,im6uirq_NAME);
if (IS_ERR(im6uirq.device))
{
printk("fail device\r\n");
ret = PTR_ERR(im6uirq.device);
goto fail_device;
}

//初始按键
ret = keyio_init(&im6uirq);
if (ret)
{
printk("fail keyio_init\r\n");
goto fail_keyio_init;
}

//初始化原子变量
atomic_set(&im6uirq.keyvalue,INVAKEY);
atomic_set(&im6uirq.releasekey,0);

//初始化等待队列头
init_waitqueue_head(&im6uirq.r_wait);



printk("all init suggess!!!\r\n");

return 0;

fail_keyio_init:

fail_device:
class_destroy(im6uirq.class);
fail_class:
cdev_del(&im6uirq.cdev);
fail_cdevadd:
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
fail_devid:
return ret;
}

static void __exit im6uirq_exit(void)
{
int i = 0;
//1.释放中断
for (i = 0; i < KEY_NUM; i++)
{
free_irq(im6uirq.irq_key[i].irqnum,&im6uirq);
}

//2.释放gpio
for (i = 0; i < KEY_NUM; i++){
gpio_free(im6uirq.irq_key[i].gpio);
}

//3.删除定时器
del_timer_sync(&im6uirq.timer);

//注销字符设备驱动
cdev_del(&im6uirq.cdev);
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
device_destroy(im6uirq.class,im6uirq.devid);
class_destroy(im6uirq.class);

printk("im6uirq exit!\r\n");
}


module_init(im6uirq_init);
module_exit(im6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwl");

2.2应用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

#define CLOSE_CMD (_IO(0XEF, 0x1))
#define OPEN_CMD (_IO(0XEF, 0x2))
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /*设置周期*/

/* argc:应用参数个数
* argv[]:具体参数内容,字符串形式

* ./irqAPP /dev/imx6uirq
*/
int main(int argc, char *argv[])
{
int fd,ret;
char *filename;
unsigned char data;
if (argc != 2)
{
printf("Error Usage!!!\r\n");
return -1;
}

filename = argv[1];

fd = open(filename,O_RDWR);
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}

while (1)
{
ret = read(fd,&data,sizeof(data));
if (ret < 0)
{

}
else
{
if (data)
{
printf("read data: %#x\r\n",data);
}
}

}

close(fd);
return 0;
}

2.3实验

加载驱动:

1
2
3
modprobe irq.ko

./irqApp /dev/imx6uirq &

查看cpu使用资源,可以看到cpu使用率为0%

1
top

卸载驱动:

1
2
3
ps   //查看进程号
kill -9 xx
rmmod irq.ko

3.非阻塞IO实验

3.1驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>

#define im6uirq_CNT 1 /* 设备号个数 */
#define im6uirq_NAME "imx6uirq" /* 名字 */

#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0XFF


struct irq_key_dev
{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[10]; //名字

irqreturn_t (*handler)(int,void *); //中断处理函数


struct tasklet_struct tasklet;
};


struct im6uirq_dev
{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */

struct cdev cdev; /* cdev */

struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */

struct irq_key_dev irq_key[KEY_NUM];

struct timer_list timer;

atomic_t keyvalue;
atomic_t releasekey;

wait_queue_head_t r_wait; //读等待队列头

};

struct im6uirq_dev im6uirq;




static int im6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &im6uirq;
return 0;
}
static int im6uirq_release(struct inode *inode,struct file *filp)
{

return 0;
}

static ssize_t im6uirq_write(struct file *filp,const char __user *buf,size_t count,loff_t *offt)
{


return 0;
}

static ssize_t im6uirq_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int ret;
unsigned char keyvalue;
unsigned char releasekey;
struct im6uirq_dev *dev = filp->private_data;

if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */
if(atomic_read(&dev->releasekey) == 0) /* 没有按键按下,返回-EAGAIN */
return -EAGAIN;
} else { /* 阻塞访问 */
/* 加入等待队列,等待被唤醒,也就是有按键按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
}

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) //有效按键
{
if (keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else{
goto data_error;
}

atomic_set(&dev->releasekey,0);
}
else {
goto data_error;
}

return 0;

wait_error:
return ret;

data_error:
return -EINVAL;

}

unsigned int im6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct im6uirq_dev *dev = (struct im6uirq_dev *)filp->private_data;

poll_wait(filp, &dev->r_wait, wait); /* 将等待队列头添加到poll_table中 */

//判断是否可读
if(atomic_read(&dev->releasekey)) { /* 按键按下 可读*/
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}

return mask;
}


/* 1.操作集 */
static const struct file_operations im6uirq_fops =
{
.owner = THIS_MODULE,
.open = im6uirq_open,
.release = im6uirq_release,
.write = im6uirq_write,
.read = im6uirq_read,
.poll = im6uirq_poll,
};


//中断处理函数
static irqreturn_t key0_irq_handler(int irq,void *dev_id)
{
struct im6uirq_dev *dev = dev_id;



// dev->timer.data = (volatile unsigned long)dev_id;
// mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms

tasklet_schedule(&dev->irq_key[0].tasklet);

return IRQ_HANDLED;
}


/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct im6uirq_dev *dev = (struct im6uirq_dev *)arg;

value = gpio_get_value(dev->irq_key[0].gpio);

if (value == 0) //按下
{
atomic_set(&dev->keyvalue, dev->irq_key[0].value);
}else if (value == 1)
{
atomic_set(&dev->keyvalue, 0x80 | dev->irq_key[0].value);
atomic_set(&dev->releasekey,1);
}

//唤醒进程
if(atomic_read(&dev->releasekey))
{
wake_up(&dev->r_wait);
}
}

static void key_tasklet(unsigned long data)
{
struct im6uirq_dev *dev = (struct im6uirq_dev *)data;

dev->timer.data = (volatile unsigned long)data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
}

static int keyio_init(struct im6uirq_dev *dev)
{
int ret;
int i;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL)
{
printk("fail find nd\r\n");
ret = -EINVAL;
goto fail_nd;
}

for (i = 0; i < KEY_NUM; i++)
{
ret = dev->irq_key[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if (ret < 0){
printk("can't get key%d\r\n",i);
goto fail_get_gpio;
}
}

for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irq_key[i].name,0,sizeof(dev->irq_key[i].name)); /* 缓冲区清零 */
sprintf(dev->irq_key[i].name,"key%d",i);
gpio_request(dev->irq_key[i].gpio,dev->irq_key[i].name);
gpio_direction_input(dev->irq_key[i].gpio);

dev->irq_key[i].irqnum = gpio_to_irq(dev->irq_key[i].gpio); /* 获取中断号 */
#if 0
dev->irq_key[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
}

dev->irq_key[0].handler = key0_irq_handler;
dev->irq_key[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irq_key[i].irqnum,dev->irq_key[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irq_key[i].name,&im6uirq);

if (ret)
{
printk("request %d irq failed\r\n",dev->irq_key[i].irqnum);
ret = -EINVAL;
goto fail_requese_irq;
}
tasklet_init(&dev->irq_key[0].tasklet,key_tasklet,(unsigned long)dev);
}


init_timer(&dev->timer);
dev->timer.function = timer_func;

return 0;
fail_requese_irq:
for (i = 0; i < KEY_NUM; i++){
gpio_free(dev->irq_key[i].gpio);
}
fail_get_gpio:
fail_nd:
return ret;
}


static int __init im6uirq_init(void)
{
int ret;

/* 1.注册字符设备驱动*/
im6uirq.major = 0;
if (im6uirq.major) /* 定义了设备号 */
{
im6uirq.devid = MKDEV(im6uirq.major, 0);
register_chrdev_region(im6uirq.devid,im6uirq_CNT,im6uirq_NAME);
}
else{ /* 没有定义设备号 */
ret = alloc_chrdev_region(&im6uirq.devid,0,im6uirq_CNT,im6uirq_NAME);
im6uirq.major = MAJOR(im6uirq.devid); /* 获取分配号的主设备号 */
im6uirq.minor = MINOR(im6uirq.devid); /* 获取分配号的次设备号 */
}
printk("major = %d,minor = %d\r\n",im6uirq.major,im6uirq.minor);

if(ret < 0)
{
printk("fail devid\r\n");
goto fail_devid;
}

/* 2.初始化cdev*/
im6uirq.cdev.owner = THIS_MODULE;
cdev_init(&im6uirq.cdev,&im6uirq_fops);

/* 3.添加设备 */
ret = cdev_add(&im6uirq.cdev,im6uirq.devid,im6uirq_CNT);
if (ret < 0)
{
printk("fail cdevadd\r\n");
goto fail_cdevadd;
}

/* 4.创建类 */
im6uirq.class = class_create(THIS_MODULE,im6uirq_NAME);
if (IS_ERR(im6uirq.class))
{
printk("fail class\r\n");
ret = PTR_ERR(im6uirq.class);
goto fail_class;
}

/* 5.创建设备 */
im6uirq.device = device_create(im6uirq.class,NULL,im6uirq.devid,NULL,im6uirq_NAME);
if (IS_ERR(im6uirq.device))
{
printk("fail device\r\n");
ret = PTR_ERR(im6uirq.device);
goto fail_device;
}

//初始按键
ret = keyio_init(&im6uirq);
if (ret)
{
printk("fail keyio_init\r\n");
goto fail_keyio_init;
}

//初始化原子变量
atomic_set(&im6uirq.keyvalue,INVAKEY);
atomic_set(&im6uirq.releasekey,0);

//初始化等待队列头
init_waitqueue_head(&im6uirq.r_wait);



printk("all init suggess!!!\r\n");

return 0;

fail_keyio_init:

fail_device:
class_destroy(im6uirq.class);
fail_class:
cdev_del(&im6uirq.cdev);
fail_cdevadd:
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
fail_devid:
return ret;
}

static void __exit im6uirq_exit(void)
{
int i = 0;
//1.释放中断
for (i = 0; i < KEY_NUM; i++)
{
free_irq(im6uirq.irq_key[i].irqnum,&im6uirq);
}

//2.释放gpio
for (i = 0; i < KEY_NUM; i++){
gpio_free(im6uirq.irq_key[i].gpio);
}

//3.删除定时器
del_timer_sync(&im6uirq.timer);

//注销字符设备驱动
cdev_del(&im6uirq.cdev);
unregister_chrdev_region(im6uirq.devid,im6uirq_CNT);
device_destroy(im6uirq.class,im6uirq.devid);
class_destroy(im6uirq.class);

printk("im6uirq exit!\r\n");
}


module_init(im6uirq_init);
module_exit(im6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwl");

3.2应用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"

#define CLOSE_CMD (_IO(0XEF, 0x1))
#define OPEN_CMD (_IO(0XEF, 0x2))
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /*设置周期*/

/* argc:应用参数个数
* argv[]:具体参数内容,字符串形式

* ./irqAPP /dev/imx6uirq
*/
int main(int argc, char *argv[])
{
fd_set readfds;
struct timeval timeout;

int fd,ret;
char *filename;
unsigned char data;
if (argc != 2)
{
printf("Error Usage!!!\r\n");
return -1;
}

filename = argv[1];

fd = open(filename,O_RDWR | O_NONBLOCK); //非阻塞打开
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}

while (1)
{
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/* 构造超时时间 */
timeout.tv_sec = 1;
timeout.tv_usec = 0; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("select timeout!\r\n");
break;
case -1: /* 错误 */
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) {
/* 读取错误 */
} else {
if (data)
printf("key value=%d\r\n", data);
}
}
break;
}
}
close(fd);
return 0;
}

3.3实验

同上。

评论