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

     
  • 相关阅读:
    一个有趣的.net程序死锁问题
    腾讯2013年实习生笔试题目(附答案)
    C#函数式程序设计初探基础理论篇
    IE的BUG?
    OpenPetra 以及CentOS Mono 3.0 部署包
    自己封装的内存缓存类DotNet.Caches.Bytecached
    Windows Azure Services安装及故障排查
    接口
    利用SQL Server的扩展属性自动生成数据字典
    CentOS配置ssh无密码登录的注意点
  • 原文地址:https://www.cnblogs.com/dream397/p/15726374.html
Copyright © 2020-2023  润新知