目录
一、Linux中断处理函数
1.中断号
每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中
断线。
获取中断号:
(1)根据路径获取设备树中的节点
struct device_node *of_find_node_by_path(const char *path);
const char *path:设备树中节点路径
返回值:获取到的节点
(2) 根据节点获取到节点的中断号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
struct device_node *dev:设备节点
int index:获取节点中第几个中断号
返回值:获取到的中断号
2.request_irq函数
在 Linux 内核中要想使用某个中断是需要申请的,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)
unsigned int irq:设备对应的中断号
irq_handler_t handler:中断的处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);
unsigned long flags:中断的触发方式
#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 //低电平触发
const char *name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
void *dev:传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
被申请了。
3.free_irq函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放
掉相应的中断。
函数原型:
void free_irq(unsigned int irq,void *dev)
irq :要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无。
4.中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
int:要中断处理函数要相应的中断号。
void *:一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。
中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义如下所示:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)
5.中断使能与禁止函数
中断使能:void enable_irq(unsigned int irq)
中断禁止:void disable_irq(unsigned int irq)irq 就是要禁止的中断号
disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:
void disable_irq_nosync(unsigned int irq),函数调用以后立即返回
上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32 的时候常说的关闭全局中断,这个时候可以使用如下两个函数:
local_irq_enable()
local_irq_disable()
6.中断下半部分
上半部:
上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢?Linux 内
核提供了多种下半部机制,分别为软中断、tasklet、工作队列。
1.软中断
处理速度比较块,但是是内核级别的机制,要修改内核源码,不推荐也不常用
2.tasklet
tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用 tasklet_struct 结构体来表示 tasklet:
1. 初始化,设置tasklet任务结构体
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
tasklet_init(&任务结构体的地址,中断下半部分执行函数,函数参数);
2. 启动中断下半部分----把任务放入到内核线程中
tasklet_schedule(&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);
......
}
3.工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的
工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重
新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软
中断或 tasklet。
1. 初始化,设置workqueue任务结构体
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
INIT_WORK(&任务结构体的地址,中断下半部分执行函数);
和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原
型如下所示:
bool schedule_work(struct work_struct *work)
2. 启动中断下半部分----把任务放入到内核线程中
//中断下半部分启动--放入内核线程
schedule_work(&workqueue);
示例代码 :
/* 定义工作(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);
......
}
二、中断驱动编写
接下来我们以exynos4412-fs4412开发板的k3控制蜂鸣器为例。
硬件连接图: key3——GPX1_2——XEINT10
蜂鸣器硬件连接图:
1.添加设备树信息
(1)修改要下载到硬件设备的设备树,做设备信息的添加
设备树位置:vi arch/arm/boot/dts/exynos4412-fs4412.dts
在编程过程中,需要添加中断设备节点信息--描述当前设备的中断号
key_int_node {
compatible = "key3";
interrupt-parent = <&gpx1>;
interrupts = <2 4>;
};
(2)重新编译修改后的设备树,并把新编译的设备树拷贝到 "tftpboot" 目录下
2.获取到中断号
在驱动中获取到中断号,并申请中断处理
(1)获取到中断号
//根据路径获取设备树中的节点
struct device_node *of_find_node_by_path(const char *path);
(2)申请中断处理(内核检查到对应的中断,驱动申请进行处理)
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
3.中断驱动框架
//外设信息对象
struct xxx_node
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int irqno;
};
struct xxx_node xxx;
//中断处理函数
irqreturn_t xxx_handler(int irqno, void * dev)
{
.........
return IRQ_HANDLED;
}
//文件IO接口实现
const struct file_operations fops=
{
.......
};
//驱动入口函数
static int __init xxx_init(void)
{
//申请设备号
xxx.major = register_chrdev(0,"xxx",&fops);
if(xxx.major < 0)
{
printk("register_chrdev error\n");
ret = -1;
goto err_0;
}
//创建设备节点
xxx.cls = class_create(THIS_MODULE,"xxx cls");
if(IS_ERR(xxx.cls))
{
printk("class_create error\n");
ret = -2;
goto err_1;
}
xxx.dev = device_create(xxx.cls,NULL,MKDEV(xxx.major,0),NULL,"xxx");
if(IS_ERR(xxx.dev))
{
printk("device_create error\n");
ret = -3;
goto err_2;
}
//地址映射、硬件设备初始化
ioremap(物理地址,映射大小);
...............
//中断初始化,获取中断号
struct device_node * np = of_find_node_by_path("/xxx_int_node");
if(np == NULL)
{
printk("of_find_node_by_path error\n");
ret = -4;
goto err_3;
}
key.irqno = irq_of_parse_and_map(np,0);
//申请中断
if( request_irq(xxx.irqno,xxx_handler,IRQF_TRIGGER_FALLING,"this is xxx","hello world") != 0 )
{
printk("request_irq error\n");
ret = -5;
goto err_3;
}
return 0;
//驱动卸载入口
static void __exit xxx_exit(void)
{
//释放中断
free_irq(xxx.irqno,"hello world");
//销毁设备节点
device_destroy(xxx.cls,MKDEV(key.major,0));
class_destroy(xxx.cls);
//释放设备号
unregister_chrdev(xxx.major,"xxx");
//解除地址映射
iounmap();
return 0;
//出错处理
err_3:
device_destroy(xxx.cls,MKDEV(xxx.major,0));
err_2:
class_destroy(xxx.cls);
err_1:
unregister_chrdev(xxx.major,"xxx");
err_0:
return ret;
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
按键控制蜂鸣器实现代码:
#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 <asm/io.h>
#include <asm/uaccess.h>
#define GPD0CON 0x114000A0
#define PWM 0x139D0000
#define PWMTCFG0 0x139D0000
#define PWMTCFG1 0x139D0004
#define PWMTCON 0x139D0008
#define PWMTCNTB0 0x139D000C
#define PWMTCMPB0 0x139D0010
//蜂鸣器信息结构体
struct BEEP
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int * pwmtcfg0;
unsigned int * pwmtcfg1;
unsigned int * pwmtcon;
unsigned int * pwmtcntb0;
unsigned int * pwmtcmpb0;
unsigned int * gpd0con;
};
struct BEEP beep;
//按键信息对象
struct Key_node
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int irqno;
};
struct Key_node key;
//中断处理函数
irqreturn_t key_handler(int irqno, void * dev)
{
printk("%s\n",(char *)dev);
static int i=1;i++;
if(i%2==1)
{
*(beep.pwmtcon) |= 1;
}
if(i%2==0)
{
*(beep.pwmtcon) &= ~1;
}
return IRQ_HANDLED;
}
//文件IO处理函数
const struct file_operations fops;
//驱动入口函数
static int __init key_init(void)
{
int ret;
//申请设备号
key.major = register_chrdev(0,"key",&fops);
if(key.major < 0)
{
printk("register_chrdev error\n");
ret = -1;
goto err_0;
}
//创建设备节点
key.cls = class_create(THIS_MODULE,"key cls");
if(IS_ERR(key.cls))
{
printk("class_create error\n");
ret = -2;
goto err_1;
}
key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
if(IS_ERR(key.dev))
{
printk("device_create error\n");
ret = -3;
goto err_2;
}
//硬件设备初始化
beep.gpd0con = ioremap(GPD0CON,4);
beep.pwmtcfg0 = ioremap(PWMTCFG0,4);
beep.pwmtcfg1 = ioremap(PWMTCFG1,4);
beep.pwmtcon = ioremap(PWMTCON,4);
beep.pwmtcntb0 = ioremap(PWMTCNTB0,4);
beep.pwmtcmpb0 = ioremap(PWMTCMPB0,4);
*(beep.gpd0con) = *(beep.gpd0con) & ~(0XF<<0) | (0X2<<0);
*(beep.pwmtcntb0) = 200000;
*(beep.pwmtcmpb0) = 100000;
*(beep.pwmtcfg0) = *(beep.pwmtcfg0) & ~0xff;
*(beep.pwmtcfg1) &= ~0XF;
*(beep.pwmtcon) &= ~(1<<4);
*(beep.pwmtcon) |= 1<<3;
*(beep.pwmtcon) |= 1<<1;
*(beep.pwmtcon) &= ~(1<<1);
*(beep.pwmtcon) |= 1<<2;
//中断初始化,获取中断号
struct device_node * np = of_find_node_by_path("/key_int_node");
if(np == NULL)
{
printk("of_find_node_by_path error\n");
ret = -4;
goto err_3;
}
key.irqno = irq_of_parse_and_map(np,0);
//申请中断
if( request_irq(key.irqno,key_handler,IRQF_TRIGGER_FALLING,"this is key","hello world") != 0 )
{
printk("request_irq error\n");
ret = -5;
goto err_3;
}
return 0;
//出错处理
err_3:
device_destroy(key.cls,MKDEV(key.major,0));
err_2:
class_destroy(key.cls);
err_1:
unregister_chrdev(key.major,"key");
err_0:
return ret;
}
//驱动卸载入口
static void __exit key_exit(void)
{
free_irq(key.irqno,"hello world");
device_destroy(key.cls,MKDEV(key.major,0));
class_destroy(key.cls);
unregister_chrdev(key.major,"key");
iounmap(beep.pwmtcmpb0);
iounmap(beep.pwmtcntb0);
iounmap(beep.pwmtcon);
iounmap(beep.pwmtcfg1);
iounmap(beep.pwmtcfg0);
iounmap(beep.gpd0con);
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
实验结果:按一下蜂鸣器响,再按下蜂鸣器停止。