HNU-2024操作系统实验-Lab5-时钟中断Tick

一、 实验目的

  1. 理解中断的原理和机制,掌握CPU访问设备控制器的方法

  2. 掌握Arm体系结构的中断机制和规范,实现时钟中断服务

二、 实验过程

1、理解Arm架构的中断系统

在这里插入图片描述

arm的中断分为两类IRQ(普通中断),与FIQ(快速中断)。

Arm采用的中断控制器叫做GIC,即general interrupt controller。gic包括多个版本,如GICv1(已弃用),GICv2,GICv3,GICv4。简单起见,我们实验将选用GICv2版本。

在这里插入图片描述

  1. GICv2 最多支持8个核的中断管理。

  2. GIC包括两大主要部分(由图中蓝色虚竖线分隔,Distributor和CPU Interface由蓝色虚矩形框标示),分别是:

  3. Distributor,其通过GICD_开头的寄存器进行控制(蓝色实矩形框标示)

  4. CPU Interface,其通过GICC_开头的寄存器进行控制(蓝色实矩形框标示)

  5. 中断类型分为以下几类(由图中红色虚线椭圆标示):

  6. SPI:(shared peripheral interrupt),共享外设中断。该中断来源于外设,通过Distributor分发给特定的core,其中断编号为32-1019。从图中可以看到所有核共享SPI。

  7. PPI:(private peripheral interrupt),私有外设中断。该中断来源于外设,但只对指定的core有效,中断信号只会发送给指定的core,其中断编号为16-31。从图中可以看到每个core都有自己的PPI。

  8. SGI:(software-generated interrupt),软中断。软件产生的中断,用于给其他的core发送中断信号,其中断编号为0-15。

  9. virtual interrupt,虚拟中断,用于支持虚拟机。图中也可以看到,因为我们暂时不关心,所以没有标注。

2.分析virt.dts中intc和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。

3.在bsp目录下新建文件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
    GIC_REG_WRITE(GICD_ICENABLERn + (intId / GICD_ICENABLER_SIZE)*sizeof(U32), 1<<(intId % GICD_ICENABLER_SIZE));
}

/*
* 描述: 使能指定中断
*/
OS_SEC_L4_TEXT void OsGicEnableInt(U32 intId)
{
    // Interrupt Set-Enable Registers
    GIC_REG_WRITE(GICD_ISENABLERn + (intId / GICD_ISENABLER_SIZE)*sizeof(U32), 1<<(intId % GICD_ISENABLER_SIZE));
}

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;
}

这段代码定义了一系列寄存器的地址、各种使能信号以及一系列相关函数;

①查阅Arm用户手册中的寄存器表,分析各寄存器的功能:

GICD:

在这里插入图片描述

GICC:

在这里插入图片描述

GICD_*寄存器:

  • GICD_CTLR:控制Distributor是否向CPU Interface转发中断。

  • GICD_ISENABLER:对于SPI和PPI类型的中断,每一位控制对应中断的转发行为。中断的使能可以通过GICD_ISENABLER寄存器设置。

  • GICD_ICENABLER:对于SPI和PPI类型的中断,每一位控制对应中断的转发行为。中断的失能可以通过GICD_ICENABLER寄存器设置。

  • GICD_ISPENDR:设置一个或一组中断为pending状态,若该中断当前为inactive状态,则转换为pending状态。若当前为pending状态,则转换为pending and active状态。该寄存器的每一个bit代表一个中断号。

  • GICD_ICPENDR:清除一个或一组中断为pending状态,该寄存器的每一个bit代表一个中断号。

  • GICD_IPRIORITYR:用于设置中断的优先级。这个寄存器是字节有效的,也就是一个字节,对应一个中断的优先级。优先级数值越小,那么这个中断的优先级越高。

  • GICD_ICFGR:中断的触发方式可分为边沿触发(上升沿、下降沿)和电平触发(高电平、低电平),两种触发方式的行为有所不同。用于设置SPI的中断触发方式。

GICC_*寄存器:

  • GICC_CTLR:此寄存器用来控制CPU interface传给CPU的中断信号。

  • GICC_PMR:提供优先级过滤功能,优先级高于某值的中断,才会发送给CPU。

  • GICC_BPR:此寄存器用来把8位的优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。

  • GICC_IAR:CPU读此寄存器,获得当前中断的interrtup ID。

  • GICC_EOIR:写此寄存器,表示某中断已经处理完毕。GICC_IAR的值表示当前在处理的中断,把GICC_IAR的值写入GICC_EOIR就表示中断处理完了。

②初始化GIC中定义的函数的功能

OsGicInitCpuInterface:首先通过向GICD_CTLR(分发器控制寄存器)和GICC_CTLR(CPU接口控制寄存器)写入禁用值(GICD_CTLR_DISABLE和GICC_CTLR_DISABLE),然后设置CPU接口的优先级掩码寄存器(GICC_PMR)和二进制点寄存器(GICC_BPR)。GICC_PMR用于设置接收中断的优先级阈值,只有高于这个阈值的中断才会被处理。GICC_BPR用于分割优先级字段,确定哪些中断有资格打断当前正在处理的中断。最后,启用GIC的分发器和CPU接口,这样GIC正常工作;

OsGicDisableInt:通过向GICD的ICENABLERn寄存器写入数据以禁用特定的中断(对应intID),由于每一个ICENABLERn寄存器可以处理GICD_ICENABLER_SIZE(32)个终端信号,所以用intID除以GICD_ICENABLER_SIZE得到寄存器号,intID模GICD_ICENABLER_SIZE得到寄存器内偏移,所以GIC_REG_WRITE(GICD_ICENABLERn + (intId / GICD_ICENABLER_SIZE)*sizeof(U32), 1<<(intId % GICD_ICENABLER_SIZE))将相应的禁用位设置为1;

OsGicEnsableInt:通过向GICD的ISENABLERn寄存器写入数据以启用特定的中断(对应intID),具体实现方式与禁用函数完全一致;

OsGicClearInt:通过向GICD的ICPENDRn寄存器写入数据以清除特定的中断(对应interrupt),具体实现方式与禁用函数完全一致;

OsGicIntSetPriority:与上文相似的思路,每一个ICPENDRn寄存器可以处理GICD_IPRIORITY_SIZE(4)个中断优先级信号,所以用interrupt除以GICD_ICPENDRn_SIZE得到寄存器号,interrupt模GICD_IPRIORITY_SIZE再乘上每个信号的大小得到寄存器内偏移,准备工作完成之后,根据寄存器号读出寄存器的值至value,根据偏移将寄存器相应位置设置为优先级priority,最后再写回寄存器;

OsGicIntSetConfig:与设置中断优先级的函数准备工作完全一致,根据寄存器号读出寄存器的值至value,根据偏移将寄存器相应位置设置为中断属性config,最后再写回寄存器;

最后两个函数OsGicIntAcknowledge与OsGicIntClear是共同使用的,前者读出当前中断的中断号,后者将中断号写入相应寄存器,表示中断处理结束。

4.在include目录下创建文件prt_config.h,设置中断时间间隔,tick处理时间不能超过1/OS_TICK_PER_SECOND(s)

在这里插入图片描述

5.在include目录下创建os_cpu_armv8.h文件

在这里插入图片描述

这个文件定义了异常处理等级,同时定义了Debug、Abort、IRQ和FIQ四个异常的屏蔽位,分别代表调试异常、终止异常、IRQ中断和FIQ中断,以及一些ARMv8架构中的内存屏障汇编指令,用来保证特定的内存访问顺序。

6.在bsp目录下创建time.c文件对定时器和相应的中断进行配置

在这里插入图片描述

7.构建时钟中断处理

① prt_vector.S 中的 EXC_HANDLE 5 OsExcDispatch 改为 EXC_HANDLE 5 OsHwiDispatcher,设置IRQ类型的异常处理函数

在这里插入图片描述

② 在 prt_vector.S 中加入 OsHwiDispatcher 处理代码,整体上下文的恢复与保存的结构与系统调用完全一致,C语言处理函数的入口变为OsExcDispatch

在这里插入图片描述

③ 在prt_exc.c中引用头文件 os_attr_armv8_external.h,os_cpu_armv8.h,同时定义C语言处理函数OsHwiDispatch,用于IRQ 类型的中断。

在这里插入图片描述

在中断处理函数中,先调用之前定义的函数OsGicIntAcknowledge获取当前的中断号,然后分析Arm用户手册可知

在这里插入图片描述

中断号的低10位表示interruptID,存储于irq_num,表示要访问的中断,高3位表示CPUID,存储于core_num,然后调用HandleActive函数,根据要访问的中断调用相应函数,最后清除中断。

④ 在src/kernel/tick目录下新建文件prt_tick.c文件,实现 OsTickDispatcher 时钟中断处理函数。

在这里插入图片描述

在函数 OsTickDispatcher中,首先将全局可屏蔽中断关闭(相关函数在后文介绍),并保存关闭前的状态(开启),令时钟计数器加一,再恢复为原来的状态,接着利用内联汇编代码设置中断周期,确定下一次产生时钟中断的时间,最后定义了一个函数可以获取当前的tick计数

⑤将新创建的文件纳入构建系统

src/kernel目录

在这里插入图片描述

src/kernel/tick目录

在这里插入图片描述

src目录,修改增加include目录、包含子目录和编译目标

在这里插入图片描述

src/bsp目录

在这里插入图片描述

⑥ 在prt_exc.c文件中实现OsIntLock 和 OsIntRestore 函数

在这里插入图片描述

在这里插入图片描述

这三个函数采用内联汇编,直接对寄存器进行操作,对于开启全局可屏蔽中断函数,首先将DAIF寄存器设置为输出对象,将DAIFClr设置为输入对象,用C语言变量state作为输出对象的接受变量,DAIF_IRQ_BIT作为立即数进行输入,从而清除DAIF寄存器的IRQ位,开启中断,第二个函数则是通过设置DAIF的IRQ位从而禁用中断,最后一个函数则是根据输入的状态对DAIF进行设置,相当于指定状态设置

⑦ 在src/bsp目录下创建头文件os_cpu_armv8_external.h,用于相关宏的定义

在这里插入图片描述

⑧ 在include目录下新建 prt_tick.h,声明 Tick 相关的接口函数.

在这里插入图片描述

8.最后修改main函数,完成时钟中断的构建

在这里插入图片描述

三、测试及分析

运行内核程序,发现正常引发时钟中断

在这里插入图片描述

四、lab5作业

实现 hwi_init.c 中缺失的 OsGicEnableInt 和 OsGicClearInt 函数。

在这里插入图片描述

与上文的分析一致,去使能指定中断通过向GICD的ICENABLERn寄存器写入数据以禁用特定的中断(对应intID),由于每一个ICENABLERn寄存器可以处理GICD_ICENABLER_SIZE(32)个终端信号,所以用intID除以GICD_ICENABLER_SIZE得到寄存器号,intID模GICD_ICENABLER_SIZE得到寄存器内偏移,所以GIC_REG_WRITE(GICD_ICENABLERn + (intId / GICD_ICENABLER_SIZE)*sizeof(U32), 1<<(intId % GICD_ICENABLER_SIZE))将相应的禁用位设置为1;

使能指定中断函数则是通过向GICD的ISENABLERn寄存器写入数据以启用特定的中断(对应intID),具体实现方式与禁用函数完全一致;

五、心得体会

  1. 深刻理解了中断的原理和机制,深刻理解CPU访问设备控制器的方法。

  2. 掌握Arm体系结构的中断机制和规范,实现时钟中断服务。

  3. 实现了时钟中断,并完成了一个定时器进行时钟中断处理。

  • 48
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值