做人呢,最紧要就系开心啦

ARM64基础14:ARM64的中断处理之GIC400实现(以树莓派4采用的BCM2711芯片为例)

1,227次阅读
没有评论

接上篇,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 处理完了,可以将这个中断清理,
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

5 GIC 硬件原理

GIC 是连接外设中断和 CPU 的桥梁,也是多个 CPU 之间中断互联的通道,它负责检测、管理、分发中断;
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

5.1 GIC 结构

ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

上图摘自 GIC400 官方手册,GIC 可以做到
1. 使能或禁止中断;
2. 把中断分组到 FIQ 或者 IRQ;
3. 多核系统中,可以将中断分配到不同 CPU 上;
4. 设置 GIC 给到 CPU 的触发方式(不等于外设的触发方式);
5. 支持虚拟化扩展;

5.2 GIC 功能

ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)
由上图知,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 信号;
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

6.GICv2 中断控制器

GIC-400 包括两组寄存器
D 系列:The Distributor registers(GICD), 包含中断设置和配置;
C 系列:The CPU Interface registers(GICC
),包含 CPU 相关的特殊寄存器;

6.1 访问 GIC-400 寄存器:

树莓派 4b 中 GIC-400 基地
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)
GIC-400 中地址偏移:
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

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. 设置分组
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)
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
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)

6.4 中断响应

1. 中断触发;

  1. 跳转异常向量表;
  2. 跳转到 GIC 中断函数里,gic_handle_irq();
  3. 读取 GICC_IAR 寄存器,获取中断号;
  4. 根据中断号来进行相应中断处理,例如,若读取中断号为 30,说明是 PNS 的 generic timer,跳转到 generic timer 处理函数;

读取中断号;

ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)
中断处理完成,写回 EOI:
ARM64 基础 14:ARM64 的中断处理之 GIC400 实现(以树莓派 4 采用的 BCM2711 芯片为例)
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;
}

正文完
 
admin
版权声明:本站原创文章,由 admin 2021-11-02发表,共计7135字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)