移植u-boot-2021.07-rc3到开发板

这篇文章只是在开发的时候遇到觉得关键的难点随手记录下来的,如果有错误,欢迎指正。

移植u-boot-2021.07-rc3到s3c2440

因为最新的uboot已经完全移除s3c2440这个soc的代码了,所以要用到的cpu相关代码,soc相关代码以及要用到的驱动都要移植甚至自己写。初步要用到的驱动为:

1.时钟,运行内存(sdram,ddr),串口,网卡(将kernel从服务器上下载到ddr),nand(可能要将image,yaffs2系统烧写到硬盘)前面四个功能必须要实现。

我的交叉编译工具是我自己根据crosstool-ng-1.24.0制作的arm-jaky2440-linux-gnueabi-gcc-8.3.0
在顶层Makefile中指定编译工具
ARCH = arm
CROSS_COMPILE=arm-linux-


make smdkc100_defconfig
make all
这一步可以编译出uboot.bin说明环境没问题

首先知道自己的cpu是三星架构的
一.构造自己的单板:参考:https://blog.csdn.net/qq_16777851/article/details/81543373
  查看u-boot-2021.07-rc3/board/samsung 有这些单板:smdk5250  smdk5420  smdkc100  smdkv310
  我会基于smdkc100这个单板构造自己的单板
  cp board/samsung/smdkc100 board/samsung/jaky2440 -rf//构造单板
  
  修改自己单板/board/samsung/jaky2440里面的东西,其实主要是将里面关于smdkc100的属性改为jaky2440的属性
  vi /board/samsung/jaky2440/Kconfig

  1:
  原来的Kconfig为:
    if TARGET_SMDKC100

    config SYS_BOARD
        default "smdkc100"

    config SYS_VENDOR
        default "samsung"

    config SYS_SOC
        default "s5pc1xx"

    config SYS_CONFIG_NAME
        default "smdkc100"

    endif
    
 改为:
    if TARGET_JAKY2440      //表明我们的单板的名字叫做JAKY2440,这个参数会在_defconfig文件里面用到

    config SYS_BOARD
        default "jaky2440"  //表明我们单板的代码主要存放在/board/samsung/jaky2440里面

    config SYS_VENDOR
        default "samsung"   //表明我们单板的代码主要存放在/board/samsung/

    config SYS_SOC
        default "s5pc1xx"   //表明我们单板所属的SOC为s5pc1xx,uboot的所有SOC都存放在:u-boot-2021.07-rc3\arch\arm ,三星的soc只有u-boot-2021.07-rc3\arch\arm\mach-s5pc1xx,所以我们的单板也只能用这个soc,也就是维持原来不变。
    config SYS_CONFIG_NAME
        default "jaky2440"  //表明我们单板文件在编译时所用到的配置头文件为:u-boot-2021.07-rc3\include\configs\jaky2440.h

    endif
    
  2.修改单板的文件名字以及Makefile,其中最后修改/board/samsung/jaky2440/MAINTAINERS
    mv /board/samsung/jaky2440/smdkc100.c /board/samsung/jaky2440/jaky2440.c  //改单板文件的名字
    vi /board/samsung/jaky2440/Makefile ->obj-y    := smdkc100.o 改为 jaky2440.o //对应也要改Makefile
    
    打开/board/samsung/jaky2440/MAINTAINERS,修改之后的样子为:
    JAKY2440 BOARD    //表明我们的单板名称为JAKY2440  对应40行命名单板名字
    M:    Minkyu Kang <[email protected]>
    S:    Maintained
    F:    board/samsung/jaky2440/       //表明我们的单板主要文件路径
    F:    include/configs/jaky2440.h    //表明我们的单板的主要配置头文件
    F:    configs/jaky2440_defconfig    //表明我们的单板的默认配置头文件
    
二:构造自己单板的配置头文件以及默认配置文件
    cp /include/configs/smdkc100.h /include/configs/jake2440.h //对应第一步的54行,67行
    cp /configs/smdkc100_defconfig /configs/jaky2440_defconfig //对应68行
    
    vi /configs/jaky2440_defconfig
    将CONFIG_TARGET_SMDKC100=y 改为:CONFIG_TARGET_JAKY2440=y
    这句话非常重要:这句话会指明编译的是哪个开发板,也就是在make jake2440_defconfig之后,编译的单板将会是
    board/samsung/jake2440,也就是说这句话的修改是让我们第一步的修改好的单板资料用起来。
    
三:修改soc相关,往soc里面添加我们的单板
    vi u-boot-2021.07-rc3/arch/arm/mach-s5pc1xx/Kconfig
    
    添加
    config TARGET_JAKY2440
    bool "Support jaky2440 board"
    select OF_CONTROL

    在底下添加
    source "board/samsung/jaky2440/Kconfig"//将soc与我们的单板资料用起来
    
    构造自己的单板这一块主要是参考了:https://blog.csdn.net/qq_16777851/article/details/81543373

构造自己的单板完毕,检验编译环境是否ok
    make jaky2440_defconfig
    make all
    
    编译成功说明可以往下走了
    
代码分析移植:
确定程序入口打开顶层uboot.lds,看到正文段为:
    ENTRY(_start) /* 程序入口代码标号 */
    SECTIONS
    {
     . = 0x00000000;
     . = ALIGN(4);
     .text :
     {
      *(.__image_copy_start)
      *(.vectors)
      arch/arm/cpu/armv7/start.o (.text*)
     }
    程序的入口为_start,_start位于.vectors(arch\arm\lib\vectors.S),从arch/arm/lib/vectors.S->_start跳到arch/arm/cpu/armv7/start.o->start.S里的reset入口。
    可以看到真正的入口是_start,真正的.text段是从.vectors开始的。从字面上就能看出.vector段是中断向量表的存放处。
    从vectors.S中跳到start.S的reset入口.

0.-1:修改cpu
    start.S文件是很重要的cpu相关文件.也是第一阶段主要分析的文件
    ls arch/arm/cpu,发现有如下cpu核
    arm11  arm1136  arm1176  arm720t  arm920t  arm926ejs  arm946es  armv7  armv7m  armv8
    其中就拥有与我的开发板cpu核一样的arm920t,但是我上面是基于smdkc100编译的,它的cpu核为armv7
    默认编译的start.S文件为arch/arm/cpu/armv7/start.o,所以我们就希望修改cpu,使其cpu核这一块编译 arch/arm/cpu/arm920t/start.o。其实如果没有arm920t,也可以在armv7的基础上修改,只不过要start.S 修改的东西特别多,可能要cpu初始化的一些东西。如下:
    0.特定的cpu初始化:1清寄存器、2设置异常向量表、3设置更多更大TLB供CPU看见地址。
    1.set the cpu to SVC32 mode
    2.disable watchdong
    3.mask all interrupt
    4.set pll
    5.enable ichach
    6.flush v4 I/D caches
    7.disable mmu
    8.init sdram ->跳到board/samsung/jaky2440/lowlevel_init.S执行

    综上所述arch/arm/cpu/文件夹要做的事情这么多主要是第0项要对cpu特别熟悉才可以,所以如果可以修改cpu为arm920t最好了。
    修改cpu/armv7为arm920t:
    1.修改顶层Makefile
      461行可以看到
      export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
      export CONFIG_SHELL HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE AS LD CC
      其中ARCH CROSS_COMPILE已经被我们指定为:
      ARCH = arm
      CROSS_COMPILE=arm-linux-
      同理我们可以在Makefile的开始添加
      CPU = arm920t
      CPUDIR = arch/arm/cpu/arm920t

    2.修改make menuconfig
      /查找CPU_,可以看到很多cpu核,如 CPU_ARC770D [=n],CPU_V7A [=y],CPU_ARM920T [=n]
      可以看到CPU_V7A被选中了,而我们想要的CPU_ARM920T没被选中,发现CPU_V7A是被宏ARCH_S5PC1XX连带选中的,而ARCH_S5PC1XX就是我们要用的soc架构,所以只能修改:
      vi arch/arm/Kconfig +702
      从:
      config ARCH_S5PC1XX
        bool "Samsung S5PC1XX"
        select CPU_V7A      
     
      改为:
      config ARCH_S5PC1XX
        bool "Samsung S5PC1XX"
        select CPU_ARM920T
    这样就完成了cpu的修改,但是这个时候去编译会发现确实已经使用arm920t的cpu配置文件了,
但会报很多有关soc设备驱动的错,如s5p_gpio,s5p_onenand,s5p_serial等等这些错误,但这些设备驱动本来就不适合我们用的,我们只是借用soc mach-s5pxx的壳,要用的设备驱动我们全部要在后续的工作中重新编译或者移植 比如uart,net,nand等。所以先把有关s5p的设备驱动去掉(到对应的Makefile下取消编译)。
        
    make all编译过了之后,接着从arch/arm/cpu/arm920t/start.S开始分析
    cpu_init_crit  //arch/arm/cpu/arm920t/start.S
        lowlevel_init  //board/samsung/jaky2440/lowlevel_init.S
    
    start.S里的_main函数入口在:arch/arm/lib/crt0.S
    
    crt0.S里调用到的c函数board_init_f_alloc_reserve,board_init_f_init_reserve,board_init_f
    位于u-boot-2021.07-rc3\common\init\board_init.c
    
    crt0.S里面调用到的board_init_f位于:u-boot-2021.07-rc3\common\board_f.c,这个函数进行第一阶段的初始化包括串口移植

start.S以及crt0.S的函数比较简单,主要是cpu相关的初始化,包括设置32位管理模式,关闭中断,flush cache,初始化时钟,关看门狗,初始化内存sdram等等,这些比较简单,就不赘述了,但是其中的重定位技术还是比较关键的,我是在start.S里的调用_main函数之前进行重定位的,这里记录一下过程:

0.0 重定位代码(将代码从flash中复制到sdram上运行)
    重定位代码技术是u-boot移植中比较难的技术,因为很多开发的是从rom启动的,rom的大小一般是在4K到16K不等。这就要求我们的uboot在4k之前就把自身copy到sdram或者ddr上,并且跳到sdram上继续运行。

    在jaky2440_defconfig或者include/configs/jaky2440.h中定义了一个链接地址,通常就是uboot拷贝到sdram的入口地址。
    修改CONFIG_SYS_TEXT_BASE为0x33f00000(我的sdram的地址空间为0x30000000~0x34000000),这样说明uboot在sdram上的使用范围为0x33f00000~(0x33f00000+$(filesize))

    通常,start.S会通过bl _main跳到第二阶段执行,_main之后的代码为第二阶段的代码,之前的代码为第一阶段。通常我们在bl _main之前就会执行重定位代码
    
    将uboot拷贝到sdram上要确定三个:源(uboot一开始是在rom(是指各种flash)上启动的,所以源为0) ,目的(就是CONFIG_SYS_TEXT_BASE)长度(__bss_start - _start,在uboot.lds中确定具体名称) 从rom或者flash上读出数据要初始化nand flash,具体参考u-boot-2021.07-rc3/board/samsung/jaky2440/init.c
    start.S上的操作为:
    
    先定义两个全局变量表示目的,和长度
    .globl _text_base
    _text_base:
    .word    0x33f00000             /* 重定位的目的,这个值要等于CONFIG_SYS_TEXT_BASE=0x33f00000 */

    .globl _relocat_len
    _relocat_len:
    .word   __bss_start - _start   /*要拷贝到sdram上的长度__bss_start - _start*/

#ifdef RELOCATC
    /*下面调用c函数进行重定位*/
    /*设置栈指针,将_main的栈放到这里设置,_main里的栈设置要取消掉,为下面的将uboot从flash上复制到sdram上的C语言函数的调用作准备.
     *CONFIG_SYS_INIT_SP_ADDR要在jaky2440.h里面设置为一个sdram可用范围的地址比如0x31000000.
     *下面的nand_init_ll,copy_code_to_sdram函数是放在board/samsung/jaky2440/init.c里面,为了确保可以在4k之前实现.
     *拷贝代码到sdram上,要在链接脚本uboot.lds加上:board/samsung/jaky2440/built-in.o (.text*),确保init.c这个文件编在前面.
     */
    
    ldr    r0, =(CONFIG_SYS_INIT_SP_ADDR)  /* 设置栈指针为0x31000000 */    
    bic    r0, r0, #7                        /* 8-byte alignment for ABI compliance */
    mov    sp, r0                          /* 第一次设置栈指针指向0x31000000 */

    bl  nand_init_ll

    ldr r0, _text_base
    mov r1,#0x00
    ldr r2,_relocat_len
    
    bl  copy_code_to_sdram              /* 把数据从flash上复制到sdram上 */ 
    bl  clear_bss
    
    ldr pc, =go_to_sdram                /* 绝对跳转到sdram上运行,go_to_sdram的链接地址肯定为0x33xxxx为开头的,此处决定跳转到sdram的地址运行 */

go_to_sdram:
    bl    _main

#endif RELOCATC
 重定位的代码要在ram的size之前被编译,所以要在链接脚本u-boot.lds里加上board/samsung/jaky2440/built-in.o (.text*),确保编译在4k之前。 
0.1:第二阶段的代码分析:u-boot-2021.07-rc3\arch\arm\lib\crt0.S
    第二阶段的代码主要做了三件事:
    
    bl board_init_f
    
    u-boot自身的重定位(我们要屏蔽掉这些代码)
    
    bl board_init_r(执行到这里代码就一去不复返了,会停留在uboot的操作界面)

    下面我们分析一下这三个调用的过程:
    board_init_f:功能是串口初始化serial_init()这个函数调用之后就可以打印东西了,
    但其最重要的是对uboot的内存规划,主要要确定5个参数
     gd->relocaddr = CONFIG_SYS_TEXT_BASE;//在reserve_uboot接口里
      /*uboot重定位到sdram上的具体地址,这个值在调用完board_init_f之后应该与CONFIG_SYS_TEXT_BASE相等,是传给board_init_r的参数之一,gd->relocaddr = gd->ram_top;一开始是指向内存的顶部的,前提是 jaky2440.h中有关sdram的基地址和size要设置对*/
  gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t));//确定gd->new_gd的位置,在reserve_global_data里gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;//重定位的开始地址跟链接地址的偏移值,可以为0,在setup_reloc接口里 memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));/* 重定位gd_data到gd->new_gd 在setup_reloc接口里*/
  gd->start_addr_sp = ?新的栈指针,在调用board_init_r之前要重新设置栈指针指向这里

    u-boot-2021.07-rc3\arch\arm\lib\crt0.S在这里我的代码为:主要是要到crt0.S文件里面阅读
    mov    r0, #0
    bl    board_init_f                  /* uboot的第2.1阶段 进行栈指针的内存规划  ,规划之后的数据保存在全局数据gd里面,此时栈指针还是指向0x30000F20*/

    ldr    r0, [r9, #GD_START_ADDR_SP]      /* sp = gd->start_addr_sp */
    bic    r0, r0, #7                      /* 8-byte alignment for ABI compliance */
    mov    sp, r0                        /* 使用board_init_f里面规划的栈指针重新设置栈指针 */
    ldr    r9, [r9, #GD_NEW_GD]          /* r9本来指向gd_data_old ,现在指向gd_data_new(在第一阶段里规划的) */

    bl    relocate_vectors     /* 在调用board_init_r之前必须在arch/arm/lib/crt0.S中调用这个函数去重定位异常变量入口,否则在board_init_r中serial_initialize就会无法成功,单板不断重启 */
    bl    c_runtime_cpu_setup  /* 在调用board_init_r之前必须在arch/arm/lib/crt0.S中调用这个函数去重定位异常变量入口,否则在board_init_r中serial_initialize就会无法成功,单板不断重启 */

    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                    /* gd_t */
    ldr    r1, [r9, #GD_RELOCADDR]          /* dest_addr */
    bl  board_init_r                  /* uboot的第2.2阶段 */

补充说明:
    在第二阶段要传递两个参数void board_init_r(gd_t *new_gd, ulong dest_addr),分别为全局数据新的地址,uboot重定位到 sdram之后在sdram的入口地址,而这两个参数是在第一阶段board_init_f函数里面进行修改设置的,所以必须在第一阶段深入分析栈指针的变化,确保给第二阶段传递的两个参数是正确的。
    board\samsung\jaky2440\jaky2440.c ->指定了gd->ram_size

    /* 在离开start.S的代码之前必须将uboot重定位到sdram上去了 */
    board_init_f  //第二阶段的代码,必须运行在ddr上
    
    第二阶段的代码board_init_f的内存规划超级重要,主要是要确定下面这5个参数
    gd->start_addr_sp = ?新的栈指针,在调用第二阶段的代码之前要设置栈指针指向这里
    gd->relocaddr     = ?重定位的开始地址
    gd->reloc_off     = ?重定位的开始地址跟链接地址的偏移值,可以为0
    id = (gd_t *) addr_sp(也就是现在的new_gd),这个参数要传递给第二阶段使用
    
    就是下面5个至关重要的事情
    gd->relocaddr = addr;                                    //关键的第一步,确定重定位之后的地址
    gd->start_addr_sp = addr_sp;                            //关键的第二步,确定最终的栈指针
    gd->reloc_off = addr - _TEXT_BASE;                      //关键的第三步,确定重定位的偏移地址
    memcpy(id, (void *)gd, sizeof(gd_t));                   //关键的第四步,将gd_data从原来的位置拷贝到新的位置,方便传递给第二阶段的函数使用

    base_sp = addr_sp;                                      //关键的第五步,将最终的栈指针返回去给调用第二阶段的代码之前重新设置栈指针

    board_init_r  //第二阶段的代码,必须运行在ddr上

    
1.0    串口驱动移植:
    crt0.S——>:
        board_init_f //u-boot-2021.07-rc3\common\board_f.c
            initcall_run_list(init_sequence_f)
                init_sequence_f[]
                    serial_init //如果make menuconfig 中配置了宏CONFIG_DM_SERIAL(这个宏被选中)
                                  就会调用u-boot-2021.07-rc3\drivers\serial\serial-uclass.c //这种驱动用设备树实现否则就会调用u-boot-2021.07-rc3\drivers\serial\serial.c //这种驱动用传统方式所以我们要在menuconfig中关闭这个宏CONFIG_DM_SERIAL
                    serial_init:这个函数调用驱动driver/serial/serial_s3c24xx.c里的start接口初始化串口控制器,利用驱动里的puts接口输出字符串
                    注意一点在u-boot源码里进行调试时打印到串口用puts("%s\n"),而不是printf
                     
    CONFIG_IS_ENABLED(SERIAL_PRESENT):如果SERIAL_PRESENT在make menuconfig中被设置为'y'或'm'的时候,CONFIG_IS_ENABLED(SERIAL_PRESENT)就等于1
    串口移植可以参考:https://www.cnblogs.com/kele-dad/p/6956738.html
                     https://blog.csdn.net/r21nn/article/details/73718514
    
    移植串口驱动有两套接口smdkc100使用设备树的方式匹配驱动的它使用的协议是/driver/serial/serial-uclass.o,而也可以采用另一套协议/driver/serial/serial.o,使用后者时要在/driver/serial/serial.c里的两处地方添加对应的s3c24xx_serial_initialize();//zhangjiaqi,同时要在include/serial.h里添加函数声明void s3c24xx_serial_initialize(void); 这个初始化函数主要是在board_init_r里面用到,将老版本里的s3c24x0_serial.c移植到新版u-boot.
错误:在board_init_f->
                env_init   //设置u-boot的启动参数在硬盘nand等的启动位置,因为此时nand的驱动还没移植,导致没法设置参数的存放位置出错。
                serial_init//这个函数执行了之后,串口才可以打印,但是因为上面的env_init函数有问题
                               //导致串口移植不能打印,后来通过点灯的方法才找到env_init出错才导致串口不打印。当将env_init屏蔽之后,当移植了nand之后再去打开CONFIG_ENV_IS_IN_NAND,CONFIG_MTD_RAW_NAND,同时打开这个函数
    
    至此串口已经打印:
    U-Boot 2021.07-rc3 (Jun 03 2021 - 17:26:45 +0800) for jaky2440

    CPU:    samsung arm920t
    Model: Samsung SMDKC100 based on S5PC100
    Board:  MYSOC
    DRAM:  64 MiB
    
    但是没有继续往下打印,串口没反应了,追查发现是第二阶段的board_init_r函数里的串口函数有问题
    board_init_r -> 
        serial_initialize        
            s3c24xx_serial_initialize    //driver/serial_s3c24x0.c
    一开始怀疑是串口出问题,后来排除是串口驱动的问题,而是重定位漏掉了异常变量的重定位。
    在arch/arm/lib/crt0.S的调用board_init_r之前添加了代码:
    bl    relocate_vectors     /* 在调用board_init_r之前必须在arch/arm/lib/crt0.S中调用这个函数去重定位异常变量入口,否则在board_init_r中serial_initialize就会无法成功,单板不断重启 */
    bl    c_runtime_cpu_setup  /* 在调用board_init_r之前必须在arch/arm/lib/crt0.S中调用这个函数去重定位异常变量入口,否则在board_init_r中serial_initialize就会无法成功,单板不断重启 */
    此时串口的打印为:
    U-Boot 2021.07-rc3 (Jun 03 2021 - 17:26:45 +0800) for jaky2440

    CPU:    samsung arm920t
    Model: Samsung SMDKC100 based on S5PC100
    Board:  MYSOC
    DRAM:  64 MiB
    Nand:
    可以看到代码已经往下走了,但是nand初始化又卡死了,此时将nand,net的调用先屏蔽,串口打印为:
    U-Boot 2021.07-rc3 (Jun 10 2021 - 11:22:08 +0800) for jaky2440

    CPU:    samsung arm920t
    Model: Samsung SMDKC100 based on S5PC100
    Board:  MYSOC
    DRAM:  64 MiB
    *** Warning - bad CRC, using default environment

    In:    serial
    Out:   serial
    Err:   serial
    Hit any key to stop autoboot:  0 
    SMDKC100 #
    //可以看到代码可以跑到board_init_r最后的回环函数run_main_loop了

2.0 移植DM9000
    在u-boot-2021.07-rc3\net\Makefile里可以看到
    ifdef CONFIG_DM_ETH
    obj-$(CONFIG_CMD_NET)  += eth-uclass.o
    else
    obj-$(CONFIG_CMD_NET)  += eth_legacy.o
    endif
    
    DM9000的驱动以前使用的是老的接口,所以要取消选中宏CONFIG_DM_ETH
    board_init_r //u-boot-2021.07-rc3/common/board_r.c
        init_sequence_r
            initr_net
                eth_initialize
                    board_eth_init//如果board/samsung/jaky2440/jaky2440.c里存在这个接口,则调用,我们要在jaky2440.c实现这个接口
    
    jaky2440用的是dm9000
    参考board/samsung/smdkc100/smdkc100.c ->board_eth_init添加dm9000的驱动
    在driver/net/里面已经有dm9000x.c的驱动了,如果没有要往这个文件夹里添加属于自己网卡的驱动,并且让它编译自己的网卡
    vi driver/net/Makefile可以看到如果想要编译dm9000x,就要定义CONFIG_DRIVER_DM9000
    所以在include/configs/jaky2440.h里添加宏
    CONFIG_DRIVER_DM9000,发现已经编译dm9000x.c了,但是报错:不认得dm9000x.c里面用到的这些宏:
    undeclared (first use in this function):DM9000_DATA,DM9000_IO,CONFIG_DM9000_BASE
    参考u-boot-2021.07-rc3\include\configs\M5253DEMO.h,在jaky2440.h里添加
    #define CONFIG_DM9000_BASE    0x20000000
    #define DM9000_IO        CONFIG_DM9000_BASE
    #define DM9000_DATA        (CONFIG_DM9000_BASE + 4)

    可以看到drivers/net/dm9000x.o,已经编译进去了
    
    编译uboot再次启动开发板,串口打印
    SMDKC100 # 
    U-Boot 2021.07-rc3 (Jun 10 2021 - 16:28:26 +0800) for jaky2440

    CPU:    samsung arm920t
    Model: Samsung SMDKC100 based on S5PC100
    Board:  MYSOC
    DRAM:  64 MiB
    *** Warning - bad CRC, using default environment

    In:    serial
    Out:   serial
    Err:   serial
    Net:   dm9000
    Warning: dm9000 (eth0) using random MAC address - de:e7:ce:f7:e4:1c

    Hit any key to stop autoboot:  0 
    
    设置ip
    set ipaddr 10.150.50.170
    set serverip 10.150.50.162
    
    ping主机
    ping 10.150.50.162 结果显示:
    SMDKC100 # ping 10.150.50.162
    ERROR: resetting DM9000 -> not responding
    dm9000 i/o: 0x20000000, id: 0x90000a46 
    DM9000: running in 16 bit mode
    MAC: de:e7:ce:f7:e4:1c
    could not establish link
    Using dm9000 device
    host 10.150.50.162 is alive
    
    使用nfs下载kernel,显示
    SMDKC100 # nfs 0x30000000 10.150.50.162:/home/share/zjq_162/uImage
    ERROR: resetting DM9000 -> not responding
    dm9000 i/o: 0x20000000, id: 0x90000a46 
    DM9000: running in 16 bit mode
    MAC: de:e7:ce:f7:e4:1c
    could not establish link
    Using dm9000 device
    File transfer via NFS from server 10.150.50.162; our IP address is 10.150.50.170
    Filename '/home/share/zjq_162/uImage'.
    Load address: 0x30000000
    Loading: #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             ############################
    done
    Bytes transferred = 3803840 (3a0ac0 hex)
    SMDKC100 # nfs 0x31000000 10.150.50.162:/home/share/zjq_162/s3c2440-smdk2440.dtb
    ERROR: resetting DM9000 -> not responding
    dm9000 i/o: 0x20000000, id: 0x90000a46 
    DM9000: running in 16 bit mode
    MAC: de:e7:ce:f7:e4:1c
    could not establish link
    Using dm9000 device
    File transfer via NFS from server 10.150.50.162; our IP address is 10.150.50.170
    Filename '/home/share/zjq_162/s3c2440-smdk2440.dtb'.
    Load address: 0x31000000
    Loading: ##
    done
    Bytes transferred = 6136 (17f8 hex)
    
    至此,dm9000的驱动移植已经ok
    
3.0 移植nand    
    make menuconfig
    选中CONFIG_MTD_RAW_NAND,CONFIG_ENV_IS_IN_NAND
    
    关了CONFIG_ENV_IS_IN_ONENAND,CONFIG_CMD_ONENAND(smdkc100的硬盘为onenand),所以这里我们要关闭onenand
    打开了CONFIG_CMD_NAND,CONFIG_ENV_IS_IN_NAND,MTD_RAW_NAND

    CONFIG_MTD_CONCAT这个宏是注册nand设备的,可能要打开,最终发现不需要打开。
    cpu相关的寄存器放在./arch/arm/include/asm/jaky2440_cpu_reg.h
    nand的移植还是参考以前移植u-boot-2016的版本的过程详细看笔记:
    分析笔记2.0\linux-系统移植\bootloader\学习笔记.txt
    其中nand的驱动代码也是用以前在2016版本的已经修改成功的代码,直接移植到2021版本。
    
    其中移植遇到的问题:修改env或者nand erase dtb(mtdparts的一个分区)会报
    Attempt to erase non block-aligned data
    Failed (1)
    报这个错的原因是nand的操作地址不对齐,比如dtb在分区时的大小为(off,size-0x22,0x33),
    nand erase dtb翻译过来的意思是nand erase 0x22 0x33(在0x22地址开始擦除,擦除的大小为0x33),
    这样肯定会报错(Attempt to erase non block-aligned data),因为0x22,0x33不是16位对齐的.
    所以在分区的时候,各个区(bootloader),(env),(kernel),(dtb),(rootfs)的大小和size必须为16对齐的,
    一般为128K,或者512k这种16的倍数的才可以。参考:http://blog.chinaunix.net/uid-20632682-id-2419198.html
    
    修改添加默认分区主要在jaky2440_defconfig里面指定:
    CONFIG_CMD_MTDPARTS=y
    CONFIG_MTDIDS_DEFAULT="nand0=jaky2440-0"
    CONFIG_MTDPARTS_DEFAULT="mtdparts=jaky2440-0:512k(bootloader),128k@0x80000(params),5m@0xA0000(kernel),128k@0x5A0000(dtb),-(rootfs)"
    CONFIG_ENV_IS_IN_NAND=y
    CONFIG_ENV_ADDR=0x80000

    还要在menuconfig里面指定env的off,size(直接搜索CONFIG_ENV_ADDR修改env的off,size)    
    
    board/samsung/jaky2440/built-in.o (.text*)
    
    
4.0 bootm启动内核
    nfs 0x30000000 10.150.50.162:/home/share/zjq_162/uImage
    nfs 0x31000000 10.150.50.162:/home/share/zjq_162/s3c2440-smdk2440.dtb
    bootm 0x30000000 - 0x31000000
    SMDKC100 # bootm 0x30000000 - 0x31000000
    
    发现无法启动成功
    
    ## Booting kernel from Legacy Image at 30000000 ...
       Image Name:   Linux-5.8.5
       Image Type:   ARM Linux Kernel Image (uncompressed)
       Data Size:    3803776 Bytes = 3.6 MiB
       Load Address: 30008000
       Entry Point:  30008000
       Verifying Checksum ... OK
    ## Flattened Device Tree blob at 31000000
       Booting using the fdt blob at 0x31000000
       Loading Kernel Image
       Loading Device Tree to 33db9000, end 33dbd7f7 ... OK

    Starting kernel ...

    Virtual root driver does not exist!
    Virtual root driver does not exist!        
    因为上面的提示关闭了宏CONFIG_DM
    
    无法启动内核的原因找到了
    do_bootm_linux
        boot_jump_linux
            announce_and_cleanup
                cleanup_before_linux//arch\arm\cpu\arm920t\cpu.c
                    disable_interrupts//这个函数是空的,要填充
                    dcache_disable//就是关掉这个dcache的时候出了问题,这个函数跟宏SYS_DCACHE_OFF的定义有关系,把这个宏在make menuconfig里面关闭就行了
    
    

  启动内核的相关知识
    kernel镜像格式:vmlinux
        vmlinuz是可引导的、可压缩的内核镜像,vm代表Virtual Memory.Linux支持虚拟内存,
    因此得名vmlinux它是由用户对内核源码编译得到,实质是elf格式的文件.也就是说,
    vmlinux是编译出来的最原始的内核文件,未压缩.这种格式的镜像文件多存放在PC机上。

    kernel镜像格式:Image
        Image是经过objcopy处理的只包含二进制数据的内核代码,它已经不是elf格式了,
    但这种格式的内核镜像还没有经过压缩.

    kernel镜像格式:zImage
       zImage是ARM linux常用的一种压缩镜像文件,它是由vmlinux加上解压代码经gzip压缩而成,
    命令格式是#make zImage.这种格式的Linux镜像文件多存放在flash上.

    kernel镜像格式:uImage
        uImage是uboot专用的镜像文件,它是在zImage之前加上一个长度为0x40的头信息,
    在头信息内说明了该镜像文件的类型、加载 位置、生成时间、大小等信息.换句话说,
    若直接从uImage的0x40位置开始执行,则zImage和uImage没有任何区别.
    命令格式是#make  uImage.这种格式的Linux镜像文件多存放在flash上.

    kernel镜像格式:xipImage
       这种格式的Linux镜像文件多存放在NorFlash上,且运行时不需要拷贝到内存SDRAM中,
    可以直接在NorFlash中运行.

    可以让我们启动使用的内核有Image、zImage、uImage、xipImage 因为我们没norflash,
    所以我们只能使用前三种。

    我们前面一直移植的是uboot,所以我们就加载uImage启动内核  
    
    
  4.1 分析bootm
    参考这里:https://www.freesion.com/article/541683985/
    //高位数据放在高地址是小段,比如0x1234(12为数据的高位,34为数据的低位),
      如果在内存中的摆放方式为0x34,0x12,否则为大端存贮。
    
    unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base);
    函数说明就是解析字符串cp 中 8,10,16 进制数字   ,返回值是解析的数字,
    endp 指向第一个不是数值的字符串起始处,base :进制 
  
    bootm这个指令的动作定义在u-boot-2021.07-rc3\cmd\bootm.c
    U_BOOT_CMD(
    bootm,    CONFIG_SYS_MAXARGS,    1,    do_bootm,
    "boot application image from memory", bootm_help_text
    );

    do_bootm
        do_bootm_states//state为:BOOTM_STATE_START | BOOTM_STATE_FINDOS \
                                  | BOOTM_STATE_FINDOTHER | BOOTM_STATE_LOADOS \
                                  | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO \
                                  | BOOTM_STATE_OS_GO,此时image结构体还是空,待会根据这些状态填充image
            bootm_start
                memset((void *)&images, 0, sizeof(images));/*清空image*/
                images.verify = env_get_yesno("verify");   /*images.verify==NULL*/
            bootm_find_os/*用argv[0]填充 &images.os.image_start, &images.os.image_len,&images->legacy_hdr_os_copy,&images->legacy_hdr_os*/
            bootm_find_other/*填充images.ft_addr==0x31000000,images.ft_len==真实长度,检查uImage与dtb是否重叠,返回0*/
            bootm_load_os(images, 0);/*对kernel进行解压,并且填充解压之后的kernel size,并且清fluch*/
            boot_relocate_fdt    /*重定位设备树,将它从0x31000000 重定位到 gd_data->fdt*/
            boot_fn = bootm_os_get_boot_func(images->os.os); /*函数指针boot_fn指向do_bootm_linux*/
            bootm_process_cmdline_env /*处理命令行参数bootargs,但是默认情况下有些宏没有enable,应该是返回0*/
            boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);/*主要是处理环境变量bootargs,或默认返回0*/
                boot_prep_linux(images);//确定使用dtb传参还是用tag传参
            boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);//核心
                boot_jump_linux//正式启动内核,并且不会返回了
                    announce_and_cleanup(fake);/*无法启动内核是因为这里面关掉dchache出问题*/
                    r2 = (unsigned long)images->ft_addr;//将设备树在ddr上的地址传给内核
                    kernel_entry(0, machid, r2);/*pc指向内核入口的地址,跳转到zImage所处的位置执行内核的代码,此后程序一去不复返*/
   
    总结:do_bootm这个函数主要是读取ddr上的uImage的信息,根据读出来的信息填充image这个结构体,比如kernel的load addr,entry_addr,fdt_addr,fdt_size等等,也会对uImage和dtb的魔数进行判断,是正确的镜像才会提取镜像的信息出来填充到全局变量image,之后用填充好的image结构体去辅助启动内核,跳转执行内核。
    
    
    最终通过串口打印定位到是dcache_disable出的问题,在menuconfig关掉SYS_DCACHE_OFF就可以了。
        
    初步怀疑:最终发现不是这个问题
    machid, r2这两个参数可能没有被正确填充,可能要正确配置uboot使得这两个参数填充了才可以正确启动内核
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)/*应该走这里*/
        r2 = (unsigned long)images->ft_addr;
        
    kernel_entry(0, machid, r2);
    images->ft_len
    images->ft_addr 
    uboot给内核传递的机器id是可以在bootargs里面定义的machid=;

5.修改u-boot代码,使其支持烧写yaffs2格式的文件到nand
  看源码cmd\nand.c
  U_BOOT_CMD(
    nand, CONFIG_SYS_MAXARGS, 1, do_nand,
    "NAND sub-system", nand_help_text
    );

  do_nand->:发现nand支持jffs2格式的文件烧写,但是不支持yaffs2格式的文件烧写
  jffs2格式的文件适合存放在nor flash,而yaffs2格式的文件适合存放在nand里面,所以
  要修改nand的操作函数使其支持我们将yaffs2格式的文件烧写到nand flash里面去。
  (uboot2021里面已经取出nand write.yaffs2了,但是uboot2012是支持nand write.yaffs2的,所以参考2012往2021里面添加nand write.yaffs2)

  
  第1步:先在nand的help信息里面添加yaffs2的使用帮助信息,而且发现jffs2的使用帮助信息也
  没有了顺手也加上去
      "nand read.jffs2 - addr off|partition [count]\n"
    "nand write.jffs2[.noverify] - addr off|partition [count]\n"
    "     Use read.jffs2/write.jffs2 count byte data from/to off\n"
    #ifdef CONFIG_CMD_NAND_YAFFS
        "nand read.yaffs2 - addr off|partition [count]\n"
        "nand write.yaffs2[.noverify] - addr off|partition [count]\n"
        "     Use read.yaffs2/write.yaffs2 count byte data from/to off\n"
    #endif

  第2步:
  添加write.yaffs2的命令判断:
    在include\nand.h 里面添加#define WITH_YAFFS_OOB    (1 << 2) /* whether write with yaffs format.*/
#ifdef CONFIG_CMD_NAND_YAFFS
        else if(!strcmp(s, ".yaffs2"))
        {
            if(read)
            {
                printf("can't support yaffs2 read\n");
                return 1;
            }
            else
            {
                ret = nand_write_skip_bad(mtd, off, &rwsize,NULL, maxsize,(u_char *)addr,WITH_YAFFS_OOB);
            }
        }
#endif

  第3步:修改nand_write_skip_bad,添加nand的写操作对yaffs2的支持
        //第一步:确定blocksize,yaffs2的这个是不一样的
#ifdef CONFIG_CMD_NAND_YAFFS
        if (flags & WITH_YAFFS_OOB)
        {
            if (flags & ~WITH_YAFFS_OOB)
                return -EINVAL;
    
            int pages;
            pages = mtd->erasesize / mtd->writesize;
            
            blocksize = (pages * mtd->oobsize) + mtd->erasesize;/*比普通的多了oob区*/
            
            if (*length % (mtd->writesize + mtd->oobsize))
            {
                printf ("Attempt to write incomplete page"
                    " in yaffs mode\n");
                return -EINVAL;
            }
        }
        else
#endif
    //第二步:yaffs2不需要进这里
    if (!need_skip && !(flags & WITH_DROP_FFS) && !(flags & WITH_YAFFS_OOB))
    {
        rval = nand_write(mtd, offset, length, buffer);

        if ((flags & WITH_WR_VERIFY) && !rval)
            rval = nand_verify(mtd, offset, *length, buffer);

        if (rval == 0)
            return 0;

        *length = 0;
        printf("NAND write to offset %llx failed %d\n",offset, rval);
        return rval;
    }
/*第三步:yaffs2的修改*/        
#ifdef CONFIG_CMD_NAND_YAFFS
        if (flags & WITH_YAFFS_OOB)
        {
            int page, pages;
            size_t pagesize = mtd->writesize;
            size_t pagesize_oob = pagesize + mtd->oobsize;
            struct mtd_oob_ops ops;

            ops.len = pagesize;
            ops.ooblen = mtd->oobsize;
            ops.mode = MTD_OPS_RAW;/*作了修改*/
            ops.ooboffs = 0;

            pages = write_size / pagesize_oob;
            for (page = 0; page < pages; page++)
            {
                WATCHDOG_RESET();

                ops.datbuf = p_buffer;
                ops.oobbuf = ops.datbuf + pagesize;

                rval = mtd->_write_oob(mtd, offset, &ops);
                if (rval)  /* [email protected] */
                    break;

                offset += pagesize;
                p_buffer += pagesize_oob;
            }
        }
        else
#endif        

  在menuconfig添加宏CONFIG_CMD_NAND_YAFFS
  vi cmd/Kconfig +1151
  添加
  config CMD_NAND_YAFFS
        bool "nand write.yaffs2"
        default y if ARCH_SUNXI
        help
          Allows one to write yaffs2 format file to the NAND.
  重定位要在nand启动,别忘了添加board/samsung/jaky2440/built-in.o (.text*)
  重新编译uboot,烧写到nand,启动

  使用一下命令烧写yaffs2文件系统到nand烧写
    nfs 0x30000000 10.150.50.162:/home/share/zjq_162/first_rootfs.yaffs2
    nand erase 0x5c0000 0x16E6240
    nand write.yaffs2 0x30000000 0x5c0000 0x16E6240

  结果显示可以烧写
  NAND write: device 0 offset 0x5c0000, size 0x16e6240
     24011328 bytes written: OK
  
  内核的dtbs文件指定root=/dev/block4,启动内核的时候就会从nand的内核的默认分区block4里面挂接文件系统。
  系统已经可以成功启动并且正确挂接yaffs2运行第一个进程init进程
    yaffs: dev is 32505860 name is "mtdblock4" ro
    yaffs: passed flags ""
    random: fast init done
    VFS: Mounted root (yaffs filesystem) readonly on device 31:4.
    devtmpfs: mounted
    Freeing unused kernel memory: 208K
    Kernel memory protection not selected by kernel config.
    Run /sbin/init as init process

    Please press Enter to activate this console.

    至此,uboot2021已经可以烧写yaffs2文件系统了。

    
6.随记
    CONFIG_DM被关掉了
    CONFIG_DM_ETH,用老的协议,不用uclass


把镜像uImage,dtb,rootfs.yaffs2烧写到nand flash之后自启动命令行值:
nand read 30000000 0xa0000 0x3A0748;nand read.jffs2 31000000 0x5a0000 0x180C;bootm 0x30000000 - 0x31000000;

烧写kernel
nfs 0x30000000 10.150.50.162:/home/share/zjq_162/uImage
nand erase 0xa0000 0x3A0748(或者nand erase.part kernel,如果有分区kernel的话)
nand write.jffs2 0x30000000 0xa0000 0x3A0748(用nand write.jffs2而不用nand write是因为前者不用字节对齐,而后者需要512页对齐)

烧写设备树:
nfs 0x31000000 10.150.50.162:/home/share/zjq_162/s3c2440-smdk2440.dtb
nand erase 0x5a0000 0x180C(或者nand erase.part dtb,如果有分区dtb的话)
nand write.jffs2 0x31000000 0x5a0000 0x180C

烧写yaffs2文件系统:
nfs 0x30000000 10.150.50.162:/home/share/zjq_162/first_rootfs.yaffs2
nand erase 0x5c0000 0x16E6240
nand write.yaffs2 0x30000000 0x5c0000 0x16E6240


记住:uboot烧写的文件系统的off,size要与kernel的默认分区的对应的block的地址相同,最好是uboot的默认nand的分区
      与内核的默认nand的分区一模一样,这样内核就可以去对应的nand的对应位置挂接文件系统。
      uboot的默认分区在jaky2440_defconfig修改或者在menuconfig搜索宏CONFIG_MTDPARTS_DEFAULT,填写分区信息
      CONFIG_MTDPARTS_DEFAULT="mtdparts=jaky2440-0:512k(bootloader),128k@0x80000(params),5m@0xA0000(kernel),128k@0x5A0000(dtb),-(rootfs)"
      
    只有uboot的分区信息跟kernel的分区信息对应,内核启动的时候才可以去对应的nand的位置挂接文件系统。


    
    

Guess you like

Origin blog.csdn.net/qq_43418840/article/details/118083101