
在5.4.79内核中,从设备树中获取
获取中断号到方法:
1, 宏定义
IRQ_EINT(号码)
2,设备树文件中
arch/arm/boot/dts/exynos4412-origen.dts
硬件连接:
key ---- gpx1_2--- EINT10
设备树文件:arch/arm/boot/dts/exynos4412-pinctrl.dtsi
gpx1: gpx1 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <2>;
};
在编程过程中,需要定义自己的节点--描述当前设备用的中断号
arch/arm/boot/dts/exynos4412-origen.dts +51
key_int_node{
compatible = "test_key";
interrupt-parent = <&gpx1>;
interrupts = <2 4>;
};
编译设备树文件:
make dtbs -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
更新dtbs文件:
复制 exynos4412-origen.dtb 到NAS

上图中gpx1设备树代码是3.14内核中的,在5.4版本中 代码略有不同,具体如下:
linux@ubuntu:~/Level10/day6/linux-5.4.79/arch/arm/boot/dts$ vim exynos4412-pinctrl.dtsi

设备树中可以查到该设备节点
[root@farsight ]# cd proc/device-tree/key_int_node/ [root@farsight key_int_node]# ls compatible interrupt-parent interrupts name
a,获取到中断号码:
int get_irqno_from_node(void)
{
// 获取到设备树中的节点
struct device_node *np = of_find_node_by_path("/key_int_node");
if(np){
printk("find node ok\n");
}else{
printk("find node failed\n");
}
// 通过节点去获取到中断号码
int irqno = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", irqno);
return irqno;
}
b,申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
参数1: 设备对应的中断号
参数2: 中断的处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);
参数3:触发方式
#define IRQF_TRIGGER_NONE 0x00000000 //内部控制器触发中断的时候的标志
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿
#define IRQF_TRIGGER_HIGH 0x00000004 // 高点平
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发
参数4:中断的描述,自定义,主要是给用户查看的
/proc/interrupts
参数5:传递给参数2中函数指针的值
返回值: 正确为0,错误非0
参数2的赋值:参数1:irqno 和参数2:devid 都是由上面的request_irq函数传递的分别是该函数的参数1和参数5.
irqreturn_t key_irq_handler(int irqno, void *devid)
{
return IRQ_HANDLED;
}
释放中断:
void free_irq(unsigned int irq, void *dev_id)
参数1: 设备对应的中断号
参数2:与request_irq中第5个参数保持一致
效果展示
[root@farsight ]# insmod *.ko [ 17.570120] key_drv: loading out-of-tree module taints kernel. [ 17.575433] find node ok [ 17.577120] irqno = 107
[root@farsight ]# cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 36: 0 0 0 0 GIC-0 89 Level mct_comp_irq 37: 20031 3406 1122 6958 GIC-0 28 Level MCT 38: 0 0 0 0 COMBINER 18 Edge arm-pmu 39: 0 0 0 0 COMBINER 26 Edge arm-pmu ... 106: 19237 0 0 0 exynos4210_wkup_irq_chip 6 Level eth0 107: 0 0 0 0 exynos4210_wkup_irq_chip 2 Edge IRQ_EINT10 IPI0: 0 1 1 1 CPU wakeup interrupts ...
按下按键K3 [root@farsight ]# [ 2979.408843] --------------key_irq_handler------------------ [ 2983.202843] --------------key_irq_handler------------------ [root@farsight ]#
// 1,设定一个全局的设备对象 key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL); // 2,申请主设备号 key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops); // 3,创建设备节点文件 key_dev->cls = class_create(THIS_MODULE, "key_cls"); key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
a,硬件如何获取数据
key: 按下和抬起: 1/0
读取key对应的gpio的状态,可以判断按下还是抬起
读取key对应gpio的寄存器--数据寄存器
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
b,驱动如何传递给用户
在中断处理中填充数据:
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
在xxx_read中将数据传递给用户
ret = copy_to_user(buf, &key_dev->event, count);
c,用户如何拿到--编写应用程序
while(1)
{
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else{
printf("APP__ key enter up\n");
}
}
}

文件io模型: 1,非阻塞 2,阻塞 3,多路复用--select/poll 4, 异步信号通知faync 阻塞: 当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠 linux应用中,大部分的函数接口都是阻塞 scanf(); read(); write(); accept(); 驱动中需要调用 1,将当前进程加入到等待队列头中 add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait) 2,将当前进程状态设置成TASK_INTERRUPTIBLE set_current_state(TASK_INTERRUPTIBLE) 3,让出调度--休眠 schedule(void) 更加智能的接口,等同于上面的三个接口: wait_event_interruptible(wq, condition) 驱动如何去写代码 1,等待队列头 wait_queue_head_t init_waitqueue_head(wait_queue_head_t *q); 2,在需要等待(没有数据)的时候,进行休眠 wait_event_interruptible(wait_queue_head_t wq, condition) // 内部会构建一个等待队列项/节点wait_queue_t 参数1: 等待队列头 参数2: 条件,如果是为假,就会等待,如果为真,就不会等待 可以用一标志位,来表示是否有数据 3,在一个合适的时候(有数据),会将进程唤醒 wake_up_interruptible(wait_queue_head_t *q) 用法: wake_up_interruptible(&key_dev->wq_head); //同时设置标志位 key_dev->key_state = 1;
用的会比较少,因为比较耗资源
open("/dev/key0", O_RDWR|O_NONBLOCK);
------------------------------------
驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;


poll的应用:
0, 存在的意义:单进程单线程下,解决同时监听多个设备的难题。(当然解决此问题也可以使用多线程或多进程来处理)
1, 需要打开多个文件(多个设备)
2, 利用poll来实现监控fd的读,写,出错
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1: 表示多个文件描述符集合
struct pollfd描述的是文件描述符到信息
struct pollfd {
int fd; //文件描述符
short events; //希望监控fd的什么事件:读,写,出错
POLLIN 读,
POLLOUT 写,
POLLERR出错
short revents; //结果描述,表示当前的fd是否有读,写,出错
//用于判断,是内核自动赋值
POLLIN 读,
POLLOUT 写,
POLLERR出错
};
参数2:被监控到fd的个数
参数3: 监控的时间:
正: 表示监控多少ms
负数: 无限的时间去监控
0: 等待0ms,类似于非阻赛
返回值: 负数:出错
大于0,表示fd中有数据
等于0: 时间到
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一个mask值
unsigned int mask;
// 调用poll_wait,将当前到等待队列注册系统中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,当没有数据到时候返回一个0
if(!key_dev->key_state)
mask = 0;
// 2,有数据返回一个POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
const struct file_operations key_fops = {
.poll = key_drv_poll,
};
a,应用--处理信号,主要是读写数据
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO");
// 读取数据
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
// 1,设置信号处理方法
signal(SIGIO,catch_signale);
// 2,将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
// 3,将io模式设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
fcntl函数 根据文件描述词来操作文件的特性 文件控制函数 fcntl -- file control
头文件:
#include <unistd.h> #include <fcntl.h>
函数原型:
int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);`
描述:
fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符.
针对cmd的值,fcntl能够接受第三个参数(arg)
fcntl函数有5种功能:
1.复制一个现有的描述符(cmd=F_DUPFD). 2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). ------本次实验使用的参数 4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).------本次实验使用的参数 5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
cmd 选项:
F_GETFL 取得fd的文件状态标志,如同下面的描述一样(arg被忽略)
F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。
F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)
命令字(cmd)F_GETFL和F_SETFL的标志如下面的描述:
O_NONBLOCK 非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误
O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
O_DIRECT 最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.
如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能
FASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候
注意: 在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
fcntl的返回值: 与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。
b,驱动--发送信号
1,需要和进程进行关联--记录信号该发送给谁
实现一个fasync的接口
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要调用一个函数记录信号该发送给谁
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
2,在某个特定的时候去发送信号,在有数据的时候
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; /* singly linked list */ struct file *fa_file; struct rcu_head fa_rcu; }; /** *fasync_helper - 将一个fasync_struct对象注册进内核 *@fd:文件描述符,由fasync传入 *@filp:file指针,由fasync传入 *@sig:信号类型,通常使用的就是SIGIO *@dev_fasync:事前准备的fasync_struct对象指针的指针 */ int fasync_helper(int fd, struct file * filp, int sig, struct fasync_struct ** dev_fasync); /** *kill_fasync - 释放一个信号 *@dev_fasync:事前使用fasync_helper注册进内核的fasync_struct对象指针的指针 *@filp:file指针,由fasync传入 *@sig:信号类型,通常使用的就是SIGIO *@flag:标志,通常,如果资源可读用POLLIN,如果资源可写用POLLOUT */ void kill_fasync(struct fasync_struct **dev_fasync, int sig, int flag);
示例代码
应用代码
//---------------key_test.c---------------------- #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <poll.h> #include <signal.h> #define KEY_ENTER 28 #define EAGAIN 35 int fd; struct key_value_dsc{ int code; //表示按键的类型:home, esc, Q, W, E, R, T... int date; //表示按下还是抬起 1/0 }; struct key_value_dsc key_value; void catch_signal(int signo) { int ret; if(signo == SIGIO) { printf("__APP__:catch_signal\n"); //可以直接去读数据, 一定可以读到数据 ret = read(fd, &key_value, sizeof(struct key_value_dsc)); if(ret < 0){ perror("read:"); exit(1); } if(key_value.code == KEY_ENTER) { if(key_value.date == 1) { printf("__APP__key enter pressed\n",key_value.date); } else if(key_value.date == 0) { printf("__APP__key enter up\n",key_value.date); } } } } int main(int argc, char **argv) { int ret, flag; char in_buf[128] = {0}; fd = open("/dev/INTRRUPT KEY3",O_RDWR); if(fd < 0){ perror("open:"); exit(1); } //1.设置信号处理方法 signal(SIGIO, catch_signal); //2.将当前进程设置成SIGIO的属主进程 fcntl(fd,F_SETOWN,getpid()); //3. 将IO模式设置成异步模式 flag = fcntl(fd,F_GETFL); fcntl(fd,F_SETFL,flag | FASYNC); while(1) { printf("__APP__: wating\n"); sleep(1); } close(fd); return 0; }
驱动代码
//--------key_drv.c-------------- #include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/input-event-codes.h> #include <linux/wait.h> #include <linux/poll.h> #include <asm/io.h> #define GPX1CON_ADDR 0x11000c20 #define GPX1_SIZE 8 //设计一个全局设备对象--描述按键信息 struct key_irq_str{ int irqno; unsigned int dev_major; unsigned int dev_minor; struct class* pkey_class; struct device* pkey_device; void* reg_virt_base; wait_queue_head_t wq; int key_state; //表示是否有数据 struct fasync_struct* fasync; }; struct key_irq_str *pkey_irq_str; //设计一个描述按键的数据的对象 struct key_value_dsc{ int code; //表示按键的类型:home, esc, Q, W, E, R, T... int date; //表示按下还是抬起 1/0 }; struct key_value_dsc key3_value_dsc = { .code = 0, .date = 0, }; int key_drv_fasync(int fd, struct file * filp, int on) { //只需要调用一个函数,记录信号该发送给谁 return fasync_helper(fd, filp, on, &pkey_irq_str->fasync); } irqreturn_t key_irq_handler(int irqno, void *devid) { unsigned int GPX1DAT; printk("--------------%s------------------\n",__FUNCTION__); GPX1DAT = readl(pkey_irq_str->reg_virt_base + 4); if( GPX1DAT &= 0x1<<2 ){ printk("__KERN__:Enter up\n"); key3_value_dsc.code = KEY_ENTER; key3_value_dsc.date = 0; } else{ printk("__KERN__:Enter down\n"); key3_value_dsc.code = KEY_ENTER; key3_value_dsc.date = 1; } //表示有数据,需要唤醒整个等待队列 wake_up_interruptible(&(pkey_irq_str->wq)); //同时设置标志位 pkey_irq_str->key_state = 1; //发送信号 kill_fasync(&(pkey_irq_str->fasync), SIGIO, POLLIN); return IRQ_HANDLED; } int key_drv_open (struct inode* inode, struct file *filp){ printk("__KERN__:open\n"); return 0; } ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos){ int ret; //兼容阻塞和非阻塞模式。如果当前是非阻塞模式,并且没有数据,立马返回一个出错码 if(filp->f_flags & O_NONBLOCK && !pkey_irq_str->key_state) { return EAGAIN; } wait_event_interruptible(pkey_irq_str->wq, pkey_irq_str->key_state); //表示有数据 ret = copy_to_user(buf, &key3_value_dsc, count); if(ret == 0) { //printk("__KERN__:read\n"); } else { printk("__KERN__:Err to read\n"); } key3_value_dsc.code = 0; key3_value_dsc.date = 0; pkey_irq_str->key_state = 0; return count; } ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos){ return 0; } int key_drv_release (struct inode *inod, struct file *filp){ printk("__KERN__:release\n"); return 0; } const struct file_operations key_drv_fops = { .open = key_drv_open, .read = key_drv_read, .write = key_drv_write, .release = key_drv_release, .fasync = key_drv_fasync, }; int get_irqno_from_node(void) { //获取到设备树中的节点 struct device_node *np = of_find_node_by_path("/key_int_node"); if(np){ printk("find node ok\n"); } else{ printk("find node failed\n"); } //通过节点去获取到中断号码 pkey_irq_str->irqno = irq_of_parse_and_map(np,0); printk("irqno = %d\n",pkey_irq_str->irqno); return pkey_irq_str->irqno; } static int __init key_drv_init(void) { int ret; //1.设定一个全局的设备对象 pkey_irq_str = kzalloc(sizeof(struct key_irq_str), 1); //2.申请主设备号,设置次设备号 pkey_irq_str->dev_major = register_chrdev(0, "key_drv_test", &key_drv_fops); pkey_irq_str->dev_minor = 0; //3.创建设备节点 pkey_irq_str->pkey_class = class_create(THIS_MODULE, "key_interrupt_dev"); pkey_irq_str->pkey_device = device_create(pkey_irq_str->pkey_class,NULL,MKDEV(pkey_irq_str->dev_major, pkey_irq_str->dev_minor),NULL,"INTRRUPT KEY%d",3); //4.硬件初始化--地址映射或中断申请 ret = get_irqno_from_node(); ret = request_irq(pkey_irq_str->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "IRQ_EINT10", NULL); if(ret != 0) { printk("request_irq ERROR!\n"); return ret; } pkey_irq_str->reg_virt_base = ioremap(GPX1CON_ADDR, GPX1_SIZE); //5. 初始化等待队列头 init_waitqueue_head(&(pkey_irq_str->wq)); pkey_irq_str->key_state = 0; return 0; } static void __exit key_drv_exit(void) { iounmap(pkey_irq_str->reg_virt_base); free_irq(pkey_irq_str->irqno,NULL); device_destroy(pkey_irq_str->pkey_class, MKDEV(pkey_irq_str->dev_major, pkey_irq_str->dev_minor)); class_destroy(pkey_irq_str->pkey_class); unregister_chrdev(pkey_irq_str->dev_major, "key_drv_test"); kfree(pkey_irq_str); } module_init(key_drv_init); module_exit(key_drv_exit); MODULE_LICENSE("GPL");
Makefie代码
ROOTFS_DIR = /home/linux/Level11 APP_NAME = key_test CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean rm $(APP_NAME) ./1/$(APP_NAME) clear install: cp -raf *.ko $(APP_NAME) ./1/ else obj-m += key_drv.o endif
0,存在的意义:有的时候中断处理程序耗时较长,会影响用户体验甚至影响到其他进程的正常执行,此时可以将中断一分为二,关键部分优先执行,
剩下的部分切分出去,不再占用中断。关键部分执行完毕后,调用剩余部分,让其在内核线程中继续执行,而不是继续占用原线程。
延申阅读:https://developer.aliyun.com/article/379819
1,softirq: 处理比较快,但是内核级别的机制,需要修改整个内核源码,不推荐也不常用
2,tasklet: 内部实现实际调用了softirq
3, workqueue: 工作队列
1,tasklet:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); // 下半部的实现逻辑
unsigned long data; // 传递给func
};
a, 初始化,不必直接对其初始化,使用下面的tasklet_init函数即可。
struct tasklet_struct mytasklet;
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
参数1:结构体地址,
参数2:下半部实现函数
参数3:传递给 下半部实现函数 的参数
例子:
void key_tasklet_half_irq(unsigned long data)
{
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
b,在上半部中放入到内核线程中--启动
// 启动下半步
tasklet_schedule(&key_dev->mytasklet);
c,模块卸载的时候:
tasklet_kill(&key_dev->mytasklet);

