实验目的
深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机制和规范,实现时钟中断服务和部分异常处理等。
实验过程
前言
中断是一种硬件机制。借助于中断,CPU可以不必再采用轮询这种低效的方式访问外部设备。将所有的外部设备与CPU直接相连是不现实的,外部设备的中断请求一般经由中断控制器,由中断控制器仲裁后再转发给CPU。如下图所示Arm的中断系统。
其中nIRQ是普通中断,nFIQ是快速中断。 Arm采用的中断控制器叫做GIC,即general interrupt controller。gic包括多个版本,如GICv1(已弃用),GICv2,GICv3,GICv4。简单起见,我们实验将选用GICv2版本。
- GICv2 最多支持8个核的中断管理。
- GIC包括两大主要部分,分别是:
- Distributor,其通过GICD_开头的寄存器进行控制
- CPU Interface,其通过GICC_开头的寄存器进行控制
- 中断类型分为以下几类:
- SPI:(shared peripheral interrupt),共享外设中断。该中断来源于外设,通过Distributor分发给特定的core,其中断编号为32-1019。
- PPI:(private peripheral interrupt),私有外设中断。该中断来源于外设,但只对指定的core有效,中断信号只会发送给指定的core,其中断编号为16-31。
- SGI:(software-generated interrupt),软中断。软件产生的中断,用于给其他的core发送中断信号,其中断编号为0-15。
- virtual interrupt,虚拟中断,用于支持虚拟机
因为soc中,中断有很多,为了方便对中断的管理,对每个中断,附加了中断优先级。在中断仲裁时,高优先级的中断,会优于低优先级的中断,发送给cpu处理。当cpu在响应低优先级中断时,如果此时来了高优先级中断,那么高优先级中断会抢占低优先级中断,而被处理器响应。
GICv2初始化
virt.dts中intc和timer:
intc@8000000 {
phandle = <0x8001>;
reg = <0x00 0x8000000 0x00 0x10000 0x00 0x8010000 0x00 0x10000>;
compatible = "arm,cortex-a15-gic";
ranges;
#size-cells = <0x02>;
#address-cells = <0x02>;
interrupt-controller;
#interrupt-cells = <0x03>;
v2m@8020000 {
phandle = <0x8002>;
reg = <0x00 0x8020000 0x00 0x1000>;
msi-controller;
compatible = "arm,gic-v2m-frame";
};
};
timer {
interrupts = <0x01 0x0d 0x104 0x01 0x0e 0x104 0x01 0x0b 0x104 0x01 0x0a 0x104>;
always-on;
compatible = "arm,armv8-timer\0arm,armv7-timer";
};
- intc中的
reg
指明GICD寄存器映射到内存的位置为0x8000000,长度为0x10000, GICC寄存器映射到内存的位置为0x8010000,长度为0x10000 - intc中的
#interrupt-cells
指明 interrupts 包括3个cells。第一个cell为中断类型,0表示SPI,1表示PPI;第二个cell为中断号,SPI范围为[0-987],PPI为[0-15];第三个cell为flags,其中[3:0]位表示触发类型,4表示高电平触发,[15:8]为PPI的cpu中断掩码,每1位对应一个cpu,为1表示该中断会连接到对应的cpu。 - 以timer设备为例,其中包括4个中断。以第二个中断的参数
0x01 0x0e 0x104
为例,其指明该中断为PPI类型的中断,中断号14, 路由到第一个cpu,且高电平触发。但注意到PPI的起始中断号为16,所以实际上该中断在GICv2中的中断号应为16 + 14 = 30。
hwi_init.c 文件,初始化 GIC:
#include "prt_typedef.h"
#include "os_attr_armv8_external.h"
#define OS_GIC_VER 2
#define GIC_DIST_BASE 0x08000000
#define GIC_CPU_BASE 0x08010000
#define GICD_CTLR (GIC_DIST_BASE + 0x0000U)
#define GICD_TYPER (GIC_DIST_BASE + 0x0004U)
#define GICD_IIDR (GIC_DIST_BASE + 0x0008U)
#define GICD_IGROUPRn (GIC_DIST_BASE + 0x0080U)
#define GICD_ISENABLERn (GIC_DIST_BASE + 0x0100U)
#define GICD_ICENABLERn (GIC_DIST_BASE + 0x0180U)
#define GICD_ISPENDRn (GIC_DIST_BASE + 0x0200U)
#define GICD_ICPENDRn (GIC_DIST_BASE + 0x0280U)
#define GICD_ISACTIVERn (GIC_DIST_BASE + 0x0300U)
#define GICD_ICACTIVERn (GIC_DIST_BASE + 0x0380U)
#define GICD_IPRIORITYn (GIC_DIST_BASE + 0x0400U)
#define GICD_ICFGR (GIC_DIST_BASE + 0x0C00U)
#define GICD_CTLR_ENABLE 1 /* Enable GICD */
#define GICD_CTLR_DISABLE 0 /* Disable GICD */
#define GICD_ISENABLER_SIZE 32
#define GICD_ICENABLER_SIZE 32
#define GICD_ICPENDR_SIZE 32
#define GICD_IPRIORITY_SIZE 4
#define GICD_IPRIORITY_BITS 8
#define GICD_ICFGR_SIZE 16
#define GICD_ICFGR_BITS 2
#define GICC_CTLR (GIC_CPU_BASE + 0x0000U)
#define GICC_PMR (GIC_CPU_BASE + 0x0004U)
#define GICC_BPR (GIC_CPU_BASE + 0x0008U)
#define IAR_MASK 0x3FFU
#define GICC_IAR (GIC_CPU_BASE + 0xc)
#define GICC_EOIR (GIC_CPU_BASE + 0x10)
#define GICD_SGIR (GIC_DIST_BASE + 0xf00)
#define BIT(n) (1 << (n))
#define GICC_CTLR_ENABLEGRP0 BIT(0)
#define GICC_CTLR_ENABLEGRP1 BIT(1)
#define GICC_CTLR_FIQBYPDISGRP0 BIT(5)
#define GICC_CTLR_IRQBYPDISGRP0 BIT(6)
#define GICC_CTLR_FIQBYPDISGRP1 BIT(7)
#define GICC_CTLR_IRQBYPDISGRP1 BIT(8)
#define GICC_CTLR_ENABLE_MASK (GICC_CTLR_ENABLEGRP0 | \
GICC_CTLR_ENABLEGRP1)
#define GICC_CTLR_BYPASS_MASK (GICC_CTLR_FIQBYPDISGRP0 | \
GICC_CTLR_IRQBYPDISGRP0 | \
GICC_CTLR_FIQBYPDISGRP1 | \
GICC_CTLR_IRQBYPDISGRP1)
#define GICC_CTLR_ENABLE 1
#define GICC_CTLR_DISABLE 0
// Priority Mask Register. interrupt priority filter, Higher priority corresponds to a lower Priority field value.
#define GICC_PMR_PRIO_LOW 0xff
// The register defines the point at which the priority value fields split into two parts,
// the group priority field and the subpriority field. The group priority field is used to
// determine interrupt preemption. NO GROUP.
#define GICC_BPR_NO_GROUP 0x00
#define GIC_REG_READ(addr) (*(volatile U32 *)((uintptr_t)(addr)))
#define GIC_REG_WRITE(addr, data) (*(volatile U32 *)((uintptr_t)(addr)) = (U32)(data))
void OsGicInitCpuInterface(void)
{
// 初始化Gicv2的distributor和cpu interface
// 禁用distributor和cpu interface后进行相应配置
GIC_REG_WRITE(GICD_CTLR, GICD_CTLR_DISABLE);
GIC_REG_WRITE(GICC_CTLR, GICC_CTLR_DISABLE);
GIC_REG_WRITE(GICC_PMR, GICC_PMR_PRIO_LOW);
GIC_REG_WRITE(GICC_BPR, GICC_BPR_NO_GROUP);
// 启用distributor和cpu interface
GIC_REG_WRITE(GICD_CTLR, GICD_CTLR_ENABLE);
GIC_REG_WRITE(GICC_CTLR, GICC_CTLR_ENABLE);
}
// src/arch/drv/gic/prt_gic_init.c
/*
* 描述: 去使能(禁用)指定中断
*/
OS_SEC_L4_TEXT void OsGicDisableInt(U32 intId)
{
// Interrupt Clear-Enable Registers
}
/*
* 描述: 使能指定中断
*/
OS_SEC_L4_TEXT void OsGicEnableInt(U32 intId)
{
// Interrupt Set-Enable Registers
}
OS_SEC_L4_TEXT void OsGicClearInt(uint32_t interrupt)
{
GIC_REG_WRITE(GICD_ICPENDRn + (interrupt / GICD_ICPENDR_SIZE)*sizeof(U32), 1 << (interrupt % GICD_ICPENDR_SIZE));
}
// 设置中断号为interrupt的中断的优先级为priority
OS_SEC_L4_TEXT void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority) {
uint32_t shift = (interrupt % GICD_IPRIORITY_SIZE) * GICD_IPRIORITY_BITS;
volatile uint32_t* addr = ((volatile U32 *)(uintptr_t)(GICD_IPRIORITYn + (interrupt / GICD_IPRIORITY_SIZE) * sizeof(U32))) ;
uint32_t value = GIC_REG_READ(addr);
value &= ~(0xff << shift); // 每个中断占8位,所以掩码为 0xFF
value |= priority << shift;
GIC_REG_WRITE(addr, value);
}
// 设置中断号为interrupt的中断的属性为config
OS_SEC_L4_TEXT void OsGicIntSetConfig(uint32_t interrupt, uint32_t config) {
uint32_t shift = (interrupt % GICD_ICFGR_SIZE) * GICD_ICFGR_BITS;
volatile uint32_t* addr = ((volatile U32 *)(uintptr_t)(GICD_ICFGR + (interrupt / GICD_ICFGR_SIZE)*sizeof(U32)));
uint32_t value = GIC_REG_READ(addr);
value &= ~(0x03 << shift);
value |= config << shift;
GIC_REG_WRITE(addr, value);
}
/*
* 描述: 中断确认
*/
OS_SEC_L4_TEXT U32 OsGicIntAcknowledge(void)
{
// reads this register to obtain the interrupt ID of the signaled interrupt.
// This read acts as an acknowledge for the interrupt.
U32 value = GIC_REG_READ(GICC_IAR);
return value;
}
/*
* 描述: 标记中断完成,清除相应中断位
*/
OS_SEC_L4_TEXT void OsGicIntClear(U32 value)
{
// A processor writes to this register to inform the CPU interface either:
// • that it has completed the processing of the specified interrupt
// • in a GICv2 implementation, when the appropriate GICC_CTLR.EOImode bit is set to 1, to indicate that the interface should perform priority drop for the specified interrupt.
GIC_REG_WRITE(GICC_EOIR, value);
}
U32 OsHwiInit(void)
{
OsGicInitCpuInterface();
return OS_OK;
}
① OsGicInitCpuInterface()
函数负责初始化 GIC 分发器和 CPU 接口。
- 首先,禁用了 GIC 分发器和 CPU 接口,以确保在配置完成之前不会触发中断。
- 然后,将 CPU 接口的优先级屏蔽寄存器(
GICC_PMR
)和中断优先级控制器(GICC_BPR
)分别设置为较低优先级和无分组,这样所有中断都能触发。 - 最后,启用了 GIC 分发器和 CPU 接口,使得中断能够被触发和处理。
② OsGicClearInt()
函数用于清除指定中断。
- 它通过计算出中断挂起寄存器(
GICD_ICPENDRn
)中相应的偏移量,然后写入一个特定的位来清除中断。
③ OsGicIntSetPriority()
函数用于设置特定中断的优先级。
- 首先,根据中断号计算出在优先级寄存器中的偏移量,并计算出对应的地址。
- 然后,读取当前寄存器的值,使用掩码操作清除原始优先级值的相应部分,并设置新的优先级值。
- 最后,将修改后的值写回寄存器中。
④ OsGicIntSetConfig()
函数用于设置特定中断的属性。
- 首先,根据中断号计算出在配置寄存器中的偏移量,并计算出对应的地址。
- 然后,读取当前寄存器的值,使用掩码操作清除原始配置值的相应部分,并设置新的配置值。
- 最后,将修改后的值写回寄存器中。
⑤ OsGicIntAcknowledge()
函数用于确认中断并获取其 ID。
- 它从 GIC CPU 接口的中断响应寄存器(
GICC_IAR
)中读取中断 ID,并返回该值。
⑥ OsGicIntClear()
函数用于标记中断为完成状态并清除相应的中断位。
- 它将中断 ID 写入 GIC CPU 接口的中断结束寄存器(
GICC_EOIR
),通知 CPU 接口中断已处理完毕,并清除相应的中断位。
⑦ OsHwiInit()
函数是硬件中断初始化函数。
- 它调用了
OsGicInitCpuInterface()
函数来初始化 GIC 分发器和 CPU 接口。 - 最后,返回一个成功的状态码。
关于以上定义的寄存器,可查看用户手册:
使能时钟中断
prt_config.h:
#define OS_TICK_PER_SECOND 1000
Tick中断时间间隔,tick处理时间不能超过1/OS_TICK_PER_SECOND(s)。
os_cpu_armv8.h:
#ifndef OS_CPU_ARMV8_H
#define OS_CPU_ARMV8_H
#include "prt_typedef.h"
// CurrentEl等级
#define CURRENT_EL_2 0x8
#define CURRENT_EL_1 0x4
#define CURRENT_EL_0 0x0
#define DAIF_DBG_BIT (1U << 3)
#define DAIF_ABT_BIT (1U << 2)
#define DAIF_IRQ_BIT (1U << 1)
#define DAIF_FIQ_BIT (1U << 0)
#define INT_MASK (1U << 7)
#define PRT_DSB() OS_EMBED_ASM("DSB sy" : : : "memory")
#define PRT_DMB() OS_EMBED_ASM("DMB sy" : : : "memory")
#define PRT_ISB() OS_EMBED_ASM("ISB" : : : "memory")
#endif /* OS_CPU_ARMV8_H */
宏定义了 DAIF 寄存器(异常屏蔽寄存器)中各个位的位置:
- D(Debug)位(第 3 位):当该位被设置为 1 时,表示调试异常(Debug Exception)被屏蔽。调试异常通常由调试器使用,以实现对程序的调试和跟踪。当该位被屏蔽时,处理器将不会响应调试异常。
- A(Asynchronous Abort)位(第 2 位):当该位被设置为 1 时,表示异步异常(Asynchronous Abort Exception)被屏蔽。异步异常通常由硬件错误或外部事件引起,例如内存错误、总线错误等。当该位被屏蔽时,处理器将不会响应异步异常。
- I(IRQ)位(第 1 位):当该位被设置为 1 时,表示 IRQ(普通中断)被屏蔽。IRQ 是由外部设备产生的可屏蔽中断。当该位被屏蔽时,处理器将不会响应 IRQ 中断。
- F(FIQ)位(第 0 位):当该位被设置为 1 时,表示 FIQ(快速中断)被屏蔽。FIQ 是由外部设备产生的紧急且优先级较高的中断。当该位被屏蔽时,处理器将不会响应 FIQ 中断。
timer.c:
#include "prt_typedef.h"
#include "prt_config.h"
#include "os_cpu_armv8.h"
U64 g_timerFrequency;
extern void OsGicIntSetConfig(uint32_t interrupt, uint32_t config);
extern void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority);
extern void OsGicEnableInt(U32 intId);
extern void OsGicClearInt(uint32_t interrupt);
void CoreTimerInit(void)
{
// 配置中断控制器
OsGicIntSetConfig(30, 0); // 配置为电平触发
OsGicIntSetPriority(30, 0); // 优先级为0
OsGicClearInt(30); // 清除中断
OsGicEnableInt(30); // 启用中断
// 配置定时器
OS_EMBED_ASM("MRS %0, CNTFRQ_EL0" : "=r"(g_timerFrequency) : : "memory", "cc"); //读取时钟频率
U32 cfgMask = 0x0;
U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND;
OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory");
PRT_ISB();
OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期
cfgMask = 0x1;
OS_EMBED_ASM("MSR CNTP_CTL_EL0, %0" : : "r"(cfgMask) : "memory"); //启用定时器 enable=1, imask=0, istatus= 0,
OS_EMBED_ASM("MSR DAIFCLR, #2");
}
时钟中断处理
将 prt_vector.S 中的 EXC_HANDLE 5 OsExcDispatch
改为 EXC_HANDLE 5 OsHwiDispatcher
,表明我们将对 IRQ 类型的异常(即中断)使用 OsHwiDispatcher
处理。
OsExcDispatch
和 OsHwiDispatcher
是操作系统中用于处理不同类型异常的函数,它们的主要区别在于处理的异常类型不同。
- OsExcDispatch:
OsExcDispatch
是用于处理软件异常(Exception)的函数。软件异常通常是由程序执行期间的错误或非法操作引起的,例如空指针访问、除零错误等。- 在操作系统中,软件异常可能会触发任务切换、异常处理程序的执行或系统的崩溃等操作,因此
OsExcDispatch
函数可能会涉及到任务管理、异常处理以及系统状态的恢复等操作。
- OsHwiDispatcher:
OsHwiDispatcher
是用于处理硬件中断(Hardware Interrupt)的函数。硬件中断是由外部设备发出的信号,用于通知处理器需要处理某种事件,例如定时器到期、外部设备的输入等。- 在操作系统中,硬件中断通常会触发相应的中断服务程序(Interrupt Service Routine,ISR)来处理,而
OsHwiDispatcher
函数则负责将中断事件分派给相应的中断服务程序。
prt_vector.S 中加入 OsHwiDispatcher 处理代码:
.globl OsHwiDispatcher
.type OsHwiDispatcher, @function
.align 4
OsHwiDispatcher:
mrs x5, esr_el1
mrs x4, far_el1
mrs x3, spsr_el1
mrs x2, elr_el1
stp x4, x5, [sp,#-16]!
stp x2, x3, [sp,#-16]!
- 这部分代码用于保存当前异常处理器的上下文信息。
mrs
指令用于将系统寄存器的值读取到通用寄存器中,分别读取了esr_el1
、far_el1
、spsr_el1
和elr_el1
的值。esr_el1
寄存器用于存储最近发生异常时的异常信息。它包含了异常的原因和其他与异常相关的信息。far_el1
寄存器用于存储最近发生数据或指令访问异常时导致异常的虚拟地址。spsr_el1
寄存器用于保存 EL1(Exception Level 1)的程序状态。它存储了 EL1 状态下的程序执行信息,例如当前执行的模式、中断屏蔽状态、处理器状态标志等。elr_el1
寄存器用于保存异常返回地址。在发生异常时,处理器会将异常发生时的下一条指令的地址保存在elr_el1
寄存器中。
stp
指令将这些值存储到栈上,为了避免覆盖其他数据,栈指针sp
向下移动了 16 字节。
mov x0, x1 // 异常类型0~15,参见异常向量表
mov x1, sp // 异常时寄存器信息,通过栈及其sp指针提供
bl OsHwiDispatch
- 这部分代码设置参数并调用
OsHwiDispatch
函数来处理硬件中断。 x0
寄存器中存放了异常类型,通常用于表示中断号。x1
寄存器中存放了栈指针sp
,即异常发生时寄存器的信息。bl
指令用于调用OsHwiDispatch
函数,执行中断的处理。
ldp x2, x3, [sp],#16
add sp, sp, #16 // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr
msr spsr_el1, x3
msr elr_el1, x2
dsb sy
isb
RESTORE_EXC_REGS // 恢复上下文
eret //从异常返回
- 这部分代码用于恢复异常处理器的上下文信息。
ldp
指令从栈中加载之前保存的elr_el1
和spsr_el1
寄存器的值到x2
和x3
寄存器中。add
指令将栈指针sp
恢复到之前的位置。msr
指令将恢复的spsr_el1
寄存器的值写回spsr_el1
寄存器中,将恢复的elr_el1
寄存器的值写回elr_el1
寄存器中。dsb
指令和isb
指令是数据同步屏障和指令同步屏障,用于确保数据和指令的同步性。
在 prt_exc.c 中添加:
extern void OsTickDispatcher(void);
OS_SEC_ALW_INLINE INLINE void OsHwiHandleActive(U32 irqNum)
{
switch(irqNum){
case 30:
OsTickDispatcher();
// PRT_Printf(".");
break;
default:
break;
}
}
extern U32 OsGicIntAcknowledge(void);
extern void OsGicIntClear(U32 value);
// src/arch/cpu/armv8/common/hwi/prt_hwi.c OsHwiDispatch(),OsHwiDispatchHandle()
/*
* 描述: 中断处理入口, 调用处外部已关中断
*/
OS_SEC_L0_TEXT void OsHwiDispatch( U32 excType, struct ExcRegInfo *excRegs) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
// 中断确认,相当于OsHwiNumGet()
U32 value = OsGicIntAcknowledge();
U32 irq_num = value & 0x1ff;
U32 core_num = value & 0xe00;
OsHwiHandleActive(irq_num);
// 清除中断,相当于 OsHwiClear(hwiNum);
OsGicIntClear(irq_num|core_num);
}
OsHwiHandleActive
用于处理激活状态的中断:
- 如果中断号为 30,则调用
OsTickDispatcher
函数,即处理定时器中断。 - 如果中断号不是 30,则不执行任何操作。
OsGicIntAcknowledge
和 OsGicIntClear
,用于中断处理的相关操作,分别是中断确认和中断清除。
OsHwiDispatch
是中断处理的入口函数:
- 首先调用
OsGicIntAcknowledge
函数确认中断,并从返回值中提取中断号和核心号。 - 然后调用
OsHwiHandleActive
函数处理中断号。 - 最后,调用
OsGicIntClear
函数清除中断。
prt_tick.c 文件,提供 OsTickDispatcher 时钟中断处理函数:
#include "os_attr_armv8_external.h"
#include "prt_typedef.h"
#include "prt_config.h"
#include "os_cpu_armv8_external.h"
extern U64 g_timerFrequency;
/* Tick计数 */
OS_SEC_BSS U64 g_uniTicks; //src/core/kernel/sys/prt_sys.c
/*
* 描述:Tick中断的处理函数。扫描任务超时链表、扫描超时软件定时器、扫描TSKMON等。
*/
OS_SEC_TEXT void OsTickDispatcher(void)
{
uintptr_t intSave;
intSave = OsIntLock();
g_uniTicks++;
OsIntRestore(intSave);
U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND;
OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); //设置中断周期
}
/*
* 描述:获取当前的tick计数
*/
OS_SEC_L2_TEXT U64 PRT_TickGetCount(void) //src/core/kernel/sys/prt_sys_time.c
{
return g_uniTicks;
}
OsTickDispatcher
函数:OsTickDispatcher
函数是 Tick 中断的处理函数。- 首先,使用
OsIntLock
函数锁定中断,确保对全局变量g_uniTicks
的操作是原子的。 - 然后,递增
g_uniTicks
变量,表示 Tick 计数增加了一个 Tick。 - 最后,计算新的中断周期,并通过内联汇编指令
MSR CNTP_TVAL_EL0
设置下一次中断的时间。
PRT_TickGetCount
函数:PRT_TickGetCount
函数用于获取当前的 Tick 计数值。- 在这段代码中,它简单地返回全局变量
g_uniTicks
的值,即当前的 Tick 计数。
这段代码实现了 Tick 计数的功能,并提供了 Tick 中断的处理函数,用于更新 Tick 计数并设置下一次中断的时间。PRT_TickGetCount
函数用于获取当前的 Tick 计数值。
将新增文件加入构造系统:
OsIntLock 和 OsIntRestore 函数用于关中断和开中断。将其放入 prt_exc.c 中:
/*
* 描述: 开启全局可屏蔽中断。
*/
OS_SEC_L0_TEXT uintptr_t PRT_HwiUnLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
uintptr_t state = 0;
OS_EMBED_ASM(
"mrs %0, DAIF \n"
"msr DAIFClr, %1 \n"
: "=r"(state)
: "i"(DAIF_IRQ_BIT)
: "memory", "cc");
return state & INT_MASK;
}
/*
* 描述: 关闭全局可屏蔽中断。
*/
OS_SEC_L0_TEXT uintptr_t PRT_HwiLock(void) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
uintptr_t state = 0;
OS_EMBED_ASM(
"mrs %0, DAIF \n"
"msr DAIFSet, %1 \n"
: "=r"(state)
: "i"(DAIF_IRQ_BIT)
: "memory", "cc");
return state & INT_MASK;
}
/*
* 描述: 恢复原中断状态寄存器。
*/
OS_SEC_L0_TEXT void PRT_HwiRestore(uintptr_t intSave) //src/arch/cpu/armv8/common/hwi/prt_hwi.c
{
if ((intSave & INT_MASK) == 0) {
OS_EMBED_ASM(
"msr DAIFClr, %0\n"
:
: "i"(DAIF_IRQ_BIT)
: "memory", "cc");
} else {
OS_EMBED_ASM(
"msr DAIFSet, %0\n"
:
: "i"(DAIF_IRQ_BIT)
: "memory", "cc");
}
return;
}
PRT_HwiUnLock
函数:- 此函数用于开启全局可屏蔽中断。
- 首先,它声明了一个名为
state
的本地变量用于保存当前中断状态。 - 然后,通过内联汇编嵌入式指令,使用
mrs
指令将当前的 DAIF 寄存器(包含了中断屏蔽位)的值加载到state
变量中。 - 接着,使用
msr
指令清除 DAIF 寄存器的中断屏蔽位(IRQ bit),以开启全局可屏蔽中断。 - 最后,返回之前保存的中断状态。
PRT_HwiLock
函数:- 此函数用于关闭全局可屏蔽中断。
- 与
PRT_HwiUnLock
函数类似,它也声明了一个名为state
的本地变量用于保存当前中断状态。 - 通过内联汇编嵌入式指令,使用
mrs
指令将当前的 DAIF 寄存器的值加载到state
变量中。 - 然后,使用
msr
指令设置 DAIF 寄存器的中断屏蔽位(IRQ bit),以关闭全局可屏蔽中断。 - 最后,返回之前保存的中断状态。
PRT_HwiRestore
函数:- 此函数用于恢复原中断状态寄存器。
- 它接受一个参数
intSave
,表示先前保存的中断状态。 - 如果先前的中断状态为非屏蔽状态,则使用
msr
指令清除 DAIF 寄存器的中断屏蔽位(IRQ bit)。 - 如果先前的中断状态为屏蔽状态,则使用
msr
指令设置 DAIF 寄存器的中断屏蔽位(IRQ bit)。 - 这样可以将中断状态恢复到调用
PRT_HwiUnLock
或PRT_HwiLock
函数前的状态。
读取系统Tick值
main函数:
#include "prt_typedef.h"
#include "prt_tick.h"
extern U32 PRT_Printf(const char *format, ...);
extern void PRT_UartInit(void);
extern void CoreTimerInit(void);
extern U32 OsHwiInit(void);
U64 delay_time = 10000;
S32 main(void)
{
// 初始化GIC
OsHwiInit();
// 启用Timer
CoreTimerInit();
PRT_UartInit();
PRT_Printf(" _ _ _____ _ _ _ _ _ _ _ _ \n");
PRT_Printf(" _ __ ___ (_)_ __ (_) ____| _| | ___ _ __ | |__ _ _ | | | | \\ | | | | | ___ _ __ \n");
PRT_Printf(" | '_ ` _ \\| | '_ \\| | _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| | \\| | | | |/ _ \\ '__|\n");
PRT_Printf(" | | | | | | | | | | | |__| |_| | | __/ | | |_) | |_| | | _ | |\\ | |_| | __/ | \n");
PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_| |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_| \n");
PRT_Printf(" |___/ \n");
PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n");
for(int i = 0; i < 10; i++)
{
U32 tick = PRT_TickGetCount();
PRT_Printf("[%d] current tick: %u\n", i, tick);
//delay
int delay_time = 10000000; // 根据自己机器计算能力不同调整该值
while(delay_time>0){
PRT_TickGetCount(); //消耗时间,防止延时代码被编译器优化
delay_time--;
}
}
while(1);
return 0;
}
作业
实现 hwi_init.c 中缺失的 OsGicEnableInt
和 OsGicDisableInt
函数。
OsGicDisableInt
:这个函数是用来禁用特定的中断。它通过写入 GICD_ICENABLERn
寄存器来实现这个功能。GICD_ICENABLERn
是 Interrupt Clear-Enable Registers
,每一位代表一个中断,当位被设置为1时,对应的中断被禁用。阅读手册可知,GICD_ICENABLERs为GIC支持的每个中断提供了一个清除使能位。向清除使能位写入1将禁止从分发器向CPU接口转发相应的中断。读取某一位可以确定该中断是否已启用。
/*
* 描述: 去使能(禁用)指定中断
*/
OS_SEC_L4_TEXT void OsGicDisableInt(U32 intId)
{
// Interrupt Clear-Enable Registers
// 计算寄存器偏移
uintptr_t reg_offset = GICD_ICENABLERn + (intId / GICD_ICENABLER_SIZE) * sizeof(U32);
// 计算对应中断位
uint32_t bit_position = intId % GICD_ICENABLER_SIZE;
// 写入对应的寄存器,将对应中断位置 1,即禁用该中断
GIC_REG_WRITE(reg_offset, 1 << bit_position);
}
首先计算偏移量和中断位,计算方法可由手册给出:
OsGicEnableInt
:这个函数是用来启用特定的中断。它通过写入 GICD_ISENABLERn
寄存器来实现这个功能。GICD_ISENABLERn
是 Interrupt Set-Enable Registers
,每一位代表一个中断,当位被设置为1时,对应的中断被启用。阅读手册可知,GICD_ISENABLERs
为 GIC 支持的每个中断提供了一个 Set-enable
位。向 Set-enable
位写入 1 可以启用将相应的中断从 Distributor
转发到 CPU 接口。读取一个位可以确定该中断是否已启用。
/*
* 描述: 使能指定中断
*/
OS_SEC_L4_TEXT void OsGicEnableInt(U32 intId)
{
// Interrupt Set-Enable Registers
// 计算寄存器偏移
uintptr_t reg_offset = GICD_ISENABLERn + (intId / GICD_ISENABLER_SIZE) * sizeof(U32);
// 计算对应中断位
uint32_t bit_position = intId % GICD_ISENABLER_SIZE;
// 写入对应的寄存器,将对应中断位置 1,即使能该中断
GIC_REG_WRITE(reg_offset, 1 << bit_position);
}
首先计算偏移量和中断位,计算方法可由手册给出:
intId / GICD_ICENABLER_SIZE
:这部分代码是计算出中断IDintId
对应的中断在哪一个中断清除使能寄存器中。每个中断清除使能寄存器可以控制GICD_ICENABLER_SIZE
个中断,所以我们用中断ID除以这个值可以得到对应的寄存器的编号。* sizeof(U32)
:这部分代码是将寄存器的编号转换为实际的内存地址。因为每个寄存器的大小是sizeof(U32)
字节,所以我们用编号乘以这个值就得到了相对于基地址的偏移。