全部博文(52)
分类: LINUX
2008-10-24 23:06:35
Hign Vector Processing in ARM Linux
Author: Dongas
Data:
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:
A
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 0xFFFF
A
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-0x
…………
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.
B
V (bit[13]) This bit is used to select the location of the exception vectors:
0 =
1 = High exception vectors selected (address range 0xFFFF0000-0xFFFF
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), +
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=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 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 0xffff
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>>