接上篇,ARM64基础13:ARM64的异常处理之中断处理(以树莓派4采用的BCM2711芯片为例)
1.GIC的诞生背景
传统中断控制器,比如树莓派4b的legacy interrupt controller,具备
中断enable寄存器;
中断状态寄存器;
随着新业务的出现,比如
1.中断源变得越来越多;
2.不同类型的中断出现,比如多核间中断,中断优先级,软中断等;
3.支持虚拟化;
传统中断控制器已经无法满足需求,由此GIC控制器应运而生;
2.GIC的版本号
GIC演进的版本号如下表
GICv1 | GICv2 | GICv3 |
---|---|---|
支持8核 支持多达1020个中断源 8bit优先级 支持软件触发中断 TrustZone支持 IP: GIC-390 应用场景: Cortex-A9 MPCore | 支持虚拟化 支持secure software IP: GIC-400 应用场景: Cortex-A7 MPCore 树莓派4b | 支持大于8核 支持基于消息的中断 支持更多的中断ID IP: GIC-500 GIC-600 应用场景: 新款手机 |
目前最新版本是GIC500/600,比如最新的高端手机大多支持,而在传统嵌入式系统中,大多GIC400就够用,比如这里以树莓派;
3.GIC支持的中断类型
SGI: 软件产生的中断(Software Generated Interrupt),用于给其他CPU核发送中断信号;
PPI: 私有外设中断(Private Perpheral Interrupt),该中断是某个指定的CPU独有的;
SPI: 共享外设中断(Shared Peripheral Interrupt),所有CPU都可以访问这个中断;
LPI: 本地特殊外设中断(Locality-specific Peripheral Interrupt),GICv3新增的中断类型,基于消息传递的中断类型
中断类型 | 中断号范围 |
---|---|
软件触发中断(SGI) | 0~15 |
私有外设中断(PPI) | 16~31 |
共享外设中断(SPI) | 32~1019 |
4 GIC的中断状态
中断的处理流程是:分发器把收集来的中断先缓存,依次把优先级最高的中断请求送往CPU接口,CPU读取一个中断,其实就是读取接口的一个寄存器,只不过这个寄存器存放的是中断号,此时中断的状态由pending转为active,CPU处理完了以后,将中断号写入GIC的接口,告诉GIC处理完了,可以将这个中断清理,
5 GIC硬件原理
GIC 是连接外设中断和CPU的桥梁,也是多个CPU之间中断互联的通道,它负责检测、管理、分发中断;
5.1 GIC结构
上图摘自GIC400官方手册,GIC可以做到
1.使能或禁止中断;
2.把中断分组到FIQ或者IRQ;
3.多核系统中,可以将中断分配到不同CPU上;
4.设置GIC给到CPU的触发方式(不等于外设的触发方式);
5.支持虚拟化扩展;
5.2 GIC功能
由上图知,GIC按功能,划分为两部分:仲裁分发和CPU接口;
5.2.1.分发器distributor
主要作用是,检测各个中断源的状态,控制各个中断源的行为,分发各个中断源产生的中断事件到一个或多个CPU接口。主要功能包括:
(1) 使能或禁止中断,分发器对中断的控制分两级别,一个是全局中断控制(GIC_DIST_CTRL),一个是针对具体中断源控制(GIC_DIST_ENABLE_CLEAR),
(2)控制优先级;
(3)将仲裁后的最高优先级中断事件,分发给一个或多个CPU接口;
(4)中断属性设定,比如触发方式等;
5.2.2 CPU接口
(1)禁止或使能CPU接口向相应的CPU提交中断事件;对于ARM核,中断信号线是nIRQ或nFIQ,若禁止GIC中断,即使分发器分发了一个事件到CPU接口,也不会提交nIRQ或nFIQ信号给CPU;
(2)ackowledging中断;ARM核一旦应答了中断,分发器就会把该中断状态从pending改为active, 若没有后续pending中断,CPU接口会deassert nIRQ或nFIQ信号线;若有后续中断,CPU接口将中断状态改为pending and active, 此时依然保持nIRQ或nFIQ信号的asserted状态;
(3)中断处理完毕;ARM核处理完中断,回向CPU接口的寄存器写EOI命令,分发器将当前中断状态改为deactive,同时也可以将其他pending的中断向CPU接口提交;
(4)设定优先级掩码;可以屏蔽较低优先级中断,使其不同时给ARM核;
(5)设定中断抢占策略;
(6)始终选定最高优先级中断事件,提交给ARM核;
一个完整的中断处理过程时序图(摘自GIC400手册B1):
假定:
a.都是电平触发;
b.都是共享外设中断;
c.M/N信号都配置为同一个CPU的为FIQEn中断;
d.N信号优先级高于M信号;
6.GICv2中断控制器
GIC-400包括两组寄存器
D系列:The Distributor registers(GICD_),包含中断设置和配置;
C系列:The CPU Interface registers(GICC_),包含CPU相关的特殊寄存器;
6.1访问GIC-400寄存器:
树莓派4b中GIC-400基地
GIC-400中地址偏移:
6.2 GIC-400初始化流程
(1)设置distributor和CPU interface寄存器组的基地址;
(2)读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源;
(3)初始化distributor:
a.disable distributor;
b.设置中断分组;
b.设置SPI中断的路由;
c.设置SPI中断的触发类型;
d.disactive和disable所有中断源;
e.enable distributor;
(4)初始化CPU Interface:
a.设置GIC_CPU_PRIMASK,设置中断优先级mask level;
b. enable CPU interface;
相关寄存器说明
1.设置分组
group0:安全中断,由nFIQ驱动
group1:非安全中断,由nIRQ驱动
6.3 注册中断
(1) 初始化外设;
(2)查找该外设的中断在GIC-400的中断号,例如PNS timer中断号为30;
(3)设置GIC_DIST_ENABLE_SET寄存器来enable这个中断号;
(4)打开设备相关的中断,例如树莓派的generic timer,需要打开ARM_LOCAL寄存器组中的TIMER_CNTRL0寄存器中相应enable位;
(5)打开CPU的PSTATE中I位;
查树莓派手册知,PNS timer中断号为30
6.4 中断响应
1.中断触发;
2. 跳转异常向量表;
3. 跳转到GIC中断函数里,gic_handle_irq();
4. 读取GICC_IAR寄存器,获取中断号;
5. 根据中断号来进行相应中断处理,例如,若读取中断号为30,说明是PNS的generic timer,跳转到generic timer处理函数;
读取中断号;
中断处理完成,写回EOI:
timer部分核心代码:
#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>
#define HZ 250
#define NSEC_PER_SEC 8000000000L
static unsigned int val = NSEC_PER_SEC / HZ;
static int generic_timer_init(void)
{
asm volatile(
"mov x0, #1\n"
"msr cntp_ctl_el0, x0"
:
:
: "memory");
return 0;
}
static int generic_timer_reset(unsigned int val)
{
asm volatile(
"msr cntp_tval_el0, %x[timer_val]"
:
: [timer_val] "r" (val)
: "memory");
return 0;
}
static void enable_timer_interrupt(void)
{
writel(CNT_PNS_IRQ, TIMER_CNTRL0);
}
void timer_init(void)
{
generic_timer_init();
generic_timer_reset(val);
gicv2_unmask_irq(GENERIC_TIMER_IRQ);
//enable_timer_interrupt();
}
void handle_timer_irq(void)
{
generic_timer_reset(val);
printk("Core0 Timer interrupt received\r\n");
}
irq部分核心代码:
#include <arm-gic.h>
#include "io.h"
#include <asm/irq.h>
struct gic_chip_data {
unsigned long raw_dist_base;
unsigned long raw_cpu_base;
struct irq_domain *domain;
struct irq_chip *chip;
unsigned int gic_irqs;
};
#define gic_dist_base(d) ((d)->raw_dist_base)
#define gic_cpu_base(d) ((d)->raw_cpu_base)
#define ARM_GIC_MAX_NR 1
static struct gic_chip_data gic_data[ARM_GIC_MAX_NR];
/* IRQs start ID */
#define HW_IRQ_START 16
static unsigned long gic_get_dist_base(void)
{
struct gic_chip_data *gic = &gic_data[0];
return gic_dist_base(gic);
}
static unsigned long gic_get_cpu_base(void)
{
struct gic_chip_data *gic = &gic_data[0];
return gic_cpu_base(gic);
}
static void gic_set_irq(int irq, unsigned int offset)
{
unsigned int mask = 1 << (irq % 32);
writel(mask, gic_get_dist_base() + offset + (irq / 32) * 4);
}
void gicv2_mask_irq(int irq)
{
gic_set_irq(irq, GIC_DIST_ENABLE_CLEAR);
}
void gicv2_unmask_irq(int irq)
{
gic_set_irq(irq, GIC_DIST_ENABLE_SET);
}
void gicv2_eoi_irq(int irq)
{
writel(irq, gic_get_cpu_base() + GIC_CPU_EOI);
}
static unsigned int gic_get_cpumask(struct gic_chip_data *gic)
{
unsigned long base = gic_dist_base(gic);
unsigned int mask, i;
for (i = mask = 0; i < 32; i += 4) {
mask = readl(base + GIC_DIST_TARGET + i);
printk("mask:0x%x\n",mask);
mask |= mask >> 16;
mask |= mask >> 8;
printk("----irq[%d],mask:0x%x\n",i,mask);
if (mask)
break;
}
return mask;
}
static void gic_dist_init(struct gic_chip_data *gic)
{
unsigned long base = gic_dist_base(gic);
unsigned int cpumask;
unsigned int gic_irqs = gic->gic_irqs;
int i;
/* 关闭中断*/
writel(GICD_DISABLE, base + GIC_DIST_CTRL);
unsigned int cpu_group=0;
for (i = 0; i < 32; i += 4) {
cpu_group = readl(base + GIC_DIST_IGROUP+i);
printk("reg[%d],cpu_group:0x%x\n",i/4,cpu_group); //default for group0
}
/* 设置中断路由:GIC_DIST_TARGET
*
* 前32个中断怎么路由是GIC芯片固定的,因此先读GIC_DIST_TARGET前面的值
* 然后全部填充到 SPI的中断号 */
cpumask = gic_get_cpumask(gic);
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
printk("----cpumask:0x%x\n",cpumask);
for (i = 32; i < gic_irqs; i += 4)
;//writel(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
/* Set all global interrupts to be level triggered, active low */
for (i = 32; i < gic_irqs; i += 16)
//writel(GICD_INT_ACTLOW_LVLTRIG, base + GIC_DIST_CONFIG + i / 4);
writel(0x1, base + GIC_DIST_CONFIG + i / 4);
/* Deactivate and disable all 中断(SGI, PPI, SPI).
*
* 当注册中断的时候才 enable某个一个SPI中断,例如调用gic_unmask_irq()*/
for (i = 0; i < gic_irqs; i += 32) {
writel(GICD_INT_EN_CLR_X32, base +
GIC_DIST_ACTIVE_CLEAR + i / 8);
writel(GICD_INT_EN_CLR_X32, base +
GIC_DIST_ENABLE_CLEAR + i / 8);
}
/*打开SGI中断(0~15),可能SMP会用到*/
writel(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
/* 打开中断:Enable group0 and group1 interrupt forwarding.*/
writel(GICD_ENABLE, base + GIC_DIST_CTRL);
}
void gic_cpu_init(struct gic_chip_data *gic)
{
int i;
unsigned long base = gic_cpu_base(gic);
unsigned long dist_base = gic_dist_base(gic);
/*
* Set priority on PPI and SGI interrupts
*/
for (i = 0; i < 32; i += 4)
//writel(0xa0a0a0a0,dist_base + GIC_DIST_PRI + i * 4 / 4);
writel(0x30,dist_base + GIC_DIST_PRI + i * 4 / 4);
//writel(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
writel(0x80, base + GIC_CPU_PRIMASK);
writel(GICC_ENABLE, base + GIC_CPU_CTRL);
}
void gic_handle_irq(void)
{
struct gic_chip_data *gic = &gic_data[0];
unsigned long base = gic_cpu_base(gic);
unsigned int irqstat, irqnr;
do {
irqstat = readl(base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK; //get lower 9bits
if (irqnr == GENERIC_TIMER_IRQ)
handle_timer_irq();
gicv2_eoi_irq(irqnr); //write eoi
} while (0);
}
int gic_init(int chip, unsigned long dist_base, unsigned long cpu_base)
{
struct gic_chip_data *gic;
int gic_irqs;
int virq_base;
gic = &gic_data[chip];
gic->raw_cpu_base = cpu_base;
gic->raw_dist_base = dist_base;
/* readout how many interrupts are supported*/
gic_irqs = readl(gic_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;
printk("%s: cpu_base:0x%x, dist_base:0x%x, gic_irqs:%d\n",
__func__, cpu_base, dist_base, gic->gic_irqs);
gic_dist_init(gic);
gic_cpu_init(gic);
return 0;
}