一般来讲启动一个系统所需的bootloader(SPL/MLO、u-boot.img)和根文件系统(/boot下包含内核zImage)要么是放在NAND Flash,或者是SD卡,或者是eMMC,或者是USB中,那么还有一种方式,就是所需要的这些文件全部都不在板子上,而是放在其他的电脑或者说服务器上。这样即使板子上没有eMMC也照样跑系统!而且由于根文件系统在NFS服务器上,使得板子上的系统的“硬盘空间”变得十分充足!
关于一个系统的启动流程相关参考资料
ARM-Linux嵌入式系统启动流程
内核源码的说明文档,关于NFS启动一些需要注意的
https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt
1、ROM-Code如何从串口获取bootloader?
对于BBB板子来说,当没有按住boot按键时,启动顺序是MMC1、MMC0、UART0、USB0,而按住boot按键时启动顺序为SPI0、MMC0、USB0、UART0。由于板子的MMC1已经接有eMMC,所以要想让ROM-Code从串口启动,那么断电时,拔掉SD卡,然后按住boot按键,再给板子上电,如果连接上了串口调试线,此时PC机串口应该不停的收到字符CCCCCC,这就表示ROM-Code在向串口请求SPL文件(PC机的串口调试工具推荐SecureCRT)。此时用Xmoden协议将SPL文件从串口发送过去,接着串口上会显示传送进度和速度。传送完后串口会又会收到字符CCCCCC,这就表示传送过去的SPL已经运行,并向串口请求u-boot.img文件,此时用Ymoden协议将u-boot.img文件传送过去,接下来从串口可以看到u-boot已经启动,但由于没有内核和根文件系统,所以u-boot自动停止,显示出命令行交互界面#u-boot:
至此ROM-Code和SPL任务完成,接下来控制权转交给u-boot。
2、u-boot如何从TFTP服务器获取内核镜像,从NFS服务器挂载根文件系统?
首先要在同一局域网内的一台PC机上配置TFTP和NFS服务器,比如可以在虚拟机ubuntu12.04中配置TFTP和NFS服务器,注意,虚拟机和宿主机一定要选择桥接模式,以使虚拟机和目标板获得同一网段的IP。
相关参考:
TFTP服务器配置
NFS服务器配置
以上配置完成后都进行都本机测试tftp localhost、mount -t nfs -o nolock localhost:/nfs/dir/ /mnt/nfs,或者用开发板来测试,以确保配置没有问题。
准备工作都做好之后,现在u-boot#命令交互等待用户输入,该如何配置环境变量让u-boot通过TFTP协议下载内核到内存,下载到哪个位置?传递什么参数给内核来让他知道根文件系统在哪里?下面来对此进行分析:
进入u-boot后,输入printenv,回车,得如下输出,
U-Boot# printenv arch=arm autoconf=off baudrate=115200 board=am335x board_name=A335BNLT board_rev=t\ ue boot_fdt=try bootcmd=gpio set 53; i2c mw 0x24 1 0x3e; run findfdt; setenv mmcdev 0; setenv bootpart 0:1; run mmcboot;gpio clear 56; gpio clear 55; gpio clear 54; setenv mmcdev 1; setenv bootpart 1:1; run mmcboot;run nandboot; bootcount=2 bootdelay=1 bootenv=uEnv.txt bootfile=zImage bootm_size=0x10000000 bootpart=0:2 bootscript=echo Running bootscript from mmc ...; source ${loadaddr} console=ttyO0,115200n8 cpu=armv7 device=eth0 dfu_alt_info_emmc=rawemmc mmc 0 3751936 dfu_alt_info_mmc=boot part 0 1;rootfs part 0 2;MLO fat 0 1;MLO.raw mmc 0x100 0x100;u-boot.img.raw mmc 0x300 0x400;spl-os-args.raw mmc 0x80 0x80;spl-os-image.raw mmc 0x900 0x2000;spl-os-args fat 0 1;spl-os-image fat 0 1;u-boot.img fat 0 1;uEnv.txt fat 0 1 dfu_alt_info_nand=SPL part 0 1;SPL.backup1 part 0 2;SPL.backup2 part 0 3;SPL.backup3 part 0 4;u-boot part 0 5;u-boot-spl-os part 0 6;kernel part 0 8;rootfs part 0 9 dfu_alt_info_ram=kernel ram 0x80200000 0xD80000;fdt ram 0x80F80000 0x80000;ramdisk ram 0x81000000 0x4000000 eth1addr=c8:a0:30:ac:7f:d1 ethact=cpsw ethaddr=c8:a0:30:ac:7f:cf fdt_addr_r=0x88000000 fdtaddr=0x88000000 fdtdir=/dtbs fdtfile=undefined findfdt=if test $board_name = A335BONE; then setenv fdtfile am335x-bone.dtb; setenv fdtbase am335x-bone; fi; if test $board_name = A335BNLT; then setenv fdtfile am335x-boneblack.dtb; setenv fdtbase am335x-boneblack; fi; if test $board_name = A33515BB; then setenv fdtfile am335x-evm.dtb; fi; if test $board_name = A335X_SK; then setenv fdtfile am335x-evmsk.dtb; fi; if test $fdtfile = undefined; then echo WARNING: Could not determine device tree to use; fi; gw_ip=192.168.1.1 importbootenv=echo Importing environment from mmc ...; env import -t -r $loadaddr $filesize kernel_addr_r=0x82000000 loadaddr=0x82000000 loadbootenv=load mmc ${bootpart} ${loadaddr} ${bootenv} loadbootscript=load mmc ${bootpart} ${loadaddr} ${scriptfile}; loadfdt=echo loading ${fdtdir}/${fdtfile} ...; load mmc ${bootpart} ${fdtaddr} ${fdtdir}/${fdtfile} loadimage=load mmc ${bootpart} ${loadaddr} ${bootdir}/${bootfile} loadramdisk=load mmc ${mmcdev} ${rdaddr} ramdisk.gz loadrd=load mmc ${bootpart} ${rdaddr} ${bootdir}/${rdfile}; setenv rdsize ${filesize} mmcargs=setenv bootargs console=${console} ${optargs} ${cape_disable} ${cape_enable} root=${mmcroot} rootfstype=${mmcrootfstype} ${cmdline} mmcboot=mmc dev ${mmcdev}; if mmc rescan; then gpio set 54;echo SD/MMC found on device ${mmcdev};setenv bootpart ${mmcdev}:1; echo Checking for: /uEnv.txt ...;if test -e mmc ${bootpart} /uEnv.txt; then if run loadbootenv; then gpio set 55;echo Loaded environment from ${bootenv};run importbootenv;fi;if test -n ${cape}; then if test -e mmc ${bootpart} ${fdtdir}/${fdtbase}-${cape}.dtb; then setenv fdtfile ${fdtbase}-${cape}.dtb; fi; echo using: $fdtfile...; fi; echo Checking if uenvcmd is set ...;if test -n ${uenvcmd}; then gpio set 56; echo Running uenvcmd ...;run uenvcmd;fi;echo Checking if client_ip is set ...;if test -n ${client_ip}; then if test -n ${dtb}; then setenv fdtfile ${dtb};echo using ${fdtfile} ...;fi;gpio set 56; echo Running nfsboot ...;run nfsboot;fi;fi; echo Checking for: /${script} ...;if test -e mmc ${bootpart} /${script}; then gpio set 55;setenv scriptfile ${script};run loadbootscript;echo Loaded script from ${scriptfile};gpio set 56; run bootscript;fi; echo Checking for: /boot/${script} ...;if test -e mmc ${bootpart} /boot/${script}; then gpio set 55;setenv scriptfile /boot/${script};run loadbootscript;echo Loaded script from ${scriptfile};gpio set 56; run bootscript;fi; echo Checking for: /boot/uEnv.txt ...;for i in 1 2 3 4 5 6 7 ; do setenv mmcpart ${i};setenv bootpart ${mmcdev}:${mmcpart};if test -e mmc ${bootpart} /boot/uEnv.txt; then gpio set 55;load mmc ${bootpart} ${loadaddr} /boot/uEnv.txt;env import -t ${loadaddr} ${filesize};echo Loaded environment from /boot/uEnv.txt;if test -n ${dtb}; then setenv fdtfile ${dtb};echo Using: dtb=${fdtfile} ...;fi;echo Checking if uname_r is set in /boot/uEnv.txt...;if test -n ${uname_r}; then gpio set 56; echo Running uname_boot ...;setenv mmcroot /dev/mmcblk${mmcdev}p${mmcpart} ro;run uname_boot;fi;fi;done;fi; mmcdev=0 mmcloados=run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdtaddr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi; mmcpart=1 mmcroot=/dev/mmcblk0p2 ro mmcrootfstype=ext4 rootwait fixrtc mtdids=nand0=omap2-nand.0 mtdparts=mtdparts=omap2-nand.0:128k(SPL),128k(SPL.backup1),128k(SPL.backup2),128k(SPL.backup3),1792k(u-boot),128k(u-boot-spl-os),128k(u-boot-env),5m(kernel),-(rootfs) nandargs=setenv bootargs console=${console} ${optargs} root=${nandroot} rootfstype=${nandrootfstype} nandboot=echo Booting from nand ...; run nandargs; nand read ${fdtaddr} u-boot-spl-os; nand read ${loadaddr} kernel; bootz ${loadaddr} - ${fdtaddr} nandroot=ubi0:rootfs rw ubi.mtd=7,2048 nandrootfstype=ubifs rootwait=1 netargs=setenv bootargs console=${console} ${optargs} root=/dev/nfs nfsroot=${serverip}:${rootpath},${nfsopts} rw ip=dhcp netboot=echo Booting from network ...; setenv autoload no; dhcp; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; run netargs; bootz ${loadaddr} - ${fdtaddr} netmask=255.255.255.0 nfs_options=,vers=3 nfsargs=setenv bootargs console=${console} ${optargs} ${cape_disable} ${cape_enable} root=/dev/nfs rw rootfstype=${nfsrootfstype} nfsroot=${nfsroot} ip=${ip} ${cmdline} nfsboot=echo Booting from ${server_ip} ...; setenv nfsroot ${server_ip}:${root_dir}${nfs_options}; setenv ip ${client_ip}:${server_ip}:${gw_ip}:${netmask}:${hostname}:${device}:${autoconf}; setenv autoload no; setenv serverip ${server_ip}; setenv ipaddr ${client_ip}; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} dtbs/${fdtfile}; run nfsargs; bootz ${loadaddr} - ${fdtaddr} nfsopts=nolock nfsrootfstype=ext4 rootwait fixrtc partitions=uuid_disk=${uuid_gpt_disk};name=rootfs,start=2MiB,size=-,uuid=${uuid_gpt_rootfs} ramargs=setenv bootargs console=${console} ${optargs} root=${ramroot} rootfstype=${ramrootfstype} ramboot=echo Booting from ramdisk ...; run ramargs; bootz ${loadaddr} ${rdaddr} ${fdtaddr} ramdisk_addr_r=0x88080000 ramroot=/dev/ram0 rw ramrootfstype=ext2 rdaddr=0x88080000 root_dir=/home/userid/targetNFS rootpath=/export/rootfs script=boot.scr scriptfile=${script} server_ip=192.168.1.100 soc=am33xx spiargs=setenv bootargs console=${console} ${optargs} root=${spiroot} rootfstype=${spirootfstype} spiboot=echo Booting from spi ...; run spiargs; sf probe ${spibusno}:0; sf read ${loadaddr} ${spisrcaddr} ${spiimgsize}; bootz ${loadaddr} spibusno=0 spiimgsize=0x362000 spiroot=/dev/mtdblock4 rw spirootfstype=jffs2 spisrcaddr=0xe0000 static_ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off stderr=serial stdin=serial stdout=serial uname_boot=setenv bootdir /boot; setenv bootfile vmlinuz-${uname_r}; if test -e mmc ${bootpart} ${bootdir}/${bootfile}; then echo loading ${bootdir}/${bootfile} ...; run loadimage;setenv fdtdir /boot/dtbs/${uname_r}; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /usr/lib/linux-image-${uname_r}; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /lib/firmware/${uname_r}/device-tree; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /boot/dtb-${uname_r}; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /boot/dtbs; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /boot/dtb; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else setenv fdtdir /boot; if test -e mmc ${bootpart} ${fdtdir}/${fdtfile}; then run loadfdt;else echo; echo unable to find ${fdtfile} ...; echo booting legacy ...;run mmcargs;bootz ${loadaddr}; fi;fi;fi;fi;fi;fi;fi; setenv rdfile initrd.img-${uname_r}; if test -e mmc ${bootpart} ${bootdir}/${rdfile}; then echo loading ${bootdir}/${rdfile} ...; run loadrd;if test -n ${uuid}; then setenv mmcroot UUID=${uuid} ro;fi;run mmcargs;bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}; else run mmcargs;bootz ${loadaddr} - ${fdtaddr}; fi;fi; usbnet_devaddr=c8:a0:30:ac:7f:d1 vendor=ti ver=U-Boot 2014.07-00016-g329fca9 (Jul 28 2014 - 12:35:02) Environment size: 8541/131068 bytes
既然是研究如何将网络文件系统NFS挂载为根文件系统,那么把其中59-63行的环境变量摘取出来进行分析,为了便于阅读和分析,整理成如下格式:
#定义变量nfs_options的值为,vers=3 nfs_options=,vers=3 #定义变量nfsargs为一个动作,该动作设置bootargs启动参数内容包括 #(1)console:用于让内核打印输出信息,Documentation/serial-console.txt找到相关描述 #(2)root:用于说明根文件系统是基于NFS。 #(3)rootfstype:指定根文件系统类型,此处指定的是ext4 #(4)nfsroot:指定根文件系统所在NFS服务器的IP以及服务器上的路径 #(5)ip:本目标板IP(必须的,其他可不写)、TFTP和NFS服务器IP、网关IP、子网掩码、主机名、网卡设备、 nfsargs= setenv bootargs console=${console} ${optargs} ${cape_disable} ${cape_enable} root=/dev/nfs rw rootfstype=${nfsrootfstype} nfsroot=${nfsroot} ip=${ip} ${cmdline} #定义变量nfsboot为一系列动作,动作之间以;隔开 nfsboot= echo Booting from ${server_ip} ...; //回显信息“从服务器IP处启动系统...” setenv nfsroot ${server_ip}:${root_dir}${nfs_options}; //指定根文件系统所在NFS服务器的IP以及服务器上的路径 setenv ip ${client_ip}:${server_ip}:${gw_ip}:${netmask}:${hostname}:${device}:${autoconf}; //设定IP setenv autoload no; //仅仅对相关配置进行查询(比如本机被分配到的IP地址),而不会同时产生去通过TFTP下载任何镜像的动作。 setenv serverip ${server_ip};//设置TFTP和NFS服务器IP地址,两个服务器可以在配置同一台电脑上,一般属于这种情况,但也可以配置在不同的电脑上。 setenv ipaddr ${client_ip};//本机目标板地址 tftp ${loadaddr} ${bootfile}; //从TFTP服务器的目录中下载bootfile文件放到内存loadaddr处 tftp ${fdtaddr} dtbs/${fdtfile}; //从TFTP服务器的dtbs/目录下载fdtfile文件,存放到内存的fdtaddr处 run nfsargs; //执行nfsargs定义的一系列动作,解释见上。 bootz ${loadaddr} - ${fdtaddr}//bootz表示执行内存中的zImage文件,后面跟三个参数,第一个参数表示内核在内存中的地址,第二个参数表示initrd的地址,如果没有initrd就用"-"表示,第三个表示设备树二进制文件内存地址 #定义变量nfsopts的值为nolock nfsopts=nolock #定义变量nfsrootfstype的值为ext4 rootwait fixrtc nfsrootfstype=ext4 rootwait fixrtc
从以上环境变量可以得出,这几个变量均是为了nfsboot而服务的,也就是说进入u-boot命令行后,我们输入run nfsboot,那么nfsboot里面的动作(回显信息、设置环境变量、下载内核镜像、设备树二进制文件、设置启动参数、执行镜像文件)全部都会被执行。那么系统也就自动开始启动了。
为了确保所有相关的环境变量都是正确无误的,现在u-boot下面从nfsboot入手把各个尚未知的环境变量依次打印出来,查看一下情况如何,得到结果如下,
----------------------------------------------------------------------------------------------------------------------------------------------------------
U-Boot# printenv server_ip root_dir client_ip gw_ip netmask hostname device autoconf loadaddr bootfile fdtaddr fdtfile
server_ip=192.168.1.100 //实际中我的TFTP、NFS服务器都在虚拟机中,虚拟机IP192.168.1.106
root_dir=/home/userid/targetNFS //NFS服务器的目录为/home/username/zynfs/
## Error: "client_ip" not defined //目标板连接在同一个局域网中,获得的IP为192.168.1.105
gw_ip=192.168.1.1 //路由器IP为192.168.1.1,故此项无需在设置
netmask=255.255.255.0 //子网掩码也正确,故此项无需在设置
## Error: "hostname" not defined //主机名无需设置留空即可
device=eth0 //u-boot默认已经设置好,设置为目标板系统的第一块以太网网卡,故此项无需设置
autoconf=off //保留默认设置,故此项无需设置
loadaddr=0x82000000 //保留默认设置,故此项无需设置
bootfile=zImage //保留默认设置,故此项无需设置,如果想用其他名字自己,记得到时候TFTP服务器目录下的文件名与此保持一致,否则会找不到内核
fdtaddr=0x88000000 //保留默认设置,故此项无需设置
fdtfile=undefined //u-boot没有设置,需要手动设置,可以先运行run findfdt,该命令读取板子信息,根据板子信息自动设置好fdtfile。如何读取的请printenv findfdt
----------------------------------------------------------------------------------------------------------------------------------------------------------
那么根据以上分析,对需要设置的环境变量进行手动更改或者设置,同时为了确保更改生效,改完后在printenv看一下设置是否成功,如下,
U-Boot# setenv server_ip 192.168.1.106 U-Boot# setenv client_ip 192.168.1.105 U-Boot# setenv root_dir /opt/ti-sdk-am335x-evm-07.00.00.00/targetNFS/ U-Boot# run findfdt U-Boot# printenv server_ip client_ip root_dir fdtfile server_ip=192.168.1.106 client_ip=192.168.1.105 root_dir=/opt/ti-sdk-am335x-evm-07.00.00.00/targetNFS/ fdtfile=am335x-boneblack.dtb
现在的准备工作已经万事俱备,只欠东风!接下来让u-boot来执行nfsboot的动作吧,run nfsboot。
实际的操作参见对应的实践篇。