《从0开始写一个微内核操作系统》6-GIC中断

ChinOS

https://github.com/jingjin666/GN-base/tree/chinos

关于GIC

GIC是一个针对Cortex-A和Cortex-R设计的中断控制器,SOC厂商根据选择的CORE来选择不同的GIC版本进行搭配
在这里插入图片描述

GICv3

GICv3的文档请参考ARM文档中心Documentation – Arm Developer
Arm Generic Interrupt Controller v3 and v4 Overview

GICv3架构

GICv3由以下模块组成

  • Distributor interface
    • SPI中断的管理,将中断发送给redistributor
  • Redistributor interface
    • PPI,SGI,LPI中断的管理,将中断发送给cpu interface
  • CPU interface
    • 传输中断给core
  • ITS
    • 用来解析LPI中断

其中,cpu interface是实现在core内部的,distributor,redistributor,ITS是实现在gic内部的

在这里插入图片描述

中断分组

在这里插入图片描述

不同异常等级下的分组:

  • GROUP0下的所有中断都被识别为FIQ中断;
  • 非安全世界下的EL0/1/2下,GROUP1下的所有中断被识别为IRQ中断,这也是我们操作系统最常用的中断分组,一般操作系统和hypervisor都处于非安全世界(除了TrustOS外)

在这里插入图片描述

中断类型

Shared Peripheral Interrupt (SPI)

共享外设中断,也就是通常用的外设中断,可以多个CPU或者说CORE处理,不限定特定的CPU

Private Peripheral Interrupt (PPI)

私有外设中断,这是每个核心私有的中断。PPI会送达到指定的CPU上,应用场景有CPU本地时钟

Software Generated Interrupt (SGI)

软中断,软件可以通过写GICD_SGIR寄存器来触发一个中断事件,一般用于核间通信

Locality-specific Peripheral Interrupt (LPI)

消息中断,不通过专用中断线向GIC发送消息,通过写GIC寄存器发送中断

不同中断类型的中断号划分

在这里插入图片描述

中断处理流程

通过distributor的中断流程

  • 外设发起中断,发送给distributor
  • distributor将该中断,分发给合适的re-distributor
  • re-distributor将中断信息,发送给cpu interface
  • cpu interface产生合适的中断异常给处理器
  • 处理器接收该异常,并且处理该中断

不通过distributor,比如LPI中断

  • 外设发起中断,发送给ITS
  • ITS分析中断,决定将来发送的re-distributor
  • ITS将中断发送给合适的re-distributor

GICv3初始化

GIC的初始化实际上就是其主要模块(distributor/redistributor/cpu_interface)的初始化

Distributor初始化

Distributor主要初始化和配置SPI中断:


static void distributor_init(void)
{
    u32 i;

    // read gic version
    u32 pidr2 = getreg32(GICD_PIDR2);
    u32 gic_version = bitfield_get(pidr2, 4, 4);
    assert((gic_version == ARCH_REV_GICV3) || (gic_version == ARCH_REV_GICV4));

    // read gic max interrupts
    u32 typer = getreg32(GICD_TYPER);
    gic_max_int = (bitfield_get(typer, 0, 5) + 1) * 32;
    kprintf("gic_version = %d, gic_max_int = %d, typer = %p\n", gic_version, gic_max_int, typer);

    // disable distributor
    putreg32(0, GICD_CTLR);
    gicd_wait_rwp();

    // assume level-trigger
    for (i = 32; i < gic_max_int; i += 16) {
        putreg32(0, GICD_ICFGR(i / 16));
    }

    // default priority for global interrupts
    u32 priority = (GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 | GIC_PRI_IRQ << 8 |
                GIC_PRI_IRQ);
    for (i = 32; i < gic_max_int; i += 4) {
        putreg32(priority, GICD_IPRIORITYR(i / 4));
    }

    // distributor config: mask and clear all spis, set group 1.
    for (i = 32; i < gic_max_int; i += 32) {
        putreg32(~0, GICD_ICENABLER(i / 32));
        putreg32(~0, GICD_ICPENDR(i / 32));
        putreg32(~0, GICD_IGROUPR(i / 32));
        putreg32(0, GICD_IGRPMODR(i / 32));
    }
    gicd_wait_rwp();
    
    // enable distributor with ARE, group 1 enable
    putreg32(CTLR_ENABLE_G0 | CTLR_ENABLE_G1NS | CTLR_ARE_S, GICD_CTLR);
    gicd_wait_rwp();

    // route all global IRQs to this CPU
    u64 affinity = mpidr_to_gic_affinity();
    kprintf("affinity = %p\n", affinity);
    for (i = 32; i < gic_max_int; i++) {
        putreg64(GICD_IROUTER(i), affinity);
    }
    gicd_wait_rwp();

    isb();
    dsb();
}

Redistributor初始化

Redistributor主要初始化和配置PPI/SGI/LPI中断


static void redistributor_init(void)
{
    u32 i;
    u32 cpu = 0;

    // mark PE online
    u32 waker = getreg32(GICR_WAKER(cpu));
    kprintf("waker = %p\n", waker);
    waker &= ~GICR_WAKER_ProcessorSleep;
    kprintf("waker = %p\n", waker);
    putreg32(waker, GICR_WAKER(cpu));
    while (getreg32(GICR_WAKER(cpu)) & GICR_WAKER_ChildrenAsleep) {
        kprintf("GICv3: GICR_WAKER returned non-zero %x\n", waker);
        halt();
    }

    /* deactivate SGIs/PPIs */
    putreg32(~0, GICR_ICACTIVER0(cpu));
    gicr_wait_rwp();

    /* set ICFGR0 for SGIs as edge-triggered */
    u32 icfgr0 = getreg32(GICR_ICFGR0(cpu));
    kprintf("icfgr0 = %p\n", icfgr0);
    putreg32(0xaaaaaaaa, GICR_ICFGR0(cpu));
    gicr_wait_rwp();

    /* set ICFGR1 for PPIs as level-triggered */
    u32 icfgr1 = getreg32(GICR_ICFGR1(cpu));
    kprintf("icfgr1 = %p\n", icfgr1);
    putreg32(0, GICR_ICFGR1(cpu));
    gicr_wait_rwp();

    /* set priority on PPI and SGI interrupts */
    u32 priority = (GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 | GIC_PRI_IRQ << 8 |
                GIC_PRI_IRQ);
    for (i = 0; i < 32; i += 4) {
        putreg32(priority, GICR_IPRIORITYR0(cpu) + i);
    }
    gicr_wait_rwp();

    // redistributer config: configure sgi/ppi as non-secure group 1.
    putreg32(~0, GICR_IGROUPR0(cpu));
    gicr_wait_rwp();

    // redistributer config: clear and mask sgi/ppi.
    putreg32(~0, GICR_ICENABLER0(cpu));
    putreg32(~0, GICR_ICPENDR0(cpu));
    gicr_wait_rwp();

    // TODO lpi init
}

cpu_interface初始化

static void cpu_interface_init(void)
{
    u32 sre;
    // enable system register access
    MRS(ICC_SRE_EL1, sre);
    kprintf("sre = %p\n", sre);
    sre |= GICC_SRE_EL1_SRE;
    kprintf("sre = %p\n", sre);
    MSR(ICC_SRE_EL1, sre);

    // no priority grouping: ICC_BPR1_EL1
    MSR(ICC_BPR1_EL1, 0);

    // set priority mask register: ICC_PMR_EL
    MSR(ICC_PMR_EL1, DEFAULT_PMR_VALUE);

    // EOI drops priority and deactivates the interrupt: ICC_CTLR_EL1
    u32 icc_ctlr;
    MRS(ICC_CTLR_EL1, icc_ctlr);
    kprintf("icc_ctlr = %p\n", icc_ctlr);
    icc_ctlr &= ~GICC_CTLR_EL1_EOImode_drop;
    kprintf("icc_ctlr = %p\n", icc_ctlr);
    MSR(ICC_CTLR_EL1, icc_ctlr);

    // Enable Group1 interrupts: ICC_IGRPEN1_EL1
    MSR(ICC_IGRPEN1_EL1, 1);

    // Sync at once at the end of cpu interface configuration
    isb();
    dsb();
}

中断向量表初始化

不同异常等级下所对应的中断向量表VBAR_ELx,决定了中断产生后跳转到哪个地址去执行

static void relocate_vector(void)
{
    unsigned long current_el;
    unsigned long vbase = (unsigned long)vector;

    MRS("CurrentEL", current_el);
    current_el = bitfield_get(current_el, 2, 2);
    switch(current_el)
    {
        case 0:
            break;
        case 1:
            MSR("VBAR_EL1", vbase);
            MRS("VBAR_EL1", vbase);
            kprintf("VBAR_EL1 = %p\n", vbase);
            break;
        case 2:
            MSR("VBAR_EL2", vbase);
            MRS("VBAR_EL2", vbase);
            kprintf("VBAR_EL2 = %p\n", vbase);
            break;
        case 3:
            MSR("VBAR_EL3", vbase);
            MRS("VBAR_EL3", vbase);
            kprintf("VBAR_EL3 = %p\n", vbase);
            break;
        default:
            break;
    }
}

vector符号定义在vector.S中,注意一个异常向量表占用0x800空间,需要2^11=2048字节对齐;
一个异常向量表里有16个表项,每个表项占用0x80空间,需要2^7=128字节对齐

.global vector

.macro ventry label
.align 7
    b       \label
.endm

.section ".vector", "ax"
.align 11

vector:
    ventry  invalid_vector_entry           // Synchronous EL1t/EL2t
    ventry  invalid_vector_entry           // IRQ EL1t/EL2t
    ventry  invalid_vector_entry           // FIQ EL1t/EL2t
    ventry  invalid_vector_entry           // SError EL1t/EL2t

    ventry  cur_el_sync                    // Current EL Synchronous (EL1/2)
    ventry  cur_el_irq                     // IRQ
    ventry  invalid_vector_entry           // FIQ
    ventry  cur_el_serr                    // SError

    ventry  lower_el_sync                  // Synchronous 64-bit EL0/EL1
    ventry  lower_el_irq                   // IRQ 64-bit EL0/EL1
    ventry  invalid_vector_entry           // FIQ 64-bit EL0/EL1
    ventry  lower_el_serr                  // SError 64-bit EL0/EL1

    ventry  invalid_vector_entry           // Synchronous 32-bit EL0/EL1
    ventry  invalid_vector_entry           // IRQ 32-bit EL0/EL1
    ventry  invalid_vector_entry           // FIQ 32-bit EL0/EL1
    ventry  invalid_vector_entry           // SError 32-bit EL0/EL1

invalid_vector_entry:
    b   .

cur_el_sync:
    b   .

cur_el_irq:
    bl      decode_irq
    eret
    b .

cur_el_serr:
    b   .

lower_el_sync:
    b .

lower_el_irq:
    b .

lower_el_serr:
    b .

默认情况下,不同异常等级使用各自异常等级下的VBAR_ELx,所以表1和表4一般不使用,表2和表3使用的最多,表2代表在当前异常等级产生的中断,表3代表在比当前异常等级低产生中断。对于我们操作系统来说,操作系统工作在EL1,那么表2代表在内核态(EL1)下产生的中断,表1代表在用户态(EL0)产生的中断

使能VBAR_ELx

SPSel为1代表使能

    u64 spsel;
    MRS("SPSel", spsel);
    kprintf("SPSel = %p\n", spsel);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
GIC-400 中断对照表是用于指导中断控制器与处理器之间的中断信号传递的参考文档。在计算机系统中,中断是处理器接收到某种事件或外设请求后暂停当前任务并转为执行其他任务的一种机制。中断对照表主要是为了在处理器和中断控制器之间建立一个映射关系,使得处理器能够正确识别和响应不同的中断请求。 GIC-400 中断对照表中列举了可能的中断源和对应的中断号码。中断源可以是各种外设、系统事件或处理器内部的异常情况。中断号码是通过中断源的类型和优先级进行编码的,用于唯一标识不同的中断请求。 通过中断对照表,处理器可以对特定的中断源进行配置,指定中断号码和中断处理程序。当中断源产生中断信号时,中断控制器会将中断请求传递给处理器,并根据中断对照表的配置将其正确映射到相应的中断号码。处理器会根据中断号码调用相应的中断处理程序来处理中断请求。 中断对照表在系统设计和开发中起到了重要的作用。它定义了不同的中断源和中断号码的关系,帮助系统开发人员正确配置中断控制器和处理器的中断连接。通过合理配置中断对照表,可以确保系统能够快速、准确地响应不同的中断请求,提高系统的可靠性和性能。 总之,GIC-400 中断对照表是用于指导中断控制器和处理器之间的中断信号传递的重要参考文档。它列举了可能的中断源和对应的中断号码,帮助系统开发人员正确配置中断连接,确保系统能够快速、准确地响应不同的中断请求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值