Linux kernel的中断子系统
Posted V一叶知秋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux kernel的中断子系统相关的知识,希望对你有一定的参考价值。
Linux kernel的中断子系统
文章目录
内核版本:5.10
中断控制器:gicv3
1、GICV3简介
1.1 GICv3定义的4种中断类型:
SPI,共享外设中断,该中断来源于外设,但是该中断可以对所有的cpu core有效。
PPI,私有外设中断,中断来源于外设,但是该中断只对指定的core有效。
SGI,软中断,用于给其它的core发送中断信号。
LPI,(Locality-specific Peripheral Interrupt) ,自定义外设中断,这个是gicv3特有的中断。
In particular, LPIs are always message-based interrupts, and their configuration is held in tables in memory rather than registers.
NOTE: LPIs are only supported when GICD_CTLR.ARE_NS==1.
1.2 中断号
1.3 GICV3编程模型
对应的寄存器编程接口描述如下:(参考GICv3_Software_Overview_Official_Release_B)
Distributor (GICD_*)
The Distributor registers are memory-mapped, and contain global settings that affect all PEs
connected to the interrupt controller. The Distributor provides a programming interface for:
Interrupt prioritization and distribution of SPIs.
Enabling and disabling SPIs.
Setting the priority level of each SPI.
Routing information for each SPI.
Setting each SPI to be level-sensitive or edge-triggered.
Generating message-based SPIs.
Controlling the active and pending state of SPIs.
Controls to determine the programmers’ model that is used in each Security state (affinity
routing or legacy).
Redistributors (GICR_*)
For each connected PE there is a Redistributor. The Redistributors provides a programming
interface for:
Enabling and disabling SGIs and PPIs.
Setting the priority level of SGIs and PPIs.
Setting each PPI to be level-sensitive or edge-triggered.
Assigning each SGI and PPI to an interrupt group.
Controlling the state of SGIs and PPIs.
Base address control for the data structures in memory that support the associated
interrupt properties and pending state for LPIs.
Power management support for the connected PE.
CPU interfaces (ICC_*_ELn)—(non memory-mapped)
Each Redistributor is connected to a CPU interface. The CPU interface provides a programming
interface for:
General control and configuration to enable interrupt handling.
Acknowledging an interrupt.
Performing a priority drop and deactivation of interrupts.
Setting an interrupt priority mask for the PE.
Defining the preemption policy for the PE.
Determining the highest priority pending interrupt for the PE.
In GICv3 the CPU Interface registers are accessed as System registers (ICC_*_ELn).
Software must enable the System register interface before using these registers. This is controlled by the SRE bit in the ICC_SRE_ELn registers, where “n” specifies the Exception level (EL1-EL3). (这里表明GICV3,CPU interfaces对应的寄存器是作为CPU内部的系统寄存器去访问的,而不是通过memory mapped去访问的)
NOTE: In GICv1 and GICv2 the CPU Interface registers were memory mapped (GICC_*).
NOTE: Software can check for GIC System register support by reading ID_AA64PFR0_EL1 for the PE, see ARM® Architecture Reference Manual, ARMv8, for ARMv8-A architecture profile for details.
1.4 GICV3中断亲和度路由
gicv3使用hierarchy来标识一个具体的cpu core,类似于ipv4,<affinity level 3>.<affinity level 2>.<affinity level 1>.<affinity level 0> 组成一个PE的路由。
每一个core的affinity值可以通过MPDIR_EL1寄存器获取, 每一个affinity占用8bit。
配置对应core的MPIDR值,可以将中断路由到该core上。
四个等级affinity的定义是根据SOC自己的定义。
比如可能affinity3代表socketid,affinity2 代表clusterid, affnity1代表coreid, affnity0代表thread id。
2、GIC的设备树描述
(1)参考rk3399 SDK下面的interrupt controller描述文档
rk3399_kernel/kernel/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.txt
(2)参考标准内核里面的interrupt controller描述文档
kernel/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml
一个gicv3定义的例子如下:
gic: interrupt-controller@2c010000
compatible = "arm,gic-v3";
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
redistributor-stride = <0x0 0x40000>; // 256kB stride
#redistributor-regions = <2>;
reg = <0x0 0x2c010000 0 0x10000>, // GICD
<0x0 0x2d000000 0 0x800000>, // GICR 1: CPUs 0-31
<0x0 0x2e000000 0 0x800000>; // GICR 2: CPUs 32-63
<0x0 0x2c040000 0 0x2000>, // GICC
<0x0 0x2c060000 0 0x2000>, // GICH
<0x0 0x2c080000 0 0x2000>; // GICV
interrupts = <1 9 4>;
gic-its@2c200000
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0x2c200000 0 0x20000>;
;
gic-its@2c400000
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0x2c400000 0 0x20000>;
;
;
- compatible: 用于匹配GICv3驱动
- #interrupt-cells: 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符(-interrupts)中 cell 的个数
- #address-cells , #size-cells, ranges:用于寻址, #address-cells表示reg中address元素的个数,#size-cells用来表示length元素的个数
- interrupt-controller: 表示该节点是一个中断控制器
- redistributor-stride: 一个GICR的大小
- #redistributor-regions: GICR域个数。
- **reg :**GIC的物理基地址,分别对应GICD,GICR,GICC…
- interrupts: 分别代表中断类型,中断号,中断类型, PPI中断亲和, 保留字段。
a为0表示SPI,1表示PPI;b表示中断号(注意SPI/PPI的中断号范围);c为1表示沿中断,4表示电平中断。 - msi-controller: 表示节点是MSI控制器
3、ARM GICV3代码分析
基于内核版本5.10
代码路径:
kernel/drivers/irqchip/irq-gic-v3.c
GICV3的初始化流程:
3.1 irq chip driver声明
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
定义IRQCHIP_DECLARE之后,相应的内容会保存到__irqchip_of_table里边。
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \\
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#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
宏展开过程如下:
--IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
--OF_DECLARE_2(irqchip, gic_v3, "arm,gic-v3", gic_of_init);
--_OF_DECLARE(irqchip, gic_v3, "arm,gic-v3", gic_of_init, of_init_fn_2);
--
static const struct of_device_id __of_table_gic_v3 \\
__used __section(__irqchip_of_table) \\
= .compatible = "arm,gic-v3", \\
.data = gic_of_init
展开以后可以看到,在vmlinux的__irqchip_of_table段中保存了一个of_device_id类型的结构体,其compatible字段为"arm,gic-v3",data指针指向gic_of_init函数。
_irqchip_of_table在vmlinux.lds文件里边被放到了 _irqchip_begin和__irqchip_of_end之间
#ifdef CONFIG_IRQCHIP
#define IRQCHIP_OF_MATCH_TABLE() \\
. = ALIGN(8); \\
VMLINUX_SYMBOL(__irqchip_begin) = .; \\
*(__irqchip_of_table) \\
*(__irqchip_of_end)
#endif
在内核启动初始化中断的函数中,of_irq_init 函数会去查找设备节点信息,该函数的传入参数就是 __irqchip_of_table 段,由于 IRQCHIP_DECLARE 已经将信息填充好了,of_irq_init 函数会根据 “arm,gic-v3” 去查找对应的设备节点,并获取设备的信息。or_irq_init 函数中,如果检测到设备树中存在和"arm,gic-v3"匹配的节点,并且节点下面存在interrupt-controller字段,那么最终会回调 IRQCHIP_DECLARE 声明的回调函数,也就是data指向的 gic_of_init函数,而这个函数就是 GIC 驱动的初始化入口。of_irq_init函数在drivers/of/irq.c中实现。
--drivers/irqchip/irqchip.c
void __init irqchip_init(void)
of_irq_init(__irqchip_of_table);
acpi_probe_device_table(irqchip);
对应arm64架构,在arch/arm64/kernel/irq.c的init_IRQ函数中会调用irqchip_init函数:
void __init init_IRQ(void)
init_irq_stacks();
irqchip_init();
if (!handle_arch_irq)
panic("No interrupt controller found.");
if (system_uses_irq_prio_masking())
/*
* Now that we have a stack for our IRQ handler, set
* the PMR/PSR pair to a consistent state.
*/
WARN_ON(read_sysreg(daif) & PSR_A_BIT);
local_daif_restore(DAIF_PROCCTX_NOIRQ);
init_IRQ则是在内核启动流程中调用,init/main.c的main函数如下:
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
3.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;
int err, i;
dist_base = of_iomap(node, 0);//index为0,映射GICD(GIC Distributor)的寄存器地址空间 ------------- (1)
if (!dist_base)
pr_err("%pOF: unable to map gic dist registers\\n", node);
return -ENXIO;
err = gic_validate_dist_version(dist_base);//检测gic的版本是否是v3或者v4,读GICD_PIDR2寄存器 --------------- (2)
if (err)
pr_err("%pOF: no distributor detected, giving up\\n", node);
goto out_unmap_dist;
if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
//读取设备树中#redistributor-regions的值,3个cpu clusters对应3个GICR域? --------------- (3)
nr_redist_regions = 1;
rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
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("%pOF: couldn't map region %d\\n", node, i);
err = -ENODEV;
goto out_unmap_rdist;
rdist_regs[i].phys_base = res.start;
//映射每一个GICR的基地址 --------- (4)
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
redist_stride = 0;//读取DTS中redistributor-stride的值,redistributor-stride代表GICR域中每一个GICR的大小,正常情况下一个CPU core对应一个GICR(redistributor-stride必须是64KB的倍数),一个cpu cluster中cpu core的个数乘以redistributor-stride的值等于一个GICR域的大小。----------- (5)
gic_enable_of_quirks(node, gic_quirks, &gic_data);
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
redist_stride, &node->fwnode);//gic初始化函数 ------------- (6)
if (err)
goto out_unmap_rdist;
gic_populate_ppi_partitions(node);
if (static_branch_likely(&supports_deactivate_key))
gic_of_setup_kvm_info(node);//虚拟化相关设置
return 0;
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)映射GICD的寄存器地址空间。 通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况, index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。
(2) 验证GICD的版本是否为GICv3 or GICv4。 主要通过读GICD_PIDR2寄存器bit[7:4]. 0x1代表GICv1, 0x2代表GICv2…以此类推。
(3) 通过DTS读取redistributor-regions的值。redistributor-regions代表GICR独立的区域数量(地址连续)。
假设一个64核的arm64 服务器,redistributor-regions=2, 那么64个核可以用2个连续的GICR连续空间表示。
(4) 为一个GICR域 分配基地址。
(5) 通过DTS读取redistributor-stride的值. redistributor-stride代表GICR域中每一个GICR的大小,正常情况下一个CPU对应一个GICR(redistributor-stride必须是64KB的倍数)
(6) 主要处理流程,下面介绍。
(7) 可以设置一组PPI的亲和性。(TODO:分析PPI亲和度的设置过程)
3.3 gic_init_bases流程
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
u32 typer;
int err;
if (!is_hyp_mode_available())
static_branch_disable(&supports_deactivate_key);
if (static_branch_likely(&supports_deactivate_key))
pr_info("GIC: Using split EOI/Deactivate mode\\n");
gic_data.fwnode = handle;
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; //初始化全局结构体static struct gic_chip_data gic_data
/*
* Find out how many interrupts are supported.
*/
typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);//读取GICD_TYPER寄存器的值,后面可以根据typer计算得到所支持的SPI中断号最大值为多少 ------------- (1)
gic_data.rdists.gicd_typer = typer;
gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR),
gic_quirks, &gic_data);
pr_info("%d SPIs implemented\\n", GIC_LINE_NR - 32);
//展开GIC_LINE_NR可以计算得到SPI中断号的最大值,GICD_TYPER寄存器bit[4:0], 如果该字段的值为N,则最大SPI INTID为32(N + 1)-1
pr_info("%d Extended SPIs implemented\\n", GIC_ESPI_NR);
/*
* ThunderX1 explodes on reading GICD_TYPER2, in violation of the
* architecture spec (which says that reserved registers are RES0).
*/
if (!(gic_data.flags & FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539))
gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2);
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);//向系统中注册一个irq domain数据结构 ------------- (2)
gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
gic_data.rdists.has_rvpeid = true;
gic_data.rdists.has_vlpis = true;
gic_data.rdists.has_direct_lpi = true;
gic_data.rdists.has_vpend_valid_dirty = true;
if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist))
err = -ENOMEM;
goto out_free;
irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);// ------------- (3)
gic_data.has_rss = !!(typer & GICD_TYPER_RSS);// ------------- (4)
pr_info("Distributor has %sRange Selector support\\n",
gic_data.has_rss ? "" : "no ");
if (typer & GICD_TYPER_MBIS)
err = mbi_init(handle, gic_data.domain);// ------------- (5)
if (err)
pr_err("Failed to initialize MBIs\\n");
set_handle_irq(gic_handle_irq);// 设定arch相关的irq handler。gic_irq_handle是内核gic中断处理的入口函数 ------------- (6)
gic_update_rdist_properties();
gic_dist_init();
gic_cpu_init();
gic_smp_init();
gic_cpu_pm_init();
if (gic_dist_supports_lpis())
its_init(handle, &gic_data.rdists, gic_data.domain);
its_cpu_init();
else
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(handle, gic_data.domain);
gic_enable_nmi_support();
return 0;
out_free:
if (gic_data.domain)
irq_domain_remove(gic_data.domain);
free_percpu(gic_data.rdists.rdist);
return err;
(1) 确认支持SPI 中断号最大的值为多少,GICv3最多支持1020个中断(SPI+SGI+SPI).GICD_TYPER寄存器bit[4:0], 如果该字段的值为N,则最大SPI INTID为32(N + 1)-1。 例如,0x00011指定最大SPI INTID为127。
(2) 向系统中注册一个irq domain的数据结构. irq_domain主要作用是将硬件中断号映射到IRQ number。 参考第4节:中断域irq_domain以及中断映射
(3) 主要作用是给irq_find_host()函数使用,找到对应的irq_domain。 这里使用 DOMAIN_BUS_WIRED,主要作用就是区分其他domain, 如MSI。
(4) 判断GICD 是否支持rss, rss(Range Selector Support)表示SGI中断亲和性的范围 GICD_TYPER寄存器bit[26], 如果该字段为0,表示中断路由(IRI) 支持affinity 0-15的SGI,如果该字段为1, 表示支持affinity 0 - 255的SGI。
(5) 判断是否支持通过写GICD寄存器生成消息中断。GICD_TYPER寄存器bit[16]。
(6) 设定arch相关的irq handler。gic_irq_handle是内核gic中断处理的入口函数。
(7) 更新vlpi相关配置。gic虚拟化相关。
(8) 初始化ITS。 Interrupt Translation Service, 用来解析LPI中断。 初始化之前需要先判断GIC是否支持LPI,该功能在ARM里是可选的。
(9) 该函数主要包含两个作用。 1.设置核间通信函数。当一个CPU core上的软件控制行为需要传递到其他的CPU上的时候,就会调用这个callback函数(例如在某一个CPU上运行的进程调用了系统调用进行reboot)。对于GIC v3,这个callback定义为gic_raise_softirq. 2. 设置CPU 上下线流程中和GIC相关的状态机。
(10) 初始化GICD。
(11) 初始化CPU interface。
(12) 初始化GIC电源管理。
4、中断域irq_domain以及中断映射
gic的中断处理程序是从ack一个硬件中断开始的, 在gic的中断处理过程中,会根据中断的映射去寻找对应的虚拟中断号, 再去进行后续的中断处理。
那么问题来了,为什么要有一个虚拟中断号的概念?
当前的SOC,通常内部会有多个中断控制器(比如gic interrupt controller, gpio interrupt controller), 每一个中断控制器对应多个中断号, 而硬件中断号在不同的中断控制器上是会重复编码的, 这时仅仅用硬中断号已经不能唯一标识一个外设中断。 对于软件工程师而言,我们不需要care是中断哪个中断控制器的第几个中断号, 因此linux kernel提供了一个虚拟中断号的概念。
4.1 irq_domain
linux kernel提供irq_domain的管理框架, 将hwirq映射到虚拟中断号上。每一个中断控制器都需要注册一个irq_domain。
irq_domian数据结构:
/**
* struct irq_domain - Hardware interrupt number translation object
* @link: Element in global irq_domain list.
* @name: Name of interrupt domain
* @ops: pointer to irq_domain methods
* @host_data: private data pointer for use by owner. Not touched by irq_domain
* core code.
* @flags: host per irq_domain flags
* @mapcount: The number of mapped interrupts
*
* Optional elements
* @fwnode: Pointer to firmware node associated with the irq_domain. Pretty easy
* to swap it for the of_node via the irq_domain_get_of_node accessor
* @gc: Pointer to a list of generic chips. There is a helper function for
* setting up one or more generic chips for interrupt controllers
* drivers using the generic chip library which uses this pointer.
* @parent: Pointer to parent irq_domain to support hierarchy irq_domains
* @debugfs_file: dentry for the domain debugfs file
*
* Revmap data, used internally by irq_domain
* @revmap_direct_max_irq: The largest hwirq that can be set for controllers that
* support direct mapping
* @revmap_size: Size of the linear map table @linear_revmap[]
* @revmap_tree: Radix map以上是关于Linux kernel的中断子系统的主要内容,如果未能解决你的问题,请参考以下文章
linux kernel的中断子系统之:IRQ number和中断描述符