linux 中断映射

中断映射的大体过程如下:

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配置中获取具体的中断配置信息,获取信息后下一步就是映射了

.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源文件,然后看去解析方式来确认具体的含义

 
原文地址:https://www.cnblogs.com/dream397/p/15726374.html