嵌入式技术
上篇文章我们从内核工程师的角度剖析了内核的外部中断,这节我们从BSP工程师的角度剖析一下外部中断。
外部中断驱动架构
HARDIRQ 部分:
也就是我们在上节内容中看到的,调用BSP工程师注册的handler的部分,它的上下文在中断上下文中,所以不能做带有"休眠"的动作。
SOFTIRQ 部分:
就是我们通常说的下半部分,通常作为BSP工程师要处理的详细例程的地方。
BSP工程师外部驱动模板
上半部调用模板
/*中断处理上半部分*/
irqreturn_t xxx_interrupt(int irq, void *dev_id) {
...
/* 中断处理下半部框架调用,常见的有下面几种:
* 1. tasklet 它的执行额上下文是软中断,执行时机通常是上半部返回时
* 2. 工作队列,与tasklet类似,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。
* 3. Softirq ,tasklet是基于softirq实现的,软中断属于原子上下文的一种,因此函数不允许睡眠。
* 4. 线程化的xxx_thread_fn, 它是基于内核线程创建的,因此可以做休眠动作。
*/
...
/*这个返回值很重要,决定了线程化时传进去的irq_handler_t thread_fn 是否会被调用
* 可以参考上节课__handle_irq_event_percpu 函数中电信号处理完,调用完BSP工程师
*handler后,判断返回值的switch(第72行)里面执行的case*/
return IRQ_WAKE_THREAD;
}
//线程化模板时,传递给额需要创建的内核线程处理函数。即线程化的下半部。
irqreturn_t xxx_thread_fn(int irq, void *dev_id){
.....
}
/*设备驱动加载模块*/
int __init xxx_init(void){
...
/*申请中断*/
result = request_irq(xxx_irq,xxx_interrupt,0,”xxx”,NULL);
//或者使用线程化模板申请中断,我们稍后通过剖析源代码可知道 request_irq 与线程化的区别就是传递额xxx_thread_fn是否为空
//result = request_threaded_irq(xxx_irq,xxx_interrupt,xxx_thread_fn,irqflag”xxx”,NULL);
....
return IRQ_HANDLERD;
}
/**设备驱动卸载模块*/
void __exit xxx_exit(void){
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
}
下半部调用模板
//工作队列模板
struct work_struct xxx_wq;
void xxxx_do_work(struct work_struct *work);
/**中断处理下半部*/void xxxx_do_work(struct work_struct *wkg){
...
}
/*中断处理上半部分*/
irqreturn_t xxx_interrupt(int irq, void *dev_id){
...
schedule_work(&xxx_wq);
...
}
/*设备驱动加载模块*/
int __init xxx_init(void){
...
/*申请中断*/
result = request_irq(xxx_irq,xxx_interrupt,0,”xxx”,NULL);
.....
/*初始化工作队列*/
INIT_WORK(&xxx_wq,xxx_do_work);
return IRQ_HANDLERD;
}
//tasklet模板
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
/**中断处理下半部*/
void xxx_do_tasklet(unsigned long){
...
}
/*中断处理上半部分*/
irqreturn_t xxx_interrupt(int irq, void *dev_id){
...
tasklet_schedule(&xxx_tasklet);
...
}
ISR安装过程
ISR注册过程实质就是如何和我们上节内容中irq_descs[] 数组中对应的irqdesc中的action建立数据关系。
//include/linux/interrupt.h
//线程化直接调用的方法,request_irq 也是封装了该方法,
extern int __must_check request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn,unsigned long flags, const char *name, void *dev);
//driver 上半部注册函数, 通过该函数,驱动程序中安装一个设备中断服务例程。
//handler就是我们通常说的ISR例程。ISR由驱动程序实现,即BSP工程师实现。
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev){
//封装了request_threaded_irq,内核线程函数为null
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
//irq/manage.c
int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id){
....
desc = irq_to_desc(irq);//关键函数,通过irq查询irq_desc, 就是上节内容中看到的内核中维护irq_desc数组
....
if (!handler) {
if (!thread_fn)
return -EINVAL;
//当为线程化时,如果上半部handler为null时,系统会默认给一个函数,返回的值就是IRQ_WAKE_THREAD,调用起线程化的下半部内核函数
handler = irq_default_primary_handler;
}
...
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
....
/*通过request_irq API的,这个默认为NULL, 如果是线程化,这个值可能不为NULL,handler的返回值会决定thread_fn是否会调用到,参见上一节*soc视角中__handle_irq_event_percpu中switch case IRQ_WAKE_THREAD的处理*/
action- >handler = handler;
action- >thread_fn = thread_fn;
....
/*构建的关键*/
retval = __setup_irq(irq, desc, action);
....
}
//irq/manage.c
static int__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new){
...... //flag 等各种检查合法性
/*线程化时模板中,传入的thread_fn, 这里创建内核thread,
*从判断条件可见,如果是共享中断,只会在第一注册的函数里面创建一次内核线程
*/
if (new- >thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new- >secondary) {
ret = setup_irq_thread(new- >secondary, irq, true);
if (ret)
goto out_thread;
}
}
if (!desc- >action) {//第一次安装,需要申请resources
ret = irq_request_resources(desc);
if (ret) {
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\\n", new- >name, irq, desc- >irq_data.chip- >name);
goto out_bus_unlock;
}
}
old_ptr = &desc- >action; /*添加到对应的irqaction*/
old = *old_ptr;
if (old) { /* 多个设备共享中断*/
.... /*共享中断的各种限制性检查,与共享中断中其它设置需要保持一致*/
/*在内核工程师角度我们分析的时候:
*__handle_irq_event_percpu 处理具体的信号函数的时候会有一个irqaction链表的遍历,这个遍历就是我们加入的共响中断的情形
*/
do {
/** Or all existing action- >thread_mask bits,
* so we can find the next zero bit for this new action.
*/
thread_mask |= old- >thread_mask;
old_ptr = &old- >next;
old = *old_ptr;
} while (old);
}
/*将当前的irqaction加入到对应irq_descs[]数组当中对应当中.至此中断控制器的__handle_irq_event_percpu 中对应的handler数据对接完成。*/
*old_ptr = new;
...
}