uboot中的中断macro宏


title: uboot中的中断macro宏
date: 2019/2/26 09:37:12
toc: true
---

uboot中的中断macro宏

引入

以前因为uboot的目的只是引导linux,没有去看关于中断相关的代码,这两天重新回顾看了下Uboot中start.S源码的指令级的详尽解析中关于uboot1.6的分析,看了下中断章节,记录一下.原文已经更新到V1.9,网上很多流传的是1.6的,本文作为对齐章节的补充.这里要先明确不同状态下都有哪些独立的寄存器

mark

内存分配

先来看下内部的sp是怎么设置的,这里的用户栈加上中断栈等为128k,是在后面代码分析得出来的

    /* Set up the stack                         */
stack_setup:
    ldr r0, _TEXT_BASE      /* upper 128 KiB: relocated uboot   */
    sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */
    sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#ifdef CONFIG_USE_IRQ
    sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
    sub sp, r0, #12     /* leave 3 words for abort-stack    */

具体的内存栈
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;

mark

流程概览

先来看下整体的流程的代码

mark

普通中断模式
----------------------------------------------------------------------------
正常程序
↓
中断
↓
硬件切换到中断SP
↓
设置SP_irq到中断栈顶
↓
保存r0-r12,使用stmdb   r8, {sp, lr}^ 保存 用户模式的sp,lr}^
直接保存 spsr 来保存用户的cpsr
str     lr, [r8, #0] 
↓
将中断栈sp传递给函数,调用打印
↓
恢复r0-r14(lr),这个lr是用户函数本身的返回
恢复S_PC 到现在的lr 设置pc=lr 返回用户函数 subs   pc, lr, #4  sub+s表示更新cpsr

其他异常模式
----------------------------------------------------------------------------
正常程序
↓
异常
↓
切换到异常的sp寄存器,设置到用户栈的栈底
↓
保存 当前的lr=用户的pc
保存 用户的cpsr
↓
切换到系统模式
↓
切换到用户模式的sp寄存器,这里的系统模式和用户模式是公用寄存器的
计算用户栈底到r2,从栈底取出上面存的 用户的pc 和 用户的cpsr
↓
当前的sp指向当前用户栈指针
↓
保存当前的lr 当前的lr这里就是用户模式的lr
保存用户的pc cpsr
保存这个用户栈的指针-----实际也是我们保存这些寄存器  r0-r12,lr,pc,cpsr..的地址
↓
将栈sp传递给函数,调用打印

保存现场,保存用户态的寄存器到异常的栈中

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

    .macro  bad_save_user_regs
    sub sp, sp, #S_FRAME_SIZE
    stmia   sp, {r0 - r12}          @ Calling r0-r12
    ldr r2, _armboot_start
    sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
    sub r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack
    ldmia   r2, {r2 - r3}           @ get pc, cpsr
    add r0, sp, #S_FRAME_SIZE       @ restore sp_SVC

    add r5, sp, #S_SP
    mov r1, lr
    stmia   r5, {r0 - r3}           @ save sp_SVC, lr_SVC, pc, cpsr
    mov r0, sp
    .endm

    .macro  irq_save_user_regs
    sub sp, sp, #S_FRAME_SIZE
    stmia   sp, {r0 - r12}          @ Calling r0-r12
    add     r8, sp, #S_PC
    stmdb   r8, {sp, lr}^                   @ Calling SP, LR
    str     lr, [r8, #0]                    @ Save calling PC
    mrs     r6, spsr
    str     r6, [r8, #4]                    @ Save CPSR
    str     r0, [r8, #8]                    @ Save OLD_R0
    mov r0, sp
    .endm

    .macro  irq_restore_user_regs
    ldmia   sp, {r0 - lr}^          @ Calling r0 - lr
    mov r0, r0
    ldr lr, [sp, #S_PC]         @ Get PC
    add sp, sp, #S_FRAME_SIZE
    subs    pc, lr, #4          @ return & move spsr_svc into cpsr
    .endm

    .macro get_bad_stack
    ldr r13, _armboot_start     @ setup our mode stack
    sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
    sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack

    str lr, [r13]           @ save caller lr / spsr
    mrs lr, spsr
    str     lr, [r13, #4]

    mov r13, #MODE_SVC          @ prepare SVC-Mode
    @ msr   spsr_c, r13
    msr spsr, r13
    mov lr, pc
    movs    pc, lr
    .endm

    .macro get_irq_stack            @ setup IRQ stack
    ldr sp, IRQ_STACK_START
    .endm

    .macro get_fiq_stack            @ setup FIQ stack
    ldr sp, FIQ_STACK_START
    .endm

具体的中断函数

/*
 * exception handlers
 */
    .align  5
undefined_instruction:
    get_bad_stack
    bad_save_user_regs
    bl  do_undefined_instruction

    .align  5
software_interrupt:
    get_bad_stack
    bad_save_user_regs
    bl  do_software_interrupt

    .align  5
prefetch_abort:
    get_bad_stack
    bad_save_user_regs
    bl  do_prefetch_abort

    .align  5
data_abort:
    get_bad_stack
    bad_save_user_regs
    bl  do_data_abort

    .align  5
not_used:
    get_bad_stack
    bad_save_user_regs
    bl  do_not_used

#ifdef CONFIG_USE_IRQ

    .align  5
irq:
    get_irq_stack
    irq_save_user_regs
    bl  do_irq
    irq_restore_user_regs

    .align  5
fiq:
    get_fiq_stack
    /* someone ought to write a more effiction fiq_save_user_regs */
    irq_save_user_regs
    bl  do_fiq
    irq_restore_user_regs

#else

    .align  5
irq:
    get_bad_stack
    bad_save_user_regs
    bl  do_irq

    .align  5
fiq:
    get_bad_stack
    bad_save_user_regs
    bl  do_fiq

#endif

普通中断

先来分析普通的中断函数

    .align  5                       ;2^5=32字节对齐
irq:
    get_irq_stack                   ;ldr    sp, IRQ_STACK_START  ;获得irq的栈
    irq_save_user_regs
    bl  do_irq
    irq_restore_user_regs

保存现场

这里使用stmdb r8, {sp, lr}^保存用户态的寄存器

    ldr sp, IRQ_STACK_START  ;获得irq的栈
    
    .macro  irq_save_user_regs
    
    sub sp, sp, #S_FRAME_SIZE
    ;sp 空出S_FRAME_SIZE =72大小
    ;这里的52个字节空间的分配如下:
    @
    @ IRQ stack frame.
    @
    #define S_FRAME_SIZE    72

    #define S_OLD_R0    68
    #define S_PSR       64
    #define S_PC        60
    #define S_LR        56
    #define S_SP        52
    @ 这里没有空余的空间
    #define S_IP        48
    #define S_FP        44
    #define S_R10       40
    #define S_R9        36
    #define S_R8        32
    #define S_R7        28
    #define S_R6        24
    #define S_R5        20
    #define S_R4        16
    #define S_R3        12
    #define S_R2        8
    #define S_R1        4
    #define S_R0        0

    #define MODE_SVC 0x13
    #define I_BIT    0x80

    
    ;到这里的时候,sp=sp(顶)-S_FRAME_SIZE
    
    stmia   sp, {r0 - r12}          @ Calling r0-r12
    ;stmia 是
    ;IA 每次传送后地址加1;
    ;IB 每次传送前地址加1;
    ;DA 每次传送后地址减1;
    ;DB 每次传送前地址减1;
    ;FD 满递减堆栈;
    ;ED 空递减堆栈;
    ;FA 满递增堆栈;
    ;EA 空递增堆栈;
    ;从[sp-S_FRAME_SIZE]的地址开始,存储r0~r12
    
    ;由于上面的指令 sp没有!,所以sp本身最后不变,依然是 sp=sp(顶)-S_FRAME_SIZE
    add     r8, sp, #S_PC
    ;这里将 sp值指向 S_PC 其实就是接着上面的存,这里直接指向的就是没有存过的sp
    ;但是本身的sp依然指向 sp=sp(顶)-S_FRAME_SIZE
    
    stmdb   r8, {sp, lr}^                   @ Calling SP, LR
    ;这里因为有^ 所以访问的是用户模式的寄存器
    ;这个是 DB 每次传送前地址减1; 也就是将  sp用户模式 lr用户模式存储
    ; [r8-1]=sp
    ; [r8-2]=lr
    
    
    ;但是没有使用! 也就是说r8依然是S_PC,也就是保存了S_PC 也就是用户函数的pc
    str     lr, [r8, #0]                    @ Save calling PC
    
    
    mrs     r6, spsr
    str     r6, [r8, #4]                    @ Save CPSR
    ; 把spsr 存到 下一个单元 
    str     r0, [r8, #8]                    @ Save OLD_R0
    ;把r0 存到下一个单元
    
    
    mov r0, sp 
    ; 这里的sp依然是sp=sp(顶)-S_FRAME_SIZE ,也就是我们保存现场的东西的头指针  传递个r0 
    ; 准备给c语言传递参数
    
    .endm

也就是说最终的地址分配是这样的

struct pt_regs {
    long uregs[18];
};

#define ARM_cpsr    uregs[16]
#define ARM_pc      uregs[15]
#define ARM_lr      uregs[14]
#define ARM_sp      uregs[13]
#define ARM_ip      uregs[12]
#define ARM_fp      uregs[11]
#define ARM_r10     uregs[10]
#define ARM_r9      uregs[9]
#define ARM_r8      uregs[8]
#define ARM_r7      uregs[7]
#define ARM_r6      uregs[6]
#define ARM_r5      uregs[5]
#define ARM_r4      uregs[4]
#define ARM_r3      uregs[3]
#define ARM_r2      uregs[2]
#define ARM_r1      uregs[1]
#define ARM_r0      uregs[0]
#define ARM_ORIG_r0 uregs[17]

中断函数打印具体寄存器


void do_irq (struct pt_regs *pt_regs)
{
#if defined (CONFIG_USE_IRQ) && defined (CONFIG_ARCH_INTEGRATOR)
    /* ASSUMED to be a timer interrupt  */
    /* Just clear it - count handled in */
    /* integratorap.c                   */
    *(volatile ulong *)(CFG_TIMERBASE + 0x0C) = 0;
#else
    printf ("interrupt request\n");
    show_regs (pt_regs);
    bad_mode ();                        /* 死循环*/
#endif
}
//--------------------------------------------------------------
void show_regs (struct pt_regs *regs)
{
    unsigned long flags;
    const char *processor_modes[] = {
    "USER_26",  "FIQ_26",   "IRQ_26",   "SVC_26",
    "UK4_26",   "UK5_26",   "UK6_26",   "UK7_26",
    "UK8_26",   "UK9_26",   "UK10_26",  "UK11_26",
    "UK12_26",  "UK13_26",  "UK14_26",  "UK15_26",
    "USER_32",  "FIQ_32",   "IRQ_32",   "SVC_32",
    "UK4_32",   "UK5_32",   "UK6_32",   "ABT_32",
    "UK8_32",   "UK9_32",   "UK10_32",  "UND_32",
    "UK12_32",  "UK13_32",  "UK14_32",  "SYS_32",
    };

    //#define condition_codes(regs) ((regs)->ARM_cpsr & (CC_V_BIT|CC_C_BIT|CC_Z_BIT|CC_N_BIT))
    flags = condition_codes (regs);

    //#define PCMASK        0
    //#define pc_pointer(v) ((v) & ~PCMASK)
    //#define instruction_pointer(regs) (pc_pointer((regs)->ARM_pc))

    printf ("pc : [<%08lx>]    lr : [<%08lx>]\n"
        "sp : %08lx  ip : %08lx  fp : %08lx\n",
        instruction_pointer (regs),
        regs->ARM_lr, regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);
    printf ("r10: %08lx  r9 : %08lx  r8 : %08lx\n",
        regs->ARM_r10, regs->ARM_r9, regs->ARM_r8);
    printf ("r7 : %08lx  r6 : %08lx  r5 : %08lx  r4 : %08lx\n",
        regs->ARM_r7, regs->ARM_r6, regs->ARM_r5, regs->ARM_r4);
    printf ("r3 : %08lx  r2 : %08lx  r1 : %08lx  r0 : %08lx\n",
        regs->ARM_r3, regs->ARM_r2, regs->ARM_r1, regs->ARM_r0);
    printf ("Flags: %c%c%c%c",
        flags & CC_N_BIT ? 'N' : 'n',
        flags & CC_Z_BIT ? 'Z' : 'z',
        flags & CC_C_BIT ? 'C' : 'c', flags & CC_V_BIT ? 'V' : 'v');
    printf ("  IRQs %s  FIQs %s  Mode %s%s\n",
        interrupts_enabled (regs) ? "on" : "off",
        fast_interrupts_enabled (regs) ? "on" : "off",
        processor_modes[processor_mode (regs)],
        thumb_mode (regs) ? " (T)" : "");
}

恢复现场

这里就是恢复r0-r12,返回到lr执行

    .macro  irq_restore_user_regs
    
    ;在irq_save_user_regs 中,sp=sp(顶)-S_FRAME_SIZE
    ;也就是指向了保存现场区域
    
    
    ldmia   sp, {r0 - lr}^          @ Calling r0 - lr
    mov r0, r0
    ldr lr, [sp, #S_PC]         @ Get PC
    add sp, sp, #S_FRAME_SIZE       ;这里恢复sp到顶部

    
    subs    pc, lr, #4          @ return & move spsr_svc into cpsr
    ; 这里返回地址是 lr-4,具体是手册确定的
    ;注意这里有个 sub+s 表示更新cpsr
    .endm

其他的返回地址如下

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 

软中断

软中断和普通中断应该只是寄存器不太一样,所以流程上是一样的

    .align  5
software_interrupt:
    get_bad_stack
    bad_save_user_regs
    bl  do_software_interrupt

空间获取

mark

这里的栈分配有点不一样从代码上看,有着模式切换,切换到用户模式后,将sp和cpsr保存到栈底

    .macro get_bad_stack
    ; 
    ldr r13, _armboot_start     @ setup our mode stack
    sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
    sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack


    ; 这里感觉是一个未知的空间,假设为x 起始
    str lr, [r13]           @ save caller lr / spsr
    ; 先存lr,这个lr是用户的pc调用者
    
    mrs lr, spsr
    str     lr, [r13, #4]
    ;再存 spsr

    mov r13, #MODE_SVC          @ prepare SVC-Mode
    @ msr   spsr_c, r13
    msr spsr, r13
    ;设置了 spsr=MODE_SVC
    
    mov lr, pc              ;把pc值 实际是endm的值给lr
    movs    pc, lr          ;把endm后面的值给pc  总的来说还是执行 endm 的语句,
                            ;也就是这两句话 本身对于程序流程没有什么影响,但是能够更新spsr
                            
    ;注意 接下去切换到了系统模式,sp会是用户模式的sp了                        
                            
    .endm

也就是我们在一段未知的空间,先存储了lr,spsr

关于这里的 mov lr, pc ,movs pc, lr参考这里,用这两条指令可以更新cpsr的标志位。

假设

100:mov lr, pc
104:movs pc,lr
108:xxx
mov lr, pc实际的pc其实等于pc+8即lr=108,后面的movs pc,lr就是跳转到108运行。

关于这里的movs指令解释如下,在ARM指令集E004armproc.chm

任何带 S 位设置的到 R15 的 32-bit 写(MOVS、ORRS、TEQP、LDM...^) 将传送当前模式的 SPSR 到 CPSR 中。

例如,假定我们在 irq_32 模式下: 
MOVS PC, R14
将复制 R14 到 PC,并接着复制 SPSR_IRQ32 到 CPSR。 
这在 USR 模式下不是非常有用因为它没有 SPSR! 

保存现场

这里


.macro get_bad_stack
; 
ldr r13, _armboot_start     @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack



; 这里感觉是一个未知的空间,假设为x 起始
str lr, [r13]           @ save caller lr / spsr
; 先存lr
;ARM处理器相应异常时,会自动完成将当前的PC保存到LR寄存器。
;也就是说 这个lr就是返回用户的地址


mrs lr, spsr
str     lr, [r13, #4]
;再存 用户的cpsr

mov r13, #MODE_SVC          @ prepare SVC-Mode
@ msr   spsr_c, r13
msr spsr, r13
;设置了 spsr=MODE_SVC

mov lr, pc              ;把pc值 实际是endm的值给lr 也就是下面的lablexxx
movs    pc, lr          ;把endm后面的值给pc  总的来说还是执行 endm 的语句,
;也就是这两句话 本身对于程序流程没有什么影响,但是能够更新spsr

;注意 接下去切换到了系统模式,sp会是用户模式的sp了                        

.endm


------------------------------------------------------------------------------
接下去就切换到用户模式了
sp 切换到用户模式的sp
lr这里应该是切换回用户自己原来的lr了,也就是说切换模式并不会赋值lr


lablexxx
.macro  bad_save_user_regs

;这里的sp是用户模式的sp了,也就是在用户区域开辟一个 S_FRAME_SIZE 的空间 


sub sp, sp, #S_FRAME_SIZE
stmia   sp, {r0 - r12}          @ Calling r0-r12
;这里的 r0~r12 是一样的



ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack
; r2指向3word
ldmia   r2, {r2 - r3}           @ get pc, cpsr ,
; 在上面的函数get_bad_stack ,我们在这个地方存储了`lr,spsr`  这个是用户态的 pc cpsr
;IA 每次传送后地址加1



add r0, sp, #S_FRAME_SIZE       @ restore sp_SVC
;sp 回到用户原来的sp尾巴

add r5, sp, #S_SP
;r5 这个区用来存 进入异常前的用户原来的sp

mov r1,   lr

;到这里为止
; r0 =sp, #S_FRAME_SIZE  =进入异常前的用户原来的sp
; r1 =lr    这个lr应该就是用户模式的lr,切换回用户模式后,并没有对他操作过
; r2 =pc    这个是进入异常前的用户原来的pc断点
; r3 =cpsr  用户的cpsr



stmia   r5, {r0 - r3}           @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp
;这里的sp 实际上是用户栈
.endm

附录速记

手动设置cpsr到中断模式 应该是不会跳转到中断向量表,只是模式变了

任何带 S 位设置的到 R15 的 32-bit 写(MOVS、ORRS、TEQP、LDM...^) 将传送当前模式的 SPSR 到 CPSR 中。

疑惑待解

从我画的图中可以看到,作者的意思应该是保留12个字节给bad_stack模式用,但实际代码是存在栈底的,是否是我哪里理解错了

猜你喜欢

转载自www.cnblogs.com/zongzi10010/p/10443614.html