uboot是什么: 是一个bootloader程序,芯片上电以后就会运行的程序
uboot的作用:
1. 初始化DDR等外设,一系列的裸机程序
2. 将linux内核从flash(emmc,nand等,emmc为例,linux内核是存放在第三区中)拷贝到DDR中
3. 引导启动Linux内核
uboot是一个裸机代码的集合,是遵循GPL协议的开源代码
uboot代码分3种类型
A. uboot官方代码 包含所有常用芯片 一般不直接使用该代码
B. 半导体厂商代码 只维护一个uboot,该uboot专门针对自家的芯片 我们自己移植uboot来适配一个新开发板的时候选择它
C. 开发板厂商代码 开发板厂商在半导体厂商提供的uboot代码基础上加入对自家开发板的支持 (相当于移植好之后的uboot,如果有我们使用的开发板对应的开发板厂商代码就直接拿过来用就ok)
uboot的编译流程和方法:
创建一个.sh文件,文件中存放编译流程命令,给权限,执行脚本编译
脚本内容:
1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
ARCH=arm: 表明代码框架是arm架构
CROSS_COMPILE=arm-linux-gnueabihf-: 表示编译工具的前缀
distclean:清理工程,这里会删除config等配置文件,故如果我们在通过KConfig界面修改了config文件后,不能在执行clean命令
具体代码:
PHONY += distclean
distclean: mrproper
@find $(srctree) $(RCS_FIND_IGNORE) \
\( -name '*.orig' -o -name '*.rej' -o -name '*~' \
-o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \
-o -name '.*.rej' -o -name '*%' -o -name 'core' \
-o -name '*.pyc' \) \
-type f -print | xargs rm -f
@rm -f boards.cfg
2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
mx6ull_14x14_ddr512_emmc_defconfig: 一个Makefile文件,该文件通常存放在uboot/config中,作为对应开发板的配置文件
mx6ull_14x14_ddr512_emmc_defconfig文件名称的来源:mx6ull半导体厂商下,芯片大小为14x14,DDR大小为512M,flash为emmc类型
执行该命令的作用;(生成两个软件和将默认配置文件中的内容输出到.config文件)
1. 自动生成文件(include/generated/version_autogenerated.h)用于保存该版本uboot信息 (uboot启动中打印的信息,uboot版本,编译工具链,GNU id等信息)
2. 自动生成文件(include/generated/timestamp_autogenerated.h)用于保存该uboot的时间戳(表示整个uboot文件是什么时候生成的)
3. 最最关键的作用
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
分为两部分: 依赖和命令
依赖:
FORCE: 使依赖文件一直比目标新,故命令会每次都被执行
outputmakefile: 文件无效
scripts_basic: 编译生成出scripts/basic/fixdep软件
命令: 生成script/kconfig/conf软件(该软件会将默认配置文件%_defconf中的配置输出到.conf文件中)和将我们的默认配置文件(mx6ull_14x14_ddr512_emmc_defconfig)中的内容输出到.config文件
.config文件中包含了许多的宏开关和宏变量,这些宏变量控制了整个uboot中的代码
3. make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12 (该命令的作用,将u_boot代码下的所有裸机驱动文件编译生成build_in.o文件,在通过链接脚本u-boot.lds将这些build-in.0
文件和启动文件链接在一起形成u-boot文件,然后将生成的u-boot文件和其他文件一起生成u-boot-nodtb.bin文件,在以u-boot-notdb.bin文件生成u-boot.bin文件)
V=1: 用来设置编译过程中的信息输出级别 ; -jn: 设置主机使用多少线程来编译uboot
make的流程:关键中的关键
1. make使用默认目标_all
2. 我们这里是编译整个uboot故_all依赖于all,如果只是编译某个模块_all依赖于modules
3. all依赖于ALL_y,ALL_y这个变量的值根据我们前面的config中定义的宏变量来决定,但是一定包含以下文件
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin ... ...
4. 这里重点分析u-boot.bin的由来,u-boot.bin依赖于u-boot-nodtb.bin,而u-boot-nodtb.bin依赖于u-boot文件,而u-boot文件依赖于u-boot_init,u_boot_main,u-boot.lds文件
boot_init:arch/arm/cpu/armv7/statrt.o 启动文件
u-boot.lds: 脚本链接文件
u_boot_main: uboot中所有子目录build-in.o的集合,这个集合就是那些裸机驱动的集合
注意:我们烧写给开发板中的不是u-boot.bin文件而是u-boot.imx文件,u-boot.imx文件=u-boot文件+ 头部(IVT_DCD等数据),通过imxdownload软件完成转换
4. uboot命令详解
//A. 环境变量命令
1. 查看当前uboot所支持的命令 ? / help
2. 查看板子信息 bdinfo
3. 输出环境变量信息 printenv
4. 查看uboot版本号 version
5. 修改环境变量 setenv + saveenv (环境变量存放在emmc中,uboot启动会将emmc中的环境变量拷贝到DDR中,setenv只是修改DDR中环境变量的值,所以只是
setenv而没有saveenv该变量只在该次有效果,而saveenv是修改emmc中的环境变量)
6. 新建环境变量 setenv 要新建的环境变量名 对应的值 +saveenv
7. 删除环境变量 setenv 要删除的环境变量名 + saveenv ==>将该环境变量赋空值
//B. 内存操作命令
8. 显示内存值(查看一段指定内存地址保存的值) md[.b/.w/.l] 要查看的那段内存的首地址 长度
.b/.w/.l代表长度的单位 分别为byte,word,long(1,2,4字节) 注意uboot命令里面的数字都是16进制的
例如 md.b 80000000 14 ==>查看起始地址为80000000,长度为0x14*1byte大小的这段内存中保存的值 (类比,14=多少块内存,bwl=每块内存多大)
9. nm 修改指定地址的内存值(单次修改) nm[.b/.w/.l] 要修改的内存地址
例如 我们现在设置80000000处的值为0x12345678
a. md.l 800000000 1 显示: 80000000: ffffffff
b. nm.l 80000000 回车
显示 80000000: ffffffff ?光标
c. 输入12345678 回车
d. 输入q 退出
md.l 80000000 1 显示: 80000000: 12345678
10. mm 修改指定地址的内存值(连续修改) mm[.b/.w/.l] 要修改的那段内存的起始地址
与nm相比是只要你不输入q你可以一直修改下去
11. mw 用于使用有关指定的数据填充一段内存 mw[.b/.w/.l] 要填充的那段内存的起始地址 要填充的值 要填充多少块内存
例如,将起始地址为80000000,每块内存大小为4byte,填充0x14块,填充的数据是0x12345678
mw.l 80000000 12345678 14
12. cp 内存地址保存的数据拷贝命令,1.将DRAM中的内存段a中保存的值拷贝到内存段b中 2.将emmc中的数据拷贝到DRAM中 cp[.b/.w/.l] 源内存起始地址 目标内存起始地址 拷贝多少块内存
例如:将80000000出开始的4*0x14个内存块中的内容,拷贝到80000100中
cp.l 80000000 80000100 14
13. cmp命令 内存地址保存的内容比较命令 cmp[.b/.w/.l] 内存地址a的起始地址 内存地址b的起始地址 比较多少块内容
例如: 比较80000000 800000100 这两个内存地址的接下来的4*0x14块内存保存的内容
cmp 80000000 80000100 14 ==>如果显示0x14块内容相等表示这两块内存保存的内容相等
//C. 网络命令
14. 配置打通开发板和主机服务器的网络
setenv ipaddr 192.168.1.50 //设置开发板的ip地址,保证开发板和服务器处于同一个网段下
setenv ethaddr 00:04:9f:04:d2:35 //设置mac地址,mac地址即物理的网络接口地址,在一个网段下必须唯一 网络驱动,会知道mac地址是网络设备net_device中一个成员变量,修改mac地址 == 修改该成员值 同时专门提供了修改mac的方法和判断mac值是否合理的方法
setenv gatewayip 192.168.1.1 //网关
setenv netmask 255.255.255.0 //子网掩码
setenv serverip 192.168.1.250 //要与开发板通信的服务器ip地址
saveenv
15. ping 验证开发板和服务器之间能否通信 ping 要测试的ip地址b if(显示alive) 表明能与ip b通信
注意: 只能通过在uboot命令行中ping服务器来验证网络是否ok,不能在服务器ping 开发板ip(因为uboot没有对ping命令做处理)
16. dhcp 用于从路由器中获取ip地址 返回一个该路由器所在网段下一个未被使用的ip
17. nfs命令(nfs是Network File System 网络文件系统的缩写) 通过网络下载东西到开发板上的DRAM中
通常使用nfs命令将Ubuntu(服务器)中的文件下载到开发板上的DRAM中 uboot中nfs命令格式:nfs 要下载到的DRAM地址 服务器地址:要下载的文件的在服务器中的绝对路径
//NFS使用流程: 在服务器中配置NFS(开启NFS服务,创建一个NFS文件,将要通过nfs下载到开发板中的文件存放在该NFS文件夹下)
例如: 将文件1.c拷贝到DRAM中的80800000处
服务器:cp 1.c /home/wjc/linux/nfs(前面配置好的nfs文件)
uboot: nfs 80800000 192.168.1.250:/home/wjc/linux/nfs/1.c
18. tftp命令 通过网络下载东西到开发板上的DRAM中 (与nfs相比,tftp更加好用)
tftp命令使用tftp协议,在该协议中Ubuntu作为TFTP服务器
tftp命令的环境配置: 在Ubuntu中安装tftp环境+创建一个tftp文件夹+给tftp文件夹设置可执行权限+修改配置文件
注意,使用tftp将文件下载到开发板中,需要设置要下载的文件具有可执行权限
命令格式: tftp 要下载到开发板中DRAM地址 要下载的文件的名称
例如:将文件2.c拷贝到DRAM中的80800000处
服务器: 1.chmod 777 /home/wjc/linux/tftp 2.cp 2.c /home/wjc/linux/tftp 3.chmod 777 /home/wjc/linux/tftp/2.c
开发板: tftp 80800000 2.c
//D. mmc操作命令(sd卡,nand和emmc flash都是mmc设备)
19. mmc info/mmcinfo 输出当前选中的mmc设备的信息 (主要包括内存大小,设备名称,ID等)
20. mmc list 查看当前开发板上一共有多少个mmc设备 (备注emmc==emmc设备,sd=sd卡设备)
21. mmc part 查看当前mmc设备具有多少个分区 (对于块设备驱动来说,不同分区之间,主设备号相同,次设备号不同)
例如查看mmc设备号为1的具有多少个分区?
mmc dev 1 ==>切换到设备号为1的分区0设备
mmc part ==>查看该设备具有的设备分区信息
22. mmc dev 切换当前mmc设备 mmc dev [设备号] [分区号] ==>[]代表可写可不写,每个mmc具有一个对应设备号,每个mmc设备又包括多个分区
mmc dev 0 == mmc dev 0 不写分区号==> 默认切换到分区0
mmc dev 1 1 切换到mmc中设备号为1的分区1 ==>设置mmc中设备号为1的分区1为当前mmc设备
23. mmc read 读取mmc设备的数据(将mmc设备中的数据读取(拷贝)到DRAM中) mmc read 读取到的数据存放在DRAM中的地址 要读取的块的起始地址 要读取多少个扇区
例如: 从EMMC设备的第0x600块开始,读取0x10个块到DRAM中的0x80800000处
知识点: 块=n个扇区 ,一个页可以有多个块,一个块有多个扇区,一个扇区=512byte 第n个扇区 == 从该mmc设备的分区0开始计算
mmc dev 1 0 ==>将当前mmc设备切换到我们要读取的EMMC设备的分区0
mmc read 80800000 600 10
24. mmc write 将数据写入到mmc设备(将DRAM中的数据写入(拷贝)到mmc设备中) mmc write 要被写入到mmc设备的数据在DRAM中的地址 在mmc设备中要写入数据的起始地址 写多少个扇区
例如:使用tftp+ mmc write实现对uboot是升级
思路: 将Ubuntu中重新编译,烧写出来的u-boot.imx文件通过tftp下载到DRAM中,然后通过mmc write将存放在DRAM中的u-boot.imx文件写入到sd卡中
mmc dev 1 0 //将mmc设备切换到sd卡的分区0
tftp 80800000 u-boot.imx //通过tftp将u-boot.imx文件下载到DRAM的80800000地址处,(注意这里需要将u-boot.imx文件拷贝到tftp文件夹中,并设置权限)
mmc write 80800000 2 32E //将DRAM中存放了u-boot.imx文件的数据写入到当前mmc设备(sd卡)的第二块分区开始的连续0x32E个扇区
mmc partconf 1 1 0 0 //分区配置
注意点: 不能写SD卡或者EMMC的前两个扇区(扇区0,1),因为里面保存着分区表信息
25. mmc erase命令 擦除mmc设备的指定块 mmc erase 要擦除的当前mmc设备块起始地址 要擦除的设备块的数量
注意点: 最好不要随便使用这个命令
//E: FAT格式文件系统操作命令 (这些命令只适用于FAT格式的文件系统)
26. fatinfo命令 用于查询指定mmc设备的下不同分区的文件系统类型 fatinfo 要查询的设备类型 设备号:分区号
例如我们要查看emmc设备中分区1的文件信息 文件系统类型:FAT16,FAT32...
fatinfo mmc 1:1 ==> 显示的结果是分区1的文件系统类型,该文件系统的描述信息和该文件系统的大小
27. fatls命令 用于查询指定设备下分区中存放的文件名称和大小 fatls 要查看的设备类型 设备号:分区号
例如我们想要查看emmc设备的分区1中存放了哪些文件
fatls mmc 1:1 ==>显示该设备分区下保存的文件名称和文件大小
28. fstype命令 用于查看mmc设备的每个分区的文件系统格式 fstype 要查看的设备类型 设备号:分区号
例如我想要查看emmc设备所有分区的文件系统格式
fstype mmc 1:0 ==>分区0,格式未知 因为没有格式化 分区0存放着uboot,
fstype mmc 1:1 ==>分区1,fat格式 分区1存放着linux内核+设备树文件
fstype mmc 1:2 ==>分区2,ext4格式 分区2存放着根文件系统(rootfs)
29. fatload命令 用于将指定的文件读取到DRAM中(与mmc read相比,这个操作对象是文件,mmc read操作对象是mmc设备中的扇区)
fatload 要从什么设备中读取文件到DRAM中 设备号:分区号 读取在DRAM中的地址 要读取的文件名称
例如: 要将emmc设备分区1中所存放的zImage文件读取到DRAM的80800000地址处
fatload mmc 1:1 80800000 zImage
30 fatwrite命令用于将DRAM中指定的文件写入到其他设备(mmc)中 (与mmc write相比一个操作文件一个操作数据)
fatwrite 该文件要写入的设备类型 设备号:分区号 要将DRAM中哪里的地址上的文件写入到其他设备中 要写入的文件名称 文件的大小
例如:通过fatwrite命令和tftp命令来实现对emmc设备中保存的linux镜像文件的更新
1.将新编译出来的zImage文件拷贝到tftp文件夹中,并修改zImage文件的权限
2. tftp 80800000 zImage //下载文件到DRAM中
3. fatwrite mmc 1:1 80800000 zImage zImage文件的大小 //文件从DRAM中写入到emmc分区1中
4. fatls mmc 1:1 //查看是否写入ok
//F: EXT格式文件系统操作命令 (这些命令只适用于EXT格式的文件系统)
31.ext4ls命令 查询ext4文件系统格式下指定设备分区中存放的文件信息 ext4ls 设备类型 设备号:分区号
例如要查看emmc设备分区2中保存的根文件系统下存在哪些文件
ext4ls mmc 1:2 ==>显示rootfs中的文件信息
//G: boot操作命令,uboot的本质工作是引导Linux启动
32. bootz命令 用于启动zImage镜像文件 bootz linux镜像文件在DRAM中的位置 initrd文件在DRAM中的位置 设备树文件在DRAM中的位置
例如: 通过tftp+bootz启动linux内核和设备树
拷贝zImage和设备树文件到tftp文件夹中并修改文件权限
tftp 80800000 zImage
tftp 83000000 imx6ul-alientek-emmc.dtb(设备树文件名称)
bootz 80800000 - 83000000
33. bootm命令 用于启动uImage镜像文件 bootm uImage镜像文件在DRAM中的位置 initrd文件在DRAM中的位置 设备树文件在DRAM中的位置
34. boot命令 用于通过调用bootcmd环境变量来启动linux系统
例如:我们想通过boot命令来开启linux,那么就设置bootcmd即可
setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ul-alientek-emmc.dtb;bootz 80800000 - 83000000 '
saveenv
//H. 其他常用命令
35. go命令 用于跳到指定的地址处执行应用 go 要执行的应用在DRAM中的地址
例如:我们要在开发板中执行Ubuntu中生成的1.bin应用
tftp 80800000 1.bin
go 80800000
36. reset命令 用于复位重启开发板
37. run命令 用于运行环境变量中定义的命令(通常是运行自定义的命令)
例如,我现在自定义一个命令:startlinux 来实现启动linux
setenv startlinux 'tftp 80800000 zImage;tftp 83000000 imx6ul-alientek-emmc.dtb;bootz 80800000 - 83000000 '
saveenv
run startlinux
38. mtest命令 是一个简单的内存读写命令 mtest 要测试的那块内存的起始地址 终止地址
5. makefile知识点
1. make -C 指定的要编译的makefile文件名称 //编译指定的makefile文件
2. export 变量名 //将这个变量传递给子makefile文件
3. 命令输出级别 make V=0(默认) //输出短命令
make V=1 //输出编译过程中执行的完整编译命令
make -s //静默输出(不显示编译命令)
4. 编译结果输出目录 make O=指定编译结果要输出到的目录,如果不使用 0=指定输出目录话,默认编译的输出结果存放到源文件所在的目录
5. 模块编译 make M=dir //单独编译某个模块
6. 代码检查 make C=1 //检查那些需要重新编译的文件 (C=2:检查所有的源文件)
7. SHELL和MAKEFLAGS变量默认是会传递给子makefile文件,除非用unexport修饰
6. uboot的运行流程
知识点: 查看一个东西的流程,首先你得找到它的开始,找一份代码的开始文件,你查看它的链接文件即可
1. _start入口地址 == uboot的起始地址 ==0x87800000
2. 根据拨码开关的值来设置对应的工作模式
3. 重定向中断向量表
4. 通过设置CP15寄存器来关闭MMU等设备
5. 设置SP栈顶指针的位置
6. 划分DRAM中的区域,(头部区域,sp指针地址,malloc区域,gd区域 ...)
7. 初始化一系列外设(串口,定时器等)
8. uboot将自己重定位DRAM最后面的区域(定位到最后面 == 我们可以使用的连续内存最大化), 通过采用位置无关码来解决重定位后的链接地址和运行
地址不一致产生的问题
uboot运行时显示的那些信息来源的跟踪
**************************************************
**************************************************
U-Boot 2016.03 (Oct 13 2020 - 23:21:44 +0800) //display_options,通过串口输出一些信息
CPU: Freescale i.MX6ULL rev1.1 69 MHz (running at 396 MHz) //print_cpuinfo 函数用于打印 CPU 信息
CPU: Industrial temperature grade (-40C to 105C) at 36C
Reset cause: POR
Board: MX6ULL 14x14 EVK //show_board_info 函数用于打印板子信息
I2C: ready //init_func_i2c 函数用于初始化 I2C,初始化完成显示ready
DRAM: 512 MiB //announce_dram_init,此函数很简单,就是输出字符串“DRAM",dram_init,只是设置 gd->ram_size 的值,对于该开发板来说就是512MB。\
DRAM 的起始地址为 0X80000000,大小为 0X20000000(512MB)
//****** 上面这些信息的初始化在board_init_f中实现,下面的这些初始化信息在board_init_r中实现 **************
MMC: FSL_SDHC: 0, FSL_SDHC: 1 //initr_mmc 函数,初始化 EMMC, 可以看出,此时有两个 EMCM 设备,FSL_SDHC:0 和 FSL_SDHC:1。
Display: ATK-LCD-4.3-800x480 (800x480) //stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL使用 drv_video_init 函数初始化 LCD
Video: 800x480x24
In: serial // console_init_r 函 数,控 制 台 初 始 化 , 初 始 化 完 成 以 后 此 函 数 会 调 用stdio_print_current_devices \
Out: serial 函数来打印出当前的控制台设备,这里表示这里uboot的控制台设备(显示终端)都是串口
Err: serial
switch to partitions #0, OK //如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数来初始化 EMMC/SD 来初始化 EMMC/SD,在函数中会切换到正在时候用的 emmc 设备
mmc0 is current device 在这个函数中会进行切换到正在时候用的 emmc 设备的操作,这两条显示是 mmc dev 命令的打印
Net: FEC1 // initr_net 函 数 , 初 始 化 网 络 设 备,流程:initr_net->eth_initialize->board_eth_init(),这是打印信息
Normal Boot
Hit any key to stop autoboot: 0 //下面第7点会详解
知识点: DDR中最终的内存分配图 (开发文档 801页)
7. uboot倒计时选择功能的实现
功能模式: uboot启动以后倒计时3s(一个环境变量控制),如果3s倒计时之前按下回车键进入uboot的命令模式,如果倒计时结束还没有按就进自动启动内核
流程分析:
1. 先获取环境变量bootdelay(倒计时的秒数)的值
2. 在将这个时间值作为参数传递给判断倒计时过程中有没有按键按下
3. 显示倒计时提示语句
4. 根据这个倒计时秒数做一个循环判断,判断有没有按键按下,每隔1s更新显示一次倒计时秒数,如果倒计时结束还没有按键按下返回0,有按键按下返回1
5. 接收前面步骤4的返回值来判断下一把该怎么进行,如果没有按键按下就调用boot命令来启动linux函数,如果有按键按下就调用启动uboot命令模式的函数
8. uboot中命令是如何定义的
通过定义宏U-BOOT-CMD来定义一个命令,在这里过程中会根据我们输入的字符串来创建一个与之对应的cmd_tab_t类型的变量
例如定义 dhcp命令
dhcp经过U-BOOT-CMD宏来创建一个cmd_tab_t类型(结构体)的变量
这个结构体成员包括:
命令的名字:dhcp
命令的最大参数: 3
该命令对应的操作函数:do_dhcp()
... ...
9. uboot命令模式下对输入命令的操作流程
1. 接收命令行输入的命令
2. 将接收到的命令和cmd_tab_t数组(保存了所有命令的信息)中每个元素的name成员做比较,如果有找到说明输入的命令是有效的就调用该命令对应的
操作函数,操作函数的返回值接收命令的结果
10. bootz启动linux内核的过程
通过调用bootz命令来实现linux内核的启动,即通过调用do_bootz()来实现启动linux
知识点,bootz启动的系统不仅仅可以是linux内核,还可以是其他内核,故不同的系统调用的启动函数不同。linux内核启动的关键是images变量,bootargs环境变量保存着uboot传递给linux kernel的信息
这里以bootz启动linux系统为例 输入 bootz 80800000 - 83000000 //80800000:linux镜像在DRAM中的地址 83000000:设备树文件在DRAM中的地址
1. 设置Linux系统在DRAM中的储存位置,即linux系统镜像的入口点 这里是设置为0x80800000
2. 判断当前的系统镜像文件是否为linux系统镜像文件 ==>这是根据我们输入的linux镜像地址中存放的镜像文件和linux镜像文件去判断,如果是liunx镜像就没有提示信息同时会记录这个镜像文件的信息(起始地址和结束地址),不匹配会打印不匹配信息
3. 查找ramdisk文件和设备树文件 ==>设备树文件到我们输入的设备树地址中获取
4. 关闭中断
5. 设置保存系统镜像类型的变量为Linux (非常重要,因为后面还需要根据这个变量去调用该系统对应的启动函数(do_bootm_linux() ))
6. 根据系统镜像类型的值选择与之对应的启动函数(do_bootm_linux())
7. 在启动函数中首先会根据我们前面查找到的设备树的“兼容性”属性(compatible属性)判断我们要启动的linux内核是否支持这个开发板
8. ************* 最后调用kernel_entry()函数 ***************************************
关键点:kernel_entry()函数不在是定义在uboot代码中而是定义在linux内核镜像中,而且linux镜像文件的第一行代码就是函数kernel_entry(),我们前面在判断
系统镜像是否为Linux系统(步骤2)时保存了linux系统镜像的起始地址,故这里就会根据这个地址而调用到kernel_entry(),从而开始进入到linux kernel的代码
11. kernel_entry函数详解
kernel_entry函数有3个参数:zero,arch,params,
第一个参数 zero 必须为 0;第二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
kernel_entry(0, machid, r2); //前面为什么说images这个全局变量非常重要,因为这些信息都保存在这个变量的成员中。(linux内核起始地址,设备树起始地址,系统类型 ...)
12. uboot移植 (学习将NXP官方uboot移植到我们自己的开发板上)
1> uboot移植的一般流程
1. 找到我们要移植的uboot的参考开发平台,一般是原厂(半导体厂商)的原厂开发板
2. 将要移植的uboot编译烧写到我们的开发板上根据现象来判断需要修改那些东西(一般是LCD,网络驱动等)
2> 实际操作流程(通用部分)
1. 根据我们的开发板查找与之对应的开发板默认配置文件
默认配置文件存放文件夹:configs 例如我们开发板对应的文件: mx6ull_14x14_evk_emmc_defconfig mx6ull:芯片的厂家 14x14:芯片尺寸为14x14mm emmc:flash类型
2. uboot编译三部曲,clean;编译默认配置文件;编译工程,然后将生成的u-boot.imx烧写到开发板中观察现象
3. 通过打印信息和命令来对这个uboot进行检查(mmc设备检查(根据命令查看mmc信息是否正确),lcd驱动检查(查看有没有显示logo),网络驱动检查(查看打印信息中有没有表示识别出网络信息) )
4. 添加我们开发板对应的默认配置文件 将前面找到的默认配置文件拷贝一份重命名mx6ull_alientek_emmc_deconfig 来和我们开发板匹配
然后修改默认配置文件的内容(修改路径,因为我们更换了默认配置文件的名称)
5. 添加我们开发板对应的头文件(include/configs/半导体厂商名称.h),先拷贝改名为 mx6ull_alientek_emmc.h
这个头文件的作用(非常关键): 这个文件中有很多宏定义,这些宏定义用于配置uboot,决定了我们有使能、禁止uboot的某些功能
详解: 这个头文件中基本上是"CONFIG_"宏定义,即该文件的主要功能是配置或者裁剪uboot
如果需要新功能在头文件里添加对应的CONFIG_XXX宏即可,禁止某些功能,那么删除对应的宏即可
基本上我们前面说的uboot流程中干的那些工作都需要看这个文件中定义的宏,比如输出cpu信息,板子信息,串口初始化,logo显示等功能都有对应的宏定义在该头文件
6. 添加开发板的板级文件夹
a. 先拷贝一份在重命名
b. 修改板级文件夹下的Makefile文件,imximage.cfg文件,Kconfig文件,MAINTAINERS文件和修改U-BOOT的图像界面配置文件
3> 移植之后造成部分驱动无效的修改流程(移植 差异部分) 根据前面的uboot启动中打印信息和命令模式下命令测试得出结果
1. LCD驱动修改
2. 网络驱动修改
13. bootcmd环境变量
知识点: bootcmd里面保存着uboot的默认命令,通常这些默认来启动linux内核
boot命令会调用bootcmd环境变量,uboot倒计时结束就会执行boot命令来调用bootcmd环境变量来启动linux
bootcmd环境变量的值可以在uboot命令模式下设置,在板子第一次运行uboot的时候会使用uboot代码中的默认值来设置bootcmd环境变量
默认bootcmd的值 = 'mmc dev 1; fatload mmc 1:1 0x80800000 zImage;fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000'
实际上,bootcmd实现默认值的定义非常复杂,因为该默认值需要适配不同的开发板(因为不同开发板的设备数文件是不同的)
14. bootargs环境变量
知识点:bootargs环境变量中保存着uboot传递给Linux内核的参数
bootargs环境变量是由环境变量设置的mmcargs
经典的bootargs环境变量中保存的命令如下: bootargs == console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw
console:设置linux终端,即设置通过什么设备和Linux来进行交互
ttymxc0 = 开发板上的串口1, linux中一切皆文件,开发板上串口1在linux系统下的设备文件就是/dev/ttymxc0
115200: 串口1的波特率为115200 ==>设置串口1作为linux的终端,串口1的波特率为115200
root: 用来设置根文件系统的位置,root = /dev/mmcblk1p2用于说明rootfs存放在mmcblk1的分区2中 (注意bl,l是字母)
kl表示mmc设备号,p2表示mmc设备的分区2 ==>说明rootfs存放在设备类型为:mmc,设备号为:1,分区号:2的地方(EMMC的分区2存放着根文件系统)
rootwait: 表示等待mmc设备初始化完成之后在挂载根文件系统,如果mmc设备还没有初始化就挂载会出现错误
rw: 表示根文件系统可能读写,如果不加rw,可能会导致无法在根文件系统中进行写操作,只能进行读操作
rootfstype: 用于指定根文件系统类型
关键点: 如果根文件系统为ext格式则可省略该选项,我们这里设置的根文件系统的ext4的故可以省略
如果非ext格式则需要通过该选项指定根文件系统类型
15. U-Boot图像化配置详解
知识点: 可以通过默认配置文件(半导体厂商名称_开发板名称_flash类型_defconfig),和开发板对应的头文件(半导体厂商_开发板名称_flash类型.h)来配置uboot,最终根据这些文件来形成uboot的最终配置文件.config
在这里我们实现用图形化界面来配置uboot形成最终的.config文件
打开图形化界面: make menuconfig
menuconfig:是一套图形化的配置工具,该工具需要ncurses库支持,故想使用图形化配置工具先按照库来搭建环境
menuconfig工具重点会使用两个文件
.congig文件: 图形化配置本质上就是修改这个文件,你对应的操作就是修改这个文件中的对应内容
Kconfig文件: 图形界面的描述文件,就是描述图形界面中应该出现什么内容
操作说明:
************* ****************** ******************
键盘上的"↑"和"↓"键来选择要配置的菜单
菜单后面有 "--->" 代表这个菜单还有子菜单,可以通过按下"enter"按键或图形界面中的下标键"select"来进入到它的子菜单
菜单前要一个[]代表可以配置这个子菜单
输入 "Y": []/[M] --> [*] ,将相应的代码编译进Uboot中
输入 "N": [*]/[M] --> [],表示不编译相应的代码
输入 "M": []/[*] --> [M],表示将相应的代码编译为模块
按下 "?": 查看现在选中的菜单的帮助信息 ==图形界面下标键 "help"
按下 "/": 表示打开搜索框,可以在搜索框中输入要搜索的内容
按两下 "Esc": 表示退出当前菜单,返回到上一级菜单 ==图形界面下标键 "exit"
下标键 "save": 保存修改后的配置文件(.config)
下标键 "load":加载指定的配置文件
例如:通过图形界面使能dns功能
1. make menuconfig 来打开图形配置界面
2. 通过“entry”来选择 "Command line interface"/"Network commands -->" 选中dns菜单 按下"Y"键,然后一直按两下"ESC"键返回到上一个菜单,最后在是否保存修改的配置时选择"YES"
3. 查看图形配置的修改效果,打开.config文件,查看是否新增了"CONFIG_CMD_DNS=y"怎么一行
4. 重新编译uboot工程,注意这里不能用脚本3步骤,因为脚本第一步clean就是删除config文件,一执行我们前面的配置就没了,故只需执行 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
5. 验证实际效果; 将编译生成的u-boot.imx烧写到sd卡中,从SD卡中启动uboot,如何进入命令模式,设置dns环境变量的值 "setenv dns 114.114.114.114"(即DNS = 114.114.114.114) saveenv
开始验证能否真的可以解析域名 dns www.baidu.com ==>显示 14.215.177138 可以显示百度的位置的ip,说明dns功能ok
图形配置界面的优势: 使能一些命令非常方便,不需要去到处找命令的配置宏
16. make menuconfig过程分析
静默编译生成可执行文件mconf,mconf软件会调用根目录下的Kconfig文件来构建图形配置界面
17. Kconfig语法简介 学习网站: https://blog.csdn.net/lang523493505/article/details/105067899
我们以uboot根目录下的顶层Kconfig文件为例,学习Kconfig语法
1. 主菜单名称"U-Boot 2016.03 Configuration"的来源
知识点: 主菜单即输入 "make menuconfig" 后默认打开的界面就是主菜单
主菜单的字符串的定义代码为 :mainmenu "U-Boot $UBOOTVERSION Configuration" 变量UBOOTVERSION == 2016.03 故显示该值
2. 调用其他目录下的Kconfig文件 (和Makefile一样,Kconfig也可以调用其他目录下的Kconfig文件)
source "xxx/Kconfig" //xxx为具体的命令名,这个目录路径是相对路径,相对当前Kconfig文件所在的位置 顶层Kconfig包含其他目录下的Kconfig == 子目录下的Kconfig文件在主菜单中生成各自的菜单项
include xxx.mk //如果没有指定要调用的makefile文件所在的路径的话,先在当前路径下查找,如何查看编译中是否有"-I" /"--include-dir" 选项(指定要调用的Makefile文件所在的路径)
3. menu/endmenu代码段来生成子菜单 不可编辑的菜单
知识点: 1. 子菜单: "菜单名 --->" ,这个子菜单下面有其他的条目
例如定义一个名为"General setup"的子菜单
menu "General setup"
... ...
endmenu
结果显示为 General setup --->
2. 条目: "[] 条目名 ",在Kconfig中,条目定义处于menu-endmenu间,代码类型是:config 宏变量(配置项名称),这样定义的代码就会在图形界面显示为条目
例如在名为"General setup"的子菜单下定义一个名为 "LOCALVERSION_AUTO"的配置项
menu "General setup"
config LOCALVERSION_AUTO
bool "Automatically append version information to the version string"
default y
help
my name is wjc
endmenu
结果显示为: General setup ---> 下按"enter进入"后显示为: [*] Automatically append version information to the version string
default y: 表示这个配置项的默认值就是y,如果没有使用default 命令,那么默认值就是n,**** 关键点: 一个配置项可以有多个默认值,但是只有第一个被定义的值是有效的
代表了.config文件中宏 CONFIG_LOCALVERSION_AUTO=y (故得: Kconfig文件中config关键字后面的配置项,在config中调用的形式为CONFIG_配置项)
条目的显示形式为:变量类型 配置项显示在图形界面的字符串(标题)
变量类型包含5种: bool,tristate,string,hex,int,常用的为bool,tristate,string
bool类型有两种值,y = [*] = 使能这个条目代表的配置项,即在config文件中定义 CONFIG_配置项=y,n = [] = 禁止这个配置项
tristate: 在bool的基础上加了个值 m = [M] ==表示将这个配置项编译为模块
string: 存储本地字符串,就是你可以给这个条目对应的配置项赋字符串值
例如: 定义字符串配置项 LOCALVERSION (保存版本号)
config LOCALVERSION
string "Local version - append to U-Boot release"
help
wjc
结果显示: () Local version - append to U-Boot release
选中这个条目,按"enter“之后会出现一个输入框,输入我们想输入的值,保存即可,在config文件中,CONFIG_LOCALVERSION = "我们输入的字符串的值"
3. depends on 命令 :普通依赖
例如 我们定义配置项A依赖于配置B,即(只有配置项B被选中之后,配置A才可以被操作)
config A
bool
depends on B
结果为,只有配置项b被选中(y)之后,才可以对配置项A操作(==在config中定义了CONFIG_A宏变量),bool表示配置项A的变量类型是bool型,可以选择(y/n)
4. select命令 : 表示方向依赖
例如:当我选中配置项C时,连带着配置项D,E也会被选中
config C
bool "if you want to use C"
select D
select E
结果:当我们选择C时(y),那么config文件中会定义 CONFIG_D=y,CONFIG_E=y
5. choice/endchoice代码段定义了一组可选择项.供用户单选(多选一)
例如 有4个选项A,B,C,D供你选择
choice
prompt "you select" #配置项的显示名称
default B #默认选择的配置项
config A
bool "A"
config B
bool "B"
config C
bool "C"
config D
bool "D"
config E
bool #没带提示信息的条件类型不会实现
endchoice
结果先显示这组选择项
you select(B) //在prompt会显示当前我们选择的配置项名称
我们选中这个按"enter"进入之后显示为
() A
(X) B //X表示我们当前选择的配置项,光标移动到要选择的地方,按Select下标键
() C
() D
6. menuconfig: 带选项的菜单,就是说你要想进入到这个菜单里面你必须要选择这个菜单
类型: [] 菜单名称 -->,与menu相比,前面多了个框,相当于多了个大开关,本来menu修饰的菜单下的条目,只需选择Y就会被配置,现在不仅仅条目要选择上,他所在的menuconfig对应的选项也要选择上
例如 选择了menuconfig菜单menu1后面会显示配置条目menu2_1和menu2_2
menuconfig menu1
bool "menu1"
default n
if menu1 #如果配置可选择菜单menu1为y,那么执行下面的代码
config menu2_1
bool "menu2_1"
default n
config menu2_2
bool "menu2_2"
default y
endif
结果显示:
[] menu1 --->
我们选中配置为y之后
[] menu2_1
[*] menu2_2
7. comment 在图形配置界面显示注释
comment "i am zhisi"
结果显示:
*** i am zhisi ***