从源码分析arm64中断与GIC

news/2025/2/6 1:38:04/文章来源:https://www.cnblogs.com/alanli07/p/18700378

本文以树莓派4b(armv8)来实现,4b支持两种

  • 传统的中断控制器
  • gic-400
    但是使用的qemu和实际的板子都是默认支持gic-400的,所以主要是借助gic-400实现中断的功能

异常处理

相关寄存器

  • PSTATE 就是cpu状态
    • DAIF 调试异常 SError(系统异常) IRQ(中断) FIQ(快速中断)
  • esr_elx 用来保存返回地址
  • spsr_elx 用来保存对应级别的PSTATE
  • elr_elx 用来保存异常的原因

处理异常时自动发生的事

CPU捕获到异常时

  1. 将PSTATE保存到对应的SPSR_ELx中
  2. 返回地址保存到ELR_ELx中
  3. PSTATE DAIF关闭
  4. 如果是同步异常把原因写入ESR_ELx,如果是中断,原因保存在GIC-400的寄存器中
  5. 切换SP到对应的SP_ELx中
  6. 跳转到中断向量表里
  7. 执行对应的处理函数

CPU处理完异常执行eret,会

  1. ELR中恢复PC
  2. SPSR中恢复PSTATE (DAIF也会变)

中断向量表

可见通过保存到对应异常的vbar中,CPU就可以在对应级别是发生异常进入中断向量表中
由于内核目前一直在EL1阶段,所以在el1的初始化函数el1_entry

el1_entry:// 加入向量表adr x0, vectorsmsr vbar_el1, x0

其中vector的实现是参考linux的实现

    .macro kernel_ventry, el, label, regsize=64.align 7sub sp, sp, #S_FRAME_SIZEb el\()\el\()_\label.endm.pushsection ".entry.text", "ax".align 11
ENTRY(vectors)kernel_ventry	1, sync_invalid			// Synchronous EL1tkernel_ventry	1, irq_invalid			// IRQ EL1tkernel_ventry	1, fiq_invalid			// FIQ EL1tkernel_ventry	1, error_invalid		// Error EL1tkernel_ventry	1, sync_invalid				// Synchronous EL1hkernel_ventry	1, irq                      // IRQ EL1hkernel_ventry	1, fiq_invalid			// FIQ EL1hkernel_ventry	1, error_invalid			// Error EL1hkernel_ventry	0, sync_invalid				// Synchronous 64-bit EL0kernel_ventry	0, irq_invalid				// IRQ 64-bit EL0kernel_ventry	0, fiq_invalid			// FIQ 64-bit EL0kernel_ventry	0, error_invalid			// Error 64-bit EL0kernel_ventry	0, sync_invalid, 32		// Synchronous 32-bit EL0kernel_ventry	0, irq_invalid, 32		// IRQ 32-bit EL0kernel_ventry	0, fiq_invalid, 32		// FIQ 32-bit EL0kernel_ventry	0, error_invalid, 32		// Error 32-bit EL0
END(vectors)

kernel_ventry 值得说一下 el\()\el\()_\label\()表示一个符号的结尾,el 然后是结尾\() ,接着\el 这个就是传入宏第一个的参数,然后是结尾\(),然后是_,在跟着\label也就是第二个参数

vectors 就是个位置,依次放着各个异常的入口,只要依次实现这些入口函数,CPU发生异常的时候就会进入对应的处理函
数。

el0_sync_invalid为例,现在先忽略kernel_entry,这个是用来现场保护的,最后会到bad_mode中进行异常的处理

/** Invalid mode handlers*/.macro	inv_entry, el, reason, regsize = 64bl kernel_entrymov	x0, spmov	x1, #\reasonmrs	x2, esr_el1b	bad_mode.endmel0_sync_invalid:inv_entry 0, BAD_SYNC
ENDPROC(el0_sync_invalid)

中断现场保护和恢复

这时候就不得不说中断发生时的现场保护了
与异常不同最后需要恢复现场

el1_irq:bl kernel_entrybl irq_handlebl kernel_exit
ENDPROC(el1_irq)

一个线框的定义是

struct pt_regs {struct {u64 regs[31];u64 sp;u64 pc;u64 pstate;};u64 orig_x0;
#ifdef __AARCH64EB__u32 unused2;s32 syscallno;
#elses32 syscallno;u32 unused2;
#endifu64 orig_addr_limit;u64 unused; // maintain 16 byte alignmentu64 stackframe[2];
};

主要就是32个寄存器和SP、PC、PSTATE,所以只要按照这个顺序依次保存就可以

// 保护 pt_regs
kernel_entry://开辟空间sub sp, sp, #S_FRAME_SIZE// 保存普通寄存器stp	x0, x1, [sp, #16 * 0]stp	x2, x3, [sp, #16 * 1]stp	x4, x5, [sp, #16 * 2]stp	x6, x7, [sp, #16 * 3]stp	x8, x9, [sp, #16 * 4]stp	x10, x11, [sp, #16 * 5]stp	x12, x13, [sp, #16 * 6]stp	x14, x15, [sp, #16 * 7]stp	x16, x17, [sp, #16 * 8]stp	x18, x19, [sp, #16 * 9]stp	x20, x21, [sp, #16 * 10]stp	x22, x23, [sp, #16 * 11]stp	x24, x25, [sp, #16 * 12]stp	x26, x27, [sp, #16 * 13]stp	x28, x29, [sp, #16 * 14]//保存最开始sp的位置到x21 add x21, sp, #S_FRAME_SIZEmrs x22, elr_el1mrs x23, spsr_el1stp lr, x21, [sp, #S_LR]stp x22, x23, [sp, #S_PC]ret

stp 会依次保存两个寄存器到 sp+第二个数的位置

接下来就是恢复时候

// 恢复 pt_regs
kernel_exit:// 先把pc 和 pstate恢复ldp x22, x23, [sp, #S_PC]msr	elr_el1, x22			// set up the return datamsr	spsr_el1, x23ldp	x0, x1, [sp, #16 * 0]ldp	x2, x3, [sp, #16 * 1]ldp	x4, x5, [sp, #16 * 2]ldp	x6, x7, [sp, #16 * 3]ldp	x8, x9, [sp, #16 * 4]ldp	x10, x11, [sp, #16 * 5]ldp	x12, x13, [sp, #16 * 6]ldp	x14, x15, [sp, #16 * 7]ldp	x16, x17, [sp, #16 * 8]ldp	x18, x19, [sp, #16 * 9]ldp	x20, x21, [sp, #16 * 10]ldp	x22, x23, [sp, #16 * 11]ldp	x24, x25, [sp, #16 * 12]ldp	x26, x27, [sp, #16 * 13]ldp	x28, x29, [sp, #16 * 14]//最后再恢复lr 和 之前的spldr	lr, [sp, #S_LR]add	sp, sp, #S_FRAME_SIZE		// restore sp   eret

就是保存的逆操作,最后通过 eret 返回到elr_el1指向的位置

GIC-400

基本介绍

上面就已经把异常发生时候的软件部分部分说完了,接下来就是控制中断的GIC-400驱动的实现了。

GIC支持的中断有:

  • SGI 软件中断
  • PPI 私有外设中断
  • SPI 共享外设中断,只有SPI可以设置分发的CPU

GIC是中断控制器,分为分发器(dist) 和 CPU接口

从GIC角度来看,一个中断发生过程

  1. 当GIC 检测到一个中断发生时,会将该中断状态从inactive状态标记为pending状态。
  2. 对于处在penging状态的中断,分发器会确定目标 CPU, 将中断请求发给这个CPU。
  3. 对于每个 CPU, 分发器会从众多处于等待状态的中断中选择一个优先级最高的中断,发送到目标CPU 的 CPU 接口。
  4. CPU 接口会决定这个中断是否可以发送给CPU, 如果这个中断的优先级满足要求,GIC 会发送一个中断请求信号给 CPU.
  5. CPU 进入中断异常, 读取 GICC_IAR 来响应该中断(一般是由 Linux 内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID)。对于 SGI 来说,返回源CPU 的ID (source processor ID) 。当GIC感知到软件读取了该寄存器后,根据如下情况处理。
    1. 如果该中断源处于pending 状态,则将该中断状态切换到 active 状态
    2. 如果该中断又重新产生,那么该中断状态则变成 active and pending 状态
  6. 如果该中断正在忙,正在处理其他中断, 则该中断状态其切换为 active and pending 状态,等待CPU将当前当前的中断处理结束之后,再将该中断切换到 active 状态
  7. 处理器完成中断服务,发送一个完成信号结束中断(End of Interupt,EOI) 给 GIC。该中断状态再切换到 inactive 状态。

从CPU来看

  1. 接受到一个中断信号,进入中断向量表
  2. 执行gic driver中的中断handle函数
  3. 读取GICC_IAR得到原因,执行对应的处理函数

寄存器描述

GIC-400 分发器寄存器 (Distributor Registers)

偏移地址 (Hex) 寄存器名 中文名 类型 位域描述 (位宽: 32-bit)
0x000 GICD_CTLR 分发器控制寄存器 RW - [0]: 全局中断转发使能
- [1]: Group1 中断使能
- [2]: Group0 中断使能
0x004 GICD_TYPER 分发器类型寄存器 RO - [4:0]: 支持的中断数(ITLinesNumber = N/32 -1)
- [7:5]: CPU 数量 -1
- [10:8]: 共享中断数(LSPI)
0x008 GICD_IIDR 分发器标识寄存器 RO - [31:0]: 厂商和版本信息
0x080 GICD_IGROUPRn 中断组寄存器 RW 每 bit 对应一个中断:
- 0: Group0(安全)
- 1: Group1(非安全)
0x100 GICD_ISENABLERn 中断使能寄存器 RW 每 bit 对应一个中断:
- 1: 使能中断
0x180 GICD_ICENABLERn 中断禁用寄存器 RW 每 bit 对应一个中断:
- 1: 禁用中断
0x400 GICD_IPRIORITYRn 中断优先级寄存器 RW 每中断占 8 位:
- [7:0]: 优先级(值越低优先级越高)
0x800 GICD_ITARGETSRn 中断目标 CPU 寄存器 RW 每中断占 8 位:
- [7:0]: 目标 CPU 掩码(每 bit 对应一个 CPU)
0xC00 GICD_ICFGRn 中断配置寄存器 RW 每中断占 2 位:
- 00: 电平触发
- 01: 边沿触发

CPU 接口寄存器 (CPU Interface Registers)如下

偏移地址 (Hex) 寄存器名 中文名 类型 位域描述 (位宽: 32-bit)
0x0000 GICC_CTLR CPU 控制寄存器 RW - [0]: CPU 接口使能
- [1]: Group0 FIQ 旁路
- [2]: Group1 IRQ 旁路
0x0004 GICC_PMR 优先级屏蔽寄存器 RW - [7:0]: 优先级阈值(仅高 4 位有效)
0x0008 GICC_BPR 二进制点寄存器 RW - [2:0]: 优先级分组值
0x000C GICC_IAR 中断应答寄存器 RO - [9:0]: 中断 ID
- [12:10]: 源 CPU ID
0x0010 GICC_EOIR 中断结束寄存器 WO - [9:0]: 结束中断的 ID
0x0014 GICC_RPR 运行优先级寄存器 RO - [7:0]: 当前中断优先级
0x0018 GICC_HPPIR 最高挂起中断寄存器 RO - [9:0]: 最高优先级挂起中断 ID
0x001C GICC_ABPR 别名二进制点寄存器 RW - [2:0]: Group0 二进制点值
0x00D0 GICC_DIR 停用中断寄存器 WO - [9:0]: 停用中断 ID(虚拟化扩展)

说明

  1. 偏移地址:相对基地址(如 GICD_BASEGICC_BASE)。
  2. 寄存器数组(如 GICD_IGROUPRn):每个寄存器管理 32 个中断(例如 n=0 对应中断 0-31)。
  3. 优先级:实际有效位数由实现决定(例如 4 位或 8 位)。
  4. 目标 CPU 掩码:例如 0x01 表示 CPU0,0x03 表示 CPU0 和 CPU1。
  5. GICD_ITARGETSRn用来控制中断号的目标CPU,每八位描述一个中断号,前32个中断号(GICD_ITARGETSR0-7)是只读的,只有SPI可以配置到哪个CPU

GIC初始化

gic的结构体如下

struct gic_chip_data {u64 raw_dist_base;u64 raw_cpu_base;struct irq_domain *domain;struct irq_chip *chip;u32 gic_irqs;
};#define gic_dist_base(d) ((d)->raw_dist_base)
#define gic_cpu_base(d) ((d)->raw_cpu_base)

初始化函数,可以对照上面的寄存器看一下所需要的寄存器

int gic_init(int chip, u32 dist_base, u32 cpu_base)
{printk("gic init ...\n");struct gic_chip_data *gic;gic = &gic_data[chip];gic->raw_dist_base = dist_base;gic->raw_cpu_base = cpu_base;u32 irq_num = (readl(gic_dist_base(gic) + GIC_DIST_TYPER) & 0x1f);irq_num = (irq_num + 1) * 32;gic->gic_irqs = irq_num;printk("cpu_base:0x%x, dist_base:0x%x, gic_irqs:%d\n",gic_dist_base(gic), gic_cpu_base(gic), gic->gic_irqs);gic_dist_init(gic);gic_cpu_init(gic);return 0;
}
static void gic_dist_init(struct gic_chip_data *gic)
{u64 base = gic_dist_base(gic);writel(GICD_ENABLE, base + GIC_DIST_CTRL);u32 cpu_mask = gic_get_cpumask(gic);cpu_mask |= cpu_mask << 8;cpu_mask |= cpu_mask << 16;u32 gic_irqs = gic->gic_irqs;s32 i = 0;/* 将SPI都配置成路由到和前32个中断一样的CPU */for (i = 32; i < gic_irqs; i += 4)writel(cpu_mask, base + GIC_DIST_TARGET + i * 4 / 4);/* 将所以的SPI都设置成电平触发,低电平有效 */for (i = 32; i < gic_irqs; i += 16)writel(GICD_INT_ACTLOW_LVLTRIG, 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 interrupt forwarding.*/writel(GICD_ENABLE, base + GIC_DIST_CTRL);
}static 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(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);writel(GICC_ENABLE, base + GIC_CPU_CTRL);
}

注册一个中断

void timer_init(void)
{
// 初始化所需要的寄存器generic_timer_init();generic_timer_reset(val);// 打开GIC对应的irqgic_set_irq(GENERIC_TIMER_IRQ);// 使能enable_timer_interrupt();
}

只需要看如何打开就好,其他的是这个timer的寄存器,现在聚焦在gic的配置

void gic_set_irq(u32 irq)
{u32 n = irq / 32;u32 mask = 1 << (irq % 32);writel(mask, get_gic_dist_base() + GIC_DIST_ENABLE_SET + 4 * n);
}

GICD_ISENABLERn是一位对应一个中断号

响应中断

中断向量表:

// entry.S
el1_irq:bl kernel_entrybl irq_handlebl kernel_exit
ENDPROC(el1_irq)
// irq.c
void irq_handle(void)
{gic_handle_irq();
}
// gic_v2.c
void gic_handle_irq(void)
{u64 cpu_base = get_gic_cpu_base();u32 irqstat, irqnr;do {irqstat = readl(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (irqnr == GENERIC_TIMER_IRQ)handle_timer_irq();gicv2_eoi_irq(irqnr);} while (0);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/879332.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《gm/ID设计法基本介绍》翻译

最近流片很累很焦虑,放松心情找篇讲\(g_m/I_D\)设计法的文档翻译一下: 《A Basic Introduction to the gm/ID-Based Design Methodology》 1. 摘要 该文章向读者介绍了基于\(g_m/I_D\)的设计方法学,用于帮助CMOS模拟电路设计者将晶体管物理参数与小信号模型联系起来,文章的…

个人英语学习笔记基于B站英语的平行世界语法课程

导读 语言学习没有捷径,只要听说读写这四大行长期日复一日的练习就行了,兴趣是最重要的,兴趣就是高效学习的基础和长期坚持下去的动力。 0基础开始痛苦学习大半年英语,没兴趣的结果就是词汇量是上去了,但是英语的听说读写水平还不如学了一年的日语。😅 该笔记基于此课程…

PostgreSQL:数据库迁移与版本控制

title: PostgreSQL:数据库迁移与版本控制 date: 2025/2/6 updated: 2025/2/6 author: cmdragon excerpt: 在现代软件开发中,数据库作为应用程序的核心组件之一,数据的结构和内容必须能够随着业务需求的变化而调整。因此,数据库迁移和版本控制成为了确保数据一致性、完整性…

Servlet基础

什么是Servlet、Servlet的架构、Servlet任务、Servlet的基本使用、Servlet的生命周期、Servlet API中主要接口及实现类、Servlet的部署(注册与映射)、缺省Servlet与启动时加载配置、ServletConfig与ServletContext、request和response什么是Servlet基础 Java Servlet 是运行在…

GNURadio模块学习——Source and Sink类

介绍GNU Radio中常见的 Source 与 Sink 模块,包括流程图端口、音频输入输出、虚拟连接、文件读写、ZMQ跨流程图通信,以及随机信号源、固定信号源、噪声源等常见信号源和时域、频域、星座图等信号展示工具。Source and Sink Pad(流程图端口) 当该流程图是hierarchical block…

【C++】gflag使用指南

一、什么是gflags? gflags 是一个用于定义命令行参数的 C++ 库,它由 Google 开发并开源。通过 gflags,你可以轻松地在你的程序中添加各种类型的命令行选项,包括整数、布尔值、字符串等,并且可以为这些选项设置默认值。此外,gflags 还提供了强大的帮助信息生成功能,使得用…

【C++】Google benchmark理解与应用

一、介绍 Google Benchmark 是一个用于 C++ 的微基准测试库。它旨在帮助开发者编写出更高效、更具表现力的基准测试代码。通过使用 Google Benchmark,可以方便地测量函数或代码片段的性能,并且能够生成详细的报告。 二、安装与配置 2.1 安装 在Ubuntu环境中安装Google Benchm…

LRU浅析

LRU算法LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使…

20250205 省选模拟赛 T3

20250205 省选模拟赛 T3 Description 设计一个 \(n\times n\) 的 01 矩阵,使得从 \((1,1)\) 走到 \((n,n)\) 且只能向右或下走且只经过为 \(1\) 的格子的方案数为 \(X\)。 \(n \leq 24\) 时得满分。\(X \leq 10^9\)。 Solution 基于 \(2\) 进制的构造方法我们称从左上到右下的…

Automa:自动化浏览器工作流

🏷️仓库名称:AutomaApp/automa 🌟截止发稿星数: 14340 (今日新增:33) 🇨🇳仓库语言: Vue 🤝仓库开源协议:Other 🔗仓库地址:https://github.com/AutomaApp/automa引言 Automa是一个浏览器扩展,允许用户通过连接模块来自动化浏览器任务。它消除了重复性任务的需…

本地部署DeepSeek教程

本地部署DeepSeek教程 步骤 本地部署DeepSeek教程步骤 1 安装Ollama 2 下载DeepSeek模型 3 可视化图文交互界面Chatbox(可选)1 安装Ollama 访问Ollama官网下载Ollama,默认安装即可。安装完成后打开终端(我这里是windows系统),输入: ollama help即可查看ollama选项,且可…

OpenLDAP篇-安装OpenLDAP服务01

1、OpenLDAP统⼀⽤户认证系统 1.1 为什么需要OpenLDAP 在没有OpenLDAP统⼀⽤户认证系统的环境中,往往会⾯临如下问题:1、当⽤户需要访问多台服务器时,管理员需要在每台服务器上⼿动创建账户。如果员⼯离职,还需逐台删除账户,整体操作繁琐且容易出现遗漏的情况,因此存在较…