Linux 中断管理之ARM GIC V3 初始化
1.ARM GIC V3中断控制器介绍GIC(Generic Interrupt Controller)是一个通用的中断控制器,用来接收硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。GIC V3是其中一个版本,支持的中断类型如下表:中断类型中断号描述SGI (Software Generated Interrupt)0-15...
1.ARM GIC V3中断控制器介绍
GIC(Generic Interrupt Controller)是一个通用的中断控制器,用来接收硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。GIC V3是其中一个版本,支持的中断类型如下表:
中断类型 | 中断号 | 描述 |
SGI (Software Generated Interrupt) | 0-15 | 软件触发中断,通常用于多核之间通讯,在Linux内核中通常被用作IPI(inter -process interrupts)中断,并送达到系统指定的CPU,最多支持16个SGI中断,中断号0-15 |
PPI (Private Peripheral Interrupt) | 16-31 | 每个处理器的私有外设中断,最多支持16个PPI中断,中断号16-31,PPI通常会送达到指定的CPU上 |
SPI (Shared Peripheral Interrupt) | 32-1019 | 系统共享的外设中断 |
LPI (Locality-specific Peripheral Interrupt) | 8192-MAX | LPI是基于消息的中断,它们的配置保存在表中而不是寄存器 |
GIC V3中断控制器的组成部分包括:distributor,redistributor,cpu interface,ITS,GIC V3中断控制器和处理器核心之间的关系图如下:
SPI 中断检测流程:
- 外设发起SPI中断,GIC检测到这个中断,并标记为pending状态
- distributor对所有处于pending状态的中断确定目标CPU
- 对每个CPU,distributor会从众多pending状态的中断中,选择优先级高的发送到对应的redistributor
- redistributor将中断发送到目标CPU的CPU Interface上
- CPU Interface将满足要求的中断发送给CPU
- CPU进入中断异常后,内核中断处理程序读取GICC_IAR寄存器来响应该中断,寄存器返回硬件中断号
- CPU处理完中断服务后,通过写GICC_EOIR寄存器,来给GIC发送一个EOI完成信号
PPI和SGI中断检测流程:
- GIC检测到PPI或者SGI中断,并标记为pending状态
- redistributor选择优先级高的中断发送到对应的CPU Interface
- CPU Interface将满足要求的中断发送给CPU
- CPU进入中断异常后,内核中断处理程序读取GICC_IAR寄存器来响应该中断,寄存器返回硬件中断号
- CPU处理完中断服务后,通过写GICC_EOIR寄存器,来给GIC发送一个EOI完成信号
LPI中断检测流程:
1.forwarding方式,由以下寄存器实现
- GICR_SERLPIR:将指定的LPI中断,设置为pending状态
- GICR_INVLPIR:将指定的LPI中断,清除pending状态。寄存器内容和GICR_SERLPIR一致
- GICR_INVLPIR:将缓存中,指定LPI的缓存给无效掉,使GIC重新从memory中载入LPI的配置
- GICR_INVALLR:将缓存中,所有LPI的缓存给无效掉,使GIC重新从memory中,载入LPI中断的配
- GICR_SYNCR:对redistributor的操作是否完成
2.使用ITS方式
- 外设写GITS_TRANSLATER寄存器,给ITS提供中断事件类型EventID和发起中断的外设的DeviceID
- ITS用DeviceID,通过查设备表,得到中断映射表的位置
- ITS用EventID,通过查中断映射表,得到LPI中断号,以及中断所属的collection号
- 使用collection号,从collection表格中redistributor的映射信息
- ITS将中断信息发送给对应的redistributor
- redistributor将该中断信息发送给CPU Interface
- CPU Interface将满足要求的中断发送给CPU
2 ARM Linux系统中GIC V3初始化
以下所有内核源码来自Linux5.5.8
2.1 Linux系统中 gic-v3 DTS定义和设备节点
Device Tree是用来描述硬件的数据结构,DTS即Device Tree Source 设备树源码,Linux源码中DTS对应的文件名后缀为.dts。 DTC是将.dts编译为.dtb的工具,.dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备。
2.1.1 Linux系统中 gic-v3 DTS定义
gic: interrupt-controller@2c010000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#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 0x200000>;
};
gic-its@2c400000 {
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>;
reg = <0x0 0x2c400000 0 0x200000>;
};
};
1) gic-v3设备节点中必须的属性:
- interrupt-controller:该设备节点为中断控制器
- compatible:必须包含字符串"arm,gic-v3",表示该设备为中断控制器arm gic-v3
- #interrupt-cells :指定编码中断源所需的单元格数目,必须至少3个单元格,单元格的详细描述如下表
单元格 | 描述 |
1 | 第一个单元格中的是中断类型:0 SPI,1 PPI,其他为保留值 |
2 | 第二个单元格中的值是中断号 |
3 | 第三个单元格中的值是一些中断标志,1为边沿触发,4为电平触发 |
- reg:指定了GIC寄存器的物理基地址,这些寄存器包括:Distributor(GICD),Redistributors (GICR),CPU interface (GICC),Hypervisor interface (GICH),Virtual CPU interface (GICV),其中 GICC, GICH ,GICV 是可选的。
- interrupts:中断源
2) gic-v3设备节点中可选的属性:
- redistributor-stride:指定redistributor的步长,必须是64KB的倍数
- #redistributor-regions:如果系统中有多个redistributor时,需要这个属性来描述多个redistributor区域
3) gic-v3设备节点中的子设备节点ITS(Interrupt Translation Services):一个GIC中有1到多个ITS设备,ITS设备用于将消息信号中断(MSI)路由到cpu,ITS设备节点的描述如下:
- compatible:必须包含字符串"arm,gic-v3-its",表示这是一个ITS设备
- msi-controller:标识该设备节点为MSI控制器
- #msi-cells:必须是1,这里面是MSI设备的DeviceID
- reg:ITS寄存器的物理基地址
2.1.2 Linux系统中 gic-v3 设备节点
内核在创建设备节点的时候的主要数据结构为struct device_node,该结构体通过解析.dtb得到,以下是struct device_node的源码和注释:
struct property {
/*该property的名称*/
char *name;
/*该property的长度*/
int length;
/*该property的值*/
void *value;
/*和该property相连接的下一个property*/
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
#if defined(CONFIG_SPARC)
struct of_irq_controller;
#endif
struct device_node {
/*设备节点名称*/
const char *name;
phandle phandle;
/*设备节点名称和地址*/
const char *full_name;
/*操作该设备节点的一些回调函数*/
struct fwnode_handle fwnode;
/*节点中的每一组数据( 比如compatible = "arm,cortex-a9-gic")通过结构体property表示,
* property->next指向另外一组数据。
*/
struct property *properties;
/*节点中被移除的property*/
struct property *deadprops; /* removed properties */
/*该设备结点的父设备节点*/
struct device_node *parent;
/*该设备结点的子设备节点*/
struct device_node *child;
/*该设备结点的兄弟设备节点*/
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
系统解析2.1.1中的 gic-v3 DTS后得到的struct device_node如下:
- device_node->name = "interrupt-controller"
- device_node->full_name = "interrupt-controller@2c010000"
- device_node->properties->name = "compatible"
- device_node->properties->value = "arm,gic-v3"
- device_node->properties->length = 10 (字符串"arm,gic-v3"的长度)
- device_node->properties->next->name = "redistributor-stride"
- device_node->properties->next->value = {0x0,0x0,0x0,0x0, 0x0,0x04,0x0,0x0}
- device_node->properties->next->length = 4(一个字段占4个字节)
- device_node->properties->next->next->name = "reg"
......
- device_node->child->name = "gic-its"
- device_node->child->full_name = "gic-itss@2c200000"
......
- device_node->child->next->name = "gic-its"
- device_node->child->next->full_name = "gic-itss@2c400000"
......
2.2 Linux系统中gic-v3初始化
2.2.1 struct of_device_id结构体
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
/*
* This special of_device_id is the sentinel at the end of the
* of_device_id[] array of all irqchips. It is automatically placed at
* the end of the array by the linker, thanks to being part of a
* special section.
*/
static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_table_end);
extern struct of_device_id __irqchip_of_table[];
1) struct of_device_id结构体
- name:设备名称
- type:设备类型
- compatible:对应DTS文件中设备结点的compatible,用来匹配适合的device node
- data:对于GIC来说,data字段为初始化GIC的函数地址
2) __irqchip_of_table:一个系统全局数组,数组中的每个对象是一个全局struct of_device_id静态常量,IRQCHIP_DECLARE宏用于初始化一个struct of_device_id的静态常量,并放置在__irqchip_of_table中。
2.2.2 宏IRQCHIP_DECLARE
/*
* 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)
#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 }
以上是linux系统中宏IRQCHIP_DECLARE的定义,这个宏初始化了一个struct of_device_id的静态常量,并放置在__irqchip_of_table中。
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
如上代码所示,IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init)声明了一个__of_table_gic_v3的struct of_device_id结构体,放在__irqchip_of_table中。__of_table_gic_v3.compatible为"arm,gic-v3",__of_table_gic_v3.data指向函数gic_of_init()的地址。
2.2.3 gic_of_init()函数分析
Linux系统启动后初始化GIC V3的函数调用顺序为init_IRQ()->irqchip_init()->of_irq_init()->gic_of_init()。以下是rqchip_init(),of_irq_init()函数相关代码及其注释:
extern struct of_device_id __irqchip_of_table[];
void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table);
acpi_probe_device_table(irqchip);
}
/**
* of_irq_init - 在DT中扫描并初始化匹配的中断控制器
* @matches: 用于扫描并匹配的中断控制器的所有设备节点的一个数据,即__irqchip_of_table
*
* 该函数扫描设备树以查找匹配的中断控制器节点,并执行它们的初始化函数。
*/
void __init of_irq_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np, *parent = NULL;
struct of_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);
/*遍历__irqchip_of_table得到的中断控制器设备信息,并为每个中断控
*制器设备分配一个struct of_intc_desc 结构体
*/
for_each_matching_node_and_match(np, matches, &match) {
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np))
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
/*
* Here, we allocate and populate an of_intc_desc with the node
* pointer, interrupt-parent device_node etc.
*/
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (!desc) {
of_node_put(np);
goto err;
}
/* match->data 中的内容是gic_of_init()函数地址,
* 这里设置of_intc_desc的irq_init_cb 回调函数为gic_of_init()
*/
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);
desc->interrupt_parent = of_irq_find_parent(np);
if (desc->interrupt_parent == np)
desc->interrupt_parent = NULL;
/*将of_intc_desc放到intc_desc_list链表中*/
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) {
int ret;
if (desc->interrupt_parent != parent)
continue;
list_del(&desc->list);
of_node_set_flag(desc->dev, OF_POPULATED);
pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
desc->dev,
desc->dev, desc->interrupt_parent);
/*执行初始化中断控制器设备的回调函数gic_of_init()*/
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);
if (ret) {
of_node_clear_flag(desc->dev, OF_POPULATED);
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_or_null(&intc_parent_list,
typeof(*desc), list);
if (!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);
of_node_put(desc->dev);
kfree(desc);
}
}
以下是gic_of_init()函数的代码和注释,gic_of_init()中调用gic_init_bases()来处理GIC V3初始化的核心工作:
struct redist_region {
/*指向Redistributor域内存区间的地址*/
void __iomem *redist_base;
/*Redistributor域物理地址*/
phys_addr_t phys_base;
/*是否只有一个Redistributor域*/
bool single_redist;
};
/*GIC V3硬件设备相关的数据结构*/
struct gic_chip_data {
/*操作该设备的一些回调方法*/
struct fwnode_handle *fwnode;
/*指向Distributor内存区间的地址*/
void __iomem *dist_base;
/*该gic中所有Redistributor域的信息*/
struct redist_region *redist_regions;
/*该gic中所有Redistributor的信息*/
struct rdists rdists;
/*该GIC对应的irq_domain,Linux系统中每个GIC对应一个irq_domain,
* 用于解析硬件中断号
*/
struct irq_domain *domain;
/*Redistributor域之间的步长,64KB的倍数*/
u64 redist_stride;
/*Redistributor域个数*/
u32 nr_redist_regions;
u64 flags;
bool has_rss;
unsigned int ppi_nr;
struct partition_desc **ppi_descs;
};
static struct gic_chip_data gic_data __read_mostly;
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;
/*为node映射内存区间,这里返回的dist_base是Distributor的地址*/
dist_base = of_iomap(node, 0);
if (!dist_base) {
pr_err("%pOF: unable to map gic dist registers\n", node);
return -ENXIO;
}
/*检查GIC硬件版本是否是V3或者V4*/
err = gic_validate_dist_version(dist_base);
if (err) {
pr_err("%pOF: no distributor detected, giving up\n", node);
goto out_unmap_dist;
}
/*读取该设备节点中redistributor个数,如果该设备DT中没有"#redistributor-regions"字段,
* 那就只有一个redistributor
*/
if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
nr_redist_regions = 1;
/*根据redistributor个数分配redistributor数组*/
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);
/*已分配的每个redistributor区域映射内存区间*/
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;
}
/*读取该设备节点中每个redistributor区域之间的步长,如果该设备DT中
* 没有"#redistributor-stride"字段,redist_stride大小为0
*/
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
redist_stride = 0;
gic_enable_of_quirks(node, gic_quirks, &gic_data);
/*开始正式初始化GIC V3设备*/
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
redist_stride, &node->fwnode);
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;
}
/*@dist_base:Distributor的地址,
*@rdist_regs:GIC设备中所有Redistributor的地址信息
*@nr_redist_regions:Redistributor的个数
*@redist_stride:Redistributor区域之间的步长,
*@handle:是该设备节点对应的数据结构struct device_node中的fwnode,
* 指向与irq_domain关联的固件节点的指针
*/
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是一个静态的全局变量,这里对其进行一些初始化*/
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;
/*
* Find out how many interrupts are supported.
*/
typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
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);
pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR);
/*在系统中为GIC V3注册一个irq domain的数据结构. irq_domain主要作用是映射硬件中断号*/
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
/*一个设备节点中可能有多个irq_domain,这些irq_domain用于不同的目的,DOMAIN_BUS_WIRED
* 表示该irq_domain用于wired IRQS
*/
irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
gic_data.rdists.has_vlpis = true;
gic_data.rdists.has_direct_lpi = true;
if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
err = -ENOMEM;
goto out_free;
}
/*rss表示SGI中断亲和性的范围,GICD_TYPER_RSS值为GICD_TYPER寄存器的bit[26],
* 0表示中断路由(IRI) 支持affinity 0-15的SGI
* 1表示支持affinity 0 - 255的SGI
*/
gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
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);
if (err)
pr_err("Failed to initialize MBIs\n");
}
/* 设置中断回调函数gic_handle_irq,当发生中断时,
* 系统会执行gic_handle_irq来处理中断
*/
set_handle_irq(gic_handle_irq);
/*更新Redistributor相关的属性*/
gic_update_rdist_properties();
/*1.设置核间通信,2.设置CPU启动时动态注册gic的回调函数gic_cpu_init*/
gic_smp_init();
/*初始化Distributor*/
gic_dist_init();
/*初始化CPU Interface*/
gic_cpu_init();
/*初始化GIC电源管理*/
gic_cpu_pm_init();
if (gic_dist_supports_lpis()) {
/*初始化ITS*/
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;
}err;
}
更多推荐
所有评论(0)