该笔记是学习左神或者伟神的教程的随笔记录,供需要时方便查阅,难免理解出错,如有纰漏还望指正。
一、linux中断的基本知识
1.1.中断号(int)
每个中断都有一个中断编号,用来区分不同中断。
1.2.中断申请(request_irq)/释放(free_irq)
在申请中断的参数:request_irq(中断号, 中断处理函数, 中断标志(共享/触发方式), 中断名字, 设备指针(传给中断处理函数))。返回值0-成功,1失败。
1.3.中断处理函数
irqreturn_t (*irq_handler_t) (int, void *);输入参数为:中断号,申请时传入的设备指针。
1.4.中断的使能和失能
在需要关闭和开启某个中断时,可以用下列函数去指定开始和关闭对应中断号的中断。而nosync失能函数的区别在于他在关闭时,不会等到该中断执行完才会返回,他会立即返回,在执行完后自动关闭。前者是需要该中断执行完成之后,才会返回。
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq) ==> void disable_irq_nosync(unsigned int irq)
在需要关闭所有中断时,用以下函数。但是当多个线程去操作中断开关时,可能会导致中断开启的时机错误。如A[0-6]期间关闭,B[0-3]之间关闭,B在3这个时刻会开启中断,导致A也在3这个时刻开启,所以就衍生出带标志位的使能/失能API。
local_irq_enable() ==> local_irq_save(flags)
local_irq_disable() ==> local_irq_restore(flags)
二、中断上下部
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可
以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
去执行,这样中断处理函数就会快进快出。
主要介绍几种常用的下半部处理方式。
2.1 软中断
类型_enum:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
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
};
//软中断数组
static struct softirq_action softirq_vec[NR_SOFTIRQS];
数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。
需要使用open_softirq函数进行注册,才能使用。软中断必须在编译的时候静态注册!!
/*
nr:enum类型中选取一个所要注册的中断类型
void(*active)(...):为软终端处理函数
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
2.2 tasklet
tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使
用 tasklet。
tasklet的机制有点跟我们通常使用的RTOS中的软件定时器类似。需要对他进行初始化、注册后即可。只不过软件定时器是有一个计数器去进行触发,而tasklet是通过中断的上半部去调用一个调度的函数,通知下半部可以在合适的时间去执行。(再说通俗点,和RTOS中硬件中断给task发送一个msg,告诉task去做什么事情一样。不过可能这个task的优先级很高,会被优先执行吧,这只是我的猜测,可能不准确)。
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的使用实例:
/* 定义 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);
......
}
2.3 工作队列
工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。【工作队列和】和【tasklet】的使用区别。
同样cp一段教程的源码。以后用的时候直接看这个实例即可。
/* 定义工作(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 fxls8471@1e {
2 compatible = "fsl,fxls8471";
3 reg = <0x1e>;
4 position = <0>;
5 interrupt-parent = <&gpio5>;
6 interrupts = <0 8>;
7 };
第 5 行,interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。
第 6 行,interrupts 设置中断信息,0 表示 GPIO5_IO00,8 表示低电平触发。
1 low to high edeg triggered
2 high to low edeg triggered
3 active high level-sensitive
8 active low level-sensitive
简单总结一下与中断有关的设备树属性信息:
①、#interrupt-cells,指定中断源的信息 cells 个数。
②、interrupt-controller,表示当前节点为中断控制器。
③、interrupts,指定中断号,触发方式等。
④、interrupt-parent,指定父中断,也就是中断控制器。
获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
int gpio_to_irq(unsigned int gpio) //只有是gpio时,才能用该api获取中断号