您尚未登录。

楼主 #1 2018-05-16 14:34:41

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

异常与中断概述 UND SVC

《单片机小白转嵌入式Linux学习记录,基于S3C2440----目录》


英文参考:ARM Architecture Reference Manual

中文参考:<ARM体系结构与编程> 杜春雷

CPU模式(Mode) 状态(State)与寄存器

7种Mode: P72
        usr/sys            用户模式/系统模式
        undefined(und)    无定义指令模式,CPU遇到不认识的指令时会进入该模式
        Supervisor(svc)    管理模式
        Abort(abt)        终止模式 1. 指令预取终止 流水线预取出错 2. 数据访问终止 数据读取过程中产生的异常。
        IRQ(irq)        中断模式
        FIQ(fiq)        快速中断模式  有多个专用寄存器,所以中断速度比较快
       
2种状态:
        ARM state        4字节指令
        Thumb state        2字节指令  用于压缩程序代码 对小容量芯片非常有用,S3C2440芯片一般不用
       
寄存器:
        通用寄存器
        分组寄存器(banked register) 特权模式的专用寄存器 P73
        当前程序状态寄存器 CPSR : (Current Program State Register) 属于通用寄存器
        CPSR的备份寄存器    SPSR: (Saved Program State Register) 特权模式专用寄存器,用于保存被中断模式的CPSR 程序状态
       
除了 usr 其它模式均为特权模式(privileged mode)。特权模式之间可以通过修改CPSR 低5位来切换。普通模式无法修改CPSR进入特权模式。 CPSR 详见 P76

-------------------------------------------------------------------



中断属于异常的一种。异常对应不同的CPU工作模式,不同的工作模式,有不同的专有寄存器 lr、sp、spsr。

lr     保存被中断模式中的下一条即将执行的指令的地址   进入异常之前硬件自动保存
sp    当前异常模式专用的栈指针, 进入异常后先设置栈指针
spsr  备份被中断状态的程序状态                             进入异常之前硬件自动保存


CPU每执行完一条指令都会去检查是否有异常产生,如果有异常,就开始去处理异常。
对于不同的异常,跳去不同的地址去执行。这整个过程是有硬件决定的,这个跳转的
地址也是固定的。不同的异常跳转地址组成了异常向量表,如下:

_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

如果是中断异常,则硬件会自动跳到 0x18(ldr    pc, _irq),跳转到0x18位置是由硬件决定的,0x18这个地址放什么指令是由软件决定。
我们一般在这个位置放置一条跳转命令,跳转到更复杂的程序(中断处理程序)

我们要在中断处理程序中做什么呢?

1. 保存现场 (保存寄存器数据)
2. 调用处理函数
3. 恢复现场 (恢复寄存器数据)

2 中调用的处理函数,判断中断类型,根据不同的中断类型调用不同中断理函数。


进入异常硬件动作:   P79
1. 保存被中断模式的 下一条指令地址到 对应异常模式的LR专用控制器
2. 保存被中断模式的CPSR程序状态寄存器到 对应异常模式的 SPSR (状态备份 )
3. 修改CPSR中的低5位 切换到对应的异常模式。
4. 修改PC指针指向异常向量表对应的异常入口


退出异常软件操作:
1. 如果是中断产生的异常,清除中断源
2. 恢复程序状态寄存器 CPSR = SPSR(对应异常模式的程序状态备份寄存器)   
3. 修改PC指针 = _LR - offset(4 or 8)  参考 P80 table 2-2

und 未定义指令异常

当CPU发现未定义指令时,会强制跳转到 0x04 地址,同时会将异常前的下一条指令地址保存在 lr_und CPSR 保存在 SPSR_und保存有被中断模式的CPSR

所以我们保存现场时要保存 lr_und 到栈中,保存现场之前要设置异常模式的栈地址。

恢复现场的时候将lr恢复到pc 将 SPSR_und恢复到CPSR


我们对start.S文件进行修改
----------------------------

_start:
	b reset  /* vector 0 : reset */
	b do_und /* vector 4 : und */

do_und:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 4	/* 这里要加上4k 对齐 否则上面und_string数据段的长度会影响程序的正确执行 */

reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
	略……

----------------------------

上面代码并不完善,我们要进行一些细节修改:

1. b do_und 函数中的 bl printException 函数有可能超出4K 范围,所以我们需要进行强制跳转到runtime addr 地址(SDRAM执行)
2. 经过反汇编代码分析知道,ldr pc,=do_und 这条指令中是将 do_und的入口地址放置在.data数据段中,数据段默认是放置在start.S .text代码段结尾,如果代码长度超过4K 并且代码是在NAND上时,ldr pc,=do_und将无法获取到do_und的入口,所以我们需要人为将入口地址放置在前面,保证程序能正确获取到入口地址。
3. 数据段 后面的代码要保持 4字节对齐,如:und_string: 中的数据长度如果有变化,会导致代码运行出错。

修改后的 start.S

----------------------------

.text
.global _start

_start:
	b reset  /* vector 0 : reset */
	ldr pc, und_addr /* vector 4 : und */

und_addr:
	.word do_und

do_und:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 4

reset:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	
	

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */

	bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss


	ldr pc, =sdram
sdram:
	bl uart0_init

	/* 故意加入一条未定义指令 */
und_code:
	.word 0xeeadc0de

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
	b halt

----------------------------
SVC异常 (SWI软中断)

一般情况下APP应该运行于usr mode 。usr受限不可访问硬件。APP想要访问硬件,必须切换mode ,通过异常(未定义异常、中断异常可遇不可求),所以我们APP需要调用swi #val 进入svc异常。然后系统根据不同的异常标号,执行相应的操作。

怎么获取异常标号。
进入异常时 lr_svc 保存了被中断模式的下一条指令地址 ,通过lr_svc - 4 即可获取上一条指令的地址:SWI #val ,然后读取指令并对指令进行解析,即可得到VAL值   参考 P86

注释:C函数调用时会用到 r4 - r11 所以在函数调用时会被保存,函数返回时恢复,所以我们可以用r4来获取lr的值  参考第8章内容 ATPCS 寄存器使用规则

--------------------------
start.S代码

.text
.global _start

_start:
	/* 中断向量表 对应工作模式 参考 P82  */
	b reset  						/* vector 0x00 : reset svc  复位 管理模式*/
	ldr	pc, _undefined_instruction 	/* vector 0x04 : und 无定义指令模式 */
	ldr	pc, _software_interrupt  	/* vector 0x08 : swi svc 软中断 管理模式 */
	b reset 	/* vector 0x0C : abt(prefetch)  终止模式 预取终止 */
	b reset  	/* vector 0x10 : abt(data)  终止模式 数据终止 */
	b reset 	/* vector 0x14 : Reserved 保留 */
	b reset  	/* vector 0x18 : IRQ 中断模式 */
	b reset 	/* vector 0x1C : FIQ 快速中断模式 */
	
/* 将函数的入口放置在前面,如果不定义默认放置在.text 段的
 * 末尾 ,start.S 汇编后超过4k 在NAND启动时CPU有可能无法
 * 读取到入口
 */
 
_undefined_instruction:	
	.word undefined_instruction	/* 保存入口地址。运行时地址,会跳转到SDRAM执行 */	
_software_interrupt:
	.word software_interrupt
	
undefined_instruction:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x04的地方执行程序 
	 */

	/* sp_und未设置, 先设置它 */
	ldr sp, =0x34000000
	
	/* 保存现场 */
	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	
	/* 处理und异常 */
	mrs r0, cpsr			// 获取程序状态寄存器 传递给 printState
	bl printState 
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
software_interrupt:
	/* 执行到这里之前:
	 * 1. lr_swi保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_swi保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10011, 进入到swi模式
	 * 4. 跳到0x08的地方执行程序 
	 */

	/* sp_swi未设置, 先设置它 */
	ldr sp, =0x33f00000
	
	/* 保存现场 */
	/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	
	/* 处理swi异常 */
	
	/* C函数调用时会用到 r4 - r11 所以在函数调用时会被保存,函
	 * 数返回时恢复,所以我们可以用r4来获取lr的值  参考第8章内
	 * 容 ATPCS 寄存器使用规则 
	 */
	mov r4, lr	// 将lr 返回地址 保存到 r4 	 sub r0, r4, #4 获取触发SWI异常的指令 ,根据指令获取传递进来的软中断号
	
	mrs r0, cpsr			// 获取程序状态寄存器 传递给 printState
	bl printState 
	
	/* 获取软中断号 */
	sub r0, r4, #4
	bl getSWIVal
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
/* 上面打印字符串长度有可能不是4字节对齐,所以这里要强
 * 制4字节对齐,否则程序可能会出错
 */
 
.align 4
	
reset:
	/* 关闭看门狗 */
	ldr r0,=0x53000000
	ldr r1,=0
	str r1,[r0]
	/* PLL配置---------------------------- */
	/* 设置MPLL,FCLK:HCLK:PCLK=400M:100M:50M */
	
	/* 设置lock time 详见手册P255
	 * LCOKTIME(0x4c000000)=0xFFFFFFFF 
	 */
	ldr r0,=0x4c000000
	ldr r1,=0xFFFFFFFF
	str r1,[r0]
	
	/* 设置HCLK、PCLK 相对于FCLK的分频系数 详见手册P260
	 * CLKDIVN(0x4c000014)=0x05 tFCLK:tHCLK:tPCLK=1:4:8  
	 */
	ldr r0,=0x4c000014
	ldr r1,=0x05
	str r1,[r0]
	
	/* 设置CPU工作于异步模式 
	 * 详见手册P244
	 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0
	
	/* 配置MPLLCON 来设置FCLK频率 详见手册P255~256
	 * 设置MPLLCON(0x4c000004)=(92<<12)|(1<<4)|(1<<0) 
	 * m = MDIV+8 = 92+8 =100
	 * p = PDIV+2 =1+2 =3
	 * s = SDIV = 1
	 * FCLK = 2*m*Fin/(p*2^s)=2*100*12/(3*2^1)=400MHz
	 */
	ldr r0,=0x4c000004
	ldr r1,=(92<<12)|(1<<4)|(1<<0)
	str r1,[r0]
	
	/* 一旦设置PLL,就会锁定 lock time 直到PLL输出稳定 */
	/* PLL配置--END-------------------------- */
	
	
	/* 设置内存:sp 栈 */
	
	/* 分辨是nor/nand启动
	 * 写0到0地址,再读出来;如果读出来是0,表示
	 * 操作是内部RAM,是nand启动;否则就是nor启动
	 * 因为nor flash 不向内存那样能直接读写
	 */
	mov r1,#0
	ldr r0,[r1]	/* 备份0地址数据 */
	str r1,[r1]	/* 将0写入0地址 */
	ldr r2,[r1]	/* 读取0地址数据到r2 */
	cmp r1,r2	/* 判断r1 r2是否相等 */
	ldr sp,=0x40000000+4096	/* 假设nor启动 */
	moveq sp,#4096	/* 如果r1==r2 nand启动 */
	streq r0,[r1]	/* 恢复原来的值 */
	
	/*  整个程序重定位 */
	bl sdram_init				/* 初始化SDRAM 否则下面无法搬运数据 */
	bl copy2sdram				/* .text .rodata .data 段数据拷贝 */
	bl clean_bss				/* .bss 段清除 */
	
	
	/* 复位之后, cpu处于svc模式
	 * 现在, 切换到usr模式
	 */
	mrs r0, cpsr      /* 读出cpsr */
	bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
	msr cpsr, r0

	/* 设置 sp_usr */
	ldr sp, =0x33e00000
	
	
	
	
	bl uart0_init			/* 故障代码中调用了串口打印,所以先初始化串口 */

und_code:
	.word 0xeeadc0de	/*  未定义指令  P86 指令集格式 原代码 0xdeadc0de 有误 参考:http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=20299&highlight=und%D2%EC%B3%A3  */ 
	
	/* 获取当前状态的CPSR */
	mrs r0, cpsr			// 获取程序状态寄存器 传递给 printState
	bl printState

	swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */
	
.align 4
	
	/* 调用main函数 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM 运行 */

	/* 死循环 */
halt:
	b halt

相关 C 函数代码

/* 当前状态打印 P78 */
void printState(unsigned int cpsr)

/* 获取SWI 异常参数 */
void getSWIVal(unsigned int *pSWI)

串口打印相关的函数请参考我前面的文章



/* 当前状态打印 P78 */
void printState(unsigned int cpsr)
{
	puts("cpsr = ");
	printHex(cpsr);
	puts(" ");
	switch(cpsr&0x1F)	// M[4:0]
	{
		case 0x10: puts(" User State !\n\r"); break;
		case 0x11: puts(" FIQ State !\n\r");	break;
		case 0x12: puts(" IRQ State !\n\r");	break;
		case 0x13: puts(" Supervisor State !\n\r"); break;
		case 0x17: puts(" Abort State !\n\r"); break;
		case 0x1B: puts(" Undefined State !\n\r"); break;
		case 0x1F: puts(" System State !\n\r"); break;
		default: puts(" error State !\n\r");break;
	}
}
/* 获取SWI 异常参数 */
void getSWIVal(unsigned int *pSWI)
{
	puts("SWI val = ");
	printHex(*pSWI & ~0xff000000); 
	puts("\n\r");
}



-----------------------------------------------------------------
# gcc Thumb 指令集 编译  不重要可以跳过
  由于Thumb 是压缩的指令集,所以代码体积比较小

.c 代码编译

修改makeflie 添加编译选项 -mthumb
------------
%.o : %.c
    arm-linux-gcc -mthumb -c -o $@ $<
-----------

.s 代码编译

.cdoe 32    表示使用arm 指令集编译
-----------

.text
.global _start
.code 32        //使用ARM
_start:
……
-----------
.code 16    表示使用thumb 指令集编译
-----------
……
.code 16   
thumb_func:   
    bl sdram_init
……
-----------

怎么从 arm state 切换到 thumb state 状态运行

用bx 跳转 如果跳转地址的bit0 =1 则切换到 thumb state 运行

-----------
……
adr r0,thumb_fun
add r0,r0,#1
bx r0
……
-----------

thumb 编译事项

1. 不能用 ldr pc,=main 跳转到main函数,需要:
ldr r0,=main
mov pc,r0

2. cpy2mem 数组 前添加 const static 修饰 防止编译出错。

-----------------------------------------

最近编辑记录 xinxiaoci (2018-05-16 14:37:35)

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn