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>; // GICVinterrupts = <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;
#endifstruct 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;
#endifunsigned 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;
}