1)实验平台:正点原子领航者V2 ZYNQ开发板
2) 章节摘自【正点原子】《领航者ZYNQ之嵌入式Linux开发指南_V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-329957-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:90562473
第十四章 U-Boot移植
上一章节我们详细的分析了uboot的启动流程,对uboot有了一个初步的了解。前两章我们都是使用的正点原子提供的uboot,本章我们就来学习如何将Xilinx官方的uboot移植到正点原子的ZYNQ开发板上,学习如何在uboot中添加我们自己的开发板。
1.1Xilinx官方开发板uboot编译测试
1.1.1查找Xilinx官方的开发板默认配置文件
uboot的移植并不是说我们完完全全的从零开始将uboot移植到我们现在所使用的开发板或者开发平台上。这个对于我们来说基本是不可能的,这个工作一般是半导体厂商做的,半导体厂商负责将uboot移植到他们的芯片上,因此半导体厂商都会自己做一个开发板,这个开发板就叫做原厂开发板,比如大家学习STM32的时候听说过的discover开发板就是ST自己做的。半导体厂商会将uboot移植到他们自己的原厂开发板上,测试好以后就会将这个uboot发布出去,这就是大家常说的原厂BSP包。我们一般做产品的时候就会参考原厂的开发板做硬件,然后在原厂提供的BSP包上做修改,将uboot或者linux kernel移植到我们的硬件上。这个就是uboot移植的一般流程:
① 在uboot中找到参考的开发平台,一般是原厂的开发板。
② 参考原厂开发板移植uboot到我们所使用的开发板上。
正点原子的ZYNQ开发板参考的是Xilinx官方的ZYNQ ZC702开发板做的硬件,因此我们在移植uboot的时候就可以以Xilinx官方的ZYNQ ZC702开发板为蓝本。
本章我们是将Xilinx官方的uboot移植到正点原子的ZYNQ开发板上,Xilinx官方的uboot放到了开发板光盘中,路径为:领航者ZYNQ开发板资料盘(A盘)\4_SourceCode\3_Embedded_Linux\资源文件\uboot\u-boot-xlnx-xilinx-v2018.3.tar.gz。将u-boot-xlnx-xilinx-v2018.3.tar.gz发送到Ubuntu中并解压。
解压Xilinx官方的uboot后得到的configs目录下有很多跟zynq有关的配置,如下图所示,
图 14.1.1 Xilinx官方zynq默认配置文件
从上图可以看出有很多的默认配置文件,其中以zynq开头的是ZYNQ相关开发板的配置文件。zynq_zc702_defconfig是ZYNQ ZC702开发板的配置文件。zynq_zc702_defconfig是ZYNQ ZC702开发板的配置文件。对与ZC702,读者不知是否还有记忆,Uboot启动时打印的信息中就带有ZC702,如下图所示:
图 14.1.2 Uboot打印信息
可见Petalinux工具自动适配其他zynq开发板的参照来源应该是ZC702开发板。因此,在移植之前,我们先编译一下Xilinx官方ZYNQ ZC702开发板对应的uboot,看能不能使其在我们的开发板上正常运行。
1.1.2编译Xilinx官方开发板对应的uboot
首先是配置uboot,然后编译uboot,命令如下(需要先source petalinux安装目录下的settings.sh脚本文件):
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_zc702_defconfig
- make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
复制代码 编译完成以后结果如下图所示:
图 14.1.3执行命令
注:上图终端中的~/work/xlnx-uboot目录为笔者解压Xilinx官方uboot后的根目录
编译完成以后输入“ls”或“l”命令,结果如下图所示:
图 14.1.4 编译结果
从上图可以看出,生成了u-boot文件,编译成功。
我们在编译的时候需要输入ARCH和CORSS_COMPILE这两个变量的值,有点麻烦,如果使用Petalinux就没有这个问题了,现在我们没有使用Petalinux,不过也可以通过以下两种方式解决:
方式一:(不推荐)可以直接在顶层Makefile中直接给ARCH和CORSS_COMPILE赋值,如下图所示的那样:
图 14.1.5添加ARCH和CROSS_COMPILE值
上图中的250、251行就是直接给ARCH和CROSS_COMPILE赋值,这样我们就可以使用如下简短的命令来编译uboot了:
- make zynq_zc702_defconfig
- make V=1 -j8
复制代码 方式二:(推荐)创建shell脚本。在uboot根目录(对应笔者的就是alientek_uboot目录)下创建一个名为zynq_zc702.sh的shell脚本,然后在shell脚本里面输入如下内容:
- 示例代码 zynq_zc702.sh文件
- 1 #!/bin/bash
- 2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
- 3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_zc702_defconfig
- 4 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
复制代码 记得给zynq_zc702.sh这个文件可执行权限,使用zynq_zc702.sh脚本编译uboot的时候每次都会清理一下工程,然后全部重新编译,编译的时候直接执行这个脚本就行了,命令如下:
编译完成以后会生成u-boot.bin、u-boot等文件,但是这些文件是Xilinx官方ZYNQ ZC702开发板的。在第六章Petalinux设计流程实战章节,我们没有单独配置uboot,使用的是Petalinux工具默认的ZC702的配置,使用上是没有问题的。不过那时我们使用了Petalinux工具,Petalinux会根据硬件描述文件自动配置uboot,现在我们没有使用Petalinux工具,能不能用到正点原子的ZYNQ开发板上呢?下面我们拭目以待。
1.1.3验证与驱动测试
如果使用Petalinux工具,测试还是比较方便的,可以直接生成BOOT.bin文件,现在我们没有使用Petalinux工具,也不在Petalinux工程目录下编译,所以测试起来还是比较麻烦的。当然了,也可以借助XSDK软件,这种方法是可行的,不过还是较麻烦,有没有一种更简便的方法呢?
答案是有的,不过首先说明一点,这种方法笔者测试是可行的,各人可能因为软硬件的问题不一定能够顺利运行,这时可以使用XSDK软件来下载。下面我们介绍下这种方法。
这种方法是使用JTAG下载,跟使用XSDK软件一样的,不过这种方式更便捷,因为是脚本化。需要提醒的是Linux需要手动安装JTAG驱动,安装方式见5.6小节Linux系统安装JTAG cable驱动。
首先我们进入第六章Petalinux设计流程实战章节建立的Petalinux工程目录中,在工程的project-spec目录下有一个hw-description文件夹,如下图所示:
图 14.1.6 hw-description文件夹
该文件夹的内容和我们从Vivado软件打开SDK时创建的system_wrapper_hw_platform_0文件夹的内容基本相同,我们需要的是里面的PS初始化文件。我们将该文件夹复制到uboot根目录下,如下图所示:
图 14.1.7 uboot根目录下的hw-description文件夹
现在我们在uboot根目录下新建一个名为linux.tcl的文件,用来下载fgpa的bitstream文件和uboot的elf文件以启动linux内核,文件内容如下:
- connect
- source hw-description/ps7_init.tcl
- targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
- rst -system
- after 3000
- targets -set -filter {jtag_cable_name =~ "Digilent*" && level==0} -index 1
- fpga -file hw-description/system_wrapper.bit
- targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
- loadhw -hw hw-description/system.hdf -mem-ranges [list {0x40000000 0xbfffffff}]
- configparams force-mem-access 1
- targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
- ps7_init
- ps7_post_config
- targets -set -nocase -filter {name =~ "ARM*#0"}
- dow u-boot
- configparams force-mem-access 0
- targets -set -nocase -filter {name =~ "ARM*#0"}
- con
复制代码 然后新建一个名为uboot.tcl的文件。当我们只是调试uboot,不启动内核的时候或没有使用fpga部分时启动内核的时候,可使用该文件,文件内容如下(该文件剩去了下载bitstream文件的步骤,更节省时间):
- connect
- source hw-description/ps7_init.tcl
- targets -set -filter {name =~"APU"}
- loadhw hw-description/system.hdf
- stop
- ps7_init
- targets -set -nocase -filter {name =~ "ARM*#0"}
- rst -processor
- dow u-boot
- con
复制代码 至此,我们可以开始下载uboot。
将领航者开发板的启动模式设置为“JTAG”启动,连接JTAG、串口和电源,然后开发板上电。打开串口软件如SecureCRT或Putty,设置好领航者开发板所使用的串口并打开。
在Vmware软件的菜单栏点击“虚拟机(M)”菜单,在弹出的子菜单中移动到“可移动设备(D)”:会弹出相应的移动设备,里面带有“Digilent USB”的是JTAG的USB接口,连接该USB接口,如下图所示:
图 14.1.8 在Vmware中连接JTAG的USB接口到虚拟机内
现在我们在终端中输入如下命令配置Petalinux的环境变量:
- source /opt/pkg/petalinux/2018.3/settings.sh //或使用spl别名
复制代码 因为这种方法使用的工具是Petalinux自带的,XSDK软件也有,这里我们使用Petalinux中的,所以需要配置Petalinux的环境变量。
在uboot根目录下,输入如下命令下载u-boot文件,也就是u-boot.elf文件:
xsct是Xilinx软件命令行工具,基于tcl脚本,方便开发和调试。执行结果如下图所示:
图 14.1.9 下载bitstream文件和u-boot文件
串口软件接收到的情况如下图所示:
图 14.1.10 串口软件接收情况
从上图可以看到,串口软件没有接收到任何启动信息,有两种可能,一是下载有问题,二是这种命令行编译确实不适用于我们自己的开发板。笔者经过多次测试之后排除了第一种可能,不信可以看后面我们下载移植好的,就能正常打印启动信息。那么就是第二种可能了。
虽然我们可以直接在官方的开发板上配置文件中直接修改,使uboot可以完整的运行在我们的板子上。但是从学习的角度来讲,这样我们就不能了解到uboot是如何添加新平台的。接下来我们就参考Xilinx官方的ZYNQ ZC702开发板,学习如何在uboot中添加我们自己的开发板或开发平台。
1.2在U-Boot中添加自己的开发板
1.2.1添加开发板默认配置文件
先在configs目录下创建领航者开发板的默认配置文件。复制zynq_zc702_defconfig,然后重命名为zynq_altk_defconfig,命令如下:
- cd configs
- cp zynq_zc702_defconfig zynq_altk_defconfig
复制代码 然后修改zynq_altk_defconfig文件,修改后的文件内容如下(因文件较长,故部分未改动内容以“……”代替):
- 示例代码 zynq_altk_defconfig文件
- 1 CONFIG_ARM=y
- 2 CONFIG_SYS_CONFIG_NAME="zynq_altk"
- 3 CONFIG_ARCH_ZYNQ=y
- 4 CONFIG_SYS_TEXT_BASE=0x400000
- 5 CONFIG_SYS_MALLOC_F_LEN=0x800
- 6 CONFIG_IDENT_STRING=" Xilinx Zynq ALTK"
- 7 CONFIG_SPL_STACK_R_ADDR=0x200000
- 8 CONFIG_DEFAULT_DEVICE_TREE="zynq-altk"
- 9 CONFIG_DEBUG_UART=y
- 10 CONFIG_DISTRO_DEFAULTS=y
- 11 CONFIG_FIT=y
- 12 CONFIG_FIT_SIGNATURE=y
- 13 CONFIG_FIT_VERBOSE=y
- 14 CONFIG_BOOTCOMMAND="run default_bootcmd"
- 15 # CONFIG_DISPLAY_CPUINFO is not set
- 16 CONFIG_SYS_L2CACHE_OFF=y
- 17 CONFIG_SPL_DM_SERIAL=y
- 18 CONFIG_SYS_PROMPT="Zynq> "
- ……
- 54 #CONFIG_PHY_XILINX=y
- 55 CONFIG_ZYNQ_GEM=y
- 56 CONFIG_BOOTDELAY=3
- 57 CONFIG_PHY_GIGE=y
- 58 CONFIG_DEBUG_UART_ZYNQ=y
- 59 CONFIG_DEBUG_UART_BASE=0xE0000000
- 60 CONFIG_DEBUG_UART_CLOCK=100000000
- 61 CONFIG_DEBUG_UART_ANNOUNCE=y
- 62 CONFIG_ZYNQ_SERIAL=y
- 63 CONFIG_ZYNQ_QSPI=y
- 64 CONFIG_USB=y
- 65 CONFIG_USB_EHCI_HCD=y
- 66 CONFIG_USB_ULPI_VIEWPORT=y
- 67 CONFIG_USB_ULPI=y
- 68 CONFIG_USB_STORAGE=y
- 69 CONFIG_USB_GADGET=y
- 70 CONFIG_USB_GADGET_MANUFACTURER="Xilinx"
- 71 CONFIG_USB_GADGET_VENDOR_NUM=0x03fd
- 72 CONFIG_USB_GADGET_PRODUCT_NUM=0x0300
- 73 CONFIG_CI_UDC=y
- 74 CONFIG_USB_GADGET_DOWNLOAD=y
复制代码 可以看出,zynq_altk_defconfig文件基本和zynq_zc702_defconfig文件中的内容一样。修改的内容如下:
首先将所有的zc702字样替换为altk,然后修改了第2行,将CONFIG_SYS_CONFIG_NAME设为zynq_altk;
修改了第8行,指定领航者开发板的设备树文件zynq-altk,对应的是我们在14.2.4节创建的领航者开发板的设备树文件名;
将第14行的CONFIG_BOOTCOMMAND定义为"run default_bootcmd";
添加了第56行的CONFIG_BOOTDELAY并设为3,也就是启动内核延时设为3;
修改了第59行的CONFIG_DEBUG_UART_BASE,该定义表示调试串口寄存器的基地址,领航者开发板使用的是PS的串口0作为PS的调试串口。查阅ug585《Zynq-7000 SoC Technical Reference Manual》参考手册,UART0的寄存器基地址是0xe0000000,如下图所示:
图 14.2.1 UART0寄存器地址
因而我们需要将宏CONFIG_DEBUG_UART_BASE的值设为0xe0000000。
1.2.2添加开发板对应的头文件
在目录include/configs下添加领航者开发板对应的头文件。这里使用zc702开发板的头文件zynq_zc70x.h并进行相应修改。在终端输入如下命令:
- cd ../include/configs/
- cp zynq_zc70x.h zynq_altk.h //复制zynq_zc70x.h为zynq_altk.h
复制代码 执行结果如下图所示:
复制完成以后将zynq_altk.h文件的内容修改成如下所示:
- 示例代码 zynq_altk.h文件
- #ifndef __CONFIG_ZYNQ_ZC70X_H
- #define __CONFIG_ZYNQ_ZC70X_H
- #define CONFIG_ZYNQ_I2C0
- #define CONFIG_ZYNQ_EEPROM
- #include <configs/altk-common.h>
- #endif /* __CONFIG_ZYNQ_ZC70X_H */
复制代码 将<configs/zynq-common.h>改为<configs/altk-common.h>。因为我们需要修改zynq-common.h的内容,为了防止破坏原有文件的内容,所以将其复制一份,名为altk-common.h。
修改完成后保存退出。
现在将zynq-common.h复制一份,并修改,命令如下:
- cp zynq-common.h altk-common.h
- vi altk-common.h
复制代码
图 14.2.2 修改altk-common.h文件
修改的内容主要是第216行起的CONFIG_EXTRA_ENV_SETTINGS内容。原内容如下:
可以看到CONFIG_EXTRA_ENV_SETTINGS宏设置的是uboot如何加载内核镜像,并且设置了各种启动方式如NOR启动的norboot、QSPI启动的qspiboot、SD卡启动的sdboot,甚至是usb启动的usbboot,不过zynq-7000系列是不支持usb启动的,zynq ultrascale系列是支持的。此外各种启动方式还有rsa启动如rsa_sdboot,这是一种rsa认证的安全启动方式,通常用于工业安全方面,具体的笔者也没研究过。下面讲解要修改的内容。
将第219行的uImage修改为image.ub,因为zynq使用的内核镜像文件是image.ub文件。修改后的内容如下图所示:
图 14.2.3将uImage修改为image.ub
由于我们一般都是通过SD卡启动,所以接下来修改sdboot。sdboot的原内容如下:
- 271 "sdboot=if mmcinfo; then " \
- 272 "run uenvboot; " \
- 273 "echo Copying Linux from SD to RAM... && " \
- 274 "load mmc 0 ${kernel_load_address} ${kernel_image} && " \
- 275 "load mmc 0 ${devicetree_load_address} ${devicetree_image} && " \
- 276 "load mmc 0 ${ramdisk_load_address} ${ramdisk_image} && " \
- 277 "bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}; " \
- 278 "fi\0" \
复制代码 可以看到,SD卡启动方式先从mmc(此处指SD卡)中加载内核镜像文件kernel_image,然后加载设备树文件devicetree_image,最后加载根文件系统镜像文件ramdisk_image,从而启动linux系统,但我们使用的内核镜像文件image.ub中已经包括了内核和设备树。另外对于根文件系统,一般都是直接放到SD卡的ext4分区,或者使用INITRAMFS格式,而不是通过加载ramdisk_image文件启动的,所以对于sdboot我们将其修改为:
- "sdboot=if mmcinfo; then " \
- "run uenvboot; " \
- "echo Copying Linux from SD to RAM... && " \
- "load mmc 0 ${kernel_load_address} ${kernel_image} && " \
- "bootm ${kernel_load_address}; " \
- "fi\0" \
复制代码 也就是删除了第275和276行,并且修改了第277行。
除了经常使用的SD卡启动方式外,有时方便调试也会用网络启动,所以我们还需添加网络启动内核方式netboot,当然了默认启动还是设置为SD卡启动,所以在sdboot后面添加下面两行:
- "netboot=tftpboot ${kernel_load_address} ${kernel_image} && bootm\0" \
- "default_bootcmd=run sdboot;\0" \
复制代码 如下图所示:
图 14.2.4 修改sdboot并添加netboot
修改完成后保存退出即可。
1.2.3添加开发板对应的板级文件夹
uboot中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等。Xilinx的ZYNQ系列芯片的所有板级文件夹都存放在board/xilinx/zynq目录下,在这个目录下有个名为zynq-zc702的文件夹,这个文件夹就是Xilinx官方ZC702开发板的板级文件夹。复制zynq-zc702,将其重命名为zynq-altk,命令如下:
- cd board/xilinx/zynq/
- cp -r zynq-zc702 zynq-altk
复制代码 进入zynq-altk目录中,可以看到只有一个名为“ps7_init_gpl.c”的文件,该文件是PS的初始化文件,可以用于我们的领航者开发板。
1.2.4添加开发板对应的设备树
uboot支持设备树,每个开发板都有一个对应的设备树文件。Xilinx的ZYNQ系列芯片的所有设备树文件夹都存放在arch/arm/dts目录下,在这个目录下有个名为zynq-zc702.dts的文件,该文件是ZC702开发板的设备树文件。这里我们就不参照zynq-zc702.dts文件,而是参照zynq-zed.dts文件,这是因为zynq-zed.dts是在zynq-zc702.dts文件基础上修改而来,能极大的方便我们的移植。我们将zynq-zed.dts重命名为zynq-altk.dts,命令如下:
- cd arch/arm/dts
- cp zynq-zed.dts zynq-altk.dts
复制代码 拷贝完成以后将zynq_altk.dts文件的内容修改成如下所示:
- 示例代码 zynq-altk.dts文件
- 6 /dts-v1/;
- 7 #include "zynq-7000.dtsi"
- 8
- 9 / {
- 10 model = "Zynq Alientek Development Board";
- 11 compatible = "xlnx,zynq-altk", "xlnx,zynq-7000";
- 12
- 13 aliases {
- 14 ethernet0 = &gem0;
- 15 serial0 = &uart0;
- 16 spi0 = &qspi;
- 17 mmc0 = &sdhci0;
- 18 };
- 19
- 20 memory@0 {
- 21 device_type = "memory";
- 22 reg = <0x0 0x40000000>;
- 23 };
- 24
- 25 chosen {
- 26 bootargs = "";
- 27 stdout-path = "serial0:115200n8";
- 28 };
- 29
- 30 usb_phy0: phy0@e0002000 {
- 31 compatible = "ulpi-phy";
- 32 #phy-cells = <0>;
- 33 reg = <0xe0002000 0x1000>;
- 34 view-port = <0x0170>;
- 35 drv-vbus;
- 36 };
- 37 };
- 38
- 39 &clkc {
- 40 ps-clk-frequency = <33333333>;
- 41 };
- 42
- 43 &gem0 {
- 44 status = "okay";
- 45 phy-mode = "rgmii-id";
- 46 phy-handle = <ðernet_phy>;
- 47
- 48 ethernet_phy: ethernet-phy@0 {
- 49 reg = <0>;
- 50 device_type = "ethernet-phy";
- 51 };
- 52 };
- 53
- 54 &qspi {
- 55 u-boot,dm-pre-reloc;
- 56 status = "okay";
- 57 is-dual = <0>;
- 58 num-cs = <1>;
- 59 flash@0 {
- 60 compatible = "n25q128a11";
- 61 reg = <0x0>;
- 62 spi-tx-bus-width = <1>;
- 63 spi-rx-bus-width = <4>;
- 64 spi-max-frequency = <50000000>;
- 65 #address-cells = <1>;
- 66 #size-cells = <1>;
- 67 partition@qspi-fsbl-uboot {
- 68 label = "qspi-fsbl-uboot";
- 69 reg = <0x0 0x100000>;
- 70 };
- 71 partition@qspi-linux {
- 72 label = "qspi-linux";
- 73 reg = <0x100000 0x500000>;
- 74 };
- 75 partition@qspi-device-tree {
- 76 label = "qspi-device-tree";
- 77 reg = <0x600000 0x20000>;
- 78 };
- 79 partition@qspi-rootfs {
- 80 label = "qspi-rootfs";
- 81 reg = <0x620000 0x5E0000>;
- 82 };
- 83 partition@qspi-bitstream {
- 84 label = "qspi-bitstream";
- 85 reg = <0xC00000 0x400000>;
- 86 };
- 87 };
- 88 };
- 89
- 90 &sdhci0 {
- 91 u-boot,dm-pre-reloc;
- 92 status = "okay";
- 93 };
- 94
- 95 &uart0 {
- 96 u-boot,dm-pre-reloc;
- 97 status = "okay";
- 98 };
- 99
- 100 &usb0 {
- 101 status = "okay";
- 102 dr_mode = "host";
- 103 usb-phy = <&usb_phy0>;
- 104 };
复制代码 修改的内容如下:
首先将zed字样替换成altk,然后
将第10行的model改成"Zynq Alientek Development Board",当然也可以使用其他的名称。
将第15行uart1改成uart0;因为我们使用的是PS端的uart0串口。
将第22行memory的大小(PS DDR的大小)设置为7020的1GB,也就是“reg = <0x0 0x40000000>”,如果是7010的核心板,就是“reg = <0x0 0x20000000>”,也就是512MB。
将第95行的uart1改成uart0。
关于设备树的内容后面在讲解linux驱动的时候会详细讲解,这里就不细述了。
1.2.5修改U-Boot图形界面配置文件
uboot支持图形界面配置,关于uboot的图形界面配置下一章会详细的讲解。修改文件arch/arm/mach-zynq/Kconfig,将第48行的SYS_CONFIG_NAME配置项的内容修改如下:
- 示例代码 Kconfig文件
- 48 config SYS_CONFIG_NAME
- 49 string "Board configuration name"
- 50 default "zynq_altk"
- 51 help
- 52 This option contains information about board configuration name.
- 53 Based on this option include/configs/<CONFIG_SYS_CONFIG_NAME>.h header
- 54 will be used for board configuration.
复制代码 修改的是第50行的内容,修改完成以后的Kconfig文件如下图所示:
图 14.2.5修改后的Kconfig文件
到此为止,领航者开发板就已经添加到uboot中了,接下来就是编译这个新添加的开发板。
1.2.6使用新添加的板子配置编译uboot
在uboot源码根目录下新建一个名为zynq.sh的shell脚本,在这个shell脚本里面输入如下内容:
- 示例代码 zynq.sh脚本文件
- #!/bin/bash
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
复制代码 第3行使用的配置文件zynq_altk_defconfig就是14.2.1节中新建的zynq_altk_defconfig配置文件。给予zynq.sh可执行权限,然后运行脚本来完成编译,命令如下:
- chmod +x zynq.sh //给予可执行权限
- ./zynq.sh //运行脚本编译uboot
复制代码 等待编译完成,编译完成以后输入如下命令,查看一下14.2.2小节中添加的zynq_altk.h这个头文件有没有被引用。
如果有很多文件都引用了zynq_altk.h这个头文件,那就说明新板子添加成功,如下图所示:
图 14.2.6查找结果
编译完成以后就可以使用“xsct uboot.tcl”或者“xsct linux.tcl”命令进行下载测试。串口软件输出结果如下图所示:
图 14.2.7 uboot启动过程
从上图可以看到,我们基本移植成功了,也验证了这种下载方式是没有问题的。uboot的最终目的就是启动Linux内核,所以还是需要通过启动Linux内核来判断uboot移植是否真的成功。在启动Linux内核之前我们先来学习两个重要的环境变量bootcmd和bootargs。
1.3bootcmd和bootargs环境变量
uboot中有两个非常重要的环境变量bootcmd和bootargs,接下来看一下这两个环境变量。bootcmd和bootagrs是采用类似shell脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量,有很多是Xilinx自己定义的。文件include/configs/altk-common.h中的宏CONFIG_EXTRA_ENV_SETTINGS保存着这些环境变量的默认值,内容如下:
宏CONFIG_EXTRA_ENV_SETTINGS中有很多有价值的信息,比如第277行的netboot变量,就可以让我们从网络启动linux。
1.3.1环境变量bootcmd
bootcmd保存着uboot默认命令,uboot倒计时结束以后就会执行bootcmd中的命令。这些命令一般都是用来启动Linux内核的,比如读取SD或者EMMC中的Linux内核镜像文件和设备树文件到DRAM中,然后启动Linux内核。可以在uboot启动以后进入命令行设置bootcmd环境变量的值。如果QSPI中没有保存bootcmd的值,那么uboot就会使用默认的值,开发板第一次运行uboot的时候都会使用默认值来设置bootcmd环境变量,默认环境变量在文件include/env_default.h中定义。打开文件include/env_default.h,在此文件中有如下所示内容:
- 示例代码 默认环境变量
- 14 env_t environment __UBOOT_ENV_SECTION__ = {
- 15 ENV_CRC, /* CRC Sum */
- 16 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
- 17 1, /* Flags: valid */
- 18 #endif
- 19 {
- 20 #elif defined(DEFAULT_ENV_INSTANCE_STATIC)
- 21 static char default_environment[] = {
- 22 #else
- 23 const uchar default_environment[] = {
- 24 #endif
- ……
- 31 #ifdef CONFIG_USE_BOOTARGS
- 32 "bootargs=" CONFIG_BOOTARGS "\0"
- 33 #endif
- 34 #ifdef CONFIG_BOOTCOMMAND
- 35 "bootcmd=" CONFIG_BOOTCOMMAND "\0"
- 36 #endif
- 37 #ifdef CONFIG_RAMBOOTCOMMAND
- 38 "ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
- 39 #endif
- 40 #ifdef CONFIG_NFSBOOTCOMMAND
- 41 "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
- 42 #endif
- 43 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- 44 "bootdelay=" __stringify(CONFIG_BOOTDELAY) "\0"
- 45 #endif
- ……
- 91 #ifdef CONFIG_ENV_VARS_UBOOT_CONFIG
- 92 "arch=" CONFIG_SYS_ARCH "\0"
- 93 #ifdef CONFIG_SYS_CPU
- 94 "cpu=" CONFIG_SYS_CPU "\0"
- 95 #endif
- 96 #ifdef CONFIG_SYS_BOARD
- 97 "board=" CONFIG_SYS_BOARD "\0"
- 98 "board_name=" CONFIG_SYS_BOARD "\0"
- 99 #endif
- 100 #ifdef CONFIG_SYS_VENDOR
- 101 "vendor=" CONFIG_SYS_VENDOR "\0"
- 102 #endif
- 103 #ifdef CONFIG_SYS_SOC
- 104 "soc=" CONFIG_SYS_SOC "\0"
- 105 #endif
- 106 #endif
- 107 #ifdef CONFIG_EXTRA_ENV_SETTINGS
- 108 CONFIG_EXTRA_ENV_SETTINGS
- 109 #endif
- 110 "\0"
- 111 #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
- 112 }
- 113 #endif
- 114 };
复制代码 从上述代码中的第14行可以看出environment是个env_t类型的变量,env_t类型如下:
- 示例代码 env_t结构体
- 148 typedef struct environment_s {
- 149 uint32_t crc; /* CRC32 over data bytes */
- 150 #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
- 151 unsigned char flags; /* active/obsolete flags */
- 152 #endif
- 153 unsigned char data[ENV_SIZE]; /* Environment data */
- 154 } env_t;
复制代码 env_t结构体中的crc为CRC值,flags是标志位,data数组就是环境变量值。因此,environment就是用来保存默认环境变量的,在示例代码默认环境变量中指定了很多环境变量的默认值,比如bootcmd的默认值就是CONFIG_BOOTCOMMAND,bootargs的默认值就是CONFIG_BOOTARGS。我们在zynq_altk_defconfig文件中通过设置CONFIG_BOOTCOMMAND来设置bootcmd的默认值,如下所示:
示例代码 CONFIG_BOOTCOMMAND默认值
- 14 CONFIG_BOOTCOMMAND="run default_bootcmd"
复制代码 看起来很简单,我们来分析下。以下分析的内容都来自include/configs/altk-common.h中的宏CONFIG_EXTRA_ENV_SETTINGS定义处。
上面的“run default_bootcmd”使用的是uboot的run命令来运行default_bootcmd。default_bootcmd是我们在14.2.2小节添加的,其内容如下:
- 278 "default_bootcmd=run sdboot;\0" \
复制代码 run sdboot表示默认使用sd卡启动方式。sdboot变量的内容如下:
- 271 "sdboot=if mmcinfo; then " \
- 272 "run uenvboot; " \
- 273 "echo Copying Linux from SD to RAM... && " \
- 274 "load mmc 0 ${kernel_load_address} ${kernel_image} && " \
- 275 "bootm ${kernel_load_address}; " \
- 276 "fi\0" \
复制代码 uboot使用了类似shell脚本语言的方式来编写变量。sdboot启动方式首先先执行mmcinfo命令,打印mmc信息,执行成功后然后执行uenvboot。uenvboot变量的内容如下:
- 262 "uenvboot=" \
- 263 "if run loadbootenv; then " \
- 264 "echo Loaded environment from ${bootenv}; " \
- 265 "run importbootenv; " \
- 266 "fi; " \
- 267 "if test -n $uenvcmd; then " \
- 268 "echo Running uenvcmd ...; " \
- 269 "run uenvcmd; " \
- 270 "fi\0" \
复制代码 uenvboot首先运行loadbootenv,loadbootenv内容如下:
- 228 "loadbootenv_addr=0x2000000\0" \
- 235 "bootenv=uEnv.txt\0" \
- 236 "loadbootenv=load mmc 0 ${loadbootenv_addr} ${bootenv}\0" \
复制代码 也就是从SD卡中加载uEnv.txt文件,显然SD卡中没有uEnv.txt文件,执行失败,所以第一个if条件不成立,执行第二个if语句,判断uenvcmd变量值的长度是否为零,由于uenvcmd变量未定义,所以第二个if语句条件不成立,直接返回到sdboot。
在sdboot中执行"echo Copying Linux from SD to RAM... && "输出“Copying Linux from SD to RAM...”,执行成功后从mmc0中加载内核镜像文件kernel_image到内存DRAM的kernel_load_address处。其中kernel_image和kernel_load_address变量的内容如下:
- 219 "kernel_image=image.ub\0" \
- 220 "kernel_load_address=0x2080000\0" \
复制代码 可以看到kernel_load_address变量值为0x2080000,是文件加载到内存中的地址。
对于bootcmd的值,也可以在启动uboot后直接在uboot命令行中设置bootcmd的值,命令如下:
setenv bootcmd ' mmcinfo && fatload mmc 0 0x2080000 image.ub; bootm 0x2080000;'
1.3.2环境变量bootargs
bootargs保存着uboot传递给Linux内核的参数。zynq的bootargs由设备树指定,在14.2.4节我们可以看到bootargs的值为空,也就是说zynq一般不用向linux内核传递参数。不过此处我们还是简单地讲解下bootargs。以下面的命令做介绍:
- setenv bootargs ‘console=${console},${baudrate} root=${mmcroot} rootfstype=ext4’
复制代码 假设console=ttyPS0,baudrate=115200,mmcroot=/dev/mmcblk0p2 rootwait rw,因此将其展开后就是:
- setenv bootargs ‘console=ttyPS0,115200 root=/dev/mmcblk0p2 rootwait rw rootfstype=ext4’
复制代码 可以看出bootargs的值为“console= ttyPS0,115200 root= /dev/mmcblk0p2 rootwait rw rootfstype=ext4。bootargs设置了很多的参数的值,这些参数Linux内核会使用到,常用的参数有:
1、console
console用来设置linux终端(或者叫控制台),也就是通过什么设备来和Linux进行交互,是串口还是LCD屏幕。如果是串口的话应该是串口几等等。一般设置串口作为Linux终端,这样我们就可以在电脑上通过串口工具来和linux交互了。这里设置console为ttyPS0,因为linux启动以后ZYNQ的串口0在linux下的设备文件就是/dev/ttyPS0,在Linux下,一切皆文件。
ttyPS0后面有个“,115200”,这是设置串口的波特率,console= ttyPS0,115200综合起来就是设置ttyPS0(也就是串口0)作为Linux的终端,并且串口波特率设置为115200。
2、root
root用来设置根文件系统的位置,root=/dev/mmcblk0p2用于指明根文件系统存放在mmcblk0设备的分区2中。领航者开发板启动linux以后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1和/dev/mmcblk1p2这样的文件,其中/dev/mmcblkx(x=0~n)表示mmc设备,而/dev/mmcblkxpy(x=0~n,y=1~n)表示mmc设备x的分区y。在领航者开发板中/dev/mmcblk0表示SD卡,而/dev/mmcblk0p2表示SD卡的分区2。
root后面有“rootwait rw”,rootwait表示等待mmc设备初始化完成以后再挂载,否则的话mmc设备还没初始化完成就挂载根文件系统会出错的。rw表示根文件系统是可以读写的,不加rw的话可能无法在根文件系统中进行写操作,只能进行读操作。
3、rootfstype
此选项一般配置root一起使用,rootfstype用于指定根文件系统类型,如果根文件系统为ext格式的话此选项无所谓。如果根文件系统是yaffs、jffs或ubifs的话就需要设置此选项,指定根文件系统的类型。
bootargs常设置的选项就这三个,后面遇到其他选项的话再讲解。
1.4uboot启动Linux测试
uboot已经移植好了,bootcmd和bootargs这两个重要的环境变量也讲解了,接下来就要测试一下uboot能不能完成它的工作:启动Linux内核。我们测试两种启动Linux内核的方法,一种是直接从SD卡启动,一种是从网络启动。
1.4.1从SD卡启动Linux系统
从SD卡启动也就是将编译出来的Linux镜像文件image.ub保存在SD卡中,uboot从SD卡中读取该文件并启动。由于目前我们还没有讲解如何移植linux,所以这里我们就以第六章生成的image.ub为例,该文件已经烧写到了SD卡中,我们可以直接读取来测试。先检查一下SD卡的分区1中有没有image.ub文件,输入命令“ls mmc 0:1”,结果如下图所示:
图 14.4.1 SD卡分区1文件
从上图中可以看出,此时SD卡分区1中存在BOOT.BIN和image.ub这两个文件,所以我们可以测试新移植的uboot能不能启动linux内核。
直接输入boot,或者run bootcmd即可启动Linux内核,如果Linux内核启动成功的话就会输出如图下图所示的启动信息:
图 14.4.2 linux内核启动成功
1.4.2从网络启动Linux系统
从网络启动linux系统的唯一目的就是为了调试。不管是为了调试linux系统还是linux下的驱动。每次修改linux系统文件或者linux下的某个驱动以后都要将其烧写到SD中去测试,这样太麻烦了。我们可以设置linux从网络启动,也就是将linux镜像文件和根文件系统都放到Ubuntu下某个指定的文件夹中,这样每次重新编译linux内核或者某个linux驱动以后只需要使用cp命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写SD,这样就加快了开发速度。我们可以通过nfs或者tftp从Ubuntu中下载image.ub文件或者zImage和设备树文件,根文件系统的话也可以通过nfs挂载,不过本小节我们不讲解如何通过nfs挂载根文件系统,这个在讲解根文件系统移植的时候再讲解。这里我们使用tftp从Ubuntu中下载image.ub文件或者zImage和设备树文件,前提是要将image.ub文件或者zImage和设备树文件放到Ubuntu下的tftpboot目录中,这些文件我们在第六章编译Petalinux工程的时候Petalinux工具已经帮我们复制到/tftpboot目录中了。
我们先运行uboot默认的netboot环境变量来从网络下载image.ub文件以启动linux。
使用“run netboot”命令即可,如下图所示:
图 14.4.3 使用“run netboot”命令启动linux
现在我们通过tftp下载zImage和system.dtb这两个文件来启动linux。首先设置bootcmd环境变量,设置如下:
- setenv bootcmd ‘tftpboot 0x2080000 zImage; tftpboot 0x2000000 system.dtb; bootz 0x2080000 - 0x2000000’
- boot
复制代码 一开始是下载zImage和system.dtb这两个文件,下载完成以后就是启动Linux内核,启动过程如下图所示:
图 14.4.5 Linux启动过程
uboot移植到此结束,简单总结一下uboot移植的过程:
①不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的dmeo板,而半导体厂商会在他们自己的开发板上移植好uboot、linux kernel和systemfs等,最终制作好BSP包提供给用户。我们可以在官方提供的BSP包的基础上添加我们的板子,也就是俗称的移植。
②我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到uboot下驱动的移植。
③一般uboot中需要解决串口、QSPI、EMMC或SD卡、网络和LCD驱动,因为uboot的主要目的就是启动Linux内核,所以不需要考虑太多的外设驱动。
④ 在uboot中添加自己的板子信息,根据自己板子的实际情况来修改uboot中的驱动。
|