一 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;
}