中断管理基础学习笔记 - 2.中断控制器初始化

1. 前言

本专题我们开始学习进程管理部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
本节记录ARM架构下中断是如何管理的,Linux内核中的中断管理机制是如何设计与实现的,以及常用的下半部机制,如软中断、tasklet、workqueue等。本文及后续中断相关笔记均以qemu 5.0.0内嵌平台为例,中断控制器采用GIC-400控制器,支持GIC version2技术规范。本文主要以GIC为例记录中断控制器的初始化流程。

kernel版本:5.10
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2. init_IRQ

kernel启动流程-start_kernel的执行_1.概述一文介绍start_kernel总体流程的时候有对init_IRQ做过简单的说明,本节将对init_IRQ做详细的介绍

void __init init_IRQ(void)
    |--init_irq_stacks();//CONFIG_VMAP_STACK
    |--irqchip_init();//初始化irq控制器,并注册irq_domain
    |      |  //扫描__irqchip_of_table,匹配DTB中定义的中断控制器,匹配成功则调用中断控制器设置的初始化函数
    |      |--of_irq_init(__irqchip_of_table);
    |      |--acpi_probe_device_table(irqchip);
    |--if (system_uses_irq_prio_masking())
            local_daif_restore(DAIF_PROCCTX_NOIRQ);

init_IRQ初始化中断,包括中断栈的初始化,中断控制器的初始化

  1. init_irq_stacks:分配per cpu中断栈。此处定义了CONFIG_VMAP_STACK,则中断栈将从vmalloc区域分配,每个cpu的中断栈指针将保存在全局per-cpu变量irq_stack_ptr中

  2. irqchip_init:初始化irq控制器,并注册irq_domain,其中of_irq_init扫描__irqchip_of_table,匹配DTB中定义的中断控制器,匹配成功则调用中断控制器设置的初始化函数

|- -of_irq_init

在介绍of_irq_init函数之前,首先要澄清它的参数matches,对应init_IRQ中就是__irqchip_of_table,下面我们来看下__irqchip_of_table的来历:

<include/linux/of.h>

#if defined(CONFIG_OF) && !defined(MODULE)
#define _OF_DECLARE(table, name, compat, fn, fn_type)                   \
        static const struct of_device_id __of_table_##name              \
                __used __section("__" #table "_of_table")               \
                 = { .compatible = compat,                              \
                     .data = (fn == (fn_type)NULL) ? fn : fn  }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type)                   \
        static const struct of_device_id __of_table_##name              \
                __attribute__((unused))                                 \
                 = { .compatible = compat,                              \
                     .data = (fn == (fn_type)NULL) ? fn : fn } 
#endif

#define OF_DECLARE_2(table, name, compat, fn) \
                _OF_DECLARE(table, name, compat, fn, of_init_fn_2)

此处定义了CONFIG_OF而未定义MODULE, OF_DECLARE宏声明了struct of_device_id 的结构体变量__of_table##name,它存放在__#table_of_talbe的section中

<include/linux/irqchip.h>
/*
 * This macro must be used by the different irqchip drivers to declare
 * the association between their DT compatible string and their
 * initialization function.
 *
 * @name: name that must be unique across all IRQCHIP_DECLARE of the
 * same file.
 * @compstr: compatible string of the irqchip driver
 * @fn: initialization function
 */
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

根据前面_OF_DECLARE和OF_DECLARE_2的宏定义,IRQCHIP_DECLARE实际声明并定义了struct of_device_id 的结构体变量__of_table_##name,将它存放在__irqchip_of_talbe的section中。
对于每个中断控制器,都会通过IRQCHIP_DECLARE静态声明自己的struct of_device_id 的结构体变量__of_table_##name,并将其加入_irqchip_of_talbe的section中。

IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);

如上为cortex_a15的声明,它的初始化函数为gic_of_init,至此如上语句等价为:

static const struct of_device_id __of_table_cortex_a15_gic              \
                __used __section("__irqchip_of_table")               \
                 = { .compatible = "arm,cortex-a15-gic",                              \
                     .data = gic_of_init}

而不同的中断控制器都将有对应的IRQCHIP_DECLARE声明,它们都位于__irqchip_of_table段,of_irq_init就是遍历__irqchip_of_table段中所有的中断控制器,对其执行相应的中断控制器初始化。

void __init of_irq_init(const struct of_device_id *matches)
    |--const struct of_device_id *match;
    |  struct of_intc_desc *desc
    |  //初始化两个list,分别为dts节点中int-controller属性的list和int-controller-parent属性的list
    |--INIT_LIST_HEAD(&intc_desc_list);
    |--INIT_LIST_HEAD(&intc_parent_list);
    |  //遍历每个中断控制器,为其创建intc_desc, 并设置每个中断控制器的parent
    |--for_each_matching_node_and_match(np, matches, &match)
    |      //如果当前节点不是interrupt-controller或者处于disabled状态,则继续遍历
    |      if (!of_find_property(np, "interrupt-controller", NULL) || !of_device_is_available(np))
    |           continue;
    |      //如果当前节点是interrupt-controller,分配一个struct of_intc_desc的结构体并初始化
    |      desc = kzalloc(sizeof(*desc), GFP_KERNEL);
    |      //中断控制器初始化回调,match->data就是上面分析的__of_table_cortex_a15_gic.data
    |      desc->irq_init_cb = match->data;
    |      //解析当前node的interrupt-parent
    |      desc->interrupt_parent = of_irq_find_parent(np);
    |      //如果是根中断控制器, 将interrupt_parent设为NULL
    |      if (desc->interrupt_parent == np)
    |           desc->interrupt_parent = NULL;
    |      //将该中断控制器加入intc_desc_list中
    |      list_add_tail(&desc->list, &intc_desc_list)
    |--while (!list_empty(&intc_desc_list)) //遍历并初始化所有的中断控制器
    |      //遍历中断控制器对其进行初始化,第一次找到的是root中断控制器
    |      list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list)
    |          //找到具有父节点的中断控制器,第一次parent为NULL,因此找到的是root中断控制器,如GIC
    |          if (desc->interrupt_parent != parent)
    |              continue; 
    |          list_del(&desc->list);
    |          of_node_set_flag(desc->dev, OF_POPULATED);
    |          //执行中断控制器的初始化函数,对root控制器,如GIC的gic_of_init
    |          ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
    |          //将所有具有儿子的中断控制器加入intc_parent_list链表
    |          list_add_tail(&desc->list, &intc_parent_list);
    |      desc = list_first_entry_or_null(&intc_parent_list,typeof(*desc), list);
    |      list_del(&desc->list);
    |      //更新parent为下一级中断控制器
    |      parent = desc->dev;
    |      kfree(desc);

of_irq_init就是执行了各个注册的中断控制器的初始化函数,下面说明gic中断控制器的初始化函数gic_of_init

|- - -gic_of_init

<drivers/irqchip/irq-gic.c>
gic_of_init(struct device_node *node, struct device_node *parent)
    |--struct gic_chip_data *gic;
    |  gic = &gic_data[gic_cnt];//gic_cnt为0
    |--gic_of_setup(gic, node);//根据device node初始化gic
    |--__gic_init_bases(gic, &node->fwnode)
    |      |--for (i = 0; i < NR_GIC_CPU_IF; i++)
    |      |      gic_cpu_map[i] = 0xff;
    |      |  //设置中断顶层处理函数handle_arch_irq为gic_handle_irq,此为中断处理的主函数
    |      |--set_handle_irq(gic_handle_irq);
    |      |  //进一步初始化gic->irq_chip
    |      |--gic_init_chip(gic, NULL, name, true);
    |      |--gic_init_bases(gic, handle)
    |             |  //初始化distributor和cpu interface的物理基地址
    |             |--gic->dist_base.common_base = gic->raw_dist_base; 
    |             |--gic->cpu_base.common_base = gic->raw_cpu_base;
    |             |  //初始化硬中断数目
    |             |--gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
    |             |  gic_irqs = (gic_irqs + 1) * 32;
    |             |  gic->gic_irqs = gic_irqs;
    |             |--if (handle) //DT/ACPI
    |             |      gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);
    |             |  else       //Legacy support
    |             |      irq_base = irq_alloc_descs(16, 16, gic_irqs,numa_node_id());
    |             |      gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,16, &gic_irq_domain_ops, gic);
    |             |--gic_dist_init(gic);//gic的distributor初始化
    |             |--gic_cpu_init(gic);//gic的cpu interface初始化
    |             |--gic_pm_init(gic); 
    |--gic_init_physaddr(node);
    |--gic_of_setup_kvm_info(node);
    |--if (parent)
    |      irq = irq_of_parse_and_map(node, 0);
    |      gic_cascade_irq(gic_cnt, irq);
    |--if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
    |      gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);
    \--gic_cnt++

gic_of_init主要完成gic中断控制器的初始化

  1. gic_of_setup:根据device node初始化gic,包括gic->raw_dist_base,gic->raw_cpu_base,gic->percpu_offset等;

  2. __gic_init_bases:初始化全局的中断顶层处理函数handle_arch_irq,初始化中断控制器硬件相关信息gic->irq_chip,创建并注册irq_domain,其中gic->domain->ops初始化为gic_irq_domain_hierarchy_ops

static inline struct irq_domain *irq_domain_create_linear(fwnode,size, ops, host_data)
    |  //Allocate a new irq_domain data structure
    |--__irq_domain_add(fwnode, size, size, 0, ops, host_data)
           |--struct irq_domain *domain;
           |  //注意此处除分配irq_domain外,还分配了(sizeof(unsigned int) * size用于linear_revmap[]成员
           |--domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
           |                         GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
           |  // Fill structure
           |--INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
              mutex_init(&domain->revmap_tree_mutex);
              domain->ops = ops;
              domain->host_data = host_data;
              domain->hwirq_max = hwirq_max;
              domain->revmap_size = size;
              domain->revmap_direct_max_irq = direct_max;
              list_add(&domain->link, &irq_domain_list);

在这里插入图片描述
关于__irq_domain_add的注释如上,它分配了一个新的irq_domain并连入全局irq_domain_list链表,从而完成了注册。

参考文档

奔跑吧,Linux内核

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值