Kernel啓動流程源碼解析 10 init_IRQ

一 init_IRQ
本文以arm,gic-v3兼容的中斷控制器爲例,介紹中斷控制器的初始化過程。

1.0 init_IRQ

定義在arch/arm64/kernel/irq.c中
void __init init_IRQ(void)
{
    irqchip_init();
    if (!handle_arch_irq)
        panic("No interrupt controller found.");
}

void __init irqchip_init(void)
{
    of_irq_init(__irqchip_begin);
}

// 在drivers/irqchip/irq-gic-v3.c中
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);

// 在drivers/irqchip/irqchip.h中
#define IRQCHIP_DECLARE(name,compstr,fn)                \
    static const struct of_device_id irqchip_of_match_##name    \
    __used __section(__irqchip_of_table)                \
    = { .compatible = compstr, .data = fn }

// 在include\asm-generic\vmlinux.lds.h中
    VMLINUX_SYMBOL(__irqchip_begin) = .;                \
    *(__irqchip_of_table)                          \
    *(__irqchip_of_end)

以上三個宏實際上定義了一個
static const struct of_device_id irqchip_of_match_gic_v3 __used __section(__irqchip_of_table) = { .compatible = "arm,gic-v3", .data = gic_of_init }

而irqchip_of_match_gic_v3保存在__irqchip_of_table

device tree中包含中斷控制器node爲
    intc: interrupt-controller@09bc0000 {
        compatible = "arm,gic-v3";
        reg = <0x9bc0000 0x10000>,       /* GICD */ // GIC Distributor interface
              <0x9c00000 0x100000>;      /* GICR * 4 */ // GIC Redistributors
        #interrupt-cells = <3>;
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        interrupt-controller;
        #redistributor-regions = <1>;
        redistributor-stride = <0x0 0x40000>;

        interrupts = <1 9 4>;

        gic-its@09BE0000 {
            compatible = "arm,gic-v3-its";
            msi-contoller;
            reg = <0x9be0000 0x20000>;
        };
    };

1.1 of_irq_init

void __init of_irq_init(const struct of_device_id *matches)
{
    struct device_node *np, *parent = NULL;
    struct intc_desc *desc, *temp_desc;
    struct list_head intc_desc_list, intc_parent_list;

    INIT_LIST_HEAD(&intc_desc_list);
    INIT_LIST_HEAD(&intc_parent_list);

    for_each_matching_node(np, matches) { // 遍歷device tree找到與arm,gic-v3兼容的中斷控制器node
        if (!of_find_property(np, "interrupt-controller", NULL))
            continue;
        /*
         * Here, we allocate and populate an intc_desc with the node
         * pointer, interrupt-parent device_node etc.
         */
        desc = kzalloc(sizeof(*desc), GFP_KERNEL); // 從內存中分配中斷控制器描述符
        if (WARN_ON(!desc))
            goto err;

        desc->dev = np; // 爲中斷控制器描述的device_node賦值
        desc->interrupt_parent = of_irq_find_parent(np);
        if (desc->interrupt_parent == np)
            desc->interrupt_parent = NULL;
        list_add_tail(&desc->list, &intc_desc_list); // 將其添加到中斷控制器描述符鏈表中
    }

    /*
     * The root irq controller is the one without an interrupt-parent.
     * That one goes first, followed by the controllers that reference it,
     * followed by the ones that reference the 2nd level controllers, etc.
     */
    while (!list_empty(&intc_desc_list)) { // 從根中斷控制器開始對所有中斷控制器進行初始化
        /*
         * Process all controllers with the current 'parent'.
         * First pass will be looking for NULL as the parent.
         * The assumption is that NULL parent means a root controller.
         */
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
            const struct of_device_id *match;
            int ret;
            of_irq_init_cb_t irq_init_cb;

            if (desc->interrupt_parent != parent) // 第一次parent等於null,所以初始化根中斷控制器,後續每次初始化一級子中斷控制器
                continue;

            list_del(&desc->list);
            match = of_match_node(matches, desc->dev);
            if (WARN(!match->data,
                "of_irq_init: no init function for %s\n",
                match->compatible)) {
                kfree(desc);
                continue;
            }

            pr_debug("of_irq_init: init %s @ %p, parent %p\n",
                 match->compatible,
                 desc->dev, desc->interrupt_parent);
            irq_init_cb = (of_irq_init_cb_t)match->data; // irqchip_of_match_gic_v3的data指向gic_of_init
            ret = irq_init_cb(desc->dev, desc->interrupt_parent); // 調用gic_of_init
            if (ret) {
                kfree(desc);
                continue;
            }

            /*
             * This one is now set up; add it to the parent list so
             * its children can get processed in a subsequent pass.
             */
            list_add_tail(&desc->list, &intc_parent_list); // 將已經初始化的中斷控制器加入到父中斷控制器鏈表,之後,它的子中斷控制器就可以初始化了
        }

        /* Get the next pending parent that might have children */
        desc = list_first_entry(&intc_parent_list, typeof(*desc), list);
        if (list_empty(&intc_parent_list) || !desc) {
            pr_err("of_irq_init: children remain, but no parents\n");
            break;
        }
        list_del(&desc->list);
        parent = desc->dev;
        kfree(desc);
    }

    list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
        list_del(&desc->list);
        kfree(desc);
    }
err:
    list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
        list_del(&desc->list);
        kfree(desc);
    }
}



1.2 gic_of_init

static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
    void __iomem *dist_base;
    struct redist_region *rdist_regs;
    u64 redist_stride;
    u32 nr_redist_regions;
    u32 typer;
    u32 reg;
    int gic_irqs;
    int err;
    int i;

    dist_base = of_iomap(node, 0); // ioremap(0x9bc0000 0x10000) // Distributor的主要的作用是檢測、控制interrupt source,並分發interrupt source產生的中斷事件分發到指定的一個或者多個CPU interface上。
    if (!dist_base) {
        pr_err("%s: unable to map gic dist registers\n",
            node->full_name);
        return -ENXIO;
    }

    reg = readl_relaxed(dist_base + GICD_PIDR2) & GIC_PIDR2_ARCH_MASK;
    if (reg != GIC_PIDR2_ARCH_GICv3 && reg != GIC_PIDR2_ARCH_GICv4) {
        pr_err("%s: no distributor detected, giving up\n",
            node->full_name);
        err = -ENODEV;
        goto out_unmap_dist;
    }

    if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
        nr_redist_regions = 1;

    rdist_regs = kzalloc(sizeof(*rdist_regs) * nr_redist_regions, GFP_KERNEL);
    if (!rdist_regs) {
        err = -ENOMEM;
        goto out_unmap_dist;
    }

    for (i = 0; i < nr_redist_regions; i++) {
        struct resource res;
        int ret;

        ret = of_address_to_resource(node, 1 + i, &res);
        rdist_regs[i].redist_base = of_iomap(node, 1 + i);
        if (ret || !rdist_regs[i].redist_base) {
            pr_err("%s: couldn't map region %d\n",
                   node->full_name, i);
            err = -ENODEV;
            goto out_unmap_rdist;
        }
        rdist_regs[i].phys_base = res.start;
    }

    if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
        redist_stride = 0;
    // 初始化全局變量gic_data
    gic_data.dist_base = dist_base;
    gic_data.redist_regions = rdist_regs;
    gic_data.nr_redist_regions = nr_redist_regions;
    gic_data.redist_stride = redist_stride;

    /*
     * Find out how many interrupts are supported.
     * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)
     */
    typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
    gic_data.rdists.id_bits = GICD_TYPER_ID_BITS(typer);
    gic_irqs = GICD_TYPER_IRQS(typer); // 640
    if (gic_irqs > 1020)
        gic_irqs = 1020;
    gic_data.irq_nr = gic_irqs;

    gic_data.domain = irq_domain_add_tree(node, &gic_irq_domain_ops,
                          &gic_data);
    gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));

    if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
        err = -ENOMEM;
        goto out_free;
    }

    set_handle_irq(gic_handle_irq); // 設定中斷控制器的irq handler // 中斷髮生時依次調用gic_handle_irq -> generic_handle_irq -> desc->handle_irq(irq, desc)

    gic_chip.flags |= gic_arch_extn.flags;
    if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis() &&
                    !IS_ENABLED(CONFIG_ARM_GIC_V3_ACL))
        its_init(node, &gic_data.rdists, gic_data.domain); // 初始化arm,gic-v3-its

    gic_smp_init(); // 初始化多核系統的非啓動核中斷控制器驅動
    gic_dist_init(); // 初始化GIC Distributor
    gic_cpu_init(); // 初始化啓動核的CPU interface
    gic_cpu_pm_init(); // 初始化GIC電源管理

#ifdef CONFIG_ARM_GIC_PANIC_HANDLER
    atomic_notifier_chain_register(&panic_notifier_list, &gic_panic_blk); // 註冊panic_notifier_list,當系統panic時會調用gic_panic_handler
#endif

    return 0;

out_free:
    if (gic_data.domain)
        irq_domain_remove(gic_data.domain);
    free_percpu(gic_data.rdists.rdist);
out_unmap_rdist:
    for (i = 0; i < nr_redist_regions; i++)
        if (rdist_regs[i].redist_base)
            iounmap(rdist_regs[i].redist_base);
    kfree(rdist_regs);
out_unmap_dist:
    iounmap(dist_base);
    return err;
}


1.3 set_handle_irq

void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return;

    handle_arch_irq = handle_irq;
}

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u64 irqnr;

    do {
        irqnr = gic_read_iar(); // 讀取中斷號

        if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { // SPI和PPI中斷
            int err;
            uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);
            err = handle_domain_irq(gic_data.domain, irqnr, regs); // 進一步調用設備註冊中斷處理函數
            if (err) {
                WARN_ONCE(true, "Unexpected interrupt received!\n");
                gic_write_eoir(irqnr);
            }
            continue;
        }
        if (irqnr < 16) { // IPI cpu核間中斷
            uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);
            gic_write_eoir(irqnr);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs); // 處理IPI // 以後有機會單獨分析
#else
            WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
            continue;
        }
    } while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}


1.4 gic_smp_init

static void gic_smp_init(void)
{
    set_smp_cross_call(gic_raise_softirq); // 設置多核cpu通信的callback函數
    register_cpu_notifier(&gic_cpu_notifier); // 註冊gic_cpu_notifier,當cpu online或offline調用gic_secondary_init
}

static struct notifier_block gic_cpu_notifier = {
    .notifier_call = gic_secondary_init,
    .priority = 100,
};

1.5 gic_dist_init

初始化GIC Distributor。
static void __init gic_dist_init(void)
{
    unsigned int i;
    u64 affinity;
    void __iomem *base = gic_data.dist_base;

    /* Disable the distributor */
    writel_relaxed(0, base + GICD_CTLR); // 禁用GIC Distributor
    gic_dist_wait_for_rwp();

    gic_dist_config(base, gic_data.irq_nr, gic_dist_wait_for_rwp); // 配置GIC Distributor

    /* Enable distributor with ARE, Group1 */
    writel_relaxed(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1,
               base + GICD_CTLR); // 使能GIC Distributor

    /*
     * Set all global interrupts to the boot CPU only. ARE must be
     * enabled.
     */
    affinity = gic_mpidr_to_affinity(cpu_logical_map(smp_processor_id()));
    for (i = 32; i < gic_data.irq_nr; i++)
        writeq_relaxed(affinity, base + GICD_IROUTER + i * 8); // 設置中斷對cpu核的親和性
}


1.6 gic_cpu_init

初始化啓動核的CPU interface。
static void gic_cpu_init(void)
{
    void __iomem *rbase;

    /* Register ourselves with the rest of the world */
    if (gic_populate_rdist())
        return;

    gic_enable_redist(true);

    rbase = gic_data_rdist_sgi_base();

    gic_cpu_config(rbase, gic_redist_wait_for_rwp);

    /* Give LPIs a spin */
    if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis() &&
                    !IS_ENABLED(CONFIG_ARM_GIC_V3_ACL))
        its_cpu_init();

    /* initialise system registers */
    gic_cpu_sys_reg_init();
}


1.7 gic_panic_handler

當系統panic時保存gic的一些關鍵寄存器信息。
static struct notifier_block gic_panic_blk = {
    .notifier_call = gic_panic_handler,
};

static int gic_panic_handler(struct notifier_block *this,
            unsigned long event, void *ptr)
{
    int i;
    void __iomem *base;

    base = gic_data.dist_base;
    for (i = 0; i < 0x400; i += 1)
        gic_data.saved_dist_regs[i] = readl_relaxed(base + 4 * i); // 保存GIC Distributor寄存器值到gic_data

    base = gic_data.dist_base + GICD_IROUTER;
    for (i = 0; i < 0x800; i += 1)
        gic_data.saved_router_regs[i] = readl_relaxed(base + 4 * i); // 保存CPU interface寄存器值到gic_data

    return NOTIFY_DONE;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章