在一个系统中,中断时常发生,而且线程调度也是由一个硬件定时器时时刻刻发出中断来支撑的。可以说中断就是linux系统的灵魂。
由于中断来源很多,加上一个硬件系统中会存在许多中断控制器级联,因此会产生中断管理上的麻烦,Linux管理中断也是比较复杂,用到了很多数据结构,如果一开始就深究这些数据结构实例如何组织,只能是事倍功半,因此先进行情景分析,看看当一个中断在设备产生后,会发生什么事情。
一、中断控制器的级联情况
下面一张图可以涵盖两种级联情况
图中的GIC、GPC、GPIO控制器都能作为中断控制器,其中我认为GIC才是硬件上真正意义的中断控制器,而其他两个之所以能被称为中断控制器,是因为他们也需要分辨下一级的中断源。
还可以看到这里基本忽略了SGI和PPI中断,因为这些中断很可能不来自于设备。
一对一的级联
从GPC到GIC的中断,是一对一的。这种有个特点就是GIC发生了中断后,无需判断是下一级哪个GPC的哪个中断发生了。举个例子,如果是GIC 的128号中断发生了,无需再判断是GPC哪个中断传递给了GIC的128号中断,直接就能断定是GPC对应的128号中断产生了。
多对一的级联
从GPIO到GPC的5号中断,是多对一的关系。这个源自于GPIO的一个特点,就是有可能多个GPIO信号只能联合产生一个中断给到上一级中断控制器,这里上一级的中断控制器就是GPC。举个例子,假如GIC发生了中断,读取GIC寄存器知道发生了GIC的122号中断,那么也就知道了是GPC的5号中断产生了,已知GPIO会导致GPC5号中断产生,但是是哪个GPIO的产生呢?那么就需要在GPIO控制器的驱动程序中分辨一下。
以上两种级联的情况,决定了当一个中断产生后,途径的中断控制器的中断执行过程。
二、linux对中断号的管理
通常如果一个设备要作为一个中断源,会在设备树指定使用到哪个中断控制器,使用到这个中断控制器里面的第x个中断。但是这个数字并不能作为唯一标识这个中断源的号码,原因在于同一个中断源,在gpio上会表示x号中断,而在gpc上面,则可能是y号中断。
举个例子,比如有个按键key使用到了gpio中断号为4的,gpio控制器会向上gpc产生一个的中断号为5,同一个中断,一下子从4变成5,那么我们指定的4 和5都具有很强的硬件关系,并不能给到linux去标识唯一的中断。
因此,linux中的中断号称为linux irq,是一个系统软件上的索引。在编写设备驱动的时候,都会根据这个irq去申请中断并且注册中断服务函数。
linux irq如何转换?
linux irq实际上是在系统初始化,在解析设备树的时候根据在设备树定义的中断信息将和硬件相关的中断号转化成linux irq。
以上是对于按键编写的设备树节点,使用到GPIO1中断控制器,并且用到了GPIO的18号中断,触发中断类型是双边沿。那么在解析设备树的时候,18的硬件中断号就会被转换成唯一标识这个中断的linux irq。至于解析过程涉及代码比较多,这里只是情景分析,就不详细追代码了。
三、中断描述符
这里还要介绍一下中断描述符这个数据结构。一个linux irq对应一个中断描述符irq_desc,通过linux irq就能找到对应的中断描述符。看一下这个数据结构可以大概知道中断描述符有什么用
irq_desc
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
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 */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;