您的位置:首页 > 运维架构 > Linux

ARM Linux源码分析之内核和异常的初始化过程

2015-03-22 13:45 489 查看
中断系统的初始化时由start_kernel调用setup_arch进行平台体系(处理器芯片)相关的初始化,然后复制中断向量表到内存中并对irq进行初始化:

/* init/main.c */
asmlinkage void __init start_kernel(void)
{
    ……
   [1]setup_arch(&command_line);
    ……
   [2]trap_init();
    ……
   [3]init_IRQ();
    ……
}
代码[1]:进入setup_arch()函数中并调用了early_irq_init()函数

代码[2]:复制中断向量表到内存地址CONFIG_VECTORS_BASE

代码[3]: init_IRQ对irq进行初始化时,又调用了init_arch_irq对于具体平台体系的中断进行初始化。而init_arch_irq是在start_kernel里调用setup_arch函数设定的

1.分析setch_arch()函数中调用的early_irq_init()函数:

void __init setup_arch(char **cmdline_p)
{#ifdef CONFIG_MULTI_IRQ_HANDLER
	handle_arch_irq = mdesc->handle_irq;
#endif
	......
	early_trap_init();

	if (mdesc->init_early)
		mdesc->init_early();
}
---------------------------------------------------分割线
int __init early_irq_init(void)
{
   struct irq_desc *desc;
   int count;
   int i;
   init_irq_default_affinity();
   printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
   desc = irq_desc;
   count = ARRAY_SIZE(irq_desc);
 
   for (i = 0; i < count; i++) {
      desc[i].irq = i;
      alloc_desc_masks(&desc[i], 0, true);
      init_desc_masks(&desc[i]);
      desc[i].kstat_irqs = kstat_irqs_all[i];
   }
   return arch_early_irq_init();
}
该函数主要工作即为初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素,它主要设置数组中每一个成员的中断号,使得数组中每一个元素的kstat_irqs字段(irq
stats per cpu),指向定义的二维数组中的对应的行。alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。

2. trap_init()拷贝中断向量表到高地址,并让cpu发生中断时在高端寻址

调用trap_init()前,先列出相关的中断向量表:

/*_ arch/arm/kernel/entry-armv.S */中断向量表
_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:


2.1进行中断向量表的拷贝:

/* linux/arch/arm/kernel/traps.c */
void __init trap_init(void)
{
       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.
        */
       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);
 
       /*
        * 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);


2.2如何保证cpu在中断的时候高端寻址

CONFIG_VECTORS_BASE是一个宏,用来获取ARM异常向量的地址;该宏在/arch/arm/configs/s3c2410_defconfig中定义:

<span style="white-space:pre">		</span>CONFIG_VECTORS_BASE=0xffff0000
另外在include/arch/asm-arm/system.h也有相关代码保证CPU在高端寻址。

/* include/arch/asm-arm/system.h*/
#define CPU_ARCH_ARMv5         4
 
#define CR_V      (1 << 13)      /* Vectors relocated to 0xffff0000    */
 
extern unsigned long cr_no_alignment;      /* defined in entry-armv.S */
extern unsigned long cr_alignment;     	   /* defined in entry-armv.S */
 
#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high()  (cr_alignment & CR_V)
#else
#define vectors_high()  (0)
#endif
-------------------------------------------------------------------分割线/*arch/arm/kernel/entry-armv.S—找到cr_alignment的定义*/
       .globl      cr_alignment
       .globl      cr_no_alignment
cr_alignment:
       .space    4
cr_no_alignment:
       .space    4
对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control
register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)

由上面可得到cr_alignment为零,异常向量则在高端地址,CPU高端寻址。

那么是如何将cr_alignment配置为零的呢?
/*linux/arch/arm/kernel/head.S—当内核启动时,进入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    __vet_atags
       bl    __create_page_tables  //创建arm启动临时使用的前4M页表
        ldr   r13, __switch_data             @ address to jump to after  //99行
                                          @ mmu has been enabled
       adr  lr, __enable_mmu        @ return (PIC) address
       add pc, r10, #PROCINFO_INITFUNC         //102行
   …… …… …… …… …… …… ……
__turn_mmu_on:
            mov   r0, r0        //填充armv4中的三级流水线:mov r0,r0 对//应一个nop,所以对应2个nop和一个mov pc,lr刚好三个"无用"操作    
        mcr    p15, 0, r0, c1, c0, 0           @ write control reg  //193行
        mrc    p15, 0, r3, c0, c0, 0           @ read id reg
  …… …… …… …… …… …… ……
mov       pc, lr                   //327行
在s3c2410平台中,它将跳转到arch/arm/mm/proc-arm920.S中执行__arm920 _setup函数。即第102行“add    pc, r10, #PROCINFO_INITFUNC”:   执行b _arm920_setup
       linux/arch/arm/mm/proc-arm920.S: MMU functions for ARM920
.section ".proc.info.init", #alloc, #execinstr
 
       .type       __arm920_proc_info,#object
__arm920_proc_info:
       .long       0x41009200
       .long       0xff00fff0
       .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     __arm920_setup
   // add      pc, r10, #PROCINFO_INITFUNC”:将执行b _arm920_setup
       .long       cpu_arch_name
       .long       cpu_elf_name
       .long       HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
       .long       cpu_arm920_name
       .long       arm920_processor_functions
       …… …… …… …… …… …… ……
         .size __arm920_proc_info, . - __arm920_proc_info
  …… …… …… …… …… …… …… …… …… …… …… …… …… …… ……
.type       __arm920_setup, #function
__arm920_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
       adr  r5, arm920_crval
       ldmia      r5, {r5, r6}
       mrc p15, 0, r0, c1, c0        @ get control register v4
       bic   r0, r0, r5
       orr   r0, r0, r6
       mov pc, lr
       .size __arm920_setup, . - __arm920_setup

当在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到linux/arch/arm/kernel/head.S文件中的第99行,在lr寄存器中放置__switch_data中的数据__mmap_switched,第327行程序会跳转到__mmap_switched处。在__turn_mmu_on:后,第193,194行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。
接下来再来看一下跳转到__mmap_switched处的代码:

/* linux/arch/arm/kernel/head-common.S */
.type       __switch_data, %object
__switch_data:
       .long       __mmap_switched
       .long       __data_loc                  @ r4
       .long       __data_start                @ r5
       .long       __bss_start                  @ r6
       .long       _end                            @ r7
       .long       processor_id               @ r4
       .long       __machine_arch_type         @ r5
       .long       __atags_pointer                  @ r6
       .long       cr_alignment                @ r7
       .long       init_thread_union + THREAD_START_SP @ sp
/*
 * The following fragment of code is executed with the MMU on in MMU mode,
 * and uses absolute addresses; this is not position independent.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags pointer
 *  r9  = processor ID
 */
.type       __mmap_switched, %function
__mmap_switched:                                         //40行
       adr  r3, __switch_data + 4                                //41行
 
       ldmia      r3!, {r4, r5, r6, r7}                               //43行
       cmp r4, r5                           @ Copy data segment if needed
1:    cmpne    r5, r6
       ldrne       fp, [r4], #4
       strne       fp, [r5], #4
       bne  1b
 
       mov fp, #0                          @ Clear BSS (and zero fp)
1:    cmp r6, r7
       strcc       fp, [r6],#4
       bcc  1b                                                //53行
 
       ldmia      r3, {r4, r5, r6, r7, sp}        // sp    ~    (init_task_union)+8192
       str   r9, [r4]                 @ Save processor ID
       str   r1, [r5]                 @ Save machine type
       str   r2, [r6]                 @ Save atags pointer
       bic   r4, r0, #CR_A                    @ Clear 'A' bit
       stmia      r7, {r0, r4}                  @ Save control register values    //60行
         b       start_kernel                         // 进入内核C程序

41~43行的结果是:r6=__bss_start,r7=__end,...,r7=cr_alignment,..,这里r7保存的是cr_alignment变量的地址。

到了60行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r7指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。

让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:

/*linux/arch/arm/kernel/entry-armv.S */
/*============================================================
 * 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
 
/*
 * We group all the following data together to optimise
 * for CPUs with separate I & D caches.
 */
       .align      5
 
.LCvswi:
       .word     vector_swi
 
       .globl      __stubs_end
__stubs_end:
 
       .equ stubs_offset, __vectors_start + 0x200 - __stubs_start
 
       .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:
 
       .data
 
       .globl      cr_alignment
       .globl      cr_no_alignment
cr_alignment:
       .space    4
cr_no_alignment:
       .space    4
当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

2.3 init_IRQ()

/* linux/arch/arm/kernel/irq.c */
void __init init_IRQ(void)
{
       int irq;
 
       for (irq = 0; irq < NR_IRQS; irq++)      // NR_IRQS代表中断数目
              irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个
//irq_desc结构,组成了一个数组。
#ifdef CONFIG_SMP
       bad_irq_desc.affinity = CPU_MASK_ALL;
       bad_irq_desc.cpu = smp_processor_id();
#endif
       init_arch_irq();
}


/* include/linux/irq.h */      
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:          highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:            low level interrupt hardware access
 * @msi_desc:            MSI descriptor
 * @handler_data:      per-IRQ data for the irq_chip methods
 * @chip_data:           platform-specific per-chip private data for the chip
 *                 methods, to allow shared chip implementations
 * @action:          the irq action chain
 * @status:          status information
 * @depth:          disable-depth, for nested irq_disable() calls
 * @wake_depth:              enable depth, for multiple set_irq_wake() callers
 * @irq_count:           stats field to detect stalled irqs
 * @irqs_unhandled:   stats field for spurious unhandled interrupts
 * @last_unhandled:   aging timer for unhandled count
 * @lock:            locking for SMP
 * @affinity:         IRQ affinity on SMP
 * @cpu:             cpu index useful for balancing
 * @pending_mask:    pending rebalanced interrupts
 * @dir:        /proc/irq/ procfs entry
 * @affinity_entry:      /proc/irq/smp_affinity procfs entry on SMP
 * @name:           flow handler name for /proc/interrupts output
 */
struct irq_desc {
       irq_flow_handler_t       handle_irq;
       struct irq_chip              *chip;
       struct msi_desc            *msi_desc;
       void               *handler_data;
       void               *chip_data;
       struct irqaction      *action;  /* IRQ action list */
       unsigned int           status;            /* IRQ status */
 
       unsigned int           depth;            /* nested irq disables */
       unsigned int           wake_depth; /* nested wake enables */
       unsigned int           irq_count;      /* For detecting broken IRQs */
       unsigned int           irqs_unhandled;
       unsigned long        last_unhandled;     /* Aging timer for unhandled count */
       spinlock_t             lock;
#ifdef CONFIG_SMP
       cpumask_t            affinity;
       unsigned int           cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
       cpumask_t            pending_mask;
#endif
#ifdef CONFIG_PROC_FS
       struct proc_dir_entry   *dir;
#endif
       const char             *name;
} ____cacheline_internodealigned_in_smp;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: