• 02-uboot启动内核前到底做了哪些必要工作


    0 说明

            嵌入式Bootloader,基本各大厂都在使用uboot,想要自己实现bootloader,那么对uboot启动内核前做了哪些工作认清后,自己实现bootloader将更有针对性和简单性。

            本文主要是从打印上分析证明uboot启动内核前做的必要工作。uboot对启动影响不大的内容,本文不关注,因为uboot做与不做,内核都将启动。

    1、uboot启动内核命令

            uboot启动内核的命令在bootcmd中存储。可以在bootcmd中选择从哪里加载内核,然后启动。这里调试方便,将uboot设置为从网络加载文件然后启动,因此bootcmd可能这么设置:

    1. bootcmd:
    2. setenv bootargs 'console=ttymxc3,115200 imx2-wdt.timeout=120 root=/dev/mmcblk1p2 rootwait rw';dhcp zImage;dhcp 0x83000000 myd-y6ull-emmc.dtb;bootz 0x80800000 - 0x83000000

    首先将内核、dtb拷贝到ddr,然后通过bootz启动内核

    2、bootz需要什么

            bootz命令执行分析将转门有一篇文章,这里直接分析到do_bootz结果,do_bootz根据几个阶段传入不同宏给do_bootm_states函数走不同流程,do_bootm_states有下面一段:

    1. /* Now run the OS! We hope this doesn't return */
    2. if (!ret && (states & BOOTM_STATE_OS_GO))
    3. ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
    4. images, boot_fn);

            到此,uboot将不希望返回,boot_selected_os,调用注册的boot_fn进入启动内核阶段。boot_fn在bootm_os_get_boot_func中被赋值为do_bootm_linux。

            do_bootm_linux中主要是boot_prep_linux、boot_jump_linux。boot_jump_linux为跳转内核。

            boot_jump_linux中,关键的一行代码:

    1. kernel_entry = (void (*)(int, int, uint))images->ep;
    2. r2 = (unsigned long)images->ft_addr;
    3. kernel_entry(0, machid, r2);

    打印一下:

    machid=0 r2=83000000  kernel_entry=80800000

    因此跳转前需要:

            两个地址:内核链接地址和dtb所在地址。r2=83000000就是dtb在前面bootcmd中的加载地址,80800000是内核编译后可以知道的连接地址。然后一句函数指针kernel_entry(0, machid, r2)内核就起来了。      

            接下来的问题是,这俩地址一个放内核,一个放设备树,然后跳转就起来了,那么bootargs怎么没体现用在哪里?bootargs中包含了内核启动的串口,文件系统等信息,是uboot和内核传递的一个关键信息。

            早期uboot没有使用设备树的时候,r2传递的是一个地址,那个地址中放了很多TAG,其中有一个TAG就是存放bootargs信息的,内核从r2地址中找到bootargs。显然现在这个工作给到设备树去处理了。

            因此,uboot对设备树进行了修改,在flash上的设备树基础上,增加了一些内容,其中就包含bootargs信息。我们可以从启动信息上得知uboot对设备树做了操作。

    1. uboot中设备树加载:
    2. TFTP from server 192.168.1.108; our IP address is 192.168.1.187
    3. Filename 'myd-y6ull-emmc.dtb'.
    4. Load address: 0x83000000
    5. Loading: ###
    6. 3 MiB/s
    7. done
    8. Bytes transferred = 34414 (866e hex)
    9. uboot启动前的打印
    10. ## Flattened Device Tree blob at 83000000
    11. Booting using the fdt blob at 0x83000000
    12. Using Device Tree in place at 83000000, end 8300b66d

    可以看到,加载时候dtb是0x866e,启动的时候,设备树使用空间是b66d。

    3、uboot对设备树的修改

            到底uboot对设备树做了啥呢,分析代码流程太多,不如直接打印出来前后的设备树直接对比。

    先确认,uboot加载的设备树在flash上的文件myd-y6ull-emmc.dtb,这个文件就是编译内核后的设备树文件。dtc -I dtb -O dts -o myd-y6ull-emmc.dts myd-y6ull-emmc.dtb改为dts。

    另一个uboot启动内核调用kernel_entry,加入打印,将设备树打印出来:

     

     将其存到txt中,先转二进制,然后再转dts

    1. root@xxzh:~/src# xxd -r -p uboot-dtb.txt uboot-dtb.dtb
    2. root@xxzh:~/src# dtc -I dtb -O dts -o uboot-dtb.dts uboot-dtb.dtb

    对比myd-y6ull-emmc.dts(加载进ddr前)、uboot-dtb.dts(启动前) 

     可见将bootargs放了进来,同时增加了一段保留区域给内核,让内核不要占用设备树这块内存。

    不过这些信息,完全可以在编译内核的时候定下来,这样自己实现bootloader的时候就可以不对设备树进行修改,毕竟是希望实现一个最小的boot程序。

    总结

            分析起uboot代码总是各种流程,各种初始化,各种跳转,层层函数迭代,抓取其主要目的是启动内核,从最根源打印,其实uboot并不需要做太多必须的工作,这里必要的工作指不存在将无法启动内核的操作。必要工作主要包含:

    uboot启动内核所做必须工作

    • 必要初始化:时钟、flash驱动初始化(emmc、sd、nand等),可从flash读取内核、设备树到ddr
    • 初始化内核启动参数(bootargs压入设备树,可选)
    • 跳转启动内核 

            对于uboot其他的初始化,设置,菜单等,都是可选的和无关紧要的。因此自己实现Bootloader的时候抓住以上几点即可。

  • 相关阅读:
    nvm 安装使用
    mybatis注解之@Mapper和@MapperScan的使用
    使用计算组自动设置列宽
    【SQL刷题】Day12----SQL汇总数据专项练习
    JavaScript —— 算法思想之递归和映射
    二维卡通数字人解决方案
    C++的string容器->基本概念、构造函数、赋值操作、字符串拼接、查找和替换、字符串比较、字符存取、插入和删除、子串
    TS-RadiMation测试软件如何在序列测试中发挥作用?
    树 —— 树和森林的遍历
    深入解析kubernetes中的选举机制
  • 原文地址:https://blog.csdn.net/fengyuwuzu0519/article/details/126358518