第8课,arm架构的异常与中断

注:以下内容学习于韦东山老师arm裸机第一期视频教程


一.arm架构的异常与中断的处理

   1.1 ARM对异常的处理流程

        1.1.1 软件初始化

            a. 设置中断源,让他可以产生中断
            b. 设置中断优先级
            c. 设置中断总开关
            

        1.1.2 正常执行程序

            对于不同的异常跳去不同的地址执行程序(这些地址一般是排在一起的,叫做异常向量),

           这些地址上只是一条跳转指令,跳去执行其它函数,如下表

        

        1.1.6 这些函数做什么事情?

            a.保存现场(各种寄存器)
            b.调用不同的处理函数
                分辨中断源
                调用不同处理函数
            c.恢复现场
            

    1.2 ARM异常种类

        在uboot 1.1.6中有如下代码

            _start:    b       reset            /* 0地址,复位异常 */
            ldr    pc, _undefined_instruction  /* 4地址,未定义指令 */
            ldr    pc, _software_interrupt        /* 8地址,软中断指令 */
            ldr    pc, _prefetch_abort            /* 12地址,指令预取异常,如果处理器预取的指令不存在,或者该地址不允许当前指令访问 */
            ldr    pc, _data_abort                /* 14地址,数据访问终止,如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问 */
            ldr    pc, _not_used
            ldr    pc, _irq                    /* 24地址,中断异常 */
            ldr    pc, _fiq                    /* 28地址,快中断异常 */

        _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

        中断发生时CPU会强制调到0x18(24)的地方,然后跳转到其它函数进行处理

        

二.CPU模式与状态

    2.1 CPU的State

        2.1.1 ARM State

              在这种状态下,使用arm指令集,每个指令占据4字节(被编译成的机器码占据4个字节)

        2.1.2 Thumb State

              在这种状态下,使用thumb指令集,每个指令占据2字节(被编译成的机器码占据2个字节)

              thumb指令集是arm指令集压缩形式的子集,thumb更加高效,可以减少存储空间,对于嵌入式系统nor,sdram很大,不需要节省这点空间

              Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集

        

        2.1.3 将一个程序使用Thumb编译示例

            对于C程序,在编译时添加-mthumb即可

                修改Makefile如下:

                    objs = uart.o main.o start.o SetTacc.o my_printf.o lib1funcs.o init.o
                    A = test

                    all:$(objs)
                        #arm-linux-ld -Ttext 0 -Tdata 0x800 $^ -o $(A).elf
                        arm-linux-ld -T sdram.lds $^ -o $(A).elf
                        arm-linux-objcopy -O  binary -S $(A).elf $(A).bin
                        arm-linux-objdump -D  $(A).elf > $(A).dis
                        
                    %.o:%.c
                        arm-linux-gcc -mthumb -c -o $@ $<
                        
                    %.o:%.S
                        arm-linux-gcc -c -o $@ $<
                        
                    clean:
                        rm *.o *.elf *.bin

            对于.S程序,需要在代码中指定,在前面添加.code 32表示后续的指令使用arm指令集,在sdram_init前面添加.code 16表示下面的都使用thumb指令集,在前面需要使用bx指令进行状态切换,bx跳转的地址bit0等于1时就会进行状态的切换

            代码如下:

                .text
                .code 32        /* 表示下面的代码使用arm指令集 */
                .global _start

                _start:
                    /* 省略一些初始化代码 */
                    
                    /* 获得thumb_func地址 */
                    adr r0, thumb_func
                    /* 由于地址是4字节对齐,最后一位等于0,因此设置最后一位等于1,切换到thumb指令集 */
                    add r0, r0,#1
                    bx r0

                .code 16        /* 表示下面的代码使用thumb指令集 */
                thumb_func:
                    bl SdramInit

                    mov r0, #0
                    ldr r1, =_start
                    ldr r2, =bss_start
                    sub r2, r2, r1
                    bl Copy2Sdram
                

                    ldr r0, =bss_start
                    ldr r1, =bss_end
                    bl CleanBss
                    
                    //bl main
                    //ldr pc, =main
                    /* 对thumb指令无法直接执行ldr pc, =main */
                    ldr r0, =main
                    mov pc, r0
                halt:
                    b halt

                编译出来可以发现bin文件变小了很多   

                                                                                                                                          

    2.2 CPU的工作模式

        2.2.1 用户模式->正常程序执行的模式

        2.2.2 系统模式

        2.2.3 异常模式

                a.未定义指令模式

                b.管理模式

                c.中止模式

                    c.1 指令预取终止(CPU是流水线操作的,在执行当前指令的时候读取第二条指令,执行第三条指令,第三条指令的读取叫做预取,可能会出错)
                    c.2 数据访问终止

                d.中断模式

                e.快中断模式    

        2.2.4 快速中断模式

        

        其中,除了用户模式的其它六种模式又称为特权模式(Privileged Modes),在这六种模式下可以操作CPSR寄存器任意切换其它模式,

        但是在用户模式下不可以直接进入其它模式

        

    2.3 每种工作模式的差别

        3.3.1 主要是寄存器之间的差别,见下图

        

        3.3.2 未备份寄存器: r0-r7

                 备份寄存器:   r8-14

                 程序计数器:    r15

            

              每个模式下都有自己的r13,r14寄存器sp与lr

            对于快中断模式,有自己的备份寄存器r8-r14,在中断发生时不需要保存r8-r14这些寄存器,因此可以加快处理速度

            在linux系统中并不会使用快中断模式

              

        3.3.3 程序状态寄存器CPSR,SPSR,格式如下图

            CPSR寄存器
              a.M0-M4表示CPU当前处于哪种工作模式,可以读取进行修改(如果处于用户模式则无法修改),见下图
              
              b.bit5-T->表示当前使用的指令集是ARM还是thmub
              c.bit6-F->表示快中断使能
              d.bit7-I->表示中断使能
              e.bit8-bit27->保留位
              f.bit28-bit31->状态位

                    例如 cmp R0,R1 (如果相等会将bit30位置1)

                         beq xxx   (会去判断bit30是不是为1,是1的话则会跳转)     

          SPSR寄存器

                        发生异常时,这个寄存器会来保存被中断模式下的CPSR寄存器

                

    2.4 异常的处理流程

        2.4.1 进入异常时硬件完成:

            1.将下一条指令的地址保存在lr寄存器中,即lr_异常 = 被中断模式的下一条指令的地址(pc + 4 / pc + 8)

            2.将CPSR寄存器保存在异常模式下的SPSR,即SPSR_异常 = CPSR

            3.修改CPSR的模式位(M0-M4),进入异常模式

            4.调到向量表

           

        2.4.2 退出异常时硬件完成:

            1.PC = lr_异常 - offset,具体见下表
            
            例如发生swi异常时,可以PC = lr_svc返回
                发生fiq异常时,可以执行PC = lr_fiq - 4返回
            2. 恢复CPSR的值,CPSR = SPSR_异常
            3. 清中断(如果发生中断)
            

三.und异常模式程序示例

    
	.text
	.global _start

	_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"


	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

		bl uart0_init

		/* 故意加入一条未定义指令 */
	und_code:
		/* 注意,必须是未定义指令,已定义指令见下表 */
		.word 0xdeadc0de  /* 未定义指令 */

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

	halt:
		b halt

三.und异常模式程序的改进

    3.1 问题引入及改进

        a. 在上面的代码中,未定义指令放在了代码重定位之后,如果执行到异常处理函数时,一旦发生未定义异常,程序将会跳转(相对跳转)

           反汇编代码如下,由于使用的的b相对跳转,程序并不会调到30000008的地址,而是会跳转到0x8的地址,一旦前面的程序过多,跳转地址的偏移较高,超过4096时,

           若是NAND启动便会出错,因为RAM只有4K

        30000000 <_start>:
        30000000:       ea00000e        b       30000040 <reset>
        30000004:       eaffffff        b       30000008 <do_und>

        30000008 <do_und>:
 

改进方法:使用绝对跳转,跳转到sdram中去,相关代码如下:

            .text
            .global _start

            _start:
                b reset  /* vector 0 : reset */
                /* 注意,不可以使用ldr pc, =und_addr,这样会将und_addr的值存放在某个内存再去读出来,有可能存放的地方超过4K
                  * 而且,一般放在汇编文件的最后边去,然后去读取,如果汇编文件超过4K也会出错
                ldr pc, und_addr /* vector 4 : und */ /* 从und_addr地址处读值赋给PC,会读到do_und的地址 */

            und_addr:
                .word do_und

            do_und:

            对应反汇编如下:         

            30000000 <_start>:
            30000000:       ea000012        b       30000050 <reset>
            30000004:       e51ff004        ldr     pc, [pc, #-4]   ; 30000008 <und_addr>

            30000008 <und_addr>:
            30000008:       3000000c        andcc   r0, r0, ip

            3000000c <do_und>:
                           

        b.在重定位完之后,后面的代码都可能超过4K,因此要跳到sdram中去执行,代码如下

            ldr pc, =sdram
        sdram:
            bl uart0_init

            bl print1
            /* 故意加入一条未定义指令 */
        und_code:
            .word 0xdeadc0de  /* 未定义指令 */
            bl print2

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

        halt:
            b halt

        c.在定义字符串时有可能字符串的长度不是4字节对齐,因此在后面要调价.align = 4,如下:    

        und_string:
        .string "undefined instruction exception" 
    .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

        bl print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0xdeadc0de  /* 未定义指令 */
        bl print2

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

    halt:
        b halt

四.swi异常模式程序示例

    4.1 swi异常介绍

        在linux系统中,app运行于用户模式,这种模式受限制,不可访问硬件,当应用程序想要访问硬件时,必须切换模式,怎么切换?

        swi就是软件中断,由于用户模式不能够修改cpsr寄存器来切换模式.因此,执行swi指令 + val来发生异常,可以根据val值判断为什么发生异常

    

    4.2 程序示例

        
    .text
    .global _start

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

    und_addr:
        .word do_und

    swi_addr:
        .word do_swi

    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

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

            /* sp_svc未设置, 先设置它 */
            ldr sp, =0x33e00000

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

            /* 复位之后, cpu处于svc模式
             * 现在, 切换到usr模式
             */
            mrs r0, cpsr      /* 读出cpsr */
            bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
            msr cpsr, r0

            /* 设置 sp_usr */
            ldr sp, =0x33f00000

            ldr pc, =sdram
        sdram:
            bl uart0_init

            bl print1
            /* 故意加入一条未定义指令 */
        und_code:
            .word 0xdeadc0de  /* 未定义指令 */
            bl print2

            swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

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

        halt:
            b halt

            

        注意,CPU一上电处于管理模式,切换到用户模式再来执行SWI指令(在别的模式也可以执行,这么做只是为了测试用户模式),修改CPSR寄存器的M4-M0为0b10000即可进入用户模式

        读出swi + val的val值,由于在发生异常时,lr寄存器会保存异常的下一条指令的地址,因此发生swi异常指令的地址就是lr-4,根据地址及机器码格式便可以找出val值

        代码如下:

            
            .text
            .global _start

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

            und_addr:
                .word do_und

            swi_addr:
                .word do_swi

            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

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

                /* sp_svc未设置, 先设置它 */
                ldr sp, =0x33e00000

                /* 保存现场 */
                /* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
                /* lr是异常处理完后的返回地址, 也要保存 */
                stmdb sp!, {r0-r12, lr}  

                /* 由于先调用了printException函数,会使用r0-r3来传递参数,并且使用完不会恢复,因此不能用r0-r3保存lr寄存器的值,r4-r8也会被使用,但是使用完后寄存器的值被恢复原来的值 */
                mov r4, lr
                
                /* 处理swi异常 */
                mrs r0, cpsr
                ldr r1, =swi_string
                bl printException

                sub r0, r4, #4
                bl printSWIVal
                
                /* 恢复现场 */
                ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
                
            swi_string:
                .string "swi 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

                /* 复位之后, cpu处于svc模式
                 * 现在, 切换到usr模式
                 */
                mrs r0, cpsr      /* 读出cpsr */
                bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
                msr cpsr, r0

                /* 设置 sp_usr */
                ldr sp, =0x33f00000

                ldr pc, =sdram
            sdram:
                bl uart0_init

                bl print1
                /* 故意加入一条未定义指令 */
            und_code:
                .word 0xdeadc0de  /* 未定义指令 */
                bl print2

                swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

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

            halt:
                b halt
                
            printSWIVal函数如下,其中swi指令格式见下图,取出低24位即可:
                
            void printSWIVal(unsigned int *pSWI)
            {
                puts("SWI val = ");
                printHex(*pSWI & ~0xff000000);
                puts("\n\r");
            }
©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值