前言

為了在之後開發過程中不用反覆燒寫 eMMC 與 SD 卡,本篇會設定 U-Boot 載入 Rootfs 以達到我們的目的。

主要內容

寫在前面

我們這次的目標是從 NFS 伺服器中載入 Rootfs。所以我們在開機時,U-Boot 載入的 Kernel 與裝置樹 ( Devicetree ) 仍是 eMMC/SD 卡內的。

在開機的過程中,首先會要把要執行的東西載入到記憶體中執行。 所以我們可以預期,待會我們會需要載入 Kernel 與 裝置樹。 最後才把主控權交給 Kernel。

我們要做的就是在執行時期,修改 U-Boot 傳給 Kernel 的開機參數。

配置 NFS 伺服器

這一個部份先前有筆記過了,如果還沒有看過的同學請參考這裡

分析 Kernel 與 Devicetree 的載入命令

U-Boot 預設開機在基本的初始化完成後,並且在指定的秒數內沒有都收到使用者的輸入,便會開始執行 bootcmd 中的內容。 我們可以藉由分析它,來初步的知道系統的開機流程。

1print bootcmd
2
3# Output:
4bootcmd=mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if test ${sec_boot} = yes; then if run loadcntr; then run mmcboot; else run netboot; fi; else if run loadimage; then run mmcboot; else run netboot; fi; fi; fi; else booti ${loadaddr} - ${fdt_addr}; fi

整理過後, 我們可以觀察到它真正會執行到的指令為 run loadimage ,接著會是 run mmcboot

 1bootcmd=
 2	mmc dev ${mmcdev};
 3	if mmc rescan; then 
 4		if run loadbootscript; then 
 5			run bootscript; 
 6		else 
 7			if test ${sec_boot} = yes; then 
 8				if run loadcntr; then 
 9					run mmcboot; 
10				else
11					run netboot; 
12				fi;
13			else
14				if run loadimage; then 
15					run mmcboot; 
16				else
17					run netboot; 
18				fi; 
19			fi; 
20		fi; 
21	else
22		booti ${loadaddr} - ${fdt_addr}; 
23	fi

從 eMMC 中把 Kernel 載到記憶體裡面

1loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}

繼續展開 mmcboot

 1mmcboot=
 2	echo Booting from mmc ...; 
 3	run mmcargs;
 4	if test ${sec_boot} = yes; then
 5		if run auth_os; then 
 6			run boot_os; 
 7		else
 8			echo ERR: failed to authenticate; 
 9		fi;
10	else
11		if test ${boot_fdt} = yes || test ${boot_fdt} = try; then 
12			if run loadfdt; then 
13				run boot_os; 
14			else
15				echo WARN: Cannot load the DT; 
16			fi; 
17		else
18			echo wait for boot; 
19		fi;
20	fi;

從 eMMC 中把 裝置樹 ( Devicetree ) 載到記憶體裡面

1loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}

開始載入 Kernel ,之後就會把控制權交給 Kernel 了。

1boot_os=booti ${loadaddr} - ${fdt_addr};
ℹ️ 小提示
我們可以使用 echo $? 來看返回值。

設定 U-Boot 環境變數

從先前的分析來看,比較重要的指令有:

  • loadfdt
  • loadimage
  • boot_os

所以我們只要進行下列修改,即可。

 1setenv serverip "NFS_SERVER_IP"
 2setenv rootfs_dir "/srv/rootfs"
 3setenv ethaddr "01:02:03:04:05:06"
 4setenv image Image
 5setenv fdt_file imx8qxp-mek-rpmsg.dtb
 6
 7setenv rootfsinfo 'setenv bootargs ${bootargs} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${rootfs_dir},v3,tcp'
 8setenv bootcmd 'run rootfsinfo; run loadfdt; run loadimage; run boot_os'
 9
10saveenv
ℹ️ 還原回預設值

如我們想還原回預設值,可以使用下列指令

1env default -a
2saveenv

結果

我們可以重新啟動系統,或是直接執行 boot 就可以看到 U-Boot 正在開始執行我們剛才所撰寫的指令了。 會發現,開機的時候有稍微變長了,並且在過程中可以看到 NFS 相關的字樣。

開機完成後,我們可以在 /proc/cmdline 看到我們先前指定的開機參數(bootarg)。

從 NFS 伺服器中載入 rootfs

從 NFS 伺服器中載入 rootfs

寫在最後

在 U-Boot 中看到的參數,在本篇並沒有多去探究。不過也別太過傷心,以後我們會專門製作一篇為大家講解。

1loadaddr=0x80280000
2fdt_addr=0x83000000
3image=Image
4fdt_file=imx8qxp-mek-rpmsg.dtb

小結

這次在撰寫本篇時,其實是想把 Kernel 與裝置樹都從網路載下來的。 但礙於筆者的網路環境比較複雜,一直無法成功的藉由 TFTP 傳輸資料。 所以也就暫時作罷。 未來如果有完成這個部份再來更新吧。

( 附上當時的筆記 )

配置 TFTP 伺服器

安裝 TFTP

1sudo aptitude install -y tftpd-hpa

配置分享路徑

1sudo cp /etc/default/tftpd-hpa{,.bk} 
2sudo sed -i 's#TFTP_DIRECTORY.*#TFTP_DIRECTORY="/srv/tftp_shared"#g' /etc/default/tftpd-hpa
3sudo sed -i 's#TFTP_ADDRESS.*#TFTP_ADDRESS=":69"#g' /etc/default/tftpd-hpa
4sudo sed -i 's#TFTP_OPTIONS.*#TFTP_OPTIONS="--secure --create"#g' /etc/default/tftpd-hpa
5
6sudo systemctl enable tftpd-hpa
7sudo systemctl restart tftpd-hpa

/etc/default/tftpd-hpa:

1# /etc/default/tftpd-hpa
2TFTP_USERNAME="tftp"
3TFTP_DIRECTORY="/srv/tftp_shared/"
4TFTP_ADDRESS=":69"
5TFTP_OPTIONS="--secure --create"

配置防火牆

1sudo firewall-cmd \
2	--add-rich-rule="rule family='ipv4' source address='192.168.1.2' service name='tftp' accept" \
3	--permanent
4sudo firewall-cmd --reload

除錯

1sudo netstat -nlp
2sudo journalctl -fu  tftpd-hpa.service

用戶端測試

在伺服器中建立測試用的資料

1echo "Hello word" > /srv/tftp_shared/hello

在用戶端進行上、下載測試

1echo "I am Groot~~~~~" > groot.txt
2
3tftp SERVER_IP
4tftp get hello
5tftp put groot.txt

參考連結