版权说明

本书为作者精校开源版,版权归作者所有。

作者允许读者在注明来源后引用内容,并通知作者。

前 言

近年来,随着硬件性能的快速提高,嵌入式Linux系统已经得到越来越多的应用。要想用好嵌入式Linux系统,首先要会启动嵌入式Linux系统。

本电子书围绕学会启动嵌入式Linux系统这个关键话题展开,利用ZYNQ7000开发板作为载体,深入介绍了ZYNQ7000开发板嵌入式Linux系统各种启动方式的启动过程、特点以及制作方法。

全书采用学以致用的方式编写,图文并茂地讲解了各项操作的具体过程,务求读者可以亲自实践。

本书面向对计算机操作比较熟悉,乐于动手实操的读者,具体要求如下:

  1. 安装以及使用过虚拟机;
  2. 有Linux命令的使用基础;
  3. 了解嵌入式Linux系统的基本概念;

符合要求的读者只要学完本书并完成相关操作,应该都能:

  1. 了解ZYNQ7000开发板嵌入式Linux系统启动的过程;
  2. 了解ZYNQ7000开发板嵌入式Linux系统启动过程涉及的文件;
  3. 了解ZYNQ7000开发板嵌入式Linux系统启动过程的关键参数;
  4. 学会使用与启动相关的Linux命令;
  5. 学会使用启动加载器U-Boot的主要命令;

最终学会启动ZYNQ7000开发板的嵌入式Linux系统。

本书由孙宇彤、孙启凡执笔,感谢康宇哲提供的技术支持,本书编写时还参考了DeepSeek提供的信息。

2026.4



第1章 概述

1.1 嵌入式Linux系统简介

单片机,以其高集成、强可控的特点,一直以来都在产业界和人们的日常生活中扮演着重要角色,发挥着越来越大的作用。

这些年,随着芯片的集成程度不断提高,单片机的功能越发强大,单片机系统已经逐步从传统的裸机应用向嵌入式Linux系统转换,很像之前手机从功能机向智能机的跨越,有种改朝换代的感觉。

使用嵌入式Linux系统的最大好处,就是智能机与功能机的差别:应用与硬件解耦,系统引入了更多的开放性。这样,无论是开发者还是用户,都会受益匪浅。

比如,得益于Linux系统的高度开放性,嵌入式Linux系统可以部署到各种各样的硬件架构上,包括树莓派、香橙派这样的准PC,或者采用openwrt的路由器,又或者是本文的主角:基于ZYNQ7000系列芯片的开发板。

在Linux操作系统之上,我们就可以开发和安装各式各样的应用软件,灵活满足用户的需求。

当然,由于硬件架构众多,部署嵌入式Linux系统就成为极具挑战性的任务。在部署任务中,上电后的第一项任务——启动,尤为关键。大家都知道,良好的开端是成功的一半,只有设备正常启动了,才能取得开门红,为后续的应用开个好头。

俗话说得好:“开门七件事,柴米油盐酱醋茶”。嵌入式Linux系统的启动并不是简单的两个字,启动是一个过程,经历很多的步骤,是一环扣一环的。

另外,启动工作的具体环节与硬件架构密切相关,后续的内容将基于ZYNQ7000系列芯片开发板,详细介绍开发板的启动过程以及如何去操控启动过程。

附带讲一下,嵌入式Linux系统的启动过程还是有很多共通之处,学会了基于ZYNQ7000系列芯片的启动过程,其他硬件架构的启动过程也就豁然开朗了。

1.2 ZYNQ7000系列芯片介绍

ZYNQ7000系列芯片是赛灵思(Xilinx,目前已经被AMD收购)研发的产品,赛灵思以研发高性能的FPGA产品而知名。

ZYNQ7000系列芯片是赛灵思研发的一款具有里程碑意义的产品,首次将高性能的ARM处理器系统(PS,Processing System)与FPGA可编程逻辑(PL,Programmable Logic)紧密集成在单颗芯片,完美地实现了处理器的顺序执行能力和FPGA的并行处理能力的融合。

ZYNQ7000系列芯片具有相同的ZYNQ芯片架构,系列中包含多种型号的芯片,对应不同的硬件规模和芯片性能,以满足不同应用的需求和成本考量。

1.2.1 芯片特色

ZYNQ-7000系列芯片的本质是一个以处理器为中心的可编程SoC,因此芯片首先是一个强大的应用处理器,可以运行像Linux这样的复杂操作系统。其次芯片拥有一个FPGA,可以作为定制硬件加速器或专用外设。

传统的“FPGA+外挂CPU”方案存在通信延迟大、功耗高、PCB设计复杂等缺点。ZYNQ的设计者将FPGA与处理器两者置于同一芯片上,通过高速内部总线互联,实现了高带宽、低延迟的协同工作,特别适用于需要大量数据预处理、实时控制、硬件加速的应用场景。

1.2.2 体系结构:PS + PL

ZYNQ芯片由PS和PL两大体系结合而成,这是理解ZYNQ芯片的关键,其结构可以清晰地分为两大部分:

1. 处理器系统 - PS

PS是一个完整的、独立的ARM Cortex-A9处理器系统,即使不使用PL部分,它也能作为一颗标准的应用处理器工作。其主要组成部分包括:

另外,ZYNQ支持多种启动方式,包括利用Flash、SD、JTAG启动。

2. 可编程逻辑 - PL

PL部分本质上就是一个传统的Xilinx 7系列FPGA,其逻辑资源根据具体型号有所不同。主要包括:

3. PS与PL的互联:关键优势

PS和PL之间通过多种高性能的AXI(Advanced eXtensible Interface)总线连接,这是ZYNQ架构的精髓。主要包括:

这种丰富的互联结构保证了数据在处理器和可编程逻辑之间能够高效、低延迟地流动。

1.2.3 芯片系列

ZYNQ-7000系列芯片提供了多种硬件规模和性能的型号,以满足不同应用的需求和成本考量。

首先看PS处理器,分为如下两类:

因此我们一般选用双核的ZYNQ-7000系列芯片。

其次看PL,也就是FPGA部分。

双核ZYNQ-7000系列芯片主要有Z-7010、Z-7015、Z-7020、Z-7030、Z-7035、Z-7045和Z-7100等型号,最后的数字代表了PL部分的规模大小。数字越大,逻辑资源(CLB、DSP、BRAM)越多,性能越强,价格也越高。

下表列出了三种典型型号Z-7010、 Z-7020和Z-7045的硬件规模:

资源类型 Z-7010 Z-7020 Z-7045
逻辑单元 28K 85K 350
查找表 (LUTs) 17,600 53,200 218,600
触发器 (FFs) 35,200 106,400 437,200
块RAM (BRAM) 60块 140块 545块
数字信号处理单元 80 220 900
最大I/O 引脚数 100 200 362

初学者推荐选择Z-7010或者Z-7020,性价比高。

1.2.4 开发板

市面上有很多采用ZYNQ-7010或者ZYNQ-7020的芯片的开发板,这里我们推荐两款:

1. EBAZ4205

EBAZ4205 是一款基于Xilinx Zynq-7010芯片的矿机主控板,由于矿机退役,被大量拆机流入市场,经过商家改造后,售价通常在百元级别,成为性价比极高的嵌入式学习开发板。

该板卡配备了256MB的DDR3 内存和128MB的NAND Flash,还配备了100M 的以太网口以及SD卡座。另外,该板卡还有多个插座,用于引出ZYNQ-7010的各种管脚,主要是PL端的GPIO端口。

该板卡支持运行 Linux 系统及自定义 FPGA 逻辑设计。

EBAZ4205的缺点是需要外加扩展板,才能实现供电以及提供USB接口。另外一个缺点是无法切换启动方式,在学习嵌入式Linux系统的启动时很不方便。

在本项目中,EBAZ4205主要作为验证板使用,用于验证学习的成果,不作为学习板使用,学习板使用下面介绍的R210。

2. R210

R210是一款基于Xilinx Zynq-7020芯片的软件无线电开发板,主要由Zynq-7020芯片和AD9361芯片组成,与ADI公司的ADALM Pluto基本兼容,价格在千元级别,是一款性价比较高的软件无线电开发板。

作为软件无线电的开发板,R210提供了2 收2发的射频SMA端口,其工作频率范围可达70MHz~6GHz,满足各种双通道无线电系统的需要。这一点,也是R210显著优于Pluto之处。

R210的其他优点包括提供了更为丰富的外部接口,例如千兆的网口、SD卡座以及两个TypeC的USB接口。这两个USB接口都可以作为供电端口,通常我们选择右侧这个USB接口供电。由于R210的功耗较大,因此电源输出需要5V/1A以上。

R210的硬件配置为512MB的DDR3 内存和32MB的NOR Flash,因此可以支持嵌入式Linux系统的启动。

在R210的最右侧,有一个2位的拨码开关,可以灵活地切换启动模式:SD启动、Flash启动或者JTAG启动,这为学习嵌入式Linux系统的启动提供了非常大的方便。

后续的章节我们就在R210开发板上,开始嵌入式Linux系统启动的学习过程。

1.3 全书内容简介

本书后续内容将围绕R210开发板,详细介绍SD启动以及Flash启动的具体步骤、工作原理以及制作方法。

其中第二章介绍基于分区的SD启动;第三章介绍RAMdisk的SD启动;第四章介绍Flash启动;第五章,介绍统一启动。

第2章 深度研究基于分区的SD启动

2.1 概述

R210作为基于ZYNQ7000系列的SDR板卡,支持SD、QSPI和JTAG等3种启动模式,QSPI就是Flash启动。

其中SD启动是R210的出厂标配,这种启动方式借鉴了树莓派的启动特点,将嵌入式Linux系统存放在TF卡上,可以把TF卡看成一个硬盘。

在树莓派系统中,TF卡做成两个分区,一个分区是启动分区,放置了启动文件。这个分区采用FAT的文件系统,可以通过windows系统访问,方便进行配置;另外一个分区是EXT4文件分区,运行树莓派的Raspberry Pi OS,也就是一种Linux系统。

R210的TF卡也是两个分区,一个FAT分区放置启动文件,当然这些启动文件与树莓派的完全不同;另外一个分区是EXT文件分区,运行嵌入式Linux系统。

本章将详细讲解R210基于分区的SD启动,并介绍相关的操作步骤。以下列出的是操作步骤用到的工具与命令

【本章windows工具】

树莓派镜像烧写工具、xshell、bootinfo

2.2 制作TF启动卡

前面说了,R210的TF启动源自树莓派,所以可以采用与树莓派一样的启动卡制作方式,也就是通过烧写镜像来制作TF启动卡。

为此,我们首先到https://www.raspberrypi.com/software/下载树莓派的镜像烧写工具:Raspberry Pi imager,然后安装Raspberry Pi imager,选择I accept the agreement,指定安装路径,点击next进行安装。

接下来下载R210的系统镜像: 2019_R1-2020_06_22.zip。

厂商提供的下载链接:https://pan.baidu.com/s/17vCbHScepADLSbk17y5MIQ 提取码:7H4M

随后找一张16GB或更大的空闲TF卡,插入电脑,运行树莓派的镜像烧写工具,设置R210系统的烧录参数。

下面3张图片展示了烧录参数的设置过程,其中“树莓派设备”项不用选择,“操作系统”项先选择“使用自定义镜像”,再选择刚刚下载的R210的系统镜像,“存储设备”选择对应的TF卡。

当出现如下图的界面时,点击下一步。

点击最右边的否,然后启动烧录过程。

烧录过程需要运行5到10分钟。烧录完成后,将TF卡重新插拔,就可以在windows系统中看到boot分区。将boot分区NeptuneSDR P210目录下的全部文件复制到根目录,就完成了TF启动卡的制作。

2.3 启动后查看TF卡的分区

制作完TF启动卡后,在启动R210之前,我们还要安装终端工具xshell,通过电脑远程操作R210的嵌入式Linux系统。

启动R210时先将R210的拨码开关打到SD启动,插入做好的TF启动卡,再用Type-C数据线连接R210的UART口与电脑的USB口,R210开始上电启动。

接着在电脑中找到新增的串口,记录串口的端口号。在xshell中增加一个会话,采用串口连接,波特率设为115200。

会话建好后,点击连接,就会出现终端界面。

在终端界面中输入回车键,如果看到如下的提示符,代表连接r210嵌入式Linux系统成功,已经以root用户成功连接到了R210:

root@analog: ~#

接下来查看分区情况:

root@analog:~# lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
mmcblk0      179:0    0 14.9G  0 disk
├─mmcblk0p2  179:2    0  6.4G  0 part /
├─mmcblk0p3  179:3    0    1M  0 part
└─mmcblk0p1  179:1    0    1G  0 part

root@analog:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       6.2G  3.1G  2.8G  53% /

不难看出TF卡的第二分区挂载为系统的根目录,这也就是分区启动的含义。我们还可以输入mount命令,将第一分区挂载到/boot目录:

root@analog:~# mount /dev/mmcblk0p1 /boot

查看/boot目录的内容,不难发现与windows系统中看到的BOOT分区根目录的内容是一致的。

root@analog:/boot# cd /boot
root@analog:/boot# ls -l
total 9212
-rwxr-xr-x 1 root root 4681552 Sep 11  2022 BOOT.BIN
drwxr-xr-x 2 root root    4096 Mar 11  2023 NeptuneSDR K210
drwxr-xr-x 2 root root    4096 Mar 11  2023 NeptuneSDR P210
drwxr-xr-x 2 root root    4096 Aug  8  2025 System Volume Information
-rwxr-xr-x 1 root root   16492 Sep 11  2022 devicetree.dtb
-rwxr-xr-x 1 root root     392 Feb  4  2020 uEnv.txt
-rwxr-xr-x 1 root root 4704936 Oct 29  2024 uImage

附带说一下,输入Linux命令需要区分大小写,命令与参数之间需要有空格。

2.4 启动相关文件

在TF卡BOOT分区的根目录中,有4个文件与启动相关:BOOT.BIN、uEnv.txt、devicetree.dtb和uImage,以下分别介绍:

2.4.1 BOOT.BIN

BOOT.BIN是ZYNQ启动的第一个文件,其内容是多个分区镜像(二进制文件)的组合,这些分区镜像包括fsbl、FPGA比特流和u-boot,其中fsbl是ZYNQ启动过程的关键代码,FPGA比特流用于初始化ZYNQ的PL部分(也就是FPGA),u-boot用于加载嵌入式Linux系统。

BOOT.BIN利用文件头来定义多个分区镜像的结构,下图展示了文件头的结构,其中比较重要的数据结构有启动头(Boot Header)、镜像头表(IMAGE HEADER TABLE)、分区头表(PARTITON HEADER TABLE)。

可见BOOT.BIN中镜像与分区一一对应,构成镜像/分区对,镜像头表给出了镜像文件的名称,分区表给出了镜像文件的存放位置、大小以及加载位置。

BOOT.BIN可以有多个镜像/分区对,利用bootinfo工具可以解析BOOT.BIN的结构参数。将bootinfo.exe和BOOT.BIN两个文件复制到合适的目录,在命令提示符中输入bootinfo BOOT.BIN,就可以看到BOOT.BIN的结构参数:

其中的分区头表展示了镜像文件的关键信息,摘录如下:

		   PARTITION HEADER TABLE (fsbl.elf)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x00006002  unencrypted_length (0x04) : 0x00006002
        total_length (0x08) : 0x00006002           load_addr (0x0c) : 0x00000000
           exec_addr (0x10) : 0x00000000    partition_offset (0x14) : 0x000005c0
          attributes (0x18) : 0x00000010       section_count (0x1C) : 0x00000001
     checksum_offset (0x20) : 0x00000000          iht_offset (0x24) : 0x00000240
           ac_offset (0x28) : 0x00000000            checksum (0x3c) : 0xfffed7e8
 attribute list -
               trustzone [non-secure]            el [el-0]         
              exec_state [aarch-32]     dest_device [none]         
              encryption [no]                  core [none]         
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (system_top.bit)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x000f6ec0  unencrypted_length (0x04) : 0x000f6ec0
        total_length (0x08) : 0x000f6ec0           load_addr (0x0c) : 0x00000000
           exec_addr (0x10) : 0x00000000    partition_offset (0x14) : 0x000065d0
          attributes (0x18) : 0x00000020       section_count (0x1C) : 0x00000001
     checksum_offset (0x20) : 0x00000000          iht_offset (0x24) : 0x00000250
           ac_offset (0x28) : 0x00000000            checksum (0x3c) : 0xffd14b7e
 attribute list -
               trustzone [non-secure]            el [el-0]         
              exec_state [el-0]         dest_device [none]         
              encryption [no]                  core [none]         
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (u-boot.elf)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x0001fbc8  unencrypted_length (0x04) : 0x0001fbc8
        total_length (0x08) : 0x0001fbc8           load_addr (0x0c) : 0x04000000
           exec_addr (0x10) : 0x04000000    partition_offset (0x14) : 0x000fd490
          attributes (0x18) : 0x00000010       section_count (0x1C) : 0x00000001
     checksum_offset (0x20) : 0x00000000          iht_offset (0x24) : 0x00000260
           ac_offset (0x28) : 0x00000000            checksum (0x3c) : 0xf7ea35a6
 attribute list -
               trustzone [non-secure]            el [el-0]         
              exec_state [aarch-32]     dest_device [none]         
              encryption [no]                  core [none]         
--------------------------------------------------------------------------------
		

这其中,total_length(代码长度)、load_addr(加载地址)、exec_addr(执行地址)和partition_offset(代码偏移)几个字段的内容最为重要。

我们可以从分区头表中提取分区文件如下表的关键参数,并作出BOOT.BIN文件的结构表(注意代码偏移和长度在分区头表中都是以字为单位,转换为字节数要乘以4)

镜像文件 代码偏移 代码长度(Bytes) 加载地址 执行地址
fsbl.elf 5888 (4*0x5c0) 98312 (4*0x6002) 0x0000000 0x0000000
system_top.bit 104256 4045568 0x0000000 0x0000000
u-boot.elf 4149824 531728 0x4000000 0x4000000

因此,boot.bin中包含了3大功能模块,2.5节将详细介绍这三大功能模块的具体作用,这里只简单描述一下名称:

最后,我们得到如下的BOOT.BIN文件的结构图:

2.4.2 uEnv.txt

uEnv.txt是个文本文件,存放有u-boot使用的环境变量,具体内容如下:

uenvcmd=run adi_sdboot
adi_sdboot=echo Copying Linux from SD to RAM... && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && if fatload mmc 0 0x2000000 ${ramdisk_image}; then bootm 0x3000000 0x2000000 0x2A00000; else bootm 0x3000000 - 0x2A00000; fi
bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
        

2.4.3 devicetree.dtb

devicetree.dtb是一个二进制文件,是系统设备树信息的编译结果。设备树指的是嵌入式Linux系统所支持的硬件设备,相当于R210的硬件信息。

devicetree.dtb由u-boot加载。

2.4.4 uImage

uImage是一个二进制文件,包括一个64字节长度的头以及一个压缩的嵌入式Linux内核镜像。

文件头中包含操作系统类型、CPU 架构、镜像类型、压缩类型和镜像名称等字段内容。

内核镜像包含了各种硬件设备的驱动,同样由u-boot加载。

2.5 基于分区的启动过程

2.5.1 启动过程图

ZYNQ芯片的嵌入式Linux系统启动过程如下图所示,分为ROM启动→FSBL→U-Boot→内核运行等四个阶段,以下就按照启动过程的顺序,详细介绍每一阶段的运行机制。

2.5.2 ROM启动阶段

ROM启动阶段是启动的第一步,所谓的ROM指的是固化在ZYNQ芯片内部的不可修改代码,负责最基础的硬件初始化和加载FSBL(第一阶段引导加载器),以下用BootROM来描述。

BootROM完成以下功能:

既然提到了ZYNQ的OCM,这里就展开讲一下。

ZYNQ同时使用两种内存,一种是片上内存OCM(On-Chip Memory),共256KB,可用的范围为192kB。OCM的优点是高速、低延迟:CPU可直接访问,无需初始化,上电即可使用,主要用于引导过程。

另外一种是片外的DDR内存,容量最大可达1GB,R210配备了512MB,这是操作系统工作的内存。

这两种内存占用同样的地址空间,其中OCM为低端0x00000000 ~ 0x0003FFFF,DDR是0x00100000以上,也就是1MB以上。

2.5.3 FSBL阶段

BOOTROM结束后,就进入了FSBL阶段。FSBL是用户可修改的启动加载程序,主要完成更复杂的硬件初始化和后续镜像加载,主要功能如下所示:

2.5.4 U-Boot阶段

FSBL结束后,就进入了U-Boot阶段,这里详细一下介绍前面多次遇到过的U-boot。

首先,这个U代表通用的意思。引入U-Boot这个中间件后,可以增强启动的灵活性,这样就可以支持各种芯片架构、各种操作系统以及各种外设的启动了。比如openwrt,就采用了U-Boot;ZYNQ芯片,同样也采用了U-Boot。

具体而言,为了加载Linux操作系统内核,U-Boot需要完成如下工作:

U-Boot启动过程与环境变量文件uEnv.txt密切相关,文件中定义了环境变量以及命令脚本。以R210的uEnv.txt为例,以下两行是环境变量的定义:

uenvcmd=run adi_sdboot
bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
		

uenvcmd代表U-Boot运行后需要执行的命令脚本;bootargs代表U-Boot加载系统内核时,向系统内核传递的启动参数,其中列出了根系统所在的启动分区以及分区的文件系统。

而adi_sdboot则定义了命令脚本的具体任务,这些任务要通过U-Boot的命令来执行:

adi_sdboot=echo Copying Linux from SD to RAM... && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && if fatload mmc 0 0x2000000 ${ramdisk_image}; then bootm 0x3000000 0x2000000 0x2A00000; else bootm 0x3000000 - 0x2A00000; fi

命令脚本中需执行多个u-Boot命令,里面的&&连结这些命令,代表只有前一个命令执行成功,才执行下一个命令。

其中一个关键的U-Boot命令是fatload,这个命令用于加载FAT文件系统的文件到DDR内存中,因此:

fatload mmc 0 0x3000000 ${kernel_image}就是将mmc设备(TF卡)第0分区上的kernel_image这个内核镜像文件加载到DDR内存地址0x3000000。

kernel_image的缺省文件名为uImage,这是在U-Boot中已经预先设定了,当然我们也可以在uEnv.txt中进行更换,比如加一行:kernel_image=uImage_r210。

由于TF启动卡的第0分区也就是BOOT分区,该分区格式化为FAT32的文件系统,因此只要kernel_image 文件在根目录,fatload就可以顺利读取并加载。

同样地,adi_sdboot中还加载了devicetree_image(缺省文件名为devicetree.dtb)到DDR内存地址0x2A00000。

加载完内核镜像以及设备树之后,就可以用bootm命令启动内核了。

bootm的命令有三个参数,分别是内核镜像地址、ramdisk镜像地址和设备树地址。因此,脚本最后还做了一个逻辑判断,如果成功加载了ramdisk_image(缺省文件名为uramdisk.image.gz),就用bootm的三个参数启动内核;如果没有加载成功,就用bootm的两个参数启动内核。

由于基于分区的SD启动没有uramdisk.image.gz文件,因此R210启动时只需要加载uImage和devicetree.dtb两个文件,启动内核的命令是:bootm 0x3000000 - 0x2A00000。

总结一下,在bootm执行前,DDR内存中程序的分布如下图所示:

注意R210的DDR内存为512MB,对应的地址空间为0x00100000~0x1FFFFFFF。

2.5.5 内核运行阶段

内核运行也分为多个阶段进行,包括内核解压与启动、挂载根文件系统和运行系统服务。

2.5.5.1 内核解压与启动

2.5.5.2 挂载根文件系统

2.5.5.3 运行系统服务

这样,嵌入式Linux系统的启动过程就结束了。

最后,我们可以输入lsb_release -a命令,查看发行版信息:

root@analog:/ # lsb_release -a
No LSB modules are available.
Distributor ID:Raspbian
Description:Kuiper GNU/Linux 11.2 (bullseye)
Release:11.2
Codename:bullseye
        

说明R210的嵌入式Linux系统还是版本比较新的,相当于Ubuntu20.04的版本。

2.6 系统配置

嵌入式Linux系统第一次启动后,通常我们还需要进行系统配置,包括设置IP地址、主机名以及登录的欢迎词等工作。

Linux通过修改系统文件的方式来配置系统,我们用vi来修改文件内容。

2.6.1 设置IP

  1. 查看以太网卡名称
  2. 输入ifconfig命令,可以查看以太网卡名称。

    图中,以太网卡的名称为eth0。

  3. 修改interfaces文件
  4. 输入命令 vi /etc/network/interfaces,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。

  5. 修改DNS服务器地址
  6. DNS服务器地址由/etc目录下的resolv.conf文件存储,但这个文件在重启后会恢复默认设置,且该文件无法用下文提到的保护手段防止覆盖。为保证DNS配置永久生效,我们可以采用下面的方法:

    输入命令rm /etc/resolv.conf删除原有的resolv.conf文件。

    输入命令 vi /etc/resolv.conf,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。

    输入命令chattr +i /etc/resolv.conf,锁定/etc/resolv.conf防止意外修改。

    现在重启后,DNS服务器配置仍能生效,下图可见重启后能ping通外网。

2.6.2 设置主机名

输入命令:hostnamectl set-hostname r210,这里的r210为新的主机名。

修改/etc/hosts文件,使其配置与新的主机名一致。输入命令 vi /etc/hosts,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。

重启后修改生效。可通过命令hostnamectl查看主机名。

2.6.3 设置欢迎词

登录的欢迎词由/etc/motd文件来配置。输入命令 vi /etc/motd,按insert键进行修改。修改后的内容如下图所示。修改完毕后,按esc键退出编辑模式,输入:wq保存并退出编辑器。

重启后可见欢迎词。

2.7 小结

本章详细介绍了ZYNQ芯片嵌入式Linux系统基于分区的SD启动过程,详细分析了启动相关文件的功能,并重点解析了BOOT.BIN文件的结构。

本章还详细讲解了使用树莓派的镜像烧写工具制作TF启动卡的过程,并介绍了用终端工具xshell访问嵌入式Linux系统的方法。

本章最后介绍了使用vi更改系统文件,进行系统配置的方法。

【本章Linux命令】

命令名称 说明
cd 改变目录
ls -l 查看目录文件详细列表
mount 挂载分区
vi 文本文件编辑器
df -h 查看文件系统磁盘空间使用情况
lsblk 查看块设备信息
lsb_release -a 查看系统发行版信息

第3章 深度研究基于Ramdisk的SD启动

3.1 概述

学会了采用基于分区的TF卡来启动R210后,接下来我们介绍利用Ramdisk系统来启动R210。本章首先介绍了基于Ramdisk系统的启动过程,然后重点讲解了如何定制Ramdisk系统。定制过程在虚拟机的Linux系统上进行,还需要使用大量的Linux命令。

【windows工具】

虚拟机软件Virtualbox、FTP工具winscp

3.2 Ramdisk系统的特点

用过基于分区的TF启动卡后,我们会发现这种启动方式有如下几个特点:

一个是优点:操作系统比较完整,功能全面,甚至还安装了编译器,还可以直接编译源代码,这与树莓派的操作系统很类似。

剩下的就是缺点了:

一个缺点是启动比较慢,这是由于系统加载了很多服务。如果用于人机交互,有windows做借鉴,大家也无所谓。但如果是单一用途的设备,比如电视机、智能音箱、网关之类的设备,或者像软件无线电SDR的应用,启动慢就很难接受了。

另外一个缺点是文件系统不稳定,经常会报错。这与操作系统在TF卡上工作有很大的关系,也与操作系统的版本有很大的关系。

为了解决这两个问题,我们可以采用Ramdisk方式启动系统。所谓Ramdisk,就是用内存模拟磁盘,这样就能大幅提升系统的读写性能。

之前我们操作系统采用的是磁盘Disk系统,也就是操作系统安装在磁盘的分区上,绝大多数人的PC机也是这样使用的,也就是将windows系统或者Linux系统安装在系统分区上。

除了将分区用于系统外,我们还可以采用文件充当操作系统,也就是文件即系统的概念,大大简化了系统的安装与备份工作。

文件即系统中有大家熟悉的虚拟机系统、VHD虚拟磁盘系统,也有本章介绍的内存磁盘系统Ramdisk。

这三者都有一个共同的特点,就是用一个文件作为一套操作系统,文件存放在磁盘上。

这三者也有明显的差别:

虚拟机系统和VHD虚拟磁盘系统可以看成是一个大箱子,里面存放了操作系统的各种文件,但不进行压缩,操作系统运行时直接针对磁盘上的文件内容进行操作。

Ramdisk系统可以看成是个压缩袋,将操作系统的各种文件压缩在一起,打包成一个镜像image。每次开机时,镜像里面的文件被释放到内存中。Ramdisk系统把内存当作磁盘使用,操作系统运行时直接针对内存操作。

由于这样的特点,虚拟机系统、VHD系统一般体积比较大,都要在10GB以上,操作系统的功能完整而全面;Ramdisk系统一般体积在100MB以下,操作系统的功能比较精简。

对于嵌入式Linux系统,人机交互简单,处理的任务明确且简单,响应速度要快,这种情况下,Ramdisk系统除了消耗内存和无法持久化等少数缺点外,剩下的都是优点了。

特别是采用Ramdisk系统后,安装和维护也变得格外简单,接下来大家就可以体会了。

3.3 安装Ramdisk系统

说了这么多,怎么把我们现有的启动卡升级到Ramdisk系统呢?

很简单,我们只需要将Ramdisk系统的相关启动文件复制到TF卡上。

Ramdisk系统的启动文件包括BOOT.BIN、uEnv.txt、devicetrree.dtb、uImage和uramdisk.image.gz共5个文件,如下图所示:

 

不难看出,与分区启动相比,Ramdisk系统多了一个uramdisk.image.gz镜像文件,另外BOOT.BIN需要升级,以支持Ramdisk系统;uEnv.txt需要更新,以支持Ramdisk系统以及分区启动。

文件复制和替换完成后,再插卡上电启动,输入用户名root,密码是analog,就进入了Ramdisk系统。启动后的界面如下图所示。

整个安装过程不到1分钟,非常简单、快捷,比之前制作基于分区的TF启动卡要方便很多。

另外,Linux系统启动过程也明显快于基于分区的启动。

接下来,就来解答大家的疑问,Ramdisk系统是如何启动的。

3.4 Ramdisk系统的启动过程

下图是从ZYNQ7000技术手册中摘录的Ramdisk系统的启动过程,从下图不难看出,Ramdisk系统的启动与基于分区的启动过程是一模一样的,只是最后加载根文件系统时,由从分区加载改为从uramdisk.image.gz镜像文件加载。

那么,R210是怎么知道要修改root文件系统的加载方式呢?

关键在uEnv.txt这个文件的内容发生了变化。

从uEnv.txt的内容不难看出,与基于分区启动的uEnv.txt一样,基于Ramdisk系统的uEnv.txt也定义了一个环境变量uenvcmd和一个脚本函数adi_sdboot:

与基于分区的adi_sdboot脚本相比,基于Ramdisk的adi_sdboot脚本在判断语句中增加了bootargs启动参数的设置,这样如果TF卡上有uramdisk.image.gz文件,就会设置基于Ramdisk的启动参数(“root=/dev/ram0 rootfstype=ramfs”)并执行Ramdisk系统启动;如果没有uramdisk.image.gz文件,就会设置基于分区的启动参数(“root= root=/dev/mmcblk0p2 rootfstype= ext4”)并执行基于分区的启动。

if fatload mmc 0 0x2000000 ${ramdisk_image}; then setenv bootargs console=ttyPS0,115200 root=/dev/ram0 rootfstype=ramfs rw earlycon rootwait bootm 0x3000000 0x2000000 0x2A00000; else setenv bootargs "console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait  bootm 0x3000000 - 0x2A00000; fi>

另外一方面, uboot的bootm指令支持从Ramdisk文件中加载root文件系统并启动,从而实现了Ramdisk镜像文件的加载。

当然,也不是所有版本的uboot都能支持启动Ramdisk系统,这也是为什么要替换掉原厂提供的BOOT.BIN的原因。

根据bootm指令,我们不难做出Ramdisk镜像文件在DDR内存中的加载位置,如下图所示。

加载了uramdisk.image.gz镜像文件后,R210根据启动参数bootargs的定义,首先会对镜像文件进行解压和解包,解包后得到的根系统释放到DDR内存中,形成了Ramdisk系统。

接下来,R210调用Ramdisk系统中的初始化脚本init,进行系统的初始化。

之后,R210查找/etc/inittab并执行,完成基础文件系统挂载、设备节点初始化和执行系统服务脚本。

这样,启动过程就完成了。

3.5 定制Ramdisk系统

前面也讲了,Ramdisk系统的缺点是无法持久化,这是由于启动的时候,系统是从镜像文件uramdisk.image.gz解压而来的,因此每次启动的系统都相同,这也就意味着系统运行时对系统的更改无法保存。

这很容易理解,要想保存更改,每次关机时就必须保存系统的内容并进行打包和压缩,重新生成镜像文件,势必消耗消耗大量的时间。用户不能忍受开机时间过长,同样也不能忍受关机时间过长。

但是,如果不能保存系统的更改,如果每次启动后都需要去重新配置系统,也是很麻烦的。

为此,我们可以在Ramdisk镜像中预先设置好相关的配置,比如IP地址、主机名等,生成一个定制后的Ramdisk镜像文件,这样每次开机后就不用再调整配置,节省了大量的时间。

3.5.1 工作环境

为了保证配置的正确性,我们需要在Linux系统上定制Ramdisk镜像文件。考虑到我们的工作电脑是Windows系统,因此简单的办法是安装VirtualBox软件,使用VirtualBox软件生成Ubuntu虚拟机,在Ubuntu虚拟机上定制Ramdisk镜像文件。

关于VirtualBox软件的安装,已经有大量的参考资料,这里就不赘述了。

我们需要生成的Ubuntu虚拟机,版本为18.04。之所以不用最新的版本,是因为我们的任务比较简单,没有必要使用更高的版本。

Ubuntu虚拟机需要安装openssh以及ftp,这样才能作为我们定制镜像的环境。

完成Ubuntu18.04安装工作,并能通过xshell访问Ubuntu虚拟机后,接下来我们需要安装FTP工具WinSCP。

下图是WinSCP的界面,镜像文件传入用户home目录下的Downloads目录。

接下来我们通过xshell连接虚拟机,在命令行中操作Ramdisk镜像文件。

定制的过程分为如下几个步骤:去头并解压、解包并设置权限、修改镜像、重新打包、重新压缩和重新加头。

3.5.2 去头并解压

镜像文件uramdisk.image.gz有一个64字节的头,供U-Boot加载时使用。不去掉这个头,镜像文件就无法解压。因此我们需要先通过dd命令去掉这64字节的头,以得到标准的gz压缩文件,具体操作步骤参考下面的命令:

sdr@Vbox:~/$ cd Downloads
sdr@Vbox:~/Downloads$ dd if=uramdisk.image.gz of=ramdisk.image.gz bs=64 skip=1
sdr@Vbox:~/Downloads$ ls -l *.gz
-rw-rw-r-- 1 sdr sdr 3303042 8月 13 16:54 uramdisk.image.gz
-rw-rw-r-- 1 sdr sdr 3302978 8月 13 22:24 ramdisk.image.gz

不难看出ramdisk.image.gz比uramdisk.image.gz少了64个字节。

得到标准的gz压缩文件ramdisk.image.gz后,我们可以用gunzip命令解压ramdisk.image.gz:

sdr@Vbox:~/Downloads$ gunzip ramdisk.image.gz
sdr@Vbox:~/Downloads$ ls -l *.image
-rw-rw-r-- 1 sdr sdr 6860288 8月 13 22:24 ramdisk.image 

解压后得到ramdisk.image,这就是打包的镜像文件ramdisk.image

3.5.3 解包并设置权限

接下来,我们创建工作目录~/work/zynq/ramfs,将ramdisk.image解包到工作目录。

sdr@Vbox:~/$ cd ..
sdr@Vbox:~/$ mkdir -p work/zynq/ramfs
sdr@Vbox:~/$ cd work/zynq/ramfs

由于镜像采用CPIO格式打包,因此解包采用cpio命令:

sdr@Vbox:~/work/zynq/ramfs$ cpio -idv < ~/Downloads/ramdisk.image

cpio命令选项说明

-i:解包模式

-d:自动创建子目录

-v:显示详细信息

< ../ramdisk.image:从上级目录读取归档文件

解包后,还需要修改文件的权限,全部置为root用户。如果不修改权限,重置的镜像文件启动将会报错。

sdr@Vbox:~/ work/zynq/ramfs$ cd ..
sdr@Vbox:~/work/zynq/ $ sudo chown -R root:root ramfs

3.5.4 修改镜像

3.5.4.1 修改IP地址

镜像文件uramdisk.image.gz设置的IP地址是192.168.1.189,我们可以修改这个IP地址以适应运行环境。

假定我们需要把IP地址修改为192.168.1.186,具体做法是修改工作目录下etc/network下的interfaces文件。

我们采用系统自带的文本编辑工具nano修改文件,比起vi更简单,命令如下:

sdr@Vbox:~/work/zynq/ramfs/etc/network$ sudo nano interfaces

可以看到如下的nano界面:

在nano界面中,我们找到192.168.1.189,将其改为192.168.1.186。修改后直接输入Ctrl+O,保存修改的结果。

3.5.3.2 设置自动挂接分区

Ramdisk系统使用了TF卡,TF卡的分区可以通过mount命令挂接到系统中,方便进行文件管理。我们可以设置自动挂接分区,就不用每次启动后输入mount命令了。

设置自动挂接分区的具体做法是修改工作目录下etc下的fstab文件,还是采用nano工具,输入如下命令:

sdr@Vbox:~/work/zynq/ramfs/ $ sudo nano etc/fstab

上图是已经设置自动挂接分区的fstab文件,不难看出挂载了TF卡的两个分区。

3.5.3.3 设置启动自动运行脚本

设置启动自动运行脚本的目的是简化系统的配置,避免反复打包镜像文件。

简单地说,就是把自动运行脚本init.sh放到了TF卡的BOOT分区中,方便修改和调整。

设置自动挂接分区的具体做法分两步,先是修改工作目录下etc下的inittab文件,还是采用nano工具,输入如下命令:

sdr@Vbox:~/work/zynq/ramfs/ $ sudo nano etc/inittab

然后在inittab中增加一行,如下图最后一行所示:

接下来修改工作目录下etc下的rc.local文件,还是采用nano工具,输入如下命令:

sdr@Vbox:~/work/zynq/ramfs/ $ sudo nano etc/rc.local

然后在rc.local中增加如下图所示的内容:

5.4.3.4 更新主机名、登录提示

如果我们需要交付产品,那么主机名、登录提示都是需要设置的。

设置主机名的第一步是修改工作目录下etc下的hostname文件,将其改为所需的主机名,如R210,如下图所示:

接下来修改工作目录下etc下的hosts文件,将第二行的内容改成R210,如下图所示:

设置登录提示是修改工作目录下etc下issue文件,将其改为所需要提示的内容:

3.5.5. 重新打包

完成了系统的定制,接下来可以重新打包Ramdisk系统。

打包还是利用cpio这个命令,先回到工作目录,然后运行如下命令:

sdr@Vbox:~/work/zynq/ramfs $ find . | sudo cpio -ov -H newc > ../ramdisk.image

cpio选项说明

-o:创建归档

-v:显示详细信息

-H newc:指定为 SVR4 格式(兼容 ZYNQ 启动要求)

> ../ramdisk.image:输出到上级目录的新文件

打包过程如下图所示,打包后得到了镜像文件ramdisk.image:

3.5.6. 重新压缩

接下来,我们用压缩命令gzip压缩镜像文件ramdisk.image,参数-9代表使用最大压缩率。

sdr@Vbox:~/work/zynq$ gzip -9 ramdisk.image
sdr@Vbox:~/work/zynq$ ls -l
-rw-rw-r--  1 sdr  sdr  3312157 8月  14 11:01 ramdisk.image.gz

3.5.7. 重新加头

最后,我们要给镜像压缩文件ramdisk.image.gz加头,这样uboot才能加载镜像压缩文件。

加头用到了专门的mkimage命令,使用这个命令需要先安装u-boot-tools。

sdr@Vbox:~/work/zynq$ sudo apt install u-boot-tools

安装完毕后输入如下的加头命令:

sdr@Vbox:~/work/zynq$ mkimage -A arm -T ramdisk -C gzip -n Ramdisk -d ramdisk.image.gz 

输出如下图所示:

3.5.8. 更新Ramdisk系统

得到了定制过uramdisk.image.gz文件后,我们有两种方法部署到TF卡上。

一种是在R210断电后,拔出TF卡再接入Windows系统,用第3.3节讲到的文件复制的方法更新uramdisk.image.gz。

另外一种是R210开机状态,如果已经将TF卡的分区挂接了到了/boot,用FTP工具就可以把uramdisk.image.gz文件上传到/boot,直接更新了。

更新uramdisk.image.gz后,下次重启时就会进入到更新的系统,可以看到修改的效果。

3.6 小结

本章介绍了Ramdisk系统的特点以及安装方法,详细介绍了ZYNQ芯片嵌入式Linux系统基于Ramdisk系统的SD启动过程,详细分析了启动相关文件的功能。

本章重点讲解了Ramdisk系统的定制方法,利用虚拟机更新Ramdisk系统文件,从而实现了系统配置。

【Linux命令】

命令名称 说明
ip a 查看IP配置
dd 数据复制与转换
chown 改变文件所有者
mkdir 创建目录
cpio 创建和提取归档文件
gunzip 解压文件
gzip 压缩文件
apt install 安装软件包


第4章 深度研究QSPI启动

4.1 概述

R210作为基于ZYNQ7000系列的SDR板卡,支持SD、QSPI和JTAG等启动方式。前面两章已经介绍了SD启动方式,这一章就来讲解QSPI启动方式。

首先介绍一下QSPI这个术语。QSPI是从SPI发展而来的,SPI(Serial Peripheral Interface,串行外设接口)是摩托罗拉公司在1970年代研发的串行接口技术,特点是用4线(CLK、CS、MOSI和MISO)实现了高达10Mbps的串行高速数据传输,遥遥领先于传统的RS232串行接口,因此在单片机领域得到了广泛的应用。

QSPI是QUAD SPI,四路串行外设接口的缩写。QSPI接口从SPI接口的4线扩展到了6线,数据线从2根扩充到了4根。QSPI采用半双工的模式,单向最高吞吐率可达40Mbps,从而进一步了提升传输速率。

在R210中,QSPI接口用于连接Flash芯片W25Q256,一款容量为32MB的可擦写的非易失存储芯片。

Flash芯片有NOR和NAND两种技术,intel公司1988年开发出了NOR flash技术。NOR的特点是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在flash 闪存内运行,不必再把代码读到系统RAM中。NOR的传输效率很高,在MB级的小容量时具有很高的成本效益,但是其较低的写入和擦除速度大大影响了它的性能。

相反地,1989年东芝公司发表了NAND flash技术。NAND内部采用非线性宏单元模式,容量大,改写速度快,为固态大容量内存的实现提供了廉价有效的解决方案,手机、平板的GB级别Flash一般都采用NAND技术。

W25Q256这块Flash芯片显然是采用了NOR技术,我们可以在Flash芯片中烧录嵌入式Linux系统,以支持QSPI启动。

本章将围绕Flash,介绍QSPI的启动过程以及定制QSPI启动的方法。

4.2 Flash的结构

Flash芯片类似于硬盘,可以被分区。

在嵌入式linux系统,Flash的分区称为MTD(Memory Technology Device)分区,而MTD是Linux系统用来管理Flash的子系统。

接下来我们来了解R210的Flash芯片的具体分区情况。

我们首先把R210的拨码开关打到QSPI启动,再用Type-C数据线连接R210的UART口与电脑的USB口,R210开始上电启动。

我们登入嵌入式linux系统后,输入如下命令:

可以看到R210有4个MTD分区,还可以看到每个MTD分区的编号、名称以及大小。

我们再输入如下命令,还可以看到R210每个MTD分区的起始地址:

这样,我们就可以做出MTD分区的分布图,如下图所示:

我们还可以输入mtdinfo命令,查看mtd分区的详细信息:

总之,从分区名称可以看到,qspi-fsbl-uboot相当于SD启动的BOOT.BIN,qspi-uboot-env相当于SD启动的uEnv.txt。至于内核以及根文件系统对应另外哪个分区,似乎还很难判断。

接下来,我们读取Flash各个MTD分区,解析其内容,来分析出各个MTD分区的功能。

4.3 导出MTD分区

4.3.1 导出分区

我们使用dd命令导出MTD分区的内容,分4次得到4个MTD分区的内容:

4.3.2 查看内容

首先查看mtd0分区的内容,也就是mtd0.bin的内容。

我们采用xxn与head两个命令的组合,用来以十六进制显示mtd0.bin的内容。不难看出,mtd0.bin与BOOT.BIN的文件头是一致的,等价于BOOT.BIN:

接着查看mtd1.bin的内容,我们还是采用xxn与head两个命令的组合,用来以十六进制显示mtd1.bin的内容。不难看出,mtd1.bin就是环境变量,等价于uEnv.txt。

4.3.3 复制内容

接下来,我们把MTD分区的内容复制到TF卡上,方便进行后续的分析。

首先插入TF卡,然后输入如下命令,将4个分区bin文件复制到TF卡上:

mkdir /boot
mount /dev/mmcblk0p1 /boot
cd /boot
cp /root/*.bin .
umount /boot
cd /
umount /boot

之后,我们在windows系统中读取TF卡,用winscp工具,将这4个文件传入虚拟机中。

4.4 解析分区镜像

得到了各个分区的镜像内容,接下来就进行镜像内容的解析。

4.4.1 mtd0.bin

mtd0.bin与boot.bin一样,也是多个分区镜像的组合,这些分区镜像包括fsbl和uboot,其组成方式与用于SD启动的boot.bin完全一致,也就是利用文件头来定义分区结构。

利用bootinfo工具可以解析mtd0.bin的格式,其输出如下:

Image Header Table:
  Partition Count: 2

   PARTITION HEADER TABLE (fsbl.elf)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x00006002  unencrypted_length (0x04) : 0x00006002
        total_length (0x08) : 0x00006002           load_addr (0x0c) : 0x00000000
           exec_addr (0x10) : 0x00000000    partition_offset (0x14) : 0x000005c0
          attributes (0x18) : 0x00000010       section_count (0x1C) : 0x00000001
     checksum_offset (0x20) : 0x00000000          iht_offset (0x24) : 0x00000240
           ac_offset (0x28) : 0x00000000            checksum (0x3c) : 0xfffed7e8
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-32]     dest_device [PS]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (u-boot.elf)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x00019699  unencrypted_length (0x04) : 0x00019699
        total_length (0x08) : 0x00019699           load_addr (0x0c) : 0x04000000
           exec_addr (0x10) : 0x04000000    partition_offset (0x14) : 0x000065d0
          attributes (0x18) : 0x00000010       section_count (0x1C) : 0x00000001
     checksum_offset (0x20) : 0x00000000          iht_offset (0x24) : 0x00000250
           ac_offset (0x28) : 0x00000000            checksum (0x3c) : 0xf7fad403
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-32]     dest_device [PS]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------

从解析的内容可以看到,与SD启动的boot.bin不同, mtd0.bin只有fsbl和uboot,不包含FPGA比特流。

根据解析的结果,可以做出mtd0.bin的结构,如下图所示:

4.4.2 mtd1.bin

mtd1.bin存放4字节的头和uboot使用的环境变量。

4个字节的头固定为0xb56eb45b(小端序存储为 5b b4 6e b5),头之后就是环境变量的内容。

4.4.3 mtd2.bin

在虚拟机中运行file命令,可以查看文件的格式:

file mtd2.bin
mtd2.bin: Linux jffs2 filesystem data little endian

从显示的内容不难看出,MTD2分区安装了jffs2系统。

jffs2的全称是Journalling Flash File System Version 2,这是由Red Hat在JFFS基础上开发的第二代日志结构文件系统,专为NOR闪存设备设计。

简单地说,jffs2文件系统相当于把Flash变成了TF卡,实现了文件的存储与读写。

4.4.4 mtd3.bin

最后我们再来看mtd3.bin。mtd3.bin与mtd0.bin类似,也是各种部件打包的组合,这些部件包括kernel、FPGA比特流、设备树以及ramdisk文件系统。

打包采用了FIT(Flattened Image Tree,扁平化镜像树)打包格式,其特点有:

查看打包结构前需要先在虚拟机安装uboot-tools,如果已经安装了可以忽略这一步:

sudo apt install uboot-tools

接下来输入dumpimage -l mtd3.bin,就可以看到mtd3.bin对应的FIT的组成:

FIT description: R210 Firmware with FPGA, Kernel, Ramdisk
Created:         Tue Aug  5 13:50:10 2025
 Image 0 (fdt@1)
  Description:  r210
  Created:      Tue Aug  5 13:50:10 2025
  Type:         Flat Device Tree
  Compression:  uncompressed
  Data Size:    22489 Bytes = 21.96 KiB = 0.02 MiB
  Architecture: ARM
  Hash algo:    sha1
  Hash value:   ab1edbc6f1790377782192efd80cba8b0e687532
 Image 1 (fpga@1)
  Description:  FPGA Bitstream
  Created:      Tue Aug  5 13:50:10 2025
  Type:         FPGA Image
  Compression:  uncompressed
  Data Size:    2363432 Bytes = 2308.04 KiB = 2.25 MiB
  Load Address: 0x0f000000
  Hash algo:    sha1
  Hash value:   b4e566833796ab1a6466f373e79715654ef4baaf
 Image 2 (kernel@1)
  Description:  Linux Kernel
  Created:      Tue Aug  5 13:50:10 2025
  Type:         Kernel Image
  Compression:  uncompressed
  Data Size:    4783832 Bytes = 4671.71 KiB = 4.56 MiB
  Architecture: ARM
  OS:           Linux
  Load Address: 0x00008000
  Entry Point:  0x00008000
  Hash algo:    sha1
  Hash value:   2639b862ee70c89a1e94ab330d4540d4f4ccd9ed
 Image 3 (ramdisk@1)
  Description:  RootFS
  Created:      Tue Aug  5 13:50:10 2025
  Type:         RAMDisk Image
  Compression:  gzip compressed
  Data Size:    5429282 Bytes = 5302.03 KiB = 5.18 MiB
  Architecture: ARM
  OS:           Linux
  Load Address: unavailable
  Entry Point:  unavailable
  Hash algo:    sha1
  Hash value:   8b7e1746af5bc80a438ecc0f08bb1bd1ccc5840f
 Default Configuration: 'config@1'
 Configuration 0 (config@1)
  Description:  R210 Hardware
  Kernel:       kernel@1
  Init Ramdisk: ramdisk@1
  FDT:          fdt@1
  FPGA:         fpga@1

不难看出这是个简化版本的FIT镜像,只有一套配置,里面的部件包括kernel、FPGA比特流、设备树以及ramdisk文件系统。

我们还可以根据FIT的信息,用dumpimage命令提取出FIT各个部件:

dumpimage -T flat_dt -p 0 -o devicetree.dtb ./mtd3.bin
dumpimage -T flat_dt -p 1 -o fpga.bit ./mtd3.bin
dumpimage -T flat_dt -p 2 -o zImage ./mtd3.bin
dumpimage -T flat_dt -p 3 -o ramdisk.image.gz ./mtd3.bin

其中,devicetree.dtb为设备树,fpga.bit为FPGA比特流,zImage为Linux内核,ramdisk.image.gz为ramdisk文件系统。

值得注意的是:

4.5 QSPI启动过程

讲解了Flash的结构,接下来我们来了解QSPI的启动过程。QSPI的启动过程如下图所示,不难看出与SD启动的过程是一致的,也是分为ROM启动→FSBL→U-Boot→内核运行等四个阶段:

4.5.1 BootROM阶段

BootROM是固化在ZYNQ芯片内部的不可修改代码,负责最基础的硬件初始化和FSBL加载:

以下是R210Flash中的mtd0.bin的头部内容

xxd mtd0.bin | head
00000000: feff ffea feff ffea feff ffea feff ffea ................
00000010: feff ffea feff ffea feff ffea feff ffea ................
00000020: 6655 99aa 584e 4c58 0000 0000 0000 0101  fU..XNLX........
00000030: 0017 0000 0880 0100 0000 0000 0000 0000 ................

可见BOOTROM中与加载fsbl相关字段的内容如下:

字段 代码偏移地址 代码长度 加载OCM地址 执行地址
字节数 0x1700(5888) 0x18008(98312) 0 0

4.5.2 FSBL阶段

FSBL是用户可修改的启动加载程序,主要完成更复杂的硬件初始化和后续镜像加载:

fsbl通常还包括加载FPGA比特流的工作,也就是通过PCAP接口将FPGA的比特流(.bit文件)配置到PL端。但是R210的BOOT.BIN中并没有包含FPGA的比特流,这项工作挪到后续阶段进行。

fsbl根据BOOT.BIN的镜像表,找到u-boot.elf所在的分区,得到偏移量为0x330。

000008c0: 0000 0201 0200 0000 2003 0000 4002 0000  ........ ...@...
000008d0: 0000 0000 ffff ffff ffff ffff ffff ffff  ................
000008e0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
000008f0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000900: 5002 0000 2003 0000 0000 0000 0100 0000  P... ...........
00000910: 6c62 7366 666c 652e 0000 0000 0000 0000  lbsffle.........
00000920: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000930: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000940: 0000 0000 3003 0000 0000 0000 0100 0000  ....0...........
00000950: 6f62 2d75 652e 746f 0000 666c 0000 0000  ob-ue.to..fl....

根据偏移量,提取出U-Boot分区表的信息:

00000cc0: b195 0100 b195 0100 b195 0100 0000 0004 ................ 
00000cd0: 0000 0004 d065 0000 1000 0000 0100 0000 .....e.......... 
00000ce0: 0000 0000 5002 0000 0000 0000 0000 0000 ....P........... 
00000cf0: 0000 0000 0000 0000 0000 0000 bbd6 faf7 ................

可见fsbl加载U-Boot的关键字段的内容如下:

字段 代码偏移 代码长度 加载DDR地址 执行地址
数值 104356(4*0x65d0) 415428(4*0x195b1) 0x4000000 0x4000000

4.5.3 U-Boot阶段

U-Boot作为第二阶段的Bootloader,负责加载操作系统内核:

U-Boot启动过程与环境变量uenv密切相关,环境变量包括变量以及命令脚本两种类型。

在R210中,QSPI的启动过程执行如下工作:

初始化SPI接口:

下面这个命令的作用是初始化QSPI Flash控制器,准备进行后续的Flash操作。Flash控制器是由SPI接口连接的,所以需要先初始化SPI接口

sf probe 0:0 50000000 0

加载Flash的内容:

下面这个命令的作用是从QSPI Flash的2MB偏移处读取一个FIT镜像到DDR内存中。参考4.2节的内容,2MB的地址正好是MTD3分区(qspi-linux)的起始位置。

sf read ${fit_load_address} 0x200000 ${fit_size}

设置内核启动参数:

内核启动参数启动参数如下:

console=ttyPS0,115200 maxcpus=${maxcpus} rootfstype=ramfs root=/dev/ram0 rw quiet loglevel=4 clk_ignore_unused uboot="${uboot-version}"

从内核启动参数不难看出QSPI启动采用Ramdisk的系统。

启动内核:

bootm ${fit_load_address}#${fit_config}

通过从环境变量获取fit_load_address和fit_config,其中fit_config=config@1,fit_load_address=0x2080000

4.5.4 内核运行阶段

这一段的运行与基于Ramdisk的SD启动的内核运行阶段是一样的,这里就不赘述了。

4.6 定制系统参数

与之前两章类似,我们可以通过修改镜像文件的内容来定制系统参数,这就涉及到修改MTD3的内容。

4.6.1 解包Flash

首先我们用4.3.1节的方法导出MTD3的内容,用4.4.4节的方法提取出ramdisk.image.gz文件。

接下来用前一章的方法将ramdisk.image.gz文件并解包到工作目录。

修改工作目录中的系统配置。

4.6.2 打包根系统

修改完成后,还是采用上一章的方法将工作目录重新打包压缩成ramdisk.image.gz。

4.6.3 创建FIT配置

在打包FIT镜像前,需要创建配置文件r210.its,完整内容如下:

    /dts-v1/;

/ {
    description = "R210 Firmware with FPGA, Kernel, Ramdisk";
    #address-cells = <1>;

    images {
        fdt@1 {
            description = "R210 Device Tree";
            data = /incbin/("./devicetree.dtb");
            type = "flat_dt";
            arch = "arm";
            compression = "none";
            hash@1 {
                algo = "sha1";
            };
        };

        fpga@1 {
            description = "FPGA Bitstream";
            data = /incbin/("./fpga.bit");
            type = "fpga";
            arch = "arm";
            compression = "none";
            load = <0x0f000000>;
            hash@1 {
                algo = "sha1";
            };
        };

        kernel@1 {
            description = "Linux Kernel";
            data = /incbin/("./zImage");
            type = "kernel";
            arch = "arm";
            os = "linux";
            compression = "none";
            load = <0x00008000>;
            entry = <0x00008000>;
            hash@1 {
                algo = "sha1";
            };
        };

        ramdisk@1 {
            description = "RootFS";
            data = /incbin/("./ramdisk.image.gz");
            type = "ramdisk";
            arch = "arm";
            os = "linux";
            compression = "gzip";
            hash@1 {
                algo = "sha1";
            };
        };
    };

    configurations {
        default = "config@1";

        config@1 {
            description = "R210 Hardware";
            kernel = "kernel@1";
            fdt = "fdt@1";
            ramdisk = "ramdisk@1";
            fpga = "fpga@1";
        };
    };
};

4.6.4 打包FIT镜像

如果FIT镜像的部件都准备好了,打包是非常快的,只需要输入如下命令:

mkimage -f r210.its r210fit.bin

将打包后得到的r210fit.bin利用winscp工具拷到TF卡的根目录下。

4.6.5 烧录FIT镜像

r210启动后,输入如下命令:

cd  /boot
flashcp -v r210fit.bin /dev/mtd3

就实现了烧录镜像的工作。

4.7 文件的持久化

QSPI启动方式的最大优点是摆脱了TF卡,简化了系统硬件设备,系统也变得更可靠了,特别适合嵌入式设备。

但是“成也萧何,败也萧何”,没有了TF卡,一旦掉电,运行数据就会丢失,不方便进行分析和维护。

为此,可以采用前面介绍的jffs2文件系统,利用jffs2文件系统的非易失性,实现关键文件的持久化保存。

在我们的系统中,jffs2文件系统挂接到了/mnt/jffs2目录。不过由于NOR芯片擦写次数以及分区大小的限制,建议只用于保存最关键的动态配置以及最新的应用程序,而不是用于保存日志文件。

4.8 小结

本章介绍了Flash芯片的数据结构,详细介绍了ZYNQ芯片嵌入式Linux系统的QSPI启动过程,详细分析了各个分区镜像的结构。

本章重点讲解了Flash芯片的定制方法,通过打包FIT镜像并进行烧录,从而实现了QSPI启动。

【Linux命令】

命令名称 说明
xxd 查看文件二进制内容
head 查看文件前部内容
cat 查看文件内容
dmesg 查看系统启动日志
mtdinfo 查看MTD分区信息
umount 取消分区挂载
cp 复制文件
dumpimage 导出镜像内容

 

第5章 用FIT镜像实现统一启动

5.1 概述

经过前面三章的学习和实操,相信大家对ZYNQ7000开发板的各种启动方式已经很熟悉了,并且也能实现自定义启动效果了。

接下来我们就对前面的内容进行一次大综合,同时也是对前面所学内容的一次小结。

综合的主角就是FIT镜像,所谓FIT,就是扁平化镜像树,类似于linux的tar命令,把kernel、FPGA比特流、设备树以及ramdisk文件系统打包到一起。

我们需要做的是利用TF卡上的FIT镜像启动Ramdisk系统,这样TF卡上的启动文件数量大幅减少,只有boot.bin、uEnv.txt以及r210fit.bin三大文件。

另外,由于QSPI启动方式使用的也是FIT镜像,两种方式的FIT镜像可以实现统一,使用同一个r210fit.bin镜像文件,这样就可以在烧录Flash前充分验证FIT镜像的启动效果,避免反复烧录的麻烦。

5.2 FIT镜像的SD启动

要想实现FIT镜像的SD启动,需要做两件事:一件是制作FIT镜像,另外一件是修改uEnv.txt。

制作FIT镜像的过程就是4.6节中介绍的打包FIT镜像过程,最后生成r210fit.bin文件。

接下来,我们需要修改第三章用到的uEnv.txt,将其内容替换为:

bootcmd=run fitboot
fit_load_address=0x2080000
fit_config=config@1
fit_image= r210fit.bin
fitboot=echo Copying fit image from SD to RAM... && fatload mmc 0 ${fit_load_address} ${fit_image} && setenv bootargs "console=ttyPS0,115200 maxcpus=2 rootfstype=ramfs root=/dev/ram0 rw earlyprintk " && bootm ${fit_load_address}#${fit_config}

替换后的uEnv.txt首先定义了三个参数fit_load_address 、fit_config和fit_image,用来指定FIT镜像加载到DDR内存的地址、FIT镜像选用的配置以及FIT镜像的文件名。

接下来定义了启动脚本fitboot,主要完成三项工作:

1. 将TF卡上的FIT镜像加载到DDR内存中。

fatload mmc 0 ${fit_load_address} ${fit_image}

2. 设置启动参数bootargs,传递给内核。

setenv bootargs "console=ttyPS0,115200 maxcpus=2 rootfstype=ramfs root=/dev/ram0 rw earlyprintk "

3. 用内存中的FIT镜像的指定配置启动,对应kernel、FPGA比特流、设备树以及ramdisk文件系统的组合。

 bootm ${fit_load_address}#${fit_config}

最后,把r210fit.bin和uEnv.txt复制到TF的boot分区,将r210的拨码开关打到SD启动,上电后就可以实现FIT镜像的启动了。

5.3 FIT镜像的启动过程

FIT镜像的启动过程与第三章介绍的基于RAMdisk的启动过程基本相同,这里就不赘述了。

需要强调一点,尽管SD启动与QSPI启动使用了同一个FIT镜像,但是启动过程还是有差别的,突出表现在对Flash设备的处理上:

SD启动时只需要初始化Flash设备的驱动器,也就是SPI的控制器;QSPI启动不光要初始化Flash设备的驱动器,还要使用Flash设备的驱动器读取Flash的内容。虽然看起来差别不大,但是会带来不同的效果。

5.4 小结

本章介绍了利用TF卡上FIT镜像文件来启动嵌入式Linux系统的方法。