当前位置: 首页 > news >正文

10_Uboot启动流程_2

目录

_main函数详解

board_init_f函数详解

relocate_code函数详解

relocate_vectors函数详解

board_init_r 函数详解


_main函数详解

在上一章得知会执行_main函数_main函数定义在文件arch/arm/lib/crt0.S 中,函数内容如下:

第76行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也就是sp指向0x0091FF00

第83行,sp做8字节对齐。

第85行,读取sp到寄存器r0里面,此时r0-0x0091FF00

第86行,调用函数board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0X0091FF00,此函数定义在文件common/init/board initc中,内容如下:

 函数board_init_f alloc-reserve主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN-0X400(在文件include/generated/autoconf.h中定义),sizeof(struct global_data)=248(GD_SIZE 值),完成以后的内存分布如图所示:

函数board_init_f_alloc_reserve是有返回值的,返回值为新的top值,从图可知,此时top=0X0091FA00。

继续回到示例代码32.2.4.1中,第87行,将r0写入到sp里面, r0保存着函数board_init_f_alloc _reserve的返回值,所以这一句也就是设置sp=0X0091FA00

第89行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件arch/arm/include/asm/global_data.h中有如图所示宏定义:

 从图可以看出, uboot中定义了一个指向gd_t的指针gd, gd存放在寄存器 r9 里面的,因此gd是个全局变量。gd_t是个结构体,在include/asm-generic/global_data.h里面有定义,gd定义如下:

因此这一行代码就是设置gd所指向的位置,也就是gd指向0X0091FA00。

继续回到示例代码32.2.4.1中,第90行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:

 可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0X0091FA00+248=0X0091FAF8,在做16字节对齐,最终gd->malloc_base-0X0091FB00,这个也就是early malloc的起始地址。

继续回到示例代码32.2.4.1中,第92行设置R0为0。

第93行,调用 board_init_f函数,此函数定义在文件common/board_f.c中!主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面在详细的分析。

第103行,重新设置环境(sp和 gd)、获取 gd->start_addr_sp的值赋给sp,在函数 board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp-0X9EF44E90,所以这里相当于设置sp-gd->start_addr_sp-0X9EF44E90。0X9EF44E90是DDR中的地址,说明新的sp和gd将会存放到 DDR 中,而不是内部的RAM了。GD_START_ADDR_SP=64。

第109行,sp做8字节对齐。

第111行,获取gd->bd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->bd的地址来计算出新的gd的位置

第112行,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd的位置以后赋值给r9。

第114行,设置Ir寄存器为here,这样后面执行其他函数返回的时候就返回到了第122行的here位置处。

第115,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=68

第116行,Ir寄存器的值加上r0寄存器的值,重新赋值给Ir寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0X87800000,下面要将 uboot拷贝到DDR最后面的地址空间出,将0X87800000开始的内存空出来),其中就包括here,因此Ir中的here要使用重定位后的位置。

第120行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0X9FF47000。GD_RELOCADDR=48。

第121行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。

继续回到示例代码32.2.4.1第131行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/army7/start.S中,函数内容如下:

继续回到示例代码32.2.4.1第141~159行,清除BSS段。

第167行,设置函数board_init_r的两个参数,函数board_init_r声明如下:

board init r(gd t *id, ulong dest addr)

第一个参数是gd,因此读取r9保存到r0里面。

第168行,设置函数board_init_r的第二个参数是目的地址,因此rl=gd->relocaddr。

第174行,调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。

这个就是_main函数的运行流程,在_main函数里面调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次看一下这4个函数都是干啥的。

board_init_f函数详解

_main中会调用board_init_f函数,board_init_f函数主要有两个工作:

1.初始化一系列外设,比如串口、定时器,或者打印一些消息等。

2.初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linux kernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置, malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。

此函数定义在文件common/board_f.c中定义,代码如下:

因为没有定义CONFIG_SYS_GENERIC_GLOBAL_DATA,所以第1037~1054行代码无效。

第1056行,初始化gd->flags=boot_flags=0

第1057行,设置gd->have console=0。

重点在第1059行!通过函数 initcall_run_list来运行初始化序列init_sequence_f里面的一些列函数, init_sequence_f.c里面包含了一系列的初始化函数, init_sequence_f也是定义在文件

common/board f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下:

接下来分析以上函数执行完以后的结果:

第2行, setup_mon_len函数设置gd的mon_len成员变量,此处为-bss_end-start,也就是整个代码的长度。0X878A8E74-0x87800000-0XA8E74,这个就是代码长度

第3行, initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit=CONFIG_SYS_MALLOC_F_LEN-0X400. malloc_limit表示malloc内存池大小。

第4行,initf_console_record,如果定义了宏CONFIG_CONSOLE-RECORD和宏CONFIG_SYS_MALLOC_F_LEN的话此函数就会调用函数console_record_init,但是IMX6ULL的uboot没有定义宏CONFIG CONSOLE RECORD,所以此函数直接返回0

第5行, arch_cpu_init函数。

第6行, initf_dm函数,驱动模型的一些初始化。

第7行, arch_cpu_init_dm函数未实现。

第8行, mark_bootstage函数应该是和啥标记有关的

第9行, board_early_init_f函数,板子相关的早期的一些初始化设置, I.MX6ULL用来初始化串口的IO配置

第10行,timer_init,初始化定时器,Cortex-A7内核有一个定时器,这里初始化的就是CortexA 内核的那个定时器。通过这个定时器来为uboot提供时间。就跟Cortex-M内核Systick定时器一样。

第11行,board_postelk_init,对于I.MX6ULL来说是设置VDDSOC电压。

第12行, get_clocks函数用于获取一些时钟值, I.MX6ULL获取的是sdhc_clk时钟,也就是SD 卡外设的时钟。

第13行, env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。

14行,init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化 gd->baudrate。

第16行, console_init_f,设置gd->have_console为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。

第17行display_options,通过串口输出一些信息,如图所示:

第18行, display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下:

 debug("U-Boot code: %08IX->%08IX BSS:->%081Xn",text base, bss start, bss end);

 第19行, print_cpuinfo函数用于打印CPU信息,结果如图所示:

第20行, show_board_info函数用于打印板子信息,会调用checkboard函数,结果如图所示: 

 第21行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于I.MX6ULL来说是空函数

第22行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于I.MX6ULL来说是空函数

第23行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如图所示信息:

第24行, announce_dram_init,此函数很简单,就是输出字符串"DRAM:"

第26行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值,对于正点原子 I.MX6ULL开发板EMMC版本核心板来说就是512MB。

第27行,post_init_f,此函数用来完成一些测试,初始化gd->post_init_f_time

第29行, testdram,测试DRAM,空函数。

第44行,setup_dest_addr函数,设置目的地址,设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,我可以修改 uboot代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c,因为setup_dest_addr函数定义在文件common/board_f.c 中,在setup_dest_addr函数输入如图所示内容:

 设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开SecureCRT, uboot会输出如图所示信息:

第45行,reserve_round_4k函数用于对gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是4K对齐了,所以调整后不变

第46行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对 gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和 gd->relocaddr如图所示:

第47行,reserve_trace函数,留出跟踪调试的内存,I.MX6ULL没有用到!

第48行,reserve_uboot,留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start addr sp,结果如图所示:

 第49行, reserve_malloc,留出malloc区域,"调整gd->start_addr_sp位置, malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下:

mx6ull_alientek_emmc.h文件中定义宏CONFIG_SYS_MALLOC_LEN为16MB-0X1000000,宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start addr sp如图所示: 

 第50行, reserve_board函数,留出板子bd所占的内存区, bd是结构体bd_t, bd_t大小为80 字节,结果如图所示:

第51行, setup_machine,设置机器ID, linux启动的时候会和这个机器ID匹配,如果匹·配的话linux就会启动正常。但是!! 1.MX6ULL不用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数无效。

第52行, reserve_global_data函数,保留出gd_t的内存区域, gd_t结构体大小为248B,结果如图所示:

 

第53行, reserve_fdt,留出设备树相关的内存区域, I.MX6ULL的uboot没有用到,因此此函数无效。

第54行,reserve_arch是个空函数。

第55行,reserve_stacks,留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对其。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如图所示:

在本uboot中并没有使用到IRQ,所以不会留出IRQ相应的内存区域,此时:

gd->start_addr_sp=0X9EF44E90

第56行, setup_dram_config函数设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。结果如图所示:

 第57行,show_dram config函数,用于显示DRAM的配置,如图所示:

 第58行, display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如图所示:

第60行, reloc fdt函数用于重定位fdt,没有用到。

第61行, setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如图所示:

 从图可以看出, uboot重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。

至此, board_init_f函数就执行完成了,最终的内存分配如图所示:

 

relocate_code函数详解

Relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:

第80行, r1=-image-copy_start,也就是r1寄存器保存源地址,可知,__image_copy start=0X87800000。

第81行,r0-0X9FF47000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0X9FF470000X87800000=0X18747000,因此r4保存偏移量。

第82行,如果在第81中,r0-r1等于0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行relocate_done函数

第83行, r2=_image_copy_end, r2中保存拷贝之前的代码结束地址,可知,__image copy end=0x8785dd54

第84行,函数copy_loop完成代码拷贝工作!从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次就只拷贝这2个32位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。

第87行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。

第88行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。

接下来的第94行~109行是重定位.rel.dyn段, .rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是uboot将自身拷贝到DRAM的另一个地放去继续运行(DRAM的高地址处)。我们知道,一个可执行的bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?为了分析这个问题,我们需要在mx6ull_alientek_emmc.c中输入如下所示内容:

 最后还需要在mx6ullevk.c文件中的board_init函数里面调用rel_test函数,否则rel_reset不会被编译进uboot。修改完成后的mx6ullevk.c如图所示: 

 

Board_init函数会调用rel_test, rel_test会调用全局变量rel_a,使用如下命令编译uboot:

./mx6ull_alientek_emmc.sh

编译完成以后,使用arm-linux-gnueabihf-objdump将u-boot进行反汇编,得到u-boot.dis 这个汇编文件,命令如下:

arm-linux-gnueabihf-objdump-D-m arm u-boot > u-boot.dis

在u-boot.dis文件中找到rel_a、rel_rest和board_init,相关内容如下所示:

第12行是borad init调用rel_test函数,用到了b指令,而 b指令是位置无关指令, bl指令是相对寻址的(pc+offset),因此uboot中函数调用是与绝对位置无关的。

再来看一下函数 rel_test 对于全局变量 rel_a 的调用,第2行设置r3的值为pc+12地址处的值,因为ARM流水线的原因,pc寄存器的值为当前地址+8,因此pc=0X87804184+8=0X8780418C,r3=0X8780418C+12=0X87804198,第7行就是0X87804198这个地址,0X87804198处的值为0X8785DA50。根据第17行可知,0X8785DA50 正是变量rel_a的地址,最终r3=0X8785DA50。

第3行, r2=100。

第5行,将r2内的值写到r3地址处,也就是设置地址0X8785DA50的值为100,这不就是示例代码代码中的第5行: rel_a=100

总结一下rel_a=100的汇编执行过程:

1.在函数rel_test末尾处有一个地址为0X87804198的内存空间,此内存空间保存着变量rel_a的地址。

2.函数rel_test 要想访问变量rel_a,首先访问末尾的0X87804198来获取变量rel_a的地址,而访问0X87804198是通过偏移来访问的,很明显是个位置无关的操作。

3.通过0X87804198获取到变量rel_a的地址,对变量rel_a进行操作。

4.可以看出,函数rel_test对变量rel_a的访问没有直接进行,而是使用了一个第三方偏移地址0X87804198,专业术语叫做Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!

uboot重定位后偏移为0X18747000,那么重定位后函数rel_test的首地址就是0X87804184+0X18747000=0X9FF4B184。保存变量rel_a地址的Label就是0X9FF4B184+8+12=0X9FF4B198(既: 0X87804198+0X18747000),变量rel_a的地址就为0X8785DA50+0X18747000-0X9FFA4A50。重定位后函数rel-test要想正常访问变量rel_a就得设置0X9FF4B198(重定位后的Label)地址出的值为0X9FFA4A50(重定位后的变量rel_a地址)。这样就解决了重定位后链接地址和运行地址不一致的问题。

可以看出,uboot对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用Id 进行链接的时候使用选项“-pie”生成位置无关的可执行文件。在文件arch/arm/config mk下有如下代码:

 第83行就是设置uboot链接选项,加入了"-pie”选项,编译链接uboot的时候就会使用到"-pie",如图所示:

 使用“-pie”选项以后会生成一个.rel.dyn 段, uboot就是靠这个.rel.dyn来解决重定位问题的,在u-bot.dis的rel.dyn段中有如下所示内容:

先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个4字节数据为一组。高4字节是Label地址标识0x17,低4字节就是Label的地址,首先判断Label地址标识是否正确,也就是判断高4字节是否为0X17,如果是的话低4字节就是Label地址值。

第7行值为0X87804198,第8行为0X00000017,说明第7行的0X87804198是个Label,这个正是示例代码中存放变量rel_a地址的那个Label。根据前面的分析,只要将地址0X87804198+offset处的值改为重定位后的变量rel_a地址即可。我们猜测的是否正确,看一下uboot对.rel.dyn段的重定位即可.rel.dyn段的重定位代码如下:

第94行, r2=__rel_dyn_start,也就是.rel.dyn段的起始地址。

第95行,r3=__rel_dyn_end,也就是.rel.dyn段的终止地址。

第97行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中,r0 存放低4字节的数据,也就是Label地址;r1存放高4字节的数据,也就是Label标志。

第98行,r1中给的值与0xff进行与运算,其实就是取r1的低8位。

第99行,判断r1中的值是否等于23(0X17)。

第100行,如果r1 不等于23的话就说明不是描述Label的,执行函数fixnext,否则的话继续执行下面的代码。

第103行,r0保存着Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label 值。此时r0保存着重定位后的Label值,相当于0X87804198+0X18747000=0X9FF4B198。

第104行,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(相当于 rel_a重定位前的地址0X8785DA50),将得到的值放到r1寄存器中。

第105行, r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后的0X8785DA50+0X18747000=0X9FFA4A50。

第106行,重定位后的变量地址写入到重定位后的Label中,相等于设置地址0X9FF4B198处的值为0X9FFA4A50。

第108行,比较r2和r3,查看.rel.dyn段重定位是否完成。

第109行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn 段。可以看出,uboot中对.rel.dyn段的重定位方法和我们猜想的一致。.rel.dyn 段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题。

relocate_vectors函数详解

函数relocate_vectors用于重定位向量表,此函数定义在文件relocate.S中,函数源码如下:

第29行,如果定义了CONFIG_CPU_V7M的话就执行第30-36行的代码,这是Cortex-M内核单片机执行的语句,因此对于I.MX6ULL来说是无效的。

第38行,如果定义了CONFIG_HAS_VBAR的话就执行此语句,这个是向量表偏移,Cortex.A7是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。

第43行,r0-gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的。

第44行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。

 

board_init_r 函数详解

board_init_f函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r来完成的, board_init_r函数定义在文件common/board_r.c中,代码如下:

第1010行调用initcall_run_list函数来执行初始化序列init_sequence_r, init_sequence_r是一个函数集合, init_sequence_r也定义在文件 common/board_r.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_r定义如下: 

第2行, initr_trace函数,如果定义了宏CONFIG_TRACE的话就会调用函数trace_init,初始化和调试跟踪有关的内容。

第3行,initr_reloc函数用于设置gd->flags,标记重定位完成。

第4行, initr_caches函数用于初始化cache,使能cache。

第5行, initr_reloc_global_data函数,初始化重定位后gd的一些成员变量。

第6行,initr_barrier函数,I.MX6ULL未用到。

第7行,initr_malloc函数,初始化malloc。

第8行, initr_console_record函数,初始化控制台相关的内容, I.MX6ULL未用到,空函数。

第9行, bootstage_relocate函数,启动状态重定位。

第10行,initr_bootstage函数,初始化bootstage什么的。

第11行,board_init 函数,板级初始化,包括74XX芯片,I2C、FEC、USB和QSPI等。这里执行的是mx6ull_alientek_emmc.c文件中的board_init函数。

第12行,stdio_init_tables函数,stdio相关初始化。

第13行,initr_serial函数,初始化串口。

第14行, initr_announce函数,与调试有关,通知已经在RAM中运行。

第18行, power_init_board函数,初始化电源芯片,正点原子的I.MX6ULL开发板没有用到。

第19行, initr_flash函数,对于I.MX6ULL而言,没有定义宏CONFIG_SYS_NO_FLASH的话函数initr_flash才有效。但是mx6_common.h中定义了宏CONFIG_SYS_NO_FLASH,所以此函数无效。

第21行, initr_nand函数,初始化NAND,如果使用NAND版本核心板的话就会初始化

第22行, initr_mmc函数,初始化EMMC,如果使用EMMC版本核心板的话就会初始化EMMC,串口输出如图所示信息:

第23行, initr_env函数,初始化环境变量。

第25行, initr_secondary_cpu函数,初始化其他CPU核, I.MX6ULL只有一个核,因此此函数没用。

第27行, stdio_add_devices函数,各种输入输出设备的初始化,如LCDdriver, I.MX6ULL使用 drE_video_init函数初始化LCD。会输出如图所示信息:

第28行,initr_jumptable函数,初始化跳转表。

第29行,console_init_r函数,控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备,如图所示:

第31行, interrupt_init函数,初始化中断。

第32行, initr_enable_interrupts函数,使能中断。

第33行, initr_ethaddr函数,初始化网络地址,也就是获取MAC地址。读取环境变量“ethaddr”的值。

第34行, board_late_init函数,板子后续初始化,此函数定义在文件mx6ull_alientek_emmc.c中,如果环境变量存储在EMMC或者SD卡中的话此函数会调用boardlate_mme_env_init函数初始化EMMC/SD。会切换到正在时候用的emmc设备,代码如图所示:

 图中的第46行和第47行就是运行“mmc dev xx”命令,用于切换到正在使用的EMMC设备,串口输出信息如图所示:

第38行,initr_net函数,初始化网络设备,函数调用顺序为:

initr_net->eth_initialize->board_eth_init(),串口输出如图所示信息:

 第40行,run_main_loop行,主循环,处理命令。

 

相关文章:

10_Uboot启动流程_2

目录 _main函数详解 board_init_f函数详解 relocate_code函数详解 relocate_vectors函数详解 board_init_r 函数详解 _main函数详解 在上一章得知会执行_main函数_main函数定义在文件arch/arm/lib/crt0.S 中,函数内容如下: 第76行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也…...

python+django汽车4S店零配件保养服务管理系统

汽车4S服务管理系统包括三种用户。管理员、普通员工、客户。 开发语言:Python 框架:django/flask Python版本:python3.7.7 数据库:mysql 数据库工具:Navicat 开发软件:PyCharm django 应用目录结构管…...

STM32F4的输出比较极性和PWM1,PWM2的关系

PWM 输出比较通道 在这里以通用定时器的通道1作为介绍。 如图,左边就是CNT计数器和CCR1第一路的捕获/比较寄存器,它俩进行比较,当CNT>CCR1, 或者CNTCCR1时,就会给输出模式控制器传送一个信号,然后输出模式控制器就…...

易优cms伪静态,EyouCms去除URL中的index.php

针对不同服务器、虚拟空间,运行PHP的环境也有所不同,目前主要分为:Nginx、apache、IIS以及其他服务器。下面分享如何去掉URL上的index.php字符,记得在管理后台清除缓存,对于一些ECS服务器可能要重启nginx等服务! 【Nginx服务器】 在原有的nginx重写文件里新增以下代码片…...

【自然语言处理】【大模型】CodeGeeX:用于代码生成的多语言预训练模型

CodeGeeX:用于代码生成的多语言预训练模型 《CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Evaluations on HumanEval-X》 论文地址:https://arxiv.org/pdf/2303.17568.pdf 相关博客 【自然语言处理】【大模型】CodeGen&#x…...

Open3D 非线性最小二乘拟合二维多项式曲线

目录 一、算法原理二、代码实现三、结果展示一、算法原理 多项式曲线表示为: p ( x ) = p 1 x n + p 2 x n...

kafka消息队列的两种模式

第一种模式: 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除) 1.消息生产者生产消息发送给队列,然后消费者从队列中取出并且消费消息 2.消息被消费以后,queue中不再有存储&#xff0…...

python语法复习

print:输出函数 print(520)效果:输出520. print(hello)效果:输出hello. print(1020)【效果:输出了:1020】注:“ ”在print里面是一个连接符。 print(1020)【效果:输出了30】注: 在此处…...

02-Java基础编程

Java基础编程 Java 基础语法Java 标识符变量变量的类型Java 基本数据类型基本数据类型转换 运算符常见运算符运算符的优先级 程序流程控制分支语句循环结构常用的循环结构循环的嵌套break 和 continue 关键字 数组一维数组多维数组的使用Arrays 工具类的使用数组中常见的异常 J…...

武忠祥老师每日一题||定积分基础训练(十)

已知f(x)连续 ∫ 0 x t f ( x − t ) d t 1 − cos ⁡ x , 求 ∫ 0 π 2 f ( x ) d x 的值。 \int_{0}^{x}tf(x-t)\,{\rm d}t1-\cos x,求\int_{0}^{\frac{\pi}{2}}f(x)dx的值。 ∫0x​tf(x−t)dt1−cosx,求∫02π​​f(x)dx的值。 已知一个关于f的变上限积分等式,&…...

C/C++趣味程序设计百例(41~50)

C/C语言经典、实用、趣味程序设计编程百例精解(5) 41.马克思手稿中的数学题 马克思手稿中有一道趣味数学问题:有30个人,其中有男人、女人和小孩,在一家饭馆吃饭花了50先令;每个男人花3先令,每个…...

论文阅读-2-DeepSMOTE Fusing Deep Learning and SMOTE for Imbalanced Data

文章目录 Abstract1. Introduction2. Learning From Imbalanced Data1. 数据级2. 算法级3. 集成方法 3. Deep Learning From Imbalanced Data基于深度神经网络的实例生成损失函数适应长尾识别 4. DeepSMOTEA. 动机B. 描述C. encoder-decoder框架D. 增强的损失函数E. 人工图像生…...

三种方法教你让模糊照片秒变高清图

现在随着数字相机和智能手机的普及,我们拍摄的照片数量越来越多,但是有些照片可能因为环境或技术等原因导致模糊不清,这时候我们就需要使用一些软件或工具来让照片变得清晰,以满足我们的需求。 下面介绍三种常用的照片变清晰的方…...

PyTorch深度学习实战 | 基于线性回归、决策树和SVM进行鸢尾花分类

鸢尾花数据集是机器学习领域非常经典的一个分类任务数据集。它的英文名称为Iris Data Set,使用sklearn库可以直接下载并导入该数据集。数据集总共包含150行数据,每一行数据由4个特征值及一个标签组成。标签为三种不同类别的鸢尾花,分别为&…...

服务端接口优化方案

一、背景 针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1. 批处理 批量思想:批量操作数据…...

【并发基础】Happens-Before模型详解

目录 一、Happens-Before模型简介 二、组成Happens-Before模型的八种规则 2.1 程序顺序规则(as-if-serial语义) 2.2 传递性规则 2.3 volatile变量规则 2.4 监视器锁规则 2.5 start规则 2.6 Join规则 一、Happens-Before模型简介 除了显示引用vo…...

Kubernetes系列---Kubernetes 理论知识 | 初识

Kubernetes系列---Kubernetes 理论知识 | 初识 1.K8s 是什么?2.K8s 特性3.小拓展(业务升级)4.K8s 集群架构与组件①架构拓扑图:②Master 组件③Node 组件 五 K8s 核心概念六 官方提供的三种部署方式总结 1.K8s 是什么&#xff1f…...

KingbaseES 原生XML系列三--XML数据查询函数

KingbaseES 原生XML系列三--XML数据查询函数(EXTRACT,EXTRACTVALUE,EXISTSNODE,XPATH,XPATH_EXISTS,XMLEXISTS) XML的简单使其易于在任何应用程序中读写数据,这使XML很快成为数据交换的一种公共语言。在不同平台下产生的信息,可以很容易加载XML数据到程序…...

【51单片机】点亮一个LED灯(看开发板原理图十分重要)

🎊专栏【51单片机】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【The Right Path】 🥰大一同学小吉,欢迎并且感谢大家指出我的问题🥰 目录 🍔基础内容 &#x1f3f3…...

数据可视化工具 - ECharts以及柱状图的编写

1 快速上手 引入echarts 插件文件到html页面中 <head><meta charset"utf-8"/><title>ECharts</title><!-- step1 引入刚刚下载的 ECharts 文件 --><script src"./echarts.js"></script> </head>准备一个…...

【AI绘画】——Midjourney关键词格式解析(常用参数分享)

目前在AI绘画模型中&#xff0c;Midjourney的效果是公认的top级别&#xff0c;但同时也是相对较难使用的&#xff0c;对小白来说比较难上手&#xff0c;主要就在于Mj没有webui&#xff0c;不能选择参数&#xff0c;怎么找到这些隐藏参数并且触发它是用好Mj的第一步。 今天就来…...

操作符知识点大全(简洁,全面,含使用场景,演示,代码)

目录 一.算术操作符 1.要点&#xff1a; 二.负数原码&#xff0c;反码&#xff0c;补码的互推 1.按位取反操作符&#xff1a;~&#xff08;二进制位&#xff09; 2.原反补互推演示 三.进制位的表示 1.不同进制位的特征&#xff1a; 2.二进制位表示 3.整型的二进制表…...

华工研究生语音课

这门课讲啥 语音蕴含的信息、语音识别的目的 语音的准平稳性、分帧、预加重、时域特征分析&#xff08;能量和过零率&#xff09;、端点检测&#xff08;双门限法&#xff09; 语音的基频及检测&#xff08;主要是自相关法、野点的处理&#xff09; 声音的产生过程&#xf…...

KingbaseES 原生XML系列二 -- XML数据操作函数

KingbaseES 原生XML系列二--XML数据操作函数(DELETEXML,APPENDCHILDXML,INSERTCHILDXML,INSERTCHILDXMLAFTER,INSERTCHILDXMLBEFORE,INSERTXMLAFTER,INSERTXMLBEFORE,UPDATEXML) XML的简单使其易于在任何应用程序中读写数据&#xff0c;这使XML很快成为数据交换的一种公共语言。…...

【Flink】DataStream API使用之源算子(Source)

源算子 创建环境之后&#xff0c;就可以构建数据的业务处理逻辑了&#xff0c;Flink可以从各种来源获取数据&#xff0c;然后构建DataStream进项转换。一般将数据的输入来源称为数据源&#xff08;data source&#xff09;&#xff0c;而读取数据的算子就叫做源算子&#xff08…...

树莓派硬件介绍及配件选择

目录 树莓派Datasheet下载地址&#xff1a; Raspberry 4B 外观图&#xff1a; 技术规格书&#xff1a; 性能介绍&#xff1a; 树莓派配件选用 电源的选用&#xff1a; 树莓派外壳选用&#xff1a; 内存卡/U盘选用 树莓派Datasheet下载地址&#xff1a; Raspberry Pi …...

O2OA (翱途) 平台 V8.0 发布新增数据台账能力

亲爱的小伙伴们&#xff0c;O2OA (翱途) 平台开发团队经过几个月的持续努力&#xff0c;实现功能的新增、优化以及问题的修复。2023 年度 V8.0 版本已正式发布。欢迎大家到 O2OA 的官网上下载进行体验&#xff0c;也希望大家在藕粉社区里多提宝贵建议。本篇我们先为大家介绍应用…...

数控解锁怎么解 数控系统解锁解密

Amazon Fargate 在中国区正式落地&#xff0c;因 数控解锁使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&#xff0c;…...

3.0 响应式系统的设计与实现

1、Proxy代理对象 Proxy用于对一个普通对象代理&#xff0c;实现对象的拦截和自定义&#xff0c;如拦截其赋值、枚举、函数调用等。里面包含了很多组捕获器&#xff08;trap&#xff09;&#xff0c;在代理对象执行相应的操作时捕获&#xff0c;然后在内部实现自定义。 const…...

Rust 快速入门60分① 看完这篇就能写代码了

Rust 一门赋予每个人构建可靠且高效软件能力的语言https://hannyang.blog.csdn.net/article/details/130467813?spm1001.2014.3001.5502关于Rust安装等内容请参考上文链接&#xff0c;写完上文就在考虑写点关于Rust的入门文章&#xff0c;本专辑将直接从Rust基础入门内容开始讲…...