Linux中断子系统分析之(一):整体框架
Linux中断子系统分析之(二):通用的中断处理
Linux中断子系统分析之(三):irq domain
Linux中断子系统分析之(四):驱动程序申请中断
当有外部中断发生时,硬件会根据异常向量表,找到中断的入口地址,开始调用由操作系统提供的通用中断处理函数。而系统初始化阶段的异常向量表的设置、执行中断服务例程前后的上下文保护与恢复等等,这部分是CPU架构相关代码。
通用中断处理函数,核心是围绕着struct irq_desc来展开。在linux kernel中,对于每一个外设的中断都用struct irq_desc来描述,我们称之中断描述符,保存了中断的相关信息,如硬/软件中断号、中断服务例程等。
struct irq_desc结构体定义如下:
struct irq_desc {
......
struct irq_data irq_data; // 保存软件中断号、irq domain等信息
......
//该中断的通用处理函数
irq_flow_handler_t handle_irq;
......
struct irqaction *action; /* IRQ action list */
......
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
中断描述符中应该会包括底层相关的数据结构,linux kernel中把这些数据组织在一起,形成struct irq_data:
struct irq_data {
u32 mask;
unsigned int irq; //该中断的软件中断号
unsigned long hwirq; //该中断的硬件中断号,仅仅限制在本中断控制器
......
struct irq_chip *chip; //该中断源连接的中断控制器
struct irq_domain *domain; //用于把hwirq映射为软件中断号
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
irq_chip结构体,描述一个中断控制器,定义如下:
struct irq_chip {
struct device *parent_device; //指向父设备,上一级的中断控制器
const char *name; //该中断控制器的name
//启动中断,如果设置成NULL,则默认为enable
unsigned int (*irq_startup)(struct irq_data *data);
//关闭中断,如果设置成NULL,则默认为disable
void (*irq_shutdown)(struct irq_data *data);
//中断使能,如果设置成NULL,则默认为chip->unmask
void (*irq_enable)(struct irq_data *data);
//中断禁止
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data); //中断应答
void (*irq_mask)(struct irq_data *data); //中断源屏蔽
void (*irq_mask_ack)(struct irq_data *data); //应答并屏蔽中断
void (*irq_unmask)(struct irq_data *data); //解除中断屏蔽
void (*irq_eoi)(struct irq_data *data); //中断处理结束后调用
//在SMP中设置CPU亲和力
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
//重新发送中断到CPU
int (*irq_retrigger)(struct irq_data *data);
//设置中断触发类型
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
//使能/禁止电源管理中的唤醒功能
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
......
//获取中断控制器的状态
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
//设置中断控制器的状态
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
......
//标志位
unsigned long flags;
};
当发生中断后,首先获取触发中断的硬件中断号,然后通过irq domain映射为软件中断号,然后通过软件中断号就可以获取对应的中断描述符,调用中断描述符中的handler来进行中断处理。
内核中会有一个数据结构保存了关于所有IRQ的中断描述符信息。如果打开CONFIG_SPARSE_IRQ宏,中断描述符以radix-tree来组织,用户在初始化时进行动态分配,然后再插入radix-tree中,否则中断描述符以数组的形式组织,并且已经分配好。
/* kernel\irq\irqdesc.c */
#ifdef CONFIG_SYSFS
......
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
......
#else /* !CONFIG_SPARSE_IRQ */
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的handle_irq回调函数一般是指向某个中断的通用中断处理函数,即标准中断流控函数,如handle_edge_irq 、handle_fasteoi_irq等。那么具体的外设中断服务例程在哪里呢?在irq_desc的action链表里。为什么用链表?这考虑到共享中断的问题,看下图就明白了。
如图有两个设备接在一条IRQ line上,设备1和设备2产生的中断,其对应的软件中断号都是一样的,也就是说irq_desc结构体的handle_irq回调函数与软件中断号一一对应。这种情况属于多个设备可以通过同一条IRQ line来共享同一个软件中断号。
irq_desc有一成员action,类型为struct irqaction:
struct irqaction {
//指向设备特定的中断服务例程函数的指针
irq_handler_t handler;
/* 调用handler时传给它的参数,在多个设备共享一个irq的情况下特别重要,
设备驱动程序通过dev_id來标识自己 */
void *dev_id;
.....
/*指向下一个action对象,用于多个设备共享同一个irq的情形,此时action通过next构成一个链表*/
struct irqaction *next;
......
} ____cacheline_internodealigned_in_smp;
驱动注册中断服务例程,实际上就是创建与初始化一个irqaction,然后插入irq_desc的action链表。
当发生中断时,执行irq_desc的handle_irq回调函数,回调函数会遍历action中的handler函数,进行具体的中断处理。