ARM 处理器一般共有 37 个寄存器,其中包括:
31 个通用寄存器,包括 PC(程序计数器)在内,都是 32 位的寄存器。
6 个状态寄存器,都是 32 位的寄存器。
ARM 处理器共有 7 种不同的处理器模式:
模式 | Mode |
---|---|
用户模式 | User |
快速中断模式 | FIQ |
普通中断模式 | IRQ |
管理模式 | Svc |
数据访问中止模式 | Abort |
未定义指令中止模式 | Und |
系统模式 | Sys |
在每一种处理器模式中有一组相应的寄存器。在任意一种处理器模式下,可见的寄存器包括 15 个通用寄存器(R0~R14)、一个或者二个状态寄存器以及程序计数器(PC)。在所有的寄存器中,有些是各模式共用同一个物理寄存器,有些寄存器是各个模式自己拥有独立的物理寄存器
其中 r0~r3 主要用于子程序间传递参数, r4~r11 主要用于保存局部变量,但在 Thumb 程序中,通常只能使用 r4~r7 来保存局部变量; r12 用作子程序间scratch 寄存器,即 ip 寄存器; r13 通常用做栈指针,即 sp; r14 寄存器又被称为连接寄存器(lr),用于保存子程序以及中断的返回地址; r15 用作程序计数器(pc),由于 ARM 采用了流水线机制,当正确读取了 PC 的值后,该值为当前指令地址加 8 个字节,即 PC 指向当前指令的下两条指令地址。
CPSR和SPSR都是程序状态寄存器,其中SPSR是用来保存中断前的CPSR中的值,以便在中断返回之后恢复处理器程序状态。
所有处理器模式下都可访问当前程序状态寄存器CPSR。CPSR中包含条件码标志、中断禁止位、当前处理器模式以及其他状态和控制信息。在每种异常模式下都有一个对用的程序状态寄存器SPSR。当异常出现时,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。
(1)条件码标志
N、Z、C、V,最高4位称为条件码标志。ARM的大多数指令可以条件执行的,即通过检测这些条件码标志来决定程序指令如何执行。
各个条件码的含义如下:
N:在结果是有符号的二进制补码情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0。
Z:如果结果为0,则Z=1;如果结果为非零,则Z=0。
C:其设置分一下几种情况:
V:对于加减法指令,在操作数和结果是有符号的整数时,如果发生溢出,则V=1;如果无溢出发生,则V=0;对于其他指令,V通常不发生变化。
(2)控制位的作用在图1中可以看出,在这里就不阐述了。
CPSR_c指的是CPSR的低8位控制位
CPSR有4个8位区域:标志域(F)、状态域(S)、扩展域(X)、控制域(C)
MSR - Load specified fields of the CPSR or SPSR with an immediate constant, or from the contents of a general-purpose register.
Syntax:
MSR{cond} , #immed_8r MSR{cond} , Rm where: cond is an optional condition code. is either CPSR or SPSR. specifies the field or fields to be moved. can be one or more of:
c control field mask byte (PSR[7:0]) x extension field mask byte (PSR[15:8]) s status field mask byte (PSR[23:16) f flags field mask byte (PSR[31:24]). immed_8r is an expression evaluating to a numeric constant. The constant must correspond to an 8-bit pattern rotated by an even number of bits within a 32-bit word. Rm is the source register.
常用于MRS或MSR指令,用于psr中的值转移到寄存器或把寄存器的内容加载到psr中.
如:
MSR CPSR_c,#0xd3
注:
Exynos4412中断控制器包括160个中断控制源,这些中断源来自软中断(SGI),私有外部中断(PPI),公共外部中断(SPI)。
Exynos4412采用GIC中断控制器,主要是因为Contex-A9 是多核处理器,GIC(Generic Interrupt Controller)通用中断控制器用来选择使用哪个CPU接口,具体主要有两个功能:
分配器:设置一个开关,是否接收外部中断源;为该中断源选择CPU接口;
CPU接口:设置一个开发,是否接受该中断源请求;
iTOP-4412 板子原理图:
我们就以 HOME 键作为中断源。
1-- 将GPX1_1引脚的上拉和下拉禁止
GPX1PUD[3:2] = 0;
2 – 将GPX1_1引脚功能设置为中断功能 WAKEUP_INT1[1] — EXT_INT41[1]
GPX1CON[7:4] = 0xF
3 – EXT_INT41CON 配置触发电平
当前配置成下降沿触发:
EXT_INT41CON[6:4] = 0x2
4 – EXT_INT41_FLTCON0 配置中断引脚滤波
默认就是打开的,不需要配置
5 – EXT_INT41_MASK 中断使能寄存器
使能INT41[1]
EXT_INT41_MASK[1] = 0b0
6 – EXT_INT41_PEND 中断状态寄存器
当GPX1_1引脚接收到中断信号,中断发生,中断状态寄存器EXT_INT41_PEND 相应位会自动置1
注意:中断处理完成的时候,需要清除相应状态位。置1清0.
EXT_INT41_PEND[1] = 0b1
1-- 找到外设中断名称和GIC中断控制器对应的名称
查看芯片手册(本例:Exynos_4412 – 9.2表)
WAKEUP_INT1[1] — EXT_INT41[1] — INT[9] — SPI[25]/ID[57]
其对应INT[9],中断ID为57,这是非常重要的,在后面的寄存器设置中起很大作用;
ICDDCR =1;
使能分配器。
3 – 使能相应中断到分配器
ICDISER.ICDISER1 |= (0x1 << 25); //57/32 =1…25 取整数(那个寄存器) 和余数(哪位)
ICDISER用于使能相应中断到分配器,一个bit控制一个中断源,一个ICDISER可以控制32个中断源,这里INT[9] 对应的中断ID为57,所以在ICDSER1中进行设置,57/32 =1余25,所以这里在ICDISER1第25位置一。
4 – 选择CPU接口
设置SPI[25]/ID[57]由那个cpu处理,当前设置为cpu0的irq中断
ICDIPTR.ICDIPTR14 |= 0x01<<8; //SPI25 interrupts are sent to processor 0 //57/4 = 14…1 14号寄存器的[15:8]
ICDIPTR寄存器每8个bit 控制一个中断源
5 – 全局使能cpu0中断处理
CPU0.ICCICR |= 0x1;
使能中断到CPU。
6 – 优先级屏蔽寄存器,设置cpu0能处理所有的中断。
CPU0.ICCPMR = 0xFF;
前面两步设置好,就可以等待中断的发生了,当中断发生时,ARM内核的处理过程如下:
2 – 中断服务程序 — start.S 汇编
.text
.global _start
_start:
b reset
ldr pc,_undefined_instruction
ldr pc,_software_interrupt
ldr pc,_prefetch_abort
ldr pc,_data_abort
ldr pc,_not_used
ldr pc,_irq
ldr pc,_fiq
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word irq_handler
_fiq: .word _fiq
reset:
ldr r0,=0x40008000
mcr p15,0,r0,c12,c0,0 @ Vector Base Address Register
init_stack:
ldr r0,stacktop /*get stack top pointer*/
/********svc mode stack********/
mov sp,r0
sub r0,#128*4 /*512 byte for irq mode of stack*/
/****irq mode stack**/
msr cpsr,#0xd2
mov sp,r0
sub r0,#128*4 /*512 byte for irq mode of stack*/
/***fiq mode stack***/
msr cpsr,#0xd1
mov sp,r0
sub r0,#0
/***abort mode stack***/
msr cpsr,#0xd7
mov sp,r0
sub r0,#0
/***undefine mode stack***/
msr cpsr,#0xdb
mov sp,r0
sub r0,#0
/*** sys mode and usr mode stack ***/
msr cpsr,#0x10
mov sp,r0 /*1024 byte for user mode of stack*/
b main
.align 4
/**** swi_interrupt handler ****/
/**** irq_handler ****/
irq_handler:
sub lr,lr,#4
stmfd sp!,{r0-r12,lr}
.weak do_irq
bl do_irq
ldmfd sp!,{r0-r12,pc}^
stacktop: .word stack+4*512
.data
stack: .space 4*512
3–中断处理程序 — do_irq函数 c语言(函数原型void name(void))
(1) 读取正在处理的中断ID寄存器(ICCIAR)
irq_num = (CPU0.ICCIAR & 0x3FF);
(2)根据irq_num,分支处理中断
switch(irq_num)
{
case 57:
break;
}
(3)清除中断状态位
(3-1)i.外设级,EXT_INT41_PEND |= 0x1 << 1;
(3-2)ii.GIC级,ICDICPR.ICDICPR1 |= 0x1 << 25;
(3-3)iii.CPU0级 CPU0.ICCEOIR = (CPU0.ICCEOIR & ~(0x1FF)) | irq_num;
下面是C 程序:
#include "exynos_4412.h"
#include "led.h"
void delay_ms(unsigned int num)
{
int i,j;
for(i=num; i>0;i--)
for(j=1000;j>0;j--)
;
}
void do_irq(void)
{
static int a = 1;
int irq_num;
irq_num = CPU0.ICCIAR&0x3ff; //获取中断号
switch(irq_num)
{
case 57:
printf("in the irq_handler\n");
if(a)
led_on(1);
else
led_off(1);
a = !a;
EXT_INT41_PEND = EXT_INT41_PEND |((0x1 << 1)); //清GPIO中断标志位
ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (0x1 << 25); //清GIC中断标志位
break;
}
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num; //清cpu中断标志位
}
/*
* 裸机代码,不同于LINUX 应用层, 一定加循环控制
*/
int main (void)
{
GPX1.CON =GPX1.CON & (~(0xf << 4)) |(0xf << 4); //配置引脚功能为外部中断
GPX1.PUD = GPX1.PUD & (~(0x3 << 2)); //关闭上下拉电阻
EXT_INT41_CON = EXT_INT41_CON &(~(0xf << 4))|(0x2 << 4); //外部中断触发方式
EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1 << 1)); //使能中断
ICDDCR = 1; //使能分配器
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (0x1 << 25); //使能相应中断到分配器
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xff << 8))|(0x1 << 8); //选择CPU接口
CPU0.ICCPMR = 255; //中断屏蔽优先级
CPU0.ICCICR = 1; //使能中断到CPU
led_init();
while(1) {}
return 0;
}
u-boot 默认没有打开中断的功能,也没有找到相关的配置选项,不过阅读源码可以发现,基本的异常向量表、relocate_vector 还是支持的,所以稍加修改应该就可以支持中断,这里也使用前文提到的 HOME 按键的中断进行验证。
在 u-boot(并非 spl)调用 relocate_code() 重定位 RAM 中的 u-boot.bin 后,会返回 here
,第一个执行的程序就是 relocate_vector(),该函数主要作用是设置 VBAR(Vector Base Address Register),VBAR 的细节还没有了解过,不过肯定就是指示异常向量的基地址了,暂时不深究了。
去掉无关的宏后,实际函数:
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
第一行获得重定位后的基地址,同时也是异常向量表的基地址。
第二行将该基地址存放在 VBAR 中,以后发生异常后就会来该地址寻找异常向量。
中断寄存器的初始化就按照第二章的描述进行就可以了,相比以前学习的 2440 确实复杂很多,但是实际上搞清楚后其实也就那样,无非是对应的关系变复杂了,需要配置和检查的寄存器变多了。
函数 do_irq() 定义在:
根据 vectors.S 里的定义:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
发生 interrupt 后,会跳转到 irq 执行。
仔细分析原有的 irq 函数使用的设置堆栈和保存现场的方法不对,需要稍作修改,SP 的地址可以比较随意,这里就设置 iRAM 的最高地址吧:
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x02060000 // irq stack
这里非常重要,虽然前面打开了很多寄存器的样子,但是实际上中断并不能到达 arm core,因为 u-boot 中并没有打开 cpsr 的 irq enable,在任意位置打开就好了,我为了方便打印该值,就在 C 函数里做了。
unsigned long i;
__asm__ __volatile__ (
"mrs %0, cpsr\n\t"
"bic %0, %0, #0x80\n\t"
"msr cpsr,%0\n\t" : "=r" (i):);
printf("cpsr = 0x%08lX!\n", i);
中断处理函数:
void do_irq (struct pt_regs *pt_regs)
{
static int irq_count = 0;
(*((volatile unsigned long*)(0x11000104)) ^= (1)); // led
printf("good irq irq_count = %d!\n", irq_count++);
(*((volatile unsigned long*)(0x11000F44)) |= (1 << 1)); // clear interrupt
}
效果:
可以看出中断发生后正确恢复现场,可以通过回车等和 u-boot 交互,支持中断成功。
当然支持中断并不是为了点个灯,主要还是为了改善 u-boot 输入时一卡一卡的手感,并且明明输入了很多很多字符却不显示,但是实际却起了作用的 bug。
关于 UART 的优化:移植 u-boot-2020.07 到 iTOP-4412(三)重定位、UART
源码下载:
ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition 不知道为啥下载下来打不开。。
Exynos4412裸机开发——中断处理
ARM寄存器及功能介绍