• 系统加电启动后,MIPS处理器默认的程序入口是0xBFC00000(虚拟地址),此地址在KSEG1(无缓存)区域内,对应的物理地址是0x1FC00000(高3位清零),所以CPU从物理地址0x1FC00000开始取第一条指令,这个地址在硬件上...

    系统加电启动后,MIPS处理器默认的程序入口是0xBFC00000(虚拟地址),此地址在KSEG1(无缓存)区域内,对应的物理地址是0x1FC00000高3位清零),所以CPU从物理地址0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASHBIOS)的位置,BIOS将Linux内核镜像文件拷贝到RAM中某个空闲地址(LOAD地址)处,然后一般有个内存移动的操作(Entry point(EP)的地址),最后BIOS跳转到EP指定的地址运行,此时开始运行Linux kernel。

    关于LOAD地址的一些说明:

    在我们编译完内核时,一般情况下会有俩个版本的内核vmlinuxvmlinuz。其中vmlinux为非压缩版内核,vmlinuz为压缩版内核(包含内核自解压程序)。
    使用readelf -l vmlinux 命令可以读到LOAD地址,这个地址是由arch/mips/kernel/vmlinux.lds决定的:

    OUTPUT_ARCH(mips) 
    ENTRY(kernel_entry) 
    jiffies = jiffies_64; 
    SECTIONS 
    { 
    . = 0xFFFFFFFF80200000; 
    /* read-only */ 
    _text = .; /* Text and read-only data */ 
    .text : { 
    *(.text) 
    …
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    关于Entry point(EP)的一些说明:

    EP(ELF可以读到)地址是BIOS移动完内核后,直接跳转的地址(控制权由BIOS转移到KERNEL)。这个地址由ld写入ELF的头中,会依次用下面的方法尝试设置入口地址,当遇见成功时则停止:
    a.命令行选项 -e entry;
    b.脚本(vmlinux.lds)中的ENTRY(xxx);
    c.如果有定义start符号,则使用start符号(xxx);
    d.如果存在.text节,则使用第一个字节的地址;
    e.地址0。

    由于上述ld 脚本(vmlinux.lds)中,用ENTRY宏设置了内核的EP是kernel_entry (KE)函数的地址,所以内核取得控制权(BIOS跳转之后)后执行的第一条指令就是 KE函数。

    注意:这种情况只是vmlinux(非压缩版的内核),对于vmlinuz(压缩版的内核),EP会被设置成内核自解压缩的程序代码的地址,这样固件就会跳转到内核自解压代码(此时的EP为解压程序的代码地址),最后还是会到KE函数去执行。

    由以上分析可知无论是压缩版还是非压缩版的Linux内核,内核第一个执行的函数是KE。接下来就是对KE函数的分析,看看它到底都做了些什么事?

    kernel_entry(KE)分析:

    内核版本:3.10.X
    源代码文件:arch/mips/kernel/head.S
    KE函数是体系相关的汇编语言实现的,源代码中汇编指令的含义(64位指令)为:

    PTR_LA          dla
    LONG_S          sd
    PTR_ADDIU       daddiu
    MTC0            dmtc0
    PTR_LI          dli
    PTR_ADDU        daddu
    PTR_SUBU        dsubu
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    源代码:

    NESTED(kernel_entry, 16, sp)            # KE函数定义,函数栈的大小为16字节
    
        kernel_entry_setup          # 对CPU的配置,详情见kernel_entry_setup函数分析NOTE1
    
        setup_c0_status_pri         #设置mips协处理器(cp0)中的寄存器,详情见NOTE2
    
        PTR_LA  t0, 0f
        jr  t0
    0:
    
    #ifdef CONFIG_MIPS_MT_SMTC     #硬件多线程
        mtc0    zero, CP0_TCCONTEXT
        mfc0    t0, CP0_STATUS
        ori t0, t0, 0xff1f
        xori    t0, t0, 0x001e
        mtc0    t0, CP0_STATUS
    #endif /* CONFIG_MIPS_MT_SMTC */
    
        PTR_LA      t0, __bss_start     # 清除BSS段,详情见NOTE3
        LONG_S      zero, (t0)
        PTR_LA      t1, __bss_stop - LONGSIZE
    1:
        PTR_ADDIU   t0, LONGSIZE
        LONG_S      zero, (t0)
        bne     t0, t1, 1b
    
        LONG_S      a0, fw_arg0     # BIOS传参数,详情见NOTE4
        LONG_S      a1, fw_arg1
        LONG_S      a2, fw_arg2
        LONG_S      a3, fw_arg3
    
        MTC0        zero, CP0_CONTEXT   # NOTE5 
        PTR_LA      $28, init_thread_union    #为0号进程准备内核栈,详情见NOTE6
        PTR_LI      sp, _THREAD_SIZE - 32 - PT_SIZE
        PTR_ADDU    sp, $28
        back_to_back_c0_hazard #NOTE7
        set_saved_sp    sp, t0, t1 #NOTE6
        PTR_SUBU    sp, 4 * SZREG       #NOTE8
    
        j       start_kernel  #NOTE9
        END(kernel_entry)
    
        __CPUINIT
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    NOTE1(kernel_entry_setup函数分析):

    Linux内核犹如一座巨大的迷宫,只有找到了正确的入口,才有可能找到出口。

    之前的分析得出的结论是Linux内核第一个调用的函数是KE,而KE第一个调用函数则是kernel_entry_setup,这才是真正执行的第一个函数,那么我们就从它开始吧。

    函数名称:kernel_entry_setup
    源代码文件:arch/mips/include/asm/mach-loongson/kernel-entry-init.h
    源代码:

    #ifndef __ASM_MACH_LOONGSON_KERNEL_ENTRY_H
    #define __ASM_MACH_LOONGSON_KERNEL_ENTRY_H
        .macro  kernel_entry_setup
    #ifdef CONFIG_CPU_LOONGSON3
        .set    push
        .set    mips64
        /* Set LPA on LOONGSON3 config3 */
        mfc0    t0, $16, 3
        or  t0, (0x1 << 7)
        mtc0    t0, $16, 3
        /* Set ELPA on LOONGSON3 pagegrain */
        mfc0    t0, $5, 1
        or  t0, (0x1 << 29)
        mtc0    t0, $5, 1
    #ifdef CONFIG_LOONGSON3_ENHANCEMENT
        /* Enable STFill Buffer */
        mfc0    t0, $16, 6
        or  t0, 0x100
        mtc0    t0, $16, 6
    #endif
        _ehb
        .set    pop
    #endif
        .endm
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通常情况下这个函数的实现与具体的CPU有关,阅读这段代码得结合LOONGSON CPU手册。
    这个函数的作用是设置CPU,由于LOONGSON是基于MIPS架构架构的,所以对CPU的设置是对CPU协处理器(CP0)的寄存器进行设置来设置CPU。

    对CP0寄存器操作的说明:

    MIPS刚刚出现的时候,最多可以有32个CP0寄存器。但是MIPS32/64可以允许多达256个寄存器。为了保持指令向前兼容,这是通过在CP0号(实际上是指令中以前编码为0的域)后附加3位的select域来实现的。这样代码

    mfc0    t0, $16, 3
    • 1

    解释为将寄存器号为16,查询号为3的寄存器的值读到t0寄存器中。

    代码分析:

    代码一:

    mfc0    t0, $16, 3
    or  t0, (0x1 << 7)
    mtc0    t0, $16, 3
    • 1
    • 2
    • 3

    将Config3寄存器的第7位(LPA)置一。

    代码二:

    mfc0    t0, $5, 1
    or  t0, (0x1 << 29)
    mtc0    t0, $5, 1
    • 1
    • 2
    • 3

    将PageGrain寄存器的第29位(ELPA)置一。

    代码三:

    mfc0    t0, $16, 6
    or  t0, 0x100
    mtc0    t0, $16, 6
    • 1
    • 2
    • 3

    将GSConfig3寄存器的第8位(STFill)置一。

    LOONGSON2000芯片手册:

    说明

    LPA:当支持大物理地址范围时为1,此时允许物理地址的范围超过236字节(此时LOONGSON物理地址的范围为248字节)。

    ELPA:当有LPA支持的时候,还会有一个额外的寄存器(PageGrain),同时EntryLo0-1EntryHi中的域的布局也会变化(如上图芯片手册所示)。

    STFill:GSConfig 寄存器用于对处理器核部分微结构相关的功能进行动态配置。自动写合并功能属于处理器核部分微结构的功能。

    自动写合并功能介绍

    对于现代CPU而言,性能瓶颈则是对于内存的访问。CPU的速度往往都比主存的高至少两个数量级。因此CPU都引入了L1_cache与L2_cache,更加高端的cpu还加入了L3_cache.很显然,这个技术引起了下一个问题:

    如果一个CPU在执行的时候需要访问的内存都不在cache中,CPU必须要通过内存总线到主存中取,那么在数据返回到CPU这段时间内(这段时间大致为cpu执行成百上千条指令的时间,至少两个数据量级)干什么呢? 答案是CPU会继续执行其他的符合条件的指令。比如CPU有一个指令序列 指令1 指令2 指令3 …, 在指令1时需要访问主存,在数据返回前CPU会继续后续的和指令1在逻辑关系上没有依赖的”独立指令“,CPU一般是依赖指令间的内存引用关系来判断的指令间的”独立关系”,具体细节可参见各CPU的文档。这也是导致CPU乱序执行指令的根源之一。

    以上方案是CPU对于读取数据延迟所做的性能补救的办法。对于写数据则会显得更加复杂一点:

    当CPU执行存储指令时,它会首先试图将数据写到离CPU最近的L1_cache, 如果此时CPU出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和CPU持平,其他的均明显低于CPU,L2_cache的速度大约比CPU慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,CPU就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。在请求L2_cache缓存行的所有权尚未完成时,CPU会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个Cache Line大小相同,一般都是64字节。这个缓冲区允许CPU在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了CPU写数据时Cache Miss时的性能影响。

    当后续的写操作需要修改相同的缓存行时,这些缓冲区变得非常有趣。在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。 这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的

    经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache)。如果我们能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。

    也许你要问,如果程序要读取已被写入缓冲区的某些数据,会怎么样?我们的硬件工程师已经考虑到了这点,在读取缓存之前会先去读取缓冲区的。

    这一切对我们的程序意味着什么?

    如果我们能在缓冲区被传输到外部缓存之前将其填满,那么将大大提高各级传输总线的效率。如何才能做到这一点呢?好的程序将大部分时间花在循环处理任务上。

    这些缓冲区的数量是有限的,且随CPU模型而异。在LOONGSON CPU中,同一时刻只能拿到4个。这意味着,在一个循环中,你不应该同时写超过4个不同的内存位置,否则你将不能体验到合并写的好处。

    从下面这个具体的例子来看吧:

    下面一段测试代码,从代码本身就能看出它的基本逻辑。

    测试代码:

    #include <unistd.h>
    #include <stdio.h>
    #include <sys/time.h>
    #include <stdlib.h>
    #include <limits.h>
    
    static const int iterations = 10000000;
    static const int items = 1<<24;
    static int mask;
    static int arrayA[1<<24];
    static int arrayB[1<<24];
    static int arrayC[1<<24];
    static int arrayD[1<<24];
    static int arrayE[1<<24];
    static int arrayF[1<<24];
    static int arrayG[1<<24];
    static int arrayH[1<<24];
    
    double run_one_case_for_8(){
            double start_time;
            double end_time;
            struct timeval start;
            struct timeval end;
            int i = iterations;
    
        gettimeofday(&start, NULL);
    
        while(--i != 0){
            int slot = i & mask;
            int value = i;
            arrayA[slot] = value;
            arrayB[slot] = value;
            arrayC[slot] = value;
            arrayD[slot] = value;
            arrayE[slot] = value;
            arrayF[slot] = value;
            arrayG[slot] = value;
            arrayG[slot] = value;
        }
    
        gettimeofday(&end, NULL);
            start_time = (double)start.tv_sec + (double)start.tv_usec/1000000.0;
            end_time = (double)end.tv_sec + (double)end.tv_usec/1000000.0;
            return end_time - start_time;
    }
    double run_two_case_for_4(){
        double start_time;
        double end_time;
        struct timeval start;
        struct timeval end;
        int i = iterations;
        gettimeofday(&start, NULL);
    
        while(--i != 0){
            int slot = i & mask;
            int value = i;
            arrayA[slot] = value;
            arrayB[slot] = value;
            arrayC[slot] = value;
            arrayD[slot] = value;
        }
    
        i = iterations;
    
        while(--i != 0){
            int slot = i & mask;
            int value = i;
            arrayE[slot] = value;
            arrayF[slot] = value;
            arrayG[slot] = value;
            arrayH[slot] = value;
        }
        gettimeofday(&end, NULL);
        start_time = (double)start.tv_sec + (double)start.tv_usec/1000000.0;
        end_time = (double)end.tv_sec + (double)end.tv_usec/1000000.0;
        return end_time - start_time;
    }
    
    int main(){
        mask = items -1;
        int i;
        printf("Test Begin---->\n");
        for(i=0;i<3;i++){
            printf("%d, run_one_case_for_8: %lf\n",i, run_one_case_for_8());
            printf("%d, run_two_case_for_4: %lf\n",i, run_two_case_for_4());
        }
        printf("Test End\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    测试环境:Fedora 21 64bits, 8G DDR3内存,LOONGSON-3A2000@999MHz
    相信很多人会认为run_two_case_for_4 的运行时间肯定要比run_one_case_for_8的长,因为至少前者多了一遍循环的i++操作。但是事实却不是这样。
    测试结果:
    测试截图
    原理:上面提到的合并写存入缓冲区离CPU很近,容量为64字节,很小了,估计很贵。数量也是有限的,个数是依赖CPU模型的,LOONGSON的CPU在同一时刻只能拿到4个(将上面的代码做改写可以证明)。

    因此,run_one_case_for_8函数中连续写入8个不同位置的内存,那么当4个数据写满了合并写缓冲时,cpu就要等待合并写缓冲区更新到L2cache中,因此CPU就被强制暂停了。然而在run_two_case_for_4函数中是每次写入4个不同位置的内存,可以很好的利用合并写缓冲区,因合并写缓冲区满到引起的CPU暂停的次数会大大减少,当然如果每次写入的内存位置数目小于4,也是一样的。虽然多了一次循环的i++操作(实际上你可能会问,i++也是会写入内存的啊,其实i这个变量保存在了寄存器上), 但是它们之间的性能差距依然非常大。

    从上面的例子可以看出,这些CPU底层特性对程序员并不是透明的。程序的稍微改变会带来显著的性能提升。对于存储密集型的程序,更应当考虑到此到特性。

    小结:

    kernel_entry_setup函数主要做了俩件事情:
    (1)使LOONGSON CPU支持大物理地址;
    (2)使LOONGSON CPU支持合并写功能。

    NOTE2(setup_c0_status_pri函数分析):

    内核版本:linux-3.10.X
    源代码文件:arch/mips/kernel/head.S
    源代码:

        mfc0    t0, CP0_STATUS
        or  t0, ST0_CU0|\set|0x1f|\clr
        xor t0, 0x1f|\clr
        mtc0    t0, CP0_STATUS
        .set    noreorder
        sll zero,3              # ehb
        .set    pop
        .endm
    .macro  setup_c0_status_pri 
    #ifdef CONFIG_64BIT
    #ifdef CONFIG_CPU_LOONGSON3
        setup_c0_status ST0_KX|ST0_MM 0  #(1)
    #else
        setup_c0_status ST0_KX 0
    #endif
    #else
    #ifdef CONFIG_CPU_LOONGSON3
        setup_c0_status ST0_MM 0 
    #else
        setup_c0_status 0 0
    #endif
    #endif
        .endm
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    setup_c0_status_pri函数与具体的CPU有关的汇编实现的,所以必须参考LOONGSON CPU手册才能知道这个函数到底做了什么。初步可以看出来对于LOONGSON 3A-2000来说这个函数 调用setup_c0_status函数,这个函数有个CONFIG_MIPS_MT_SMTC宏,这个宏是个开关,决定LOONGSON CPU是否支持硬件多线程,而LOONGSON CPU不支持硬件多线程(关于MIPS的多线程,请看See MIPS Run Linux和MIPS硬件多线程介绍)。我们结合手册看看setup_c0_status函数到底做了啥事,依照代码初步可以看出来这个函数主要设置CP0 Status 寄存器。
    芯片手册:
    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    小结:

    根据芯片手册及代码可以看出这个函数主要做了一下几个事情:
    (1)使能XTLB Refill列外向量;
    (2)使能协处理器2;
    (3)使能协处理器0;
    (4)关闭中断;
    (5)还有一些其他位设置,详情请看LOONGSON 3A-2000用户手册下册(7.21)。

    NOTE3(清除BBS段):

    源代码:

        PTR_LA      t0, __bss_start     
        LONG_S      zero, (t0)
        PTR_LA      t1, __bss_stop - LONGSIZE
    1:
        PTR_ADDIU   t0, LONGSIZE
        LONG_S      zero, (t0)
        bne     t0, t1, 1b
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这段代码很简单,以bss_start为起始地址,步调为LONGSIZE(LOONGSON 是64位处理器,所以LOONGSIZE为8),终点地址为 bss_stop-LONGSIZE做循环清零的事情。

    小结:

    这段代码做清除整个BSS段。

    NOTE4(BIOS传参):

    源代码:

        LONG_S      a0, fw_arg0     
        LONG_S      a1, fw_arg1
        LONG_S      a2, fw_arg2
        LONG_S      a3, fw_arg3
    • 1
    • 2
    • 3
    • 4

    固件将要传递的参数的地址放在了a0,a1,a2,a3寄存器中,通过这段代码将地址赋予fw_arg*等变量。

    小结:

    这段代码通过传递地址间接做参数传递。

    NOTE5:

    MTC0        zero, CP0_CONTEXT   
    • 1

    小结:

    清除CP0的Context寄存器,这个寄存器用来保存页表的起始地址(详情见芯片手册)。

    NOTE6(为0号进程准备内核栈):

    源代码:
    代码片段1:

        PTR_LA      $28, init_thread_union    
        PTR_LI      sp, _THREAD_SIZE - 32 - PT_SIZE
        PTR_ADDU    sp, $28
        set_saved_sp    sp, t0, t1 
    • 1
    • 2
    • 3
    • 4

    源文件:arch/mips/include/asm/stackframe.h
    代码片段2:

        .macro  set_saved_sp stackp temp temp2
        ASM_CPUID_MFC0  \temp, ASM_SMP_CPUID_REG
        LONG_SRL    \temp, SMP_CPUID_PTRSHIFT
        LONG_S  \stackp, kernelsp(\temp)
        .endm
    • 1
    • 2
    • 3
    • 4
    • 5

    这段代码主要做了什么?
    一图胜万语:
    这里写图片描述
    代码片段2将SP保存到kernelsp数组中去。
    其中kernelsp数组定义在arch/mips/kernel/setup.c中。

    unsigned long kernelsp[NR_CPUS]; #NR_CPUS CPU核的个数
    • 1

    注意:代码片段2将SP最终保存到kernelsp数组中,它是以CPUID号作为数组的偏移,而CPUID是存在CP0 Context寄存器中的,虽然前面已经清零,但是在这一刻,CPU将ID存到了这个寄存器中。
    由上图引发的一些问题:
    1、init_thread_union 是何物?它存在哪里?
    2、为何要将它的地址保存到GP?
    3、PT_SIZE作甚?
    4、为何将最后的SP保存到kernelsp数组中去?
    一图胜万语:
    这里写图片描述

    展开全文
  • mips架构内核启动时默认从BFC00000地址开始取指运行,也就是对应KSEG1,物理地址的1fc00000开始运行。 在文件arch/mips/kernel/vmlinux.lds中指定了第一个运行的函数: #define mips mips OUTPUT_ARCH(mips) ...
    mips架构内核启动时默认从BFC00000地址开始取指运行,也就是对应KSEG1,物理地址的1fc00000开始运行。

    在文件arch/mips/kernel/vmlinux.lds中指定了第一个运行的函数:
    #define mips mips
    OUTPUT_ARCH(mips)
    ENTRY(kernel_entry)
    PHDRS {
    	text PT_LOAD FLAGS(7);	/* RWX */
    	note PT_NOTE FLAGS(4);	/* R__ */
    }
    
    #ifdef CONFIG_32BIT
    	#ifdef CONFIG_CPU_LITTLE_ENDIAN
    		jiffies	 = jiffies_64;
    	#else
    		jiffies	 = jiffies_64 + 4;
    	#endif
    #else
    	jiffies	 = jiffies_64;
    #endif
    
    SECTIONS
    {
    #ifdef CONFIG_BOOT_ELF64
    	. = 0xffffffff80300000;
    #endif
    	. = VMLINUX_LOAD_ADDRESS;
    .......
    


    elf文件的入口地址,即bootloader移动完内核后,直接跳转到的地址,由ld写入elf的头中,其会依次使用下面的方法尝试设置入口点,遇到成功时则停止:
    a.命令行选项-e entry
    b.脚本中的ENTRY(symbol)
    c.如果由定义的start符号,则使用start符号
    d.如果存在.text的section,则使用第一个字节的地址
    e.地址0

    这里指定了地址位kernel_entry,那加载完内核后就跳转到这个地址。

    kernel_entry定义:arch/mips/kernel/head.s

    声明的入口地址的宏:

    #define NESTED(symbol, framesize, rpc)			\
    		.globl	symbol;				\
    		.align	2;				\
    		.type	symbol, @function;		\
    		.ent	symbol, 0;			 \
    symbol:		.frame	sp, framesize, rpc
    

    汇编伪指令frame用来声明堆栈布局:
    有三个参数:
    1,第一个参数framereg:声明用于访问局部堆栈的寄存器,一般为$sp
    2,第二个参数framesize:声明该函数已分配堆栈的大小,应该符合$sp+framesize=原来的$sp
    3,第三个参数returnreg:这个寄存器用来保存返回地址。

    对指令的一些宏定义封装:
    32位架构:
    #define PTR_ADD		add
    #define PTR_ADDU	addu
    #define PTR_ADDI	addi
    #define PTR_ADDIU	addiu
    #define PTR_SUB		sub
    #define PTR_SUBU	subu
    #define PTR_L		lw
    #define PTR_S		sw
    #define PTR_LA		la
    #define PTR_LI		li
    #define PTR_SLL		sll
    #define PTR_SLLV	sllv
    #define PTR_SRL		srl
    #define PTR_SRLV	srlv
    #define PTR_SRA		sra
    #define PTR_SRAV	srav
    #define	PTR_SCALESHIFT	2
    #define	PTR		.word
    #define	PTRSIZE		4
    #define	PTRLOG		2
    
    
    #define	LONG_ADD	add
    #define	LONG_ADDU	addu
    #define	LONG_ADDI	addi
    #define	LONG_ADDIU	addiu
    #define	LONG_SUB	sub
    #define	LONG_SUBU	subu
    #define	LONG_L		lw
    #define	LONG_S		sw
    #define	LONG_SP		swp
    #define	LONG_SLL	sll
    #define	LONG_SLLV	sllv
    #define	LONG_SRL	srl
    #define	LONG_SRLV	srlv
    #define	LONG_SRA	sra
    #define	LONG_SRAV	srav
    #define LONG		.word
    #define LONGSIZE	4
    #define LONGMASK	3
    #define LONGLOG		2
    
    64位架构:
    #define PTR_ADD		dadd
    #define PTR_ADDU	daddu
    #define PTR_ADDI	daddi
    #define PTR_ADDIU	daddiu
    #define PTR_SUB		dsub
    #define PTR_SUBU	dsubu
    #define PTR_L		ld
    #define PTR_S		sd
    #define PTR_LA		dla
    #define PTR_LI		dli
    #define PTR_SLL		dsll
    #define PTR_SLLV	dsllv
    #define PTR_SRL		dsrl
    #define PTR_SRLV	dsrlv
    #define PTR_SRA		dsra
    #define PTR_SRAV	dsrav
    #define PTR_SCALESHIFT	3
    #define PTR		.dword
    #define PTRSIZE		8
    #define PTRLOG		3
    
    #define	LONG_ADD	dadd
    #define	LONG_ADDU	daddu
    #define	LONG_ADDI	daddi
    #define	LONG_ADDIU	daddiu
    #define	LONG_SUB	dsub
    #define	LONG_SUBU	dsubu
    #define	LONG_L		ld
    #define	LONG_S		sd
    #define	LONG_SP		sdp
    #define	LONG_SLL	dsll
    #define	LONG_SLLV	dsllv
    #define	LONG_SRL	dsrl
    #define	LONG_SRLV	dsrlv
    #define	LONG_SRA	dsra
    #define	LONG_SRAV	dsrav
    #define LONG		.dword
    #define LONGSIZE	8
    #define LONGMASK	7
    #define LONGLOG		3
    

    kernel_entry函数定义:

    NESTED(kernel_entry, 16, sp)			# kernel entry point
    
    	kernel_entry_setup			#设置cpu的cp0寄存器
    
    	setup_c0_status_pri			#设置cpu的状态寄存器
    
    	PTR_LA	t0, 0f
    	jr	t0     #跳转到下面0标号处运行
    0:	#给bss段清零
    	PTR_LA		t0, __bss_start		#_bss_start在vmlinux.lds中定义
    	LONG_S		zero, (t0)
    	PTR_LA		t1, __bss_stop - LONGSIZE #一共需要清多少次
    1:
    	PTR_ADDIU	t0, LONGSIZE	#下一个要清零的地址
    	LONG_S		zero, (t0)
    	bne		t0, t1, 1b
    
    	LONG_S		a0, fw_arg0		#保存bios传过来的参数
    	LONG_S		a1, fw_arg1		#arg0表示参数个数
    	LONG_S		a2, fw_arg2
    	LONG_S		a3, fw_arg3
    
    	MTC0		zero, CP0_CONTEXT	#context register清零
    
    	#设置栈指针
    	PTR_LA		$28, init_thread_union  #加载初始化进程
    	PTR_LI		sp, _THREAD_SIZE - 32 - PT_SIZE
    	PTR_ADDU	sp, $28                #设置栈指针
    	back_to_back_c0_hazard
    	set_saved_sp	sp, t0, t1
    	PTR_SUBU	sp, 4 * SZREG		# init stack pointer
    
    	j		start_kernel		#最后跳转到函数start_kernel函数
    	END(kernel_entry)   
    

    最后函数就跳转到了start_kernel中运行了,不会再返回。

    看一下这里调用的两个函数做了什么工作:

    kernel_entry_setup函数:

    	.macro	kernel_entry_setup
    #ifdef CONFIG_CPU_LOONGSON3
    	.set	push
    	.set	mips64
    	/* Set LPA on LOONGSON3 config3 */
    	mfc0	t0, $16, 3	#设置cp0寄存器中的config3寄存器的LPA位,打开大物理页
    	or	t0, (0x1 << 7)
    	mtc0	t0, $16, 3
    	/* Set ELPA on LOONGSON3 pagegrain */
    	mfc0	t0, $5, 1	 #打开大物理页(cp0寄存器)
    	or	t0, (0x1 << 29)
    	mtc0	t0, $5, 1
    	/* Enable STFill Buffer */
    	mfc0	t0, $16, 6	#在loongson手册中没有找到这个寄存器
    	or	t0, 0x100
    	mtc0	t0, $16, 6
    	_ehb
    	.set	pop
    #endif
    	.endm
    
    看出来这里所作的主要的工作就是打开物理大页的使用。

    setup_c0_status_pri函数:

    	.macro	setup_c0_status_pri
    	setup_c0_status ST0_KX|ST0_MM 0 #打开KX以及MM位,第0位清零
    	.endm
    

    setup_c0_status函数定义:

    	.macro	setup_c0_status set clr
    	.set	push
    	#设置状态寄存器
    	mfc0	t0, CP0_STATUS    #取出status寄存器原来的值
    	or	t0, ST0_CU0|\set|0x1f|\clr  #给CUO位,低五位,以及set和clr指定位置位
    	xor	t0, 0x1f|\clr    #把低五位以及clr指定位清零
    	mtc0	t0, CP0_STATUS	 #写回到寄存器中
    	.set	noreorder
    	sll	zero,3				# ehb
    	.set	pop
    	.endm
    

    这里主要就是对cp0辅助寄存器中status进行操作。设置特定的位。

    然后看一下主线函数start_kernel:

    asmlinkage void __init start_kernel(void)
    {
    	char * command_line;
    	extern const struct kernel_param __start___param[], __stop___param[];
    
    	/*
    	 * Need to run as early as possible, to initialize the
    	 * lockdep hash:
    	 */
    	lockdep_init();
    	smp_setup_processor_id();
    	debug_objects_early_init();
    
    	/*
    	 * Set up the the initial canary ASAP:
    	 */
    	boot_init_stack_canary();
    
    	cgroup_init_early();
    
    	local_irq_disable();
    	early_boot_irqs_disabled = true;
    
    /*
     * Interrupts are still disabled. Do necessary setups, then
     * enable them
     */
    	boot_cpu_init();
    	page_address_init();
    	pr_notice("%s", linux_banner);
    	setup_arch(&command_line);       #板级代码初始化
    	mm_init_owner(&init_mm, &init_task);
    	mm_init_cpumask(&init_mm);
    	setup_command_line(command_line);
    	setup_nr_cpu_ids();
    	setup_per_cpu_areas();
    	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */
    
    	build_all_zonelists(NULL, NULL);
    	page_alloc_init();
    
    	pr_notice("Kernel command line: %s\n", boot_command_line);
    	parse_early_param();
    	parse_args("Booting kernel", static_command_line, __start___param,
    		   __stop___param - __start___param,
    		   -1, -1, &unknown_bootoption);
    
    	jump_label_init();
    
    	/*
    	 * These use large bootmem allocations and must precede
    	 * kmem_cache_init()
    	 */
    	setup_log_buf(0);
    	pidhash_init();
    	vfs_caches_init_early();
    	sort_main_extable();
    	trap_init();
    	mm_init();
    
    	/*
    	 * Set up the scheduler prior starting any interrupts (such as the
    	 * timer interrupt). Full topology setup happens at smp_init()
    	 * time - but meanwhile we still have a functioning scheduler.
    	 */
    	sched_init();
    	/*
    	 * Disable preemption - early bootup scheduling is extremely
    	 * fragile until we cpu_idle() for the first time.
    	 */
    	preempt_disable();
    	if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
    		local_irq_disable();
    	idr_init_cache();
    	rcu_init();
    	tick_nohz_init();
    	rcu_init_nohz();
    	context_tracking_init();
    	radix_tree_init();
    	/* init some links before init_ISA_irqs() */
    	early_irq_init();
    	init_IRQ();
    	tick_init();
    	init_timers();
    	hrtimers_init();
    	softirq_init();
    	timekeeping_init();
    	time_init();
    	perf_event_init();
    	profile_init();
    	call_function_init();
    	WARN(!irqs_disabled(), "Interrupts were enabled early\n");
    	early_boot_irqs_disabled = false;
    	local_irq_enable();
    
    	kmem_cache_init_late();
    
    	/*
    	 * HACK ALERT! This is early. We're enabling the console before
    	 * we've done PCI setups etc, and console_init() must be aware of
    	 * this. But we do want output early, in case something goes wrong.
    	 */
    	console_init();
    	if (panic_later)
    		panic(panic_later, panic_param);
    
    	lockdep_info();
    
    	/*
    	 * Need to run this when irqs are enabled, because it wants
    	 * to self-test [hard/soft]-irqs on/off lock inversion bugs
    	 * too:
    	 */
    	locking_selftest();
    
    #ifdef CONFIG_BLK_DEV_INITRD
    	if (initrd_start && !initrd_below_start_ok &&
    	    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
    		pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
    		    page_to_pfn(virt_to_page((void *)initrd_start)),
    		    min_low_pfn);
    		initrd_start = 0;
    	}
    #endif
    	page_cgroup_init();
    	debug_objects_mem_init();
    	kmemleak_init();
    	setup_per_cpu_pageset();
    	numa_policy_init();
    	if (late_time_init)
    		late_time_init();
    	sched_clock_init();
    	calibrate_delay();
    	pidmap_init();
    	anon_vma_init();
    #ifdef CONFIG_X86
    	if (efi_enabled(EFI_RUNTIME_SERVICES))
    		efi_enter_virtual_mode();
    #endif
    	thread_info_cache_init();
    	cred_init();
    	fork_init(totalram_pages);
    	proc_caches_init();
    	buffer_init();
    	key_init();
    	security_init();
    	dbg_late_init();
    	vfs_caches_init();
    	signals_init();
    	/* rootfs populating might need page-writeback */
    	page_writeback_init();
    #ifdef CONFIG_PROC_FS
    	proc_root_init();
    #endif
    	cgroup_init();
    	cpuset_init();
    	taskstats_init_early();
    	delayacct_init();
    
    	check_bugs();
    
    	acpi_early_init(); /* before LAPIC and SMP init */
    	sfi_init_late();
    
    	if (efi_enabled(EFI_RUNTIME_SERVICES)) {
    		efi_late_init();
    		efi_free_boot_services();
    	}
    
    	ftrace_init();
    
    	/* Do the rest non-__init'ed, we're now alive */
    	rest_init();
    }
    

    接下来主要的工作就是按照这里函数的初始化流程进行分析,以此来熟悉内核的启动流程。











    展开全文
  • 本文件基于mips芯片架构linux系统,可以以此对mipslinux系统进行系统的了解。
  • 老样子先看函数把. void __init prom_init(void) { //初始化命令 prom_init_cmdline(); //初始化环境 prom_init_env();...//这里所作的是把桥片进行了一些配置 ... loongson_pch->early_config();...

    老样子先看函数把.

    
    void __init prom_init(void)
    {
    //初始化命令
    	prom_init_cmdline();
    //初始化环境
    	prom_init_env();
    //这里所作的是把桥片进行了一些配置
    	if (loongson_pch)
    		loongson_pch->early_config();
    
    #ifdef CONFIG_NUMA
    	prom_init_numa_memory();
    #else
    	prom_init_memory();
    #endif
    
    //初始化串口基地址信息
    	prom_init_uart_base();
    #if defined(CONFIG_SMP)
    	register_smp_ops(&loongson3_smp_ops);
    #endif
    	board_nmi_handler_setup = mips_nmi_setup;
    #ifdef CONFIG_CPU_LOONGSON3
    //这里是设置cpu的HT总线的窗口信息(也就是涉及到cpu的映射地址)
    	if (!hw_coherentio) {
    		/* set HT-access uncache */
    		switch (cputype) {
    		case Loongson_3A:
    			HT_uncache_enable_reg0	= 0xc0000000; //Low 256M
    			HT_uncache_base_reg0	= 0x0080fff0;
    			HT_uncache_enable_reg1	= 0xc0000000; //Node 0
    			HT_uncache_base_reg1	= 0x0000e000;
    			HT_uncache_enable_reg2	= 0xc0100000; //Node 1
    			HT_uncache_base_reg2	= 0x2000e000;
    			HT_uncache_enable_reg3	= 0xc0200000; //Node 2/3
    			HT_uncache_base_reg3	= 0x4000c000;
    		writeq(0x0000202000000000, (void *)0x900000003ff02708);
    		writeq(0xffffffe000000000, (void *)0x900000003ff02748);
    		writeq(0x0000300000000086, (void *)0x900000003ff02788);
    			break;
    		default:
    			break;
    		}
    	} 
    		printk("SET HT_DMA CACHED\n");
    	}
    	__sync();
    #endif /* CONFIG_CPU_LOONGSON3 */
    }
    

    今天只分析函数    prom_init_cmdline();,    prom_init_env();

    分别是接收bios传递的信息,和初始化环境

    void __init prom_init_cmdline(void)
    {
    	int prom_argc;		//记录参数的个数
    	/* pmon passes arguments in 32bit pointers */
    	int *_prom_argv;	 //记录参数的内容
    	int i;
    	long l;
    
    	/* fw_arg0和fw_arg1在head.s文件中初始化,也就是记录了bios传递的参数 */
    	prom_argc = fw_arg0;     //参数个数
    	_prom_argv = (int *)fw_arg1;   //参数内容
    
    	/*第一个参数为什么是g呢?因为pmon下载内核后运行的命令是g */
    	arcs_cmdline[0] = '\0';  //第一个参数是g,所以吧第一个空出来
    	for (i = 1; i < prom_argc; i++) {
    		l = (long)_prom_argv[i];
    //判断参数有没有超过规定的长度
    		if (strlen(arcs_cmdline) + strlen(((char *)l) + 1)
    		    >= sizeof(arcs_cmdline))
    			break;
    //strcat的作用就是合并两个字符串,在这里就是把参数内容放入到arcs_cmdline数组中,并且
    //参数之间由空格隔开
    		strcat(arcs_cmdline, ((char *)l));
    		strcat(arcs_cmdline, " ");
    	}
    	prom_init_machtype();
    }
    

    初始化机器类型

    #define MACHTYPE_LEN 50
    //系统定义的机器的类型
    static const char *system_types[] = {
    	[MACH_LOONGSON_UNKNOWN]		"unknown loongson machine",
    	[MACH_LEMOTE_FL2E]		"lemote-fuloong-2e-box",
    	[MACH_LEMOTE_FL2F]		"lemote-fuloong-2f-box",
    	[MACH_LEMOTE_ML2F7]		"lemote-mengloong-2f-7inches",
    	[MACH_LEMOTE_YL2F89]		"lemote-yeeloong-2f-8.9inches",
    	[MACH_DEXXON_GDIUM2F10]		"dexxon-gdium-2f",
    	[MACH_LEMOTE_NAS]		"lemote-nas-2f",
    	[MACH_LEMOTE_LL2F]		"lemote-lynloong-2f",
    	[MACH_LOONGSON_GENERIC]		"generic-loongson-machine",
    	[MACH_LOONGSON_END]		NULL,
    };
    
     //初始化系统的机器类型
    void __init prom_init_machtype(void)
    {
    	char *p, str[MACHTYPE_LEN + 1];
    	int machtype = MACH_LEMOTE_FL2E;  //默认的初始值
    
    	mips_machtype = LOONGSON_MACHTYPE; //在板级setup文件中定义,初始时位unknow
    //在arcs_cmdline中查找有没有定义machtype
    	p = strstr(arcs_cmdline, "machtype=");
    //没有传递就使用函数mach_prom_init_machtype初始化一下,一般是定义的
    	if (!p) {
    		mach_prom_init_machtype();
    		return;
    	}
    //查看参数machtype的长度
    	p += strlen("machtype=");
    //把参数machtype拷贝到str中
    	strncpy(str, p, MACHTYPE_LEN);
    	str[MACHTYPE_LEN] = '\0';
    //查找str中有没有空格,如果由就用\0替换
    	p = strstr(str, " ");
    	if (p)
    		*p = '\0';
    //在system_types中和bios传递过来的machtype对比,如果由匹配的就把mips_machtype设置成
    //对应的匹配值
    	for (; system_types[machtype]; machtype++)
    		if (strstr(system_types[machtype], str)) {
    			mips_machtype = machtype;
    			break;
    		}
    }

    prom_init_env环境初始化函数:

    主要是:

    1,解析bios传递过来的参数

    2,初始化环境

    先看一下封装参数的一些结构体,看不懂没关系,了解就行。

    对应使用到的结构体,这些结构体都是BIOS传递过来的信息.
    这些信息都是再bios中定义的,并封装了要传递给内核的信息。
    目前我的平台上的这些信息是再bios中写死的,也就是说是人工把这些结构体初始化的。

    ----------------------------------------------------------------------
    struct boot_params{
    	struct efi_loongson efi;
    	struct efi_reset_system_t reset_system;
    };
    ----------------------------------------------------------------------
    struct efi_loongson {
    	u64 mps;	/* MPS table */
    	u64 acpi;	/* ACPI table (IA64 ext 0.71) */
    	u64 acpi20;	/* ACPI table (ACPI 2.0) */
    	struct smbios_tables smbios;	/* SM BIOS table */
    	u64 sal_systab;	/* SAL system table */
    	u64 boot_info;	/* boot info table */
    };
    ----------------------------------------------------------------------
    struct smbios_tables {
    	u16 vers;     /* version of smbios */
    	u64 vga_bios; /* vga_bios address */
    	struct loongson_params lp;
    };
    ----------------------------------------------------------------------
    struct loongson_params{
    	u64 memory_offset;	/* efi_memory_map_loongson struct offset */
    	u64 cpu_offset;		/* efi_cpuinfo_loongson struct offset */
    	u64 system_offset;	/* system_loongson struct offset */
    	u64 irq_offset; 	/* irq_source_routing_table struct offset */
    	u64 interface_offset;	/* interface_info struct offset */
    	u64 special_offset;    /*loongson_special_attribute struct offset*/
    	u64 boarddev_table_offset;  /* board_devices offset */
    };
    ----------------------------------------------------------------------
    struct efi_reset_system_t{
    	u64 ResetCold;
    	u64 ResetWarm;
    	u64 ResetType;
    	u64 Shutdown;
    	u64 DoSuspend; /* NULL if not support */
    };
    
    ----------------------------------------------------------------------
    struct efi_cpuinfo_loongson {
    	u16 vers;     /* version of efi_cpuinfo_loongson */
    	u32 processor_id; /* PRID, e.g. 6305, 6306 */
    	enum loongson_cpu_type cputype; /* 3A, 3B, etc. */
    	u32 total_node;   /* num of total numa nodes */
    	u16 cpu_startup_core_id; /* Core id */
    	u16 reserved_cores_mask;
    	u32 cpu_clock_freq; /* cpu_clock */
    	u32 nr_cpus;
    }__attribute__((packed));
    
    struct efi_cpuinfo_loongson *ecpu;
    
    enum loongson_cpu_type
    {
    	Legacy_2E = 0x0,
    	Legacy_2F = 0x1,
    	Legacy_3A = 0x2,
    	Legacy_3B = 0x3,
    	Legacy_1A = 0x4,
    	Legacy_1B = 0x5,
    	Legacy_2G = 0x6,
    	Legacy_2H = 0x7,
    	Loongson_1A = 0x100,
    	Loongson_1B = 0x101,
    	Loongson_2E = 0x200,
    	Loongson_2F = 0x201,
    	Loongson_2G = 0x202,
    	Loongson_2H = 0x203,
    	Loongson_3A = 0x300,
    	Loongson_3B = 0x301
    };
    ----------------------------------------------------------------------
    struct efi_memory_map_loongson{
    	u16 vers;	/* version of efi_memory_map */
    	u32 nr_map;	/* number of memory_maps */
    	u32 mem_freq;	/* memory frequence */
    	struct mem_map{
    		u32 node_id;	/* node_id which memory attached to */
    		u32 mem_type;	/* system memory, pci memory, pci io, etc. */
    		u64 mem_start;	/* memory map start address */
    		u32 mem_size;	/*each memory_map size, not the total size*/
    	}map[LOONGSON3_BOOT_MEM_MAP_MAX];
    }__attribute__((packed));
    
    struct efi_memory_map_loongson *emap;
    ----------------------------------------------------------------------
    #define MAX_UARTS 64
    struct uart_device {
    	u32 iotype; /* see include/linux/serial_core.h */
    	u32 uartclk;
    	u32 int_offset;
    	u64 uart_base;
    }__attribute__((packed));
    
    #define MAX_SENSORS 64
    #define SENSOR_TEMPER  0x00000001
    #define SENSOR_VOLTAGE 0x00000002
    #define SENSOR_FAN     0x00000004
    struct sensor_device {
    	char name[32];  /* a formal name */
    	char label[64]; /* a flexible description */
    	u32 type;       /* SENSOR_* */
    	u32 id;         /* instance id of a sensor-class */
    	u32 fan_policy; /*see arch/mips/include/asm/mach-loongson/loongson_hwmon.h */
    	u32 fan_percent;/* only for constant speed policy */
    	u64 base_addr;  /* base address of device registers */
    }__attribute__((packed));
    
    struct system_loongson{
    	u16 vers;     /* version of system_loongson */
    	u32 ccnuma_smp; /* 0: no numa; 1: has numa */
    	u32 sing_double_channel; /* 1:single; 2:double */
    	u32 nr_uarts;
    	struct uart_device uarts[MAX_UARTS];
    	u32 nr_sensors;
    	struct sensor_device sensors[MAX_SENSORS];
    	char has_ec;
    	char ec_name[32];
    	u64 ec_base_addr;
    	char has_tcm;
    	char tcm_name[32];
    	u64 tcm_base_addr;
    	u64 workarounds; /* see workarounds.h */
    }__attribute__((packed));
    
    
    struct system_loongson *esys;
    ----------------------------------------------------------------------
    #define MAX_RESOURCE_NUMBER 128
    struct resource_loongson {
    	u64 start; /* resource start address */
    	u64 end;   /* resource end address */
    	char name[64];
    	u32 flags;
    };
    
    struct board_devices{
    	char name[64];    /* hold the device name */
    	u32 num_resources; /* number of device_resource */
    	struct resource_loongson resource[MAX_RESOURCE_NUMBER]; /* for each device's resource */
    	/* arch specific additions */
    	struct archdev_data archdata;
    };
    
    struct board_devices *eboard;
    ----------------------------------------------------------------------
    struct irq_source_routing_table {
    	u16 vers;
    	u16 size;
    	u16 rtr_bus;
    	u16 rtr_devfn;
    	u32 vendor;
    	u32 device;
    	u32 PIC_type;   /* conform use HT or PCI to route to CPU-PIC */
    	u64 ht_int_bit; /* 3A: 1<<24; 3B: 1<<16 */
    	u64 ht_enable;  /* irqs used in this PIC */
    	u32 node_id;    /* node id: 0x0-0; 0x1-1; 0x10-2; 0x11-3 */
    	u64 pci_mem_start_addr;
    	u64 pci_mem_end_addr;
    	u64 pci_io_start_addr;
    	u64 pci_io_end_addr;
    	u64 pci_config_addr;
    	u16 dma_mask_bits;
    	u16 dma_noncoherent;
    }__attribute__((packed));
    
    
    struct irq_source_routing_table *eirq_source;
    ----------------------------------------------------------------------
    struct interface_info{
    	u16 vers; /* version of the specificition */
    	u16 size;
    	u8  flag;
    	char description[64];
    }__attribute__((packed));
    
    struct interface_info *einter;
    ----------------------------------------------------------------------
    struct loongson_special_attribute{
    	u16 vers;     /* version of this special */
    	char special_name[64]; /* special_atribute_name */
    	u32 loongson_special_type; /* type of special device */
    	struct resource_loongson resource[MAX_RESOURCE_NUMBER]; /* for each device's resource */
    };
    
    struct loongson_special_attribute *especial;
    ----------------------------------------------------------------------
    

    void __init prom_init_env(void)
    {
    	/* pmon passes arguments in 32bit pointers */
    	unsigned int processor_id;
            char *bios_info;
            char *board_info;
    
    	int i;
    
    	/* firmware arguments are initialized in head.S */
    	boot_p = (struct boot_params *)fw_arg2;
    	loongson_p = &(boot_p->efi.smbios.lp);
    //接收bios传递的信息,为解析做准备
    	esys= (struct system_loongson *)((u64)loongson_p + loongson_p->system_offset);
    	ecpu= (struct efi_cpuinfo_loongson *)((u64)loongson_p + loongson_p->cpu_offset);
    	emap= (struct efi_memory_map_loongson *)((u64)loongson_p + loongson_p->memory_offset);
    	eboard	= (struct board_devices *)((u64)loongson_p + loongson_p->boarddev_table_offset);
    	einter= (struct interface_info *)((u64)loongson_p + loongson_p->interface_offset);
    	eirq_source = (struct irq_source_routing_table *)((u64)loongson_p + loongson_p->irq_offset);
    	especial = (struct loongson_special_attribute *)((u64)loongson_p + loongson_p->special_offset);
    //根据cpu的类型,进行cpu一些特定的配置
    	cputype = ecpu->cputype;
    	switch (cputype) {
    	case Loongson_3A:
    		cores_per_node = 4;
    		cores_per_package = 4;
    		smp_group[0] = 0x900000003ff01000;
    		smp_group[1] = 0x900010003ff01000;
    		smp_group[2] = 0x900020003ff01000;
    		smp_group[3] = 0x900030003ff01000;
    		ht_control_base = 0x90000EFDFB000000;
    		loongson_chipcfg[0] = 0x900000001fe00180;
    		loongson_chipcfg[1] = 0x900010001fe00180;
    		loongson_chipcfg[2] = 0x900020001fe00180;
    		loongson_chipcfg[3] = 0x900030001fe00180;
    		loongson_chiptemp[0] = 0x900000001fe0019c;
    		loongson_chiptemp[1] = 0x900010001fe0019c;
    		loongson_chiptemp[2] = 0x900020001fe0019c;
    		loongson_chiptemp[3] = 0x900030001fe0019c;
    		loongson_freqctrl[0] = 0x900000001fe001d0;
    		loongson_freqctrl[1] = 0x900010001fe001d0;
    		loongson_freqctrl[2] = 0x900020001fe001d0;
    		loongson_freqctrl[3] = 0x900030001fe001d0;
    		loongson_workarounds = WORKAROUND_CPUFREQ;
    		break;
    	default:
    		cores_per_node = 1;
    		cores_per_package = 1;
    		loongson_chipcfg[0] = 0x900000001fe00180;
    	}
    //确定cpu的核数
    	nr_cpus_loongson = ecpu->nr_cpus;
    //确定cpu的频率
    	cpu_clock_freq = ecpu->cpu_clock_freq;
    //确定cpu的启动核的id
    	loongson_boot_cpu_id = ecpu->cpu_startup_core_id;
    	loongson_reserved_cpus_mask = ecpu->reserved_cores_mask;
    #ifdef CONFIG_KEXEC
    //启动核的中断基地址
    	loongson_boot_cpu_id = read_c0_ebase() & 0x3ff;
    	for (i = 0; i < loongson_boot_cpu_id; i++)
    		loongson_reserved_cpus_mask |= (1<<i);
    #endif
    	if (nr_cpus_loongson > NR_CPUS || nr_cpus_loongson == 0)
    		nr_cpus_loongson = NR_CPUS;
    	nr_nodes_loongson = (nr_cpus_loongson + cores_per_node - 1) / cores_per_node;
    //pci_mem的开始和结束地址
    	pci_mem_start_addr = eirq_source->pci_mem_start_addr;
    	pci_mem_end_addr = eirq_source->pci_mem_end_addr;
    //pciio的基地址
    	loongson_pciio_base = eirq_source->pci_io_start_addr;
    //DMA的掩码
    	loongson_dma_mask_bits = eirq_source->dma_mask_bits;
    	if (loongson_dma_mask_bits < 32 || loongson_dma_mask_bits > 64)
    		loongson_dma_mask_bits = 32;
    //判断cpu是否具有dma硬件一致性 
    	if (((read_c0_prid() & 0xf) == PRID_REV_LOONGSON3A_R2)
    		|| ((read_c0_prid() & 0xf) == PRID_REV_LOONGSON3A_R3)) {
    		eirq_source->dma_noncoherent = 1;
    	}
    //根据启动时传递的参数,来配置是否使能dmah一致性 
    	if (strstr(arcs_cmdline, "cached"))
    		eirq_source->dma_noncoherent = 0;
    	if (strstr(arcs_cmdline, "uncached"))
    		eirq_source->dma_noncoherent = 1;
    
    	if (strstr(arcs_cmdline, "hwmon"))
    		loongson_hwmon = 1;
    	else
    		loongson_hwmon = 0;
    
    	hw_coherentio = !eirq_source->dma_noncoherent;
    //判断使用的桥片是2H还是rs780,这里使用的是2H
    	if (strstr(eboard->name,"2H")) {
    		loongson_pch = &ls2h_pch;
    		loongson_ec_sci_irq = 0x80;
    	}
    	else {
    		loongson_pch = &rs780_pch;
    		loongson_ec_sci_irq = 0x07;
    	}
    //解析bios信息
            /* parse bios info */
            strcpy(_bios_info, einter->description);
            bios_info = _bios_info;
            bios_vendor = strsep(&bios_info, "-");
            strsep(&bios_info, "-");
            strsep(&bios_info, "-");
            bios_release_date = strsep(&bios_info, "-");
            if (!bios_release_date)
                    bios_release_date = especial->special_name;
    //解析板卡信息
            /* parse board info */
            strcpy(_board_info, eboard->name);
            board_info = _board_info;
            board_manufacturer = strsep(&board_info, "-");
    //解析poweroff,restart,suspend的地址信息
    	poweroff_addr = boot_p->reset_system.Shutdown;
    	restart_addr = boot_p->reset_system.ResetWarm;
    	suspend_addr = boot_p->reset_system.DoSuspend;
    //vga地址
    	vgabios_addr = boot_p->efi.smbios.vga_bios;
    //先把loongson_ecname清零,再给其赋值
    	memset(loongson_ecname, 0, 32);
    	if (esys->has_ec)
    		memcpy(loongson_ecname, esys->ec_name, 32);
    	loongson_workarounds |= esys->workarounds;
    //串口数
    	loongson_nr_uarts = esys->nr_uarts;
    	if (loongson_nr_uarts < 1 || loongson_nr_uarts > MAX_UARTS)
    		loongson_nr_uarts = 1;
    //复制bios传递的串口信息到loongsin_uarts中
    	memcpy(loongson_uarts, esys->uarts,
    		sizeof(struct uart_device) * loongson_nr_uarts);
    //读取sensors的数量,并把bios传递过来的其信息复制到loongson_sensors中
    	loongson_nr_sensors = esys->nr_sensors;
    	if (loongson_nr_sensors > MAX_SENSORS)
    		loongson_nr_sensors = 0;
    	if (loongson_nr_sensors)
    		memcpy(loongson_sensors, esys->sensors,
    			sizeof(struct sensor_device) * loongson_nr_sensors);
    //根据cpu_id配置cpu的频率
    	if (cpu_clock_freq == 0) {
    		processor_id = (¤t_cpu_data)->processor_id;
    		switch (processor_id & PRID_REV_MASK) {
    		case PRID_REV_LOONGSON3A_R1:
    		case PRID_REV_LOONGSON3A_R2:
    		case PRID_REV_LOONGSON3A_R3:
    			cpu_clock_freq = 900000000;
    			break;
    		default:
    			cpu_clock_freq = 100000000;
    			break;
    		}
    	}
    }
    







    展开全文
  • 标准设备树串口驱动早期初始化

    本设备树串口驱动基于linux3.0.4内核版本

    start kernel()

    从start kernel函数开始追踪,函数原型如下,函数实体路径见./init/main.c。可以看到该函数实体包含两个有关于串口初始化函数的调用实体,第一个是early_printk函数实现,第二个是标准设备树串口驱动实现,下面本文将会逐步进行追踪分析。

    asmlinkage void __init start_kernel(void)
    {
    	.............
    	setup_arch(&command_line);
    	.............
    	console_init();
    	.............
    }

    setup_arch()

    首先,追踪分析early_printk初始化调用流程,继续追踪setup_arch()函数实体,函数原型如下,函数实体路径见./arch/mips/kernel/setup.c。第一个函数功能是探测cpu属于什么架构,常见有arm/mips/x86架构,本文剖析是基于探测mips架构。第二个函数功能是解析boot传参环境变量与命令行,初始化linux操作系统环境变量。第三个函数就是本文重点关注setup_early_printk()函数实体,即串口早期的打印初始化流程。

    void __init setup_arch(char **cmdline_p)
    {
        cpu_probe();
        prom_init();
    #ifdef CONFIG_EARLY_PRINTK
        setup_early_printk();
    #endif
      .............
      .............

    setup_early_printk()

    继续追踪setup_early_printk()函数实体,函数原型如下,函数实体路径见
    ./arch/mips/kernel/early_printk.c,关于register_console()注册函数会在标准设备树串口驱动注册进行讲解,不在此处赘述。本文重点关注结构体struct early_console内成员变量.write = early_console_write,可以看到函数实体early_console_write()实现了简单prom_putchar()打印封装。对于prom_putchar()这函数实现,由于不同平台硬件架构会有不同的串口地址与之对应,故需要开发者自行设计函数存放路径与函数实体。

    extern void prom_putchar(char);
    static void __init
    early_console_write(struct console *con, const char *s, unsigned n)
    {
        while (n-- && *s) {
            if (*s == '\n')
                prom_putchar('\r');
            prom_putchar(*s);
            s++;
        }
    }
    static struct console early_console __initdata = {
        .name   = "early",
        .write  = early_console_write,
        .flags  = CON_PRINTBUFFER | CON_BOOT,
        .index  = -1
    };
    static int early_console_initialized __initdata;
    void __init setup_early_printk(void)
    {
        if (early_console_initialized)
            return;
        early_console_initialized = 1;
        register_console(&early_console);
    }

    至此early printk串口流程分析完成,比较简单。
    题外寄语:若不作此处早期的串口打印初始化流程分析,博主觉得对于介绍下面的标准设备树串口驱动注册不是一个完整的系统剖析文章。

    console_init()

    接下来,回到start kernel函数,追踪分析console_init()函数实体,函数原型如下,函数实体路径见drivers/tty/tty_io.c。tty_ldisc_begin()函数暂时不作追踪分析,直接看while流程处理,call作了初始化赋值__con_initcall_start,跑while循环调用call函数一直到__con_initcall_end结束,那么此处call上面挂载一个什么功能的函数实体,

    void __init console_init(void)                                                                         
    {
        initcall_t *call;
        tty_ldisc_begin();
        call = __con_initcall_start;
        while (call < __con_initcall_end) {
            (*call)();
            call++;
        }
    }

    继续追踪源码,函数原型如下,函数实体路径见./include/asm-generic/vmlinux.lds.h
    ,可以看到__con_initcall_start与__con_initcall_end之间多了一个.con_initcall.init,全部被定义成一个CON_INITCALL宏。CON_INITCALL宏在内核启动流程之前会被翻译成符号表,在内核启动过程进行初始化。有关于vmlinux.lds.h文件与vmlinux.lds.h文件编译成System.map符号表文件,本文不作赘述,感兴趣的小伙伴可以继续追踪研究System.map

    #define CON_INITCALL                            \
            VMLINUX_SYMBOL(__con_initcall_start) = .;       \                                                                       
            *(.con_initcall.init)                   \
            VMLINUX_SYMBOL(__con_initcall_end) = .;

    继续追踪源码.con_initcall.init实体,函数原型如下,函数实体路径见./include/linux/init.h
    ,可以看出.con_initcall.init实体被定义成一个宏console_initcall(fn),其中fn又是什么,本文继续追踪console_initcall(fn)

    #define console_initcall(fn) \                                                                                                  
    static initcall_t __initcall_##fn \
    __used __section(.con_initcall.init) = fn

    console_initcall(fn)

    继续追踪函数实体console_initcall(fn)初始化赋值,追踪分析变量fn在什么地方被赋值,函数初始化如下,函数初始化路径见./drivers/tty/serial/8250.c。可以看到这里的fn等于标准设备树串口驱动serial8250_console_init()。至此,层次越来越清楚了,追到了熟悉标准串口驱动。

    static int __init serial8250_console_init(void)
    {
        if (nr_uarts > UART_NR)
            nr_uarts = UART_NR;
        serial8250_isa_init_ports();
        register_console(&serial8250_console);
        return 0;
    }
    console_initcall(serial8250_console_init);

    小结:
    1.early_printk初始化流程:start kernel()–>setup_arch()–>setup_early_printk()
    2.console 初始化流程分两步
    第一步,注册标准设备树串口驱动console_initcall()–>serial8250_console_init()
    第二步,调用注册好的标准设备树串口驱动start kernel()–>console_init()

    展开全文
  • 设备树dts文件转换dtb文件

    dts文件转换dtb文件

    本设备树解析基于linux3.0.4内核版本

    dts 文件

    设备树源文件,类似于C语言的xxx.c文件,文件格式如下,本文主要重点关注编译部分,也就是dts转换dtb部分,关于dts格式详细介绍不在此处进行赘述。

    /dts-1/;                                                                                                                       
    /{                             //根节点
        node1{                          //node1是节点名,是/的子节点
            key=value;                  //node1的属性
            ...
            node2{                      //node2是node1的子节点
                key=value;              //node2的属性
                ...
            }
        }                               //node1的描述到此为止
        node3{
            key=value;
            ...
        }
    }

    dtb 文件介绍

    设备树二进制文件,类似于C语言的目标二进制文件obj,可以被linux内核启动过程中识别。文件格式如下,本文的dtb文件打开是采用linux自带工具fdtdump,在linux 终端执行fdtdump –sd xxx.dtb > xxx.txt ,打开vi xxx.txt文本文件显示如下,也可以通过三方软件ultraEdit查看,本文主要重点关注编译部分,也就是dts转换dtb部分,关于dtb格式详细介绍不在此处进行赘述。

    // magic:                   0xd00dfeed 
    // totalsize:               0xce4 (3300) 
    // off_dt_struct:       0x38 
    // off_dt_strings:      0xc34 
    // off_mem_rsvmap:  0x28 
    // version:             17 
    // last_comp_version:   16 
    // boot_cpuid_phys:     0x0 
    // size_dt_strings:     0xb0 
    //size_dt_struct:          0xbfc 

    dtc 工具

    dtc工具有两种编译工具包,第一种是直接在linux终端下载apt-get install device-tree-compiler工具链编译,第二种是采用linux内核自带的编译工具包,本文主要介绍linux内核自带工具包编译。

    dtc 命令编译

    打开linux下设备树宏开关,操作方式如下,打开make menuconfig 进入 Kernel type —>选中[*] Flattened Device Tree support,make编译就会自动编译dtc工具链编译出dtc可执行文件。
    (1) Dts编译生成dtb命令: ./dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts
    把A_dts.dts编译生成B_dtb.dtb
    (2) Dtb编译生成dts 命令:./dtc -I dtb -O dts -o A_dts.dts A_dtb.dtb
    把A_dtb.dtb反编译生成为A_dts.dts

    dtc make单独编译

    单独编译,表示在linux下只编译dts源文件,不需要编译内核版本出来,命令如下make dtbs,接下来,追踪下它的功能实现流程,从顶层目录开始分析make dtbs会调用到arch/mips/Makefile文件,实现代码如下,可以看到一共三句代码,第一句指定dts源文件存储路径,第二句dtbs标签依赖于scripts脚本文件,第三句代码就是具体执行路径。

    board:= arch/mips/xxx/board
    dtbs: scripts
        $(Q)$(MAKE) $(build)=$(board)/dts dtbs

    继续追踪scripts,经过层层调用,最终调用到 Makefile.lib文件封装好的DTC编译命令, Makefile.lib文件路径见scripts/Makefile.lib,DTC命令实现如下
    预处理

    dtc_cpp_flags  = -Wp,-MD,$(depfile).pre.tmp -nostdinc                    \
             -I$(srctree)/arch/$(SRCARCH)/boot/dts                   \
    #找到将要执行的源文件进行预处理
             -I$(srctree)/arch/$(SRCARCH)/boot/dts/include           \
             -undef -D__DTS__

    编译

    cmd_dtc = $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
        $(objtree)/scripts/dtc/dtc -O dtb -o $@ -b 0 \
    #./dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts 
            -i $(dir $<) $(DTC_FLAGS) \  
            -d $(depfile).dtc.tmp $(dtc-tmp) ; \
        cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)
    #编译当前所有*.dts文件生成*.dtb文件编译规则
    $(obj)/%.dtb: $(src)/%.dts FORCE#这句makefile语法意思是目标文件*.dtb依赖于*.dts源文件
        $(call if_changed_dep,dtc)#如果上述依赖文件.dts有变化就去执dtc可执行文件编译
    附加内容展开介绍:
             -i $(dir $<) $(DTC_FLAGS) \  
             -d $(depfile).dtc.tmp $(dtc-tmp) ; \
    这两句makefile语法定义-i 搜索源文件存放的路径 -d 输出目标文件dtb,其它参数操作见dtc -h用法介绍。

    汇编

    quiet_cmd_dt_S_dtb= DTB    $@ #编译当前路径下的所有.dtb文件生成*dtb.S目标文件, $@意思就是代指所有文件是makefile语法编译规则。
    cmd_dt_S_dtb=       \#编译当前路径下的所有.dtb文件生成*dtb.S目标文件编译规则
    (                           \
        echo '\#include <asm-generic/vmlinux.lds.h>';   \
        echo '.section .dtb.init.rodata,"a"';       \
        echo '.balign STRUCT_ALIGNMENT';        \
        echo '.global __dtb_$(*F)_begin';       \
        echo '__dtb_$(*F)_begin:';          \
        echo '.incbin "$<" ';               \
        echo '__dtb_$(*F)_end:';            \
        echo '.global __dtb_$(*F)_end';         \
        echo '.balign STRUCT_ALIGNMENT';        \
    ) > $@#把上述所有编译规则重定向到$@中
    $(obj)/%.dtb.S: $(obj)/%.dtb#这句makefile语法意思是目标汇编文件*.dtb.S依赖于*.dtb文件
        $(call cmd,dt_S_dtb)#此处意思是如果上面的依赖文件有变化列如*.dtb有变化,我就会执行下面的编译规则,回调dtb命令也就是回调cmd_dt_S_dtb,这里面就是汇编处理源文件生成汇编文件的执行代码。

    链接库函数
    *dtb.o文件链接静态dtc工具库生成设备树执行文件,这个Makefile文件不是内核自带,需要开发者自己设计,实现路径见arch/mips/xxx/board/dts/Makefile。

    obj-y += xxx.dtb.o
    dtb-y += xxx.dtb
    dtbs: $(addprefix $(obj)/, $(dtb-y))
    clean-files := *.dtb
    

    dtc make整体编译

    整体编译表示和内核版本一起编译,比较简单,实现过程是基于单独编译命令基础上增加编译设备树的标签。实现路径见./arch/mips/Makefile

     all: dtbs | uImage

    至此,dts文件转换dtb文件分析完成。
    原创文章,转载请注明此处!

    展开全文
  • Linux kernel在V2.6之后,modules的编译有些修改。不再是单独编译,而是将本module添加入:obj-m,并最终调用Linux KernelMakefile 来编译modules.   任务一: 这次Sam需要在某一平台上编译Bluetooth driver.其实...
  • 接下来就要介绍板级初始化了。 这篇主要是说一下板级初始化函数setup_arch中的cpu_probe函数。 这里看一下setup_arch函数的样子: void __init setup_arch(char **cmdline_p) ... setup_early_printk
  • 由于本系列文档在介绍过程中,参考了很多MIPS官方,以及北京君正(Ingenic)的xburst系列处理器的资料,目的仅仅是为拓展MIPS架构以及Linux进自己的绵薄之力,如果有侵权行为时,请告知本人处理,谢谢 1 ...
  • 某些MIPS架构的机顶盒提供了六种交叉编译工具GCC,如下:· mipsel-linux-gcc· mipsel-linux-uclibc-gcc· mipsel-uclibc-gcc· mips-linux-gcc· mips-linux-uclibc-gcc· mips-uclibc-gcc这六种有什么区别?...
  • MIPS架构的传奇演化

    2017-09-01 14:14:14
    本文将快速浏览MIPS架构的演化过程,描述它如何从斯坦福大学计算科学实验室的最早版本演变为当前的架构。 从嵌入式系统中使用的微型微控器到数据中心使用的众核,到处都可以发现基于MIPS的处理器。下面视频回溯...
  • 所以呢,首先就要在板子上安装ipfs,可是令人头疼的是,那块板的芯片架构是mips架构的,ipfs官网没有提供支持mips架构的安装包。我就想着,既然没有,我就自己编译一个出来呗,接着就展开了以下工作,使用ipfs源码,...
  • ARM和MIPS架构

    2019-04-06 11:11:35
    ARM体系 1、历史 1978年12月5日,物理学家赫尔曼·豪泽(Hermann Hauser)和工程师Chris Curry,在英国剑桥创办了CPU公司(Cambridge Processing Unit),主要业务是为当地市场供应电子设备。...
  • 如何在linux主机上运行/调试 arm/mips架构的binary 原文链接M4x@10.0.0.55 本文中用于展示的binary分别来自Jarvis OJ上pwn的add,typo两道题 写这篇教程的主要目的是因为最近想搞其他系统架构的pwn,因此第一步...
  • centos 6.5 64位 ./configure --prefix=/opt/gdb-7.4/zyz/ --host=x86_64-unknown-linux-gnu --target=mips-linux ./configure --host=mips-linux --target=mips-linux CC=mips64-octeon-linux-gnu-gcc
  • 在使用IDA对MIPS-Linux程序做动态分析的时候,按照常规的逻辑,我们应该假设MIPS-Linux程序能够以一种等待调试的状态运行着,然后我们就可以使用IDA远程附加调试的方式attach到这个MIPS-Linux进程上,然后通过断点或...
  • openwrt在mips架构上添加自定义系统调用操作步骤1)添加系统调用号2)在系统调用表中添加该调用的服务例程3)实现系统调用服务例程4)重新编译内核5)编写用户态程序测试下面详细说明一、添加系统调用号二、在内核...
1 2 3 4 5 ... 20
收藏数 8,679
精华内容 3,471
热门标签