前言 在rk平台上 uboot的适配已经非常完善,基本不需要开发人员进行二次开发
但是如海思,fulhan此类,需要用到tftp烧录和flash的平台,需要在uboot里面修改bootarg
bootcmd,还需要移植phy的驱动等,如果只有一个mac对接的swith,甚至需要移植交换机的驱动
深入学习uboot还是很有必要的,以下为我个人对uboot 的学习笔记,主要参考各路大神,
让自己对uboot不只是停留在表面,而是结合arm架构对uboot有一个更深刻的认识
本文参考csdn 对海思3521a对uboot 2010.06的分析
https://caibiao-lee.blog.csdn.net/article/details/103239130
以及以及知乎博主等等
对fulhan为平台使用的 2017.09 uboot进行分析,以便于更深刻的理解uboot的启动流程和dm驱动框架
个人学习笔记。
1.顶层makefile 主要功能:
确定版本号及主机信息
确认版本号
获取cpu架构 和 os
makefile的函数调用与变量调用很类似,格式是$( function arguments),其实上面一大段的意思是变量HOSTARCH的值是一个函数的返回值shell是makefile中的一个函数,$(shell XXX)会被解析成执行shell命令XXX;此处是执行了一条 uname -m |sed -e ……,符号 \是makefile的换行符其中,|是shell语法中的管道结构,例如:XXX | YYY ,表达式XXX 的输出将作为表达式YYY的输入,YYY的输出才是整句表达式的输出uname -m 指令将输出负责编译的主机cpu架构,比如ixx86;sed -e是替换命令,比如把ixx86替换为i386,由此可见这个HOSTARCH变量的值将得到负责编译的主机cpu架构。大部分情况下我们得到的都是i386
这个HOSTOS变量和上一句HOSTARCH变量的原理类似,管道第一部分uname -s会得到负责编译的主机的OS,比如Linux
管道第二部分是将大写转换成小写
管道第三部分的意思是如果前面一个部分得到了cygwin系统,则格式要转换一下。不必深究,因为cygwin基本没人用……
由此可见这个HOSTOS变量的值将得到负责编译的主机操作系统,大部分情况下我们得到的都是linux
设置各种路径
这里开始阐述了makefile所支持的“单独外部路径编译”,
它的原理和keil把.o、.l、.bin、.a等中间文件放在单独的文件夹内的原理相同,都是为了使源码更简洁明了,
防止被中间文件污染;以及为了同时维护多个配置编译方式
若要使用单独外部路径编译,有两种方法,
方法一:例如在控制台中输入 make O=/tmp/build ,将输出文件夹路径作为参数
方法二:导出环境变量,即export BUILD_DIR=/tmp/build
注:方法一的优先级高,会覆盖方法二。
而且方法一必须每次输入make时都要输入参数(不论是make clean还是make config 的时候),
格式如make O=/tmp/build disclean
设置obj的存放位置
设置编译工具链
设置规则
本段出现了顶层makefile的第一条规则(即目标-依赖-操作),
故默认情况下将以all这个变量作为最终目标。但是由于本条规则没有任何操作,
所以一旦把依赖(也就是$(ALL))实现了,整个makefile文件的最终目标就达成了
可以认为,本makefile的终极目标其实是$(ALL)所代表的那些文件
下面的这些都是为了产生最终文件的规则
2.u-boot.lds
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 /* SPDX-License-Identifier: GPL-2.0+ */ /* * (C) Copyright 2013 * David Feng <fenghua@phytium.com.cn> * * (C) Copyright 2002 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de> */ #include <config.h> #include <asm/psci.h> OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") OUTPUT_ARCH(aarch64) ENTRY(_start) -------------------------------------------------------------------- (1) /* *(1)首先定义了二进制程序的输出格式为"elf64-littleaarch64", * 架构是"aarch64",程序入口为"_start"符号; */ SECTIONS { #ifdef CONFIG_ARMV8_SECURE_BASE -------------------------------------------------- (2) /* *(2)ARMV8_SECURE_BASE是u-boot对PSCI的支持,在定义时可以将PSCI的文本段, * 数据段,堆栈段重定向到指定的内存,而不是内嵌到u-boot中。 * 不过一般厂商实现会使用atf方式使其与bootloader分离,这个功能不常用; */ /DISCARD/ : { *(.rela._secure*) } #endif . = 0x00000000; -------------------------------------------------------------- (3) /* *(3)定义了程序链接的基地址,默认是0,通过配置CONFIG_SYS_TEXT_BASE可修改 * 这个默认值。 */ . = ALIGN(8); .text : { *(.__image_copy_start) --------------------------------------------------- (4) /* *(4)__image_copy_start和__image_copy_end用于定义需要重定向的段, * u-boot是一个分为重定向前初始化和重定向后初始化的bootloader, * 所以此处会定义在完成重定向前初始化后需要搬运到ddr中数据的起始地址和结束地址; * * 大多数时候u-boot是运行在受限的sram或者只读的flash上, * u-boot为了启动流程统一会在ddr未初始化和重定位之前不去访问全局变量, * 但是又为了保证u-boot能够正常读写全局变量,内存,调用各类驱动能力, * 所以u-boot将启动初始化分为了两个部分,重定向前初始化board_f和 * 重定向后初始化 board_r,在重定向之前完成一些必要初始化, * 包括可能的ddr初始化,然后通过__image_copy_start和__image_copy_end * 将u-boot搬运到ddr中,并在ddr中进行重定向后初始化,这个时候的u-boot就可以 * 正常访问全局变量等信息了。 * * 如果想要在board_f过程中读写一些全局变量信息该怎么办呢? * u-boot通过定义global_data(gd)来完成此功能, * 后续在分析到时会详细讲解实现方式。 */ CPUDIR/start.o (.text*) -------------------------------------------------- (5) /* *(5)定义了链接程序的头部文本段,armv8就是 * arch/arm/cpu/armv8/start.S, * start.S中所有文本段将会链接到此段中并且段入口符号就是_start; */ } /* This needs to come before *(.text*) */ .efi_runtime : { ------------------------------------------------------------ (6) /* *(6)在定义了efi运行时相关支持时才会出现使用的段,一般不用关心; */ __efi_runtime_start = .; *(.text.efi_runtime*) *(.rodata.efi_runtime*) *(.data.efi_runtime*) __efi_runtime_stop = .; } .text_rest : ---------------------------------------------------------------- (7) /* *(7)除了start.o,其他的所有文本段将会链接到此段中; */ { *(.text*) } #ifdef CONFIG_ARMV8_PSCI -------------------------------------------------------- (8) /* *(8)同(2),是PSCI相关功能的支持,一般不会使用; */ .__secure_start : #ifndef CONFIG_ARMV8_SECURE_BASE ALIGN(CONSTANT(COMMONPAGESIZE)) #endif { KEEP(*(.__secure_start)) } #ifndef CONFIG_ARMV8_SECURE_BASE #define CONFIG_ARMV8_SECURE_BASE #define __ARMV8_PSCI_STACK_IN_RAM #endif .secure_text CONFIG_ARMV8_SECURE_BASE : AT(ADDR(.__secure_start) + SIZEOF(.__secure_start)) { *(._secure.text) . = ALIGN(8); __secure_svc_tbl_start = .; KEEP(*(._secure_svc_tbl_entries)) __secure_svc_tbl_end = .; } .secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text)) { *(._secure.data) } .secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data), CONSTANT(COMMONPAGESIZE)) (NOLOAD) : #ifdef __ARMV8_PSCI_STACK_IN_RAM AT(ADDR(.secure_stack)) #else AT(LOADADDR(.secure_data) + SIZEOF(.secure_data)) #endif { KEEP(*(.__secure_stack_start)) . = . + CONFIG_ARMV8_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE; . = ALIGN(CONSTANT(COMMONPAGESIZE)); KEEP(*(.__secure_stack_end)) } #ifndef __ARMV8_PSCI_STACK_IN_RAM . = LOADADDR(.secure_stack); #endif .__secure_end : AT(ADDR(.__secure_end)) { KEEP(*(.__secure_end)) LONG(0x1d1071c); /* Must output something to reset LMA */ } #endif . = ALIGN(8); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } ------------------- (9) /* *(9)所有仅读数据将会在这个段中对齐排序存放好; */ . = ALIGN(8); .data : { -------------------------------------------------------------------- (10) /* *(10)所有数据段将会链接到此段中; */ *(.data*) } . = ALIGN(8); . = .; . = ALIGN(8); .u_boot_list : { ------------------------------------------------------------- (11) /* *(11)u_boot_list段定义了系统中当前支持的所有命令和设备驱动,此段把散落在各个文件中 * 通过U_BOOT_CMD的一系列拓展宏定义的命令和U_BOOT_DRIVER的拓展宏定义的设备驱动收集到一起, * 并按照名字排序存放,以便后续在命令行快速检索到命令并执行和检测注册的设备和设备树匹配 * probe设备驱动初始化;(设备驱动的probe只在定义了dm模块化驱动时有效) */ KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(8); .efi_runtime_rel : { __efi_runtime_rel_start = .; *(.rel*.efi_runtime) *(.rel*.efi_runtime.*) __efi_runtime_rel_stop = .; } . = ALIGN(8); .image_copy_end : { *(.__image_copy_end) } . = ALIGN(8); .rel_dyn_start : -------------------------------------------------------- (12) /* *(12)一般u-boot运行时是根据定义的基地址开始执行,如果加载地址和链接地址 * 不一致则会出现不能执行u-boot的问题。通过一个 * 配置CONFIG_POSITION_INDEPENDENT即可打开地址无关功能, * 此选项会在链接u-boot时添加-PIE参数。此参数会在u-boot ELF文件中 * 生成rela*段,u-boot通过读取此段中表的相对地址值与实际运行时地址值 * 依次遍历进行修复当前所有需要重定向地址,使其可以实现地址无关运行; * 即无论链接基地址如何定义,u-boot也可以在任意ram地址 * 运行(一般需要满足最低4K或者64K地址对齐); * * 注意此功能只能在sram上实现,因为此功能会在运行时修改文本段数据段中的地址, * 如果此时运行在片上flash,则不能写flash,导致功能失效无法实现地址无关; */ { *(.__rel_dyn_start) } .rela.dyn : { *(.rela*) } .rel_dyn_end : { *(.__rel_dyn_end) } _end = .; . = ALIGN(8); .bss_start : { -------------------------------------------------------- (13) /* *(13)众所周知的bbs段; */ KEEP(*(.__bss_start)); } .bss : { *(.bss*) . = ALIGN(8); } .bss_end : { KEEP(*(.__bss_end)); } /DISCARD/ : { *(.dynsym) } -------------------------------------------- (14) /* *(14)一些在链接时无用需要丢弃的段; */ /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) } #ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER ----------------------------------- (15) /* *(15)在efi加载时会很有用,主要在u-boot的二进制头部添加了一些头部信息, * 包括大小端,数据段文本段大小等,以便于efi相关的加载器读取信息, * 此头部信息来自于Linux arm64的Image的头部信息;该头部也不属于u-boot的 * 一部分只是被附加上去的; */ #include "linux-kernel-image-header-vars.h" #endif }
ECTIONS表示正式开始地址划分
.的意思是当前地址,这句将当前地址(代码段起始地址)设为0x00000000,但是其实这个地址会被config.mk用-Ttext $(TEXT_BASE)指定的虚拟地址0x80800000(/board/hi3521a/config.mk中定义)覆盖掉
3.第一阶段 armv8启动流程:
uboot第一阶段从start.s开始 是汇编实现,通过uboot.lds文件可以找到 入口的entry
代码流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 _start arch/arm/cpu/armv8/start.S reset: save_boot_params_ret lowlevel_init (非pdl sdl spl构建时执行低级初始化 GIC(中断控制器)初始化 多核 CPU 启动同步 特定 SoC 的早期硬件初始化) bl _main arch/arm/lib/crt0_64.S /*先获取uboot的初始栈 */ board_init_f_alloc_reserve common/init/board_init.c //为gd和early malloc设置地址 board_init_f_init_reserve //初始化gd 和设置early malloc的堆管理器基地址 board_init_f common/board_f.c common/board_f.c initcall_run_list(init_sequence_f) 遍历init_sequence_f中的初始化回调函数 并执行 setup_mon_len 设置 gd 的 mon_len 成员变量,整个代码的长度 fdtdec_setup 获取设备树地址 initf_malloc 设置gd 的malloc_limit,malloc内存池大小 CONFIG_SYS_MALLOC_F_LEN=0x2000 arch_cpu_init 硬件 CPU初始化 mach_cpu_init 特定SoC的初始化 initf_dm,arch_cpu_init_dm:DM驱动模型的一些初始化 get_clocks 获取一些时钟值 timer_init 初始化定时器,为 uboot 提供时间 env_init 设置 gd 的成员变量 env_addr,也就是环境变量的保存地址 init_baud_rate 初始化波特率 serial_init:初始化串口 console_init_f 控制台初始化阶段1 display_text_info 打印调试信息 --执行init_sequence力度函数 初始化cpu DM驱动模型,内核定时器 波特率 串口 可以在serial_init里加一句初始化 /*relocate_code之前将调整gd的位置,保存 relocation_return的地址 记录重定位后的汇编代码地址用于跳转*/ relocate_code \\用于拷贝代码到sram, arch/arm/lib/relocate.S c_runtime_cpu_setup 非spl 这个函数也很简单,就是如果使能了ICache,则将缓存中的所有有效数据标记为无效,这意味着下次访问这些数据时,系统将不会从缓存中读取。 spl_relocate_stack_gd uboot已经在relocate_code中被拷贝到ram中了,还需要重定位spl 的gd栈。这个函数功能就是计算栈的位置,使用memcpy将gd结构拷贝给一个新的gd结构,并返回新的gd栈指针 /*clear bss section 清除bss端*/ ldr x0, =__bss_start /* this is auto-relocated! */ ldr x1, =__bss_end /* this is auto-relocated! */ clear_loop: str xzr, [x0], #8 cmp x0, x1 b.lo clear_loop board_init_r 进入到第二阶段的启动
重要知识点 global_data gd是全局数据结构 include/asm-generic/global_data.h 具体字段因架构而异 用于存储关键信息 bd是gd中的板级数据
1 2 3 4 5 6 7 8 9 10 typedef struct global_data { bd_t *bd; unsigned long flags; unsigned int baudrate; unsigned long cpu_clk; unsigned long ram_size; unsigned long env_addr; unsigned long relocaddr; } gd_t ;
uboot重定位 谓的relocation,就是重定位,uboot运行后会将自身代码拷贝到sdram的另一个位置继续运行 ,这个在uboot启动流程分析中说过。
但基于以前的理解,一个完整可运行的bin文件,link时指定的链接地址,load时的加载地址,运行时的运行地址,这3个地址应该是一致的
relocation后运行地址不同于加载地址 特别是链接地址,ARM的寻址会不会出现问题?
新版uboot跟老版uboot不太一样的地方在于新版uboot不管uboot的load addr(entry pointer)在哪里,启动后会计算出一个靠近sdram顶端的地址,将自身代码拷贝到该地址,继续运行。
个人感觉uboot这样改进用意有二,一是为kernel腾出低端空间,防止kernel解压覆盖uboot,二是对于由静态存储器(spiflash nandflash)启动,这个relocation是必须的。
但是这样会有一个问题,relocation后uboot的运行地址跟其链接地址不一致,compiler会在link时确定了其中变量以及函数的绝对地址,链接地址 加载地址 运行地址应该一致,
详解:https://zhuanlan.zhihu.com/p/520060653
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK) ldr x0, =(CONFIG_TPL_STACK) #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr x0, =(CONFIG_SPL_STACK) #elif defined(CONFIG_INIT_SP_RELATIVE) #if CONFIG_POSITION_INDEPENDENT adrp x0, __bss_start add x0, x0, #:lo12:__bss_start #else adr x0, __bss_start #endif add x0, x0, #CONFIG_SYS_INIT_SP_BSS_OFFSET #else ldr x0, =(CONFIG_SYS_INIT_SP_ADDR) (1) #endif bic sp, x0, #0xf (2) mov x0, sp bl board_init_f_alloc_reserve (3) mov sp, x0 (4) mov x18, x0 (5) bl board_init_f_init_reserve (6)
1)以上部分根据不同的配置情况获取uboot的初始栈地址
(2)为了遵循ABI规范,栈地址需要16字节对齐,该指令将地址做对齐以后设置到栈指针寄存器中,以为系统设置运行栈
(3)该函数为gd和early malloc分配内存,其代码如下:
1 2 3 4 5 6 7 8 ulong board_init_f_alloc_reserve(ulong top) { #if CONFIG_VAL(SYS_MALLOC_F_LEN) top -= CONFIG_VAL(SYS_MALLOC_F_LEN); (a) #endif top = rounddown(top-sizeof(struct global_data), 16); (b) return top; }
a 为早期堆管理器预留内存
b 为gd预留内存
(4)将预留后的内存地址设置为新的栈地址,此时各部分的地址如下:
(5)将gd地址保存到x18寄存器中,其可用被于后续gd指针的获取
(6)该流程主要用于初始化gd,和设置early malloc的堆管理器基地址,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void board_init_f_init_reserve(ulong base) { struct global_data *gd_ptr; gd_ptr = (struct global_data *)base; memset(gd_ptr, '\0', sizeof(*gd)); (a) #if !defined(CONFIG_ARM) arch_setup_gd(gd_ptr); (b) #endif if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE)) board_init_f_init_stack_protection_addr(base); (c) base += roundup(sizeof(struct global_data), 16); #if CONFIG_VAL(SYS_MALLOC_F_LEN) gd->malloc_base = base; (d) #endif if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE)) board_init_f_init_stack_protection(); (e) }
a 获取gd指针,并清空gd结构体内存
b 该函数用于非arm架构的gd指针获取,armv8架构则通过前面设置的x18寄存器获取gd指针,其定义如下(arch/arm/include/asm/global_data.h
):
1 2 3 4 5 #ifdef CONFIG_ARM64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18") #else #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") #endif
c 该函数用于获取该栈溢出检测的地址
d 设置early malloc的基地址
e 初始化栈溢出检测的canary值,该值被设置为SYS_STACK_F_CHECK_BYTE
由于内核需要从内存的低地址开始运行,为了防止内核三件套(kernel、dtb和ramdisk)的加载地址与uboot运行地址重叠,因此uboot的重定位地址需要被设置到内存顶端附近。同时我们还需要为一些特定模块预留一些内存空间(比如页表空间、framebuffer等),下图就是uboot规划的重定位后内存布局:
代码解析:重定位前的主板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ldr x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */ 将gdd的栈启使地址 给x0 bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */ x016字节对齐-》栈指针 ldr x18, [x18, #GD_BD] /* x18 <- gd->bd */ 加载gd的bd指针给x18 sub x18, x18, #GD_SIZE /* new GD is below bd */ 将新的gd放在bd的地址之下 adr lr, relocation_return /*存储relocationg_return标签的地址*? ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */ 读取偏移量 add lr, lr, x9 /* new return address after relocation */ 根据重定位偏移调整lr的位置 ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr */ 将冲低昂为地址传给x0 b relocate_code 进入重定位 更具x0的地址 将镜像拷贝到重定位的位置 relocation_return: .........
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 ENTRY(relocate_code) stp x29, x30, [sp, #-32]! mov x29, sp str x0, [sp, #16] (1) adrp x1, __image_copy_start add x1, x1, :lo12:__image_copy_start subs x9, x0, x1 b.eq relocate_done (2) ldr x1, _TEXT_BASE subs x9, x0, x1 (3) adrp x1, __image_copy_start add x1, x1, :lo12:__image_copy_start adrp x2, __image_copy_end add x2, x2, :lo12:__image_copy_end (4) copy_loop: (5) ldp x10, x11, [x1], #16 stp x10, x11, [x0], #16 cmp x1, x2 b.lo copy_loop str x0, [sp, #24] (6) adrp x2, __rel_dyn_start (7) add x2, x2, :lo12:__rel_dyn_start adrp x3, __rel_dyn_end add x3, x3, :lo12:__rel_dyn_end fixloop: ldp x0, x1, [x2], #16 ldr x4, [x2], #8 and x1, x1, #0xffffffff cmp x1, #R_AARCH64_RELATIVE bne fixnext add x0, x0, x9 add x4, x4, x9 str x4, [x0] fixnext: cmp x2, x3 b.lo fixloop relocate_done: switch_el x1, 3f, 2f, 1f (8) bl hang 3: mrs x0, sctlr_el3 b 0f 2: mrs x0, sctlr_el2 b 0f 1: mrs x0, sctlr_el1 0: tbz w0, #2, 5f (9) tbz w0, #12, 4f (10) ic iallu isb sy 4: ldp x0, x1, [sp, #16] bl __asm_flush_dcache_range bl __asm_flush_l3_dcache 5: ldp x29, x30, [sp],#32 (11) Ret (12) ENDPROC(relocate_code)
(1)构造一个栈帧,该栈帧中包含lr寄存器x30,fp寄存器x29和函数入参x0,其中x0为重定位的起始目的地址,该流程之后栈帧的内容如下:
(2)计算镜像运行地址与目的地址的偏移,若它们相等,则显然无须执行重定位,可直接跳过该流程
(3)计算镜像链接地址与目的地址的偏移
(4)读取镜像运行地址的起始地址和结束地址
(5)从运行地址处将镜像拷贝到重定位目的地址处
(6)将重定位结束地址入栈,入栈后栈帧的内容如下:
(7)位置无关代码相关处理
(8)根据当前执行的异常等级,跳转到对应的位置以读取sctlr寄存器的内容
(9 - 10)由于重定位后pc将会跳转到新的位置执行,因此若使能了cache,显然重定位之前已加载到cache中的指令还是老的地址,此时若直接跳转则cache中的内容是错误的。因此必须要失效掉cache中已经加载的内容
(11)从栈帧中恢复x29和x30(lr)的内容。现在已经万事俱备,只欠东风了,我们只要通过ret命令跳转到新地址执行即可
(12)经过慢慢长途,让我们在新的位置愉快地继续奔跑吧
init_sequence_f[] 函数数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 static const init_fnc_t init_sequence_f[] = { setup_mon_len, fdtdec_setup, initf_malloc, log_init, initf_bootstage, setup_spl_handoff, arch_cpu_init, mach_cpu_init, initf_dm, arch_cpu_init_dm, get_clocks, board_postclk_init, env_init, init_baud_rate, serial_init, console_init_f, display_options, display_text_info, checkcpu, announce_dram_init, dram_init, testdram, setup_dest_addr, reserve_pram, reserve_round_4k, arch_reserve_mmu, reserve_video, reserve_trace, reserve_uboot, reserve_malloc, reserve_board, reserve_global_data, reserve_fdt, reserve_bootstage, reserve_bloblist, reserve_arch, reserve_stacks, dram_init_banksize, show_dram_config, setup_bdinfo, display_new_sp, reloc_fdt, reloc_bootstage, reloc_bloblist, setup_reloc, clear_bss, NULL , };
4.第二阶段 代码流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 board_init_r common/board_r.c board_init_r (common/board_r.c) initcall_run_list (include/initcall.h) init_sequence_r[](common/board_f.c) initr_trace, initr_reloc, #ifdef CONFIG_ARM initr_caches, initr_barrier, initr_malloc, initr_bootstage, initr_console_record ........... run_main_loop common/board_r.c boot_logo () main_loop common/main.c cli_init bootdelay_process () ; s = env_get ("bootdelay" ); 从gd中找bootdelay参数 bootdelay = s ? (int )simple_strtol (s, NULL , 10 ) : CONFIG_BOOTDELAY; run_preboot_environment_command (); debug ("### main_loop entered: bootdelay=%d\n\n" , bootdelay); s = env_get ("bootcmd" ); process_fdt_options cli_process_fdt cli_secure_boot_cmd (s); autoboot_command (s); debug ("### main_loop: bootcmd=\"%s\"\n" , s ? s : "<UNDEFINED>" ); __abortboot() \\中断检测用户按键 用于终止自动启动 printf ("Hit any key to stop autoboot: %2d " , bootdelay);\\倒计时 run_command_list () \\用户未终止则执行boot命令 调用 hush shell执行booti命令 加载内核 cli_simple_run_command_list () common/cli_simple.c cli_simple_run_command () cmd_process () common/command.c find_cmd (argv[0 ]) ; cmd_call (cmdtp, flag, argc, argv); cli_loop () \\用户按键中断后执行 用户循环 hush shell 输入指令 然后执行指令 cli_simple_loop (); cli_readline run_command_repeatable cli_simple_run_command ..... cmd_process () common/command.c
重要知识点 cmd_tbl_s cmd结构体 1 2 3 4 5 6 7 struct cmd_tbl_s { char *name; int maxargs; int repeatable; int (*cmd)(struct cmd_tbl_s *, int , int , char * const []); };
bootm命令的注册
1 U_BOOT_CMD(bootm, CONFIG_SYS_MAXARGS, 1 , do_bootm, "Boot an image" , bootm_help_text);
5.uboot DM驱动模型 1.数据结构 主要包含 uclass udevice udriver
uclass又包含 uclass driver 用于描述总线的驱动 包含uclass总线运行的方法,重点关注post_bind post_probe
uclass会维护一个设备链表,存储绑定的设备,绑定的设备中可以找到绑定的drv
UBOOT最重要的数据结构gd中,与dm相关的成员如下:
1 2 3 4 5 6 7 8 9 10 typedef struct global_data { // dts中的根节点,第一个创建的udevice struct udevice *dm_root; // relocation之前的根设备 struct udevice *dm_root_f; // uclass的链表, 挂的是有udevice的uclass struct list_head uclass_root; } gd_t;
2.模型解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define U_BOOT_DRIVER(__name) \ ll_entry_declare(struct driver, __name, driver) #define U_BOOT_DEVICE(__name) \ ll_entry_declare(struct driver_info, __name, driver_info) #define ll_entry_declare(_type, _name, _list) \ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" #_list"_2_" #_name))) #define UCLASS_DRIVER(__name) \ ll_entry_declare(struct uclass_driver, __name, uclass) #define ll_entry_declare(_type, _name, _list) \ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" #_list"_2_" #_name)))
1.dm驱动框架的初始化 代码流程 在root.c中 注册了root uclass driver 和 root_driver uboot driver
在第一阶段 的 init_borad_f中
1 2 3 4 borad_init_f() common/board_f.c initcall_run_list (const init_fnc_t init_sequence[]) init_sequence_f initf_dm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 initf_dm() common/board_f.c dm_init_and_scan dm_init (IS_ENABLED(CONFIG_OF_LIVE)) ; 创建udevice和uclass空链表,创建根设备(root device) INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST); 创建uclass空链表 第一个节点是 gd->uclass device_bind_by_name (NULL , false , &root_info, &DM_ROOT_NON_CONST) ; drivers/core/device.c 创建一个device并将其绑定到driver,这是一个帮助器函数,用于绑定不使用设备树的设备。 lists_driver_lookup_name(info->name); drivers/core/lists.c device_bind_common uclass_get() 根据id从uclass链表中招到uclass节点,没有的话就创建一个 这里创建了id为0 的 root class dev = calloc (1 , sizeof (struct udevice)); dev->platdata = platdata; dev->driver_data = driver_data; dev->name = name; dev->driver = drv; \\绑定uroot drv dev->uclass = uc; \\绑定root uclass ......填充dev成员 uclass_bind_device(dev) ret = drv->bind(dev); parent->driver->child_post_bind uc->uc_drv->post_bind(dev) device_probe(DM_ROOT_NON_CONST); uclass_resolve_seq(dev); \\为设备分配序列号 dev->seq = seq; dev->flags |= DM_FLAG_ACTIVATED; dm_scan_platdata(pre_reloc_only) lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);\\遍历driver_info数组 找到 dirverlist中对应的驱动,并创建设备,互相绑定 device_bind_by_name \\流程同上 dm_scan_fdt(gd->fdt_blob, pre_reloc_only); dm_scan_fdt_node(gd->dm_root, blob, 0 , pre_reloc_only); lists_bind_fdt(parent, offset_to_ofnode(offset), NULL ); compat_list = ofnode_get_property(node, "compatible" , &compat_length); \\找到所有带compatible的节点 driver_check_compatible(entry->of_match, &id, compat); \\对每个节点 做一次 driver list 的遍历并对比名字 ret = device_bind_with_driver_data(parent, entry, name, id->data, node, &dev); device_bind_common(parent, drv, name, NULL , driver_data, node, 0 , devp);
几个重要接口的总结: dm_init():创建udevice和uclass空链表,创建根设备(root device)
dm_scan_platdata():调用函数lists_bind_drivers,扫描U_BOOT_DEVICE定义的设备,与U_BOOT_DRIVER定义的driver进行查找,创建udevice,并绑定相应driver。
dm_scan_fdt():扫描由FDT设备树文件定义的设备,与U_BOOT_DRIVER定义的driver进行查找,创建udevice,并绑定相应driver。
uclass的创建:
lists_driver_lookup_name函数: #define U_BOOT_DRIVER(__name) \
ll_entry_declare(struct driver, __name, driver)
这里通过name寻找driver 是通过 uboot.map文件
u_boot_list_2_driver_1 是驱动开始标记
.u_boot_list_2_driver_3 是驱动结束标记
.u_boot_list_2_driver_2_xx 就是加入的设备驱动。
u_boot_list_2_driver_1和.u_boot_list_2_driver_3 用于定位作用 用于以下两个接口
1 2 3 4 ll_entry_start(struct uclass_driver, uclass); const int n_ents = ll_entry_count(struct uclass_driver, uclass);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 .u_boot_list_2_driver_1 0x00000000002d25c0 0x0 drivers/built-in.o .u_boot_list_2_driver_2_analogix_dp 0x00000000002d25c0 0x78 drivers/built-in.o 0x00000000002d25c0 _u_boot_list_2_driver_2_analogix_dp .u_boot_list_2_driver_2_arasan_sdhci_drv 0x00000000002d2638 0x78 drivers/built-in.o 0x00000000002d2638 _u_boot_list_2_driver_2_arasan_sdhci_drv .u_boot_list_2_driver_2_clk_fixed_rate 0x00000000002d26b0 0x78 drivers/built-in.o 0x00000000002d26b0 _u_boot_list_2_driver_2_clk_fixed_rate .u_boot_list_2_driver_2_clk_rk3399 0x00000000002d2728 0x78 drivers/built-in.o 0x00000000002d2728 _u_boot_list_2_driver_2_clk_rk3399 .u_boot_list_2_driver_2_dmc_rk3399 0x00000000002d27a0 0x78 drivers/built-in.o 0x00000000002d27a0 _u_boot_list_2_driver_2_dmc_rk3399 .u_boot_list_2_driver_2_dw_mipi_dsi 0x00000000002d2818 0x78 drivers/built-in.o 0x00000000002d2818 _u_boot_list_2_driver_2_dw_mipi_dsi .u_boot_list_2_driver_2_ehci_generic 0x00000000002d2890 0x78 drivers/usb/host/built-in.o 0x00000000002d2890 _u_boot_list_2_driver_2_ehci_generic .u_boot_list_2_driver_2_eth_designware 0x00000000002d2908 0x78 drivers/net/built-in.o 0x00000000002d2908 _u_boot_list_2_driver_2_eth_designware .u_boot_list_2_driver_2_eth_gmac_rockchip 0x00000000002d2980 0x78 drivers/net/built-in.o 0x00000000002d2980 _u_boot_list_2_driver_2_eth_gmac_rockchip .u_boot_list_2_driver_2_fixed_regulator 0x00000000002d29f8 0x78 drivers/power/regulator/built-in.o 0x00000000002d29f8 _u_boot_list_2_driver_2_fixed_regulator .u_boot_list_2_driver_2_generic_syscon 0x00000000002d2a70 0x78 drivers/built-in.o 0x00000000002d2a70 _u_boot_list_2_driver_2_generic_syscon .u_boot_list_2_driver_2_gpio_rockchip 0x00000000002d2ae8 0x78 drivers/gpio/built-in.o 0x00000000002d2ae8 _u_boot_list_2_driver_2_gpio_rockchip .u_boot_list_2_driver_2_i2c_generic_chip_drv 0x00000000002d2b60 0x78 drivers/i2c/built-in.o 0x00000000002d2b60 _u_boot_list_2_driver_2_i2c_generic_chip_drv .u_boot_list_2_driver_2_i2c_rockchip 0x00000000002d2bd8 0x78 drivers/i2c/built-in.o 0x00000000002d2bd8 _u_boot_list_2_driver_2_i2c_rockchip .u_boot_list_2_driver_2_mmc 0x00000000002d2c50 0x78 drivers/built-in.o 0x00000000002d2c50 _u_boot_list_2_driver_2_mmc .u_boot_list_2_driver_2_mmc_blk 0x00000000002d2cc8 0x78 drivers/built-in.o 0x00000000002d2cc8 _u_boot_list_2_driver_2_mmc_blk .u_boot_list_2_driver_2_ns16550_serial 0x00000000002d2d40 0x78 drivers/serial/built-in.o 0x00000000002d2d40 _u_boot_list_2_driver_2_ns16550_serial .u_boot_list_2_driver_2_ohci_generic 0x00000000002d2db8 0x78 drivers/usb/host/built-in.o 0x00000000002d2db8 _u_boot_list_2_driver_2_ohci_generic .u_boot_list_2_driver_2_pinconfig_generic 0x00000000002d2e30 0x78 drivers/built-in.o 0x00000000002d2e30 _u_boot_list_2_driver_2_pinconfig_generic .u_boot_list_2_driver_2_pinctrl_rockchip 0x00000000002d2ea8 0x78 drivers/built-in.o 0x00000000002d2ea8 _u_boot_list_2_driver_2_pinctrl_rockchip .u_boot_list_2_driver_2_pmic_rk8xx 0x00000000002d2f20 0x78 drivers/power/pmic/built-in.o 0x00000000002d2f20 _u_boot_list_2_driver_2_pmic_rk8xx .u_boot_list_2_driver_2_psci 0x00000000002d2f98 0x78 drivers/built-in.o 0x00000000002d2f98 _u_boot_list_2_driver_2_psci .u_boot_list_2_driver_2_psci_sysreset 0x00000000002d3010 0x78 drivers/built-in.o 0x00000000002d3010 _u_boot_list_2_driver_2_psci_sysreset .u_boot_list_2_driver_2_pwm_backlight 0x00000000002d3088 0x78 drivers/built-in.o 0x00000000002d3088 _u_boot_list_2_driver_2_pwm_backlight .u_boot_list_2_driver_2_pwm_regulator 0x00000000002d3100 0x78 drivers/power/regulator/built-in.o 0x00000000002d3100 _u_boot_list_2_driver_2_pwm_regulator .u_boot_list_2_driver_2_rk8xx_buck 0x00000000002d3178 0x78 drivers/power/regulator/built-in.o 0x00000000002d3178 _u_boot_list_2_driver_2_rk8xx_buck .u_boot_list_2_driver_2_rk8xx_ldo 0x00000000002d31f0 0x78 drivers/power/regulator/built-in.o 0x00000000002d31f0 _u_boot_list_2_driver_2_rk8xx_ldo .u_boot_list_2_driver_2_rk8xx_switch 0x00000000002d3268 0x78 drivers/power/regulator/built-in.o 0x00000000002d3268 _u_boot_list_2_driver_2_rk8xx_switch .u_boot_list_2_driver_2_rk_pwm 0x00000000002d32e0 0x78 drivers/built-in.o 0x00000000002d32e0 _u_boot_list_2_driver_2_rk_pwm .u_boot_list_2_driver_2_rockchip_crypto_v1 0x00000000002d3358 0x78 drivers/built-in.o 0x00000000002d3358 _u_boot_list_2_driver_2_rockchip_crypto_v1 .u_boot_list_2_driver_2_rockchip_display 0x00000000002d33d0 0x78 drivers/built-in.o 0x00000000002d33d0 _u_boot_list_2_driver_2_rockchip_display .u_boot_list_2_driver_2_rockchip_dw_hdmi 0x00000000002d3448 0x78 drivers/built-in.o 0x00000000002d3448 _u_boot_list_2_driver_2_rockchip_dw_hdmi .u_boot_list_2_driver_2_rockchip_dwmmc_drv 0x00000000002d34c0 0x78 drivers/built-in.o 0x00000000002d34c0 _u_boot_list_2_driver_2_rockchip_dwmmc_drv .u_boot_list_2_driver_2_rockchip_efuse 0x00000000002d3538 0x78 drivers/built-in.o 0x00000000002d3538 _u_boot_list_2_driver_2_rockchip_efuse .u_boot_list_2_driver_2_rockchip_lvds 0x00000000002d35b0 0x78 drivers/built-in.o 0x00000000002d35b0 _u_boot_list_2_driver_2_rockchip_lvds .u_boot_list_2_driver_2_rockchip_panel 0x00000000002d3628 0x78 drivers/built-in.o 0x00000000002d3628 _u_boot_list_2_driver_2_rockchip_panel .u_boot_list_2_driver_2_rockchip_reset 0x00000000002d36a0 0x78 drivers/built-in.o 0x00000000002d36a0 _u_boot_list_2_driver_2_rockchip_reset .u_boot_list_2_driver_2_rockchip_rgb 0x00000000002d3718 0x78 drivers/built-in.o 0x00000000002d3718 _u_boot_list_2_driver_2_rockchip_rgb .u_boot_list_2_driver_2_rockchip_rk3399_pmuclk 0x00000000002d3790 0x78 drivers/built-in.o 0x00000000002d3790 _u_boot_list_2_driver_2_rockchip_rk3399_pmuclk .u_boot_list_2_driver_2_rockchip_saradc 0x00000000002d3808 0x78 drivers/built-in.o 0x00000000002d3808 _u_boot_list_2_driver_2_rockchip_saradc .u_boot_list_2_driver_2_rockchip_usb2phy 0x00000000002d3880 0x78 drivers/built-in.o 0x00000000002d3880 _u_boot_list_2_driver_2_rockchip_usb2phy .u_boot_list_2_driver_2_rockchip_usb2phy_port 0x00000000002d38f8 0x78 drivers/built-in.o 0x00000000002d38f8 _u_boot_list_2_driver_2_rockchip_usb2phy_port .u_boot_list_2_driver_2_rockchip_vop 0x00000000002d3970 0x78 drivers/built-in.o 0x00000000002d3970 _u_boot_list_2_driver_2_rockchip_vop .u_boot_list_2_driver_2_root_driver 0x00000000002d39e8 0x78 drivers/built-in.o 0x00000000002d39e8 _u_boot_list_2_driver_2_root_driver .u_boot_list_2_driver_2_simple_bus_drv 0x00000000002d3a60 0x78 drivers/built-in.o 0x00000000002d3a60 _u_boot_list_2_driver_2_simple_bus_drv .u_boot_list_2_driver_2_simple_panel 0x00000000002d3ad8 0x78 drivers/built-in.o 0x00000000002d3ad8 _u_boot_list_2_driver_2_simple_panel .u_boot_list_2_driver_2_spi_generic_drv 0x00000000002d3b50 0x78 drivers/spi/built-in.o 0x00000000002d3b50 _u_boot_list_2_driver_2_spi_generic_drv .u_boot_list_2_driver_2_syscon_rk3399 0x00000000002d3bc8 0x78 arch/arm/mach-rockchip/built-in.o 0x00000000002d3bc8 _u_boot_list_2_driver_2_syscon_rk3399 .u_boot_list_2_driver_2_sysreset_rockchip 0x00000000002d3c40 0x78 drivers/built-in.o 0x00000000002d3c40 _u_boot_list_2_driver_2_sysreset_rockchip .u_boot_list_2_driver_2_sysreset_syscon_reboot 0x00000000002d3cb8 0x78 drivers/built-in.o 0x00000000002d3cb8 _u_boot_list_2_driver_2_sysreset_syscon_reboot .u_boot_list_2_driver_2_usb_dev_generic_drv 0x00000000002d3d30 0x78 drivers/usb/host/built-in.o 0x00000000002d3d30 _u_boot_list_2_driver_2_usb_dev_generic_drv .u_boot_list_2_driver_2_usb_generic_hub 0x00000000002d3da8 0x78 common/built-in.o 0x00000000002d3da8 _u_boot_list_2_driver_2_usb_generic_hub .u_boot_list_2_driver_2_usb_mass_storage 0x00000000002d3e20 0x78 common/built-in.o 0x00000000002d3e20 _u_boot_list_2_driver_2_usb_mass_storage .u_boot_list_2_driver_2_usb_storage_blk 0x00000000002d3e98 0x78 common/built-in.o 0x00000000002d3e98 _u_boot_list_2_driver_2_usb_storage_blk .u_boot_list_2_driver_2_vidconsole_normal 0x00000000002d3f10 0x78 drivers/built-in.o 0x00000000002d3f10 _u_boot_list_2_driver_2_vidconsole_normal .u_boot_list_2_driver_2_xhci_dwc3 0x00000000002d3f88 0x78 drivers/usb/host/built-in.o 0x00000000002d3f88 _u_boot_list_2_driver_2_xhci_dwc3 .u_boot_list_2_driver_3 0x00000000002d4000 0x0 drivers/built-in.o
2.initr_dm与initf_dm的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static int initf_dm (void ) { #if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN) int ret; bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f" ); ret = dm_init_and_scan(true ); bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F); if (ret) return ret; #endif #ifdef CONFIG_TIMER_EARLY ret = dm_timer_init(); if (ret) return ret; #endif return 0 ; } static int initr_dm (void ) { int ret; gd->dm_root_f = gd->dm_root; gd->dm_root = NULL ; #ifdef CONFIG_TIMER gd->timer = NULL ; #endif bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r" ); ret = dm_init_and_scan(false ); bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R); if (ret) return ret; return 0 ; }
两个均调用了dm_init_and_scan这个接口,这两个的关键区别在于参数的不同。
首先说明一下dts节点中的“u-boot,dm-pre-reloc”属性,当设置了这个属性时,则表示这个设备在relocate之前就需要使用。
当dm_init_and_scan的参数为true时,只会对带有“u-boot,dm-pre-reloc”属性的节点进行解析。而当参数为false的时候,则会对所有节点都进行解析。
3.prob函数的执行 实际prob的执行都在 init_r里的
1 2 3 4 5 6 7 8 9 board_init_r common/board_r.c initcall_run_list (init_sequence_r) init_sequence_r[] initr_dm initr_nand initr_mmc initr_ethaddr initr_net .......
MMC驱动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 initr_mmc common/board_r.c mmc_initialize (gd->bd) ; drivers/mmc/mmc.c ret = mmc_probe(bis); ret = uclass_get(UCLASS_MMC, &uc); drivers/core/uclass.c uclass_get_device_by_seq (UCLASS_MMC, i, &dev) ; uclass_find_device_by_seq \\先尝试按顺序找已探测的对比dev->seq和seq,然后找未探测的对比dev->req_seq请求序列与seq相同 uclass_get_device_tail ret = device_probe(dev); drivers/core/device.c uclass_foreach_dev (dev, uc) include/dm/uclass.h device_probe (dev) ; drv->probe(dev); static const struct udevice_id mc_sdhci_ids [] = { { .compatible = "mc,dw-sdhci" }, { } }; U_BOOT_DRIVER(mc_sdhci_drv) = { .name = "mc_sdhci" , .id = UCLASS_MMC, .of_match = mc_sdhci_ids, .ops = &sdhci_ops, .bind = mc_sdhci_bind, .probe = mc_sdhci_probe, .priv_auto_alloc_size = sizeof (struct sdhci_host), .platdata_auto_alloc_size = sizeof (struct mc_sdhci_plat), }; mc_sdhci_probe sdhci_probe (dev) ; sdhci_init
1 2 3 4 5 6 7 8 9 10 device_probe drv = dev->driver; uclass_resolve_seq(dev); \\分配seq序列号 dev->seq = seq; dev->flags |= DM_FLAG_ACTIVATED; \\flag设置为激活状态 uclass_pre_probe_device(dev); 执行 uclass->drv->pre_prob drv->probe(dev); uclass_post_probe_device(dev); 执行 uclass->drv->post_prob
网络驱动