Zynq-Linux移植学习笔记之四-fsbl

2024-03-16 17:30

本文主要是介绍Zynq-Linux移植学习笔记之四-fsbl,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这一篇讲一讲FSBL

1、  FSBL简介

在zynq上运行程序的时候,加载过程中肯定需要用到一个文件,那就是fsbl,fsbl的全称为first stage boot loader,从字面上就能够看出这是zynq启动第一阶段的加载程序,经过了fsbl这一阶段,后面系统才能够运行裸奔程序或者是引导操作系统的u-boot。启动过程如下图:


在上图中,Boot Rom是直接固化在zynq硬件中的,开发者无法更改,fsbl.elf可以在Xilinx的SDK中进行修改。


2、  FSBL代码过程(参考FSBL代码导读)

打开zynq_fsbl_bsp——>ps7_cortexa9_0——>libsrc——>standalone_v3_07_a——>src文件夹,里面有一个asm_vector.S文件,这个文件

声明了一个代码段,位于地址0处。开机之后,PS自动执行地址0处的指令,其第一句话就是一个跳转:B  _boot。如下:


于是就跳转到boot.S中执行_boot标号下的代码了,_boot会对系统做初始化,当它执行完后,PS将具备执行C代码的能力,接着在_boot的代码中,再次执行了一个跳转:


_start标号位于xil.crt0.S中,仍然对系统进行设置,我们看到,第一句话就是跳转到_cpu_init去执行cpu初始化。代码部分截图如下:


  在_start的末尾,BSP终于完成了自己的工作,PS将跳转到main函数开始执行。如下:


终于系统进入了FSBL阶段。我们打开zynq_fsbl——>src文件夹,然后打开main函数:


Main函数首先是一些宏定义,接下来就是执行ps7_int()函数。SDK是一个很智能的工具,图中的灰色阴影部分是SDK判断出了PEEP_CODE这个宏没有定义,所以用灰颜色提示读者这段代码不用执行。

ps7_init函数位于ps7_init.c文件中。这个C文件是由XPS根据用户的配置自动生成的。我们进入ps7_init函数看一下,这个函数很短:


根据代码,很明显可以猜到,ps7_init函数其实执行了mio,pll,clock,ddr和某些外设的初始化。

 

我们接着看FSBL的main函数,根据XPS自动生成的ps7_init.c执行完初始化之后,FSBL将根据启动状态寄存器判断是采用的哪种启动模式。有四种启动模式,分别是QSPI,NOR,JTAG,SD卡等模式。每种模式都有一段独立的代码,举个例子,我们看SD卡模式的执行代码:


可以看到,系统先对SD卡初始化,并且要求SD卡中必须要有BOOT.BIN文件。如果没有,那么从SD卡启动板子就会失败。这也是为什么我们生成启动镜像必须命名为BOOT.BIN的原因。需要注意的是NAND启动模式被禁用了,因为FSBL中有一个宏没有定义,所以处于灰色状态。接下来的代码我们只看一下注释就大概知道干什么了:


 在明确了启动模式之后,PS将在相应的flash中去寻找.bit文件和用户程序,通过遍历一些partition(最多15个),如果找到了.bit文件,那么就不重启,直接配置PL,然后再找用户程序。如果没找到.bit就软件复位一下,然后调整地址,接着验证下一个partition。充分体现这个过程的,是这个函数:


PartitionMove函数很复杂,我们只看注释:


这个注释说的就是找到了.bit文件就配置PL,找到了用户程序就加载到内存。这个函数执行完之后,返回值就是用户程序的执行地址。这个执行地址位于用户文件的文件头中,是由编译器或者ISE自己生成的。

找到了用户程序的执行地址,那么FSBL函数就该交接了,完成这个过程的是FsblHandoff函数:


这个函数完成交接,并且一去不复返,再也不会返回,从此PS就开始执行了用户代码。那么交接究竟是怎么完成的?其实我们直观上很容易猜到肯定是一个跳转指令。带着猜测,我们深入FsblHandoff函数,最后果然找到了:


其中bx    lr指令就是跳转到用户代码执行。


3、  FSBL中对DDR的初始化

在fsbl main函数执行过程中很重要的一步是对DDR进行初始化,这里调用了ps7_init这个函数,该函数根据PS的类型进行MIO,PLL,CLOCK,DDR一系列参数的设定,代码如下:

Int ps7_init() 
{// Get the PS_VERSION on run timeunsigned long si_ver = ps7GetSiliconVersion ();int ret;if (si_ver == PCW_SILICON_VERSION_1){ps7_mio_init_data = ps7_mio_init_data_1_0;ps7_pll_init_data = ps7_pll_init_data_1_0;ps7_clock_init_data = ps7_clock_init_data_1_0;ps7_ddr_init_data = ps7_ddr_init_data_1_0;ps7_peripherals_init_data = ps7_peripherals_init_data_1_0;} 
else if (si_ver == PCW_SILICON_VERSION_2) {ps7_mio_init_data = ps7_mio_init_data_2_0;ps7_pll_init_data = ps7_pll_init_data_2_0;ps7_clock_init_data = ps7_clock_init_data_2_0;ps7_ddr_init_data = ps7_ddr_init_data_2_0;ps7_peripherals_init_data = ps7_peripherals_init_data_2_0;}
else {ps7_mio_init_data = ps7_mio_init_data_3_0;ps7_pll_init_data = ps7_pll_init_data_3_0;ps7_clock_init_data = ps7_clock_init_data_3_0;ps7_ddr_init_data = ps7_ddr_init_data_3_0;ps7_peripherals_init_data = ps7_peripherals_init_data_3_0;}// MIO initret = ps7_config (ps7_mio_init_data);  if (ret != PS7_INIT_SUCCESS) return ret;// PLL initret = ps7_config (ps7_pll_init_data); if (ret != PS7_INIT_SUCCESS) return ret;// Clock initret = ps7_config (ps7_clock_init_data);if (ret != PS7_INIT_SUCCESS) return ret;// DDR initret = ps7_config (ps7_ddr_init_data);if (ret != PS7_INIT_SUCCESS) return ret;// Peripherals initret = ps7_config (ps7_peripherals_init_data);if (ret != PS7_INIT_SUCCESS) return ret;return PS7_INIT_SUCCESS;
}

以DDR为例,这里定义了一个表(ps7_ddr_init_data_1_0)用于根据PS的类型进行不同初始化参数配置,在文件中能够找到该表具体的内容:


unsigned long ps7_ddr_init_data_1_0[] = {// START: top// .. START: DDR INITIALIZATION// .. .. START: LOCK DDR// .. .. reg_ddrc_soft_rstb = 0// .. .. ==> 0XF8006000[0:0] = 0x00000000U// .. ..     ==> MASK : 0x00000001U    VAL : 0x00000000U// .. .. reg_ddrc_powerdown_en = 0x0// .. .. ==> 0XF8006000[1:1] = 0x00000000U// .. ..     ==> MASK : 0x00000002U    VAL : 0x00000000U// .. .. reg_ddrc_data_bus_width = 0x0// .. .. ==> 0XF8006000[3:2] = 0x00000000U// .. ..     ==> MASK : 0x0000000CU    VAL : 0x00000000U// .. .. reg_ddrc_burst8_refresh = 0x0// .. .. ==> 0XF8006000[6:4] = 0x00000000U// .. ..     ==> MASK : 0x00000070U    VAL : 0x00000000U// .. .. reg_ddrc_rdwr_idle_gap = 0x1// .. .. ==> 0XF8006000[13:7] = 0x00000001U// .. ..     ==> MASK : 0x00003F80U    VAL : 0x00000080U// .. .. reg_ddrc_dis_rd_bypass = 0x0// .. .. ==> 0XF8006000[14:14] = 0x00000000U// .. ..     ==> MASK : 0x00004000U    VAL : 0x00000000U// .. .. reg_ddrc_dis_act_bypass = 0x0// .. .. ==> 0XF8006000[15:15] = 0x00000000U// .. ..     ==> MASK : 0x00008000U    VAL : 0x00000000U// .. .. reg_ddrc_dis_auto_refresh = 0x0// .. .. ==> 0XF8006000[16:16] = 0x00000000U// .. ..     ==> MASK : 0x00010000U    VAL : 0x00000000U// .. .. EMIT_MASKWRITE(0XF8006000, 0x0001FFFFU ,0x00000080U),EMIT_MASKWRITE(0XF8006004, 0x1FFFFFFFU ,0x00081081U),EMIT_MASKWRITE(0XF8006008, 0x03FFFFFFU ,0x03C0780FU),EMIT_MASKWRITE(0XF800600C, 0x03FFFFFFU ,0x02001001U),
//以下省略…

这里对DDR进行初始化其实就是配置ARM内DDR控制器的对应寄存器,从注释可以看到对寄存器每一位都进行了配置。

在fsbl.h中能够找到DDR寄存器的起始地址和结束地址:

#define DDR_START_ADDR   XPAR_PS7_DDR_0_S_AXI_BASEADDR

#define DDR_END_ADDR        XPAR_PS7_DDR_0_S_AXI_HIGHADDR

xparameters.h中能找到上面列出了的0XF8006000

/* Definitions for peripheral PS7_DDRC_0 */

#define XPAR_PS7_DDRC_0_S_AXI_BASEADDR0xF8006000

#define XPAR_PS7_DDRC_0_S_AXI_HIGHADDR0xF8006FFF

经过配置后DDR才能够使用,接下来fsbl将后续要执行的程序放入内存中。

4、  对FSBL的一点疑惑

按照流程图来看,要让zynq跑起来肯定需要fsbl,但是在通过jtag模式加载linux的过程中并没有fsbl.elf这个文件,而是直接dow u-boot.eld,uimage,devicetree.dtb文件。同时运行简单的裸奔程序helloworld时也是直接用SDK通过jtag让zynq跑起来。对于这种现象,我的猜想是fsbl.elf这个文件可能已经存放在arm内部的on-chip memory中了,硬件的boot rom启动后立刻执行的是这个内部的fsbl,然后再加载后续程序。当然,我们也可以创建自己的fsbl.elf,但是也是执行完内部的fsbl.elf后再执行用户自定义的fsbl.elf。

疑惑已经解决:见https://forums.xilinx.com/t5/Embedded-Development-Tools/When-launching-Zynq-from-SDK-using-JTAG-is-FSBL-irrelevant/m-p/662186/highlight/true#M38261

使用JTAG方式确实不需要FSBL


附:FSBL调试

参看 http://www.cnblogs.com/otod3r/p/5275732.html 调试fsbl的时候可以打开debug,这样串口就有打印信息了。


这篇关于Zynq-Linux移植学习笔记之四-fsbl的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/816196

相关文章

linux打包解压命令方式

《linux打包解压命令方式》文章介绍了Linux系统中常用的打包和解压命令,包括tar和zip,使用tar命令可以创建和解压tar格式的归档文件,使用zip命令可以创建和解压zip格式的压缩文件,每... 目录Lijavascriptnux 打包和解压命令打包命令解压命令总结linux 打包和解压命令打

linux如何复制文件夹并重命名

《linux如何复制文件夹并重命名》在Linux系统中,复制文件夹并重命名可以通过使用“cp”和“mv”命令来实现,使用“cp-r”命令可以递归复制整个文件夹及其子文件夹和文件,而使用“mv”命令可以... 目录linux复制文件夹并重命名我们需要使用“cp”命令来复制文件夹我们还可以结合使用“mv”命令总

Linux使用cut进行文本提取的操作方法

《Linux使用cut进行文本提取的操作方法》Linux中的cut命令是一个命令行实用程序,用于从文件或标准输入中提取文本行的部分,本文给大家介绍了Linux使用cut进行文本提取的操作方法,文中有详... 目录简介基础语法常用选项范围选择示例用法-f:字段选择-d:分隔符-c:字符选择-b:字节选择--c

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程