0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FSBL的数据段和代码段如何连接?

FPGA之家 来源:FPGA之家 2023-07-06 09:50 次阅读

FSBL的数据段和代码段如何链接

搞懂数据段和代码段是如何被链接成一个二进制文件的,这应该是每一个ARM程序员必须搞清楚的一个事情。它会帮助程序员更加透彻的知道ARM是怎么被安排去工作的,所以数据段你和代码段如何链接在一起,是我们搞懂FSBL的第一步。

建个Example工程,不要光顾着看,自己动动手掌握的更快。

要回答这个问题其实必须要建一个工程,相关的软件操作流程可以参考各种开发板的实验手册,我这里见得描述一下:

现在VIVADO里面新建一个PL工程,可以自己搭,也可以用范例,本小节所涉及的PL来自范例,如下所示,整个PL实际上由:

1.1 ARM部分(硬核+外设),如图中所示的processing_system,其中就包含了除APU以外,还有DDR,以及FIXED_IO。DDR好理解,就是连接外部DDR存储器呗,那这个FIXED_IO是个啥呢?这个实际上就是arm的外设,包含了Q-spi的必要引脚,也包括了Debug Info所需的串口。总而言之都是ARM的外设

1.2 复位部分,看名字就很好理解,该模块专门用于所以Zynq的PL部分部件的复位

1.3 AXI Interconnect,这个模块非常重要,简单地说这就是一个总线解析器,一主(一个master AXI4)多从(两个slave AXI4)。我们之前提到过,AXI4将会用于连接Zynq的PS(ARM部分)和PL(FPGA部分),这里就是一个例子,后面每一个Slave AXI4都连着一个 Xilinx 的IP,或者是用户自定义的具备AXI4的IP。这样就简单了,只要用户定义的IP包含AXI4接口,同时将必要的可读或者可写数据映射到这个AXI4接口上,那么Zynq的ARM就能够通过总线接触到这些映射到总线上的数据,it means the ARM could read/write its content mapped on the Bus of AXI4.

1.4 AXI GPIO & AXI BRAM Controller, 这两个就是上述的Xilinx的IP,自带有AXI4总线接口,这样ARM就能够通过总线解析器控制他们

1.5 实际的应用,其实也不会比这个在复杂太多,只是再加一些自定义的IP

20e78094-1b9b-11ee-962d-dac502259ad0.png

2 利用这个范例,我们进一步建立BSP,然后基于BSP建立APP(用户程序),以及FSBL(范例,Zynq的加载程序),如下图所示,其包含了app, bsp, platform, fsbl。通过任何一个开发版的用户手册都可以获得完整的工程建立流程,这里不再赘述。

211498ea-1b9b-11ee-962d-dac502259ad0.png

3 其中bsp和fsbl里面,包含加载过程中所用到的所有源码,下面一一解析。

查看链接文件,原来存储空间是这样有条不紊的被分配

点击FSBL->src->lscript.ld,界面上将会呈现(这里的SDK是2017.2版本):

21217e70-1b9b-11ee-962d-dac502259ad0.png

感谢这个SDK的开发工具,使得用户能够以图表的方式去查看数据段和代码段的具体分布(以前都是通过直接看源码,毕竟科技进步了~),不过老程序员可能更喜欢看源码,那我们就结合的看吧

这个图主要呈现了三部分内容:

定义了两个存储空间,包括offset和length,其源码表达如下

213db66c-1b9b-11ee-962d-dac502259ad0.png

接下来定义了堆栈的大小,忘了啥是堆栈的可以自行百度复习一下

214b6f0a-1b9b-11ee-962d-dac502259ad0.png

接下来就是将FSBL编译完成后的所有数据和代码,按照一定的顺序链接生成二进制文件,举个例子:

215cd24a-1b9b-11ee-962d-dac502259ad0.png

上面的源码的作用是:

(1)定义FSBL的程序入口在== _vector_table ==

(2)将代码段(.text*)链接到ps7_ram_0_S_AXI_BASEADDR的最前头,而这里的代码段实际包含了.vector等等内容,我们查看一下.vectors到底是个啥吧,搜索一下把,结果就在bsp的asm_vectors.S(汇编文件里面)

2172fa16-1b9b-11ee-962d-dac502259ad0.png

进到这个汇编程序,如下所示:

2182d77e-1b9b-11ee-962d-dac502259ad0.png

这里先关注两个名字,一个就是==.vectors==,另一个就是==_vector_table==

看下面的源码可知,.vectors就是一个.section,相当于下面所有的汇编源码取了一个别名,叫做.vectors,这些源码最终被放置到了上述位置!

第二个需要关注的是_vector_table,其实际上就是全局变量(看下面的源码.globl _vector_table ),这个全局变量在这里就是一个指针,指向了B_boot 这个操作。

同时回过头看上面的源码ENTRY(_vector_table),这就是定义了FSBL的程序入口,也就是cpu执行的第一条指令保存在 _vector_table -----> B_boot

这里可以简单的小结一下, FSBL执行的第一条指令就是B_boot,这是通过查看(编写)FSBL->src->lscript.ld才获悉的,可想而知这个链接文件有多重要,后期等我们更加熟悉,可以尝试一下取修改它,这里做个记号,继续往下走!

ARM要开始运行FSBL了,然而并不是main()

上面已经提及实际FSBL程序最先被执行的语句是B_boot,这是一条汇编指令,意思就是说跳转到 _boot程序块,同时转跳指令B是无需返回的,所以后续BUndefined啥的实际上并不会被执行,看一下**_boot**是什么:

218f0936-1b9b-11ee-962d-dac502259ad0.png

汇编语言不是笔者的强项,因此只能大概说明一下(有兴趣的可以自己逐条查看作用,过程会比较痛苦。方式能收获更多CPU底层的细节,这里不展开):

1 针对CPU0和CPU1有不同的程序,基本就是CPU0干活,CPU1就是WFE

2 CPU干的活就是初始化MMU和TLB等等,其中比较关键的就是初始化堆栈(SP寄存器指向栈顶),前面也提及过,在链接的时候分配了堆栈空间,而堆栈对C语言函数是非常重要的。栈的作用:一般来说函数调用或者中断,都会涉及到现场保护和现场恢复,被保护的现场实际上就是CPU的几个专用的reg,以及正在执行的函数的局部变量等数据,这些数据会被推进栈内,其相应的SP寄存器也会加上入栈数据的长度,在函数执行返回挥着中断返回时,栈内的数据按顺序再次出来,总体来说就是先进后出。而堆的作用一般就是给系统动态分配存储空间的,包括用户经常调用的malloc说分配的空间,就是在堆里。简而言之,堆栈的完成初始化是为了c语言函数提供了环境。否则C语言是无法正确被执行的。

3 完成上面一系列的功能后,开始一执行去第一条C语言函数**_start**,见下面的汇编代码

21a57bbc-1b9b-11ee-962d-dac502259ad0.png

21c265ce-1b9b-11ee-962d-dac502259ad0.png

一样的,我们不仔细展开这段汇编,其实通过注释就能够明白,这里的主要功能就是初始化各种数据,包括bss等等。最后,汇编来到了main,这个main就是FSBL的主函数,也就是大家比较熟悉的c语言函数。

小结,实际上BSP在背后干了好多事情(上述所有的汇编都是bsp提供的),这是为了让用户能够忽略一些技术细节,直奔主题main。而这些技术细节已经有Xilinx官方为我们完整无误的准备好了,所以FSBL我们其实只用聚焦在main函数即可,其他的脏活累活BSP已经替我们完成了,我们用不用太操心。不过通过上面的一些展开,大伙儿应该也有了一个模糊的概念,也就是说虽然我们写的所有的函数都是从main函数开始,然后CPU执行的第一条指令,绝对不是main,而是最基础的汇编。这个汇编会替你搞定c语言环境,让我们的main能够很ojbk的运行。下次把目光回到main函数

终于要开始进入main()了,激动不?

费话不多讲,直接怼源码,如下所示

21d2e098-1b9b-11ee-962d-dac502259ad0.png

逐条怼:
一开始就定义了三个变量,这三个变量的作用请看下面的注释

21ea9288-1b9b-11ee-962d-dac502259ad0.png

next, 接下来开始初始化MIO,PLL,CLK和DDR,调用的函数就是ps7_init()

21fad490-1b9b-11ee-962d-dac502259ad0.png

如果看过我们上一篇blog应该有个印象,MIO不是已经被初始化过一遍吗,怎么又要?是的,就是这么灵活,也就是说你的FSBL可以在Qspi(这样BootROM只会初始化Qspi的接口MIO)里,你的BitStream可以保存在eMMC上,那这个多出来的eMMC的MIO也需要在初始化一下了。不多讲,直接看ps7_init()

220c6e26-1b9b-11ee-962d-dac502259ad0.png

221e230a-1b9b-11ee-962d-dac502259ad0.png

该函数主要完成:

读取PS版本,估计一些老料子的方式略有不同吧

根据PS版本,赋予相对应初始化数组的指针。这个数组基本上构成举例(ps7_mio_init_data_1_0)如下

222d19c8-1b9b-11ee-962d-dac502259ad0.png

可以简单的理解为,这个ps7_mio_init_data_1_0数组中的而每一个元素都是一种操作,这个操作包含了EMIT_WRITE,EMIT_READ等等。比如说EMIT_WRITE,为了完成这个操作,实际上包含了3个元素,操作指令码+地址+数据(不同的操作包含的数据不同,有些操作会有四个元素)。

223c3606-1b9b-11ee-962d-dac502259ad0.png

其想要实现的功能就是往addr write val,比如说EMIT_WRITE(0XF8000008, 0x0000DF0DU),其想要实现的功能就是将地址0XF8000008上的数据写为0x0000DF0DU

而0XF8000008这个地址,通过查看TRM,实际上就是给SCLR_UNLOCK寄存器写入0xDF0D,目的就是为了解锁SCLR所有的寄存器,使其可写,也就是说没有完成这一步的话,SCLR的其余寄存器使不允许写操作的!

22495a84-1b9b-11ee-962d-dac502259ad0.png

Xilinx希望通过这种比较奇怪的方式完成了一系列操作(EMIT_WRITE和其他操作)的封装成一个组合(ps7_mio_init_data_1_0),这一些列的操作共同完成了比如说MIO的初始化,DDR的初始化等等。同时Xilinx提供了一个函数去解读这些操作,**ps7_config ()**正是为了实现这个功能,如下所示,利用ps7_config ()和ps7_mio_init_data来完成MIO的初始化

225efd9e-1b9b-11ee-962d-dac502259ad0.png

下面来看一下ps7_config()

226ae4f6-1b9b-11ee-962d-dac502259ad0.png

22843f96-1b9b-11ee-962d-dac502259ad0.png

229b47c2-1b9b-11ee-962d-dac502259ad0.png

该函数很简单,实际上就是EMIT_WRITE,EMIT_EXIT,EMIT_READ等一系列操作的解包过程,有兴趣的可以深入查看一下。需要注意的是,最后一个操作一定是EMIT_EXIT,也就是说不管是ps7_mio_init_data还是ps7_pll_init_data,这些数组的最后一个元素(操作)一定是EMIT_EXIT,读者可自行检查。

小节

Xilinx利用了一种非常不常见的方式完成了部分(MIO或者DDR)初始化,究其原因可能是这部分初始化工作是固定的,所以什么可读性啊都不需要了?既然xilinx这么干了,我们看得懂就行了,这种方式极其不推荐




审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM
    ARM
    +关注

    关注

    134

    文章

    9098

    浏览量

    367707
  • FPGA设计
    +关注

    关注

    9

    文章

    428

    浏览量

    26523
  • 存储器
    +关注

    关注

    38

    文章

    7493

    浏览量

    163876
  • DDR
    DDR
    +关注

    关注

    11

    文章

    712

    浏览量

    65363

原文标题:FPGA - Zynq - 加载 - FSBL源码解析1

文章出处:【微信号:zhuyandz,微信公众号:FPGA之家】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    FSBL数据代码如何连接

    搞懂数据代码是如何被链接成一个二进制文件的,这应该是每一个ARM程序员必须搞清楚的一个事情。
    发表于 07-21 09:02 961次阅读
    <b class='flag-5'>FSBL</b>的<b class='flag-5'>数据</b><b class='flag-5'>段</b>和<b class='flag-5'>代码</b><b class='flag-5'>段</b>如何<b class='flag-5'>连接</b>?

    C代码关联的知识点

    之前有位网友在交流群里发了一代码的截图,我觉得很有意思,在此分享一下。
    发表于 08-30 10:42 450次阅读
    一<b class='flag-5'>段</b>C<b class='flag-5'>代码</b>关联的知识点

    浅谈text、data和bss

    保存在.bss 中。 text: 用于存放程序代码的区域, 编译时确定, 只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解
    发表于 08-21 15:51

    如何查看CCS编译后程序数据的大小?

    hi, 编译文件时提示没有空间了,调整了code区域后,程序运行ram区域又不够了。想查看一下ccs编译完后,程序数据、bss的大小,可以吗?
    发表于 06-21 11:24

    请问写DSP程序自己定义代码数据有什么好处啊? 默认分配不是更省事吗?

    写DSP程序自己定义代码数据有什么好处啊? 默认分配不是更省事?
    发表于 08-22 08:31

    代码的跨

    08 代码的跨执行
    发表于 06-10 06:50

    请问链接脚本文件里面的代码数据,bss的位置可以更改吗?

    (4) : {*(.rodata)} //3.data ALIGH(4): {*(.data)} //4.bss ALIGH(4) : {*(.bss) *(COMMON)} //5}代码都在最前面,数据
    发表于 06-25 02:25

    请问数据/代码/BSS/栈/堆存放什么量?

    请问数据/代码/BSS/栈/堆存放什么量?
    发表于 12-03 06:06

    代码数据、附加、堆栈定义

    代码:程序员在编制程序时要把存储器划分成代码用来存放程序的指令序列,代码
    发表于 06-30 10:41 9967次阅读

    什么是临界 RTOS临界的作用是什么

    代码的临界也称为临界区,指处理时不可分割的代码区域,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界
    的头像 发表于 10-06 14:38 1.2w次阅读
    什么是临界<b class='flag-5'>段</b> RTOS临界<b class='flag-5'>段</b>的作用是什么

    汇编语言和RSEG应该如何使用详细资料说明

    RSEG是选择指令,要想明白它的意思就要了解的意思。是程序代码数据对象的存储单位。程序代码
    发表于 09-10 17:26 2次下载
    汇编语言<b class='flag-5'>段</b>和RSEG应该如何使用详细资料说明

    FreeRTOS学习笔记--临界代码处关闭中断

    的一句话:“临界代码是指不能被中断的代码”。实际上,不只在进行全局操作的代码需要对中断进行管控,在某些严格时序上也需要对中断进行管控。例如A任务需要利用IIC、SPI、并口等协议进行
    发表于 12-04 14:51 10次下载
    FreeRTOS学习笔记--临界<b class='flag-5'>段</b><b class='flag-5'>代码</b>处关闭中断

    段段存储介绍

    .text代码: 用来放程序代码(code), 在代码编译完成后, 长久只读存放于此,属于图中的代码
    的头像 发表于 09-28 15:39 807次阅读
    六<b class='flag-5'>段段</b>存储介绍

    什么是RTOS临界

    代码的临界也称为临界区,指处理时不可分割的代码区域,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界
    的头像 发表于 02-14 09:48 1113次阅读
    什么是RTOS临界<b class='flag-5'>段</b>

    devc怎么注释掉一代码

    在DevC中,要注释掉一代码,你可以使用注释符号来标记这段代码。注释符号的作用是告诉编译器不要编译这些代码,而是将其视为注释,这样可以方便开发人员在
    的头像 发表于 11-22 10:23 2506次阅读