首页 > 其他分享 >minos 2.3 中断虚拟化——GICv2 管理

minos 2.3 中断虚拟化——GICv2 管理

时间:2024-06-08 15:24:18浏览次数:13  
标签:GICD 中断 irq gicv2 CPU GICv2 2.3 cpu minos

  • 首发公号:Rand_cs

硬件肯定需要软件配合,这一节就来实战 GICv2

首先准备好 GICv2 手册:https://developer.arm.com/documentation/ihi0048/bb/?lang=en,对于硬件的管理,最底层的操作就是读写硬件的寄存器,所以这里准备好手册,随时查阅手册

// gicv2.c 

static void *gicv2_dbase;
static void *gicv2_cbase;

首先定义了两个变量,gicc(cpu interface) 和 gicd(distributor)基地值,这个值是在设备树文件中定义,然后 gic 初始化函数中映射到 hyp 虚拟地址

static inline void writeb_gicd(uint8_t val, unsigned int offset)
{
    writeb_relaxed(val, gicv2_dbase + offset);
}

static inline void writel_gicd(uint32_t val, unsigned int offset)
{
    writel_relaxed(val, gicv2_dbase + offset);
}

static inline uint32_t readl_gicd(unsigned int offset)
{
    return readl_relaxed(gicv2_dbase + offset);
}

static inline void writel_gicc(uint32_t val, unsigned int offset)
{
    writel_relaxed(val, gicv2_cbase + offset);
}

static inline uint32_t readl_gicc(unsigned int offset)
{
    return readl_relaxed(gicv2_cbase + offset);
}

随后定义了一系列读写寄存器的函数,就是相对 (gicc/gicd) 不同的偏移上进行读写,下面我们来看看主要需要操作哪些寄存器

GICD

GICD_ISENABLERn、 GICD_ICENABLERn

一个 GICD_ISENABLER 寄存器 32 bit,每一位控制一个中断的使能情况,n 表示第几个 GICD_ISENABLER 寄存器。向某位写 1 表示使能该中断,写 0 无效。

一个 GICD_ICENABLER 寄存器 32 bit,每一位控制一个中断的屏蔽情况,n 表示第几个 GICD_ICENABLER 寄存器。向某位写 1 表示屏蔽该中断,写 0 无效。

在 ARM 平台,使能和屏蔽一个中断是通过不同的寄存器来控制的,而不是一个寄存器使用 0 1 来分别表示屏蔽使能。这么做的好处之前有了解过是为了操作可以并行,比如说现在有操作使能第一号中断和屏蔽第二号中断,如果只使用0、1的方式来控制中断的屏蔽使能情况,第一号中断和第二号中断位于同一个寄存器,那么想要实现此操作,需要串行执行,但是如果使用 GICD_ISENABLER 来控制使能,GICD_ICENABLER 控制屏蔽,那么两个步骤就可以并行。另外因为写 0 无效,所以想要执行写操作的时候,不用先读后写(一般操作:read a,a |= x,write a,现在直接 write x)

// 屏蔽中断号为 irq 的中断
static void gicv2_mask_irq(uint32_t irq)
{
    unsigned long flags;

    spin_lock_irqsave(&gicv2_lock, flags);
    // 写 0 无效,所以可以直接写入值 1UL << (irq % 32)
    // irq / 32 表示第几个 GICD_ICENABLER 寄存器
    // (irq / 32) * 4,按字节算偏移,乘以 4
    writel_gicd(1UL << (irq % 32), GICD_ICENABLER + (irq / 32) * 4);
    dsb();
    spin_unlock_irqrestore(&gicv2_lock, flags);
}
// 使能中断号为 irq 的中断
static void gicv2_unmask_irq(uint32_t irq)
{
    unsigned long flags;

    spin_lock_irqsave(&gicv2_lock, flags);
    writel_gicd(1UL << (irq % 32), GICD_ISENABLER + (irq / 32) * 4);
    dsb();
    spin_unlock_irqrestore(&gicv2_lock, flags);
}

GICD_ICFGRn

Interrupt Configuration Registers,用来配置中断的类型

中断类型:边沿触发、电平触发

中断模型:

  1. 1-N 模型,只有一个 cpu 能够处理该中断,这指的是一个中断发送给多个 cpu,当此中断被某个 cpu 响应之后,gic 会清除掉此中断在其他所有 cpu 上的 pending 状态。
    1. 这是专门用来描述 SPI 类型中断的。试想,磁盘数据好了,发送磁盘中断信号给 cpu,每个 cpu 都可以处理,但是肯定只能有一个 cpu 处理,否则数据紊乱
    2. 当 CPU 从 GICC_IAR 读取中断号的时候,可能会读取到正确的中断号,这是响应 cpu。其他 cpu 会读取到 1023,这表示一个伪中断。
  2. N-N 模型,所有 cpu 都会独立的收到该中断信号,当一个 cpu 相响应中断后,只是为该 cpu 清除掉此中断的 pending 状态。此中断对于其他 cpu 来说仍然是 pending 状态,其他 cpu 仍然需要响应处理此中断

GICD_ICFGR 寄存器就是用来配置一个中断的类型和模型

一个 GICD_ICFGR 寄存器 32bits,一个中断占 2bits

bit[0] 表示中断模型,为 0 表示 N-N 模型,为 1 表示 1-N 模型

bit[1] 表示中断类型,为 0 表示电平触发,为 1 表示边沿触发

static int gicv2_set_irq_type(uint32_t irq, uint32_t type)
{
    uint32_t cfg, edgebit;

    if (irq < 16)
        return 0;

    spin_lock(&gicv2_lock);

    /* Set edge / level */
    cfg = readl_gicd(GICD_ICFGR + (irq / 16) * 4);
    edgebit = 2u << (2 * (irq % 16));  // 边沿触发
    if ( type & IRQ_FLAGS_LEVEL_BOTH)
        cfg &= ~edgebit;
    else if (type & IRQ_FLAGS_EDGE_BOTH)
        cfg |= edgebit;

    writel_gicd(cfg, GICD_ICFGR + (irq / 16) * 4);
    spin_unlock(&gicv2_lock);

    return 0;
}

GICD_IPRIORITYR

Interrupt Priority Registers,设置每个中断的优先级

一个 GICD_IPRIORITYR 寄存器 32 bits,分为 4 组,每 8 bits 表示一个中断的优先级

static int gicv2_set_irq_affinity(uint32_t irq, uint32_t pcpu)
{
    if (pcpu > NR_GIC_CPU_IF || irq < 32)
        return -EINVAL;

    spin_lock(&gicv2_lock);
    /* Set target CPU mask (RAZ/WI on uniprocessor) */
    writeb_gicd(1 << pcpu, GICD_ITARGETSR + irq);
    spin_unlock(&gicv2_lock);
    return 0;
}

GICD_ITARGETSR

Interrupt Processor Targets Registers,设置中断亲和性

一个 GICD_ITARGETSR 寄存器 32 bits,分为 4 组,每 8 bits 表示一个中断的亲和性。举个例子,如果某个中断的亲和性设置 3(0b11),那么这个中断将会被 distributor 转发给 0、1 号 cpu interface。

另外,前面提到过,SPI 的中断号从 32 开始,所以 GICD_ITARGETSR0~GICD_ITARGETSR7 "无用",读取GICD_ITARGETSR0~GICD_ITARGETSR7会返回执行读取操作 cpu 的 id 值

static int gicv2_set_irq_affinity(uint32_t irq, uint32_t pcpu)
{
    if (pcpu > NR_GIC_CPU_IF || irq < 32)
        return -EINVAL;

    spin_lock(&gicv2_lock);
    /* Set target CPU mask (RAZ/WI on uniprocessor) */
    writeb_gicd(1 << pcpu, GICD_ITARGETSR + irq);
    spin_unlock(&gicv2_lock);
    return 0;
}

GICD_SGIR

Software Generated Interrupt Register,写这个寄存器来产生 SGI 中断

关注两个字段:

  • TargetListFilter
    • 0b00,表示向位于 CPUTargetList 位图中的 CPU Interface集合发送 SGI
    • 0b01,表示向所有 CPU Interface发送一个 SGI 中断
    • 0b10,表示向自己发送一个 SGI
    • 0b11,reserved
  • CPUTargetList,一个 CPU 位图,某一位为 1 表示要向该 CPU Interface 发送一个 SGI(TargetListFilter=0b00 的情况下)
static void gicv2_send_sgi(uint32_t sgi, enum sgi_mode mode, cpumask_t *mask)
{
    unsigned int cpu;
    unsigned int value = 0;

    switch (mode) {
    // 发送一个 SGI 给所有 CPU
    case SGI_TO_OTHERS:
        writel_gicd(GICD_SGI_TARGET_OTHERS | sgi, GICD_SGIR);
        break;
    // 给自己发送一个 SGI
    case SGI_TO_SELF:
        writel_gicd(GICD_SGI_TARGET_SELF | sgi, GICD_SGIR);
        break;
    // 发送一个 SGI 给目标 CPU 组
    case SGI_TO_LIST:
        for_each_cpu(cpu, mask)
            value |= gic_cpu_mask[cpu];
        // 填写目标 CPU 位图集合
        writel_gicd(GICD_SGI_TARGET_LIST |
            (value << GICD_SGI_TARGET_SHIFT) | sgi,
            GICD_SGIR);
        isb();
        break;
    default:
        break;;
    }
}

NOTE,Distributor 只会向 CPU Interface 转发中断请求,CPU Interface 才会向 CPU 发送中断信号

GICD_TYPER

Interrupt Controller Type Register,有关 GICD 的一些信息

ITLinesNumber:支持的中断个数

CPUNumber:CPU 个数

GICC

GICC_EOIR、GICC_DIR

当一个中断完成的时候,CPU 必须发送一个 “complete” 信号给 GIC,“complete” 步骤分为两步:

  1. Priority drop,优先级降低。中断状态并未改变,仍然是 active。优先级降低指的是当前 CPU 上 "running priority",降低之后 cpu interface 可以继续发送优先级较低的中断给 cpu。
  2. Interrupt deactivation,真正改变中断状态了。可以从 active->inactive,active and pending->pending

这涉及到了 3 个寄存器,GICC_EOIR(End of Interrupt Register),GICC_DIR(Deactivate Interrupt Register),GICC_CTLR(CPU Interface Control Register)

当 GICC_CTLR.EOImode = 1 时,Priority drop 和 Interrupt deactivation 两个步骤是分开的,也就是说写 GICC_EOIR 会 Priority drop,写 GICC_DIR 会 Interrupt deactivation

当 GICC_CTLR.EOImode = 0 是,Priority drop 和 Interrupt deactivation 两个步骤是在一起的,写 GICC_EOIR 寄存器就表示完成两个步骤

GICC_DIR 和 GICC_EOIR 格式都如下所示:

static void gicv2_eoi_irq(uint32_t irq)
{
    writel_gicc(irq, GICC_EOIR);
    dsb();
}

static void gicv2_dir_irq(uint32_t irq)
{
    writel_gicc(irq, GICC_DIR);
    dsb();
}

用法也很简单,写入相应的中断号就行

GICC_IAR

Interrupt Acknowledge Register,CPU 读取此寄存器来获取中断号,并且也是一个 ACK 操作

static uint32_t gicv2_read_irq(void)
{
    uint32_t irq;

    irq = readl_gicc(GICC_IAR);
    isb();
    irq = irq & GICC_IA_IRQ;

    return irq;
}

格式同前 EOIR,操作也是很简单, 从低 10 bits 获取中断号

GICC_PMR

Interrupt Priority Mask Register,只有优先级比 GICC_PMR 里面设置的高,才会将该中断信号发送给 cpu

格式如上,一般我们写 0xff,表示不屏蔽任何中断(值越大,优先级越低)

GIC INIT

gicv2_dist_init

static void __init_text gicv2_dist_init(void)
{
    uint32_t type;
    uint32_t cpumask;
    uint32_t gic_cpus;
    unsigned int nr_lines;
    int i;

    // 所有中断都往 pcpu0 发送
    cpumask = readl_gicd(GICD_ITARGETSR) & 0xff;
    cpumask = (cpumask == 0) ? (1 << 0) : cpumask;
    cpumask |= cpumask << 8;
    cpumask |= cpumask << 16;

    /* Disable the distributor */
    writel_gicd(0, GICD_CTLR);

    // 从 GICD_TYPER 寄存器里面获取 cpu 数量和支持的中断数
    type = readl_gicd(GICD_TYPER);
    nr_lines = 32 * ((type & GICD_TYPE_LINES) + 1);
    gic_cpus = 1 + ((type & GICD_TYPE_CPUS) >> 5);
    pr_notice("GICv2: %d lines, %d cpu%s%s (IID %x).\n",
        nr_lines, gic_cpus, (gic_cpus == 1) ? "" : "s",
        (type & GICD_TYPE_SEC) ? ", secure" : "",
        readl_gicd(GICD_IIDR));

    /* Default all global IRQs to level, active low */
    // 配置所有 irq 为边沿触发
    for ( i = 32; i < nr_lines; i += 16 )
        writel_gicd(0x0, GICD_ICFGR + (i / 16) * 4);

    /* Route all global IRQs to this CPU */
    // 配置所有中断的亲和性为 cpu0
    for ( i = 32; i < nr_lines; i += 4 )
        writel_gicd(cpumask, GICD_ITARGETSR + (i / 4) * 4);

    /* Default priority for global interrupts */
    for ( i = 32; i < nr_lines; i += 4 )
        writel_gicd(GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 |
            GIC_PRI_IRQ << 8 | GIC_PRI_IRQ,
            GICD_IPRIORITYR + (i / 4) * 4);

    /* Disable all global interrupts */
    // 屏蔽所有中断
    for ( i = 32; i < nr_lines; i += 32 )
        writel_gicd(~0x0, GICD_ICENABLER + (i / 32) * 4);

    /* Only 1020 interrupts are supported */
    // 从支持的中断数和 1020 之中选一个小的
    gicv2_nr_lines = min(1020U, nr_lines);

    /* Turn on the distributor */
    // 使能所有中断
    writel_gicd(GICD_CTL_ENABLE, GICD_CTLR);
    dsb();
}

gicv2_cpu_init

static void __init_text gicv2_cpu_init(void)
{
    int i;
    int cpuid = smp_processor_id();
    // 读取 GICD_ITARGETSR0 会返回当前 cpuid
    gic_cpu_mask[cpuid] = readl_gicd(GICD_ITARGETSR) & 0xff;
    pr_debug("gicv2 gic mask of cpu%d: 0x%x\n", cpuid, gic_cpu_mask[cpuid]);
    if (gic_cpu_mask[cpuid] == 0)
        gic_cpu_mask[cpuid] = 1 << cpuid;

    /* The first 32 interrupts (PPI and SGI) are banked per-cpu, so
     * even though they are controlled with GICD registers, they must
     * be set up here with the other per-cpu state. */
    // TODO ???
    writel_gicd(0xffff0000, GICD_ICENABLER); /* Disable all PPI */
    writel_gicd(0x0000ffff, GICD_ISENABLER); /* Enable all SGI */

    /* Set SGI priorities */
    // 设置 SGI 的优先级,IPIs must preempt normal interrupts
    for ( i = 0; i < 16; i += 4 )
        writel_gicd(GIC_PRI_IPI << 24 | GIC_PRI_IPI << 16 |
            GIC_PRI_IPI << 8 | GIC_PRI_IPI,
            GICD_IPRIORITYR + (i / 4) * 4);

    /* Set PPI priorities */
    // 设置 PPI 的优先级
    for ( i = 16; i < 32; i += 4 )
        writel_gicd(GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 |
            GIC_PRI_IRQ << 8 | GIC_PRI_IRQ,
            GICD_IPRIORITYR + (i / 4) * 4);

    /* Local settings: interface controller */
    /* Don't mask by priority */
    writel_gicc(0xff, GICC_PMR);
    /* Finest granularity of priority */
    writel_gicc(0x0, GICC_BPR);
    /* Turn on delivery */
    // GICC_CTL_ENABLE 允许 group1(非安全中断,目前minos里面都是)中断发送给 cpu
    // GICC_CTL_EOI drop priority 和 deactivate interrupt 分开
    writel_gicc(GICC_CTL_ENABLE|GICC_CTL_EOI, GICC_CTLR);
    dsb();
}

gicv2_init

static struct irq_chip gicv2_chip = {
    .irq_mask       = gicv2_mask_irq,
    .irq_mask_cpu       = gicv2_mask_irq_cpu,
    .irq_unmask         = gicv2_unmask_irq,
    .irq_unmask_cpu     = gicv2_unmask_irq_cpu,
    .irq_eoi        = gicv2_eoi_irq,
    .irq_dir        = gicv2_dir_irq,
    .irq_set_type       = gicv2_set_irq_type,
    .irq_set_affinity   = gicv2_set_irq_affinity,
    .send_sgi       = gicv2_send_sgi,
    .get_pending_irq    = gicv2_read_irq,
    .irq_set_priority   = gicv2_set_irq_priority,
    .irq_xlate      = gic_xlate_irq,
    .init           = gicv2_init,
    .secondary_init     = gicv2_secondary_init,
};

irq_chip 是一个 gic 芯片抽象,对于 gicv2 的抽象定义在了 gicv2_chip,大多操作我们都讲述了

gicv2_init 涉及设备树操作以以及虚拟化的初始化,后面讲述。gicv2_secondary_init 这是级联相关(一个 gic 芯片不够,多个 gic 芯片连接起来共同工作)目前不涉及

  • 首发公号:Rand_cs

标签:GICD,中断,irq,gicv2,CPU,GICv2,2.3,cpu,minos
From: https://www.cnblogs.com/randcs/p/18238634

相关文章

  • minos 2.2 中断虚拟化——异常处理流程
    首发公号:Rand_cs上一节讲述了ARMv8异常模型,很多理论,这一节来看一个实际的例子,来看看minos中的异常处理流程异常向量表直接来看minos的异常向量表,很多事情就明了了:elx_vectors:c0sync://CurrentELwithSP0BAD_MODEVECTOR_C0_SYNC.balign0x80c0ir......
  • minos 2.5 中断虚拟化——vGIC
    首发公号:Rand_cs这一节开始讲述真正的中断虚拟化,首先来看硬件方面的虚拟化。前文minos2.3中断虚拟化——GICv2管理主要讲述GICv2的Distributor和CPUInterface,在Hypervisor存在的情况下,它们都是为Hypervisor服务的。现在有了vm,vm里面的内核也需要操作GIC,怎么......
  • minos 2.6 中断虚拟化——虚拟中断子系统
    首发公号:Rand_csHypervisor需要对每个虚机的虚拟中断进行管理,这其中涉及的一系列数据结构和操作就是虚拟中断子系统VIRQ虚拟中断描述符structvcpu{uint32_tvcpu_id;.........../**membertorecordtheirqlistwhichthe*vcpuishandlingn......
  • minos 2.4 中断虚拟化——中断子系统
    首发公号:Rand_cs前面讲述了minos对GICv2的一些配置和管理,这一节再往上走一走,看看minos的中断子系统中断中断描述符/**ifairqishandledbyminos,thenneedtoregister*theirqhandlerotherwiseitwillreturnthevnum*tothehandlerandpassthe......
  • 信息系统项目管理师0145:敏捷与适应方法(9项目范围管理—9.2项目范围管理过程—9.2.3敏
    点击查看专栏目录文章目录9.2.3敏捷与适应方法9.2.3敏捷与适应方法  对于需求不断变化、风险大或不确定性高的项目,在项目开始时通常无法明确项目的范围,而需要在项目期间逐渐明确。敏捷或适应型方法特意在项目早期缩短定义和协商范围的时间,为后续细化范围、明......
  • C++Primer Plus第十二章程序清单12.1~12.3一个失败的例子分析
    12.1.2特殊成员函数StringBad类的问题是由特殊成员函数引起的。这些成员函数是自动定义的,就StringBad而言,这些函数的行为与类设计不符。具体地说,C++自动提供了下面这些成员函数:1,默认构造函数,如果没有定义构造函数;2,默认析构函数,如果没有定义;3,复制构造函数,如果没有......
  • 2.3Docker部署java工程
    2.3Docker部署java工程1.导入jar包2.在Docker部署jdk(容器名为myjdk17)3.修改jar包名mv原包名新包名4.配置启动脚本Dockerfile是一个文本文件,其中包含了构建Docker镜像所需的一系列步骤和指令。通过编写Dockerfile文件,可以生成我们想要的镜像。基于JDK17镜像使......
  • 意得辑真不错,85喆优惠码延长到25.12.31
    意得辑真不错,85喆优惠码延长到25.12.31了我用editage意得辑润色SCI已经第4年了,今天他家的学术支持老师让我写几句感受,那我真的感受太多了。因为下单太多一度被导师怀疑是在他家套经费。22年刚读博同时润色了三篇,被导师叫到办公室,问我是什么途径联系到的。我说师兄给说的,网上下......
  • 02-2.3.1 单链表的定义
    ⚠️这两天实在过于忙碌,以至于没有学习的时间,今天闲下来,立刻恢复更新,有等待我最新文章的小伙伴实在抱歉!⚠️另外,有喜欢我笔记的小伙伴可以订阅我的《数据结构》专栏,该专栏收录在我的大专栏《cs-self-learning》中,也可以订阅,日后会有更多有用的技能、笔记全部收录在大专栏里......
  • 一个简单的OCR识别引擎,但是很强大,支持80+的语言,Star 22.3K+!(本文附带一个简单的开发教
    OCR(OpticalCharacterRecognition,光学字符识别)已经融入到我们日常生活和工作中,有可能你没感觉到,但是你一定用到过,比如常见的一些场景:文档数据存储:例如,将纸质书籍、报纸、杂志或其他文件转换为可编辑的文本格式,便于存储和搜索;自动数据录入:比如在报销发票、整理收据时。利......