Linux内核外部中断解析(下)

嵌入式技术

1368人已加入

描述

上篇文章我们从内核工程师的角度剖析了内核的外部中断,这节我们从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;   
      ...
}
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分