在linux 中断简述中,对整个中断框架做了一个简单的概述。其中irq_domain在中断中的位置,也做了概述。记下来分析irq_domain的具体使用。
1.irq doamin创建申请
申请一个irq doamin,可使用的接口如下,这些接口调用的核心接口为 __irq_domain_add。
struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
unsigned int size, unsigned int first_irq,
const struct irq_domain_ops *ops, void *host_data);
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
unsigned int size, unsigned int first_irq,
irq_hw_number_t first_hwirq, const struct irq_domain_ops *ops,
void *host_data);
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size, const struct irq_domain_ops *ops,
void *host_data);
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);
static inline struct irq_domain *irq_domain_add_legacy_isa(
struct device_node *of_node, const struct irq_domain_ops *ops,
void *host_data);
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
const struct irq_domain_ops *ops, void *host_data);
static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode,
unsigned int size, const struct irq_domain_ops *ops,
void *host_data);
static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
const struct irq_domain_ops *ops, void *host_data);
extern struct irq_domain *irq_domain_create_hierarchy(struct irq_domain *parent,
unsigned int flags, unsigned int size, struct fwnode_handle *fwnode,
const struct irq_domain_ops *ops, void *host_data);
static inline struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent,
unsigned int flags, unsigned int size, struct device_node *node,
const struct irq_domain_ops *ops, void *host_data);
1.1 irq_domain_add_simple & irq_domain_add_legacy,这两个接口实现irq domain的创建,且可以绑定用户已经申请的irq desc。不同的是,irq_domain_add_simple如果入参first_irq为0,则不会执行绑定操作,irq_domain_add_legacy则默认irq_base被配置为正确的irq desc。first_irq为用户已经申请过的virq rangs的初始编号。例:
irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 32, numa_node_id());
if (irq_base < 0) {
err = irq_base;
goto err_out;
}
port->domain = irq_domain_add_legacy(np, 32, irq_base, 0, &irq_domain_simple_ops, NULL);
if (!port->domain) {
err = -ENODEV;
goto err_out;
}
1.2 irq_domain_add_hierarchy&irq_domain_create_hierarchy,这两个接口实现的是在父irq domain节点下继续创建一个irq domain。如GPIO中断可以该接口,将GPIO控制器的irq domain挂在GIC中断下。
parent_np = of_irq_find_parent(dev->of_node);
if (!parent_np)
return -ENXIO;
parent_domain = irq_find_host(parent_np);
of_node_put(parent_np);
if (!parent_domain)
return -EPROBE_DEFER;
priv->domain = irq_domain_create_hierarchy(
parent_domain, 0, XXX_GPIO_IRQ_MAX_NUM,
of_node_to_fwnode(dev->of_node), &xxx_gpio_irq_domain_ops, priv);
1.3 __irq_domain_add 申明如下,其中比较关键的几个参数为size, hwirq_max,direct_max。
size: 如果非0的话,则存储hwirq和virq的映射关系的空间将为线性的,关系映射存储地址为unsigned int linear_revmap[ ]。反之则为链式存储,存储地址为struct radix_tree_root revmap_tree。
hwirq_max:定义了此irq_domain的最大子中断号。该值限定在domain域下申请子中断时,最大的子中断号,如果超过该值,则产生一个WARN。
direct_max:该值仅用于不映射的情况下,hwirq即为virq的情况。如果子中断为hwirq的话,direct_max被配置为0。
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data);
通过以上__irq_domain_add分析,便可以理解如下几个封装好的接口。
irq_domain_add_linear、irq_domain_create_linear:Virq与Hwirq的映射关系存储为线性区域的domain创建。其实现如下:
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size,
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);
}
irq_domain_add_tree、irq_domain_create_tree:与上面的接口不一样的是, 该接口的实现的映射关系存储为基数树radix_tree方式(基数树相关算法不详述),其映射关系以数的关系建立,可实现映射关系动态插入和删除,一般用于中断数目较多,且应用到的数目较少的情况,如GIC控制器实现了500个中断,但通常应用场景只使用了80个中断的情况。其实现如下:
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);
}
irq_domain_add_nomap: 不建立virq和hwirq的映射关系,hwirq即为virq。direct_max为max_irq,其实现如下:
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_add_legacy_isa:部分传统的ISA控制器只能使用预留的virq 0~15作为中断,所以内核提供相关的该接口,方便ISA控制器注册中断号0~15,NUM_ISA_INTERRUPTS值为16。其实现如下:
static inline struct irq_domain *irq_domain_add_legacy_isa(
struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops,
host_data);
}
2. 常用的Virq中断申请
irq_create_mapping:根据中断所属的irq_domain,直接map出来Virq。该接口的实现为先查询中断是否已经map,如果没有map则alloc一个irq desc,然后将desc的virq在domain中建立映射关系。
of_irq_get:使用dts的配置申请irq,首先找到interrput-parent的节点,如果domain中没有map这个中断号,则会调用irq_create_mapping接口去申请一个Virq,并绑定在父domain中,建立其map关系。
platform_get_irq:当device为一个platform device时,可通过该接口申请一个virq。该接口底层根据device的配置去申请中断。如果配置dts,则调用of_irq_get接口申请;如果创建device时,预先配置Virq到resource中的IORESOURCE_IRQ类型,则将resource中的值读出来返回;另外如果配置了acpi方式,也会使用acpi_irq_get等方式去申请Virq(此处不描述)。
gpio_to_irq:顾名思义将根据GPIO号获取一个virq。需要GPIO控制驱动端实现gpio_chip的to_irq的功能,有些驱动做在pinctrl端,有些做在gpio控制器端。
pci_irq_vector: 获取pci dev下的Virq,通常为msi或msi-X中断,pci dev的中断Virq会在dev枚举时,linux驱动框架提前map好(过程请参考pcie相关流程),此接口即返回对应的Virq。
pci_request_irq:不关心Virq,直接注册对应pci dev的中断服务,其核心是request_threaded_irq,其中Virq的参数使用pci_irq_vector来获取。