【IMX6ULL学习笔记】六、U-BOOT环境变量与Linux启动
一、环境变量 bootcmd
bootcmd 和 bootagrs 是采用类似 shell 脚本语言编写的,里面很多的变量引用,这些变量都是环境变量,很多是 NXP 定义的。文件mx6ull_alientek_emmc.h 中的宏 CONFIG_EXTRA_ENV_SETTINGS 保存着这些环境变量的默认值,内容如下:
#if defined(CONFIG_SYS_BOOT_NAND)
#define CONFIG_EXTRA_ENV_SETTINGS \
CONFIG_MFG_ENV_SETTINGS \
"panel=TFT43AB\0" \
"fdt_addr=0x83000000\0" \
"fdt_high=0xffffffff\0" \
......
"bootz ${loadaddr} - ${fdt_addr}\0"
#else
#define CONFIG_EXTRA_ENV_SETTINGS \
CONFIG_MFG_ENV_SETTINGS \
"script=boot.scr\0" \
"image=zImage\0" \
"console=ttymxc0\0" \
"fdt_high=0xffffffff\0" \
"initrd_high=0xffffffff\0" \
"fdt_file=undefined\0" \
......
"findfdt="\
"if test $fdt_file = undefined; then " \
"if test $board_name = EVK && test $board_rev = 9X9; then " \
"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
"if test $board_name = EVK && test $board_rev = 14X14; then " \
"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
"if test $fdt_file = undefined; then " \
"echo WARNING: Could not determine dtb to use; fi; " \
"fi;\0" \
宏 CONFIG_EXTRA_ENV_SETTINGS 是个条件编译语句,使用 NAND 和 EMMC 的时候宏 CONFIG_EXTRA_ENV_SETTINGS 的值是不同的。用户可以根据需求自定义修改这些默认值,如前面移植 LCD 时修改默认屏幕为"panel=KODO_TFT4384\0"。
bootcmd 的默认值就是 CONFIG_BOOTCOMMAND,bootargs 的默认值就是 CONFIG_BOOTARGS。
在 mx6ull_alientek_emmc.h 文件中通过设置宏 CONFIG_BOOTCOMMAND 来设置 bootcmd 的默认值,NXP 官方设置的 CONFIG_BOOTCOMMAND 值如下:
#define CONFIG_BOOTCOMMAND \
"run findfdt;" \
"mmc dev ${mmcdev};" \
"mmc dev ${mmcdev}; if mmc rescan; then " \
"if run loadbootscript; then " \
"run bootscript; " \
"else " \
"if run loadimage; then " \
"run mmcboot; " \
"else run netboot; " \
"fi; " \
"fi; " \
"else run netboot; fi"
使用了类似 shell 脚本语言的方式来编写。
第 2 行:run findfdt;使用的是 uboot 的 run 命令来运行 findfdt,findfdt 是 NXP 自行添加的环境变量。findfdt 是用来查找开发板对应的设备树文件(.dtb)。IMX6ULL EVK 的设备树文件为 imx6ull-14x14-evk.dtb,findfdt 内容如下:
"findfdt="\
"if test $fdt_file = undefined; then " \
"if test $board_name = EVK && test $board_rev = 9X9; then " \
"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
"if test $board_name = EVK && test $board_rev = 14X14; then " \
"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
"if test $fdt_file = undefined; then " \
"echo WARNING: Could not determine dtb to use; fi; " \
"fi;\0" \
findfdt 里面用到的变量有 fdt_file,board_name,board_rev,这三个变量内容如下:
fdt_file=undefined,board_name=EVK,board_rev=14X14
findfdt 做的事情就是判断,fdt_file 是否为 undefined,如果 fdt_file 为 undefined 的话那就要根据板子信息得出所需的.dtb 文件名。
此时 fdt_file 为 undefined,所以根据 board_name 和 board_rev 来判断实际所需的.dtb 文件,如果 board_name 为 EVK 并且 board_rev=9x9 的话 fdt_file 就为 imx6ull-9x9-evk.dtb。如果 board_name 为 EVK 并且 board_rev=14x14 的话 fdt_file 就设置为 imx6ull-14x14-evk.dtb。因此 IMX6ULL EVK 板子的设备树文件就是 imx6ull-14x14-evk.dtb,因此 run findfdt 的结果就是设置 fdt_file 为 imx6ull-14x14-evk.dtb。
第 3 行:mmc dev ${mmcdev}用于切换 mmc 设备,mmcdev 为 1,因此这行代码就是:mmcdev 1,也就是切换到 EMMC 上。
第 4 行:先执行 mmc dev ${mmcdev}切换到 EMMC 上,然后使用命令 mmc rescan 扫描看有没有 SD 卡或者 EMMC 存在,如果没有的话就直接跳到 216 行,执行 run netboot,netboot 也是一个自定义的环境变量,这个变量是从网络启动 Linux 的。如果 mmc 设备存在的话就从 mmc 设备启动。
第 5 行:运行 loadbootscript 环境变量,此环境变量内容如下:
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,script= boot.scr,因此展开以后就是:
loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;
loadbootscript 就是从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0X80800000 处。但是 mmc1 的分区 1 中没有 boot.src 这个文件,可以使用命令“ls mmc 1:1”查看一下 mmc1 分区 1 中的所有文件,看看有没有 boot.src 这个文件。
第 6 行:如果加载 boot.src 文件成功的话就运行 bootscript 环境变量,bootscript 的内容如下:
bootscript=echo Running bootscript from mmc ...;
source
因为 boot.src 文件不存在,所以 bootscript 也就不会运行。
第 8 行:如果 loadbootscript 没有找到 boot.src 的话就运行环境变量 loadimage,环境变量 loadimage 内容如下:
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,image = zImage,展开以后就是:
loadimage=fatload mmc 1:1 0x80800000 zImage
可以看出 loadimage 就是从 mmc1 的分区中读取 zImage 到内存的 0X80800000 处,而 mmc1 的分区 1 中存在 zImage。
第 9 行:加载 linux 镜像文件 zImage 成功以后就运行环境变量 mmcboot,否则的话运行 netboot 环境变量。mmcboot 环境变量如下:
"mmcboot=echo Booting from mmc ...; " \
"run mmcargs; " \
"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
"if run loadfdt; then " \
"bootz ${loadaddr} - ${fdt_addr}; " \
"else " \
"if test ${boot_fdt} = try; then " \
"bootz; " \
"else " \
"echo WARN: Cannot load the DT; " \
"fi; " \
"fi; " \
"else " \
"bootz; " \
"fi;\0" \
第 1 行:输出信息“Booting from mmc ...”。
第 2 行:运行环境变量 mmcargs,mmcargs 用来设置 bootargs,后面分析 bootargs 的时候再学习。
第 3 行:判断boot_fdt是否为 yes 或者 try,根据 uboot 输出的环境变量信息可知 boot_fdt=try。因此会执行第 4 行的语句。
第 4 行:运行环境变量 loadfdt,环境变量 loadfdt 定义如下:
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
展开以后就是:
loadfdt=fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb
因此 loadfdt 的作用就是从 mmc1 的分区 1 中读取 imx6ull-14x14-evk.dtb 文件并放到 0x83000000 处。
第 5 行:如果读取.dtb 文件成功的话那就调用命令 bootz 启动 linux,调用方法如下:
bootz ${loadaddr} - ${fdt_addr};
展开就是:
bootz 0x80800000 - 0x83000000 (注意‘-’前后要有空格)
至此 Linux 内核启动,如此复杂的设置就是为了从 EMMC 中读取 zImage 镜像文件和设备树文件。经过分析,浓缩出来的仅仅是 4 行精华:
mmc dev 1 //切换到 EMMC
fatload mmc 1:1 0x80800000 zImage //读取 zImage 到 0x80800000 处
fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb //读取设备树到 0x83000000 处
bootz 0x80800000 - 0x83000000 //启动 Linux
二、环境变量 bootargs
bootargs 保存着 uboot 传递给 Linux 内核的参数,在上一小节讲解 bootcmd 的时候说过,bootargs 环境变量是由 mmcargs 设置的,mmcargs 环境变量如下:
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
其中 console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,因此将 mmcargs 展开以后就是:
mmcargs=setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
可以看出环境变量 mmcargs 就是设置 bootargs 的值为“console= ttymxc0, 115200 root=/dev/mmcblk1p2 rootwait rw”,bootargs 就是设置了很多的参数的值,这些参数 Linux 内核会使用到,常用的参数有:
1、console 参数
console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这样我们就可以在电脑上通过 SecureCRT 来和 linux 交互了。这里设置 console 为 ttymxc0,因为 linux 启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,在 Linux 下,一切皆文件。
ttymxc0 后有个“,115200”,是设置串口波特率,console=ttymxc0,115200 综合起来就是设置 ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。
2、root 参数
root 用来设置根文件系统的位置,root=/dev/mmcblk1p2 用于指明根文件系统存放在 mmcblk1 设备的分区 2 中。EMMC 版本的核心板启动 linux 以后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1 和/dev/mmcblk1p2 这样的文件,其中/dev/mmcblkx(x=0~n)表示 mmc 设备,而/dev/mmcblkxpy(x=0n,y=1n)表示 mmc 设备 x 的分区 y。在 I.MX6U-ALPHA 开发板中/dev/mmcblk1 表示 EMMC,而/dev/mmcblk1p2 表示
EMMC 的分区 2。
root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话 mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加 rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
3、rootfstype 参数
此选项一般配置 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为 ext 格式的话此选项无所谓。如果根文件系统是 yaffs、jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。
4、mem 参数
mem=xxM 指定内存的大小,不是必须的
5、ramdisk_size 参数(一般不用)
ramdisk 即虚拟内存盘,是通过软件将部分内存模拟为硬盘来使用的一种技术。
ramdisk=xx //不推荐
ramdisk_size=xx //推荐
上面这两个都可以告诉 ramdisk 驱动,创建的 ramdisk 的 size,默认情况下是4m(s390默认8M),可以查看 Documentation/ramdisk.txt 找到相关的描述,不过 ramdisk=xx 在新版的内核都已经没有提了,不推荐使用。
6、initrd、noinitrd 参数
当没有使用 ramdisk 启动系统的时候,需要使用 noinitrd 这个参数,但是如果使用了的话,就需要指定 initrd=r_addr,size,r_addr 表示 initrd 在内存中的位置,size 表示 initrd 的大小。
7、 init 参数
init 指定内核启动后进入系统中运行的第一个脚本,一般 init=/linuxrc, 或者 init=/etc/preinit,preinit 的内容一般是创建 console null 设备节点、运行 init 程序、挂载一些文件系统等操作。
注:很多初学者以为 init=/linuxrc 是固定写法,其实不然,/linuxrc 指的是/目录下面的 linuxrc 脚本,一般是一个链接罢了。
此参数一般不需要设置,因为内核源码中有一定的启动项,内核如何启动 init 进程在内核的 init/main.c 中 static init noinline init_post(void)。
若出现 No init found Try passing init= 这种错误的时候可能原因是内核找不到文件系统或者无法挂载文件系统,不用考虑是init参数设置的错误。
Eg:kernelpanic-nosyncing :no init found.Tying init= option to know 此错误的原因是因为 root=/dev/mtdblock4 但是 nand 中没有烧写文件系统,而不是因为 init 参数设置不对。
8、mtdparts 参数
mtdparts=fc000000.nor_flash:1920k(linux),128k(fdt),20M(ramdisk),4M(jffs2),38272k(user),256k(env),384k(uboot)
要想这个参数起作用,内核中的 mtd 驱动必须要支持,即内核配置时需要选上:
Device Drivers --->
Memory Technology Device (MTD) support--->
Command line partition table parsing
mtdparts 的格式如下:
mtdparts=<mtddef>[;<mtddef]
mtdparts 可以由一个或多个 mtddef 组成。每个mtddef定义如下:
<mtddef> := <mtd-id>:<partdef>[,<partdef>]
mtddef 可以由 mtd-id 和一个或多个 partdef 组成,每个 partdef 定义如下:
<partdef> := <size>[@offset][<name>][ro]
参数含义如下:
<mtd-id> := unique id used in mapping driver/device
<size> := standard linux memsize OR "-" to denote all remaining space
<name> := (NAME)
因此在使用的时候,需要按照下面的格式来设置:
mtdparts=mtd-id:<size1>@<offset1>(<name1>),<size2>@<offset2>(<name2>)
这里需要注意的地方如下:
(1) mtd-id 必须跟当前平台 flash 的 mtd-id 一致,不然整个 mtdparts 会失效。
(2)size 在设置的时候,可以为实际的 size(xxM,xxK,xx),也可以为‘-’,表示剩余的所有空间,相关信息可以查看 drivers/mtd/cmdlinepart.c 中注释找到相关描述。
mtdparts 参数格式及当前平台的 flash 的 mtd-id 如何查找?
先看看 linux-4.14.63/Documentation 里面怎么说的:
~/opt/linux-4.14.63/Documentation$ grep mtdparts . -rn
./admin-guide/kernel-parameters.txt:2408: mtdparts= [MTD]
./block/cmdline-partition.txt:10:The format for the command line is just like mtdparts:
./admin-guide/kernel-parameters.txt 里面写到
mtdparts= [MTD]
See drivers/mtd/cmdlinepart.c
直接看源码 drivers/mtd/cmdlinepart.c
先看看 Makefile ,确定什么时候会编译 cmdlinepart.c,可知需要配置 CONFIG_MTD_CMDLINE_PARTS=y 开启内核对 mtdparts 参数的支持:
obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
接着看源码,开头注释里面写了 mtdparts 格式:
* The format for the command line is as follows:
*
* mtdparts=<mtddef>[;<mtddef]
* <mtddef> := <mtd-id>:<partdef>[,<partdef>]
* <partdef> := <size>[@<offset>][<name>][ro][lk]
* <mtd-id> := unique name used in mapping driver/device (mtd->name)
* <size> := standard linux memsize OR "-" to denote all remaining space
* size is automatically truncated at end of device
* if specified or truncated size is 0 the part is skipped
* <offset> := standard linux memsize
* if omitted the part will immediately follow the previous part
* or 0 if the first part
* <name> := '(' NAME ')'
* NAME will appear in /proc/mtd
*
* <size> and <offset> can be specified such that the parts are out of order
* in physical memory and may even overlap.
mtd-id 与 SPI Flash 设备注册名保存一致。支持设备树的 Kernel,mtd-id 写法一般为 spi0.0,可通过 sysfs 查看,如:
/sys/devices/platform/soc/1c68000.spi/spi_master/spi0/spi0.0
mtdparts 写法举例:
mtdparts=spi0.0:512k(u-boot)ro,64k(env),64k(dtb),5M(kernel),8M(rootfs),-(data)
mtdparts=spi0.0:512k@0(u-boot)ro,64k@512k(env), 64k@576k(dtb) 5M@640k(kernel),8M@5760k(rootfs),-(data)
mtdparts=spi0.0:512k@0(u-boot)ro,64k@0x80000(env)ro,64k@0x90000(dtb),5M@a0000(kernel),8M@5a0000(rootfs),-(data)
9、ip 参数
指定系统启动之后网卡的 ip 地址,如果你使用基于 nfs 的文件系统,那么必须要有这个参数,其他的情况下就看自己的喜好。设置ip有两种方法:
ip = ip addr
ip=ip addr:server ip addr:gateway:netmask::which netcard:off
这两种方法可以用,不过很明显第二种要详细很多,请注意第二种中 which netcard 是指开发板上的网卡,而不是主机上的网卡。
说明一下,如果用 NFS 或者为了 ping 通的话只需设置三个就可以了,ipaddr、serverip、eth就好了。
10、常见的几种组合形式
说完常见的几种 bootargs,来讨论平常经常使用的几种组合:
1). 假设文件系统是 ramdisk,且直接就在内存中,bootargs 的设置应该如下:
setenv bootargs ‘initrd=0x32000000,0xa00000 root=/dev/ram0 console=ttySAC0mem=64M init=/linuxrc’
2). 假设文件系统是 ramdisk,且在 flash 中,bootargs 的设置应该如下:
setenv bootargs ‘mem=32M console=ttyS0,115200 root=/dev/ram rw init=/linuxrc’
注意这种情况下应该在 bootm 命令中指定 ramdisk 在 flash 中的地址,如:
bootm kernel_addr ramdisk_addr(fdt_addr)
3). 假设文件系统是 jffs2 类型的,且在 flash 中,bootargs 的设置应该如下:
setenv bootargs ‘mem=32M console=ttyS0,115200 noinitrd root=/dev/mtdblock2 rwrootfstype=jffs2 init=/linuxrc’
4). 假设文件系统是基于 nfs 的,bootargs 的设置应该如下:
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5:192.168.0.3:192.168.0.3:255.255.255.0::eth0:off’
或者
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5’
上面就是我们经常使用的几种bootargs的组合,老实说,bootargs非常非常的灵活,所以设置的方法有很多中形式,具体的还应该根据你的平台具体的情况来设置。
bootargs 还有一些其他选项,后面遇到其他选项的话再讲解。
三、uboot 启动 Linux 测试
1、从 EMMC 启动 Linux 系统
从 EMMC 启动也就是将编译出来的 Linux 镜像文件 zImage 和设备树文件保存在 EMMC 中,uboot 从 EMMC 中读取这两个文件并启动。
先检查一下 EMMC 的分区 1 中有没有 zImage 文件和设备树文件,输入命令“ls mmc 1:1”,结果如图所示:
ls mmc 1:1
从图中可以看出,此时 EMMC 分区 1 中存在 zimage 和 imx6ull-alientek-emmc.dtb 两个文件,所以我们可以测试新移植的 uboot 能不能启动 linux 内核。
设置 bootargs 和 bootcmd 这两个环境变量,设置如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; bootz 80800000 - 83000000;'
saveenv
设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如图所示的启动信息:
2、从网络启动 Linux 系统
从网络启动 linux 系统的唯一目的就是为了调试。我们可以设置 linux 从网络启动,也就是将 linux 镜像文件和根文件系统都放到 Ubuntu 下某个指定的文件夹中,这样每次重新编译 linux 内核或者某个 linux 驱动以后只需要使用 cp 命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写 EMMC,这样就加快了开发速度。
可以通过 nfs 或者 tftp 从 Ubuntu 中下载 zImage 和设备树文件,根文件系统的话也可以通过 nfs 挂载。
使用 tftp 从 Ubuntu 中下载 zImage 和设备树文件,前提是要将 zImage 和设备树文件放到 Ubuntu 下的 tftp 目录中。
设置 bootargs 和 bootcmd 这两个环境变量,设置如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-kodo-emmc.dtb;bootz 80800000 - 83000000'
saveenv
设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核,先通过tftp下载 zImage 和 imx6ull-alientek-emmc.dtb 这两个文件,如图所示:
下载完成以后就是启动 Linux 内核,启动过程如图所示:
简单总结一下 uboot 移植的过程:
①、不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 dmeo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、linux kernel 和 rootfs 等,最终制作好 BSP 包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。
②、我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo 板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。
③、一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot 的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。
④、在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。