1.配置文件中对应命令的分析

我们一般在使能OpenOCD的刷写功能的时候,需要在OpenOCD的cfg文件中添加如下两条语句:

(1) $_TARGETNAME configure -work-area-phys 0xaaaaaaaa -work-area-size 0xbbbbbbbb -work-area-backup 0
(2) flash bank spi_flash fespi 0xcccccccc 0 0 0 $_TARGETNAME 0xdddddddd

这两条语句会在配置参数的同时,注册一系列的handler函数.
对于第一条语句的解析:下面的图是OpenOCD中的代码调用栈, 可以看到 -work-area-phys字符串后的数字会被解析出来,放入target->working_area_phys变量中,该变量存放的是Core上一个物理起始地址,同样-work-area-size后面跟的是未来要以这个物理起始地址开始分配的一个空间的大小; -work-area-backup决定了是否要对Core上那个空间的数据进行备份(但我感觉没有大的用处,OpenOCD一般都会做备份动作).

对于第二条语句的解析:大家如果看过我的另一篇讲解OpenOCD代码结构的文章应该清楚,OpenOCD在启动的时候注册了一系列的函数handler,主要分两大类:一类是为了解析config文件中的语句而注册的handler函数,另一类是为了在OpenOCD运行阶段解析输入进来的命令而注册的handler函数. 可以看到由于第二条语句中包含"flash bank"字串,因此通过遍历下面图上显示的定义关系,预先注册的handler函数handle_flash_bank_command()就会被调用到. 用它来解析第二条语句后续的字段.

下图是函数handle_flash_bank_command()的调用关系,这里CMD_ARGV[]中保存的就是第二条语句中的后续字段.依靠字符串"fespi"从全局表flash_drivers[]中找到对应的flash driver,这个表中包含各种的flash-driver,是根据名称进行查找的,其中的"&fespi_flash"项就是工作在RISCV平台上的项. 找到该项后,通过register_commands()函数将其中的函数进行注册.在函数的最后,将新建立的flash_bank{}实例加入到全局变量flash_banks链表中,它的name就是我们在命令里面定义好的"spi_flash",后续要搜索这个链表时,就是通过name来查找的.

2. 执行命令的handler函数的注册

我们说过,在OpenOCD中会有两类handler函数会被注册,刚才讲的config类的handler函数部分,到这里就该涉及到flash相关的执行类handler函数的注册了. 如下图,因为flash_exec_command_handlers[]实在太长,我只能截取一部分放在这里,而且大家也注意到了后面还有一张截图是flash_init_drivers()函数,它里面会对这个数组进行注册.

那么flash_init_drivers()函数在哪里被调用呢? 看下面的截图, openocd_main()函数已经属于比较顶层的函数了,属于比较早的启动代码了.它做了两件事情: 一个就是调用setup_command_handler()函数,主要完成对于config类handler的注册;另一个就是调用openocd_thread()函数开始主线程循环,同时进行执行handler函数的注册.它会调用handle_init_command()函数,在该函数中会有专门调用"flash init"字符串相关的handler函数.

下面的截图就是关于handle_flash_init_command()怎么进行注册的一个关系,当然它是在config类的handler函数注册时就完成的工作. openocd_thread()-->handle_init_command()-->command_run_line("flash init")的调用关系使得flash_exec_command_handlers[]中的函数都得到了注册.

3. 开始讲解FLASH刷写前的准备工作

第一个预备知识: 在开始将image文件中的数据刷写到flash上之前,OpenOCD自己需要完成一个重要的工作,那就是编译出一个可以运行在目标处理器上的小程序,这个小程序很简单,主要的逻辑就是操作处理器中关于SPI的一组寄存器,将来自OpenOCD的image数据通过SPI总线写入到FLASH中. 对于RISCV平台来讲,目前它是放在这个路径下的:

riscv-openocd/contrib/loaders/flash/fespi/

它最终会生成两个版本的binary文件: riscv32_fespi.incriscv64_fespi.inc分别对应32位与64位的版本.这两个bin文件可以在编译OpenOCD工具的时候自动生成,也可以在该目录下手动编译生成. 打开它里面的Makefile文件,很多的疑问迎刃而解: 首先它使用了"-fPIC"的编译选项,保证了生成出来的代码是与地址无关的,那么不管这个代码下载到哪个地址边界处,它都是可以正常运行的. 其次它使用了 "-Obinary"的选项,将ELF文件不需要的垃圾信息都清除掉,只留下最小最干净的binary. 最后它使用了 "-nostdlib"以及"-nostartfiles",因此它自己写了一个很小的入口汇编文件"riscv_wrapper.S"作为替代. 这样一个干净小巧的可运行在RISCV平台上代码就完成了.

再多说几句关于这个运行在处理器上的小程序的处理逻辑. 打开它的文件"riscv_wrapper.S"如下图所示, 因为代码很短我就直接全部贴出来并加以说明: 首先stack的空间它是已经开辟好了,在程序的入口首先设置好了栈寄存器sp的值,为以后进行函数调用做准备;在下面截图的第15行进行了函数调用,进入到函数flash_fespi()中,这个函数是这个片上小程序唯一的主体函数,刷写FLASH的工作就是在这里完成的.注意它的入口参数有六个,一定要记住,后面我们会讲到. 在下面截图的第16行有个ebreak指令,这个非常关键. 因为flash_fespi()函数不是个死循环,在完成flash的刷写工作后,它会从该函数中返回(当然返回时,"a0"寄存器中带回了刷写过程中的状态). 为了让OpenOCD知道这个刷写的动作已经完成,也为了防止片上程序乱飞,此处ebreak指令的执行会将当前Core给Halt住,同时切换当前模式为Debug模式,并等待OpenOCD的垂询.

第二个预备知识: 在OpenOCD启动之后,在它的启动Terminal窗口,会显示出3333/4444/5555三个端口,我们需要使用的是Telnet端口,也就是4444端口.如果读者不喜欢这个数字,其实是可以自行修改的(如下所示),修改完成后,重新编译生成OpenOCD即可.

riscv-openocd/src/server/telnet_server.c 文件中的 telnet_register_commands()中:
telnet_port = strdup("4444");

启动Telnet的命令步骤如下:

(1)新开启一个Terminal窗口
(2)在窗口中输入命令: telnet  localhost  4444 或者  telnet  127.0.0.1   4444

即可将telnet与OpenOCD建立起通信来,他们之间的通信连接方式也是socket的方式,同GDB与OpenOCD之间的方式一样的.

到这里预备知识就准备完毕了.

4. 在Telnet中输入命令完成flash的刷写

PROBE命令的分析:
首先,需要在Telnet的Terminal窗口中输入如下命令:

flash probe <bank-number>

此时如果FLASH设备上有对应的bank号,那么就会打印出对应的flash设备的信息. 由该命令的前两个字符串"flash probe", OpenOCD可以找到已注册的handler函数handle_flash_probe_command(). 该函数的调用栈如下图所示. 通过CMD_ARGV[0]中存放的bank-name从全局变量flash_banks[]中查找对应的flash_bank{}实例. 然后从预定义数组target_devices[]中找到对应的项,该项包含了重要的信息:在CPU-Core上SPI寄存器空间的基地址,后续需要访问CPU上的SPI总线寄存器都需要根据该基地址.

要从target_devices[]数组中找到对应的表项,主要是通过"tap idcode"来查找的,这个IDCODE就是存放在Debug-Module寄存器中的一个ID寄存器(在RISCV平台上,就是IDCODE 0x01寄存器), 存放在target_devices[]数组中的值要与该IDCODE寄存器中的值一致才可以被搜索到.下面是该数组内容的一个截图, 它每一行属于一项内容,每项又有三个element,我们刚才讲的是指第二个element的值要匹配.那么第三个element就是我们说的"ctrl_base",它就是SPI寄存器域的基地址,这个信息需要查询相应的CPU的手册才能获得到.
该表存放在riscv-openocd/src/flash/nor/fespi.c文件中.

在确定好了SPI的基地址寄存器后,可以看到后续又通过SPI总线访问到FLASH设备上的信息"flash-id".通过该ID信息与下图表(全局预配置表"flash_devices[]")中的device_id进行匹配. 该表的内容包含各种FLASH设备的信息,该表存放在riscv-openocd/src/flash/nor/spi.c文件中,现截图如下,大家可以观察它的注释,里面有清晰的对各项的解释.

通过以上的分析可以知道:如果工程师正确的配置了以上两个表,那么probe过程就会顺利完成.

WRITE-FLASH命令的分析

在完成第一步的probe后, 就可以开始进行写flash的操作了,它的命令如下:

flash write_image erase <your-image-file> <address> bin
or
flash write_bank <bank_id> <your-image-file> <offset>

我们就分析 "flash write_bank" 这条命令,另一条命令多了erase的操作,写的动作其实是一样的. 通过这条命令字串,OpenOCD可以很顺利的找到预注册的handler函数handle_flash_write_bank_command(),下图是该函数的调用栈截图, 这个函数稍长一些,下面会解释详细解释.

对于函数handle_flash_write_bank_command(),详细分析如下:
(1) 首先根据传递进来的bank_id通过查找flash_banks[]数组找到匹配项.
(2) 获取offset的值,该值是相对于bank的offset,未来作为一个目的地址使用的.
(3) 打开输入的image-file的句柄,并读取到本地新分配的buffer中
(4) 然后调用flash_driver_write()函数,开始将存放好的image-file中的数据写入到特定的flash-bank中.

对于flash_driver_write()->fespi_write()函数,详细分析如下:
(1) 校验flash要写入的bank中的每个sector是否被保护了,若是则无法继续写则返回
(2) 确定好后面要下载的片上运行小程序及其大小: riscv32_bin/riscv64_bin, 就是预备知识里面讲到的程序
(3) 调用target_alloc_working_area()函数: 先在OpenOCD中开辟一个buffer,大小是要下载的bin的大小.建立相应的管理结构体. 调用函数target_read_memory()将Core上由target->working_area_phys指定的地址,由target->working_area_size指定大小的这块区域的数据读取到本地刚开辟的buffer中,备份起来.
(4) 调用target_write_buffer()函数,将小程序bin(SPI的驱动代码)写入到target->working_area_phys指定的起始地址处.如果写的过程中发生失败,那么调用target_free_working_area()函数将备份的数据恢复到Core中去.
(5) 调用target_get_working_area_avail()函数中,将最终要写入的image-file那么大的一块数据这样的空间对应的一个管理实例给开辟出来.
(6) 再次调用target_alloc_working_area()函数,这次开辟的数据区是为了要下载image-file中的数据到FLASH中而分配的,因为先由OpenOCD下载数据到Core的working-area中,最后才有片上小程序将working-area中存放的image-file中的数据写入到FLASH中去的. 所以Core中的working-area上的这块区间的数据也需要备份到OpenOCD的本地buffer中.
(7) 在调用target_write_buffer()函数写入image-file中的数据之前,首先需要配置片上小程序在运行时的那六个入参(这个在预备知识中已经提及).
(8) 这里就开始调用target_write_buffer()将image-file中的数据写入到Core中由configue文件中指定的working-area中了.
(9) 调用target_run_algorithm()函数,同时把预先配置好的含有六个入参的结构发送到Core中去,写入到Core中的a0~a5这六个寄存器中,作为片上小程序在"jal flash_fespi"时的入参. 这六个参数包含了SPI寄存器域的基地址,要访问的image-file临时存放的物理地址以及它的大小.
(10) 当片上小程序完成将image-file的数据由working-area通过SPI总线写入到FLASH这个过程后,就会从flash_fespi()函数返回后,就会执行"ebreak"指令而Halt住.
(11) 其实target_write_buffer()以及target_run_algorithm()函数都是多次被调用的, OpenOCD写入一部分数据到Core中的DDR中,就会触发片上小程序将这一部分数据搬移写入到FLASH中,这也是为了避免如果image文件过大,尝试在一次搬移中就完成的话,那么写入的时间就会过长,那样可能会出现问题,所以不得不切割成多次来处理.
(12) 在每次运行target_run_algorithm()函数时,OpenOCD都会持续的polling当前Debug-Module的状态,如果polling到的状态是"RISCV_HALT_BREAKPOINT",那么就表示当前的这次传输完毕了.就可以调整参数进行下一次的传输了. (其实这部分我在<<RISCV上Semihosting功能浅析>>文章已经涉及到了)
(13) 完成所有的传输后,分别调用两次target_free_working_area()对备份到OpenOCD本地的两个数据区恢复到Core上对应的区间中去. 等于片上小程序以及image的缓存区的数据就被覆盖掉了.

校验写入的数据

完成上述的写入操作之后,就是最后一步校验的工作,它的命令如下:

flash verify_bank <bank_number> <your-image-file> <offset>

OpenOCD通过命令字串"flash verify_bank"可以找到预注册的handler函数handle_flash_verify_bank_command(). 它实际的操作很简单就是重新打开image-file,读出其中的数据,将其与从FLASH中读取的数据进行比较即可.

5. 总结:

以上就是对OpenOCD所支持的FLASH烧写逻辑的分析. 可以看到它主要采用了下载一个片上小程序到CPU上去的方式,由小程序来完成数据的搬移工作. OpenOCD先将要烧写的image写入到CPU中由cfg文件指定的working-area中,然后由小程序通过SPI总线写入到FLASH中,所有烧写工作完成后,毁尸灭迹不留一点痕迹!

OpenOCD刷写FLASH代码结构浅析(基于RISCV)相关推荐

  1. SPDK代码结构浅析

    最近这三周时间一直因为工作的需要在研究SPDK移植到RISCV平台上,在编译通过的时候,也顺带把SPDK的代码粗粗过了一遍,顺便做了一点笔记. SPDK (Storage Performance De ...

  2. 闪存Nand Flash存储结构浅析

    NandFlash存储器由多个Block组成,每一个Block又由多个Page组成,Page的大小一般为2K+64Bytes或512+16Bytes.Page是读取和编程的基本单位,而擦除的基本单位是 ...

  3. 基于RISC-V架构的开源处理器及SoC研究综述

    RISC-V是加州大学伯克利分校(University of California at Berkeley,以下简称UCB)设计并发布的一种开源指令集架构,其目标是成为指令集架构领域的Linux,应用 ...

  4. Uboot代码结构详细分析

    1. Bootloader功能分析 Bootloader(如Uboot.Redboot.Blob.vivi等)直接和CPU.外围硬件设备(存储器.网卡.LCD等)打交道,负责初始化硬件设备,以及负责拉 ...

  5. RISC-V生态架构浅析(认识RISC-V)

    个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈 RISC-V 生态架构浅析 前言 RISC-V最近越来越多的出现在科技新闻中,大量的公司加入到RISC-V研究和生产中.在越来越多的 ...

  6. 从站代码迁移,基于stm32f103与LAN9252

    从站代码迁移,基于stm32f103与LAN9252 最近刚刚完成了从站代码的迁移,新的控制芯片使用了stm32f103,通信芯片使用了LAN9252,过程当中碰到了很多问题,当然在知乎寻求帮助得到了 ...

  7. 基于RISC-V指令集架构的单周期CPU与五级流水线的实现(一)——分析

    本文是为完全不了解CPU的朋友所写的入门级教程,对于较为精通的朋友,多数章节均为赘述,完整代码在下一篇博客中,请见谅哈 一.实现功能 实现了部分RV32I指令集中的部分指令类型,如下表 具体指令如下( ...

  8. 解读eXtremeComponents代码结构--转载

    原文地址:http://blog.csdn.net/lark3/article/details/1937466 大致整理了去年写的东西,罗列如下: ec是一系列提供高级显示的开源JSP定制标签,当前的 ...

  9. storm源码之storm代码结构【译】

    说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助 ...

最新文章

  1. 如何将模糊的扫描版pdf转为清晰的pdf或word_pdf问题小结
  2. 你知道吗?脑机接口训练会对大脑物质结构和功能产生影响
  3. StringBuffer 案例
  4. Token认证微服务
  5. 腾讯开源视频动作检测算法DBG,打破两项世界纪录!
  6. mysql 合并相加_mysql 多条记要判断相加减合并一条
  7. Python poetry的使用
  8. 剑指Offer_12_矩阵中的路径(参考问题:马踏棋盘)
  9. 10. 王道考研-树与二叉树
  10. Unity3D中Grid Layout Group组件一键实现自动排版Image
  11. 485终端电阻的重要性
  12. docker bi工具superset汉化
  13. 人工智能在围棋程序中的应用
  14. python 工具变量_工具变量读书笔记
  15. flutter: The method ‘DioHttpHeaders.add‘ has fewer named arguments thanthose of overridden ....
  16. 【思源笔记】2.5.0 版本之后官方支持的第三方数据同步配置方式
  17. 程序员如何优雅地写公众号
  18. vue中的for循环如何循环到到一定次数换行(歪门邪道)
  19. 小甲鱼python入门_python基础笔记(非系统/自用/参考小甲鱼的零基础入门学习python)上...
  20. 【转载】损失函数 - 交叉熵损失函数

热门文章

  1. 【附源码】计算机毕业设计SSM特大城市地铁站卫生防疫系统
  2. 毕业十年即登全球副总裁,微软的这个后浪有点强
  3. 自己总结的sci写作句型~~词汇~~
  4. *关于系统调用我自己再整理一下(系统调用,任务切换,pendsv中断,SVC,整个理顺打通了)
  5. 如何查看虚拟机mysql安装路径_Linux虚拟机下mysql 5.7安装配置方法图文教程
  6. 如何关闭win10任务栏的Microsoft start资讯
  7. Windows下MySQL的安装教程及相关问题解决方案
  8. python怎么变大字体_python – 更改字体大小而不会弄乱Tkinter按钮大小
  9. (附源码)计算机毕业设计SSM基于java平台的心理测试系统
  10. 2022年湖南省证券从业资格(保荐代表人)练习题及答案