中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。
使用某个中断是需要申请的, request_irq 函数用于申请中断,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:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志.
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值: 0 中断申请成功,其他负值中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:
void free_irq(unsigned int irq,void *dev)
函数参数和返回值含义如下:
irq: 要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无
使用 request_irq 函数申请中断的时候需要设置中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。
第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)
local_irq_enable()
local_irq_disable()
local_irq_enable 用于使能当前处理器中断系统,
local_irq_disable 用于禁止当前处理器中断系统。
local_irq_save(flags)
local_irq_restore(flags)
这两个函数是一对, local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态。
我们在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,
上半部中断处理函数,下半部机制:
Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中
要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *))
函数参数和返回值含义如下:
nr:要开启的软中断,在示例代码 51.1.2.3 中选择一个。
action:软中断对应的处理函数。
返回值: 没有返回值。
注册好软中断以后需要通过 raise_softirq 函数触发
void raise_softirq(unsigned int nr)
函数参数和返回值含义如下:
nr:要触发的软中断,在示例代码 51.1.2.3 中选择一个。
返回值: 没有返回值。
软中断必须在编译的时候静态注册! Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面
tasklet 是利用软中断来实现的另外一种下半部机制,Linux 内核使用结构体
tasklet_struct 结构体
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 };
如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
函数参数和返回值含义如下:
t:要初始化的 tasklet
func: tasklet 的处理函数。
data: 要传递给 func 函数的参数
返回值: 没有返回值
也可以使用宏DECLARE_TASKLET 来一次性完成tasklet的定义和初始化,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中
DECLARE_TASKLET(name, func, data)
name 为要定义的 tasklet 名字,是一个 tasklet_struct 类型的变量,
func就是 tasklet 的处理函数,
data 是传递给 func 函数的参数。
在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行, tasklet_schedule 函数原型如下:
void tasklet_schedule(struct tasklet_struct *t)
函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
返回值: 没有返回值
tasklet 的参考使用示例
/* 定义 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);
......
}
工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。
在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。
直接定义一个 work_struct 结构体变量,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作, _func 是工作对应的处理函数。
也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:
#define DECLARE_WORK(n, f)
n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:
bool schedule_work(struct work_struct *work)
函数参数和返回值含义如下:
work: 要调度的工作。
返回值: 0 成功,其他值 失败
工作队列的参考使用示例
/* 定义工作(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);
......
}
1、打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点
2、 gpio 节点也可以作为中断控制器
imx6ull.dtsi 文件中的 gpio5 节点
打开 imx6ull-alientek-emmc.dts 文件
irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
函数参数和返回值含义如下:
dev: 设备节点。
index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。
如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:
int gpio_to_irq(unsigned int gpio)
函数参数和返回值含义如下:
gpio: 要获取的 GPIO 编号。
返回值: GPIO 对应的中断号。
驱动 I.MX6U-ALPHA 开发板上的 KEY0 按键,采用中断的方式,并且采用定时器来实现按键消抖,应用程序读取按键值并且通过终端打印出来。
按键 KEY0 使用中断模式,在“key”节点下添加中断相关属性
设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。
新建名为“13_irq”的文件夹,然后在 13_irq 文件夹里面创建 vscode 工程,工作区命名为“imx6uirq”。工程创建好以后新建 imx6uirq.c 文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define IMX6UIRQ_CNT 1 /*设备号个数*/
#define IMX6UIRQ_NAME "imx6uirq" /*设备名字*/
#define KEY0VALUE 0x01 /*KEY0按键值*/
#define INVAKEY 0xEF /*无效按键值*/
#define KEY_NUM 1 /*按键数量*/
/*中断IO描述结构体*/
struct irq_keydesc{
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/*imx6uirq设备结构体*/
struct imx6uirq_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*lei*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct device_node * nd; /*设备节点*/
atomic_t keyvalue; /*有效按键值*/
atomic_t releasekey; /*标记是否完成一次完整的按键*/
struct timer_list timer; /*定义一个定时器*/
struct irq_keydesc irqkey[KEY_NUM]; /*按键描述数组*/
unsigned char curkeynum; /*当前按键号*/
};
struct imx6uirq_dev imx6uirq; /*定义irq设备*/
/*中断服务函数,开启定时器,延时10ms,定时器用于按键消抖*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data=(volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/*定时器服务函数,用于按键消抖,定时器到了以后
再次读取按键值,如果按键还是处于按下状态就表示按键有效。*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev=(struct imx6uirq_dev*)arg;
num=dev->curkeynum;
keydesc=&dev->irqkey[num];
value=gpio_get_value(keydesc->gpio);/* 读取 IO 值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue,keydesc->value);
}else{
atomic_set(&dev->keyvalue, 0x80|keydesc->value);/* 按键松开 */
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}
/*初始化按键 IO*/
static int keyio_init(struct imx6uirq_dev *dev)
{
unsigned char i = 0;
int ret=0;
/*1、获取设备节点:key*/
dev->nd=of_find_node_by_path("/key");
if(dev->nd == NULL){
printk("key node can not found!\r\n");
return -EFAULT;
}else{
printk("key node has been found!\r\n");
}
/*2、获取设备树中的gpio属性,得到key所使用的编号*/
for(i=0;i<KEY_NUM;i++){
dev->irqkey[i].gpio =of_get_named_gpio(dev->nd,"key-gpio",i);
if(dev->irqkey[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EFAULT;
}
printk("key%d:gpio= %d",i,dev->irqkey[i].gpio);
}
/*3. 初始化 key 所使用的 IO,并且设置成中断模式 */
for(i=0;i<KEY_NUM;i++){
memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name,"KEY%d",i);
gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);
gpio_direction_input(dev->irqkey[i].gpio);
dev->irqkey[i].irqnum=gpio_to_irq(dev->irqkey[i].gpio);
printk(" irqnum=%d\r\n",dev->irqkey[i].irqnum);
}
/*4. 申请中断 */
dev->irqkey[0].handler=key0_handler;
dev->irqkey[0].value=KEY0VALUE;
for(i=0;i<KEY_NUM;i++){
ret=request_irq(dev->irqkey[i].irqnum,dev->irqkey[i].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,dev->irqkey[i].name,&imx6uirq);
if(ret<0){
printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
return -EFAULT;
}
printk("irq %d request done!\r\n",dev->irqkey[i].irqnum);
}
/*5. 创建定时器*/
init_timer(&dev->timer);
dev->timer.function=timer_function;
return 0;
}
/*打开设备*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/*从设备读取数据*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue=0;
unsigned char releasekey=0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
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;
data_error:
return -EINVAL;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops={
.owner=THIS_MODULE,
.open=imx6uirq_open,
.read=imx6uirq_read,
};
/*驱动模块入口函数*/
static int __init imx6uirq_init(void)
{
/*注册字符设备驱动*/
/*1、注册设备号*/
if(imx6uirq.major){ /*定义设备号*/
imx6uirq.devid=MKDEV(imx6uirq.major,0);
register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
}else{ /*没有定义设备*/
alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME); /*申请设备号*/
imx6uirq.major=MAJOR(imx6uirq.devid); /* 获取分配号的主设备号 */
imx6uirq.minor=MINOR(imx6uirq.devid);
}
printk("imx6uirq major=%d, minor=%d\r\n",imx6uirq.major,imx6uirq.minor);
/*2、初始化cdev*/
//imx6uirq.cdev.owner=THIS_MODULE;
cdev_init(&imx6uirq.cdev,&imx6uirq_fops);
/*3、添加一个cdev*/
cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);
/*4、创建类*/
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/*5、创建设备*/
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid,NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/*6、初始化按键*/
atomic_set(&imx6uirq.keyvalue,INVAKEY);
atomic_set(&imx6uirq.releasekey,0);
keyio_init(&imx6uirq);
return 0;
}
/*驱动出口*/
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
del_timer_sync(&imx6uirq.timer); /*删除timer定时器*/
/*释放中断*/
for(i=0;i<KEY_NUM;i++){
free_irq(imx6uirq.irqkey[i].irqnum,&imx6uirq);
}
/* 注销字符设备驱动 */
cdev_del(&imx6uirq.cdev); /* 删除 cdev */
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); /*注销设备号*/
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
/*注册模块入口和出口*/
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bruce");
通过不断的读取/dev/imx6uirq 文件来获取按键值,当按键按下以后就会将获取到的按键值输出在终端上,新建名为 imx6uirqApp.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 "linux/ioctl.h"
/* 使用方法 :./irqApp /dev/imx6uirq 打开测试 App*/
int main(int argc, char* argv[])
{
int fd;
int ret = 0;
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",argv[1]);
return -1;
}
/*/dev/irq读取数据*/
while(1){
ret=read(fd,&data,sizeof(data));
if(ret<0){
}else{
if(data){
printf("key value = %#X\r\n",data);
}
}
}
close(fd);/*关闭文件*/
return ret;
}
编译驱动程序和测试 APP
编写 Makefile 文件,将 obj-m 变量的值改为 imx6uirq.o
输入命令make -j32
编译出驱动模块文件:imx6uirq.ko
输入如命令arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
编译出 imx6uirqApp 测试程序
imx6uirq.ko 和 imx6uirqApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入insmod imx6uirq.ko
加载imx6uirq.ko 驱动模块
驱动加载成功以后可以通过查看/proc/interrupts 文件来检查一下对应的中断有没有被注册上cat /proc/interrupts
./imx6uirqApp /dev/imx6uirq
按下开发板上的 KEY0 键,终端就会输出按键值,按键值获取成功,并且不会有按键抖动导致的误判发生,说明按键消抖工作正常。
./ mutexApp /dev/gpioled 0&
//关闭