5

UBOOT 启动流程 - 浇筑菜鸟

 1 year ago
source link: https://www.cnblogs.com/jzcn/p/16966628.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

uboot 的启动流程在网上有很多大佬记录,但是了对于像我这样的新手就有些困难了,而我也不做 uboot 相关的工作,所以没必去研究代码,这里我特意整理了一下,以流程图的形式展现代码执行的流程,方便快速了解 uboot 是怎么启动的,此笔记就不进行代码分析了,主要记录 uboot 启动流程中所执行的函数已经函数所在的文件,需要了解函数中的代码实现,可以结合 uboot 源码和正点原子的开发手册或者其他博客。

注意: uboot 运行过程中都是以单线程执行的,所以分析启动流程的时候相对多线程好理解。流程中有些函数名和文件位置可能不一样,但是不要慌,就这样慢慢的找下去就可以快速了解到自己的工程是怎么启动的了

二、SOC 启动流程

uboot 只是一个启动引导向,最终的目的是启动 linux 那么即使不使用 uboot 也可以用其他的引导向,但是目前主流都是使用的 uboot,所以这里对uboot的执行函数进行了整理,方便大家好阅读 uboot 的工程源码,在了解uboot之前,需要了解一下芯片的都做啥了。

看到这个笔记的小伙伴们,应该都知道,系统的启动方式有很多种,比如 SD、mmc、norflash、nandflash等,那么我们 uboot 就可以存在其中一个硬件设备中,芯片是怎么知道 uboot 在那里又是怎么去执行 uboot 代码的?

半导体厂商在制作芯片的时候,会在芯片内部的 ROM 中植入一小段程序,上电后芯片会先执行内部的代码,然后判断我们是以什么方式启动,并在对应的设备中找到 uboot 程序,最终启动 linux 系统,当然芯片内部的这段代码还是比较麻烦的,并且厂家也不会公布这段代码,这里我就不做过多介绍了,需要的小伙伴可以去了解一下。

芯片内部的 SRAM 是比较小的,不足以跑复杂的程序,所以当芯片找到 uboot 程序后,会执行 uboot 的一小段程序,这小段程序叫做uboot SPL,他的主要目的就是初始化芯片使用的外部 RAM 然后将剩余的 uboot 放到外部的内存中运行,提高芯片的运行能力,具体可以了解这位大佬的博客:u-boot (3) —— spl

到这里差不多了,接下来可以了解 uboot 的启动流程了。

三、uboot 入口

2406897-20221210091218154-573296504.png

在分析程序之前,都会从入口函数开始,从上图可知uboot 的入口是 u-boot.lds 链接脚本开始的。可能会有小伙变怎有疑问,我是怎么知道最先执行的 u-boot.lds 链接脚本,其实在了解一个工程之前,会先从 makefile 开始,只是我 uboot 中的makefile 比较复杂,我还有些不了明白,这里就不献丑了,有需要的小伙伴可以先看大佬的分析,所以从makefile文件中知道,最先执行的是 u-boot.lds 链接脚本。

  1. u-boot.lds
    分析 uboot 顶层 Makefile 时,得知 uboot 的启动是从链接脚本 u-boot.lds 文件开始的,所以我们需要找到 u-boot.lds 的文件位置,如果没有编译的话,最初的链接脚本在 arch/arm/cpu/ 路径下,但是这个不是最终使用的链接脚本,在编译时会在 uboot 的根目录下生成 u-boot.lds 文件,所以在编译过程中使用的是根目录下的连接脚本。

    链接脚本中描述了 uboot 的段的内存使用地址,以及中断向量表的地址,可以结合 uboot 根目录下的 u-boot.map 文件进行分析,这里就不详细介绍了。

  2. _start
    打开链接脚本后,会看到 ENTRY(_start) 声明的入口函数 _start ,而函数 _start 在 arch/arm/lib/vectors.S 文件中,此函数的作用是声明一些中断函数,当上电启动时会跳转到 reset 复位函数。

  3. reset
    reset 函数在文件 arch/arm/cpu/armv7/start.S 文件中,不同的芯片文件位置不同,我使用的芯片是armv7架构的,在 reset 函数中有 save_boot_params 、cpu_init_cp15 、 cpu_init_crit 、 _main 函数

    • save_boot_params 也在 start.S 文件中,主要是设置 CPU 的为SVC模式。
    • cpu_init_cp15 也在文件 start.S 中,主要作用是设置 CP15 相关的内容,比如关闭 MMU 啥的。
    • cpu_init_crit 也在文件 start.S 中,cpu_init_crit 内部仅仅是调用了函数 lowlevel_init。
  4. lowlevel_init
    lowlevel_init函数在文件 arch/arm/cpu/armv7/lowlevel_init.S 中,主要用于设置堆栈以调用C函数执行进一步的初始化,lowlevel_init 函数中调用了 s_init 函数。

  5. s_init
    s_init 函数在 arch/arm/cpu/armv7/xxx/soc.c 文件中,有的芯片型号中没有 soc.c 文件,而 s_init 函数没有什么作用,就可以不用了解了 。

三、uboot 外设初始化

2406897-20221210093536197-1001212292.png

此流程主要是完成 uboot 工作的基本条件,并初始一些外设,代码很多,初次学习最好不要直接对函数进行具体的分析,先了解框架。

  1. _main
    _main 函数定义在文件 arch/arm/lib/crt0.S 中,在 _main 函数主要有 board_init_f 、 relocate_code 、relocate_vectors 、 c_runtime_cpu_setup 、 board_init_r 函数

  2. board_init_f
    board_init_f 函数在文件 common/board_f.c 中,如下图所示:

    2406897-20221210094405566-644109817.png

    board_init_f 函数中会执行 init_sequence_f 表中的函数,主要有两个工作

    • 初始化一系列外设,比如串口、定时器,或者打印一些消息等。
    • 初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。

    其中 serial_init 函数初始串口后,我们就可以使用 printf 函数打印日志,打印后便会在控制台中看到相应的信息,和C语言中的用法一样,

    display_options 函数中会打印 uboot 的版本信息等,具体的函数实现只能后后面需要的时候自行了解了。

  3. relocate_code
    relocate_code 函数在文件 arch/arm/lib/relocate.S 中,主要作用是用于代码拷贝。

  4. relocate_vectors
    relocate_vectors 函数在文件 arch/arm/lib/relocate.S 中,主要作用是用于重定位向量表。

  5. c_runtime_cpu_setup
    c_runtime_cpu_setup 函数在文件 arch/arm/cpu/armv7/start.S 中

  6. board_init_r
    board_init_r 函数在文件 common/board_r.c 中,主要作用是完成 board_init_f 没有初始化的外设,以及一些后续工作。也会执行 init_sequence_r 表中的函数,在函数最后会调用 run_main_loop 函数。

  7. run_main_loop
    函数 run_main_loop 也在文件 common/board_r.c 中,此函数主要是在死循环中调用 main_loop() 函数

四、uboot 命令执行

2406897-20221210095906389-2007411179.png
  1. main_loop()
    main_loop 函数在文件 common/main.c 中,在函数中主要执行 autoboot_command 和 cli_loop 函数。

  2. autoboot_command
    autoboot_command 函数在 common/autoboot.c 中,其中会通过 Abortboot 函数判断在控制台打印的倒计时结束之前是否有按键按下,如果存在按键按下时,会执行 run_command_list 函数进入 uboot 系统。反之会返回到 main_loop 函数中执行 cli_loop 函数
    注意:run_command_list 函数也在 cli.c 文件中,只是流程图不好直观的表示出来。

  3. cli_loop
    cli_loop 在文件 common/cli.c 中,主要作用是执行相应的命令操作,在 cli_simple_loop 函数存在一个死循环,用于接收控制台的命,并处理相应的命令工作。

  4. cli_simple_run_command
    cli_simple_run_command 函数在 common/cli_simple.c 文件中,主要作用是执行相应的命令操作,从图中可以看出,不论是正常启动 linux 或 进入uboot系统,最终都会执行此函数,在函数中会调用 find_cmd 查找命令,调用 cmd_call 执行命令操作。

  5. find_cmd
    find_cmd 函数在 common/command.c 文件中,主要作用是在映射表中查找相应的命令是否存在,命令通过宏 U_BOOT_CMD 进行定义的。

  6. find_call
    find_call 函数在 common/command.c 文件中,主要作用是调用 find_cmd 中查找到的 do_xxx 函数,最终执行相应的命令操作。

  7. do_xxx
    do_xxx 函数在 cmd 目录下,作用就是命令操作的实现函数,比如启动函数 bootz 或 bootm ,所以从图中可知,不论是正常启动 linux 还是在 uboot 中通过命令启动 linux 原理都是一样的,最终也是执行 bootz 或 bootm 命令。

五、bootm 启动 Linux 内核

2406897-20221210105021347-2130333319.png

注意:这里我就没有画对应的流程图了,因为在正点原子的教材中有相应的流程图,所以我这里就直接引用了。关于启动linux 的流程我也没有仔细分析,只是大体看了一下,此笔记的主要原因是我好奇 uboot 都做了些什么,学习驱动开发是否有必要去学习 uboot 中的驱动开发。

通过对 uboot 流程的启动分析,发现 uboot 中的驱动主要根据自己在启动阶段的去求是实现驱动即可,因为在启动 linux 的时候,会在对外设驱动进行实现,达到同一管理,并且在 linux 启动后 uboot 就没有作用了,想在再次进入uboot,执行重新启动。

  1. bootm
    bootm 命令的执行函数为 do_bootm,在文件 cmd/bootm.c 中,do_bootm 最后调用的就是函数 do_bootm_states

  2. do_bootm_states
    do_bootm_states 函数定义在文件 common/bootm.c 中,函数会根据不同的 BOOT 状态执行不同的代码段。

  3. bootm_start
    bootm_start 函数在 common/bootm.c 文件中,作用是清空 images 结构体,获取 uboot 的环境变量 verify 的值

  4. bootm_find_os
    bootm_find_os 函数,函数在 common/bootm.c 文件中,在函数中会调用 boot_get_kernel,
    boot_get_kernel 会根据 bootm 传过来的参数去获取 uImage(镜像)的存储地址,如果 bootm 没有参数就使用全局变量 load_addr,最后会调用 image_get_kernel 函数进行 kernel 格式校验。

  5. bootm_find_other
    bootm_find_other 函数common/bootm.c 文件中,主要作用是获取 ramdisk 或者设备树信息。

  6. bootm_disable_interrupts
    bootm_disable_interrupts 的作用是函数禁用中断。

  7. do_bootm_linux
    do_bootm_linux 函数在 arch/arm/lib/bootm.c 文件中,次函数就是最终启动 Linux 内核的函数。

到此 uboot 的启动流程也算完成,有什么不对的地方望大佬指出,我会积极学习。

u-boot (3) —— spl:https://blog.csdn.net/zhoutaopower/article/details/123133291


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK