海思hi3520dv400 kernel分析(2)——汇编阶段启动分析

    在完成了zImage自解压之后,就跳转到了解压后的内核(也就是vmlinux的bin版本Image),具体的入口可以在arch/arm/kernel/vmlinux.lds.S(最终的链接脚本是通过这个文件产生的)中获得:

......
SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif
.init : { /* Init code and data */
_stext = .;
_sinittext = .;
......

    这个入口在arch/arm/kernel/head.S中,这个文件就是Linux内核真正启动的地方,是初始化部分的开始,用汇编写成。他必须为后面的C代码做好准备,下面先给出程序的流程图,后面是中文注释的代码。

这里有一些宏定义必须知道他的含义:

出现的位置 默认值 定义
KERNEL_RAM_ADDR arch/arm/kernel/head.S 0xC0008000 内核在内存中的虚拟地址
PAGE_OFFSET arch/arm/include/asm/memory.h 0xC0000000 内核虚拟地址空间的起始地址
TEXT_OFFSET arch/arm/Makefile 0x00008000 内核起始位置相对于内存起始位置的偏移
PHYS_OFFSET arch/arm/include/asm/memory.h 构架相关 物理内存的起始地址

该阶段完成的工作有: 

  • 1.进入SVC模式,关闭中断
  • 2.获取处理器ID,并检测处理器ID是否匹配
  • 3.获取RAM的起始物流地址,保存在R8寄存器中
  • 4.判断R2(内核启动参数)指针的有效性,检测内容是否有效
  • 5.初始化页表
  • 6.为启动MMU做最后准备
  • <1>设置启动MMU后跳转的第一个虚拟地址(r13)
  • <2>保存页目录物理地址(r8),以备启动mmu时放入TTBR1
  • 7.初始化TLB,缓存,让MMU状态满足启动的条件
  • 8.启动MMU
  • 9.清零BSS段
  • 10.将需要的数据转移到全局变量中
  • 11.跳到C语言代码执行 start_kernel 

 arch/arm/kernel/head.S

/*
 * linux/arch/arm/kernel/head.S
 *
 * Copyright (C) 1994-2002 Russell King
 * Copyright (c) 2003 ARM Limited
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 所有32-bit CPU的内核启动代码
 */
#include <linux/linkage.h>
#include <linux/init.h>

#include <asm/assembler.h>
#include <asm/domain.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <asm/memory.h>
#include <asm/thread_info.h>
#include <asm/system.h>

#ifdef CONFIG_DEBUG_LL
#include <mach/debug-macro.S>
#endif

/*
 * swapper_pg_dir 是初始页表的虚拟地址.
 * 我们将页表放在KERNEL_RAM_VADDR以下16K的空间中. 因此我们必须保证
 * KERNEL_RAM_VADDR已经被正常设置. 当前, 我们期望的是
 * 这个地址的最后16 bits为0x8000, 但我们或许可以放宽这项限制到
 * KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
#define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

    .globl    swapper_pg_dir
    .equ    swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000

/*
 * TEXT_OFFSET 是内核代码(解压后)相对于RAM起始的偏移.
 * 而#TEXT_OFFSET - 0x4000就是页表相对于RAM起始的偏移.
 * 这个宏的作用是将phys(RAM的启示地址)加上页表的偏移,
 * 而得到页表的起始物理地址
 */
    .macro    pgtbl, rd, phys
    add    \rd, \phys, #TEXT_OFFSET - 0x4000
    .endm

#ifdef CONFIG_XIP_KERNEL
#define KERNEL_START    XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
#define KERNEL_END    _edata_loc
#else
#define KERNEL_START    KERNEL_RAM_VADDR
#define KERNEL_END    _end
#endif

/*
 * 内核启动入口点.
 * ---------------------------
 *
 * 这个入口正常情况下是在解压完成后被调用的.
 * 调用条件: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags or dtb pointer.
 * 这些条件在解压完成后会被逐一满足,然后才跳转过来。
 *
 * 这些代码大多数是位置无关的, 如果你的内核入口地址在连接时确定为
 * 0xc0008000, 你调用此函数的物理地址就是 __pa(0xc0008000).
 *
 * 完整的machineID列表,请参见 linux/arch/arm/tools/mach-types
 *
 * 我们尽量让代码简洁; 不在此处添加任何设备特定的代码
 * - 这些特定的初始化代码是boot loader的工作(或在极端情况下,
 * 有充分理由的情况下, 可以由zImage完成)。
 */
    __HEAD
ENTRY(stext)
    setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ CPU模式设置宏
                                                    @ (进入svc模式并且关闭中断)
    mrc    p15, 0, r9, c0, c0                       @ 获取处理器id-->r9
    bl    __lookup_processor_type                   @ 返回r5=procinfo r9=cpuid
    movs    r10, r5                                 @ r10=r5,并可以检测r5=0?注意当前r10的值
 THUMB( it    eq )            @ force fixup-able long branch encoding
    beq    __error_p            @ yes, error 'p'如果r5=0,则内核处理器不匹配,出错~死循环

    /*
     * 获取RAM的起始物理地址,并保存于 r8 = phys_offset
     * XIP内核与普通在RAM中运行的内核不同
     * (1)CONFIG_XIP_KERNEL
     *         通过运行时计算????
     * (2)正常RAM中运行的内核
     *         通过编译时确定(PLAT_PHYS_OFFSET 一般在arch/arm/mach-xxx/include/mach/memory.h定义)
     *        
     */
#ifndef CONFIG_XIP_KERNEL
    adr    r3, 2f
    ldmia    r3, {r4, r8}
    sub    r4, r3, r4            @ (PHYS_OFFSET - PAGE_OFFSET)
    add    r8, r8, r4            @ PHYS_OFFSET
#else
    ldr    r8, =PLAT_PHYS_OFFSET
#endif

    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */
    bl    __vet_atags            @ 判断r2(内核启动参数)指针的有效性
#ifdef CONFIG_SMP_ON_UP
    bl    __fixup_smp            @ ???如果运行SMP内核在单处理器系统中启动,做适当调整
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
    bl    __fixup_pv_table    @ ????根据内核在内存中的位置修正物理地址与虚拟地址的转换机制
#endif
    bl    __create_page_tables    @ 初始化页表!

    /*
     * 以下使用位置无关的方法调用的是CPU特定代码。
     * 详情请见arch/arm/mm/proc-*.S
     * r10 = xxx_proc_info 结构体的基地址(在上面__lookup_processor_type函数中选中的)
     * 返回时, CPU 已经为 MMU 的启动做好了准备,
     * 且 r0 保存着CPU控制寄存器的值.
     */
    ldr    r13, =__mmap_switched                @ 在MMU启动之后跳入的第一个虚拟地址
    adr    lr, BSYM(1f)                        @ 设置返回的地址(PIC)
    mov    r8, r4                                @ 将swapper_pg_dir的物理地址放入r8,
                                            @ 以备__enable_mmu中将其放入TTBR1
 ARM(    add    pc, r10, #PROCINFO_INITFUNC    )    @ 跳入构架相关的初始化处理器函数(例如A8的是__v7_setup)
 THUMB(    add    r12, r10, #PROCINFO_INITFUNC    )    @主要目的只配置CP15(包括缓存配置)
 THUMB(    mov    pc, r12                )
1:    b    __enable_mmu                        @ 启动MMU
ENDPROC(stext)
    .ltorg
#ifndef CONFIG_XIP_KERNEL
2:    .long    .
    .long    PAGE_OFFSET
#endif

/*
 * 创建初始化页表. 我们只创建最基本的页表,
 * 以满足内核运行的需要,
 * 这通常意味着仅映射内核代码本身.
 *
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 *
 * 返回:
 * r0, r3, r5-r7 被篡改
 * r4 = 页表物理地址
 */
__create_page_tables:
    pgtbl    r4, r8                @ 现在r4 = 页表的起始物理地址

    /*
     * 清零16K的一级初始页表区
     * 这些页表在内核自解压时被设置过
     * (此时MMU已关闭)
     */
    mov    r0, r4
    mov    r3, #0
    add    r6, r0, #0x4000
1:    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    teq    r0, r6
    bne    1b

    /*
     * 获取节描述符的默认配置(除节基址外的其他配置)
     * 这个数据依构架而不同,数据是用汇编文件配置的:
     * arch/arm/mm/proc-xxx.S
     * (此时MMU已关闭)
     */
    ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ 获取mm_mmuflags(节描述符默认配置),保存于r7

    /*
     * 创建特定映射,以满足__enable_mmu的需求。
     * 此特定映射将被paging_init()删除。
     *
     * 其实这个特定的映射就是仅映射__enable_mmu功能函数区的页表
     * 以保证在启用mmu时代码的正确执行--1:1映射(物理地址=虚拟地址)
     */
    adr    r0, __enable_mmu_loc
    ldmia    r0, {r3, r5, r6}
    sub    r0, r0, r3            @ 获取编译时确定的虚拟地址到当前物理地址的偏移
    add    r5, r5, r0            @ __enable_mmu的当前物理地址
    add    r6, r6, r0            @ __enable_mmu_end的当前物理地址
    mov    r5, r5, lsr #20        @ __enable_mmu的节基址
    mov    r6, r6, lsr #20        @ __enable_mmu_end的节基址

1:    orr    r3, r7, r5, lsl #20        @ 生成节描述符:flags + 节基址
    str    r3, [r4, r5, lsl #2]    @ 设置节描述符,1:1映射(物理地址=虚拟地址)
    teq    r5, r6                    @ 完成映射?(理论上一次就够了,这个函数应该不会大于1M吧~)
    addne    r5, r5, #1            @ r5 = 下一节的基址
    bne    1b

    /*
     * 现在创建内核的逻辑映射区页表(节映射)
     * 创建范围:KERNEL_START---KERNEL_END
     * KERNEL_START:内核最终运行的虚拟地址
     * KERNEL_END:内核代码结束的虚拟地址(bss段之后,但XIP不是)
     */
    mov    r3, pc                @ 获取当前物理地址
    mov    r3, r3, lsr #20        @ r3 = 当前物理地址的节基址
    orr    r3, r7, r3, lsl #20    @ r3 为当前物理地址的节描述符
    /*
     * 下面是为了确定页表项的入口地址
     * 其实页表入口项的偏移就反应了对应的虚拟地址的高位
     *
     * 由于ARM指令集的8bit位图问题,只能分两次得到
     * KERNEL_START:内核最终运行的虚拟地址
     *
     */
    add    r0, r4, #(KERNEL_START & 0xff000000) >> 18
    str    r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    ldr    r6, =(KERNEL_END - 1)
    add    r0, r0, #4
    add    r6, r4, r6, lsr #18    @ r6 = 内核逻辑映射结束的节基址
1:    cmp    r0, r6
    add    r3, r3, #1 << 20    @ 生成节描述符(只需做基址递增)
    strls    r3, [r0], #4    @ 设置节描述符
    bls    1b

#ifdef CONFIG_XIP_KERNEL
    /*
     * 如果是XIP技术的内核,上面的映射只能映射内核代码和只读数据部分
     * 这里我们再映射一些RAM来作为 .data and .bss 空间.
     */
    add    r3, r8, #TEXT_OFFSET
    orr    r3, r3, r7            @ 生成节描述符:flags + 节基址
    add    r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18
    str    r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
    ldr    r6, =(_end - 1)
    add    r0, r0, #4
    add    r6, r4, r6, lsr #18
1:    cmp    r0, r6
    add    r3, r3, #1 << 20
    strls    r3, [r0], #4
    bls    1b
#endif

    /*
     * 然后映射启动参数区(现在r2中的atags物理地址)
     * 或者
     * 如果启动参数区的虚拟地址没有确定(或者无效),则会映射RAM的头1MB.
     */
    mov    r0, r2, lsr #20
    movs    r0, r0, lsl #20
    moveq    r0, r8                @ 如果atags指针无效,则r0 = r8(映射RAM的头1MB)
    sub    r3, r0, r8
    add    r3, r3, #PAGE_OFFSET    @ 转换为虚拟地址
    add    r3, r4, r3, lsr #18        @ 确定页表项(节描述符)入口地址
    orr    r6, r7, r0                @ 生成节描述符
    str    r6, [r3]                @ 设置节描述符

    /*
     * 下面是调试信息的输出函数区
     * 这里做了IO内存空间的节映射
     */
#ifdef CONFIG_DEBUG_LL
#ifndef CONFIG_DEBUG_ICEDCC
    /*
     * 为串口调试映射IO内存空间(将串口IO内存之上的所有地址都映射了)
     * 这允许调试信息(在paging_init之前)从串口控制台输出
     *
     */
    addruart r7, r3        @ 宏代码,位于arch/arm/mach-xxx/include/mach/debug-macro.S
                        @ 作用是将串口控制寄存器的基址放入r7(物理地址)和r3(虚拟地址)
    mov    r3, r3, lsr #20
    mov    r3, r3, lsl #2

    add    r0, r4, r3        @ r0为串口IO内存映射页表项的入口地址
    rsb    r3, r3, #0x4000            @ 16K(PTRS_PER_PGD*sizeof(long))-r3
    cmp    r3, #0x0800            @ limit to 512MB,入口地址有效性检查(只能在最后#0x0800内)
    movhi    r3, #0x0800        @ 也就是说虚拟地址被限制在3.5G以上
    add    r6, r0, r3            @ r6为页表结束地址
    mov    r3, r7, lsr #20
    ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
    orr    r3, r7, r3, lsl #20    @ 生成节描述符
1:    str    r3, [r0], #4
    add    r3, r3, #1 << 20
    teq    r0, r6
    bne    1b

#else /* CONFIG_DEBUG_ICEDCC */
    /* 我们无需任何串口调试映射 for ICEDCC */
    ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif /* !CONFIG_DEBUG_ICEDCC */

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
    /*
     * 如果我们在使用 NetWinder 或 CATS,我们也需要为调试信息映射
     * 16550-type 串口
     */
    add    r0, r4, #0xff000000 >> 18
    orr    r3, r7, #0x7c000000
    str    r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
    /*
     * Map in screen at 0x02000000 & SCREEN2_BASE
     * Similar reasons here - for debug. This is
     * only for Acorn RiscPC architectures.
     */
    add    r0, r4, #0x02000000 >> 18
    orr    r3, r7, #0x02000000
    str    r3, [r0]
    add    r0, r4, #0xd8000000 >> 18
    str    r3, [r0]
#endif
#endif
    mov    pc, lr        @页表创建结束,返回
ENDPROC(__create_page_tables)
    .ltorg
    .align
__enable_mmu_loc:
    .long    .
    .long    __enable_mmu
    .long    __enable_mmu_end

#if defined(CONFIG_SMP)
    __CPUINIT
ENTRY(secondary_startup)
    /*
     * Common entry point for secondary CPUs.
     *
     * Ensure that we're in SVC mode, and IRQs are disabled. Lookup
     * the processor type - there is no need to check the machine type
     * as it has already been validated by the primary processor.
     */
    setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9
    mrc    p15, 0, r9, c0, c0        @ get processor id
    bl    __lookup_processor_type
    movs    r10, r5                @ invalid processor?
    moveq    r0, #'p'            @ yes, error 'p'
 THUMB( it    eq )        @ force fixup-able long branch encoding
    beq    __error_p

    /*
     * Use the page tables supplied from __cpu_up.
     */
    adr    r4, __secondary_data
    ldmia    r4, {r5, r7, r12}        @ address to jump to after
    sub    lr, r4, r5            @ mmu has been enabled
    ldr    r4, [r7, lr]            @ get secondary_data.pgdir
    add    r7, r7, #4
    ldr    r8, [r7, lr]            @ get secondary_data.swapper_pg_dir
    adr    lr, BSYM(__enable_mmu)        @ return address
    mov    r13, r12            @ __secondary_switched address
 ARM(    add    pc, r10, #PROCINFO_INITFUNC    ) @ initialise processor
                         @ (return control reg)
 THUMB(    add    r12, r10, #PROCINFO_INITFUNC    )
 THUMB(    mov    pc, r12                )
ENDPROC(secondary_startup)

    /*
     * r6 = &secondary_data
     */
ENTRY(__secondary_switched)
    ldr    sp, [r7, #4]            @ get secondary_data.stack
    mov    fp, #0
    b    secondary_start_kernel
ENDPROC(__secondary_switched)

    .align

    .type    __secondary_data, %object
__secondary_data:
    .long    .
    .long    secondary_data
    .long    __secondary_switched
#endif /* defined(CONFIG_SMP) */

/*
 * 在最后启动MMU前,设置一些常用位 Essentially
 * 其实,这里只是加载了页表指针和域访问控制数据寄存器
 *
 *
 * r0 = cp#15 control register
 * r1 = machine ID
 * r2 = atags or dtb pointer
 * r4 = page table pointer
 * r9 = processor ID
 * r13 = 最后要跳入的虚拟地址
 */
__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        @ 载入域访问控制数据到DACR
    mcr    p15, 0, r4, c2, c0, 0        @ 载入页表基址到TTBR0
    b    __turn_mmu_on                @ 开启MMU
ENDPROC(__enable_mmu)

/*
 * 使能 MMU. 这完全改变了可见的内存地址空间结构。
 * 您将无法通过这里跟踪执行。
 * 如果你已对此进行探究, *请*在向邮件列表发送另一个新帖之前,
 * 检查linux-arm-kernel的邮件列表归档
 *
 * r0 = cp#15 control register
 * r1 = machine ID
 * r2 = atags or dtb pointer
 * r9 = processor ID
 * r13 = 最后要跳入的*虚拟*地址
 *
 * 其他寄存器依赖上面的调用函数
 */
    .align    5
__turn_mmu_on:
    mov    r0, r0
    mcr    p15, 0, r0, c1, c0, 0        @ 设置cp#15控制寄存器(启用MMU)
    mrc    p15, 0, r3, c0, c0, 0        @ read id reg
    mov    r3, r3
    mov    r3, r13                        @ r3中装入最后要跳入的*虚拟*地址
    mov    pc, r3                        @ 跳转到__mmap_switched
__enable_mmu_end:
ENDPROC(__turn_mmu_on)


#ifdef CONFIG_SMP_ON_UP
    __INIT
__fixup_smp:
    and    r3, r9, #0x000f0000    @ architecture version
    teq    r3, #0x000f0000        @ CPU ID supported?
    bne    __fixup_smp_on_up    @ no, assume UP

    bic    r3, r9, #0x00ff0000
    bic    r3, r3, #0x0000000f    @ mask 0xff00fff0
    mov    r4, #0x41000000
    orr    r4, r4, #0x0000b000
    orr    r4, r4, #0x00000020    @ val 0x4100b020
    teq    r3, r4            @ ARM 11MPCore?
    moveq    pc, lr            @ yes, assume SMP

    mrc    p15, 0, r0, c0, c0, 5    @ read MPIDR
    and    r0, r0, #0xc0000000    @ multiprocessing extensions and
    teq    r0, #0x80000000        @ not part of a uniprocessor system?
    moveq    pc, lr            @ yes, assume SMP

__fixup_smp_on_up:
    adr    r0, 1f
    ldmia    r0, {r3 - r5}
    sub    r3, r0, r3
    add    r4, r4, r3
    add    r5, r5, r3
    b    __do_fixup_smp_on_up
ENDPROC(__fixup_smp)

    .align
1:    .word    .
    .word    __smpalt_begin
    .word    __smpalt_end

    .pushsection .data
    .globl    smp_on_up
smp_on_up:
    ALT_SMP(.long    1)
    ALT_UP(.long    0)
    .popsection
#endif

    .text
__do_fixup_smp_on_up:
    cmp    r4, r5
    movhs    pc, lr
    ldmia     {r0, r6}
 ARM(    str    r6, [r0, r3]    )
 THUMB(    add    r0, r0, r3    )
#ifdef __ARMEB__
 THUMB(    mov    r6, r6, ror #16    )    @ Convert word order for big-endian.
#endif
 THUMB(    strh    r6, [r0], #2    )    @ For Thumb-2, store as two halfwords
 THUMB(    mov    r6, r6, lsr #16    )    @ to be robust against misaligned r3.
 THUMB(    strh    r6, [r0]    )
    b    __do_fixup_smp_on_up
ENDPROC(__do_fixup_smp_on_up)

ENTRY(fixup_smp)
    stmfd     {r4 - r6, lr}
    mov    r4, r0
    add    r5, r0, r1
    mov    r3, #0
    bl    __do_fixup_smp_on_up
    ldmfd     {r4 - r6, pc}
ENDPROC(fixup_smp)

#ifdef CONFIG_ARM_PATCH_PHYS_VIRT

/* __fixup_pv_table - patch the stub instructions with the delta between
 * PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and
 * can be expressed by an immediate shifter operand. The stub instruction
 * has a form of '(add|sub) rd, rn, #imm'.
 */
    __HEAD
__fixup_pv_table:
    adr    r0, 1f
    ldmia    r0, {r3-r5, r7}
    sub    r3, r0, r3    @ PHYS_OFFSET - PAGE_OFFSET
    add    r4, r4, r3    @ adjust table start address
    add    r5, r5, r3    @ adjust table end address
    add    r7, r7, r3    @ adjust __pv_phys_offset address
    str    r8, [r7]    @ save computed PHYS_OFFSET to __pv_phys_offset
#ifndef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT
    mov    r6, r3, lsr #24    @ constant for add/sub instructions
    teq    r3, r6, lsl #24 @ must be 16MiB aligned
#else
    mov    r6, r3, lsr #16    @ constant for add/sub instructions
    teq    r3, r6, lsl #16    @ must be 64kiB aligned
#endif
THUMB(    it    ne        @ cross section branch )
    bne    __error
    str    r6, [r7, #4]    @ save to __pv_offset
    b    __fixup_a_pv_table
ENDPROC(__fixup_pv_table)

    .align
1:    .long    .
    .long    __pv_table_begin
    .long    __pv_table_end
2:    .long    __pv_phys_offset

    .text
__fixup_a_pv_table:
#ifdef CONFIG_THUMB2_KERNEL
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT
    lsls    r0, r6, #24
    lsr    r6, #8
    beq    1f
    clz    r7, r0
    lsr    r0, #24
    lsl    r0, r7
    bic    r0, 0x0080
    lsrs    r7, #1
    orrcs r0, #0x0080
    orr    r0, r0, r7, lsl #12
#endif
1:    lsls    r6, #24
    beq    4f
    clz    r7, r6
    lsr    r6, #24
    lsl    r6, r7
    bic    r6, #0x0080
    lsrs    r7, #1
    orrcs    r6, #0x0080
    orr    r6, r6, r7, lsl #12
    orr    r6, #0x4000
    b    4f
2:    @ at this point the C flag is always clear
    add r7, r3
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT
    ldrh    ip, [r7]
    tst    ip, 0x0400    @ the i bit tells us LS or MS byte
    beq    3f
    cmp    r0, #0        @ set C flag, and ...
    biceq    ip, 0x0400    @ immediate zero value has a special encoding
    streqh    ip, [r7]    @ that requires the i bit cleared
#endif
3:    ldrh    ip, [r7, #2]
    and    ip, 0x8f00
    orrcc    ip, r6    @ mask in offset bits 31-24
    orrcs    ip, r0    @ mask in offset bits 23-16
    strh    ip, [r7, #2]
4:    cmp    r4, r5
    ldrcc    r7, [r4], #4    @ use branch for delay slot
    bcc    2b
    bx    lr
#else
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT
    and    r0, r6, #255    @ offset bits 23-16
    mov    r6, r6, lsr #8    @ offset bits 31-24
#else
    mov    r0, #0        @ just in case...
#endif
    b    3f
2:    ldr    ip, [r7, r3]
    bic    ip, ip, #0x000000ff
    tst    ip, #0x400    @ rotate shift tells us LS or MS byte
    orrne    ip, ip, r6    @ mask in offset bits 31-24
    orreq    ip, ip, r0    @ mask in offset bits 23-16
    str    ip, [r7, r3]
3:    cmp    r4, r5
    ldrcc    r7, [r4], #4    @ use branch for delay slot
    bcc    2b
    mov    pc, lr
#endif
ENDPROC(__fixup_a_pv_table)

ENTRY(fixup_pv_table)
    stmfd     {r4 - r7, lr}
    ldr    r2, 2f            @ get address of __pv_phys_offset
    mov    r3, #0            @ no offset
    mov    r4, r0            @ r0 = table start
    add    r5, r0, r1        @ r1 = table size
    ldr    r6, [r2, #4]        @ get __pv_offset
    bl    __fixup_a_pv_table
    ldmfd     {r4 - r7, pc}
ENDPROC(fixup_pv_table)

    .align
2:    .long    __pv_phys_offset

    .data
    .globl    __pv_phys_offset
    .type    __pv_phys_offset, %object
__pv_phys_offset:
    .long    0
    .size    __pv_phys_offset, . - __pv_phys_offset
__pv_offset:
    .long    0
#endif

#include "head-common.S"

 arch/arm/kernel/head-common.S

/*
 * linux/arch/arm/kernel/head-common.S
 *
 * Copyright (C) 1994-2002 Russell King
 * Copyright (c) 2003 ARM Limited
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#define ATAG_CORE 0x54410001
#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
#define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)

#ifdef CONFIG_CPU_BIG_ENDIAN
#define OF_DT_MAGIC 0xd00dfeed
#else
#define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */
#endif

/*
 * 异常处理. 一些我们无法处理的错误.
 * 我们应当告诉用户(这些错误信息),但因为我们甚至无法保证是在正确的架构上运行,
 * 所以我们什么都不做(死循环)。
 *
 * 如果 CONFIG_DEBUG_LL 被设置,我们试图打印出错误信息,
 * 并希望这可以对我们有帮助 (例如这对bootloader没有提供适当的处理器ID
 * 是有帮助的).
 */
    __HEAD

/* 确定r2(内核启动参数)指针的有效性。 The heuristic 要求
 * 是4Byte对齐的、在物理内存的头16K中,且以ATAG_CORE标记开头。
 * 如果选择了CONFIG_OF_FLATTREE,dtb指针也是可以接受的.
 *
 * 在这个函数的未来版本中 可能会对物理地址的要求更为宽松,
 * 且如果有必要的话,可能可以移动ATAGS数据块.
 *
 * 返回:
 * r2 可能是有效的 atags 指针, 有效的 dtb 指针,或者0
 * r5, r6 被篡改
 */
__vet_atags:
    tst    r2, #0x3            @ 是否4Byte对齐?
    bne    1f                    @ 不是则认为指针无效,返回

    ldr    r5, [r2, #0]        @获取r2指向的前4Byte,用于下面测试
#ifdef CONFIG_OF_FLATTREE
    ldr    r6, =OF_DT_MAGIC        @ is it a DTB?
    cmp    r5, r6
    beq    2f
#endif

    /* 内核启动参数块的规范是:
     * (wait for updata)
     */
    cmp    r5, #ATAG_CORE_SIZE        @ 第一个tag是ATAG_CORE吗?测试的是tag_header中的size
                                @ 如果为ATAG_CORE,那么必为ATAG_CORE_SIZE
    cmpne    r5, #ATAG_CORE_SIZE_EMPTY    @ 如果第一个tag的tag_header中的size为ATAG_CORE_SIZE_EMPTY
                                        @ 说明此处也有atags
    bne    1f
    ldr    r5, [r2, #4]            @ 第一个tag_header的tag(魔数)
    ldr    r6, =ATAG_CORE            @ 获取ATAG_CORE的魔数
    cmp    r5, r6                    @ 判断第一个tag是否为ATAG_CORE
    bne    1f                        @ 不是则认为指针无效,返回

2:    mov    pc, lr                @ atag/dtb 指针有效

1:    mov    r2, #0
    mov    pc, lr
ENDPROC(__vet_atags)

/*
 * 以下的代码段是在MMU开启的状态下执行的,
 * 而且使用的是绝对地址; 这不是位置无关代码.
 *
 * r0 = cp#15 控制寄存器值
 * r1 = machine ID
 * r2 = atags/dtb pointer
 * r9 = processor ID
 */
    __INIT
__mmap_switched:
    adr    r3, __mmap_switched_data

    ldmia     {r4, r5, r6, r7}
    cmp    r4, r5                @ 如果有必要,拷贝数据段。
                            @ 对比__data_loc和_sdata
                            @ __data_loc是数据段在内核代码映像中的存储位置
                            @ _sdata是数据段的链接位置(在内存中的位置)
                            @ 如果是XIP技术的内核,这两个数据肯定不同
1:    cmpne    r5, r6            @ 检测数据是否拷贝完成
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b

    mov    fp, #0                @ 清零 BSS 段(and zero fp)
1:    cmp    r6, r7                @ 检测是否完成
    strcc    fp, [r6],#4
    bcc    1b

    /* 这里将需要的数据从寄存器中转移到全局变量中,
     * 因为最后会跳入C代码,寄存器会被使用。
     */
 ARM(    ldmia    r3, {r4, r5, r6, r7, sp})
 THUMB(    ldmia    r3, {r4, r5, r6, r7}    )
 THUMB(    ldr    sp, [r3, #16]        )
    str    r9, [r4]            @ 保存 processor ID到全局变量processor_id
    str    r1, [r5]            @ 保存 machine type到全局变量__machine_arch_type
    str    r2, [r6]            @ 保存 atags指针到全局变量__atags_pointer
    bic    r4, r0, #CR_A            @ 清除cp15 控制寄存器值的 'A' bit(禁用对齐错误检查)
    stmia    r7, {r0, r4}            @ 保存控制寄存器值到全局变量cr_alignment(在arch/arm/kernel/entry-armv.S)
    b    start_kernel        @ 跳入C代码(init/main.c)
ENDPROC(__mmap_switched)

    .align    2
    .type    __mmap_switched_data, %object
__mmap_switched_data:
    .long    __data_loc            @ r4
    .long    _sdata                @ 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
    .size    __mmap_switched_data, . - __mmap_switched_data

/*
 * 这里提供一个 C-API 版本的 __lookup_processor_type
 */
ENTRY(lookup_processor_type)
    stmfd     {r4 - r6, r9, lr}
    mov    r9, r0
    bl    __lookup_processor_type
    mov    r0, r5
    ldmfd     {r4 - r6, r9, pc}
ENDPROC(lookup_processor_type)

/*
 * 读取处理器ID寄存器 (CP#15, CR0), 并且查找编译时确定的处理器
 * 支持列表. 注意:我们不能对__proc_info使用绝对地址,
 * 因为我们还没有重新初始化页表(MMU已关闭,之前是解压时使用的1:1映射)。
 * (我们不在正确的地址空间:内核是按虚拟地址(0xc00008000)编译的,
 * 而现在我们运行在MMU关闭的情况下)。
 * 我们必须计算偏移量。
 *
 *    r9 = cpuid
 * Returns:
 *    r3, r4, r6 被篡改
 *    r5 = proc_info 指针(物理地址空间)
 *    r9 = cpuid (保留)
 */
    __CPUINIT
__lookup_processor_type:
    adr    r3, __lookup_processor_type_data        @获取运行时的地址数据
    ldmia    r3, {r4 - r6}    @获取编译时确定的地址数据(虚拟地址)
    sub    r3, r3, r4            @ 获取地址偏移 virt&phys(r3)
    add    r5, r5, r3            @ 将虚拟地址空间转换为物理地址空间
    add    r6, r6, r3            @ r5=__proc_info_begin r6=__proc_info_end
1:    ldmia    r5, {r3, r4}    @ 获取proc_info_list结构体中的value, mask
    and    r4, r4, r9            @ 利用掩码处理从CP15获取的处理器ID
    teq    r3, r4                @ 对比编译时确定的处理器ID
    beq    2f                    @ 若处理器ID匹配,返回
    add    r5, r5, #PROC_INFO_SZ        @ 利用sizeof(proc_info_list)跳入下一个处理器ID的匹配
    cmp    r5, r6                @ 是否已经处理完proc_info_list数据
    blo    1b                    @ 如果还有proc_info_list数据,再次检查匹配
    mov    r5, #0                @ 否则,编译的内核与此处理器不匹配,r5 = #0
2:    mov    pc, lr
ENDPROC(__lookup_processor_type)

/*
 * 参见 <asm/procinfo.h> 中关于 __proc_info 结构体的信息.
 */
    .align    2
    .type    __lookup_processor_type_data, %object
__lookup_processor_type_data:
    .long    .
    .long    __proc_info_begin
    .long    __proc_info_end
    .size    __lookup_processor_type_data, . - __lookup_processor_type_data

/*
 * 处理器ID不匹配时的入口
 * 如果启用了调试信息,会从consol打印提示信息
 * 之后会进入__error的死循环
 */
__error_p:
#ifdef CONFIG_DEBUG_LL
    adr    r0, str_p1
    bl    printascii
    mov    r0, r9
    bl    printhex8
    adr    r0, str_p2
    bl    printascii
    b    __error
str_p1:    .asciz    "\nError: unrecognized/unsupported processor variant (0x"
str_p2:    .asciz    ").\n"
    .align
#endif
ENDPROC(__error_p)

/*
 * 出错时的死循环入口
 */
__error:
#ifdef CONFIG_ARCH_RPC
/*
 * 出错时屏幕变红 - RiscPC only.
 */
    mov    r0, #0x02000000
    mov    r3, #0x11
    orr    r3, r3, r3, lsl #8
    orr    r3, r3, r3, lsl #16
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
#endif
1:    mov    r0, r0
    b    1b
ENDPROC(__error)

内容转载自:

Linux内核源码分析--内核启动之(2)Image内核启动(汇编部分)(Linux-3.0 ARMv7)
http://blog.chinaunix.net/uid-20543672-id-3019565.html

发布了164 篇原创文章 · 获赞 229 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/103562989