zoukankan      html  css  js  c++  java
  • Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析

    1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c
     start_kernel()
       --2>setup_arch(&command_line);//该函数位于arch/arm/kernel/setup.c
              //在这个函数中定义了一个描述开发板的属性的结构体struct machine_desc *mdesc
              struct machine_desc {
          /*
           * Note! The first four elements are used
           * by assembler code in head-armv.S
           */
          unsigned int  nr;  /* architecture number */
          unsigned int  phys_io; /* start of physical io */
          unsigned int  io_pg_offst; /* byte offset for io
                * page tabe entry */
         
          const char  *name;  /* architecture name */
          unsigned long  boot_params; /* tagged list  */
         
          unsigned int  video_start; /* start of video RAM */
          unsigned int  video_end; /* end of video RAM */
         
          unsigned int  reserve_lp0 :1; /* never has lp0 */
          unsigned int  reserve_lp1 :1; /* never has lp1 */
          unsigned int  reserve_lp2 :1; /* never has lp2 */
          unsigned int  soft_reboot :1; /* soft reboot  */
          void   (*fixup)(struct machine_desc *,
               struct tag *, char **,
               struct meminfo *);
          void   (*map_io)(void);/* IO mapping function */
          void   (*init_irq)(void);
          struct sys_timer *timer;  /* system tick timer */
          void   (*init_machine)(void);
         };
              //这个结构体中包括机器ID,物理地址、IO地址偏移、IO映射函数、IRQ中断函数初始化
              //开发板初始化函数:完成平台驱动程序初始化注册函数的调用
           --3>mdesc = setup_machine(machine_arch_type);
               //machine_arch_type为外部定义的一个全局变量,用来标识机器ID
               --4>struct machine_desc *list = lookup_machine_type(machine_arch_type)
                   //用于搜索开发板机器ID,lookup_machine_type是一个汇编函数
                     位于linux-2.6.22/arch/arm/kernel/head-common.S
                     lookup_machine_type中对机器ID号码从r0寄存器复制到r1寄存器中,调用汇编函数:__lookup_machine_type
                     --5>__lookup_machine_type从.arch.info.init段中比较是否存在相同机器ID号码的机器描述结构体,
                     在使用MACHINE_START(_type,_name)宏定义一个开发板机器描述结构体时,会把这个结构体变量放到.arch.info.init段内
          
          //宏定义和宏开如下所述:
          #define MACHINE_START(_type,_name)   
          static const struct machine_desc __mach_desc_##_type 
           __used       
           __attribute__((__section__(".arch.info.init"))) = { 
           .nr  = MACH_TYPE_##_type,  
           .name  = _name,
          
          #define MACHINE_END    
          };
          
          MACHINE_START(CSB337, "Cogent CSB337")
           /* Maintainer: Bill Gatliff */
           .phys_io = AT91_BASE_SYS,
           .io_pg_offst = (AT91_VA_BASE_SYS >> 18) & 0xfffc,
           .boot_params = AT91_SDRAM_BASE + 0x100,
           .timer  = &at91rm9200_timer,
           .map_io  = csb337_map_io,
           .init_irq = csb337_init_irq,
           .init_machine = csb337_board_init,
          MACHINE_END
          
          //宏展开如下:
          
          MACHINE_START(CSB337, "Cogent CSB337")
          
          static const struct machine_desc __mach_desc_CSB337 __used __attribute__((__section__(".arch.info.init"))) = { 
           .nr  = MACH_TYPE_CSB337,  
           .name  = Cogent CSB337,
          
          /* Maintainer: Bill Gatliff */
           .phys_io = AT91_BASE_SYS,
           .io_pg_offst = (AT91_VA_BASE_SYS >> 18) & 0xfffc,
           .boot_params = AT91_SDRAM_BASE + 0x100,
           .timer  = &at91rm9200_timer,
           .map_io  = csb337_map_io,
           .init_irq = csb337_init_irq,
           .init_machine = csb337_board_init,
          };
          
          __lookup_machine_type函数:位于arch/arm/kernel/head-common.s
           __lookup_machine_type:
           adr r3, 3b
           ldmia r3, {r4, r5, r6}
           sub r3, r3, r4   @ get offset between virt&phys
           add r5, r5, r3   @ convert virt addresses to
           add r6, r6, r3   @ physical address space
          1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
           teq r3, r1    @ matches loader number?
           beq 2f    @ found
           add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
           cmp r5, r6
           blo 1b
           mov r5, #0    @ unknown machine
          2: mov pc, lr
          这个函数中的#MACHINFO_TYPE,不是太明白,搜索源码发现它出现在一个c文件的main函数中,被main函数调用
          很不理解,按理说一条宏命令应该出现在一个h文件或函数外才对。google后的结果如下,没有进一步地观察跟踪
          编译过程是不是这样,不过觉得还是挺有道理的。留待以后考证。
          #define DEFINE(sym, val)
                  asm volatile(" ->" #sym " %0 " #val : : "i" (val))
            根据上面的宏定义,进行宏展开
            DEFINE(SIZEOF_MACHINE_DESC, sizeof(struct machine_desc));
            asm volatile(" ->" SIZEOF_MACHINE_DESC " %0 " sizeof(struct machine_desc) : : "i" (sizeof(struct machine_desc)))
            google后的结果是:
            asm volatile(" ->" #sym " %0 " #val : :"i" (val))估计是  #define  sym  val 的意思
            也就是#define SIZEOF_MACHINE_DESC sizeof(struct machine_desc)
            只不过这里是动态的定义。只需要传一个sym和val进来,就可以帮你完成#define  sym  val的功能
            那个宏定义在asm-offsets.c中,这个.c文件根本就不是用来编译运行的,只是在编译内核的时候,
            用它生成一个asm-offsets.s文件,然后使用一个脚本将这个asm-offsets.s再转换为asm-offsets.h。
            这个头文件遵循汇编语法,用来被汇编文件include的。
            DEFINE(MACHINFO_TYPE,  offsetof(struct machine_desc, nr));
            #define MACHINFO_TYPE offsetof(struct machine_desc, nr)
           
            函数调用--4>如果lookup_machine_type(nr)查找到了目标开发板机器ID,返回一个指向该机器描述结构体的指针,否则返回0
            而函数调用--3>setup_machine(machine_arch_type)中的实参machine_arch_type (也就是作为实参传入函数lookup_machine_type
            函数的变量nr) 是在哪里定义的呢?
           
            在arch/arm/tools下有三个文件:gen-mach-types、mach-types、Makefile
            1 阅读gen-mach-types,发现这是一个shell脚本文件,看注释是:
              generate include/asm-arm/mach-types.h用于产生mach-types.h的头文件
              mach-types.h里的生成的是板子相关的宏定义
            2 mach-types文件中是很多块板子的配置文件,所以当为内核添加一款新的开发板时,需要修改这个文件,
              参考别的板子的定义方法,添加一项配置,其中配置的最后一项number就是机器ID号码
            3 mach-types.h源码中是没有这个文件的,这个文件是编译过程中动态生成的。
              # machine_is_xxx CONFIG_xxxx  MACH_TYPE_xxx  number
                     csb337   MACH_CSB337  CSB337       399
              mach-types.h首先根据mach-types配置文件生成#define MACH_TYPE_XXX  number的宏定义机器ID
              如:#define MACH_TYPE_CSB337  399
              然后成如所有配置项的其他部分,如:
              #ifdef CONFIG_MACH_CSB337
            # ifdef machine_arch_type
            #  undef machine_arch_type
            #  define machine_arch_type __machine_arch_type
            # else
            #  define machine_arch_type MACH_TYPE_CSB337
            # endif
            # define machine_is_csb337() (machine_arch_type == MACH_TYPE_CSB337)
            #else
            # define machine_is_csb337() (0)
            #endif
            在这里我们看到了machine_arch_type的踪影。。。。
            解析这段宏命令:如果定义了machine_arch_type,则结束machine_arch_type原来的宏定义,
            然后把machine_arch_type重新定义成__machine_arch_type;如果没有定义过machine_arch_type,
            那么就把machine_arch_type定义成mach-types中配置的机器ID号码。
            
            Makefile中把mach-types.h动态包含到工程去。
           4 如果定义了machine_arch_type宏的话,machine_arch_type又被重定义为__machine_arch_type;
             __machine_arch_type又是在哪里定义的呢?
             在源码中搜索__machine_arch_type,在arch/arm/boot/compressed/misc.c中发现了:
             unsigned int __machine_arch_type;的定义,
             并在该文件下的函数:
               decompress_kernel()中对__machine_arch_type进行了初始化赋值为arch_id。
               其实这个arch_id是由uboot等bootloader传入内核的一个参数。
           5 追踪decompress_kernel函数
             decompress_kernel是使解压内核的意义,该函数应该是在内核运行之前先运行的函数,用于把内核
             从uImage镜像解压到ram中去,便于内核在内存中运行。(以上都是自己的猜测:根据这个函数中打印
             出的字符和内核运行初输出到终端上的字符一致)
             uImage在arch/arm/boot目录下生成,那么阅读该目录下的Makefile文件。
             看不太懂什么意思,不过最后一句:subdir-     := bootp compressed
             那么继续看这两个子目录中的Makefile
             bootp目录里都是些配置链接相关的代码,看不懂,查看linux主机发现,这个目录上没有生成*.o文件,暂时跳过。
             compressed目录
             这个目录下有个文件vmlinux.lds.in,编译时会生成一个vmlinux.lds
             阅读这个链接脚本,好多看不太明白,不过链接脚本中.text段中放在最前面的是_start,那么可以在本目录下
             寻找一个含有_start的汇编文件,发现head.s含有这个start标号,可以猜测内核最先就是从这个会变文件开始
             执行下去的。分析下这个head.s汇编
           5.1 保存uboot传入的参数,关闭中断进入管理模式
             uboot跳入到linux内核时传入三个参数:(内核偏移地址:我自己猜的)0,机器ID,参数tags指针
             分别把这三个参数保存到r0,r7,r8三个寄存器。
           5.2 内核代码的重定位
             adr r0, LC0//把LC0表的地址加载到r0中
            ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}//把LC0表的数据分别加载到r1 r2 r3 r4 r5 r6 r12 r13中
            subs r0, r0, r1 
            beq not_relocated //通过比较r0 r1也就是比较LC0表的加载地址和链接地址是否相同,相同就调用not_relocated
                           函数,就不用再进行代码重定位了,否则还需要对代码在内存进行搬移重定位到链接地址。
            标号LC0是指在内存中的地址,而这个标号作为地址的内存单元中存储的LC0是指由链接脚本指定的内存地址数据

             .type LC0, #object
             LC0:  .word LC0   @ r1        表LC0地址
               .word __bss_start  @ r2      BSS段开始地址:由链接脚本确定
               .word _end   @ r3          BSS段结束地址:由链接脚本确定
               .word zreladdr  @ r4        由arch/arm/mach-xx/Makefile.boot文件配置内核加载地址
               .word _start   @ r5        压缩内核的开始地址
               .word _got_start  @ r6      got段开始地址
               .word _got_end  @ ip        got段结束地址
               .word user_stack+4096  @ sp  栈指针sp
             LC1:  .word reloc_end - reloc_start
               .size LC0, . - LC0
             假如r0 与r1值不一样,需要进行重定位:
               add r5, r5, r0   重定位内核地址
              add r6, r6, r0   重定位got段开始地址
              add ip, ip, r0   重定位got段结束地址
              
              add r2, r2, r0
              add r3, r3, r0
              add sp, sp, r0
              个人理解:LC0表中除LC0 zreladdr外都是相对LC0的相对位移地址,这样就能明白这6句指令了
            重定位got表:
            1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
               add r1, r1, r0  @ table.  This fixes up the
               str r1, [r6], #4  @ C references.
               cmp r6, ip
               blo 1b
          5.3 完成重定位后
              《A》 清bss段
                mov r0, #0
            1: str r0, [r2], #4  @ clear bss
              str r0, [r2], #4
              str r0, [r2], #4
              str r0, [r2], #4
              cmp r2, r3
              blo 1b
            《B》为解压缩内核设置堆栈空间
               bl cache_on  //打开cache
               mov r1, sp   @ malloc space above stack
              add r2, sp, #0x10000 @ 64k max 在栈上方开辟64K堆空间,现在r2是堆的底地址。。
            《C》调整内核空间与堆栈空间的地址,然后填充decompress_kernel的四个参数
            /*检查堆栈空间是否和内核uImage所站的空间重合
              *   r4 = final kernel address 同makefile.boot配置的内核地址(2410配置为0x30008000)
              *   r5 = start of this image  内核镜像地址
              *   r2 = end of malloc space (and therefore this image)
              * We basically want:
              *   r4 >= r2 -> OK
              *   r4 + image length <= r5 -> OK
            */
             cmp r4, r2
             bhs wont_overwrite
             sub r3, sp, r5  @ > compressed kernel size
             add r0, r4, r3, lsl #2 @ allow for 4x expansion
             cmp r0, r5
             bls wont_overwrite     
               //一般地r4不会比r2大,r0比r5小,个人猜测,这部分不是太明白什么意思
             mov r5, r2   @ decompress after malloc space
             mov r0, r5
             mov r3, r7
             bl decompress_kernel
             。。。。。
             wont_overwrite: mov r0, r4
             mov r3, r7
             bl decompress_kernel
             b call_kernel
           《D》跳入到解压缩后的内核里去运行linux操作系统
             call_kernel: bl cache_clean_flush
             bl cache_off
             mov r0, #0   @ must be zero
             mov r1, r7   @ restore architecture number
             mov r2, r8   @ restore atags pointer
             mov pc, r4   @ call kernel
           《E》运行Linux操作系统的前奏
              阅读bootp.lds连接脚本,发现连接到真正内核可执行文件的最前面的是 _stext 段。
              该段在arch/arm/kernel/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 //通过协处理器命令读处理器ID号
            bl __lookup_processor_type  @ r5=procinfo r9=cpuid
            //该函数位于arch/arm/kernel/head-common.s中
            movs r10, r5    @ invalid processor (r5=0)?
            beq __error_p   @ yes, error 'p'
            //没有找到r5为0
            bl __lookup_machine_type  @ r5=machinfo
            //该函数位于arch/arm/kernel/head-common.s中
            movs r8, r5    @ invalid machine (r5=0)?
            beq __error_a   @ yes, error 'a' 
            //没有找到r5为0
            bl __create_page_tables  
            //创建页表
            ldr r13, __switch_data  @ address to jump to after
            @ mmu has been enabled
            adr lr, __enable_mmu  @ return (PIC) address
            //打开MMU
            add pc, r10, #PROCINFO_INITFUNC
            
            《F》运行Linux操作系统的前奏迷云
            PROCINFO_INITFUNC 是__cpu_flush成员变量在结构体proc_info_list中的便宜量,
            这可以从arch/arm/kernel/asm-offset.c中的一条语句如下得知。
            DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
            在arch/arm/kernel/vmlinux.lds.s中有
            __proc_info_begin = .;
            *(.proc.info.init)
             __proc_info_end = .;
            这三行把所有处理器信息结构组合在一块,就像一个结构数组。
            这样查找时只要找到 __proc_infor_end 的地址,很快就能找到处理器信息结构数组。
            对于机器信息也是一样
            __arch_info_begin = .;
            *(.arch.info.init)
             __arch_info_end = .;
             由Makefile可知对于arm920t系列的处理器proc-arm920.S被编译进内核
             在arch/arm/mm/proc-arm920.S的448行有如下代码:
             第一行伪汇编可能是把下面的这段数据存放到.proc.info.init代码段中
             .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
             .long cpu_arch_name
             .long cpu_elf_name
             .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
             .long cpu_arm920_name
             .long arm920_processor_functions
             .long v4wbi_tlb_fns
             .long v4wb_user_fns
            #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
             .long arm920_cache_fns
            #else
             .long v4wt_cache_fns
            #endif
             .size __arm920_proc_info, . - __arm920_proc_info
             
           结合以上代码及add pc, r10, #PROCINFO_INITFUNC 指令可知
           r10存储的是proc.info.init的中某一项的起始地址,而PROCINFO_INITFUNC是__cpu_flush
           在结构体proc_info_list的偏移值,而这个值对应的正是__arm920_proc_info中的第五项
           b __arm920_setup ,由此可知,接下来跳转到 __arm920_setup 处运行。
           
            .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
             。。。。。
              .type arm920_crval, #object
             arm920_crval:
              crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
           这段代码先使I/D CACHE无效写buffer TLB无效,然后加载arm920_crval值,用来设置r0,
           mov pc,lr会跳转到 arch/arm/kernel/head.s中的__enable_mmu 函数中去执行。
           __enable_mmu
             b __turn_mmu_on
             mov pc, r13  //r13中的值是__switch_data在上面出现指令 ldr r13, __switch_data
             在源码中搜索 grep '__switch_data' -nR ./*
             搜索结果:./arch/arm/kernel/head-common.S:14: .type   __switch_data, %object
              .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 cr_alignment   @ r6
              .long init_thread_union + THREAD_START_SP @ sp
              。。。。。。。
             .type __mmap_switched, %function
             __mmap_switched:
              adr r3, __switch_data + 4
             
              ldmia r3!, {r4, r5, r6, r7}
              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
             
              ldmia r3, {r4, r5, r6, sp}
              str r9, [r4]   @ Save processor ID
              str r1, [r5]   @ Save machine type
              bic r4, r0, #CR_A   @ Clear 'A' bit
              stmia r6, {r0, r4}   @ Save control register values
              b start_kernel
             分析上述代码:
             mov pc ,r13
             r13中存储的是__switch_data目标内存区的首地址,这个地址上存储的是
             函数 __mmap_switched 的入口地址。
             这个函数完成了__data_loc数据段到__data_start数据段的数据复制、
             __bss_start BSS数据段的清0、设置SP指针、保存处理器ID到全局变量processor_id中、
             保存开发板机器架构类型到全局变量__machine_arch_type中、r0,r4压入栈、
             最后调用start_kernel,这个函数在init/main.c中开始正式运行操作系统。

    总结:
      1 内核的启动过程:
        a uboot传入参数
        b 跳入到内核的起始地址;内核=head.o+misc.o+压缩内核镜像
        c 保存uboot传入的参数,然后检查、重定位代码
        d 打开cache,调整内核镜像地址、堆栈地址等为运行解压缩内核函数做准备
        e 调用 decompress_kernel 函数
             --> 调用平台相关的接口函数 arch_decomp_setup();
             --> 调用 makecrc();
             --> 调用 gunzip();
        f 调用汇编函数b call_kernel,call_kernel中刷新cache,关cache,然后把
          uboot传入的三个参数再传入到解压缩后的内核中。
        g 通过指令 mov pc,r4,跳转到内核入口
          r4中保存的是解压缩内核的入口地址,该地址为arch/arm/kernel/head.s中的
          .type stext, %function ENTRY(stext)
          -->读processor ID
             调用汇编函数 __lookup_processor_type 检查内核是否支持该处理器
          -->调用汇编函数 __lookup_machine_type   检查内核是否支持该目标板
          -->调用汇编函数 __create_page_tables    创建页表
        h 接下来调用arch/arm/mm/proc-arm920.S中的汇编函数 __arm920_setup
        i 接下来返回调用arch/arm/kernel/head.s中的__enable_mmu
             -->__turn_mmu_on
             -->mov pc, r13即 ./arch/arm/kernel/head-common.S中的标号: __switch_data
                -->再调用 __mmap_switched 完成数据段的重定位、BSS段清0、设置SP、保存全局变量
        J 接下来就是b start_kernel 该函数位于init/main.c中
        此次分析完毕,接开始部分。。
       
        在分析start_kernel启动过程时,
            -->setup_arch
               这个函数中搜索支持的单板,然后对这三个全局变量进行赋值
                init_arch_irq = mdesc->init_irq;
          system_timer = mdesc->timer;
          init_machine = mdesc->init_machine;
          来完成对目标板的指针接口。
          以后再来分析其他的驱动启动过程。。。。。。。
    参考:
      http://blog.csdn.net/lanmanck/article/details/4288048
      http://blog.163.com/fj_ltls/blog/static/1380271112011610101118227/
      http://blog.chinaunix.net/uid-20672257-id-2891129.html   

  • 相关阅读:
    编程命名规范化
    傻孩子菜单框架(转)
    《数据结构》示范程序树的长子-兄弟表示法
    keil中编译时出现*** ERROR L107: ADDRESS SPACE OVERFLOW
    单片机C语言下LCD多级菜单的一种实现方法
    指针函数与函数指针的区别
    LCD1602汉字、自定义字符取模
    FFmpeg纯净版解码 av_parser_parse2
    ffmpeg 内存读写相关
    AudioSpecificConfig
  • 原文地址:https://www.cnblogs.com/riskyer/p/3257930.html
Copyright © 2011-2022 走看看