上篇文章我們從內(nèi)核工程師的角度剖析了內(nèi)核的外部中斷,這節(jié)我們從BSP工程師的角度剖析一下外部中斷。
外部中斷驅(qū)動架構(gòu)
HARDIRQ 部分:
也就是我們在上節(jié)內(nèi)容中看到的,調(diào)用BSP工程師注冊的handler的部分,它的上下文在中斷上下文中,所以不能做帶有"休眠"的動作。
SOFTIRQ 部分:
就是我們通常說的下半部分,通常作為BSP工程師要處理的詳細(xì)例程的地方。
BSP工程師外部驅(qū)動模板
上半部調(diào)用模板
/*中斷處理上半部分*/
irqreturn_t xxx_interrupt(int irq, void *dev_id) {
...
/* 中斷處理下半部框架調(diào)用,常見的有下面幾種:
* 1. tasklet 它的執(zhí)行額上下文是軟中斷,執(zhí)行時機通常是上半部返回時
* 2. 工作隊列,與tasklet類似,但是工作隊列的執(zhí)行上下文是內(nèi)核線程,因此可以調(diào)度和睡眠。
* 3. Softirq ,tasklet是基于softirq實現(xiàn)的,軟中斷屬于原子上下文的一種,因此函數(shù)不允許睡眠。
* 4. 線程化的xxx_thread_fn, 它是基于內(nèi)核線程創(chuàng)建的,因此可以做休眠動作。
*/
...
/*這個返回值很重要,決定了線程化時傳進去的irq_handler_t thread_fn 是否會被調(diào)用
* 可以參考上節(jié)課__handle_irq_event_percpu 函數(shù)中電信號處理完,調(diào)用完BSP工程師
*handler后,判斷返回值的switch(第72行)里面執(zhí)行的case*/
return IRQ_WAKE_THREAD;
}
//線程化模板時,傳遞給額需要創(chuàng)建的內(nèi)核線程處理函數(shù)。即線程化的下半部。
irqreturn_t xxx_thread_fn(int irq, void *dev_id){
.....
}
/*設(shè)備驅(qū)動加載模塊*/
int __init xxx_init(void){
...
/*申請中斷*/
result = request_irq(xxx_irq,xxx_interrupt,0,”xxx”,NULL);
//或者使用線程化模板申請中斷,我們稍后通過剖析源代碼可知道 request_irq 與線程化的區(qū)別就是傳遞額xxx_thread_fn是否為空
//result = request_threaded_irq(xxx_irq,xxx_interrupt,xxx_thread_fn,irqflag”xxx”,NULL);
....
return IRQ_HANDLERD;
}
/**設(shè)備驅(qū)動卸載模塊*/
void __exit xxx_exit(void){
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
}
下半部調(diào)用模板
//工作隊列模板
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);
...
}
/*設(shè)備驅(qū)動加載模塊*/
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注冊過程實質(zhì)就是如何和我們上節(jié)內(nèi)容中irq_descs[] 數(shù)組中對應(yīng)的irqdesc中的action建立數(shù)據(jù)關(guān)系。
//include/linux/interrupt.h
//線程化直接調(diào)用的方法,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 上半部注冊函數(shù), 通過該函數(shù),驅(qū)動程序中安裝一個設(shè)備中斷服務(wù)例程。
//handler就是我們通常說的ISR例程。ISR由驅(qū)動程序?qū)崿F(xiàn),即BSP工程師實現(xiàn)。
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,內(nèi)核線程函數(shù)為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);//關(guān)鍵函數(shù),通過irq查詢irq_desc, 就是上節(jié)內(nèi)容中看到的內(nèi)核中維護irq_desc數(shù)組
....
if (!handler) {
if (!thread_fn)
return -EINVAL;
//當(dāng)為線程化時,如果上半部handler為null時,系統(tǒng)會默認(rèn)給一個函數(shù),返回的值就是IRQ_WAKE_THREAD,調(diào)用起線程化的下半部內(nèi)核函數(shù)
handler = irq_default_primary_handler;
}
...
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
....
/*通過request_irq API的,這個默認(rèn)為NULL, 如果是線程化,這個值可能不為NULL,handler的返回值會決定thread_fn是否會調(diào)用到,參見上一節(jié)*soc視角中__handle_irq_event_percpu中switch case IRQ_WAKE_THREAD的處理*/
action- >handler = handler;
action- >thread_fn = thread_fn;
....
/*構(gòu)建的關(guān)鍵*/
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, 這里創(chuàng)建內(nèi)核thread,
*從判斷條件可見,如果是共享中斷,只會在第一注冊的函數(shù)里面創(chuàng)建一次內(nèi)核線程
*/
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; /*添加到對應(yīng)的irqaction*/
old = *old_ptr;
if (old) { /* 多個設(shè)備共享中斷*/
.... /*共享中斷的各種限制性檢查,與共享中斷中其它設(shè)置需要保持一致*/
/*在內(nèi)核工程師角度我們分析的時候:
*__handle_irq_event_percpu 處理具體的信號函數(shù)的時候會有一個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);
}
/*將當(dāng)前的irqaction加入到對應(yīng)irq_descs[]數(shù)組當(dāng)中對應(yīng)當(dāng)中.至此中斷控制器的__handle_irq_event_percpu 中對應(yīng)的handler數(shù)據(jù)對接完成。*/
*old_ptr = new;
...
}