中断控制器(GIC)


文章目录

  • 中断控制器(GIC)
  • 前言
  • linux中断管理机制
  • ARM中断控制器
  • GIC结构分析
    • 分发器(Distributor)
    • CPU接口单元(CPU Interface)
    • 中断类型
    • 中断状态
    • 中断处理顺序
    • 中断状态机
  • GIC驱动
    • 节点信息
  • 驱动流程
  • 数据结构
  • ARM中断架构
  • gic_of_init()
  • gic_init_bases()函数
  • gic_handle_irq()
  • __handle_domain_irq()

前言

上篇大概讲了一下ARM的异常处理,但是处理器是如何知道是哪一种异常或中断的呢?这就需要中断控制器了。


linux中断管理机制

操作系统有个非常重要的部分外设,比如鼠标,键盘,声卡等。处理器与外设计算能力与处理速度上是不在一个数量级的。假设处理器要得到键盘或鼠标的事件,如果处理器发出一个请求信号,它就一直再轮询键盘或鼠标的响应,由于键盘鼠标响应速度慢得多,这就会导致浪费很多资源。但是如果键盘或鼠标产生数据了,去通知处理器,处理器停下当前工作来处理键盘,鼠标事件,处理完后继续原来的工作,这样就会高效很多。

从系统的角度来讲,linux内核中断管理可以分为4层:

  • 硬件层面:比如CPU和中断控制器的连接。
  • 处理器架构管理:比如CPU中断异常处理。
  • 中断控制器管理:比如IRQ中断号映射。
  • linux内核通用中断处理层:比如中断注册和中断处理。

不同体系结构中对中断控制器设计理念有所不同,ARM公司提供通用中断控制器(GIC),x86体系架构采用高级可编程中断控制器(PIC).

ARM中断控制器

GIC(Generic Interrupt Controller)作为 ARM 系统中通用中断控制器,目前有四个版本,V1~V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64系统结构)。我们以GIC-400为例:

如图所示,当中断发生的时候,通过中断控制器,发出中断给CPU。GIC-400通过AMBA(Advanced Microcontroller Bus Architecture)片上总线连接到一个或者多个ARM处理器上。从图中我们可以看出GIC 是联系外设中断和 CPU 的桥梁,也是各 CPU 之间中断互联的通道(也带有管理功能),它负责检测、管理、分发中断。简化图,如下:

根据简化图,ARM CPU 对外的连接只有2 个中断: IRQ和FIQ ,相对应的处理模式分别是一般中断(IRQ )处理模式和快速中断(FIQ )处理模式。所以GIC 最后要把中断汇集成2 条线,与CPU 对接。

GIC结构分析

如下图,是中断控制器的结构:

GIC中断控制器可以分为:仲裁单元和CPU接口模块。状态机有:inactive,pending,active,active and pending.
其中有两个重要组成部分分别是分发器(Distributor)与CPU接口单元(CPU Interface):

分发器(Distributor)

系统中的所有中断源都连接到该单元。可以通过仲裁单元的寄存器来控制各个中断源的属性,例如优先级、状态、安全性、路由信息和使能状态。
分发器把中断输出到“CPU接口单元”,后者决定将哪个中断转发给CPU核。
分发器的主要的作用是检测各个中断源的状态,控制各个中断源的行为,分发各个中断源产生的中断事件到指定的一个或者多个CPU接口上。虽然分发器可以管理多个中断源,但是它总是把优先级最高的那个中断请求送往CPU接口。分发器对中断的控制包括:

  1. 中断使能或禁能控制。分发器对中断的控制分成两个级别,一个是全局中断的控制(GIC_DIST_CTRL),一旦禁止了全局的中断,那么任何的中断源产生的中断事件都不会被传递到CPU接口;另外一个级别是对针对各个中断源进行控制(GIC_DIST_ENABLE_CLEAR),禁止某一个中断源会导致该中断事件不会分发到CPU接口,但不影响其他中断源产生中断事件的分发。
  2. 控制将当前优先级最高的中断事件分发到一个或者一组CPU接口。
  3. 优先级控制。
  4. 中断属性设定,例如是电平触发还是边沿触发。
  5. 中断的设定。
  6. 分发器可以管理若干个中断源,这些中断源用ID来标识,我们称之interrupt ID

CPU接口单元(CPU Interface)

CPU核通过控制器的CPU接口单元接收中断。CPU接口单元寄存器用于屏蔽,识别和控制转发到CPU核的中断的状态。系统中的每个CPU核心都有一个单独的CPU接口。分发器对中断的控制包括:
* 使能中断请求信号到CPU上;
* 中断的确认;
* 标识中断处理的完成;
* 为处理器设置中断优先级掩码;
* 设置处理器的中断抢占策略;
* 确定处理器的最高优先级pending中断;

中断类型

SPI:公用外设中断:最多可以支持 988 个外设中断,硬件中断号从 ID32~ID1019。

PPI:私有外设中断:是每个 CPU 私有的中断。最多支持 16 个 PPI 中断,硬件中断号从 ID16~ID31。

SGI:软件触发中断:通常用于多核间通讯,最多支持 16 个 SGI 中断,硬件中断号从 ID0~ID15。

中断状态

  • lnactive:中断源没有发送中断;
  • Pending:中断源已经发送中断,等待处理器处理;
  • Active:处理器已经确认中断,正在处理;
  • Active and Pending:处理器正在处理中断,相同的中断源* 又发送了一个中断。

当GIC接收到一个中断请求,将其状态设置为Pending。重新产生一个挂起状态的中断不影响该中断状态。

中断处理顺序

① GIC决定该中断是否使能,若没有被使能对GIC没有影响;

② 对于每个Pending中断,GIC决定目标处理器;

③ 对于每个处理器 ,Distributor根据它拥有的每个中断优先级信息决定最高优先级的挂起中断,将该中断传递给目标CPU Interface;

④ GIC Distributor将一个中断传递给CPU Interface后,该CPU Interface决定该中断是否有足够的优先级将中断请求发给CPU;

⑤ 当CPU开始处理该异常中断,它读取GICC_IAR应答中断。读取的GICC_IAR获取到中断ID,对于SGI,还有源处理器ID。中断ID被用来查找正确的中断处理程序。

GIC识别读过程后,将改变该中断的状态:

a) 当中断状态变为active时,如果该中断挂起状态持续存在或者中断再次产生,中断状态将从Pending转化为pending & active
b) 否则,中断状态将从pending状态变为active

⑥ 当中断完成中断处理后,它需要通知GIC处理已经完成。这个过程称为 priority drop and interrupt deactivation:

a) 总是需要向EOIR寄存器写入一个有效的值(end of interrupt register)
b) 也需要接着向GICC_DIR写入值(deactivate interrupt register)

中断状态机

  1. 添加挂起状态(A1、A2)
    对于一个 SGI,发生以下 2 种情况的 1 种:

    • 软件写 GICD_SGIR 寄存器,指定目标处理器
    • 目标处理器上软件写GICD_SPENDSGIRn 寄存器

    对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
    * 外设发出一个中断请求信号
    * 软件写 GICD_ISPENDRn 寄存器

  2. 删除挂起状态(B1、B2)
    对于 SGI,目标处理器写 GICD_CPENDSGIRn 寄存器,对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
    * 电平触发类型中断,信号取消
    * 边沿触发类型中断,软件写 GICD_ICPENDRn 寄存器

  3. 挂起到激活(C)
    如果中断使能,并且高优先级,软件从 GICC_IAR 寄存器读取时发生状态改变。

  4. 挂起到激活和挂起(D)
    对于 SGI,这种转变发生在以下任一情况下:
    * 将 SGI 状态设置为挂起的写入操作与读取 GICC_IAR 几乎同时发生
    * 当多个挂起的 SGI 具有相同 ID 时,并且它们来自同一个源处理器并指向同一个处理器。其中一个 SGI 状态变为激活(C),其他 SGI 状态变为激活和挂起(D)。
    对于 SPI 或 PPI,满足以下所有条件,则发生这种转换
    * 中断开启
    * 软件读取 GICC_IAR,读操作将激活状态添加到中断中。
    此外,还应满足以下条件之一:
    * 对于电平触发中断,中断信号保持。通常都是这样,因为外设直到处理器处理完中断后才会取消触发信号。
    * 对于边沿触发中断,是否发生此转换取决于读取 GICC_IAR 的时间(中断再次触发,上一次未处理),读取 GICC_IAR 可能会转换到 C,后面可能会转换到 A2。

  5. 删除激活状态(E1、E2)
    软件写入 GICC_EOIR 或 GICC_DIR 来停用中断,

GIC驱动

节点信息

 gic: interrupt-controller@2c001000 {compatible = "arm,cortex-a9-gic";//用于与具体的驱动来进行匹配#interrupt-cells = <3>;/*用于指定编码一个中断源所需要的单元个数,这个值为3。比如在外设在设备树中添加中断信号时,通常能看到类似interrupts = <0 23 4>;的信息,第一个单元0,表示的是中断类型(1:PPI,0:SPI),第二个单元23表示的是中断号,第三个单元4表示的是中断触发的类型;*/#address-cells = <0>;interrupt-controller;//表示该设备是一个中断控制器,外设可以连接在该中断控制器上;reg = <0x2c001000 0x1000>,//描述中断控制器的地址信息以及地址范围<0x2c000100 0x100>;};

内核启动后会将该节点解析成device_node结构。

驱动流程


GIC的工作,本质上是由中断信号来驱动,因此驱动本身的工作就是完成各类信息的初始化,注册好相应的回调函数,以便能在信号到来之时去执行set_handle_irq函数的设置很关键,它将全局函数指针handle_arch_irq指向了gic_handle_irq,而处理器在进入中断异常时,会跳转到handle_arch_irq执行,所以,可以认为它就是中断处理的入口函数了。驱动中完成了各类函数的注册,此外还完成了irq_chip, irq_domain等结构体的初始化,这些结构在下文会进一步分析。最后,完成GIC硬件模块的初始化设置,以及电源管理相关的注册等工作。

数据结构


GIC驱动中,使用struct gic_chip_data结构体来描述GIC控制器的信息,整个驱动都是围绕着该结构体的初始化,驱动中将函数指针都初始化好,实际的工作是由中断信号触发,也就是在中断来临的时候去进行回调struct irq_chip结构,描述的是中断控制器的底层操作函数集,这些函数集最终完成对控制器硬件的操作struct irq_domain结构,用于硬件中断号和Linux IRQ中断号(virq,虚拟中断号)之间的映射;


每个中断控制器都对应一个IRQ Domain;
中断控制器驱动通过irq_domain_add_*()接口来创建IRQ Domain;
IRQ Domain支持三种映射方式:linear map(线性映射),tree map(树映射),no map(不映射);

  1. linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射;
  2. tree map:硬件中断号可能很大,可以选择树映射;
  3. no map:硬件中断号直接就是Linux的中断号;

ARM中断架构


中断也是异常模式的一种,当外设触发中断时,处理器会切换到特定的异常模式进行处理,而这部分代码都是架构相关的;ARM64的代码位于arch/arm64/kernel/entry.S。

ARM64处理器有四个异常级别Exception Level:0~3,EL0级对应用户态程序,EL1级对应操作系统内核态,EL2级对应Hypervisor,EL3级对应Secure Monitor;

异常触发时,处理器进行切换,并且跳转到异常向量表开始执行,针对中断异常,最终会跳转到irq_handler中;

中断触发,处理器去异常向量表找到对应的入口,比如EL0的中断跳转到el0_irq处,EL1则跳转到el1_irq处;

在GIC驱动中,会调用set_handle_irq接口来设置handle_arch_irq的函数指针,让它指向gic_handle_irq,因此中断触发的时候会跳转到gic_handle_irq处执行;gic_handle_irq函数处理时,分为两种情况,一种是外设触发的中断,硬件中断号在16 ~ 1020之间,一种是软件触发的中断,用于处理器之间的交互,硬件中断号在16以内;

外设触发中断后,根据irq domain去查找对应的Linux IRQ中断号,进而得到中断描述符irq_desc,最终也就能调用到外设的中断处理函数了;

gic_of_init()


static int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{void __iomem *cpu_base;void __iomem *dist_base;u32 percpu_offset;int irq;if (WARN_ON(!node))return -ENODEV;[]dist_base = of_iomap(node, 0);//映射GIC Distributor的寄存器地址空间WARN(!dist_base, "unable to map gic dist registers\n");cpu_base = of_iomap(node, 1);//映射GIC CPU interface的寄存器地址空间WARN(!cpu_base, "unable to map gic cpu registers\n");if (of_property_read_u32(node, "cpu-offset", &percpu_offset))//处理cpu-offset属性。percpu_offset = 0;gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);//主处理过程,后面详述 if (!gic_cnt)gic_init_physaddr(node);//对于不支持big.LITTLE switcher(CONFIG_BL_SWITCHER)的系统,该函数为空。if (parent) {//处理interrupt级联 irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupts属性,并进行mapping,返回IRQ number gic_cascade_irq(gic_cnt, irq);}if (IS_ENABLED(CONFIG_ARM_GIC_V2M))gicv2m_of_init(node, gic_data[gic_cnt].domain);gic_cnt++;return 0;
}

gic_init_bases()函数

void __init gic_init_bases(unsigned int gic_nr, int irq_start,void __iomem *dist_base, void __iomem *cpu_base,u32 percpu_offset, struct device_node *node)
{irq_hw_number_t hwirq_base;struct gic_chip_data *gic;int gic_irqs, irq_base, i;BUG_ON(gic_nr >= MAX_GIC_NR);gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKEDif (percpu_offset) { /* Frankein-GIC without banked registers... */unsigned int cpu;gic->dist_base.percpu_base = alloc_percpu(void __iomem *);gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);if (WARN_ON(!gic->dist_base.percpu_base ||!gic->cpu_base.percpu_base)) {free_percpu(gic->dist_base.percpu_base);free_percpu(gic->cpu_base.percpu_base);return;}for_each_possible_cpu(cpu) {u32 mpidr = cpu_logical_map(cpu);u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);unsigned long offset = percpu_offset * core_id;*per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;}gic_set_base_accessor(gic, gic_get_percpu_base);} else
#endif{         /* Normal, sane GIC... */WARN(percpu_offset,"GIC_NON_BANKED not enabled, ignoring %08x offset!",percpu_offset);gic->dist_base.common_base = dist_base;gic->cpu_base.common_base = cpu_base;gic_set_base_accessor(gic, gic_get_common_base);}/** Initialize the CPU interface map to all CPUs.* It will be refined as each CPU probes its ID.*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff;/** Find out how many interrupts are supported.* The GIC only supports up to 1020 interrupt sources.*//*读取GIC 的最大支持的中断数目,从 GIC_DIST_CTR 寄存器(这是V1版本的寄存器名字,V2中是GICD_TYPER,Interrupt Controller Type Register,)的低五位ITLinesNumber获取的。如果ITLinesNumber等于N,那么最大支持的中断数目是32(N+1)。此外,GIC规范规定最大的中断数目不能超过1020,1020-1023是有特别用户的interrupt ID。*/gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;gic_irqs = (gic_irqs + 1) * 32;if (gic_irqs > 1020)gic_irqs = 1020;gic->gic_irqs = gic_irqs;/*分配一个 irq_domain 的结构,一个 irq_domain 代表了一个 GIC 控制器*/if (node) {     /* DT case */gic->domain = irq_domain_add_linear(node, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);} else {     /* Non-DT case *//** For primary GICs, skip over SGIs.* For secondary GICs, skip over PPIs, too.*/if (gic_nr == 0 && (irq_start & 31) > 0) {hwirq_base = 16;if (irq_start != -1)irq_start = (irq_start & ~31) + 16;} else {hwirq_base = 32;}gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());if (IS_ERR_VALUE(irq_base)) {WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",irq_start);irq_base = irq_start;}gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,hwirq_base, &gic_irq_domain_ops, gic);}if (WARN_ON(!gic->domain))return;if (gic_nr == 0) {//只对root GIC操作,因为设定callback、注册Notifier只需要一次就OK了
#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq);register_cpu_notifier(&gic_cpu_notifier);
#endifset_handle_irq(gic_handle_irq);}gic_dist_init(gic);//GIC Distributer   部分初始化gic_cpu_init(gic);//GIC CPU Interface 部分初始化gic_pm_init(gic);//GIC PM            部分初始化
}

gic_handle_irq()

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u64 irqnr;do {irqnr = gic_read_iar();//读取gic寄存器并获取hwirq中断号if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {int err;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) {//软件触发的中断SGIgic_write_eoir(irqnr);
#ifdef CONFIG_SMPhandle_IPI(irqnr, regs);//处理核间交互
#elseWARN_ONCE(true, "Unexpected SGI received!\n");
#endifcontinue;}} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}

__handle_domain_irq()

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();//进入中断上下文#ifdef CONFIG_IRQ_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq);//根据hwirq去查找linux中断号
#endif/** Some hardware gives randomly wrong interrupts.  Rather* than crashing, do something sensible.*/if (unlikely(!irq || irq >= nr_irqs)) {ack_bad_irq(irq);ret = -EINVAL;} else {generic_handle_irq(irq);/*根据linux中断号找到对应的中断描述符,从而执行中断服务函数*/}irq_exit();//退出中断上下文set_irq_regs(old_regs);return ret;
}

中断控制器(GIC)相关推荐

  1. Linux中断子系统(一)中断控制器GIC架构

    Linux中断子系统(一)中断控制器GIC架构 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一)中 ...

  2. Linux中断子系统(二)中断控制器GIC驱动分析

    Linux中断子系统(二)中断控制器GIC驱动分析 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一 ...

  3. ARM通用中断控制器GIC之中断控制

    在阅读本章之前,可以参考笔者之前关于GIC的一些描述: ARM通用中断控制器GIC(generic Interrupt Controller)简介 ARM架构Generic Interrupt Con ...

  4. ARM通用中断控制器GIC之中断处理简介

    在阅读本章之前,可以参考笔者之前关于GIC的一些描述: ARM通用中断控制器GIC(generic Interrupt Controller)简介 ARM架构Generic Interrupt Con ...

  5. ARM通用中断控制器GIC(generic Interrupt Controller)简介

    参考文档: Documentation – Arm Developer  ARM Generic Interrupt Controller Architecture Specification 目录 ...

  6. ARM通用中断控制器GIC之中断处理状态机 Interrupt handling state machine

    中断有四种状态:inactive,pending,active 和active and pending.而产生中断的方式有两种,一种是通过写pending寄存器,让中断进入pending状态,可以忽略 ...

  7. 8259A中断控制器详细介绍

    中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外,还有来自于外部硬件设备的中断,这些中断是可屏蔽的.这些中断也都通过PIC(Programmable Interr ...

  8. soc(九) 中断控制器

    中断控制器在SOC中的位置 中断控制器需要做什么事情 从某个 简单中断处理器 的角度考虑 整个过程 既然处理中断的最核心模块是 中断控制器 , 那么 我们就应该了解 中断控制器的典型工作流程 . 在了 ...

  9. GIC通用中断控制器

    1. GIC简介 操作系统中,中断是很重要的组成部分.有了中断系统才可以不用一直轮询(polling)是否有事件发生,系统效率才得以提高.一般在系统中,中断控制分为三个部分:模块.中断控制器和处理器. ...

最新文章

  1. 不服来战!多伦多大学教授500美元挑战整个机器学习圈子
  2. 盘点|应用落地,构建城市“大脑”
  3. html折线图怎么控制y轴数值,echarts Y轴数据类型不同怎么让折线图显示差距不大...
  4. 【Tiny4412】烧写Android系统(SD卡)
  5. ajax给data赋值,vue 2.0 methods 里ajax生成的数据,怎么赋值给data
  6. 基本机器学习面试问题 ---- Company/Industry Specific/Interest
  7. 解决 There are no resources that can be added or removed from the server
  8. 如何使用Elasticsearch,Logstash和Kibana实时可视化Python中的日志
  9. Bootstrap HTML 编码规范之布尔型属性
  10. 剑指offer面试题32 - I. 从上到下打印二叉树(二叉树)(BFS)
  11. FFmpeg源代码简单分析:日志输出系统(av_log()等)
  12. Ubuntu 1604 升级 1804 记录
  13. printf是如何实现变长参数的
  14. python对文件操作采用的统一步骤是_python之文件操作总结
  15. Java实现视频加密及播放
  16. 如何设置matlab和.m、.slx .mdl .p .mat等文件之间关联
  17. Office2010初次体验之应用程序安全性
  18. 25行Java代码将普通图片转换为字符画图片和文本
  19. 硅谷丛林的故事 EDA篇
  20. 博文翻译:Tackling the Cold Start Problem in Recommender Systems

热门文章

  1. 短视频如何创作出吸引人的爆款文案
  2. Apache Dolphinscheduler —— CDH6.3.2集群模式部署(Cluster)
  3. html渐变色原理,html5教程实现Photoshop渐变色效果
  4. 域名隐藏转发html代码,如何作域名URL隐藏转发
  5. 转:华为-NE40ENE80ENE5000E维护宝典
  6. Genymotion强大好用高性能的Android模拟器(在电脑流畅运行APK安卓软件游戏的利器)
  7. alter database datafile resize(+释放空间)
  8. Python零基础爬虫之回车桌面壁纸并实现自动换壁纸(内附完整源码)
  9. 那英唱歌软件测试,52岁那英素颜一言难尽,KTV变个人演唱会,挑战韩红成名曲...
  10. k8s-Kuboard国产页面管理