irq_desc[]数组是linux内核中用于维护IRQ资源的管理单元,它存储了某IRQ号对应的哪些处理函数,属于哪个PIC管理、来自哪个设备、IRQ自身的属性、资源等,是内核中断子系统的一个核心数组,习惯上称其为“irq数组”(个人爱好,下标就irq号)。本篇博客着重学习irq_desc[]数组的一些操作的过程和方法,如初始化、中断处理、中断号申请、中断线程等,而对于辅助性的8259A和APIC等设备的初始化过程,不详细讨论,对于某些图片或代码,也将其省略掉了。

    本文中出现的irq_desc->和desc->均表示具体的irq数组变量,称其中的一个个体为irq_desc[]数组元素,描述个体时也直接时用字符串desc。为了区别PIC的handle和driver的handle,将前者称为中断处理函数(对应desc->handle_irq,实际上对应handle_xxx_irq()),而将后者称为中断处理操作(对应desc->action)。本文中将以irq_descp[]数组为操作对象的层称为irq层。本文使用的内核代码版本为3.10.9。

    一篇好的博客应该是尽量多的说明,配少量的核心代码,这里偷懒了,很多部分实际是代码分析的过程,也没有省略掉。本篇博客耗时48小时。

一、irq_desc结构和irq_desc[]数组

    irq_desc[]数组,在kernel/irq/irqdesc.c中声明,用于内核管理中断请求,例如中断请求来自哪个设备,使用什么函数处理,同步资源等:

1
2
3
4
5
6
7
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq = handle_bad_irq,
        .depth      = 1,
        .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }  
};

    整体上,关于irq_desc结构体,如下图所示:irq_desc

 

    struct irq_desc结构体(以前的版本结构体的名字是irq_desc_t)定义如下所示(简化过,include/linux/irqdesc.h)。大部分成员都是辅助性的,关键的成员是irq_data、handle_irqs、action、depth、lock、istat,所谓irq_desc[]数组的初始化,看其主要成员的初始化的过程,在这里做简单的说明:

  • action指针指向具体的设备驱动提供的中断处理操作,就是所为的ISR,action本身是一个单向链表结构体,由next指针指向下一个操作,因此action实际上是一个操作链,可以用于共享IRQ线的情况。
  • handle_irq是irq_desc结构中与PIC相关的中断处理函数的接口,通常称作”hard irq handler“。此函数对应了PIC中的handle_xxx_irq()系列函数(xxx代表触发方式),do_IRQ()就会调用该函数,此函数最终会执行desc->action。
  • irq_data用于描述PIC方法使用的数据,irq_data下面有两个比较重要的结构:chip和state_use_accessors,前者表示此irq_desc[]元素时用的PIC芯片类型,其中包含对该芯片的基本操作方法的指针;后者表示该chip的状态和属性,其中有些用于判断irq_desc本身应该所处的状态。
  • lock用于SMP下不同core下的同步。
  • depth表示中断嵌套深度,也即一个中断打断了几个其他中断。
  • istate表示该desc目前的状态,将在“六、istate状态”中描述。
1
2
3
4
5
6
7
8
9
10
11
12
13
struct irq_desc {
    struct irq_data     irq_data;
    irq_flow_handler_t  handle_irq;
    ...
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    unsigned int        depth;      /* nested irq disables */
    raw_spinlock_t      lock;
    ...
    struct module       *owner;
    const char      *name;
} ____cacheline_internodealigned_in_smp;

   这里还是看一下irqaction结构体,action的handler是具体的中断服务程序,next指针用于指向同一个链上的后一个的irqaction,thread_fn用于描述软中断处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef irqreturn_t (*irq_handler_t)(int, void *);
 
struct irqaction {
    irq_handler_t       handler;
    void            *dev_id;
    void __percpu       *percpu_dev_id;
    struct irqaction    *next;
    irq_handler_t       thread_fn;
    struct task_struct  *thread;
    unsigned int        irq;
    unsigned int        flags;
    unsigned long       thread_flags;
    unsigned long       thread_mask;
    const char      *name;
    struct proc_dir_entry   *dir;
} ____cacheline_internodealigned_in_smp;

    这意味着所有的驱动在写中断处理函数时,必须以irqreturn_t为类型:

1
2
3
4
5
6
7
8
// intel e1000
static irqreturn_t e1000_intr(int irq, void *data);
// acpi
static irqreturn_t acpi_irq(int irq, void *dev_id)
// hd
static irqreturn_t hd_interrupt(int irq, void *dev_id)
// ac97
static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev)

    在这里,很容易产生一个问题,就是驱动程序处理的数据在哪?总要有些数据要处理,是从void参数吗?那么这个数据怎么获取的?handle_irq_event_percpu()函数里有具体的action的调用方式:

1
res = action->handler(irq, action->dev_id);

    那么,void *参数来自action->dev_id,而dev_id是驱动程序注册时,调用request_irq()函数传递给内核的。而这个dev_id通常指向一个device设备,驱动程序就通过该device设备将需要的数据接收上来,并进行处理。