你不得不学的U-Boot基础:命令和环境变量
1. 引言
在前期学习U-Boot的过程中,我建议大家,要先学会如何使用U-Boot,切记不要一头扎进源码中分析源码。如果你的基础支撑(ARM汇编和体系结构、Makefile、GCC编译器)不够,很容易造成挫败感,并且收效甚微,看了好久,你都不知道U-Boot到底是干什么的。当学会了熟练使用U-Boot后,如果工作中遇到了一些问题,需要进一步探究和剖析U-Boot的细节,这时再去深入去学习。
U-Boot提供了环境变量和命令行机制,其使用也主要集中在U-Boot常用命令的掌握和环境变量的相关操作。在U-Boot启动阶段,按下任意按键,则会进入U-Boot的命令行,等待用户输入并执行相应的命令。通过输入命令,可以下载文件到内存、对Flash/EMMC进行编程、查看内存、操作网络等等;通过环境变量,可以在不修改U-Boot源码的情况下,改变程序行为。好了,让我们开始愉快的学习之旅吧!
2. U-Boot常用命令
- 输入命令时,可以使用命令开头的若干个字母代替命令的全称,比如tftpboot,可以使用t、tf、tftp等代替,只要开头字母没有与其它命令重复即可
- 当执行完一个命令后,若该命令是可重复的,按下回车,则会再次执行。若不想在按下回车时重复执行,请按Ctrl + C
- U-Boot接收的数据都是16进制的,可以省略0x、0X前缀
2.1 查询相关命令
-
help
执行help命令可以看到所有命令的帮助信息,如下图所示,可以看到,U-Boot支持的命令还是很多的,其中
?
是help的别名,即输入?
和输入help的效果是一样的。如果只想查看某个命令的使用方法,可以执行help 命令名
,比如:help tftpboot
-
version
执行version命令可以看到U-Boot的版本信息、编译日期以及使用的交叉编译器的版本
- U-Boot版本:2015.07
- 编译日期:2015.10.07 14:27:57,使用的是TI官方SDK中提供的镜像,大概于2015年10月发布
- 交叉编译器版本:Linaro GCC 4.9-2015.05
-
bdinfo
执行bdinfo命令可以看到硬件板卡相关信息,如下所示
2.2 环境变量相关命令
关于什么是环境变量,请参见下一个章节U-Boot环境变量,切记不要把环境变量和命令搞混了,尽管两者都是一个个字符串,但命令是命令,环境变量是环境变量,U-Boot提供相关的命令可以操作环境变量。
-
查看环境变量
使用print或printenv,会输出系统中所有的环境变量值,如下所示
如果只需要查看某一个环境变量,只需在print或printenv后加上要查看的环境变量名称即可
print bootdelay
-
设置环境变量
setenv ipaddr 192.168.1.100
-
新增环境变量
如果环境变量不存在,设置环境变量的命令,将会新增一个环境变量
setenv autor wdyf
autor不是U-Boot系统默认存在的环境变量,按上述设置,则会新增一个名为autor的环境变量
-
保存环境变量
修改环境变量后,只在本次上电周期内有效,如果希望一直有效,需要执行保存环境变量命令
saveenv
-
删除环境变量
值设置为空,则会删除该环境变量。
setenv ipaddr
环境变量为空,U-Boot会使用程序中指定的默认值
2.3 内存相关命令
-
查看内存命令md
md.b表示以字节为单元查看,md.w表示以2字节为单元查看,md.l表示以4字节为单元查看
address表示要查看的内存起始地址
[# of objects]表示要查看的单元个数,可以省略,则会以上次命令的输入为准
-
修改内存命令
U-Boot支持两个修改内存命令nm和mm,两者都是关于两者的区别如下所示:
-
nm内存修改指令,修改后内存地址不增加,如下所示
- 查看80000000值为FFFF0000
- 输入nm.l 80000000,进入指令交互模式
- 第一次修改值为55AA55AA
- 第二次修改值为12345678
- 第三次修改值为AA55AA55
- 按下Q退出nm指令
- 再次查看80000000值为最后一次的修改AA55AA55,证明nm指令修改内存后,地址不增加,从nm指令修改内存后,左边的地址栏也可看出来
-
mm指令修改内存值后,地址自增一个单元,如下所示
- 查看80000000值为AA55AA55
- 输入mm.l 80000000,进入指令交互模式
- 第一次修改值为10000000
- 第二次修改值为20000000
- 第三次修改值为30000000
- 按下Q退出nm指令
- 再次查看80000000处16个单元的值,发现80000000为10000000,80000004为20000000,80000008为30000000。证明mm指令修改内存后,地址自增,从mm指令修改内存后,左边的地址栏也可看出来
-
-
填充内存命令mw
mw指令用于批量写相同的值到一段连续的内存单元
- address为内存地址
- value为要填充的值
- count为要填充的单元个数
如下操作,将80000000开始的16个4字节单元全部填充为AA55AA55
-
拷贝内存命令cp
cp命令用于拷贝一块内存到目标地址
- source为源地址
- target为目标地址
- count为单元个数
如下操作,将80000000开始的16个4字节单元拷贝到80000040开始的位置
-
内存比较指令cmp
cmp指令用于比较两块内存的值是否相同
- addr1为目标1的地址
- addr2为目标2的地址
- count为比较的单元个数
如下操作,比较80000000开始的16个4字节单元与80000040开始处相同,与80000080开始处不同
2.4 MMC操作相关命令
MMC包括EMMC和SD卡,一般认为EMMC和SD卡是同一个东西。
-
mmc指令
mmc是一系列指令,其后可以跟不同的参数
-
mmc list
列出系统中所有的MMC设备,可以看出,BeagleBone Black开发板目前有两个MMC设备,其中设备0是SD卡。设备号可能与启动设备的选择有关,目前是从SD卡启动的,所以SD卡是0号设备。注意:设备号从0开始
-
mmc rescan
重新扫描MMC设备
-
mmc dev
切换当前选择的MMC设备:
mmc dev [dev] [part]
- dev表示要切换的设备编号
- part表示分区号,可以通过mmc part获得,不写默认为分区0
假设要将当前MMC设备从0号设备SD卡,切换到1号设备EMMC,执行以下命令即可:
mmd dev 1
-
mmc info
列出当前所选择的MMC设备的信息,如下所示,为SD卡的相关信息,可以看出块大小为512字节,容量为7.4GB
-
mmc part
列出当前所选MMC设备的分区信息,如下所示,SD卡有两个分区,注意:分区编号从1开始
- 分区1类型为0x0c,即FAT32类型,且为Boot分区,容量从块2048开始,共计143360个,占70M空间
- 分区2类型为0x83,即Linux分区,容量从块145408开始,共计8013824个,占3913M空间
关于磁盘分区类型,请参考磁盘分区类型
-
mmc read
mmc read addr blk# cnt
读MMC设备的内容到内存中:addr为要导入的目标内存的地址,blk为要读取的起始块号,cnt为要读取的块个数
如下所示,读第一个块到内存0x80000000处,并使用md命令查看内容。
SD卡的第一个块为MBR(Main Boot Record 主引导记录区),对MBR了解的童鞋可以分析一下读出的内容,可以得出SD卡有两个分区,第一个分区为FAT32类型,第二个分区为Linux分区类型。
-
mmc erase/mmc write
mmc erase blk# cnt
擦除当前MMC设备,blk为起始的块号,cnt为要擦除的块个数
mmc write addr blk# cnt
写入当前MMC设备,addr为要写入的内容在内存中的地址,blk为起始的块号,cnt为要写入的块个数
可以使用mmc erase和mmc write命令往SD卡或EMMC中写入内容,很多厂商的CPU其U-Boot是直接写入到SD卡或EMMC的固定块的,这时就可以利用mmc erase和mmc write实现在U-Boot命令行自己升级自己。BeagleBone Black的U-Boot位于第一个FAT32分区内,以FAT文件系统中一个文件的形式存在,故不能按照上述方法升级,但可以使用FAT文件系统相关的命令进行升级,后文再叙述。
-
2.5 文件系统相关命令
-
fstype
fstype命令用于查看指定设备的指定分区的文件系统类型,interface可以取mmc。如下所示:SD卡的第一个分区为fat类型,第二个分区为ext4类型,与实际情况一致。
-
fatinfo
fatinfo命令用于查询指定设备的指定分区的文件系统信息:dev和part为设备号和分区号,可以参见前面mmc命令的
mmc list
和mmc part
的输出信息。如下图所示:SD卡的第1个分区为FAT文件系统类型,输出相应的信息,第2个分区则不是FAT类型,则显示不支持 -
fatls
fatls命令用于查看指定设备的指定分区的指定目录,类似Linux下的ls命令。directory可以省略,默认为根目录。
执行命令查看SD卡第一个分区下的文件列表,有3个文件:MLO、u-boot.img和.ipaddr,与在Windows下查看的情况一致。
-
fatload
fatload命令将位于指定设备指定分区的指定文件拷贝到内存的指定地址中,并且可以通过pos指定文件的起始位置,以及通过bytes指定要拷贝的字节个数。如果bytes和pos未指定,则全部拷贝。如下所示:将SD卡第一个分区内的MLO文件拷贝到内存0x80000000处,由输出信息可知,在8ms时间内拷贝了69400个字节。
-
fatwrite
fatwrite命令用于从内存指定地址,向指定设备的指定分区写入指定文件。首先需要将文件读入到内存的指定地址,并记录读取的文件的大小,然后再执行fatwrite命令。利用fatwrite命令可以实现U-Boot的自我升级,因为BeagleBone Black的U-Boot位于第一个FAT分区下。
-
fatsize
看帮助信息,fatsize命令用于查看指定设备的指定分区指定文件的大小。实测该命令没有作用,可能其它U-Boot版本已经完全支持了。
-
ext4ls
ext4ls与fatls类似,只不过用于文件系统类型是ext4的设备分区。如下所示,查看SD卡的第2个分区,可以看到一个比较完整的根文件系统目录结构,而该分区也正是根文件系统所在的分区
-
ext4load
与fatload类似,这里不再赘述,只不过用于文件系统类型是ext4的设备分区。
-
ext4size
与fatsize类似,这里不再赘述,只不过用于文件系统类型是ext4的设备分区。实测该命令没有作用,可能其它U-Boot版本已经完全支持了。
-
其它
比如ext2ls、ext2load、ext2size其用法与ext4ls、ext4load、ext4size一样,只不过用于文件系统类型为ext2格式的设备分区。
2.6 网络相关命令
做网络相关命令的操作前,请先设置网络相关的环境变量,根据自己的情况
- ipaddr,这里设置IP地址为192.168.2.101,执行
setenv ipaddr 192.168.2.101
- serverip,这里设置为我所使用的VMvare Ubuntu的IP地址,192.168.2.103,执行
setenv serverip 192.168.2.103
- netmask,这里设置为255.255.255.0,执行
setenv netmask 255.255.255.0
-
ping
用于测试网络是否通畅,通常是开发板和开发用的Ubuntu系统之间,如下所示:测试开发板与Ubuntu系统之间网络连接是畅通的
如果在Ubuntu系统中ping处于U-Boot状态下的开发板是ping不通的,因为U-Boot此时在等待命令输入,并不支持处理ping请求
-
nfs
nfs命令通过nfs协议下载文件到开发板的内存中,使用之前需要在Ubuntu系统中支持并启动NFS服务。
- loadAddress为内存的起始地址,即接收文件的内存位置
- hostIPaddr为NFS服务器的IP地址,在这里是指Ubuntu系统的IP地址,即192.168.2.103
- bootfilename即要下载的文件,在NFS服务器目录下的路径
如下图所示,
nfs 80000000 192.168.2.103:/home/book/u-boot.img
通过nfs下载u-boot.img到内存0x80000000处,共传输373784字节数据 -
tftpboot
tftfboot命令和nfs命令类似,也是通过网络将文件下载到开发板内存中,只不过其借助的是tftp协议。使用之前需要在Ubuntu系统中支持并启动TFTP服务
tftpboot命令也可以简写成tftp,只要不存在字母相同的命令即可,在其它版本的U-Boot中,也有叫tftpload的
- loadAddress为内存的起始地址,即接收文件的内存位置
- 经过实际测试:这里只需设置好serverip,不需要输入hostIPaddr,直接输入要下载的文件名字即可。当然,文件需要在Ubuntu系统tftp目录下
如下图所示,
tftpload 80000000 u-boot.img
通过nfs下载u-boot.img到内存0x80000000处
2.7 启动操作相关命令
-
boot
boot命令用于启动Linux系统(别忘了,U-Boot的主要作用是启动Linux系统),由帮助信息可知:boot命令其实就是执行环境变量
bootcmd
定义命令集合。至于什么是环境变量,下文会有专门的介绍。这里先看一下bootcmd的值,如下所示:
看到这里,暂时先放一放,等到环境变量章节我们再仔细分析bootcmd这个环境变量。目前只需知道执行boot命令会按环境变量bootcmd定义的命令集合执行,以此来启动Linux系统。
-
bootz
bootz命令也是用于启动Linux系统的, 其只支持zImage格式的Linux镜像文件。addr 是 Linux 镜像文件在 内存中的位置, initrd 是 initrd 文件在内存中的地址,如果不使用 initrd 的话使用‘-’代替即可, fdt 就是设备树文件在 内存中的地址。
实际使用的过程中,通常是先把Linux镜像文件和设备树文件使用网络相关的命令或者文件系统相关的命令,从网络或存储设备中拷贝到内存中,然后使用bootz命令启动。
如果不使用设备树和initrd文件,则只需传递addr即可。
-
bootm
bootm命令的参数个数也可以用于启动Linux系统, 其支持uImage格式的Linux镜像文件
- 如果不使用设备树,格式:
bootm addr
,其中addr为Linux镜像文件在内存中的位置 - 如果使用设备树或再加上initrd,格式:
bootm [addr [initrd[:size]] [fdt]]
,同bootz,这里不再赘述
- 如果不使用设备树,格式:
2.8 实用工具命令
-
reset
reset命令用于重启U-Boot
-
go
go命令用于执行内存中的可执行文件,addr为内存地址,go命令会跳到该地址处并执行位于此处的可执行文件。此外,还可以通过arg向可执行文件传递参数。通常情况下,会将可执行文件通过网络下载到内存或者从MMC设备拷贝到内存,然后再调用go命令执行。
-
run
run命令用于执行定义在环境变量中的命令集合,在学习boot命令时,执行boot命令,其实就是
run bootcmd
,即执行环境变量bootcmd定义的命令集合 -
i2c
如帮助信息所示,i2c命令和mmc命令一样,是一系列的命令集合
-
i2c bus
列出i2c总线的信息,如下所示:列出了BeagleBone Black开发板支持3个i2c总线
-
i2c dev
用于显示当前选中的i2c总线编号或者设置当前的i2c总线编号,如下所示:反复切换几次当前选中的i2c总线,并显示
-
i2c probe
列出当前选中的i2c总线下,所挂接的i2c从设备,以设备地址的形式显示。如下所示:i2c0总线下挂接了4个设备,
-
i2c读写相关
-
i2c md
用于显示i2c设备中寄存器的内容,类似于内存操作命令md,只不过只能以字节为单元,不似md有md.b、md.w、md.l之分。
i2c md chip address [# of objects]
,chip为i2c设备的地址,通过i2c probe可得出;address为i2c设备要读取的寄存器首地址;of objects为寄存器的个数。如下所示,为读取当前i2c总线,即i2c0下所挂接的地址为0x24的设备,从地址0x00开始的0x10个字节的内容。 -
i2c mm
用于修改i2c设备中寄存器的内容,且修改后寄存器地址递增,类似于内存操作命令mm,只不过只能以字节为单元,不似mm有mm.b、mm.w、mm.l之分。
i2c mm chip address
,chip为i2c设备的地址,通过i2c probe可得出;address为i2c设备的要修改的寄存器地址 -
i2c nm
用于修改i2c设备中寄存器的内容,且修改后寄存器地址保持不变,类似于内存操作命令nm,只不过只能以字节为单元,不似nm有nm.b、nm.w、nm.l之分。
i2c nm chip address
,chip为i2c设备的地址,通过i2c probe可得出;address为i2c设备的要修改的寄存器地址 -
i2c mw
用于批量填充i2c设备中寄存器的内,类似于内存操作命令mw,只不过只能以字节为单元,不似mw有mw.b、mw.w、mw.l之分。
i2c mw chip address value [count]
,chip为i2c设备的地址,通过i2c probe可得出;address为要填充的i2c设备的寄存器首地址;value为要填充的数据;count为要填充的寄存器个数,不写则默认为1 -
i2c read
用于读取i2c设备的寄存器内容到内存中
i2c read chip address length memaddress
,chip为i2c设备的地址,通过i2c probe可得出;address为i2c设备要读取的寄存器首地址;length为要读取的个数;memaddress为内存地址 -
i2c write
用于将内存中的一块数据,写入到i2c设备的寄存器中
i2c write memaddress chip address length [-s]
,memaddress为内存地址,chip为i2c设备的地址,通过i2c probe可得出;address为i2c设备要写入的寄存器首地址;length为要写入的个数;-s选项表示the -s option selects bulk write in a single transaction
因为I2C设备中可能有重要的数据,这里不做实验进行演示了。实际的工作中,如果需要调试i2c设备,可以使用读写命令进行调试,后续博客会有演示。
-
-
3. U-Boot环境变量
3.1 环境变量的作用
在U-Boot中存在环境变量,那什么是环境变量,它又有什么作用呢?
U-Boot的环境变量可以理解为U-Boot的全局配置,它提供一种方式,可以在不修改U-Boot程序的前提下,改变系统的行为,可以理解为环境变量是一种软件解耦设计。
eg1:修改bootdelay环境变量,达到修改U-Boot自动启动倒计时时间的功能
eg2:修改开发板ip地址,只需修改ipaddr环境变量即可
3.2 环境变量有哪些
U-Boot常用的环境变量有以下几个:
-
netmask
设置开发板的子网掩码
-
ipaddr
设置开发板的IP地址
-
serverip
设置服务端的IP地址,常用于通过tftp和nfs服务器下载文件
-
bootfile
默认的下载文件
-
bootdelay
U-Boot自动启动倒计时时间,以秒为单位
-
bootcmd
U-Boot自动启动执行的命令,通常是加载内核到内存,并跳转执行
后续有文章会专门介绍这个环境变量
-
bootargs
U-Boot传递给内核的启动参数
后续有文章会专门介绍这个环境变量
-
stdin
标准输入设备
-
stdout
标准输出设备
-
stderr
标准错误设备
-
baudrate
串口控制台的波特率
3.3 环境变量的操作
操作环境变量,也是通过U-Boot提供的与环境变量相关的命令,这里把U-Boot常用命令中环境变量变量相关命令一节再放一遍,加深一下印象,如果你已经掌握,请略过。
-
查看环境变量
使用print或printenv,会输出系统中所有的环境变量值,如下所示
如果只需要查看某一个环境变量,只需在print或printenv后加上要查看的环境变量名称即可
print bootdelay
-
设置环境变量
setenv ipaddr 192.168.1.100
-
新增环境变量
如果环境变量不存在,设置环境变量的命令,将会新增一个环境变量
setenv autor wdyf
autor不是U-Boot系统默认存在的环境变量,按上述设置,则会新增一个名为autor的环境变量
-
保存环境变量
修改环境变量后,只在本次上电周期内有效,如果希望一直有效,需要执行保存环境变量命令
saveenv
-
删除环境变量
值设置为空,则会删除该环境变量。
setenv ipaddr
环境变量为空,U-Boot会使用程序中指定的默认值
3.4 环境变量的保存位置
环境变量设置之后,如果不使用saveenv
命令保存一下,则再次上电将会丢失当前的设置。所以,环境变量是需要存储在非易失性存储器里,一般为MMC设备、Flash等,视U-Boot配置而定。后续会有专门的文章讲解环境变量的存储,这里先暂且略过,只需知道环境变量最终是需要保存在某个位置的。
[外链图片转存中…(img-xeUYKvRO-1710482327589)]
-
设置环境变量
setenv ipaddr 192.168.1.100
-
新增环境变量
如果环境变量不存在,设置环境变量的命令,将会新增一个环境变量
setenv autor wdyf
autor不是U-Boot系统默认存在的环境变量,按上述设置,则会新增一个名为autor的环境变量
-
保存环境变量
修改环境变量后,只在本次上电周期内有效,如果希望一直有效,需要执行保存环境变量命令
saveenv
-
删除环境变量
值设置为空,则会删除该环境变量。
setenv ipaddr
环境变量为空,U-Boot会使用程序中指定的默认值
3.4 环境变量的保存位置
环境变量设置之后,如果不使用saveenv
命令保存一下,则再次上电将会丢失当前的设置。所以,环境变量是需要存储在非易失性存储器里,一般为MMC设备、Flash等,视U-Boot配置而定。后续会有专门的文章讲解环境变量的存储,这里先暂且略过,只需知道环境变量最终是需要保存在某个位置的。