2440中断
2440中断
ARM的寄存器
通用寄存器
备份寄存器(banked register)
CPSR:当前程序状态寄存器(Current Program Status Register);CPSR
SPSR:CPSR的备份寄存器:SPSR(Save Program Status Register)
表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR.也就是保存上一个模式下的状态寄存器.
LR:被中断函数的下一个指令
--------------------------------------------------------------------------------------
usr 正常模式
sys
undefined(und) 未定义模式
Supervisor(svc) 管理模式
Abort(abt) 终止模式 -a 指令预取终止(读写某条错误的指令导致终止运行)
-b 数据访问终止 (读写某个地址,这个过程出错)
IRQ(irq) 中断模式
FIQ(fiq) 快中断模式
中断向量表如下:
可以看下uboot的cpu\arm920t\start.S
.globl _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
_fiq: .word fiq
.balignl 16,0xdeadbeef
异常优先级
- 高优先级
- 复位
- 数据终止
- 快中断
- 中断IRQ
- 欲取终止
- 低优先级
- 未定义指令,软件中断,
中断互斥:
- 未定义指令和软件中断异常互斥,因为是软件中断指令,就不可能是未定义指令
异常之复位
- 复制当前 PC 和 CPSR 的值覆盖到 R14_svc 和 SPSR_svc 中。未定义保存的 PC 和 SPSR 的值
- 强制将 M[4:0]设置为 10011(管理模式),置位 CPSR 中的 I(禁止中断) 和 F(禁止fiq) 位并清除 CPSR 的 T(ARM状态) 位。
- 强制 PC 从 0x00 地址开始对下一条指令的取指。
- 在 ARM 状态恢复执行
小结: pc从0开始,禁止快中断,禁止中断,ARM指令状态
异常之未定义指令(最简单的中断处理程序)
中断处理
.text
.global _start
_start:
b reset //reset
ldr pc,undef_addr //未定义指令
undef_addr:
.word do_und
do_und:
/*
保存现场
{
硬件操作
1.保存cpsr到scpsr
2.保存返回地址到lr中
软件操作
1.r0~r12寄存器
2.返回地址lr
}
do_something
恢复现场
{
恢复r0~r12
恢复cpsr状态寄存器
}
*/
//设置sp,这里正常的流程应该是在reset中设置sp的
ldr sp,=0x34000000
//保存现场,这个入栈的代码可以看反汇编调用函数的例子
stmdb sp!, {r0-r12, lr}
//do_something
mrs r0, cpsr //把cpsr的值读入r0,11011 后五位
ldr r1, =und_string //汇编调用字符串
bl interupt_und
//恢复现场,同时恢复cpsr,这个异常pc=lr=pc+4
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
主函数手动触发
bl print1
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
异常之软中断
-
系统复位进入管理模式
-
正常流程进入用户模式
// M[4:0]=10000 为用户模式 mrs r0, cpsr /* 读出cpsr 读到r0 */ /使用bic命令 bitclean 把低4位清零/ //管理模式——BIT4本来就是1 bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */ msr cpsr, r0
-
App运行在用户模式,不可访问硬件,需要进入特权模式.可以通过触发软中断
命令
swi xxx
如何获得SWI 参数
-
LR
寄存器保存着下次的返回地址,所以对LR-4
就能获得swi x
这个指令的地址,所以读取这个地址就能获取到这个数值 -
代码如下:
mov r4, lr sub r0, r4, #4 bl printSWIVal void printSWIVal(unsigned int *pSWI) { puts("SWI val = "); printHex(*pSWI & ~0xff000000); puts("\n\r"); }
中断框图
- 某个中断信号如果使能了FIQ快中断MODE寄存器,则不会再去触发中断IRQ
- INTPEND指示了当前优先级最高的要处理的中断
- mask为屏蔽寄存器有些中断公用一个
INTPEND
,需要再去查询subsrcpend
- 两个中断挂起寄存器:源挂起寄存器(SRCPND)和中断挂起寄存器(INTPND)
- INTPEND只对IRQ起作用,还有一个INTOFFSET也是一样,前者是bit位,后者指示哪个bit位
-
注意仲裁器的 REQ0 的优先级总是最高并且 REQ5 的优先级总是最低
-
这里的中断优先级是有一个轮寻的概念,也就是排除
REQ0
和REQ5
之后,剩下的1,2,3,4
,首先保持着1,2,3,4
的绕尾形式.也就是当ARB_MODE=1
的时候,可以指定当前最前面的是那个REQx
,同时如果发生了某个REQ
,则下次的轮训,该中断就在最后面了.比如当前发生了3,则下次的顺序是4,1,2,3
,注意,这个自动变换也是需要ARB_MODE=1
快中断与中断
-
IRQ模式只能被FIQ模式打断,FIQ模式下谁也打不断。
-
快中断的优先级?,应是没有这个东西
同一时间只能有一个中断可以被设成快速中断
-
清除中断标志需要从源头开始清除,因为先清除后端的时候,前端的标志会置位后端标志
流程设计
注意
进入异常之前应该先设置好异常的栈,韦东山的实例中是进入中断后设置异常的sp
保护现场
-
硬件自动保存
-
保存当前的
cpsr
到中断下的scpsr
-
保存返回地址到LR寄存器,(某种异常模式的LR等于被中断的下一条指令的地址)它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况
-
-
软件需要做的
stmdb sp!, {r0-r12, lr}
-
保存
R0~R12
寄存器 -
保存
LR
寄存器,注意这个保存之后是退出中断的返回地址,因为ARM
架构的原因,不同的中断异常有不同的返回地址,所以需要对这个LR
先进行一些运算再保存.偏移地址如下:举个例子比如UDEF
异常,只需要直接复制,而FIQ
则需要保存LR+4
BL MOV PC, R14 SWI MOVS PC, R14_svc UDEF MOVS PC, R14_und FIQ SUBS PC, R14_fiq, #4 IRQ SUBS PC, R14_irq, #4 PABT SUBS PC, R14_abt, #4 DABT SUBS PC, R14_abt, #8
-
恢复现场
- 恢复
R0~R12
,恢复LR
,恢复scpsr
到CPSR
,代码:ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
程序设计
-
初始化
-
打开总中断开关,切换到用户模式,
mrs r0, cpsr /* 读出cpsr */ bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */ bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */ msr cpsr, r0 /* 设置 sp_usr */ ldr sp, =0x33f00000
-
设置框图中的寄存器,外部中断注意设置管脚复用,边沿方式
-
-
中断处理函数,先在
IRQ
判断是哪个INTPND
,如果有sub源还需要进一步判断.外中断公用的,使用io值判断.EINTPEND
这个也能判断
按键中断程序
按键IO如下
* eint0 GPF0
* eint2 GPF2
* eint11 GPG3
* eint19 GPG11
初始化程序
INTMSK&=~(_BIT0|_BIT2|_BIT5);//取消 eint0,eint2,eint8~23的mask
EINTMASK&=~(_BIT11|_BIT19); //取消eint11,19的mask
//配置gpio为irq,双边触发
//eint0
EXTINT0|=(_BIT0|_BIT1|_BIT2);
//eint2
EXTINT0|=(_BIT8|_BIT9|_BIT10);
//eint11
EXTINT1|=(_BIT12|_BIT13|_BIT14);
//eint19
EXTINT2|=(_BIT12|_BIT13|_BIT14);
//GPF0,GPF2
GPFCON&=~(_BIT0|_BIT1|_BIT4|_BIT5);
GPFCON|= (_BIT1|_BIT5);
//GPG3 GPG11
GPGCON&=~(_BIT6|_BIT7|_BIT22|_BIT23);
GPGCON|= (_BIT7|_BIT23);
中断判断
void interrupt_irq(void)
{
uint32_t pend=INTOFFSET;
if (pend == 0 || pend == 2 || pend == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(pend); /* 处理中断, 清中断源EINTPEND */
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<pend);
INTPND = (1<<pend);
}
void key_eint_irq(uint32_t irq)
{
unsigned int val = EINTPEND; //如果是eint9//11 还需要判断这位
}
定时器中断
框图
- 内部有一个16位的计数器TCNTn,以及一个比较值寄存器TCMPn,可以理解为影子寄存器
- 使用TCMPBn赋值到TCMPBn,使用TCNTBn初始化计数器TCNTn
初始化
- Write the initial value into TCNTBn and TCMPBn.
- Set the manual update bit of the corresponding timer. It is recommended that you configure the inverter on/off bit. (Whether use inverter or not).
- Set start bit of the corresponding timer to start the timer (and clear the manual update bit).
也就是说
-
初始值写入到 TCNTBn 和 TCMPBn 中
-
设置相应定时器的手动更新位
-
设置相应定时器的开始位来启动定时器(并且清除手动更新位)。如果定时器被强制停止,TCNTn 保持计数器值并且不会从 TCNTBn 重载。如果需要设置一个新值,执行手动更新。
注意: 先设置
TCON|=_BIT1;
更新值,然后清除这一位,然后再开启定时器.必须要这么做
void TimerInit()
{
INTMSK&=~(_BIT10);
// 预分频=99
TCFG0=99;
// 1/16 clk
TCFG1&=~(_BIT0|_BIT1|_BIT2|_BIT3);
TCFG1|=(_BIT0|_BIT1);
//clk=50M/100/16=31250
TCNTB0=31250>>1;
//TCMPB0
TCON|=_BIT1; //更新初值
TCON&=~_BIT1; //关闭手动更新
TCON|=(_BIT0|_BIT3);// 开启定时器,同时设置自动重载
}
void TimerIrq(void)
{
GPFDAT ^= (1<<6);
}
流程优化
-
中断处理使用注册函数,也就是说中断函数通过中断号查询一个数组去执行函数
-
注册函数会注册中断应该使用的函数,以及打开中断
typedef void(*irq_func)(int); irq_func irq_array[32]; void handle_irq_c(void) { /* 分辨中断源 */ int bit = INTOFFSET; /* 调用对应的处理函数 */ irq_array[bit](bit); /* 清中断 : 从源头开始清 */ SRCPND = (1<<bit); INTPND = (1<<bit); } void register_irq(int irq, irq_func fp) { irq_array[irq] = fp; INTMSK &= ~(1<<irq); }
汇编中的指令对齐
搜索下官方文档的索引.align
,有如下描述,也就是有两种情况,对于ARM,表示的是末尾几个0,也就是2^x了.
For other systems, including ppc, i386 using a.out format, arm and strongarm, it is the number of low-order zero bits the location counter must have after advancement. For example ‘.align 3’ advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
当在代码中定义了字符串之后,可能会出现代码指令非4字节对齐的情况,如下:reset
的指令在30000045
的位置,显然有问题,使用aligin
来保持对齐
//
und_string:
.string "undefined instruction exception-"
reset:
//-------------------------------------------------------------
30000024 <und_string>:
22 30000024: 65646e75 strvsb r6, [r4, #-3701]!
23 30000028: 656e6966 strvsb r6, [lr, #-2406]!
24 3000002c: 6e692064 cdpvs 0, 6, cr2, cr9, cr4, {3}
25 30000030: 75727473 ldrvcb r7, [r2, #-1139]!
26 30000034: 6f697463 swivs 0x00697463
27 30000038: 7865206e stmvcda r5!, {r1, r2, r3, r5, r6, sp}^
28 3000003c: 74706563 ldrvcbt r6, [r0], #-1379
29 30000040: 2d6e6f69 stccsl 15, cr6, [lr, #-420]!
30 ...
31
32 30000045 <reset>:
33 30000045: e3a00453 mov r0, #1392508928 ; 0x53000000
-
使用
align 4
之后对齐在3000,0050
,32 30000050 <reset>:
-
使用
align 5
之后 对齐在30000060
,30000060 <reset>:
写一个demo测试一下,发现
.align x
对齐的是2^x
字节,uboot对齐的是align 5
,也就是32字节_start: .string "1234" _test_addr: ldr r0,[r2] 反汇编如下 00000000 <_start>: 00000005 <_test_addr>: //--------------------------------------------------- _start: .string "1234" .align 4 _test_addr: ldr r0,[r2] 反汇编如下 00000000 <_start>: 00000010 <_test_addr>: //--------------------------------------------------- _start: .string "1234" .align 5 _test_addr: ldr r0,[r2] 反汇编如下 00000000 <_start>: 00000020 <_test_addr>:
中断向量表的优化
假设存在以下的跳转,如果hello这个函数在4kNand之外,使用bl指令还是在Nand或者Nor中跳转,是无法实际跳转到hello中的,所以要保证跳转到undef_irq
是绝对跳转,或者后面使用ldr,pc=hello
_start:
b reset
b undef_irq
undef_irq:
....
....bl hello //跳转到一个c函数
....
所以这里的 跳转中断应该使用ldr pc,=undef_irq
,实际上重定位后的代码的跳转都应该使用ldr
_start:
b reset
ldr pc,=do_und
do_und:
....
....bl hello //跳转到一个c函数
....
查看下这种写法的反汇编,程序是将地址存放到一个地址300000c0
,然后去这个地址读出想要的pc值30000008
.这个地址是在start.s
的最后面
30000000 <_start>:
7 30000000: ea00000e b 30000040 <reset>
8 30000004: e59ff0b4 ldr pc, [pc, #180] ; 300000c0 <.text+0xc0>
9
10 30000008 <do_und>:
300000bc <halt>:
64 300000bc: eafffffe b 300000bc <halt>
65 300000c0: 30000008 andcc r0, r0, r8
也就是说ldr pc,=do_und
是从最后面的内存单元去读取pc值.如果是从Nand启动的时候,Start.S
大于4K,ldr
这个取址就会失败,所以希望ldr
不要去程序的最后面读取pc值.所以我们将其放到前面的4k
.globl _start
_start:
b reset
ldr pc, _do_und
所以先在前面4k保存下这个跳转的地址,注意,这里是ldr pc,undef_addr
表示的是取标号的内容
_start:
b reset //reset
ldr pc,undef_addr //未定义指令
undef_addr:
.word do_und
do_und:
反汇编如下,可以看到是按照pc+4
的地方去读
6 30000000 <_start>:
7 30000000: ea00000f b 30000044 <reset>
8 30000004: e51ff004 ldr pc, [pc, #-4] ; 30000008 <undef_addr>
9
10 30000008 <undef_addr>:
11 30000008: 3000000c andcc r0, r0, ip
12
13 3000000c <do_und>:
14 3000000c: e3a0d30d mov sp, #872415232 ; 0x34000000
最终的向量表
- 跳转使用
ldr
表示跳到链接地址 - 使用
word
保存地址是为了防止ldr
取地址的地方超过4K.NAND启动不可访问
.globl _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
_fiq: .word fiq
Linux的中断处理
https://blog.csdn.net/linyangspring/article/details/39670449?utm_source=blogxgwz0
Linux不用FIQ,只用到了IRQ。但是我们有时候一个中断需要处理很长时间,那我们就需要占用IRQ模式那么长的时间吗?没有,linux在IRQ模式下只是简单的记录是什么中断,马上就切换回了SVC模式,换句话说,Linux的中断处理都是在SVC模式下处理的。那么中断号是怎么来的呢?在ARM上固死了,相应的中断号只有一个办法得到:查询irqs.h 。那我先用一个中断号注册一个中断处理程序,当中断发生的时候,Linux怎么知道是我这个中断号发生的中断呢?在处理中断的时候,先读取INTPND,根据需要再读取EINTPEND或SUBSRCPND计算出一个中断号,相应的处理算法在 get_irq_nr_base这个宏中。而且irqs.h中的中断号就是根据这个算法把每个中断算一下得来的。
在linux的中断处理子模块中,把中断的处理分为两部分,上半部和下半部。上半部运行在IRQ模式,置位I位,禁止IRQ中断,主要处理一些紧急的事情,比如记录中断号、清除中断源等。下半部运行在SVC模式,清零I位,允许IRQ中断,处理一些中断相关的非紧急事情,比如读取设备缓存等。