Linux中断子系统分析之(一):整体框架
Linux中断子系统分析之(二):通用的中断处理
Linux中断子系统分析之(三):irq domain
Linux中断子系统分析之(四):驱动程序申请中断
irq domain的作用前面也要提到过,来张图:
irq domain结构体的定义如下所示:
/* include/linux/irqdomain.h */
struct irq_domain {
/* 系统中的irq_domain都会挂入全局链表irq_domain_list,link就是挂入该链表的节点 */
struct list_head link;
//IRQ domain的name
const char *name;
//irq_domain操作集
const struct irq_domain_ops *ops;
void *host_data;
unsigned int flags;
......
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent; /* //支持级联的话,指向父设备 */
#endif
/* 该domain中最大的硬件中断号 */
irq_hw_number_t hwirq_max;
//直接映射的最大hwirq,对于线性、Radix Tree map映射该域为0
unsigned int revmap_direct_max_irq;
/* 线性映射的size,对于Radix Tree map和no map,该值等于0 */
unsigned int revmap_size;
/* 对于Radix Tree map,revmap_tree指向Radix tree的root node */
struct radix_tree_root revmap_tree;
/* 线性映射使用的lookup table */
unsigned int linear_revmap[];
};
struct irq_domain_ops {
/* match是判断一个指定的device_node(node参数)是否和一个irq domain匹配(d参数),
如果匹配的话,返回1 */
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
//和match的功能是一样的,系统会优先使用select函数
int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token);
/* 创建或者更新硬件中断号(hw参数)和软件中断号(virq参数)的映射关系 */
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
//map和unmap是操作相反的函数
void (*unmap)(struct irq_domain *d, unsigned int virq);
/* 在DTS文件中,各个使用中断的device node会通过一些属性(interrupts、interrupt-parent属性)
来提供中断信息,以便kernel可以正确的进行driver的初始化动作。
而xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译
成硬件中断号(保存在out_hwirq参数)和中断trigger类型(保存在out_type)
*/
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY //支持中断控制器级联
//分配IRQ描述符和中断控制器相关资源
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg);
//释放IRQ描述符和中断控制器相关资源
void (*free)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs);
......
//和xlate的功能是一样的,系统会优先使用translate
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
#endif
};
每个中断控制器都对应一个irq domain。IRQ domain支持三种映射方式:linear map(线性映射),Radix tree map(树映射),no map(直接映射)。
根据不同的场景选择不同的映射方式:
-
linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,硬件中断号连续,可以选择线性映射;
-
Radix tree map:硬件中断号可能很大,可以选择树映射;
-
no map:硬件中断号直接就是Linux的中断号;
三种映射的方式如下图:
图中描述了三个中断控制器,对应到三种不同的映射方式,各个控制器的硬件中断号可以一样,最终在Linux内核中映射的中断号是唯一的。
注册irq domain
在中断控制器的驱动程序中,都要注册irq domain,不同的映射方式,调用的api也不同。
对于线性映射,其接口API如下:
/* include/linux/irqdomain.h */
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size, //该interrupt domain支持多少IRQ
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}
对于Radix Tree map,其接口API如下:
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}
在系统中只有一个中断控制器的情况下,可以不需要进行映射。对于这种类型的映射,其接口API如下:
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
unsigned int max_irq,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}
注册irq domain这类接口的逻辑很简单,根据自己的映射类型,初始化struct irq_domain中的各个成员,最后将该irq domain挂入irq_domain_list的全局列表。
下面来分析外设在设备树上描述的中断信息怎么被解析的。
一个外设想使用中断,得说明它接在哪个中断控制器上,硬件中断号是多少,中断触发类型等信息。
比如下列这个例子(下面的源码分析,也是以这个为例):
interrupt-parent属性描述的信息是该外设的interrupt request line连接到了哪一个中断控制器上;interrupts属性描述了该外设的中断信息,如硬件中断号、中断触发类型等,可以描述多个中断的信息。
来看看中断控制器的设备节点:
有nterrupt-controller属性,表明是一个中断控制器;#interrupt-cells属性为2,用2个整数描述一个中断。
在设备树上描述号中断信息后,驱动程序可以调用of_irq_get函数获得软件中断号了。
先来张of_irq_get的执行流程图:
of_irq_get函数定义如下:
int of_irq_get(struct device_node *dev, int index) //index获取哪个中断信息
{
int rc;
struct of_phandle_args oirq;
struct irq_domain *domain;
//解析设备节点的interrupt-parent、interrupts属性,解析结果保存在oirq
rc = of_irq_parse_one(dev, index, &oirq);
if (rc)
return rc;
/* 根据中断控制器的device_node,在irq_domain_list链表里找该控制器的irq_domain
会调用irq_domain->irq_domain_ops->select/match函数,判断device_node是否与控制器的
irq_domain匹配
*/
domain = irq_find_host(oirq.np);
if (!domain)
return -EPROBE_DEFER;
//重点要分析的函数,函数返回软件中断号
return irq_create_of_mapping(&oirq);
}
struct of_phandle_args {
struct device_node *np; //指向中断控制器的device_node
int args_count;
uint32_t args[MAX_PHANDLE_ARGS];
};
of_irq_parse_one函数可以总结如下:
irq_create_of_mapping函数会返回软件中断号,里面涉及硬件中断号到软件中断号的映射等,下面就来分析该函数:
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
struct irq_fwspec fwspec;
of_phandle_args_to_fwspec(irq_data, &fwspec); //fwspec由irq_data初始化
/*
struct irq_fwspec {
struct fwnode_handle *fwnode; //中断控制器节点np->fwnode
int param_count; // 2
u32 param[IRQ_DOMAIN_IRQ_SPEC_PARAMS];//param[0] = 14, param[1] = IRQ_TYPE_EDGE_FALLING
};
*/
return irq_create_fwspec_mapping(&fwspec);
}
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
struct irq_domain *domain;
struct irq_data *irq_data;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
int virq;
//通过irq_fwspec找到中断控制器的irq_domain,在irq_domain_list链表里找
if (fwspec->fwnode) {
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
if (!domain)
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
} else {
domain = irq_default_domain;
}
......
/* 调用irq_domain->irq_domain_ops->translate/xlate,解析fwspec->param
解析得到的硬件中断号保存在hwirq,中断触发类型保存在type
如果驱动程序未提供irq_domain->irq_domain_ops->translate/xlate函数,则hwirq为第一个
参数fwspec->param[0] (14)
*/
if (irq_domain_translate(domain, fwspec, &hwirq, &type))
return 0;
......
/* 通过硬件中断号找到软件中断号,函数返回0表示硬件中断号与软件中断号还未映射,需要建立映
射关系
*/
virq = irq_find_mapping(domain, hwirq);
if (virq) {
if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
return virq;
if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {
irq_data = irq_get_irq_data(virq);
if (!irq_data)
return 0;
irqd_set_trigger_type(irq_data, type); //设置中断触发类型
return virq; //返回软件中断号
}
......
}
/* 执行到这里说明还未建立映射关系 */
if (irq_domain_is_hierarchy(domain)) {
/* 申请一个未使用的软件中断号,内核通过位图记录系统中软件中断号的使用情况;
会调用到irq_domain->irq_domain_ops->alloc函数,设置软件中断号应的中断描述符
之后建立映射关系,对于线性映射:irq_domain->linear_revmap[hwirq] = virq
*/
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
......
} else {
......
}
......
return virq;
}