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)
5、mod_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