linux arm 定时器 irq,uboot下开启ARMv8的定时器中断

本文详细介绍了如何在U-Boot环境下配置和启用基于Cortex-A53的ARMv8-A架构的定时器中断。内容涉及打开中断、配置中断向量表、GIC中断路由、定时器参数设置以及中断服务函数的编写。通过这些步骤,可以在启动过程中利用定时器进行特定业务处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在工作需求,需要在uboot下实现一个定时器来完成一些业务,使用的是Cortex A53 CPU(基于ARM v8-A架构),因此对ARMv8-A的定时器以及中断作了一些研究,这篇文章主要描述如在uboot下开启ARM v8的定时器中断。

文章内容会涉及到GIC及ARM v8-A(后面简写成ARMv8)架构相关知识,可查看下面2篇文章

、ARM v8架构

注:本文中所使用和参考的uboot版本:U-Boot 2015.07

总体思路

一般情况下uboot都是关闭中断的,所以要让定时器正常工作,需要完成以下几点:

打开ARM核心中断,这里主要是指IRQ中断

正确配置中断向量表,中断现场保存与恢复

正确配置GIC

正确配置定时器参数

编写好正确的中断服务函数

打开ARMv8中断

这一节描述如何打开ARMv8的IRQ中断,有兴趣的可以自行阅读ARMv8体系结构手册,可以从这里下载。

首先ARMv8运行在64位模式时与ARMv7有很大的区别,不能简单的通过修改CPSR寄存器来完成。

设置DAIF寄存器,打开IRQ中断

64位模式下DAIF寄存器定义如下,其实DAIF寄存器只是将ARMv7中CPSR寄存器的A、I、F单独拿出来使用

cb4fda592598ef7b6eff44bb5b734ccb.png

只需要将I位清空成0即可打开 IRQ 中断

针对DAIF寄存器,体系结构中提供了2个位操作寄存器DAIFSet、DAIFClr

打开中断代码

1

2

3

4

5

6void enable_interrupts(void)

{

//开启IRQ

asm volatile("msr daifclr, #2");

return;

}

相应的关闭中断代码

1

2

3

4

5

6int disable_interrupts(void)

{

//关闭IRQ

asm volatile("msr daifset, #2");

return 0;

}

配置中断路由

中断部分,ARMv8与ARMv7最大的不同可能是中断路由了,因为ARMv8中取消了工作模式改用异常级别,因为中断可以通过配置被路由到不同的级别,比如一个中断发生后,可以进入EL1级别处理,也可以进入EL2级别处理。

目前工作使用的软硬件方向中,uboot启动后CPU运行中EL2级别(不确定其他硬件方向运行的级别),使用最小化修改代码框架原则,不修改uboot的运行级别,通过配置将相应的中断路由到EL2级别处理。

下图是我这次选择的中断路由配置,目前使用的硬件方案(A53)实现了EL2和EL3

ff1dfffa1a0e91805f448502382ef030.png

其中HCR_EL2寄存器的默认值与上图的配置不一致,IMO默认是0,需要修改成1

当然其他方案的寄存器可能不同,需要根据实际情况修改

修改代码如下

1

2

3

4

5

6

7

8

9

10

11

12

13int interrupt_init(void)

{

unsigned long value, cur_el;

/*如果当前处于EL2级别,需要设置HCR_EL2寄存器,将IRQ路由到EL2*/

asm volatile("mrs %0, CurrentEL" : "=r" (cur_el));

if(cur_el == 0x8) {

asm volatile("mrs %0, HCR_EL2" : "=r" (value));

value |= (1<<4);

asm volatile("msr HCR_EL2, %0" : : "r" (value));

}

return 0;

}

中断向量表

U-Boot 2015.07官方代码中已经正确配置了中断向量表,可以不用太关注。

ARMv8的中断向量表也ARMv7也有很大的区别,v7的中断向量表只有一个并且在固定位置。v8的不同异常级别对应不同的中断向量表,当然可以只实现一个异常级别的中断,比如linux kernel只使用EL1。

ARMv8的中断向量表由VBAR_ELx(x=1,2,3)决定,比如我此次使用的EL2级别,需要配置vbar_el2

因为官方代码已经实现,可以直接查看uboot源码,这里不再详解。

中断现场保护与恢复

与中断现场相关的寄存器

ELR_ELx(x=1,2,3,下同):进入ELx异常时保存需要返回的地址,功能ARMv7的LR寄存器类似

SP、SP_ELx:各级别所使用的栈指针,其中SP是当前模式下的栈指针,可以通过SPSel来选择(所有级别都使用SP_EL0、不同级别使用相应的SP_ELx)

SPSR_ELx:进入ELx异常时保存处理器的状态,异常返回时会自动恢复到相应的状态寄存器中,正常的中断程序可以不用处理

中断现场保护

将所有通用寄存器(x0-x30)和程序返回寄存器(ELR_ELx)入栈,入栈完成后即可进入中断处理流程

因为官方代码已经实现了入栈功能,可以直接查看uboot源码,这里不再详解。

中断现场恢复

相应的需要将通用寄存器(x0-x30)和程序返回寄存器(ELR_ELx)出栈,然后使用eret指令返回

代码如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27.macro exception_exit

ldp x2, x0, [sp], #16

switch_el x11, 3f, 2f, 1f

3: msr elr_el3, x2

b 0f

2: msr elr_el2, x2

b 0f

1: msr elr_el1, x2

b 0f

0:

ldp x1, x2, [sp], #16

ldp x3, x4, [sp], #16

ldp x5, x6, [sp], #16

ldp x7, x8, [sp], #16

ldp x9, x10, [sp], #16

ldp x11, x12, [sp], #16

ldp x13, x14, [sp], #16

ldp x15, x16, [sp], #16

ldp x17, x18, [sp], #16

ldp x19, x20, [sp], #16

ldp x21, x22, [sp], #16

ldp x23, x24, [sp], #16

ldp x25, x26, [sp], #16

ldp x27, x28, [sp], #16

ldp x29, x30, [sp], #16

eret

.endm

这里再简单说一下eret指令

eret指令的大概作用是:使用当前的SPSR和ELR寄存器,将异常返回,SPSR寄存器的内容会恢复到PSTATE寄存器中,程序从ELR指向的地址继续执行

配置GIC

GIC是ARM CPU里的中断控制器,对于没有了解过的人来说还是有一点小小的复杂,不过如果只是将ARM核心的定时器中断打开,配置起来还是非常简单的,只需要几个操作:

打开 GIC Distributor总中断

打开CPU核心定时器中断(选择 physical timer,对应的中断是30)

打开GIC CPU interface总中断

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19void timer_gic_init(void)

{

uint32_t value;

/*打开GIC Distributor总中断*/

value = readl(GICD_BASE+GICD_CTLR);

value |= 1;

writel(value, GICD_BASE+GICD_CTLR);

/*打开Non-secure physical timer中断,具体可以看GIC-400手册*/

value = readl(GICD_BASE+GICD_ISENABLERn);

value |= (1<<30);

writel(value, GICD_BASE+GICD_ISENABLERn);

/*打开GIC CPU interface总中断*/

value = readl(GICC_BASE+GICC_CTLR);

value |= 1;

writel(value, GICC_BASE+GICC_CTLR);

}

配置定时器参数

参考了linux内核,选择physical timer定时器,详细的配置可以查看ARMv8体系结构手册

与定时器相关的寄存器:

CNTFRQ_EL0:系统定时器的频率,由硬件决定,软件被始化时需要填写正确的值,目前我使用的是24Mhz

CNTP_CTL_EL0,:定时器使能(包括中断使能)控制

CNTPCT_EL0:定时器计数,只要CPU在运行(没有休眠)就会一直累加,累加的频为CNTFRQ_EL0,不可关闭

CNTP_CVAL_EL0:比较寄存器,如果定时器使能且中断已经打开(由CNTP_CTL_EL0控制),当CNTPCT_EL0计数达到CNTP_CVAL_EL0时,就会产生中断

根据以上几个寄存器的描述可以,只要在CNTP_CVAL_EL0里写入合适的值即可产生想要的中断,具体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21void set_physical_timer(int timeout_ms)

{

/*定时器使用细节可查看ARMv8体系结构手册*/

unsigned long value, freq, cnt, cmp;

/*关闭定时器*/

value = 0;

asm volatile("msr CNTP_CTL_EL0, %0" : : "r" (value));

/*计算下次超时时间*/

asm volatile("mrs %0, CNTFRQ_EL0" : "=r" (freq));

asm volatile("mrs %0, CNTPCT_EL0" : "=r" (cnt));

cmp = cnt + (freq/1000)*timeout_ms;

asm volatile("msr CNTP_CVAL_EL0, %0" : :"r" (cmp));

/*打开定时器*/

value = 1;

asm volatile("msr CNTP_CTL_EL0, %0" : : "r" (value));

}

中断服务函数

中断服务函数需要完成几件必要的事情

中断现场保护(前面已经提到)

中断处理

中断现场恢复(前面已经提到)

中断处理分成3个步骤:

从GIC中找出当前的中断编号,对于此次应用,应该是30

重写设置定时器的CNTP_CVAL_EL0寄存器

写GIC的EOI寄存器,指示此次中断处理完毕

具体代码如下 :

1

2

3

4

5

6

7

8

9

10

11

12void do_irq(struct pt_regs *pt_regs, unsigned int esr)

{

int irq;

irq = readl(GICC_BASE + GICC_IAR);

if((irq & 0x3ff) == 30) {

set_physical_timer(TIMER_PERIOD);

printf("%s.%d\n", __FUNCTION__, __LINE__);

}

writel(irq, GICC_BASE + GICC_EOIR);

}

1

2

3

4_do_irq:

exception_entry

bldo_irq

exception_exit

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值