本帖最后由 正点原子运营 于 2024-2-21 11:01 编辑
第十二章 U-Boot移植
1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南 V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=692450874670
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-MPSOC.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)Linux技术交流QQ群:887820935
1.1 bootcmd和bootargs环境变量环境变量是uboot中非常重要的一种变量,可以让我们在不修改uboot源码的情况下改变uboot运行的特性。uboot中有两个非常重要的环境变量bootcmd和bootargs,接下来看一下这两个环境变量。bootcmd和bootagrs是采用类似shell脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量,有很多是Xilinx自己定义的。文件include/configs/zynqmp_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=setenvstdout 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=loadmmc $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=setenvimg boot; setenv psize ${bootsize}; setenv installcmd"install_boot"; run load_boot test_img; setenv img; setenv psize;setenv installcmd\0" \
- 190 "sd_update_boot=echoUpdating boot from SD; mmcinfo && fatload mmc ${sdbootdev}:1${clobstart} ${boot_img} && run install_boot\0" \
- 191 "install_boot=sfprobe 0 && sf erase ${bootstart} ${bootsize} && " \
- 192 "sf write ${clobstart} ${bootstart} ${filesize}\0" \
- 193 "bootenvsize=0x20000\0" \
- 194 "bootenvstart=0x500000\0" \
- 195 "eraseenv=sfprobe 0 && sf erase ${bootenvstart} ${bootenvsize}\0" \
- 196 "jffs2_img=rootfs.jffs2\0" \
- 197 "load_jffs2=tftpboot${clobstart} ${jffs2_img}\0" \
- 198 "update_jffs2=setenvimg jffs2; setenv psize ${jffs2size}; setenv installcmd"install_jffs2"; run load_jffs2 test_img; setenv img; setenv psize;setenv installcmd\0" \
- 199 "sd_update_jffs2=echoUpdating jffs2 from SD; mmcinfo && fatload mmc ${sdbootdev}:1${clobstart} ${jffs2_img} && run install_jffs2\0" \
- 200 "install_jffs2=sfprobe 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=setenvimg kernel; setenv psize ${kernelsize}; setenv installcmd"install_kernel"; run load_kernel test_crc; setenv img; setenvpsize; setenv installcmd\0" \
- 207 "sd_update_kernel=echoUpdating kernel from SD; mmcinfo && fatload mmc ${sdbootdev}:1${clobstart} ${kernel_img} && run install_kernel\0" \
- 208 "install_kernel=sfprobe 0 && sf erase ${kernelstart} ${kernelsize} && " \
- 209 "sf write ${clobstart} ${kernelstart}${filesize}\0" \
- 210 "cp_kernel2ram=sfprobe 0 && sf read ${netstart} ${kernelstart} ${kernelsize}\0" \
- 211 "dtb_img=system.dtb\0" \
- 212 "load_dtb=tftpboot${clobstart} ${dtb_img}\0" \
- 213 "update_dtb=setenvimg dtb; setenv psize ${dtbsize}; setenv installcmd "install_dtb";run load_dtb test_img; setenv img; setenv psize; setenv installcmd\0" \
- 214 "sd_update_dtb=echoUpdating 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 NOTUPDATED\0"\
- 217 "test_crc=ifimi ${clobstart}; then run test_img; else echo ${img} Bad CRC - ${img} is NOTUPDATED; fi\0" \
- 218 "test_img=setenvvar "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=runcp_kernel2ram && bootm ${netstart}\0" \
- 221 ""
复制代码宏CONFIG_EXTRA_ENV_SETTINGS中有很多有价值的信息,比如第219行的netboot变量,就可以让我们从网络启动linux。
1.1.1 环境变量bootcmdbootcmd保存着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 uchardefault_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 #ifdefDEFAULT_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 #ifdefCONFIG_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。我们可以在platform-altk.h文件中通过设置宏CONFIG_BOOTCOMMAND来设置bootcmd的默认值,Petalinux工具设置的CONFIG_BOOTCOMMAND值如下: - 示例代码CONFIG_BOOTCOMMAND默认值
- 215/* BOOTCOMMAND */
- 216 #define CONFIG_BOOTCOMMAND "rundefault_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=runuenvboot; 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 "envimport -t ${loadbootenv_addr} $filesize\0" \
- 167 "loadbootenv=loadmmc $sdbootdev:$partid ${loadbootenv_addr} ${bootenv}\0" \
- 168 "sd_uEnvtxt_existence_test=test-e mmc $sdbootdev:$partid /uEnv.txt\0" \
- 169 "uenvboot="\
- 170 "if runsd_uEnvtxt_existence_test; then " \
- 171 "run loadbootenv; " \
- 172 "echoLoaded environment from ${bootenv}; " \
- 173 "runimportbootenv; " \
- 174 "fi;" \
- 175 "iftest -n $uenvcmd; then " \
- 176 "echoRunning uenvcmd ...; " \
- 177 "runuenvcmd; " \
- 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 0x10000000image.ub;" \
- "bootm 0x10000000;"
复制代码或者可以直接在uboot中设置bootcmd的值,命令如下: - setenv bootcmd ' mmcinfo && fatload mmc 0 0x10000000image.ub; bootm 0x10000000;'
复制代码 1.1.2 环境变量bootargsbootargs保存着uboot传递给Linux内核的参数。ZYNQ MPSoC的bootargs由设备树指定,在12.4.4节我们可以看到bootargs的值为空,也就是说ZYNQ MPSoC一般不用向linux内核传递参数。不过此处我们还是简单地讲解下bootargs。以下面的命令做介绍: - setenv bootargs ‘console=${console},${baudrate}root=${mmcroot} rootfstype=ext4’
复制代码假设console=ttyPS0,baudrate=115200,mmcroot=/dev/mmcblk0p2 rootwait rw,因此将其展开后就是: - setenv bootargs ‘console=ttyPS0,115200root=/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 MPSoC的串口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.2 uboot启动Linux测试uboot已经移植好了,bootcmd和bootargs这两个重要的环境变量也讲解了,接下来就要测试一下uboot能不能完成它的工作:启动Linux内核。我们测试两种启动Linux内核的方法,一种是直接从SD卡启动,一种是从网络启动。 1.2.1 从SD卡启动Linux系统从SD卡启动也就是将编译出来的Linux镜像文件image.ub保存在SD卡中,uboot从SD卡中读取该文件并启动。由于目前我们还没有讲解如何移植linux,所以这里我们就以第六章生成的image.ub为例,该文件已经烧写到了SD卡中,我们可以直接读取来测试。 按照12.3.3节验证与驱动测试中的步骤,先检查一下SD卡的分区1中有没有image.ub文件,输入命令“ls mmc 1:1”(注意和12.3.3节不同,SD卡位置已经改变为mmc 1),结果如下图所示: 从上图中可以看出,此时SD卡分区1中存在image.ub这个文件,所以我们可以测试新移植的uboot能不能启动linux内核。 接下来使用“fatload”命令将镜像传输到内存中,然后用“bootm”命令启动Linux内核,如果Linux内核启动成功的话就会输出如图下图所示的启动信息: 1.2.2 从网络启动Linux系统从网络启动linux系统的唯一目的就是为了调试。不管是为了调试linux系统还是linux下的驱动。每次修改linux系统文件或者linux下的某个驱动以后都要将其烧写到SD中去测试,这样太麻烦了。我们可以设置linux从网络启动,也就是将linux镜像文件和根文件系统都放到Ubuntu下某个指定的文件夹中,这样每次重新编译linux内核或者某个linux驱动以后只需要使用cp命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写SD,这样就加快了开发速度。我们可以通过tftp从Ubuntu中下载image.ub文件或者Image和设备树文件,根文件系统的话也可以通过nfs挂载,不过本小节我们不讲解如何通过nfs挂载根文件系统,这个在讲解根文件系统移植的时候再讲解。这里我们使用tftp从Ubuntu中下载image.ub,前提是要将image.ub文件放到Ubuntu下的tftpboot目录中,这些文件我们在第六章编译Petalinux工程的时候Petalinux工具已经帮我们复制到/tftpboot目录中了(前提是已经按照4.3节搭建好tftp服务器)。 接下来按照11.4.8节中的步骤使用tftpboot网络协议将image.ub下载到ddr中(需要先按照11.4.4节设置开发板和服务器ip地址),如图 12.6.3所示,然后使用bootm命令启动镜像,如所示: 图 12.6.3使用tftpboot网络协议下载镜像 图12.6.4 使用“bootm”命令启动linux uboot移植到此结束,简单总结一下uboot移植的过程: ① 不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的dmeo板,而半导体厂商会在他们自己的开发板上移植好uboot、linux kernel和systemfs等,最终制作好BSP包提供给用户。我们可以在官方提供的BSP包的基础上添加我们的板子,也就是俗称的移植。 ② 我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到uboot下驱动的移植。 ① 一般uboot中需要解决串口、QSPI、eMMC或SD卡、网络和LCD驱动,因为uboot的主要目的就是启动Linux内核,所以不需要考虑太多的外设驱动。 在uboot中添加自己的板子信息,根据自己板子的实际情况来修改uboot中的驱动。
|