Chinaunix首页 | 论坛 | 博客
  • 博客访问: 492748
  • 博文数量: 52
  • 博客积分: 5017
  • 博客等级: 大校
  • 技术积分: 707
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-20 11:34
文章分类
文章存档

2011年(1)

2008年(51)

我的朋友

分类: LINUX

2008-10-24 23:06:35

Hign Vector Processing in ARM Linux

Author: Dongas

Data: 08-07-01

 

In Linux 2.6, when an interrupt occurs, the kernel will go to addr 0xffff0000 (virtual address) to handle the interrupt&exception by default.

Why does the kernel go to 0xffff0000 to handle interrupt rather than 0x00000000 which we usually consider as a conventional rule in ARM interface programming without an os ?

 

The answer is It’s up to the ARM hardware and the linux kernel design.

Let’s go through the documentation of << ARM Architecture Reference Manual >> to learn about the Exception processing for ARM, from which ,we can get the following interesting information:

 

What ARM does for Exception Vectors Processing.

When an exception occurs, execution is forced from a fixed memory address corresponding to the type of exception. These fixed addresses are called the exception vectors.

Let’s see the exception vector table. Pls pay attention to the follow differences between normal address and high vector address at the right side in the vector table, from which we can know if the arm is running on high vector mode, the vector address will be 0xffff0000.

Let’g suck more info about high vector:

A2.6.2 Reset

When the Reset input is asserted on the processor, the ARM processor immediately stops execution of the  current instruction. When Reset is de-asserted, the following actions are performed:

…………

if high vectors configured then

PC = 0xFFFF0000

else

PC = 0x00000000

After Reset, the ARM processor begins execution at address 0x00000000 or 0xFFFF0000 in Supervisor mode with interrupts disabled.

 

A2.6.11 High vectors

High vectors were introduced into some implementations of ARMv4 and are required in ARMv6

implementations. High vectors 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.

…………

The ARM instruction set does not contain any instructions that can directly change whether normal or high vectors are configured. However, if the standard System Control coprocessor is attached to an ARM processor that 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 registers on page B3-12).

ß-------Pls pay attention to this line.

 

B3.4.1 Control register

V (bit[13])  This bit is used to select the location of the exception vectors:

0 = Normal exception vectors selected (address range 0x00000000-0x0000001C)

1 = High exception vectors selected (address range 0xFFFF0000-0xFFFF001C).

 

Well, by now we know well abou the high vector such as what’s the high vector, why introduces high vector, as well as how it works. However, the hardware provides the machinism, how does the linux make use of it? At the following we’ll describe it. The most important question we should take into the following go through is when the bit[13] of the Control Register of arm is set as 1 which commands the arm to run on high vector mode.

 

What kernel does for Exception Vectors Processing.

First , command the arm to run on high vector mode which is accomplished by set the bit[13] of the Control Register (coprocessor 15) of arm.

 

Let’s start form the entry of the linux kernel in Linux/arch/arm/head.S

 

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

 

Generally say, “add pc, r10, #PROCINFO_INITFUNC“ will call funciton “__arm926_setup” in “linux/arch/arm/mm/proc-arm926.S” in our platform(arm926ejs architecture). In the function __arm926_setup, the bit[13] of the Control Register value will be set and the value will be stored in register r0. Then this value will be written into cp15 register in __enable_mmu function when turning on the MMU. Why the __enable_mmu function will be called after __arm926_setup is caused that the instruction “adr     lr, __enable_mmu” has already stored the address of function “__enable_mmu” in lr register in advance.

 

How does “add    pc, r10, #PROCINFO_INITFUNC” becomes invoke “b       __arm926_setup” ?

 

From above comments by kernel, we know that register r10 holds the value of the address of  “arm926_proc_info”, which is the start address of section “.proc.info.init”, defined in “linux/arm/kernel/vmlinux.lds”.

__proc_info_begin = .;

   *(.proc.info.init)

  __proc_info_end = .;

 

The following is what the data is in the section .proc.info.init.

/* 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”. It’s exactly the address of “b       __arm926_setup”.

Subsequently, 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 function __enable_mmu in 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 jumps to __enable_mmu function in “linux/arch/arm/kernel/head.S”, which presaved in register lr before entering the function __arm926_setup.

 

       .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 of register r0 into the first register of coprocessor cp15. We know that value of register r0 has already set the bit[13] which indicates that arm running on high vector mode.

 

 

Second , the kernel exception vector predefination and copy it to the high vector address 0xffff0000.

 

The kernel exception vector are predefined in arch/arm/kernel/entry-armv.S .

/* arch/arm/kernel/entry-armv.S */

         .globl        __vectors_start

__vectors_start:

         swi   SYS_ERROR0

         b       vector_und + stubs_offset

         ldr    pc, .LCvswi + stubs_offset

         b       vector_pabt + stubs_offset

         b       vector_dabt + stubs_offset

         b       vector_addrexcptn + stubs_offset

         b       vector_irq + stubs_offset

         b       vector_fiq + stubs_offset

 

         .globl        __vectors_end

__vectors_end:

 

The code copying the predefined exception vectors to the fixed address 0xffff0000 is defined in arch/arm/kernel/traps.c.

/* arch/arm/kernel/traps.c */

void __init trap_init(void)

{

#if   defined(CONFIG_KGDB)

       return;

}

 

void __init early_trap_init(void)

{

#endif

         unsigned long vectors = CONFIG_VECTORS_BASE;  /* CONFIG_VECTORS_BASE is defined as 0xffff0000 in file “include/linux/autoconf.h” */

         extern char __stubs_start[], __stubs_end[];

         extern char __vectors_start[], __vectors_end[];

         /*

          * 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.

          */

         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 “trap_init” will be called by function “start_kernel” during the kernel setup time.

asmlinkage void __init start_kernel(void)

{        

       ……

       unwind_init();

       trap_init();

       rcu_init();

……

}

 

Reference:

1.      << ARM Architecture Reference Manual >>

2.      << How does arm linux handle exceptions>>

阅读(6095) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~