全部博文(75)
分类: LINUX
2008-06-13 15:09:26
How does arm linux handle exceptions?
Author: Edwin.Rong
Date: 13th, Jun, 2008
— The initialization of vector table
The entry of the initialization of vector table is involved in function trap_init() called by start_kernel().
Start_kernel() ----- > trap_init()
Where, start_kernel() is implemented in “linux/init/main.c”
And Trap_init() is implemented in “linux/arch/arm/kernel/traps.c”
Now, let’s see the implementation of trap_init().
void __init trap_init(void)
{
#if defined(CONFIG_KGDB)
return;
}
void __init early_trap_init(void)
{
#endif
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* 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.
*/
printk("[early_trap_init start] vectors=%x, __vectors_start=%x\n",\
(unsigned long)vectors,(unsigned long)__vectors_start);
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
printk("[early_trap_init end] vectors=%x, __vectors_start=%x\n",\
(unsigned long)vectors,(unsigned long)__vectors_start);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
CONFIG_VECTORS_BASE is defined in “include/linux/autoconf.h” with value 0xffff0000.
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
The above three instructions copy vector table and corresponding disposal funcions to designated position with base address of CONFIG_VECTORS_BASE(0xffff0000).
So, why vector table and disposal fuctions are copy to the vritual address 0xffff0000? If it must be, what preliminaries should be made, for example, some control bits should be set to inform cpu where to fetch instructions while exceptions come up, or a certain section of memory should be mapped to 0xfffff0000, or else, how does the copy operations go on.
Let’s find out the questiones one by one.
May be we can know something from the below paragraphs from << ARM Architecture Reference Manual >>.
“The normal vector at address 0x00000014 and the hign vector at address 0xffff0014 are not normally used and are reserved for future expansion. The reserved vector at address 0x00000014 was used fro an Address Exception vector in earlier versions of the ARM architecture which had a 26-bit address space. See Chapter A8 The 26-bit Architectures for more information.”
------ 2.6 exceptions, on page A2-13.
“Some ARM implementations allow the exception vector locations to be moved from their normal address range 0x00000000-0x
It is IMPLEMENTATION DEFINED whether the high vectors are supported. When they are, a hardwear configuration input selects whether the normal vectors or the high vectors are to be used.
The ARM instruction set does not contain any instructions which can directly change whether normal or high vectors are configured. However, if the standard System Control coprocessor is attached to an ARM processor which supports the high vectors, bit[13] of coprocessor 15 register 1 can be used to switch between using the normal vectors and the high vectors(see Register 1: control register on page B2-13)”
---
From reference [1], it tells us that the functionality is controlled by coprocessor, on the 13th bit of the first register. Page B2-15
vector on page A2-21, this bit is used tio select the location of the exception vector:
0 =
1 = High exception vectors selected(address range 0xffff0000 – 0xffff
On ARM processors that do not support high vectors, this bit reads as 0 and ignores
writes.
Ok, up to now, believe that we have cleared up the two doubts. Subsequently, I’ll introduce you how the high vector bit is set in the linux kernel.
Linux/arch/arm/head.S
As is known to all, stext is the entry of the linux kernel.
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
“add pc, r10, #PROCINFO_INITFUNC“ call funciton “__arm926_setup” in “linux/arch/arm/mm/proc-arm926.S”( How does it implement will be presented in the following portion), in the function __arm926_setup, the high vector bit value is kept in register r0.
“adr lr, __enable_mmu” stores address of sign “__enable_mmu”, thus, it will return to execute __enable_mmu funcion. In __enable_mmu fuction, it will set the high vector bit , that is 13th bit of first register in coprocessor cp15.
Refer to the annotation above, we can known that register r10 keeps the base address of sign “arm926_proc_info”, which is the start address of section “.proc.info.init”, defined in “linux/arm/kernel/vmlinux.lds”, generated after compliation. The definition of the section in this file is as follows:
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
Then, what will be stuffed into this area during the loading phase. Let’s see the below content in file “linux/arch/arm/mm/proc-arm926.S”
.section ".proc.info.init", #alloc, #execinstr
.type __arm926_proc_info,#object
__arm926_proc_info:
.long 0x41069260 @ ARM926EJ-S (v5TEJ)
.long 0xff0ffff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm926_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA
.long cpu_arm926_name
.long arm926_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
.long arm926_cache_fns
.size __arm926_proc_info, . - __arm926_proc_info
#PROCINFO_INITFUNC is the offset of struct proc_info_list against the base address of __arm926_proc_info, which is defined in “linux/include/asm-arm/asm-offsets.h” as:
#define PROCINFO_INITFUNC 16 /* offsetof(struct proc_info_list, __cpu_flush) @ */
Thus, “add pc, r10, #PROCINFO_INITFUNC” equals
“add pc, address of (__arm926_proc_info), +
.type __arm926_setup, #function
__arm926_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
mov r0, #4 @ disable write-back on caches explicitly
mcr p15, 7, r0, c15, c0, 0
#endif
adr r5, arm926_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
#ifdef CONFIG_CPU_CACHE_ROUND_ROBIN
orr r0, r0, #0x4000 @ .1.. .... .... ....
#endif
mov pc, lr-------------------à return to head.S
.size __arm926_setup, . - __arm926_setup
/*
* R
* .RVI ZFRS BLDP WCAM
* .011 0001 ..11 0101
*
*/
.type arm926_crval, #object
arm926_crval:
crval clear=0x
“adr r5, arm926_crval” instruction puts the relative address of arm926_crval” in to register r5.
Ldmian r5, {r5, r6} instruction executes following operations:
R5 = [r5], r6 = [r5+4], namly, r5 = [address of arm926_crval], r6 = [address of arm926_crval + 4].
The contents at address of arm926_crval and the following 4 bytes are:
.word 0x
.word 0x00003135
Macro “crval” is defined as follows:
.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm
So, r5 = 0x
r6 = 0x00003135 = 0b 0000 0000 0000 0000 0011 0001 0011 0101
bic r0, r0, r5 => r0 = 0b xxxx xxxx xxxx xxxx x000 0000 xx00 0000
orr r0, r0, r6 => r0 = 0b xxxx xxxx xxxx xxxx x011 0001 xx11 0101
In register r0, the 13th bit is set 1 after the above operations.
“Mov pc , lr” intruction returns pc back to __enable_mmu function in “linux/arch/arm/kernel/head.S”, for that the relative address of sign “__enable_mmu” is saved into register lr.
.type __enable_mmu, %function
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #CR_A
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp#15 control register
* r13 = *virtual* address to jump to upon completion
*
* other registers depend on the function called upon completion
*/
.align 5
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
In the __enable_mmu function, __turn_mmu_on function is called, in which instruction “mcr p15, 0, r0, c1, c0, 0 @ write control reg” writes the value in register r0 into the first register of coprocessor cp15. We know that register r0 keeps the configuration value to high vector bit stated above.
In this way, the high vector bit is set, eventually.
— The disposals of exceptions
To be continued.
References
[1] <
[2]
|