上篇文章我們從內核工程師的角度剖析了內核的外部中斷,這節我們從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;
...
}