参考文章:linux kernel的中断子系统之(七):GIC代码分析 (wowotech.net)
《arm_gic_architecture_specification.pdf》
GIC关于支持安全扩展、虚拟化的部分就不分析,先了解最基本的中断功能。
GIC与外设,CPU的关系可如简化:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9P0KNjE-1658853609883)(D:\blog\ARM\picture\未命名文件 (5)].png)
GIC是ARM的中断控制器,用于接收soc上外设的中断信号,并通知cpu进行处理。GIC可分为两个部分:distributor和cpu interface。
distributor负责配置中断优先级并将中断信号发送 到cpu interface,cpu interface与cpu进行物理连接,负责优先级屏蔽和抢占处理。GIC中有一个distributor和最多8个cpu interface,他们的寄存器都映射到内存中读写,寄存器的地址由GICD_prefix和GICCprefix定义。如下是GIC的框架图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5266qNrl-1658853609890)(D:\blog\ARM\pictures\image-20220625094512755.png)]
distributor收集所有外设的中断信号,决定他们的优先级,并将最高优先级的中断传输给cpu。distributor提供的编程接口有:
另外,distributor还提供了一个中断状态机,用于设置or清除外设中断。如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxslGoeP-1658853609893)(D:\blog\ARM\picture\image-20220620132505469.png)]
GIC支持最多1020个中断id,每一个中断id代表一个中断信号线。其中0-31是私有id,对应指定的cpu,32-1019是共享id,所有cpu都可见。这些id就是硬件中断号。
cpu interface 一共有8个,每一个对接一个cpu,提供的编程接口有:
应答中断:就是gic产生的中断信号使cpu成功进入irq mode,cpu读取gic 的寄存器,获取最高优先级的pending中断id,此时distributor的状态机就会从pending变成active,表示中断正在处理。此时,假如产生另一个中断,cpu interface就可以再次产生新的中断来抢占当前active的中断。
指示中断处理完成:当cpu的中断处理函数运行完成后,需要两个步骤结束整个中断:
GIC中断处理的流程和状态机很有关系,流程如下
note
running 优先级,当前cpu interface的active中断的最高优先级,若没有active中断,则是idle优先级。
GIC将中断优先级分为两个部分:分组优先级和子优先级。当中断产生时,会先比较分组优先级,分组优先级高的话可以抢占当前的中断,分组优先级相同时,再比较子优先级。
中断的优先级保存在寄存器 GICD_IPRIORITYRn 中,数值越小,优先级越大。优先级是一个8bit的数值,每一个位的意义如下:
GICC_BPR 寄存器将GICD_IPRIORITYRn 中的8bit分成两个部分:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MORQZ3nf-1658853609894)(D:\blog\ARM\picture\image-20220625134245519.png)]
只有分组优先级高于 GICC_PMR 寄存器定义时,中断才会被响应。 GICC_PMR 寄存器用于屏蔽优先级。
参考《arm_gic_architecture_specification.pdf》第四章
gic的地址在复位时会被采样PERIPHEBASE[39:15],gic支持32bit、16bit、8bit的小端模式访问。
distributor的寄存器:
cpu interface 的寄存器:
CPU Interface Control Register:全局中断信号开关
Interrupt Priority Mask Register:优先级过滤器,优先级高的中断才能被传递到cpu
Binary Point Register:将中断优先级分为两个部分,优先级和子优先级
Interrupt Acknowledge Register:cpu读取这个寄存器获取当前中断id,并认为是对这个中断ack,会使中断的状态从pending编程active,或者变成pending and active(电平触发)
End of Interrupt Register:写入中断号到该寄存器,表示中断处理完成
Running Priority Register:当前cpu接口的中断优先级
Highest Priority Pending Interrupt Register:就像他的名字说的
Deactivate Interrupt Register:把中断状态从active变成inactive
如上图,为GIC里面的中断对应的状态,当没有任何中断的时候状态为inactive。外设触发中断,distributor会将状态改为pending。当ARM处理器响应中断服务(中断服务程序响应之前,由GIC驱动会产生一个ACK信号给控制器)的时候,cpu interface在收到ACK信号后,将状态改为active或者active+pending,什么情况下是active呢?比如,外部中断是一个gpio按钮产生,假如是低电平有效,当用户按下按钮时,电平变为低电平,此时在GIC上产生电信号变化,GIC将状态改为penging,如果此时,用户突然松开了手,那么cpu interface只将状态改为active。但是如果用户没有松开手,那么cpu interface则将状态改为active+pending。所以可以了解到如果外设持续产生中断信号,那么就会出现active+pending的状态。这就是为什么绝大部分外设驱动在中断服务程序里面都会有一个清除pending状态的动作,如果不清除,即使中断服务程序处理结束返回,也会继续触发新的无用中断。
另外,当中断服务程序处理结束后,通常GIC驱动还需要做一个动作叫做EOI,这个EOI一般是将两个操作合并成了一个(GICv1就是这样),但是到了GICv2,它允许将两个操作分开。那么是哪两个操作呢?即priority drop和deactivation。
priority drop叫做优先级还原,即把当前中断的优先级从running priority改为响应中断之前的优先级。比如,我们给中断配置了一个优先级5,running priority数字为0, 那么肯定0的优先级高于5。当这个优先级为5的中断被CPU响应时,CPU会临时将这个中断的优先级改为0(running priority),这样可以防止被同组内的其它高优先级抢占而破坏中断流程,影响关键数据读取。当这个中断服务处理完成后,GIC又通过EOI的步骤将这个优先级变回原来的优先级5。
deactivation叫做失效,即因为在GIC驱动ACK应答GIC控制器的时候,GIC控制器会把中断状态改为active或者active+penging,因此,这个deactivation可以将状态active改为inactive状态或者active+penging改为penging。
在GICv2当中,它允许将两个方法分开,由软件控制,通过配置GICC_CTLR寄存器的EOImode位达到目的。如果分开,软件在中断服务程序结束后,需要做两个动作:1、写EOIR寄存器,释放running优先级;2、写GICC_DIR寄存器,修改中断状态。如果不分开,那么久只需要做一个写写EOIR寄存器的动作就可以了。
参考另一篇文章
在imx6ul.dti中可以找到intc节点,表示GIC
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>; //子节点需要3个cell,eg:interrupts = ;
interrupt-controller; //中断控制器
reg = <0x00a01000 0x1000>, //distributor内存地址
<0x00a02000 0x100>; //cpu interface 内存地址
};
GIC的驱动程序,猜测应该需要实现irq_domain,中断服务程序,初始化GIC寄存器。
搜索设备树的compatible属性,确定驱动程序在irq-gic.c 如下:
static int gic_cnt __initdata;
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;
//distributor基地址0x00a01000
dist_base = of_iomap(node, 0);
//interface基地址0x00a02000
cpu_base = of_iomap(node, 1);
if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
percpu_offset = 0;
//初始化硬件寄存器
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
//本例子中gic_cnt=0
if (!gic_cnt)
gic_init_physaddr(node);
//gic一般不会有parent
if (parent) {
irq = irq_of_parse_and_map(node, 0);
gic_cascade_irq(gic_cnt, irq);
}
//CONFIG_ARM_GIC_V2M is not set
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_of_init(node, gic_data[gic_cnt].domain);
//每一个gic控制器都会记录
gic_cnt++;
return 0;
}
从设备树中获取distributor和interface的基地址,然后调用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;
//从全局数组中获取gic对象
gic = &gic_data[gic_nr];
{ /* 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_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;
if (node) { /*只考虑设备树的情况 */
//添加一个irq_domain,对应GIC
gic->domain = irq_domain_add_linear(node, gic_irqs,
&gic_irq_domain_hierarchy_ops,
gic);
} else {
}
if (gic_nr == 0) {
//handle_arch_irq会把handle_arch_irq指向gic_handle_irq
set_handle_irq(gic_handle_irq);
}
//设置寄存器
gic_dist_init(gic);
gic_cpu_init(gic);
gic_pm_init(gic);
}
对这个GIC进行默认的配置,写寄存器
static void __init gic_dist_init(struct gic_chip_data *gic)
{
unsigned int i;
u32 cpumask;
unsigned int gic_irqs = gic->gic_irqs; //中断数量
void __iomem *base = gic_data_dist_base(gic); //distributor基地址
writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL); //关闭全局的中断传递
/*
* Set all global interrupts to this CPU only.
* 把所有中断都输出到这个cpu
*/
cpumask = gic_get_cpumask(gic); //获取所使用的cpu接口
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
//默认中断配置
gic_dist_config(base, gic_irqs, NULL);
//开启全局的中断传递
writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}
void __init gic_dist_config(void __iomem *base, int gic_irqs,
void (*sync_access)(void))
{
unsigned int i;
/*
* 设置所有中断为低电平触发
*/
for (i = 32; i < gic_irqs; i += 16)
writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
base + GIC_DIST_CONFIG + i / 4);
/*
* 设置默认优先级中断
*/
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i);
/*
* 关闭所有中断
*/
for (i = 32; i < gic_irqs; i += 32)
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ENABLE_CLEAR + i / 8);
if (sync_access)
sync_access();
}
GIC作为一个中断控制器,必须要实现一个irq_domain对象:
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
.xlate = gic_irq_domain_xlate, //从设备树中获取硬件中断号
.alloc = gic_irq_domain_alloc, //为下一个中断控制器分配irq_domain
.free = irq_domain_free_irqs_top,
};
//从设备树中获取hw_irq
//interrupts = ;
static int gic_irq_domain_xlate(struct irq_domain *d,
struct device_node *controller,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type)
{
/* 跳过16个SGI eg 114+16*/
*out_hwirq = intspec[1] + 16;
/* For SPIs, we need to add 16 more to get the GIC irq ID number */
//GIC_SPI == 0,当是SPI时,需要+16 eg:114+16+16
if (!intspec[0])
*out_hwirq += 16;
//中断触发类型
*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
return ret;
}
//为下一个中断控制器分配irq_domain
static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
int i, ret;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
struct of_phandle_args *irq_data = arg;
//获取硬件中断号
ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args,
irq_data->args_count, &hwirq, &type);
if (ret)
return ret;
//硬件中断号与虚拟中断号建立映射关系
for (i = 0; i < nr_irqs; i++)
gic_irq_domain_map(domain, virq + i, hwirq + i);
return 0;
}
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
//专属中断
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
} else {
//spi
irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
}
return 0;
}
当cpu接收到GIC传来的中断信号时,会进入IRQ模式,跳转到中断向量表irq_handler处执行中断处理:
entry-armv.S
.macro irq_handler //定义irq_handler宏
ldr r1, =handle_arch_irq //handle_arch_irq 地址复制到r1
mov r0, sp //sp复制r0
adr lr, BSYM(9997f)
ldr pc, [r1] //跳转handle_arch_irq
而set_handle_irq()函数就是将handle_arch_irq指向gic_handle_irq,也就是当中断产生时,gic_handle_irq会首先得到执行。
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
//读取中断ID
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
//掩码过滤掉reserved
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
//将hw_irq转换成irq,并调用对应irq_desc的handler进行处理
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
中断描述符什么时候进行分配?答案是在驱动程序调用 irq_of_parse_and_map()的时候:
irq_of_parse_and_map()->
of_irq_parse_one()
irq_create_of_mapping()->
irq_create_mapping()->
irq_domain_alloc_descs()->
irq_alloc_descs() //分配描述符