Chinaunix首页 | 论坛 | 博客
  • 博客访问: 641198
  • 博文数量: 75
  • 博客积分: 7001
  • 博客等级: 少将
  • 技术积分: 1465
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-11 17:39
文章分类

全部博文(75)

文章存档

2010年(1)

2009年(25)

2008年(49)

我的朋友

分类: 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-0x0000001C at the bottom of the 32-bit address space, to an alternative address range 0xFFFF0000-0xFFFF001C near the top of the address space. These alternative locations are known as the high vectors.

 

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)”

                                                                               ---2.6.9 High vectors on Page A2-21

 

How is cpu informed to fetch instructions at the designated address while exceptions come up?

From reference [1], it tells us that the functionality is controlled by coprocessor, on the 13th bit of the first register. Page B2-15

 

V(bit[13])   On ARM processors which support the alternative high vectors described in High

                     vector on page A2-21, this bit is used tio select the location of the exception vector:

                     0 = Normal exception vectors selected(address range 0x00000000 – 0x0000001c)

                     1 = High exception vectors selected(address range 0xffff0000 – 0xffff001c).

                     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), +16”. Then, what is it? Yes, it is just what we expect, this instruction put the address of instruction “b  __arm926_setup” in to pc, therefore, function __arm926_setup is invoked.

 

 

       .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=0x00007f3f, mmuset=0x00003135, ucset=0x00001134

 

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 0x00007f3f

.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  = 0x00007f3f   =  0b 0000 0000 0000 0000 0111 1111 0011 1111

   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]

 

 
 
 
 
 
 
 
 
 
 
 
 
 
文件: How does arm linux handle exceptions.rar
大小: 67KB
下载: 下载
阅读(1291) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~