tnblog
首页
视频
资源
登录

ARM8 U-boot启动源码分析(学习笔记)

2633人阅读 2024/7/19 22:38 总访问:3455292 评论:0 收藏:0 手机
分类: 嵌入式

ARM8 U-boot启动源码分析(学习笔记)

什么是U-Boot?


U-Boot是嵌入式系统中首先执行的程序之一。
也是开源引导程序。

安装Jetson BSP


下载Jetson BSP包:https://developer.nvidia.com/embedded/jetson-linux-archive
我这里下的是:https://developer.nvidia.com/embedded/linux-tegra-r3275


选择驱动程序包(BSP)来源进行下载源码。

  1. scp public_sources.tbz2 yhai@192.168.153.129:/home/yhai/armv8/armboot
  2. tar -xvf public_sources.tbz2
  3. cd Linux_for_Tegra/source/public
  4. tar -xvf u-boot_src.tbz2 //解压u-boot
  5. cd u-boot
  6. ctags -R //进入代码根目录,生成符号索引
  7. cscope -Rbq

编译qemu版的u-boot

  1. cd u-boot
  2. ls configs |grep qemu # 查看可以编译的配置
  3. make qemu_arm64_defconfig # 选择qemu支持的arm64配置
  4. make #编译有生成u-boot.bin表示成功

遇到了下面的uboot问题,请安装bison解决。

  1. sudo apt-get install bison -y

遇到了缺少flex的问题,通过如下命令安装后总算是成功了。

在make的时候有遇到这个问题。

如果缺少openssl包,请到官网找到相应的包,然后用dpkg进行安装。
官网链接:http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/

  1. sudo apt install gcc-aarch64-linux-gnu
  2. export CROSS_COMPILE=aarch64-linux-gnu- #在编译 U-Boot 时,你需要设置交叉编译器环境变量。
  3. # 重新编译
  4. make distclean
  5. make qemu_arm64_defconfig
  6. make

源码入口


对于任何程序,入口函数是在链接时决定的,所以u-boot的入口是由链接脚本决定的。
u-boot下armv8默认的链接文件是u-boot.lds
当然我们也可以通过CONFIG_SYS_LDSCRIPTMakefile进行指定路径。
入口地址也是由连接器决定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定,这个会在编译时在ld连接器的选项-Ttext中。

配置 描述
OUTPUT_FORMAT 这行代码指定了链接器生成的输出文件的格式。
OUTPUT_ARCH 这行代码指定了目标架构为aarch64。
ENTRY(_start) 这行代码指定了程序的入口点函数为_start
*(.__image_copy_start) 将名为__image_copy_start的符号插入到.text段的开头。
arch/arm/cpu/armv8/start.o (.text*) 将start.o文件中所有以.text开头的段放入.text段。


接着我们来看一下start.S文件。


.global声明_start为全局符号,_start就会被连接器链接到,也就是链接脚本中的入口地址。
这段代码根据预编译宏定义选择性地包含不同的头文件,或者跳转到reset标签。
如果定义了CONFIG_LINUX_KERNEL_IMAGE_HEADER,则包含<asm/boot0-linux-kernel-header.h>文件。
如果定义了CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK,则包含<asm/arch/boot0.h>文件。
否则,跳转到reset标签。

那么这些定义是在哪儿定义的呢?是在根目录中的.config下进行定义的


.config中两个都未定义,自然就执行b reset进行重置的方法了。
下面是执行重置方法的流程图。

reset
save_boot_params
save_boot_params_ret


其中有设置异常的方法,那么我们应该如何跳转过去呢?可以使用vim


通过vim在根目录下打开文件,找到我们的Start.S文件中的vectors函数,然后通过按一下Ctrl+]跳转到对应的函数位置。

如果发现跳转不过去,请在根目录下执行ctags -R .命令,生成标签文件方便跳转。


然后我们可以通过ctrl+o跳转回来。


接着又是对异常中断的处理。


我们来看一下switch_el宏函数。


根据获取CurrentEL当前异常状态进行获取,进行el3,el2,el1不同的异常进行处理。
为什么是0xc,0x8,0x4。这是由于armv8对应的当前异常处理的值


接着在3:中有调用了set_vbar宏函数,它将在start.S文件中定义,其实就是获取异常处理特殊寄存器的处理附上处理方式。


定义好异常处理后,接着调用了apply_core_errata这个函数。
可以通过按/Enter键来查找。


这个函数对CPU做了一些处理。

branch_if_a53_core x0, apply_a53_core_errata:检查当前是否运行在 Cortex-A53 核心上。如果是,则跳转到 apply_a53_core_errata 标签,执行针对 Cortex-A53 的特定错误修复操作。

branch_if_a57_core x0, apply_a57_core_errata:检查当前是否运行在 Cortex-A57 核心上。如果是,则跳转到 apply_a57_core_errata 标签,执行针对 Cortex-A57 的特定错误修复操作。


然后调用了bl lowlevel_init进行低级别初始化。
下面是它的定义:


接着就是处理 ARM 处理器多核系统中的从属 CPU(即不是主 CPU 的其他处理器核心)的启动逻辑。


核心的cpu将直接调用_main方法。
如果找不到main函数,我们还可以从u-boot.map中找到对应的函数。


然后通过对应的文件夹找到相应的_main函数。


crt0是运行C代码之前的初始化代码。
crt0_64.S文件中有非常详细的注释。

复位系统配置函数


主要也是在start.S中的reset_sctrl函数


我们可以看到首先还是设置的异常级别的处理。
在每个异常级别里面都进行调用了b 0f为了,调用0函数。里面主要是为了清除指定位。

  1. ldr x1, =0xffffffffa //将值0xffffffffa加载到寄存器x1中。
  2. and x0, x0, x1 //对x0寄存器中的值进行按位与操作,清除后四位。


接着x1中保留当前的异常处理状态,然后再次调用switch_el,将EL3,EL2,EL1,分别对应6,5,4。
其中都调用了7

  1. dsb sy // 数据同步屏障指令,确保所有的内存访问完成。
  2. isb // 指令同步屏障指令,确保所有之前的指令执行完成。
  3. b __asm_invalidate_tlb_all // 跳转到__asm_invalidate_tlb_all函数,可能是用于失效TLB(翻译后备缓冲)。
  4. ret //返回。

低级初始化


首先保存了返回的地址到x29寄存器。

  1. WEAK(lowlevel_init)
  2. mov x29, lr /* 保存链接寄存器 (LR) x29 */
  3. #if defined(CONFIG_GICV2) || defined(CONFIG_GICV3)
  4. branch_if_slave x0, 1f /* 如果处理器不是主处理器,则跳转到标签 1 */
  5. ldr x0, =GICD_BASE /* 将中断分配器接口的基地址加载到 x0 寄存器中 */
  6. bl gic_init_secure /* 调用函数初始化 GIC 的安全部分 中断控制器的初始化 */
  7. 1:
  8. #if defined(CONFIG_GICV3)
  9. ldr x0, =GICR_BASE /* 将重新分配器接口的基地址加载到 x0 寄存器中 */
  10. bl gic_init_secure_percpu /* 调用函数初始化 GIC 的每个 CPU 的安全部分 */
  11. #elif defined(CONFIG_GICV2)
  12. ldr x0, =GICD_BASE /* 将中断分配器接口的基地址加载到 x0 寄存器中 */
  13. ldr x1, =GICC_BASE /* CPU 接口的基地址加载到 x1 寄存器中 */
  14. bl gic_init_secure_percpu /* 调用函数初始化 GIC 的每个 CPU 的安全部分 */
  15. #endif
  16. #endif


这里GICD_BASE基地址是根据不同的板子进行定义的。
接着bl gic_init_secure执行的是中断处理(GIC控制器)的初始化。

GIC 的主要功能包括:
中断管理:接收和管理来自各种外设的中断信号。
中断分发:根据中断的优先级和配置,将中断信号分发给适当的处理器核心。
中断处理:提供中断处理的相关寄存器和接口,支持中断的使能、屏蔽和优先级管理。

  1. .macro branch_if_slave, xreg, slave_label
  2. #ifdef CONFIG_ARMV8_MULTIENTRY
  3. /* 注意:在多簇机器上,这段代码可能会有问题 */
  4. mrs \xreg, mpidr_el1 /* 读取处理器ID寄存器的值到 \xreg */
  5. tst \xreg, #0xff /* 检查最低8位是否为0(处理器的第一级亲和度) */
  6. b.ne \slave_label /* 如果不是0,就跳转到 slave_label(从CPU 标签 */
  7. lsr \xreg, \xreg, #8 /* 右移8位,准备检查下一级 */
  8. tst \xreg, #0xff /* 检查第二级亲和度 */
  9. b.ne \slave_label /* 如果不是0,就跳转到 slave_label(从CPU 标签 */
  10. lsr \xreg, \xreg, #8 /* 再右移8位,准备检查下一级 */
  11. tst \xreg, #0xff /* 检查第三级亲和度 */
  12. b.ne \slave_label /* 如果不是0,就跳转到 slave_label(从CPU 标签 */
  13. lsr \xreg, \xreg, #16 /* 再右移16位,准备检查最后一级 */
  14. tst \xreg, #0xff /* 检查第四级亲和度 */
  15. b.ne \slave_label /* 如果不是0,就跳转到 slave_label(从CPU 标签 */
  16. #endif
  17. .endm


再往下其实就是不同的级别进行不同的执行和操作。

异常向量表


主要的文件在arch/arm/cpu/armv8/exceptions.S文件下。
文件的注释讲得很好,我翻译一下:

  • AArch64 异常向量:
  • 我们有四种类型的异常:
    • 同步:陷阱、数据中止、未定义指令,…
    • IRQ:第 1 组(正常)中断
    • FIQ:第 0 组或安全中断
    • SError:致命系统错误
  • 以上四个条目针对不同的上下文都有:
    • 使用 SP_EL0 堆栈指针时,来自同一异常级别
    • 使用 SP_ELx 堆栈指针时,来自同一异常级别
    • 当这是 AArch64 时,来自较低异常级别
    • 当这是 AArch32 时,来自较低异常级别
  • 这 16 个条目中的每一个都有 32 条指令的空间,每个条目必须 128 字节对齐,整个表必须 2K 对齐。
  • 32 条指令不足以保存和恢复所有寄存器并分支到实际处理程序,因此我们将其拆分:
  • 每个条目保存 LR,分支到保存例程,然后到实际处理程序,然后到恢复例程。保存和恢复例程分别分成两半并塞入条目之间未使用的间隙中。
  • 此外,由于我们不在较低的异常级别运行任何内容,我们仅提供来自同一 EL 的异常的前 8 个条目。


首先就是.align 11进行2^11=2048 整个异常向量表进行2k对齐 —> 通过对齐,实现向量空间的预留
16个异常每个异常32条指令 16324=2048
接着.globl进行全局方法名的声明。
第一个就是同步异常,然后stp x29, x30, [sp, #-16]!这条指令表示:将寄存器 x29x30 的值存储到栈上,同时将栈指针 sp 向下移动 16 个字节(因为每个寄存器占用 8 个字节的存储空间,总共需要 16 个字节),以预留存储空间。
这种操作通常用于函数调用的开头,用来保存返回地址(x30)和帧指针(x29),以便在函数返回时恢复这些值。这里每当偏移16个字节就会有一个这样的操作对应的是寄存器中的每一个异常处理。
如下图所示:


接着执行bl _exception_entry保存现场所有的值到栈中。


当然除了保存现场长常规寄存器,还保存了特殊寄存器,也就是调用了b _save_el_regs方法。
其中主要争对ELR和ESR进行保存。(但我其实看到它好像只保存了ELR的信息没有保存SER的)
代码如下:

ELR:获取导致异常的指令的地址。
ESR:包括有关异常信息的原因。


接着返回到上面,执行bl do_bad_sync命令。
/Linux_for_Tegra/source/public/u-boot/arch/arm/lib/interrupts_64.c文件中找到同步处理的方法。


最后调用b exception_exit指令进行恢复现场里面就是一些出栈的操作。


eret返回当初应用异常的位置,这样一系列的操作就完成了下面这幅图的流程。


中断过程的流程同样有些相似。
在刚刚下面就是处理中断异常的代码。

中断控制器初始化


bl gic_init_secure命令执行的是中断处理(GIC控制器)的初始化操作,这段代码我们在低级初始化的时候提到过。
在这之前执行了获取GIC基地址到wx0寄存器中,然后由gic_init_secure操作。


任何硬件或软件发送中断型号到管脚中,然后发送到CPU进行处理。


像按键触发到GIC中断控制器后,将再次发到多核中进行处理。
关于它定义的基地址50041000可以查看芯片手册。

C语言执行环境初始化


crt0_64.S文件主要是初始化c语言的代码。


1.设置栈堆。如果当前的编译是SPL(由CONFIG_SPL_BUILD定义),可单独定义堆栈基址(CONFIG_SPL_STACK),否则,通过CONFIG_SYS_INIT_SP_ADDR定义栈堆。

2.调用board_init_f_alloc_reserve接口,从栈堆开始的地方,为全局数据(gdglobal data)结构分配空间。
也就是预留一些数据空间。


接着调用board_init_f()函数,完成一些前期的初始化工作


board_init_f()函数的一些源码,例如:
点亮一个Debug用的LED灯,表示u-boot已经激活。
初始化DRAM、DDR等system范围的RAM。
启用指令缓存(Instruction Cache)。
3.如果当前是SPL(由CONFIG_SPL_BUILD控制),则_main()函数结束,直接返回。如果是正常的u-boot,则继续执行后续的动作。
4.根据board_init_f()函数指定的参数,执行u-boot的relocation操作。
5.清理BBS段。
6.调用board_init_r()函数,执行后续的初始化炒作。

board_init_f()函数


board_init_f()函数主要是更具配置全局信息结构体gd进行初始化,主要工作内容如下:
1.初始化一系列外设,比如串口、定时器,获取打印一些消息等。
2.初始化gd(global data)的各个成员变量,u-boot会将自己重定位到DRAM后面的地址区域,也就是将自己复制到DRAM(DRAM [Dynamic Random-Access Memory,动态随机存取存储器]是一种广泛使用的存储器类型,用于计算机和其他电子设备中的主内存。)后面的的内存区域中。
这么做的目的是给Linux腾出空间,防止Linuxkernel覆盖u-boot,将DRAM当前的区域完整的空出来。
在复制之前肯定要给u-boot各个部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该放到哪个位置等。
这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。

relocate_code()函数


接着我们回到_main方法。
这段代码主要是更新gd结构体。

  1. #if !defined(CONFIG_SPL_BUILD)
  2. /*
  3. * Set up intermediate environment (new sp and gd) and call
  4. * relocate_code(addr_moni). Trick here is that we'll return
  5. * 'here' but relocated.
  6. */
  7. ldr x0, [x18, #GD_START_ADDR_SP] /* 从gd结构中加载start_addr_sp到x0寄存器 */
  8. bic sp, x0, #0xf /* 将sp寄存器设置为x0的值,并且16字节对齐,满足ABI规范 */
  9. ldr x18, [x18, #GD_NEW_GD] /* 从gd结构中加载new_gd到x18寄存器 */
  10. adr lr, relocation_return /* 将重定位返回地址加载到链接寄存器lr */
  11. #if CONFIG_POSITION_INDEPENDENT
  12. /* Add in link-vs-runtime offset */
  13. adr x0, _start /* 获取当前_start的运行时地址到x0寄存器 */
  14. ldr x9, _TEXT_BASE /* 获取_start的链接时地址到x9寄存器 */
  15. sub x9, x9, x0 /* 计算运行时地址与链接时地址的偏移量并存储在x9寄存器 */
  16. add lr, lr, x9 /* 将这个偏移量加到lr寄存器中,得到新的返回地址 */
  17. #endif
  18. /* Add in link-vs-relocation offset */
  19. ldr x9, [x18, #GD_RELOC_OFF] /* 从gd结构中加载重定位偏移量到x9寄存器 */
  20. add lr, lr, x9 /* 将这个偏移量加到lr寄存器中,得到新的返回地址 */
  21. ldr x0, [x18, #GD_RELOCADDR] /* 从gd结构中加载重定位地址到x0寄存器 */
  22. b relocate_code /* 跳转到重定位代码 */
  23. relocation_return: /* 重定位完成后的返回标签 */
  24. #endif


b relocate_code这段代码主要负责重定位U-Boot的监控代码。
这段代码主要负责重定位U-Boot的监控代码。
在嵌入式系统中,U-Boot通常在启动时将其自身从非易失性存储(如闪存)复制到易失性存储(如RAM),然后继续执行。

  1. #include <asm-offsets.h>
  2. #include <config.h>
  3. #include <elf.h>
  4. #include <linux/linkage.h>
  5. #include <asm/macro.h>
  6. /*
  7. * void relocate_code(addr_moni)
  8. *
  9. * This function relocates the monitor code.
  10. * x0 holds the destination address.
  11. */
  12. ENTRY(relocate_code)
  13. stp x29, x30, [sp, #-32]! /* 创建一个栈帧,保存x29和x30寄存器 */
  14. mov x29, sp
  15. str x0, [sp, #16]
  16. /*
  17. * u-boot从闪存复制到RAM
  18. */
  19. adrp x1, __image_copy_start /* x1 <- 地址的高20 */
  20. add x1, x1, :lo12:__image_copy_start/* x1 <- 地址的低12 */
  21. subs x9, x0, x1 /* x9 <- 运行时地址与复制地址的偏移量 */
  22. b.eq relocate_done /* 如果已经在正确位置,跳过重定位 */
  23. /*
  24. * 不能在此处使用ldr x1, __image_copy_start,因为如果代码已经
  25. * 运行在不同于链接地址的位置,该指令会加载重定位后的值。
  26. * 为了正确应用重定位,我们需要知道链接时的值。
  27. *
  28. * 链接时的&__image_copy_start,我们知道它在
  29. * CONFIG_SYS_TEXT_BASE,该值存储在_TEXT_BASE
  30. * 不是符号引用,所以不会被重定位。
  31. */
  32. ldr x1, _TEXT_BASE /* x1 <- 链接时的&__image_copy_start */
  33. subs x9, x0, x1 /* x9 <- 链接地址与复制地址的偏移量 */
  34. adrp x1, __image_copy_start /* x1 <- 地址的高20 */
  35. add x1, x1, :lo12:__image_copy_start/* x1 <- 地址的低12 */
  36. adrp x2, __image_copy_end /* x2 <- 地址的高20 */
  37. add x2, x2, :lo12:__image_copy_end /* x2 <- 地址的低12 */
  38. copy_loop:
  39. ldp x10, x11, [x1], #16 /* 从源地址[x1]复制 */
  40. stp x10, x11, [x0], #16 /* 到目标地址[x0] */
  41. cmp x1, x2 /* 直到源地址的结束地址[x2] */
  42. b.lo copy_loop
  43. str x0, [sp, #24]
  44. /*
  45. * 修正.rela.dyn重定位
  46. */
  47. adrp x2, __rel_dyn_start /* x2 <- 地址的高20 */
  48. add x2, x2, :lo12:__rel_dyn_start /* x2 <- 地址的低12 */
  49. adrp x3, __rel_dyn_end /* x3 <- 地址的高20 */
  50. add x3, x3, :lo12:__rel_dyn_end /* x3 <- 地址的低12 */
  51. fixloop:
  52. ldp x0, x1, [x2], #16 /* (x0,x1) <- (源位置, 修正值) */
  53. ldr x4, [x2], #8 /* x4 <- 加数 */
  54. and x1, x1, #0xffffffff
  55. cmp x1, #R_AARCH64_RELATIVE
  56. bne fixnext
  57. /* 进行相对修正:将加数加上偏移量存储在目标位置 */

简单来讲:这段代码的作用是将 U-Boot 程序从闪存(或其他非易失性存储器)复制到 RAM(随机存取存储器)中。
这样做的原因是 RAM 的速度比闪存快,程序运行起来会更高效。

board_init_r()函数


relocate_code()函数回到_main()函数中,接下来是main()函数最后一段代码,如图11-17所示。


首先跳转到c_runtime_cpu_setup中,如果icache为启用,则icache无效,保证从sdram中更新指令到cache中。接着更新异常向量表首地址,因为代码被重新定位,所以异常向量表也被重新定位。
接着清空BBS段。

  1. bl c_runtime_cpu_setup /* 调用旧的例行程序,进行CPU的初始化设置 */
  2. #endif /* !CONFIG_SPL_BUILD */
  3. #if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
  4. #if defined(CONFIG_SPL_BUILD)
  5. bl spl_relocate_stack_gd /*该函数负责在 SPL 构建(Secondary Program Loader,二级引导程序)期间进行栈和全局数据结构 gd 的重定位。 可能返回NULL,调用spl_relocate_stack_gd函数 */
  6. /* set up gd here, outside any C code, if new stack is returned */
  7. cmp x0, #0
  8. csel x18, x0, x18, ne
  9. /*
  10. * Perform 'sp = (x0 != NULL) ? x0 : sp' while working
  11. * around the constraint that conditional moves can not
  12. * have 'sp' as an operand
  13. */
  14. mov x1, sp
  15. cmp x0, #0
  16. csel x0, x0, x1, ne
  17. mov sp, x0
  18. #endif


x0赋值gd指针,x1赋值relocaddr变量sp(栈指针),然后进入board_init_r()函数。


board_init_r()函数是需要实现的版级支持函数,做开发板的基本初始化.
该函数是实现开发板所有功能的初始化,包括CPU、内存、串口、电源、环境变量、中断、网络等。


比如串口初始化,init_sequence_r()函数中有initr_serial命令调用serial_initialize()函数源码实现在drivers/serial/serial.c文件中。


里面有很多平台的串口驱动。

  1. void serial_initialize(void)
  2. {
  3. // 初始化 Atmel 的串行端口
  4. atmel_serial_initialize();
  5. // 初始化 ColdFire 的串行端口
  6. mcf_serial_initialize();
  7. // 初始化 Freescale MPC85xx 的串行端口
  8. mpc85xx_serial_initialize();
  9. // 初始化 Freescale i.MX 的串行端口
  10. mxc_serial_initialize();
  11. // 初始化 NS16550 兼容的串行端口
  12. ns16550_serial_initialize();
  13. // 初始化 ARM PL010 PL011 串行端口
  14. pl01x_serial_initialize();
  15. // 初始化 PXA 的串行端口
  16. pxa_serial_initialize();
  17. // 初始化 SuperH 的串行端口
  18. sh_serial_initialize();
  19. // 初始化 MediaTek 的串行端口
  20. mtk_serial_initialize();
  21. // 分配默认串行控制台
  22. serial_assign(default_serial_console()->name);
  23. }


这些函数将所有需要的串口(用结构体struct serial_device函数表示,其中实现了基本的收发配置)调用serial_register函数注册。


serial_dev()函数加到全局链表中serial_devices中。可以想象,如果有4个串口则在串口驱动中分别定义4个串口设备,并实现对应的收发配置,然后调用serial_register()函数注册4个串口。

串口注册后,代码回到serial_initialize中继续执行serial_assign(default_serial_console()->name);,串口驱动给出一个默认调试串口,serial_assign()函数的部分内容下面所示:

  1. /**
  2. * serial_assign() - 选择串行输出设备
  3. * @name: 需要作为默认输出的串行驱动名称
  4. *
  5. * 这个函数通过选择哪个串行设备作为默认输出来配置串行输出的多路复用。
  6. * 如果 STDIO "serial" 设备被选择作为标准输入/输出/错误输出,
  7. * 那么之前由这个函数配置的串行设备将用于特定的操作。
  8. *
  9. * 成功时返回 0,错误时返回负值。
  10. */
  11. int serial_assign(const char *name)
  12. {
  13. struct serial_device *s;
  14. // 遍历所有已注册的串行设备
  15. for (s = serial_devices; s; s = s->next) {
  16. // 比较当前设备的名称与传入的名称
  17. if (strcmp(s->name, name))
  18. continue; // 如果名称不匹配,则继续检查下一个设备
  19. // 如果名称匹配,则将当前设备设置为默认串行设备
  20. serial_current = s;
  21. return 0; // 成功返回 0
  22. }
  23. // 如果没有找到匹配的设备,返回错误代码 -EINVAL
  24. return -EINVAL;
  25. }


首先serial_assign()函数就是从serial_devices函数扎到指定的默认调试串口,其次条件就是串口default_Serial_Console变量中默认的串口设备名,最后serial_current()函数就是当前的默认串口。
serial_initialize函数的工作是将serial驱动中所有的串口注册到serial_devices()函数链表中,然后找到指定的默认串口。


回到board_r.c文件中的init_sequence_r函数我们往下看可以看到初始化电源部分、nand初始化、API()函数初始化、控制台初始化、中断使能等,最后到执行run_main_loop()函数。


run_main_loop()函数调用了main_loop()函数,main_loop()函数中会有延时函数bootdelay_process()。下图就是开机时延时几秒进入内核,然后调用autoboot_command环境变量,也就是开机时下任一按键,开始操作控制台。


autoboot_command()函数中有abortboot()函数调用``

  1. /**
  2. * abortboot - 检查是否需要中止启动
  3. * @bootdelay: 启动延迟时间(秒)
  4. *
  5. * 该函数用于检测是否需要中止启动过程。
  6. * 如果启动延迟时间大于或等于0,则根据配置检查是否需要中止启动。
  7. *
  8. * 返回 1 表示中止启动,0 表示继续启动。
  9. */
  10. static int abortboot(int bootdelay)
  11. {
  12. // 定义一个变量,用于标记是否中止启动,初始值为 0
  13. int abort = 0;
  14. // 如果启动延迟时间大于或等于 0
  15. if (bootdelay >= 0) {
  16. // 如果启用了带键序列的自动启动中止功能
  17. if (IS_ENABLED(CONFIG_AUTOBOOT_KEYED))
  18. // 调用函数检测键序列是否中止启动,并设置 abort 的值
  19. abort = abortboot_key_sequence(bootdelay);
  20. else
  21. // 否则,调用函数检测单个按键是否中止启动,并设置 abort 的值
  22. abort = abortboot_single_key(bootdelay);
  23. }
  24. // 如果启用了静默控制台模式,并且中止启动
  25. if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && abort)
  26. // 清除全局数据标志中的静默标志,恢复正常输出
  27. gd->flags &= ~GD_FLG_SILENT;
  28. // 返回是否中止启动的标志
  29. return abort;
  30. }


这一段主要在中间判断那儿:假设你启动了设备,它会有一段延迟时间(比如几秒钟),在这段时间内,你可以按下某个键来中止启动,让你有机会进入一些调试模式或者其他操作。
代码会检查你是否启用了一个配置选项(CONFIG_AUTOBOOT_KEYED),这个配置决定了你是要按一个特定的键序列(比如连续按下几个特定的键),还是只需要按一个单独的键来中止启动。
如果没有启用键序列方式,那么代码就会调用函数 abortboot_single_key

abort = abortboot_key_sequence(bootdelay):如果启用了键序列方式,这行代码会在启动延迟期间检查你是否按下了正确的键序列。如果你按下了,abort 变量会被设置为 1,表示你想中止启动。
abort = abortboot_single_key(bootdelay):如果没有启用键序列方式,这行代码会在启动延迟期间检查你是否按下了一个特定的键。如果你按下了,abort 变量会被设置为 1,表示你想中止启动。


abortboot_single_key函数被调用后,会打印Hit any key to stop autoboot


main_loop()函数最后会有cli_loop()函数,cli_loop()函数调用cli_simple_loop()函数,里面有run_command_repeatable(lastcommand,flag)函数,如下图所示,最后运行lastcommand字符串中的命令启动内核。

调试源码


在u-boot目录下进行编译。

  1. cd u-boot
  2. export CROSS_COMPILE=aarch64-linux-gnu-
  3. make qemu_arm64_defconfig
  4. make


启动一个具有 4 个 Cortex-A57 CPU 核心和 2048 MB 内存的 ARM64 虚拟机,使用 u-boot.bin 作为引导加载程序,通过命令行界面进行输入输出,启动时暂停并启用 GDB 调试服务器。

  1. qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -smp 4 -m 2048 -bios u-boot.bin -S -s


设置lanuch.json调试文件。

  1. {
  2. // 使用 IntelliSense 了解相关属性。
  3. // 悬停以查看现有属性的描述。
  4. // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  5. "version": "0.2.0",
  6. "configurations": [
  7. {
  8. "name": "(gdb) 启动",
  9. "type": "cppdbg",
  10. "request": "launch",
  11. "program": "${workspaceFolder}/u-boot",
  12. "args": [],
  13. //stopAtEntry 设为true 让它在第一条指令处停下来
  14. "stopAtEntry": true,
  15. "cwd": "${fileDirname}",
  16. "environment": [],
  17. "externalConsole": false,
  18. "MIMode": "gdb",
  19. // 如果远程登入到linux 服务器上面,路径不用写 /user/bin/gdb-multiarch
  20. "miDebuggerPath": "gdb-multiarch",
  21. "miDebuggerServerAddress": "localhost:1234",
  22. "setupCommands": [
  23. {
  24. "description": "为 gdb 启用整齐打印",
  25. "text": "-enable-pretty-printing",
  26. "ignoreFailures": true
  27. }
  28. ]
  29. }
  30. ]
  31. }


开始调试。

在调试时如果发现中途没有找到common.h文件,打开命令面板:按下 Ctrl+Shift+P,输入并选择“C/C++: Edit Configurations (JSON)”。来解决。


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

C ?、?? 问号和2个问号的用法类型?、对象?

C# ?C# ???:单问号1.定义数据类型可为空。可用于对int,double,bool等无法直接赋值为null的数据类型进行null的赋值如这...

Python实例 1-日志抓取处理 补错附日志小技巧

有时候数据出了问题,可以从日志中恢复数据(如果你没记日志..没备份..→_→..)一、日志展示介绍个平常自己用的小方法,如...

C 数组拆分泛型

主要用到了泛型。泛型是c#2.0的一个新增加的特性,它为使用c#语言编写面向对象程序增加了极大的效力和灵活性。不会强行对值...

MySQL 视图的增删改 查

要显示视图的定义,需要在SHOWCREATEVIEW子句之后指定视图的名称, 我们先来创建几张表,完事后在进行演示:--用户信息表...

使用NPOI导出excel包括图片

Excl模板导出相信我们都会,那么模板上要导出图片呢?嗯~还是来个例子:准备工作:首先要引用NPOI包:然后获取数据集(我这...

ajaxSubmit异步上传图片嘘,外面都是假的

引用代码&lt;scriptsrc=&quot;/Scripts/jquery.form.js&quot;&gt;&lt;/script&gt;js就在旁边img链接中,只不过大小为0x0,...

.NET MVC 使用百度编辑器详细教程:1配置编辑器

一、什么是百度编辑器百度编辑器UEditor是由百度web前端研发部开发一款应用于网站的编辑器,具有轻量,可定制,注重用户体...

使用jquery操作元素的css样式获取、修改等等

使用jquery操作元素的css样式(获取、修改等等) //1、获取和设置样式 $(&quot;#tow&quot;).attr(&quot;class&quot;)...

.net辗转java系列视野

.net辗转java系列(一)视野.net系java系其它语言C#Java框架.net Framework Standardjava se.net corejava eejave meJava S...

.NET MVC json对象或者json对象数组的序列化和反序列化

1、用JSON.stringify()将对象stuarr或者json数组stuarr序列化成字符串,然后提交给后台。$.post(&quot;/home/DoUpdate&quot...

.NET MVC json对象或者json对象数组的序列化和反序列化

1、用JSON.stringify()将对象stuarr或者json数组stuarr序列化成字符串,然后提交给后台。$.post(&quot;/home/DoUpdate&quot...

mui框架-移动端跳转以及传值的简单方法修改解决方法

纠结了两天的MUI跳转的问题,终于解决了 ,现在分享给大家,希望大家有什么坑的解决也给我分享分享 哈哈,废话不多说,上代...

MVC全局异常处理错误日记

1、在Filter文件夹中创建一个IsExceptionFilter类(类名随意取)2、使用3、在访问的页面控制器中添加几个错误4、在IsExcept...

MVC全局异常处理错误日记

1、在Filter文件夹中创建一个IsExceptionFilter类(类名随意取)2、使用3、在访问的页面控制器中添加几个错误4、在IsExcept...

Hbuilder打包APP的教程会操作的略过

首先项目必须是APP端的,可能讲解有点啰嗦,讲解准备的工具:HBuilderX(其他版本也可以,这里用X版本来讲解)、待测试手机...
这一世以无限游戏为使命!
排名
2
文章
633
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术