内核定时器+内核中断demo(正点原子笔记)

Linux 按键输入实验

原理

按键驱动和 LED 驱动原理上来讲基本都是一样的,都是操作 GPIO,只不过一个是读取GPIO 的高低电平,一个是从 GPIO 输出高低电平。本章我们实现按键输入,在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。在这里,这个保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。所以我们要对其进行保护,对于整形变量而言我们首选的就是原子操作,使用原子操作对变量进行赋值以及读取。

修改设备树文件

1、添加 pinctrl 节点

I.MX6U-ALPHA 开发板上的 KEY 使用了 UART1_CTS_B 这个 PIN,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点

1 pinctrl_key: keygrp {
2 fsl,pins = <
3 MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
4 >;
5 };

2、添加 KEY 设备节点

在根节点“/”下创建 KEY 节点,节点名为“key”,

1 key {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-key";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_key>;
7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8 status = "okay";
9 };

3、检查 PIN 是否被其他外设使用

UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

驱动

56 static int keyio_init(void)
57 {
58 keydev.nd = of_find_node_by_path("/key");
59 if (keydev.nd== NULL) {
60 return -EINVAL;
61 }
62 
63 keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);
64 if (keydev.key_gpio < 0) {
65 printk("can't get key0\r\n");
66 return -EINVAL;
67 }
68 printk("key_gpio=%d\r\n", keydev.key_gpio);
69 
70 /* 初始化 key 所使用的 IO */
71 gpio_request(keydev.key_gpio, "key0"); /* 请求 IO */
72 gpio_direction_input(keydev.key_gpio); /* 设置为输入 */
73 return 0;
74 }
75 
76 /*
77 * @description : 打开设备
78 * @param – inode : 传递给驱动的 inode
79 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
80 * 一般在 open 的时候将 private_data 指向设备结构体。
81 * @return : 0 成功;其他 失败
82 */
83 static int key_open(struct inode *inode, struct file *filp)
84 {
85 int ret = 0;
86 filp->private_data = &keydev; /* 设置私有数据 */
87 
88 ret = keyio_init(); /* 初始化按键 IO */
89 if (ret < 0) {
90 return ret;
91 }
92 
93 return 0;
94 }
95 
96 /*
97 * @description : 从设备读取数据
98 * @param - filp : 要打开的设备文件(文件描述符)
99 * @param - buf : 返回给用户空间的数据缓冲区
100 * @param - cnt : 要读取的数据长度
101 * @param - offt : 相对于文件首地址的偏移
102 * @return : 读取的字节数,如果为负值,表示读取失败
103 */
104 static ssize_t key_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
105 {
106 int ret = 0;
107 unsigned char value;
108 struct key_dev *dev = filp->private_data;
109
110 if (gpio_get_value(dev->key_gpio) == 0) { /* key0 按下 */
111 while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */
112 atomic_set(&dev->keyvalue, KEY0VALUE);
113 } else { /* 无效的按键值 */
114 atomic_set(&dev->keyvalue, INVAKEY);
115 }
116
117 value = atomic_read(&dev->keyvalue); /* 保存按键值 */
118 ret = copy_to_user(buf, &value, sizeof(value));
119 return ret;
120 }
121
122
123 /* 设备操作函数 */
124 static struct file_operations key_fops = {
125 .owner = THIS_MODULE,
126 .open = key_open,
127 .read = key_read,
128 };
129
130 /*
131 * @description : 驱动入口函数
132 * @param : 无
133 * @return : 无
134 */
135 static int __init mykey_init(void)
136 {
137 /* 初始化原子变量 */
138 atomic_set(&keydev.keyvalue, INVAKEY);
139
140 /* 注册字符设备驱动 */
141 /* 1、创建设备号 */
142 if (keydev.major) { /* 定义了设备号 */
143 keydev.devid = MKDEV(keydev.major, 0);
144 register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
145 } else { /* 没有定义设备号 */
146 alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME); 
147 keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */
148 keydev.minor = MINOR(keydev.devid); /* 获取分配号的次设备号 */
149 }
150 
151 /* 2、初始化 cdev */
152 keydev.cdev.owner = THIS_MODULE;
153 cdev_init(&keydev.cdev, &key_fops);
154 
155 /* 3、添加一个 cdev */
156 cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
157
158 /* 4、创建类 */
159 keydev.class = class_create(THIS_MODULE, KEY_NAME);
160 if (IS_ERR(keydev.class)) {
161 return PTR_ERR(keydev.class);
162 }
163
164 /* 5、创建设备 */
165 keydev.device = device_create(keydev.class, NULL, keydev.devid,
NULL, KEY_NAME);
166 if (IS_ERR(keydev.device)) {
167 return PTR_ERR(keydev.device);
168 }
169 
170 return 0;
171 }
172
173 /*
174 * @description : 驱动出口函数
175 * @param : 无
176 * @return : 无
177 */
178 static void __exit mykey_exit(void)
179 {
180 /* 注销字符设备驱动 */
181 cdev_del(&keydev.cdev); /* 删除 cdev */
182 unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */
183
184 device_destroy(keydev.class, keydev.devid);
185 class_destroy(keydev.class);
186 }
187
188 module_init(mykey_init);
189 module_exit(mykey_exit);


APP:

30 int main(int argc, char *argv[])
31 {
32 int fd, ret;
33 char *filename;
34 unsigned char keyvalue;
35 
36 if(argc != 2){
37 printf("Error Usage!\r\n");
38 return -1;
39 }
40
41 filename = argv[1];
42
43 /* 打开 key 驱动 */
44 fd = open(filename, O_RDWR);
45 if(fd < 0){
46 printf("file %s open failed!\r\n", argv[1]);
47 return -1;
48 }
49
50 /* 循环读取按键值数据! */
51 while(1) {
52 read(fd, &keyvalue, sizeof(keyvalue));
53 if (keyvalue == KEY0VALUE) { /* KEY0 */
54 printf("KEY0 Press, value = %#X\r\n", keyvalue);/* 按下 */
55 }
56 }
57
58 ret= close(fd); /* 关闭文件 */
59 if(ret < 0){
60 printf("file %s close failed!\r\n", argv[1]);
61 return -1;
62 }
63 return 0;
64 }

内核定时器实验

硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率,这个是可以设置的,单位是Hz。可以在menuconfig里面设置。

高节拍率和低节拍率的优缺点:

①、高节拍率会提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。
②、高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz 和 100Hz的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。根据自己的实际情况,选择合适的系统节拍率,默认100hz。

jiffies

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会
将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:

extern u64 __jiffy_data jiffies_64; #用于 64 位系统
 extern unsigned long volatile __jiffy_data jiffies    ;#用于32 位系统

 当我们访问 jiffies 的时候其实访问的是 jiffies_64 的低 32 位,使用 get_jiffies_64 这个函数可以获取 jiffies_64 的值。在 32 位的系统上读取 jiffies 的值,在 64 位的系统上 jiffes 和 jiffies_64表示一个变量,因此也可以直接读取 jiffies 的值。所以不管是 32 位的系统还是 64 位系统,都可以使用 jiffies。

绕回

HZ 表示每秒的节拍数,jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就
是系统运行时间,单位为秒。不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重
新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如 HZ 为最大
值 1000 的时候,32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要
5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。处理 32 位 jiffies 的绕回显得尤为重要,
Linux 内核提供了如表 50.1.1.1 所示的几个 API 函数来处理绕回:

 如果 unkown 超过 known 的话,time_after 函数返回真,否则返回假。如果 unkown 没有超过 known 的话 time_before 函数返回真,否则返回假。time_after_eq 函数和 time_after 函数类似,只是多了判断等于这个条件。同理,time_before_eq 函数和 time_before 函数也类似。

1 unsigned long timeout;
2 timeout = jiffies + (2 * HZ); /* 超时的时间点 */
3 
4 /*************************************
5 具体的代码
6 ************************************/
7 
8 /* 判断有没有超时 */
9 if(time_before(jiffies, timeout)) {
10 /* 超时未发生 */
11 } else {
12 /* 超时发生 */
13 }

为了方便开发,Linux 内核提供了几个 jiffies 和 ms、us、ns 之间的转换函数

 内核定时器

内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。Linux 内核使用 timer_list 结构体表示内核定时器:

struct timer_list {
 struct list_head entry;
 unsigned long expires; /* 定时器超时时间,单位是节拍数 */
 struct tvec_base *base;
 void (*function)(unsigned long); /* 定时处理函数 */
 unsigned long data; /* 要传递给 function 函数的参数 */
 int slack;
};

相关函数

1、init_timer 函数

init_timer 函数负责初始化 timer_list 类型变量

void init_timer(struct timer_list *timer)

2、add_timer 函数

add_timer 函数用于向 Linux 内核注册定时器

void add_timer(struct timer_list *timer)

3、del_timer 函数

del_timer 函数用于删除一个定时器

int del_timer(struct timer_list * timer)

4、del_timer_sync 函数

del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
del_timer_sync 不能使用在中断上下文中。

int del_timer_sync(struct timer_list *timer)

5mod_timer 函数

mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!

int mod_timer(struct timer_list *timer, unsigned long expires)
示例
1 struct timer_list timer; /* 定义定时器 */
2 
3 /* 定时器回调函数 */
4 void function(unsigned long arg)
5 { 
6 /* 
7 * 定时器处理代码
8 */
9 
10 /* 如果需要定时器周期性运行的话就使用 mod_timer
11 * 函数重新设置超时值并且启动定时器。
12 */
13 mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
14 }
15
16 /* 初始化函数 */
17 void init(void) 
18 {
19 init_timer(&timer); /* 初始化定时器 */
20
21 timer.function = function; /* 设置定时处理函数 */
22 timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
23 timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
24 
25 add_timer(&timer); /* 启动定时器 */
26 }
27
28 /* 退出函数 */
29 void exit(void)
30 {
31 del_timer(&timer); /* 删除定时器 */
32 /* 或者使用 */
33 del_timer_sync(&timer);
34 }

Linux 内核短延时函数

有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。

 Linux中断实验

Linux中断API函数

裸机实验里面中断的处理方法:
①、使能中断,初始化相应的寄存器。
②、注册中断服务函数,也就是向 irqTable 数组的指定标号处写入中断服务函数
②、中断发生以后进入 IRQ 中断服务函数,在 IRQ 中断服务函数在数组 irqTable 里面查找具体的中断处理函数,找到以后执行相应的中断处理函数。

1、中断号

每个中断都有一个中断号,通过中断号即可区分不同的中断。

2、request_irq 函数

在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断,request_irq
函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 。

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,如下表所示

name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将
dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

 这些标志可以通过“|”来实现多种组合

3、free_irq 函数

使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断;

void free_irq(unsigned int irq, void *dev)

4、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数:

irqreturn_t (*irq_handler_t) (int, void *)

第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,

返回值为 irqreturn_t 类型

10 enum irqreturn {
11 IRQ_NONE = (0 << 0),
12 IRQ_HANDLED = (1 << 0),
13 IRQ_WAKE_THREAD = (1 << 1),
14 };
15
16 typedef enum irqreturn irqreturn_t;

一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

5、中断使能与禁止函数

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32 的时候常说的关闭全局中断,这个时候可以使用如下两个函数:

local_irq_enable()
local_irq_disable()

假如 A 任务调用 local_irq_disable 关闭全局中断 10S,当关闭了 2S 的时候 B 任务开始运行,B 任务也调用 local_irq_disable 关闭全局中断 3S,3 秒以后 B 任务调用 local_irq_enable 函数将全局中断打开了。此时才过去 2+3=5 秒的时间,然后全局中断就被打开了,此时 A 任务要关闭 10S 全局中断的愿望就破灭了,然后 A 任务就“生气了”,结果很严重,可能系统都要被A 任务整崩溃。为了解决这个问题,B 任务不能直接简单粗暴的通过 local_irq_enable 函数来打开全局中断,而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下面两个函数:

local_irq_save(flags)
local_irq_restore(flags)

这两个函数是一对,local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态。

上半部与下半部

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部

②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

下半部机制

1.软中断
433 struct softirq_action
434 {
435 void (*action)(struct softirq_action *);
436 };

在 kernel/softirq.c 文件中一共定义了 10 个软中断,

static struct softirq_action softirq_vec[NR_SOFTIRQS];

NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:

enum
{
 HI_SOFTIRQ=0, /* 高优先级软中断 */
 TIMER_SOFTIRQ, /* 定时器软中断 */
 NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
 NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
 BLOCK_SOFTIRQ, 
 BLOCK_IOPOLL_SOFTIRQ, 
 TASKLET_SOFTIRQ, /* tasklet 软中断 */
 SCHED_SOFTIRQ, /* 调度软中断 */
 HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
 RCU_SOFTIRQ, /* RCU 软中断 */
 NR_SOFTIRQS
};
软中断函数

void open_softirq(int nr, void (*action)(struct softirq_action *))

#注册软中断处理函数

void raise_softirq(unsigned int nr)

#触发软中断

2.tasklet(建议使用)
484 struct tasklet_struct
485 {
486 struct tasklet_struct *next; /* 下一个 tasklet */
487 unsigned long state; /* tasklet 状态 */
488 atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
489 void (*func)(unsigned long); /* tasklet 执行的函数 */
490 unsigned long data; /* 函数 func 的参数 */
491 };
API函数

tasklet 的 定 义 和 初 始 化

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), 
unsigned long data);

或者

DECLARE_TASKLET(name, func, data)

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)

t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

例如
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
 /* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
 ......
 /* 调度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
 ......
 /* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}
3、工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
Linux 内核使用 work_struct 结构体表示一个工作,内容如下:

struct work_struct {
 atomic_long_t data; 
 struct list_head entry;
 work_func_t func; /* 工作队列处理函数 */
};

这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示:

struct workqueue_struct {
 struct list_head pwqs; 
 struct list_head list; 
 struct mutex mutex; 
 int work_color;
 int flush_color; 
 atomic_t nr_pwqs_to_flush;
 struct wq_flusher *first_flusher;
 struct list_head flusher_queue; 
 struct list_head flusher_overflow;
 struct list_head maydays; 
 struct worker *rescuer; 
 int nr_drainers; 
 int saved_max_active;
 struct workqueue_attrs *unbound_attrs;
 struct pool_workqueue *dfl_pwq; 
 char name[WQ_NAME_LEN];
 struct rcu_head rcu;
 unsigned int flags ____cacheline_aligned;
 struct pool_workqueue __percpu *cpu_pwqs;
 struct pool_workqueue __rcu *numa_pwq_tbl[];
};

Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用
worker 结构体表示工作者线程:

struct worker {
 union {
 struct list_head entry; 
 struct hlist_node hentry;
 };
 struct work_struct *current_work; 
 work_func_t current_func; 
 struct pool_workqueue *current_pwq;
 bool desc_valid;
 struct list_head scheduled; 
 struct task_struct *task; 
 struct worker_pool *pool; 
 struct list_head node; 
 unsigned long last_active; 
 unsigned int flags; 
 int id; 
 char desc[WORKER_DESC_LEN];
 struct workqueue_struct *rescue_wq;
};

每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。

API函数

#define INIT_WORK(_work, _func)或#define DECLARE_WORK(n, f)

n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

工作的调度函数为 schedule_work

bool schedule_work(struct work_struct *work)

/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
 /* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
 ......
 /* 调度 work */
 schedule_work(&testwork);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
 ......
 /* 初始化 work */
 INIT_WORK(&testwork, testwork_func_t);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}

从dtsi的 interupts 属性中提取到对应的设备号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号

int gpio_to_irq(unsigned int gpio)
例子
1 key {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-key";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_key>;
7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8 interrupt-parent = <&gpio1>;
9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
10 status = "okay";
11 };
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/semaphore.h>
15 #include <linux/timer.h>
16 #include <linux/of_irq.h>
17 #include <linux/irq.h>
18 #include <asm/mach/map.h>
19 #include <asm/uaccess.h>
20 #include <asm/io.h>
31 #define IMX6UIRQ_CNT 1 /* 设备号个数 */
32 #define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
33 #define KEY0VALUE 0X01 /* KEY0 按键值 */
34 #define INVAKEY 0XFF /* 无效的按键值 */
35 #define KEY_NUM 1 /* 按键数量 */
36 
37 /* 中断 IO 描述结构体 */
38 struct irq_keydesc {
39     int gpio; /* gpio */
40     int irqnum; /* 中断号 */
41     unsigned char value; /* 按键对应的键值 */
42     char name[10]; /* 名字 */
43     irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
44 };
45 
46 /* imx6uirq 设备结构体 */
47 struct imx6uirq_dev{
48     dev_t devid; /* 设备号 */
49     struct cdev cdev; /* cdev */
50     struct class *class; /* 类 */
51     struct device *device; /* 设备 */
52     int major; /* 主设备号 */
53     int minor; /* 次设备号 */
54     struct device_node *nd; /* 设备节点 */
55     atomic_t keyvalue; /* 有效的按键键值 */
56     atomic_t releasekey; /* 标记是否完成一次完成的按键*/
57     struct timer_list timer; /* 定义一个定时器*/
58     struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
59     unsigned char curkeynum; /* 当前的按键号 */
60 };
61 
62 struct imx6uirq_dev imx6uirq; /* irq 设备 */
63 
64 /* @description : 中断服务函数,开启定时器,延时 10ms,
65 * 定时器用于按键消抖。
66 * @param - irq : 中断号
67 * @param - dev_id : 设备结构。
68 * @return : 中断执行结果
69 */
70 static irqreturn_t key0_handler(int irq, void *dev_id)
71 {
72     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
73 
74     dev->curkeynum = 0;
75     dev->timer.data = (volatile long)dev_id;
76     mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
77     return IRQ_RETVAL(IRQ_HANDLED);
78 }
79 
80 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
81 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
82 * @param – arg : 设备结构变量
83 * @return : 无
84 */
85 void timer_function(unsigned long arg)
86 {
87     unsigned char value;
88     unsigned char num;
89     struct irq_keydesc *keydesc;
90     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
91 
92     num = dev->curkeynum;
93     keydesc = &dev->irqkeydesc[num];
94 
95     value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
96     if(value == 0){ /* 按下按键 */
97     atomic_set(&dev->keyvalue, keydesc->value);
98     }
99     else{ /* 按键松开 */
100     atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
101     atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ 
102     } 
103 }
104
105 /*
106 * @description : 按键 IO 初始化
107 * @param : 无
108 * @return : 无
109 */
110 static int keyio_init(void)
111 {
112     unsigned char i = 0;
113 
114     int ret = 0;
115 
116     imx6uirq.nd = of_find_node_by_path("/key");
117     if (imx6uirq.nd== NULL){
118         printk("key node not find!\r\n");
119         return -EINVAL;
120 }
121
122     /* 提取 GPIO */
123     for (i = 0; i < KEY_NUM; i++) {
124         imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,"key-gpio",i);
125         if (imx6uirq.irqkeydesc[i].gpio < 0) {
126         printk("can't get key%d\r\n", i);
127         }
128     }
129 
130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
131     for (i = 0; i < KEY_NUM; i++) {
132     memset(imx6uirq.irqkeydesc[i].name,0,sizeof(imx6uirq.irqkeydesc[i].name)); 
133     sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); 
134     gpio_request(imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].name);
135     gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); 
136     imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
137 #if 0
138     imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
139 #endif
140     printk("key%d:gpio=%d, irqnum=%d\r\n",i,imx6uirq.irqkeydesc[i].gpio,
141         imx6uirq.irqkeydesc[i].irqnum);
142     }
143 /* 申请中断 */
144     imx6uirq.irqkeydesc[0].handler = key0_handler;
145     imx6uirq.irqkeydesc[0].value = KEY0VALUE;
146 
147     for (i = 0; i < KEY_NUM; i++) {
148     ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,    imx6uirq.irqkeydesc[i].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,imx6uirq.irqkeydesc[i].name, &imx6uirq);
149 if(ret < 0){
150     printk("irq %d request failed!\r\n",imx6uirq.irqkeydesc[i].irqnum);
151     return -EFAULT;
152     }
153 }
154
155 /* 创建定时器 */
156     init_timer(&imx6uirq.timer);
157     imx6uirq.timer.function = timer_function;
158     return 0;
159 }
160
161 /*
162 * @description : 打开设备
163 * @param – inode : 传递给驱动的 inode
164 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
165 * 一般在 open 的时候将 private_data 指向设备结构体。
166 * @return : 0 成功;其他 失败
167 */
168 static int imx6uirq_open(struct inode *inode, struct file *filp)
169 {
170     filp->private_data = &imx6uirq; /* 设置私有数据 */
171     return 0;
172 }
173
174 /*
175 * @description : 从设备读取数据
176 * @param – filp : 要打开的设备文件(文件描述符)
177 * @param – buf : 返回给用户空间的数据缓冲区
178 * @param - cnt : 要读取的数据长度
179 * @param – offt : 相对于文件首地址的偏移
180 * @return : 读取的字节数,如果为负值,表示读取失败
181 */
182 static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
183 {
184     int ret = 0;
185     unsigned char keyvalue = 0;
186     unsigned char releasekey = 0;
187     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
188
189     keyvalue = atomic_read(&dev->keyvalue);
190     releasekey = atomic_read(&dev->releasekey);
191
192     if (releasekey) { /* 有按键按下 */
193     if (keyvalue & 0x80) {
194         keyvalue &= ~0x80;
195         ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
196     } else {
197         goto data_error;
198 }
199     atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
200     } else {
201     goto data_error;
202     }
203     return 0;
204 
205     data_error:
206     return -EINVAL;
207 }
208
209 /* 设备操作函数 */
210 static struct file_operations imx6uirq_fops = {
211 .owner = THIS_MODULE,
212 .open = imx6uirq_open,
213 .read = imx6uirq_read,
214 };
215
216 /*
217 * @description : 驱动入口函数
218 * @param : 无
219 * @return : 无
220 */
221 static int __init imx6uirq_init(void)
222 {
223 /* 1、构建设备号 */
224 if (imx6uirq.major) {
225 imx6uirq.devid = MKDEV(imx6uirq.major, 0);
226 register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
227 } else {
228 alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
229 imx6uirq.major = MAJOR(imx6uirq.devid);
230 imx6uirq.minor = MINOR(imx6uirq.devid);
231 }
232
233 /* 2、注册字符设备 */
234 cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
235 cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
236
237 /* 3、创建类 */
238 imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
239 if (IS_ERR(imx6uirq.class)) {
240 return PTR_ERR(imx6uirq.class);
241 }
242
243 /* 4、创建设备 */
244 imx6uirq.device = device_create(imx6uirq.class, NULL,
imx6uirq.devid, NULL, IMX6UIRQ_NAME);
245 if (IS_ERR(imx6uirq.device)) {
246 return PTR_ERR(imx6uirq.device);
247 }
248 
249 /* 5、初始化按键 */
250 atomic_set(&imx6uirq.keyvalue, INVAKEY);
251 atomic_set(&imx6uirq.releasekey, 0);
252 keyio_init();
253 return 0;
254 }
255
256 /*
257 * @description : 驱动出口函数
258 * @param : 无
259 * @return : 无
260 */
261 static void __exit imx6uirq_exit(void)
262 {
263 unsigned int i = 0;
264 /* 删除定时器 */
265 del_timer_sync(&imx6uirq.timer); 
266 
267 /* 释放中断 */
268 for (i = 0; i < KEY_NUM; i++) {
269 free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
270 }
271 cdev_del(&imx6uirq.cdev);
272 unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
273 device_destroy(imx6uirq.class, imx6uirq.devid);
274 class_destroy(imx6uirq.class);
275 }
276
277 module_init(imx6uirq_init);
278 module_exit(imx6uirq_exit);
279 MODULE_LICENSE("GPL");
APP
27 int main(int argc, char *argv[])
28 {
29 int fd;
30 int ret = 0;
31 char *filename;
32 unsigned char data;
33 if (argc != 2) {
34 printf("Error Usage!\r\n");
35 return -1;
36 }
37
38 filename = argv[1];
39 fd = open(filename, O_RDWR);
40 if (fd < 0) {
41 printf("Can't open file %s\r\n", filename);
42 return -1;
43 }
44
45 while (1) {
46 ret = read(fd, &data, sizeof(data));
47 if (ret < 0) { /* 数据读取错误或者无效 */
48 
49 } else { /* 数据读取正确 */
50 if (data) /* 读取到数据 */
51 printf("key value = %#X\r\n", data);
52 }
53 }
54 close(fd);
55 return ret;
56 }

加载 imx6uirq.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe imx6uirq.ko //加载驱动

驱动加载成功以后可以通过查看/proc/interrupts 文件来检查一下对应的中断有没有被注册上

看出 imx6uirq.c 驱动文件里面的 KEY0 中断已经存在了,触发方式为边沿(Edge),中断号为 49。

将上一小节编译出来的 led.ko 和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,
重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 led.ko 驱动模块:

 depmod
//第一次加载驱动的时候需要运行此命令
modprobe led.ko

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值