中断映射的大体过程如下:
irq_of_parse_and_map
static int bcm2835_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int ret = 0; struct resource *iomem; struct bcm2835_mbox *mbox; mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); if (mbox == NULL) return -ENOMEM; spin_lock_init(&mbox->lock); ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), bcm2835_mbox_irq, 0, dev_name(dev), mbox); if (ret) { dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", ret); return -ENODEV; }
irq_of_parse_and_map(dev->of_node, 0)这是我们比较常用的方法。 dev->of_node这是dts解析生成的结构体数据,0 这是中断数组下标表示你要映射的第几个中断。现在来看源码
1.irq_of_parse_and_map
unsigned int irq_of_parse_and_map(struct device_node *dev, int index) { struct of_phandle_args oirq; //解析一个irq,读取其配置值 if (of_irq_parse_one(dev, index, &oirq)) return 0; //获取映射后的irq return irq_create_of_mapping(&oirq); }
2.of_irq_parse_one int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq) { struct device_node *p; const __be32 *intspec, *tmp, *addr; u32 intsize, intlen; int i, res; pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index); /* OldWorld mac stuff is "special", handle out of line */ if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC) return of_irq_parse_oldworld(device, index, out_irq); /* Get the reg property (if any) */ addr = of_get_property(device, "reg", NULL); /* Try the new-style interrupts-extended first */ res = of_parse_phandle_with_args(device, "interrupts-extended", "#interrupt-cells", index, out_irq); if (!res) return of_irq_parse_raw(addr, out_irq); /* Get the interrupts property */ //获取该设备的interrupts属性,反正属性值的地址,及数据大小 intspec = of_get_property(device, "interrupts", &intlen); if (intspec == NULL) return -EINVAL; //获得数据个数 intlen /= sizeof(*intspec); pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen); /* Look for the interrupt parent. */ p = of_irq_find_parent(device); if (p == NULL) return -EINVAL; /* Get size of interrupt specifier */ //解析interrupt-cells 属性,取得一个interrupt有几个成员 tmp = of_get_property(p, "#interrupt-cells", NULL); if (tmp == NULL) { res = -EINVAL; goto out; } intsize = be32_to_cpu(*tmp); pr_debug(" intsize=%d intlen=%d\n", intsize, intlen); /* Check index */ //如果下标过大超过数据大小,将出错 if ((index + 1) * intsize > intlen) { res = -EINVAL; goto out; } /* Copy intspec into irq structure */ 这里根据我们传递的数组下标作位移,index是具体下票,intsize为每个interrupt成员数据个数,也就是整数个数。指针按这个offset进行位移后,就会指向我们需要的中断数据 intspec += index * intsize; out_irq->np = p; out_irq->args_count = intsize; for (i = 0; i < intsize; i++) //将这个具体的中断属性值保存到out_irq中 out_irq->args[i] = be32_to_cpup(intspec++); /* Check if there are any interrupt-map translations to process */ res = of_irq_parse_raw(addr, out_irq); out: of_node_put(p); return res; }
上面代码主要目地就是从dts配置中获取具体的中断配置信息,获取信息后下一步就是映射了
3.irq_create_of_mapping unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) { struct irq_fwspec fwspec; //下面这个函数就是数据转移而已,都现成的数据保存到fwspec of_phandle_args_to_fwspec(irq_data, &fwspec); //映射 return irq_create_fwspec_mapping(&fwspec); } 4.irq_create_fwspec_mapping unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec) { struct irq_domain *domain; irq_hw_number_t hwirq; unsigned int type = IRQ_TYPE_NONE; int virq; if (fwspec->fwnode) domain = irq_find_matching_fwnode(fwspec->fwnode, DOMAIN_BUS_ANY); else domain = irq_default_domain; //这里用的是irq_default_domain,这个是由外部进行注册得到的。主要用来解析中断配置 if (!domain) { pr_warn("no irq domain found for %s !\n", of_node_full_name(to_of_node(fwspec->fwnode))); return 0; } //调用注册的irq_default_domain的回调函数得到硬件中断和中断的类型 if (irq_domain_translate(domain, fwspec, &hwirq, &type)) return 0; if (irq_domain_is_hierarchy(domain)) { /* * If we've already configured this interrupt, * don't do it again, or hell will break loose. */ virq = irq_find_mapping(domain, hwirq); if (virq) return virq; virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec); if (virq <= 0) return 0; } else { /* Create mapping */ //创建映射,下面具体来看看这个函数 virq = irq_create_mapping(domain, hwirq); if (!virq) return virq; } /* Set type if specified and different than the current one */ if (type != IRQ_TYPE_NONE && type != irq_get_trigger_type(virq)) //直译中断类型信息 irq_set_irq_type(virq, type); return virq; }
irq_find_mapping
当中断经过中断控制器到达CPU后,Linux会首先通过irq_find_mapping()函数,根据物理中断号"hwirq"的值,查找上文讲到的包含映射关系的radix tree或者线性数组,得到"hwirq"对应的虚拟中断号"irq"。
unsigned int irq_find_mapping(struct irq_domain *domain, irq_hw_number_t hwirq)
{
struct irq_data *data;
//线性映射的查找
if (hwirq < domain->revmap_size)
return domain->linear_revmap[hwirq];
//radix tree映射的查找
rcu_read_lock();
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
rcu_read_unlock();
return data ? data->irq : 0;
}
__irq_alloc_descs
irq_create_mapping
->irq_domain_alloc_descs
->irq_alloc_descs_from
->irq_alloc_descs
->__irq_alloc_descs
上面是具体的调用过程,只看最后是如何进行映射的
int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node, struct module *owner) { int start, ret; if (!cnt) return -EINVAL; if (irq >= 0) { if (from > irq) return -EINVAL; from = irq; } else { /* * For interrupts which are freely allocated the * architecture can force a lower bound to the @from * argument. x86 uses this to exclude the GSI space. */ from = arch_dynirq_lower_bound(from); } mutex_lock(&sparse_irq_lock); //这里就是从数组allocated_irqs中获取一个没有使用的下标作为映射的中断号返回给驱动使用 start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, from, cnt, 0); ret = -EEXIST; if (irq >=0 && start != irq) goto err; if (start + cnt > nr_irqs) { ret = irq_expand_nr_irqs(start + cnt); if (ret) goto err; } bitmap_set(allocated_irqs, start, cnt); mutex_unlock(&sparse_irq_lock); return alloc_descs(start, cnt, node, owner); err: mutex_unlock(&sparse_irq_lock); return ret; } 下面来看看allocated_irqs的定义 static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS); 宏定义位置:include/linux/types.h #define DECLARE_BITMAP(name,bits) \ unsigned long name[BITS_TO_LONGS(bits)]
根据代码得知道,这个数组大小为129
通过以上分析得知,中断映射就是将硬件中断号与allocated_irqs数组下标建立对应关系,下面来看看DTS中中断属性的定义interrupts
gic: interrupt-controller@8d000000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
#redistributor-regions = <1>;
redistributor-stride = <0x0 0x30000>;
reg = <0x0 0x8d000000 0 0x10000>, /* GICD */
<0x0 0x8d100000 0 0x300000>, /* GICR */
<0x0 0xfe000000 0 0x10000>, /* GICC */
<0x0 0xfe010000 0 0x10000>, /* GICH */
<0x0 0xfe020000 0 0x10000>; /* GICV */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
its_totems: interrupt-controller@8c000000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0x8c000000 0x0 0x40000>;
};
};
根据驱动源码里面的文档描述来看
1.一般中断必须包含一个interrupts属性或者一个interrupts-extended属性。或者两者都有,如果两者都有在解析的时候会优先解析。interrupts-extended这个属性仅仅只能用于设备有多个中断父节点
2.interrupt-parent 中断父类,一般是interrupt-controller
3.interrupt-controller标记设备是一个中断控制者
4.#interrupt-cells定义每个中断配置有几个成员
只有一个成员:
Example:
vic: intc@10140000 { compatible = "arm,versatile-vic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10140000 0x1000>; };
sic: intc@10003000 { compatible = "arm,versatile-sic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10003000 0x1000>;
interrupt-parent = <&vic>;
interrupts = <31>; /* Cascaded to vic */ };
代表硬件中断号
两个成员:
成员一代表:硬件中断号 成员二代表中断的类型
- bits[3:0] trigger type and level flags
- 1 = low-to-high edge triggered
- 2 = high-to-low edge triggered
- 4 = active high level-sensitive
- 8 = active low level-sensitive
Example:
i2c@7000c000 { gpioext: gpio-adnp@41 { compatible = "ad,gpio-adnp";
reg = <0x41>;
interrupt-parent = <&gpio>;
interrupts = <160 1>;
gpio-controller;
#gpio-cells = <1>;
interrupt-controller;
#interrupt-cells = <2>;
nr-gpios = <64>; };
sx8634@2b { compatible = "smtc,sx8634";
reg = <0x2b>;
interrupt-parent = <&gpioext>;
interrupts = <3 0x8>;
#address-cells = <1>;
#size-cells = <0>;
threshold = <0x40>;
sensitivity = <7>; }; };
三个成员的要看上面irq_default_domain注册的函数具体是怎么解析的,对于gic来说。
成员一代表使用GIC的方式,成员二代表硬件中断号,成员三代表中断类型
if (intspec[0] == GIC_SHARED)
*out_hwirq = GIC_SHARED_TO_HWIRQ(intspec[1]);
else if (intspec[0] == GIC_LOCAL)
*out_hwirq = GIC_LOCAL_TO_HWIRQ(intspec[1]);
else
return -EINVAL;
*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
当然还有其它的定义,比如下面的
第一个值:中断号
第二个值:触发的类型
第三个值:优先级,0级是最高的,7级是最低的;其中0级的中断系统当做 FIQ处理。
具体是怎么定义的,要看驱动MakeFile文件参与编译是哪个,根据宏开关来确定注册的domain源文件,然后看去解析方式来确认具体的含义