从零开始之驱动发开、linux驱动(九、linux的异常处理流程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82528933

在学习裸机的时候我们知道,异常的触发分为下面几个过程:

1.设置好异常向量表(把异常向量表放在异常向量的位置)

2.写异常处理函数

3.执行异常处理

下面列出简单的代码实现

.text
.global _start

.global __reset_exception
.global __undefined_instruction
.global __software_interrupt
.global __prefetch_abort
.global __data_abort	
.global __not_used	
.global __irq	
.global __fiq

_start:
	b __reset_exception
	b __undefined_instruction
	b __software_interrupt
	b __prefetch_abort
	b __data_abort	
	b __not_used	
	b __irq	
	b __fiq

.align 4

__reset_exception:

	/* 开发板制锁*/
	ldr r0, = 0xe010e81c
	ldr r1, = 0x301
	str r1, [r0]

	/* 关闭看门狗 */
	ldr r0, = 0xe2700000
	mov r1, #0
	str r1, [r0]

	/* 设置SVC栈地址 */
	ldr sp, = 0xd0037d80

	/* 设置0x30000000为异常向量表的起始地址 */
	ldr r0, = _start
	mcr p15, 0, r0, c12, c0, 0

	/* 启动icache */
	bl enable_icache
	
	/* 设置时钟 */
	bl init_clock

	/* 初始化DDR */
	bl sdram_init

	/* 创建页表 */
	bl create_page_table
	
	/* 使能mmu */
	bl enable_mmu

    /* 拷贝代码到ddr的0x30000000位置 */
	bl copy2sdram    

    /* 清bss段 */
	bl clear_bss
	
	/* 从片内iram跳转到ddr */
	ldr pc, = sdram
sdram:
	bl uart0_init

und_code:
	.word 0xdeadc0de        /* 该指令在指令表中不存在,所以会译码错误 */

	swi 0x33                /* 触发软中断 */

	/* 开irq中断 */
	mrs r0, cpsr
	bic r0, r0, #1<<7
	msr cpsr, r0

	ldr sp, = 0x45000000
	
	/* 调用main函数 */
	bl main

	b .

enable_icache:
	mrc p15, 0, r1, c1, c0, 0 	@Read Control Regist
	orr r1, r1,#(1<<12)			@enable instructon cache
	//bic r1, r1,#(1<<12)
	mcr p15, 0, r1, c1, c0, 0
	mov pc, lr

enable_mmu:
	/* translation table base write cp5 */
	ldr r1, = 0x4f000000
	mrc p15, 0, r2, c2, c0, 0 	@ Read Translation Table Base Register
	orr r2, r2, r1
	mcr p15, 0, r2, c2, c0, 0 	@ Write Translation Table Base Register

	/* set domain 0xffffffff */
	ldr r0, = 0xffffffff
	mcr p15, 0, r0, c3, c0, 0 @ Read Domain Access Control Register


	/* enable i/d canche */
	mrc p15, 0, r1, c1, c0, 0 	@Read Control Regist
	orr r1, r1,#(1<<12)			@enable instructon cache
	orr r1, r1,#(1<<2)			@enable data cache
	orr r1, r1,#(1<<0)			@enable mmu
	mcr p15, 0, r1, c1, c0, 0   @write Control Regist
	mov pc, lr

/* 未定义指令异常的处理 */
__undefined_instruction:
	/*	执行到这里之前: 
	 *	1. lr 保存被异常的下一条即将执行的指令的地址
	 *  2. SPSR保存被异常大断的模式的CPSR
	 *  3. CPSR中的m4-m0被设置为11011,进入__undefined模式
	 *	4. 调到0xd0037404的地方执行程序
	 */
	/* undefine的sp未设置,先设置它 */
	ldr sp, = 0x31000000
	

	/* 在undef异常可能会修改r0-r12,先保存 */
	stmfd sp!, {r0-r12, lr}
	
	mrs r0, cpsr
	and r0, r0, #0x1f
	bl undefine_instruct_exception

	@ldmfd sp!, {r0-r12, pc}^
	ldmfd sp!, {r0-r12, lr}
	movs pc,lr
.align 4

/* 软中断异常的处理 */
__software_interrupt:
	/*	执行到这里之前: 
	 *	1. lr 保存被异常的下一条即将执行的指令的地址
	 *  2. SPSR保存被异常大断的模式的CPSR
	 *  3. CPSR中的m4-m0被设置为11011,进入__undefined模式
	 *	4. 调到0xd0037404的地方执行程序
	 */
	/* undefine的sp未设置,先设置它 */
	ldr sp, = 0x32000000
	

	/* 在undef异常可能会修改r0-r12,先保存 */
	stmfd sp!, {r0-r12, lr}
	
	mrs r0, cpsr
	and r0, r0, #0x1f
	bl software_interrupt_exception
	
	ldmfd sp!, {r0-r12, pc}^	
.align 4

__prefetch_abort:	
.align 4

__data_abort:
.align 4

__not_used:
.align 4

/* 中断的处理 */
__irq:
	ldr sp, = 0x33000000
	stmfd sp!, {r0-r12, lr}
	bl irq_excepion
	ldmfd sp!, {r0-r12, lr}
	subs pc,lr,#4 
	
.align 4

__fiq:
.align 4

上面的代码主要说明这么几点:

1.默认启动ARM核CPU的异常向量表的起始地址在0x0位置,但硬件厂商都自己做了扩充,提供了各种启动机制等等,它们的代码也就放置在0地址处开始,而且是出厂就已经烧录好的,通常只做了中断的跳转,即发生中断断后,在0地址+irq偏移执行跳转,先让程序跳转到0x30037400+irq偏移的位置,然后让用户自己在0x30037400+irq偏移的位置,放置中断处理函数的地址。

2.上面的例子做了未定义指令异常和软中断异常,因为三星出厂并没有处理这两个异常,所以我们要用,就要自己重新定义异常向量表的地址了。我上面程序的链接脚本.text段地址在0x3000,0000,所以通过设置cp15的c12寄存器的值为0x3000,0000中,在以后发生异常就会直接到0x3000,0000位置执行相应后续的处理。

3.细心的朋友注意到了irq中断处理完后,返回地址是先减4,后返回,而未定义指令异常,软中断等则没有,这是什么原因呢。(分析下面这位朋友的博文就清楚了)

https://blog.csdn.net/Setul/article/details/53992920

linux的异常处理其实也和裸机中的流程一样,只不过linux要对所有的异常都进行具体分析处理。

先看一下异常向量表在启动过程的流程所在的位置

head_common.S
    b start_kernel(void);
        ....
        local_irq_disable();    /* 关中断 */
        ....
        setup_arch(&command_line);
            paging_init(mdesc);
                devicemaps_init(mdesc);
                    early_trap_init(vectors);    /* 设置异常向量表 */

        ....
        trap_init();            /* 空函数 */
        ....
        early_irq_init();       /* 初始化irq_desc数组 */
        init_IRQ();             /* 芯片相关的中断的初始化
        ....
        local_irq_enable();    /* 开中断 */


开关中断比较好理解,比较异常向量表还没设置,所以此时发生中断肯定就跑飞了,所以要关中断

1.异常向量表的建立


void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
    /* 异常向量表 exception vectors的基址 */
	unsigned long vectors = (unsigned long)vectors_base;
	extern char __stubs_start[], __stubs_end[];        /* 汇编和链接脚本定义的各异常处理的核心代码 */
	extern char __vectors_start[], __vectors_end[];    /* 汇编中定义的异常向量表的标号 */
	unsigned i;

	vectors_page = vectors_base;

	/*
	 * Poison the vectors page with an undefined instruction.  This
	 * instruction is chosen to be undefined for both ARM and Thumb
	 * ISAs.  The Thumb version is an undefined instruction with a
	 * branch back to the undefined instruction.
     * 上面官方注释写的很明白,即用一条未定义指令初始化异常向量所在这一页(4K)
	 */
	for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
		((u32 *)vectors_base)[i] = 0xe7fddef1;

	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
    /* 拷贝异常向量表到vectors起始地址 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    /* 拷贝异常处理核心代码到异常向量表base+0x1000位置  */
	memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

    /* 从用户空间可以访问的内核段提供的用户代码拷贝到固定位置 */
	kuser_init(vectors_base);

    /* 刷异常向量这两页的,如果icache中存有相关代码的话(因为刚设置,所以要用新内存的,不能用老的icache里面的) */
	flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
	modify_domain(DOMAIN_USER, DOMAIN_CLIENT);    /* 清理权限管理 */
#else /* ifndef CONFIG_CPU_V7M */
	/*
	 * on V7-M there is no need to copy the vector table to a dedicated
	 * memory area. The address is configurable and so a table in the kernel
	 * image can be used.
	 */
#endif
}

虽然我们知道上面的vectors_base是0xffff,0000.但还是看一下代码

可以看到其实这里是这里只是早期的申请2页的空间而已。并不一定是在虚拟地址0xffff,0000

看一下真正是怎么放到高端地址去的。

static void __init devicemaps_init(const struct machine_desc *mdesc)
{
	struct map_desc map;
	unsigned long addr;
	void *vectors;

	/*
	 * Allocate the vector page early.
	 */
	vectors = early_alloc(PAGE_SIZE * 2);    /* 申请2*4k地址的空间 */

	early_trap_init(vectors);                /* 在这块空间填充异常相关的地址,代码等 */

	for (addr = VMALLOC_START; addr; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));

	/*
	 * Map the kernel if it is XIP.
	 * It is always first in the modulearea.
	 */
#ifdef CONFIG_XIP_KERNEL
	map.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);
	map.virtual = MODULES_VADDR;
	map.length = ((unsigned long)_etext - map.virtual + ~SECTION_MASK) & SECTION_MASK;
	map.type = MT_ROM;
	create_mapping(&map);
#endif

	/*
	 * Map the cache flushing regions.
	 */
#ifdef FLUSH_BASE
	map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS);
	map.virtual = FLUSH_BASE;
	map.length = SZ_1M;
	map.type = MT_CACHECLEAN;
	create_mapping(&map);
#endif
#ifdef FLUSH_BASE_MINICACHE
	map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);
	map.virtual = FLUSH_BASE_MINICACHE;
	map.length = SZ_1M;
	map.type = MT_MINICLEAN;
	create_mapping(&map);
#endif

	/*
	 * Create a mapping for the machine vectors at the high-vectors
	 * location (0xffff0000).  If we aren't using high-vectors, also
	 * create a mapping at the low-vectors virtual address.
	 */
	map.pfn = __phys_to_pfn(virt_to_phys(vectors));    /* 通过申请的虚拟地址,找打物理地址,再用物理地址确定是那个页 */
	map.virtual = 0xffff0000;       /* 将要映射的虚拟地址(默认高地址映射) */
	map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
	map.type = MT_HIGH_VECTORS;
#else
	map.type = MT_LOW_VECTORS;
#endif
	create_mapping(&map);        /*  物理地址到虚拟地址的应设 */

	if (!vectors_high()) {       /* 判断如果是低地址映射,则覆盖掉前面高地址的映射 */
		map.virtual = 0;
		map.length = PAGE_SIZE * 2;
		map.type = MT_LOW_VECTORS;
		create_mapping(&map);
	}

	/* Now create a kernel read-only mapping */
	map.pfn += 1;
	map.virtual = 0xffff0000 + PAGE_SIZE;
	map.length = PAGE_SIZE;
	map.type = MT_LOW_VECTORS;
	create_mapping(&map);

	/*
	 * Ask the machine support to map in the statically mapped devices.
	 */
	if (mdesc->map_io)
		mdesc->map_io();
	else
		debug_ll_io_init();
	fill_pmd_gaps();

	/* Reserve fixed i/o space in VMALLOC region */
	pci_reserve_io();

	/*
	 * Finally flush the caches and tlb to ensure that we're in a
	 * consistent state wrt the writebuffer.  This also ensures that
	 * any write-allocated cache lines in the vector page are written
	 * back.  After this point, we can start to touch devices again.
	 */
	local_flush_tlb_all();
	flush_cache_all();
}

通过上面可以发现,起始并不是直接到0xffff,0000复制异常的内容。

而是通过下面三步实现的:

1.系统先申请一块内存,用异常向量表填充这个内存区域。

2.通过虚拟地址找到物理地址,并把这块内存区域再次映射到0xffff,0000区域

3.之后检查,如果不是高端映射,则映射到0地址(页表会覆盖掉前面的高端映射)

其中检查是否是高端映射是通过检查cp15的c1寄存器的V位来判断的

#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high()	(get_cr() & CR_V)
#else
#define vectors_high()	(0)
#endif

#ifdef CONFIG_CPU_CP15

extern unsigned long cr_alignment;	/* defined in entry-armv.S */

static inline unsigned long get_cr(void)
{
	unsigned long val;
	asm("mrc p15, 0, %0, c1, c0, 0	@ get CR" : "=r" (val) : : "cc");
	return val;
}

其中cp15的c1寄存器是在proc-v7.S中设置的.

arch/arm/mm/proc-v7.S

2.异常向量表的内容

arch/arm/kernel/entry-armv.S

对比手册,位置是一样的,唯一就是swi异常是直接跳到0xFFFF,1000位置(即我们前面搬移向量表时也搬移了__stubs_start开始的代码段)

__vectors_start:
	W(b)	vector_rst
	W(b)	vector_und
	W(ldr)	pc, __vectors_start + 0x1000
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq
	W(b)	vector_fiq

	.data

	.globl	cr_alignment
cr_alignment:
	.space	4

#ifdef CONFIG_MULTI_IRQ_HANDLER
	.globl	handle_arch_irq
handle_arch_irq:
	.space	4
#endif

异常的初步跳转分析

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff1000 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not exceed
 * a page size.
 *
 * Common stub entry macro:   其中中断可以在用户模式,也可以在系统模式进入
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
	.macro	vector_stub, name, mode, correction=0    /* 汇编中定义的宏 */
	.align	5

vector_\name:        /* 宏的起始位置代码 */
	.if \correction
	sub	lr, lr, #\correction        /* 计算返回地址 */
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr    先把lr和r0入栈
	mrs	lr, spsr            /* 读出spsr */
	str	lr, [sp, #8]		@ save spsr    cpsr也保存到栈中

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.强制进入SVC模式
	@
	mrs	r0, cpsr        
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f             /* lr在前面存的是spsr的值,这里只保留低四位 */   
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp                    /* 栈地址存在r0中传给下一个函数 */
 ARM(	ldr	lr, [pc, lr, lsl #2]	)        /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址  */
	movs	pc, lr			@ branch to handler in SVC mode 调到表中执行处理异常
ENDPROC(vector_\name)        /* 宏的结束位置代码 */

	.align	2
	@ handler addresses follow this label
1:
	.endm

	.section .stubs, "ax", %progbits
__stubs_start:        /* 异常处理,也即将被搬运到0xFFFF,1000开始位置 */
	@ This must be the first word 这句注释说的很明白,因为软中断指令是W(ldr)	pc, __vectors_start + 0x1000,
        发生后是直接跳到0xFFFF,1000处,而__stubs_start被搬移到0xFFFF,1000位置,所以vector_swi    必须在first word
    /* 这条汇编的入口在其它地方定义,即发生软中断(系统调用)后,先到0xFFFF,0008,再跳到0xFFFF,1000
     * ,之后再次跳到vector_swi    标号处执行软中断异常
     */
	.word	vector_swi    

vector_rst:
 ARM(	swi	SYS_ERROR0	)        /* 复位异常,我们这里还是执行软中断异常 */
 THUMB(	svc	#0		)
 THUMB(	nop			)
	b	vector_und

/*
 * Interrupt dispatcher    irq中断的处理,
 */
	vector_stub	irq, IRQ_MODE, 4        /* 这句用最前面的宏替换 */

	.long	__irq_usr			@  0  (USR_26 / USR_32)
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)
	.long	__irq_invalid			@  4
	.long	__irq_invalid			@  5
	.long	__irq_invalid			@  6
	.long	__irq_invalid			@  7
	.long	__irq_invalid			@  8
	.long	__irq_invalid			@  9
	.long	__irq_invalid			@  a
	.long	__irq_invalid			@  b
	.long	__irq_invalid			@  c
	.long	__irq_invalid			@  d
	.long	__irq_invalid			@  e
	.long	__irq_invalid			@  f

/*
 * Data abort dispatcher    数据异常
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
	vector_stub	dabt, ABT_MODE, 8        /* 这句用最前面的宏替换 */

	.long	__dabt_usr			@  0  (USR_26 / USR_32)
	.long	__dabt_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__dabt_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__dabt_svc			@  3  (SVC_26 / SVC_32)
	.long	__dabt_invalid			@  4
	.long	__dabt_invalid			@  5
	.long	__dabt_invalid			@  6
	.long	__dabt_invalid			@  7
	.long	__dabt_invalid			@  8
	.long	__dabt_invalid			@  9
	.long	__dabt_invalid			@  a
	.long	__dabt_invalid			@  b
	.long	__dabt_invalid			@  c
	.long	__dabt_invalid			@  d
	.long	__dabt_invalid			@  e
	.long	__dabt_invalid			@  f

/*
 * Prefetch abort dispatcher        预取址异常
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
	vector_stub	pabt, ABT_MODE, 4        /* 这句用最前面的宏替换 */

	.long	__pabt_usr			@  0 (USR_26 / USR_32)
	.long	__pabt_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__pabt_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__pabt_svc			@  3 (SVC_26 / SVC_32)
	.long	__pabt_invalid			@  4
	.long	__pabt_invalid			@  5
	.long	__pabt_invalid			@  6
	.long	__pabt_invalid			@  7
	.long	__pabt_invalid			@  8
	.long	__pabt_invalid			@  9
	.long	__pabt_invalid			@  a
	.long	__pabt_invalid			@  b
	.long	__pabt_invalid			@  c
	.long	__pabt_invalid			@  d
	.long	__pabt_invalid			@  e
	.long	__pabt_invalid			@  f

/*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
	vector_stub	und, UND_MODE       /* 未定义指令异常 */

	.long	__und_usr			@  0 (USR_26 / USR_32)
	.long	__und_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__und_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__und_svc			@  3 (SVC_26 / SVC_32)
	.long	__und_invalid			@  4
	.long	__und_invalid			@  5
	.long	__und_invalid			@  6
	.long	__und_invalid			@  7
	.long	__und_invalid			@  8
	.long	__und_invalid			@  9
	.long	__und_invalid			@  a
	.long	__und_invalid			@  b
	.long	__und_invalid			@  c
	.long	__und_invalid			@  d
	.long	__und_invalid			@  e
	.long	__und_invalid			@  f

	.align	5

/*=============================================================================
 * Address exception handler
 *-----------------------------------------------------------------------------
 * These aren't too critical.
 * (they're not supposed to happen, and won't happen in 32-bit data mode).
 */

vector_addrexcptn:            /* 没使用,进入这里直接死循环 */
	b	vector_addrexcptn

/*=============================================================================
 * Undefined FIQs    fiq内核没实现处理,需要我们自己实现代码,觉大多数情况下我们也不会用到
 *-----------------------------------------------------------------------------
 * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
 * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
 * Basically to switch modes, we *HAVE* to clobber one register...  brain
 * damage alert!  I don't think that we can execute any code in here in any
 * other mode than FIQ...  Ok you can switch to another mode, but you can't
 * get out of that mode without clobbering one register.
 */
vector_fiq:
	subs	pc, lr, #4        /* 到fiq后,直接返回,没有做任何处理 */

	.globl	vector_fiq_offset
	.equ	vector_fiq_offset, vector_fiq   /* #define vector_fiq_offset vector_fiq */

	.section .vectors, "ax", %progbits
__vectors_start:        /* 异常向量表 */
	W(b)	vector_rst                        /* 复位异常 */
	W(b)	vector_und                        /* 未定义指令异常 */
	W(ldr)	pc, __vectors_start + 0x1000      /* 软中断swi异常 */
	W(b)	vector_pabt                       /* 预取址异常*/
	W(b)	vector_dabt                       /* 数据异常(比如访问了不能访问的地址) */
	W(b)	vector_addrexcptn                 /* 保留的,没使用,进入直接死循环 */
	W(b)	vector_irq                        /* 中断 */
	W(b)	vector_fiq                        /* 快速中断 */

	.data

	.globl	cr_alignment
cr_alignment:
	.space	4

#ifdef CONFIG_MULTI_IRQ_HANDLER
	.globl	handle_arch_irq
handle_arch_irq:
	.space	4
#endif

上面就是内核的异常处理比较底层的代码了。

其中复位异常和swi都是用swi直接跳转到vector_swi,在vector_swi里面实现的。

而und,irq,pabt,dabt都是用宏+一系列跳转实现的,fiq未实现。

下面用我们最常用的irq异常,拆开上面的宏来分析

/**************************下面是宏*******************************/
	.macro	vector_stub, name, mode, correction=0    /* 汇编中定义的宏 */
	.align	5

vector_\name:        /* 宏的起始位置代码 */
	.if \correction
	sub	lr, lr, #\correction        /* 计算返回地址 */
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr    先把lr和r0入栈
	mrs	lr, spsr            /* 读出spsr */
	str	lr, [sp, #8]		@ save spsr    cpsr也保存到栈中

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.强制进入SVC模式
	@
	mrs	r0, cpsr        
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f             /* lr在前面存的是spsr的值,这里只保留低四位 */   
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp                    /* 栈地址存在r0中传给下一个函数 */
 ARM(	ldr	lr, [pc, lr, lsl #2]	)        /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址  */
	movs	pc, lr			@ branch to handler in SVC mode 调到表中执行处理异常
ENDPROC(vector_\name)        /* 宏的结束位置代码 */
/***************************************************************************/

/*
 * Interrupt dispatcher   irq异常
 */
	vector_stub	irq, IRQ_MODE, 4        /* 用这句来替换掉上面的宏 */

	.long	__irq_usr			@  0  (USR_26 / USR_32)
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)
	.long	__irq_invalid			@  4
	.long	__irq_invalid			@  5
	.long	__irq_invalid			@  6
	.long	__irq_invalid			@  7
	.long	__irq_invalid			@  8
	.long	__irq_invalid			@  9
	.long	__irq_invalid			@  a
	.long	__irq_invalid			@  b
	.long	__irq_invalid			@  c
	.long	__irq_invalid			@  d
	.long	__irq_invalid			@  e
	.long	__irq_invalid			@  f



贴出代码前,先把宏中的内容说明一下

.macro	vector_stub, name, mode, correction=0  
vector_stub	irq, IRQ_MODE, 4

irq替换掉name

mode为IRQ_MODE(用宏定义,如下表的红框的低四位所示,irq为2)

4替换掉correction   (如果不传这个参数,默认值是0)

其中为什么correction在每个模式下不同,如下表的return code。详细原因查看前面贴的那个链接。

这里写图片描述

替换后

/*
 * Interrupt dispatcher   irq异常
 */

vector_irq:        /* 宏的起始位置代码 */
	
	sub	lr, lr, #4        /* 计算返回地址 */
	
	stmia	sp, {r0, lr}		@ save r0, lr    先把lr和r0入栈
	mrs	lr, spsr                /* 读出spsr */
	str	lr, [sp, #8]		@ save spsr    cpsr也保存到栈中

	mrs	r0, cpsr        
	eor	r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)    /* 强制进入SVC模式 */
	msr	spsr_cxsf, r0

	and	lr, lr, #0x0f             /* lr在前面存的是spsr的值,这里只保留低四位(进入异常前的模式) */   
	mov	r0, sp                    /* 栈地址存在r0中传给下一个函数 */
 ARM(	ldr	lr, [pc, lr, lsl #2]	)        /* 以pc为基址,spsr的低四位*4(左移2bit)为偏移 ,找到对应的表地址  */
	movs	pc, lr			@ branch to handler in SVC mode 调到表中执行处理异常
/* pc = 当前指令 + 8,所以执行ldr	lr, [pc, lr, lsl #2]时,pc指向 __irq_usr */
	.long	__irq_usr			@  0  (USR_26 / USR_32)    表示在用户态时发生的中断
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)    表示在内核态时发生的中断
	.long	__irq_invalid			@  4
	.long	__irq_invalid			@  5
	.long	__irq_invalid			@  6
	.long	__irq_invalid			@  7
	.long	__irq_invalid			@  8
	.long	__irq_invalid			@  9
	.long	__irq_invalid			@  a
	.long	__irq_invalid			@  b
	.long	__irq_invalid			@  c
	.long	__irq_invalid			@  d
	.long	__irq_invalid			@  e
	.long	__irq_invalid			@  f



看了上面的注释应该差不多理解了。

其中SPSR的低四位为模式位。

进入异常模式前要么是在user模式,要么是在supervisor模式,如果是其它模式则统一做无效处理。(因为其它异常也是在svc模式处理【前面代码强制,所有的异常处理在svc模式】)

所以无论是irq,und,dabr,pabt他们均只实现了pc位置0偏移【用户模式进入异常】和3<<2的偏移【管理模式进入异常】,以及无效模式处理程序。

如下简单列出的所有汇编代码的标号,就是armv7内核的所有的异常处理的有效代码标号:

__pabt_invalid:
...

__dabt_invalid:
...

__irq_invalid:
...

__und_invalid:
...


__dabt_svc:
...

__irq_svc:
...

__und_svc:
...

__pabt_svc:
...

__dabt_usr:
...

__irq_usr:
...

__und_usr:
...

__pabt_usr:
...

/* 上面的所有都在arch/arm/kernel/entry-armv.S中定义 */


/*下面这个在arch/arm/kernel/entry-common.S  */

ENTRY(vector_swi)        

因为太多,这里就不一一分析了。一下一节我以最常用到的irq异常的处理来分析。

后面再升入学习一段后,再以swi的来分析,基本就可以算掌握arm的异常处理了。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82528933
今日推荐