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源文件,然后看去解析方式來確認具體的含義

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM