正点原子 发表于 2020-9-1 10:33:48

【正点原子FPGA连载】第十四章U-Boot移植--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

本帖最后由 正点原子 于 2020-9-1 10:36 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:          
5)关注正点原子公众号,获取最新资料





第十四章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\ZYNQ_7020\3_Embedded_Linux\资源文件\uboot\u-boot-xlnx-xilinx-v2018.3.tar.gz。将u-boot-xlnx-xilinx-v2018.3.tar.gz发送到Ubuntu中并解压。
在移植之前,我们先编译一下Xilinx官方ZYNQ ZC702开发板对应的uboot。首先是配置uboot,解压Xilinx官方的uboot后得到的configs目录下有很多跟zynq有关的配置,如下图所示,

图 25.1.1 Xilinx官方zynq默认配置文件
从上图可以看出有很多的默认配置文件,其中以zynq开头的是ZYNQ相关开发板的配置文件。zynq_zc702_defconfig是ZYNQ ZC702开发板的配置文件。
1.1.2编译Xilinx官方开发板对应的uboot
找到Xilinx官方ZYNQ ZC702开发板对应的默认配置文件以后可以编译一下。编译uboot命令如下:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_zc702_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
编译完成以后结果如下图所示:

图 25.1.2编译结果
从上图可以看出,编译成功。我们在编译的时候需要输入ARCH和CORSS_COMPILE这两个变量的值,有点麻烦,如果使用Petalinux就没有这个问题了,现在我们没有使用Petalinux,不过也可以通过以下两种方式解决:
方式一:(不推荐)可以直接在顶层Makefile中直接给ARCH和CORSS_COMPILE赋值,如下图所示的那样:

图 25.1.3添加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的时候每次都会清理一下工程,然后全部重新编译,编译的时候直接执行这个脚本就行了,命令如下:
./zynq_zc702.sh
编译完成以后会生成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驱动,安装方式见16.6小节Linux系统安装JTAG cable驱动。
首先我们进入第十七章Petalinux设计流程实战章节建立的Petalinux工程目录中,在工程的project-spec目录下有一个hw-description文件夹,如下图所示:

图 25.1.4 hw-description文件夹
该文件夹的内容和我们从Vivado软件打开SDK时创建的system_wrapper_hw_platform_0文件夹的内容基本相同,我们需要的是里面的PS初始化文件。我们将该文件夹复制到uboot根目录下,如下图所示:

图 25.1.5 uboot根目录下的hw-description文件夹
现在我们在uboot根目录下新建一个名为linux.tcl的文件,用来需要下载fgpa的bitstream文件和uboot的elf文件以启动linux内核,文件内容如下:
1connect
2source hw-description/ps7_init.tcl
3targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
4rst -system
5after 3000
6targets -set -filter {jtag_cable_name =~ "Digilent*" && level==0} -index 1
7fpga -file hw-description/system_wrapper.bit
8targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
9loadhw -hw hw-description/system.hdf -mem-ranges
10 configparams force-mem-access 1
11 targets -set -filter {name =~"APU*" && jtag_cable_name =~ "Digilent*"} -index 0
12 ps7_init
13 ps7_post_config
14 targets -set -nocase -filter {name =~ "ARM*#0"}
15 dow u-boot
16 configparams force-mem-access 0
17 targets -set -nocase -filter {name =~ "ARM*#0"}
18 con
然后新建一个名为uboot.tcl的文件。当我们只是调试uboot,不启动内核的时候或没有使用fpga部分时启动内核的时候,可使用该文件,文件内容如下(该文件剩去了下载bitstream文件的步骤,更节省时间):
1connect
2source hw-description/ps7_init.tcl
3targets -set -filter {name =~"APU"}
4loadhw hw-description/system.hdf
5stop
6ps7_init
7targets -set -nocase -filter {name =~ "ARM*#0"}
8rst -processor
9dow u-boot
10 con
至此,我们可以开始下载uboot。
将领航者开发板的启动模式设置为“JTAG”启动,连接JTAG、串口和电源,然后开发板上电。打开串口软件如SecureCRT或Putty,设置好领航者开发板所使用的串口并打开。
在Vmware软件的菜单栏点击“虚拟机(M)”菜单,在弹出的子菜单中移动到“可移动设备(D)”:会弹出相应的移动设备,里面带有“Digilent USB”的是JTAG的USB接口,连接该USB接口,如下图所示:

图 25.1.6 在Vmware中连接JTAG的USB接口到虚拟机内
现在我们在终端中输入如下命令配置Petalinux的环境变量:
source /opt/pkg/petalinux/2018.3/settings.sh       //或使用spl别名
因为这种方法使用的工具是Petalinux自带的,XSDK软件也有,这里我们使用Petalinux中的,所以需要配置Petalinux的环境变量。
在uboot根目录下,输入如下命令下载u-boot文件,也就是u-boot.elf文件:
xsct linux.tcl


串口软件接收到的情况如下图所示:

图 25.1.8 串口软件接收情况
从上图可以看到,串口软件没有接收到任何启动信息,有两种可能,一是下载有问题,二是这种命令行编译确实不适用于我们自己的开发板。笔者经过多次测试之后排除了第一种可能,不信可以看后面我们下载移植好的,就能正常打印启动信息。那么就是第二种可能了。
虽然我们可以直接在官方的开发板上配置文件中直接修改,使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文件
1CONFIG_ARM=y
2CONFIG_SYS_CONFIG_NAME="zynq_altk"
3CONFIG_ARCH_ZYNQ=y
4CONFIG_SYS_TEXT_BASE=0x400000
5CONFIG_SYS_MALLOC_F_LEN=0x800
6CONFIG_IDENT_STRING=" Xilinx Zynq ALTK"
7CONFIG_SPL_STACK_R_ADDR=0x200000
8CONFIG_DEFAULT_DEVICE_TREE="zynq-altk"
9CONFIG_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文件中的内容一样。主要修改了第8行,指定领航者开发板的设备树文件,对应的是我们在25.2.4节创建的领航者开发板的设备树文件名,第14行的CONFIG_BOOTCOMMAND,第56行的CONFIG_BOOTDELAY改为3。第59行指定串口地址,宏CONFIG_DEBUG_UART_BASE表示调试串口寄存器基地址,领航者开发板使用的是PS的串口0作为PS的调试串口。查阅ug585《Zynq-7000 SoC Technical Reference Manual》参考手册,UART0的寄存器基地址是0xe0000000,如下图所示:

图 25.2.1 UART0寄存器地址
因而我们需要将宏CONFIG_DEBUG_UART_BASE的值设为0xe0000000。
1.2.2添加开发板对应的头文件
在目录include/configs下添加领航者开发板对应的头文件。使用zc702开发板的头文件zynq_zc70x.h进行修改是可以的。不过此处我们就不复制zc702开发板的头文件了,而是使用Petalinux创建的头文件,该头文件是我们在22.2节使用Petalinux编译uboot时Petalinux工具自动生成的。这样做的目的是对使用Petalinux工具搭建的工程有更好的了解。需要说明的是配置Petalinux工程时选择不同的启动方式产生的头文件不一样,此处我们使用的是从SD卡启动的头文件。在终端输入如下命令:
cd ../include/configs/
ll platform*
执行结果如下图所示:

可以看到有两个以“platform”开头的文件。我们将platform-top.h重命名为zynq_altk.h,platform-auto.h重命名为platform-altk.h,命令如下:
mv platform-top.h zynq_altk.h
mv platform-auto.h platform-altk.h
重命名完成以后将zynq_altk.h文件的内容修改成如下所示:
示例代码 zynq_altk.h文件
1#ifndef __CONFIG_ZYNQ_ALTK_H
2#define __CONFIG_ZYNQ_ALTK_H
3
4#include <configs/platform-altk.h>
5#define CONFIG_SYS_BOOTM_LEN 0xF000000
6#define DFU_ALT_INFO_RAM \
7                  "dfu_ram_info=" \
8          "setenv dfu_alt_info " \
9          "image.ub ram $netstart 0x1e00000\0" \
10         "dfu_ram=run dfu_ram_info && dfu 0 ram 0\0" \
11         "thor_ram=run dfu_ram_info && thordown 0 ram 0\0"
12
13 #define DFU_ALT_INFO_MMC \
14         "dfu_mmc_info=" \
15         "set dfu_alt_info " \
16         "${kernel_image} fat 0 1\\\\;" \
17         "dfu_mmc=run dfu_mmc_info && dfu 0 mmc 0\0" \
18         "thor_mmc=run dfu_mmc_info && thordown 0 mmc 0\0"
19
20 #define CONFIG_ZYNQ_I2C0
21 #define CONFIG_ZYNQ_EEPROM
22
23 /* EEPROM */
24 #ifdef CONFIG_ZYNQ_EEPROM
25 #define CONFIG_SYS_I2C_EEPROM_ADDR_LEN         1
26 #define CONFIG_SYS_I2C_EEPROM_ADDR             0x54
27 #define CONFIG_SYS_EEPROM_PAGE_WRITE_BITS      4
28 #define CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS5
29 #define CONFIG_SYS_EEPROM_SIZE               1024 /* Bytes */
30 #define CONFIG_SYS_I2C_MUX_ADDR                0x74
31 #define CONFIG_SYS_I2C_MUX_EEPROM_SEL          0x4
32 #endif
33
34 #endif /* __CONFIG_ZYNQ_ALTK_H */
可以看到zynq_altk.h里面有一些以“CONFIG_”开头的宏定义,这些宏定义可以用于配置或者裁剪uboot。如果我们要想使能或者禁止uboot的某些功能,可以在该文件中添加这个功能对应的CONFIG_XXX宏即可,如果不需要某个功能的话就删除掉对应的宏。zynq_altk.h文件内容不是很多,主要是第4行的include语句,将configs/platform-altk.h文件包含进来了,该文件里面是领航者开发板平台的配置,具体的我们就不看了,毕竟是由Petalinux自动生成的。
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          usb0 = &usb0;
19      };
20
21      memory@0 {
22          device_type = "memory";
23          reg = <0x0 0x40000000>;
24      };
25
26      chosen {
27          bootargs = "";
28          stdout-path = "serial0:115200n8";
29      };
30
31      usb_phy0: phy0@e0002000 {
32          compatible = "ulpi-phy";
33          #phy-cells = <0>;
34          reg = <0xe0002000 0x1000>;
35          view-port = <0x0170>;
36          drv-vbus;
37      };
38};
39
40&clkc {
41      ps-clk-frequency = <33333333>;
42};
43
44&gem0 {
45      status = "okay";
46      phy-mode = "rgmii-id";
47      phy-handle = <ðernet_phy>;
48
49      ethernet_phy: ethernet-phy@0 {
50          reg = <0>;
51          device_type = "ethernet-phy";
52      };
53};
54
55&qspi {
56      u-boot,dm-pre-reloc;
57      status = "okay";
58      is-dual = <0>;
59      num-cs = <1>;
60      flash@0 {
61          compatible = "n25q128a11";
62          reg = <0x0>;
63          spi-tx-bus-width = <1>;
64          spi-rx-bus-width = <4>;
65          spi-max-frequency = <50000000>;
66          #address-cells = <1>;
67          #size-cells = <1>;
68          partition@qspi-fsbl-uboot {
69            label = "qspi-fsbl-uboot";
70            reg = <0x0 0x100000>;
71          };
72          partition@qspi-linux {
73            label = "qspi-linux";
74            reg = <0x100000 0x500000>;
75          };
76          partition@qspi-device-tree {
77            label = "qspi-device-tree";
78            reg = <0x600000 0x20000>;
79          };
80          partition@qspi-rootfs {
81            label = "qspi-rootfs";
82            reg = <0x620000 0x5E0000>;
83          };
84          partition@qspi-bitstream {
85            label = "qspi-bitstream";
86            reg = <0xC00000 0x400000>;
87          };
88      };
89};
90
91&sdhci0 {
92      u-boot,dm-pre-reloc;
93      status = "okay";
94};
95
96&uart0 {
97      u-boot,dm-pre-reloc;
98      status = "okay";
99};
100
101 &usb0 {
102   status = "okay";
103   dr_mode = "host";
104   usb-phy = <&usb_phy0>;
105 };
关于设备树的内容后面在讲解linux驱动的时候会详细讲解,这里需要提醒的是我们使用的是PS的UART0作为通信接口,而不是UART1,另外memory的大小这里设置为7020的1GB,也就是“reg = <0x0 0x40000000>”,如果是7010的核心板,就是“reg = <0x0 0x20000000>”,也就是512MB。在qspi节点可以看到有5个分区,分别是fsbl-uboot分区、linux分区、设备树分区、根文件系统分区和bitstream分区,不过在实际使用的时候并不是如此分布,特别是在使用Petalinux工具的时候,一般都是使能“Advanced Flash Auto Configuration”功能,自动对qspi进行分区。
1.2.5修改U-Boot图形界面配置文件
uboot支持图形界面配置,关于uboot的图形界面配置下一章会详细的讲解。修改文件arch/arm/mach-zynq/Kconfig,将第48行的SYS_CONFIG_NAME配置项的内容修改如下:
示例代码 Kconfig文件
48 config SYS_CONFIG_NAME
49string "Board configuration name"
50default "zynq_altk"
51help
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.
修改完成以后的Kconfig文件如下图所示:

图 25.2.2修改后的Kconfig文件
到此为止,领航者开发板就已经添加到uboot中了,接下来就是编译这个新添加的开发板。
1.2.6使用新添加的板子配置编译uboot
在uboot源码根目录下新建一个名为zynq.sh的shell脚本,在这个shell脚本里面输入如下内容:
示例代码 zynq.sh脚本文件
1 #!/bin/bash
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zynq_altk_defconfig
4 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
第3行我们使用的默认配置文件就是25.2.1节中新建的zynq_altk_defconfig配置文件。给予zynq.sh可执行权限,然后运行脚本来完成编译,命令如下:
chmod 777 zynq.sh                     //给予可执行权限
./zynq.sh                                        //运行脚本编译uboot
等待编译完成,编译完成以后输入如下命令,查看一下33.2.2小节中添加的zynq_altk.h这个头文件有没有被引用。
grep -nR "zynq_altk.h"
如果有很多文件都引用了zynq_altk.h这个头文件,那就说明新板子添加成功,如下图所示:

图 25.2.3查找结果
编译完成以后就可以使用“xsct jtag.tcl”进行下载测试。串口软件输出结果如下图所示:

图 25.2.4 uboot启动过程
从上图可以看到,我们基本移植成功了,也验证了这种下载方式是没有问题的。uboot的最终目的就是启动Linux内核,所以还是需要通过启动Linux内核来判断uboot移植是否真的成功。在启动Linux内核之前我们先来学习两个重要的环境变量bootcmd和bootargs。
1.3bootcmd和bootargs环境变量
uboot中有两个非常重要的环境变量bootcmd和bootargs,接下来看一下这两个环境变量。bootcmd和bootagrs是采用类似shell脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量,有很多是Xilinx自己定义的。文件include/configs/platform-altk.h中的宏CONFIG_EXTRA_ENV_SETTINGS保存着这些环境变量的默认值,内容如下:
示例代码 宏CONFIG_EXTRA_ENV_SETTINGS默认值
155 /* Extra U-Boot Env settings */
156 #define CONFIG_EXTRA_ENV_SETTINGS \
157   SERIAL_MULTI \
158   CONSOLE_ARG \
159   DFU_ALT_INFO_RAM \
160   DFU_ALT_INFO_MMC \
161   PSSERIAL0 \
162   "nc=setenv stdout nc;setenv stdin nc;\0" \
163   "ethaddr=00:0a:35:00:1e:53\0" \
164   "bootenv=uEnv.txt\0" \
165   "importbootenv=echo \"Importing environment from SD ...\"; " \
166         "env import -t ${loadbootenv_addr} $filesize\0" \
167   "loadbootenv=load mmc $sdbootdev:$partid ${loadbootenv_addr} ${bootenv}\0" \
168   "sd_uEnvtxt_existence_test=test -e mmc $sdbootdev:$partid /uEnv.txt\0" \
169   "uenvboot=" \
170         "if run sd_uEnvtxt_existence_test; then " \
171             "run loadbootenv; " \
172             "echo Loaded environment from ${bootenv}; " \
173             "run importbootenv; " \
174             "fi; " \
175         "if test -n $uenvcmd; then " \
176             "echo Running uenvcmd ...; " \
177             "run uenvcmd; " \
178         "fi\0" \
179   "autoload=no\0" \
180   "sdbootdev=0\0" \
181   "clobstart=0x10000000\0" \
182   "netstart=0x10000000\0" \
183   "dtbnetstart=0x23fff000\0" \
184   "loadaddr=0x10000000\0" \
185   "bootsize=0x500000\0" \
186   "bootstart=0x0\0" \
187   "boot_img=BOOT.BIN\0" \
188   "load_boot=tftpboot ${clobstart} ${boot_img}\0" \
189   "update_boot=setenv img boot; setenv psize ${bootsize}; setenv installcmd \"install_boot\"; run load_boot test_img; setenv img; setenv psize; setenv installcmd\0" \
190   "sd_update_boot=echo Updating boot from SD; mmcinfo && fatload mmc ${sdbootdev}:1 ${clobstart} ${boot_img} && run install_boot\0" \
191   "install_boot=sf probe 0 && sf erase ${bootstart} ${bootsize} && " \
192         "sf write ${clobstart} ${bootstart} ${filesize}\0" \
193   "bootenvsize=0x20000\0" \
194   "bootenvstart=0x500000\0" \
195   "eraseenv=sf probe 0 && sf erase ${bootenvstart} ${bootenvsize}\0" \
196   "jffs2_img=rootfs.jffs2\0" \
197   "load_jffs2=tftpboot ${clobstart} ${jffs2_img}\0" \
198   "update_jffs2=setenv img jffs2; setenv psize ${jffs2size}; setenv installcmd \"install_jffs2\"; run load_jffs2 test_img; setenv img; setenv psize; setenv installcmd\0" \
199   "sd_update_jffs2=echo Updating jffs2 from SD; mmcinfo && fatload mmc ${sdbootdev}:1 ${clobstart} ${jffs2_img} && run install_jffs2\0" \
200   "install_jffs2=sf probe 0 && sf erase ${jffs2start} ${jffs2size} && " \
201         "sf write ${clobstart} ${jffs2start} ${filesize}\0" \
202   "kernelsize=0xa80000\0" \
203   "kernelstart=0x520000\0" \
204   "kernel_img=image.ub\0" \
205   "load_kernel=tftpboot ${clobstart} ${kernel_img}\0" \
206   "update_kernel=setenv img kernel; setenv psize ${kernelsize}; setenv installcmd \"install_kernel\"; run load_kernel test_crc; setenv img; setenv psize; setenv installcmd\0" \
207   "sd_update_kernel=echo Updating kernel from SD; mmcinfo && fatload mmc ${sdbootdev}:1 ${clobstart} ${kernel_img} && run install_kernel\0" \
208   "install_kernel=sf probe 0 && sf erase ${kernelstart} ${kernelsize} && " \
209         "sf write ${clobstart} ${kernelstart} ${filesize}\0" \
210   "cp_kernel2ram=sf probe 0 && sf read ${netstart} ${kernelstart} ${kernelsize}\0" \
211   "dtb_img=system.dtb\0" \
212   "load_dtb=tftpboot ${clobstart} ${dtb_img}\0" \
213   "update_dtb=setenv img dtb; setenv psize ${dtbsize}; setenv installcmd \"install_dtb\"; run load_dtb test_img; setenv img; setenv psize; setenv installcmd\0" \
214   "sd_update_dtb=echo Updating dtb from SD; mmcinfo && fatload mmc ${sdbootdev}:1 ${clobstart} ${dtb_img} && run install_dtb\0" \
215   "loadbootenv_addr=0x00100000\0" \
216   "fault=echo ${img} image size is greater than allocated place - partition ${img} is NOT UPDATED\0" \
217   "test_crc=if imi ${clobstart}; then run test_img; else echo ${img} Bad CRC - ${img} is NOT UPDATED; fi\0" \
218   "test_img=setenv var \"if test ${filesize} -gt ${psize}\\; then run fault\\; else run ${installcmd}\\; fi\"; run var; setenv var\0" \
219   "netboot=tftpboot ${netstart} ${kernel_img} && bootm\0" \
220   "default_bootcmd=run cp_kernel2ram && bootm ${netstart}\0" \
221 ""
宏CONFIG_EXTRA_ENV_SETTINGS中有很多有价值的信息,比如第219行的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,在此文件中有如下所示内容:
示例代码 默认环境变量
14env_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)
21static char default_environment[] = {
22#else
23const uchar default_environment[] = {
24#endif
……
31#ifdefCONFIG_USE_BOOTARGS
32      "bootargs=" CONFIG_BOOTARGS         "\0"
33#endif
34#ifdefCONFIG_BOOTCOMMAND
35      "bootcmd="CONFIG_BOOTCOMMAND      "\0"
36#endif
37#ifdefCONFIG_RAMBOOTCOMMAND
38      "ramboot="CONFIG_RAMBOOTCOMMAND       "\0"
39#endif
40#ifdefCONFIG_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#ifdefCONFIG_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 #ifdefCONFIG_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; /* Environment data   */
154 } env_t;
env_t结构体中的crc为CRC值,flags是标志位,data数组就是环境变量值。因此,environment就是用来保存默认环境变量的,在示例代码默认环境变量中指定了很多环境变量的默认值,比如bootcmd的默认值就是CONFIG_BOOTCOMMAND,bootargs的默认值就是CONFIG_BOOTARGS。我们可以在platform-altk.h文件中通过设置宏CONFIG_BOOTCOMMAND来设置bootcmd的默认值,Petalinux工具设置的CONFIG_BOOTCOMMAND值如下:
示例代码 CONFIG_BOOTCOMMAND默认值
215 /* BOOTCOMMAND */
216 #define CONFIG_BOOTCOMMAND"run default_bootcmd"
看起来很简单,我们来分析下。以下分析的内容都来自include/configs/platform-altk.h中的宏CONFIG_EXTRA_ENV_SETTINGS定义处。
第216行,“run default_bootcmd”使用的是uboot的run命令来运行default_bootcmd,default_bootcmd是Petalinux工具生成的。default_bootcmd内容如下:
212   "default_bootcmd=run uenvboot; run cp_kernel2ram && bootm ${netstart}\0" \
default_bootcmd里面用到的变量有uenvboot、cp_kernel2ram和netstart。uenvboot变量的内容如下:
164   "bootenv=uEnv.txt\0" \
165   "importbootenv=echo \"Importing environment from SD ...\"; " \
166         "env import -t ${loadbootenv_addr} $filesize\0" \
167   "loadbootenv=load mmc $sdbootdev:$partid ${loadbootenv_addr} ${bootenv}\0" \
168   "sd_uEnvtxt_existence_test=test -e mmc $sdbootdev:$partid /uEnv.txt\0" \
169   "uenvboot=" \
170         "if run sd_uEnvtxt_existence_test; then " \
171             "run loadbootenv; " \
172             "echo Loaded environment from ${bootenv}; " \
173             "run importbootenv; " \
174             "fi; " \
175         "if test -n $uenvcmd; then " \
176             "echo Running uenvcmd ...; " \
177             "run uenvcmd; " \
178         "fi\0" \
uboot使用了类似shell脚本语言的方式来编写变量。uenvboot做的事情是首先判断SD卡是否存在环境变量文件uEnv.txt,显然SD卡中没有uEnv.txt文件,然后判断uenvcmd变量值的长度是否为零,由于uenvcmd变量未定义,所以领航者开发板启动时不打印uenvboot中的信息。
cp_kernel2ram和netstart变量的内容如下:
180   "sdbootdev=0\0" \
182   "netstart=0x10000000\0" \
198   "kernel_img=image.ub\0" \
202   "cp_kernel2ram=mmcinfo && fatload mmc ${sdbootdev} ${netstart} ${kernel_img}\0" \
可以看到netstart变量值为0x10000000,是文件加载到内存的地址。使用netstart变量名是因为使用tftpboot命令从网络下载内核镜像文件时,存放在内存中的位置由netstart指定。
cp_kernel2ram先执行mmcinfo命令,打印mmc信息,执行成功后从mmc0中加载内核镜像文件image.ub到内存DRAM的0x10000000处。
当我们明确知道我们所使用的板子的时候就可以大幅简化宏CONFIG_BOOTCOMMAND的设置,比如我们要从SD启动,那么宏CONFIG_BOOTCOMMAND就可简化为:
#define CONFIG_BOOTCOMMAND \
         "mmcinfo && fatload mmc 0 0x10000000 image.ub;" \
         "bootm 0x10000000;"
或者可以直接在uboot中设置bootcmd的值,命令如下:
setenv bootcmd ' mmcinfo && fatload mmc 0 0x10000000 image.ub; bootm 0x10000000;'
1.3.2环境变量bootargs
bootargs保存着uboot传递给Linux内核的参数。zynq的bootargs由设备树指定,在25.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/mmcblk1p2 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”,结果如下图所示:

图 25.4.1 SD卡分区1文件
从上图中可以看出,此时SD卡分区1中存在BOOT.BIN和image.ub这两个文件,所以我们可以测试新移植的uboot能不能启动linux内核。
直接输入boot,或者run bootcmd即可启动Linux内核,如果Linux内核启动成功的话就会输出如图下图所示的启动信息:

图 25.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”命令即可,如下图所示:

图 25.4.3 使用“run netboot”命令启动linux
现在我们通过tftp下载zImage和system.dtb这两个文件来启动linux。首先设置bootcmd环境变量,设置如下:
setenv bootcmd 'tftpboot 8000 zImage; tftpboot 103cdda8 system.dtb; bootz 8000 - 103cdda8'
一开始是下载zImage和system.dtb这两个文件,过程如下图所示:

图 25.4.4下载过程
下载完成以后就是启动Linux内核,启动过程如下图所示:

图 25.4.5 Linux启动过程
uboot移植到此结束,简单总结一下uboot移植的过程:
①不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的dmeo板,而半导体厂商会在他们自己的开发板上移植好uboot、linux kernel和systemfs等,最终制作好BSP包提供给用户。我们可以在官方提供的BSP包的基础上添加我们的板子,也就是俗称的移植。
②我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到uboot下驱动的移植。
③一般uboot中需要解决串口、QSPI、EMMC或SD卡、网络和LCD驱动,因为uboot的主要目的就是启动Linux内核,所以不需要考虑太多的外设驱动。
④      在uboot中添加自己的板子信息,根据自己板子的实际情况来修改uboot中的驱动。

页: [1]
查看完整版本: 【正点原子FPGA连载】第十四章U-Boot移植--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南