zoukankan      html  css  js  c++  java
  • 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
    ");
    	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>]
    "
    		"sp : %08lx  ip : %08lx  fp : %08lx
    ",
    		instruction_pointer (regs),
    		regs->ARM_lr, regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);
    	printf ("r10: %08lx  r9 : %08lx  r8 : %08lx
    ",
    		regs->ARM_r10, regs->ARM_r9, regs->ARM_r8);
    	printf ("r7 : %08lx  r6 : %08lx  r5 : %08lx  r4 : %08lx
    ",
    		regs->ARM_r7, regs->ARM_r6, regs->ARM_r5, regs->ARM_r4);
    	printf ("r3 : %08lx  r2 : %08lx  r1 : %08lx  r0 : %08lx
    ",
    		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
    ",
    		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模式用,但实际代码是存在栈底的,是否是我哪里理解错了

  • 相关阅读:
    C# LINQ和Lambda表达式详解
    .NET面试题2021.7.13
    linux每日命令(11):cat命令
    linux每日命令(10):touch命令
    linux每日命令(9):cp命令
    linux每日命令(8):mv命令
    linux每日命令(7):rmdir命令
    linux每日命令(5):mkdir命令
    进程和线程的区别?什么时候用进程?什么时候用线程?
    八种方式实现跨域请求
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10443614.html
Copyright © 2011-2022 走看看