刘力维
(北京信息职业技术学院人工智能学院 北京 100018)
随着集成电路技术的不断发展,基于处理器的智能系统在移动通信、物联网等行业得到了广泛应用。处理器的不断更新换代,需要能够快速开发出与之相匹配的嵌入式系统。而Boot Loader 作为处理器板级开发的第一个软件程序,是整个嵌入式系统板级软件开发的基础[1]。
出于容量和使用成本等方面考虑,用于支撑CPU 系统运行的内存设备通常采用动态随机存取存储器(DRAM)。为支持DRAM 正常工作,需要针对DRAM 的控制器(内存控制器)完成比较复杂的参数配置,以满足DRAM 工作需要的正确时序和刷新要求[2]。因此,内存控制器的配置是Boot Loader 启动过程中的一项重要任务。同时,由于在配置内存控制器之前无法使用内存,导致无法执行高级语言函数调用过程中必需的入栈和出栈操作,因此内存控制器初始化的代码不能使用高级语言实现。在很多传统的Boot Loader 中,这部分功能是使用汇编语言实现的。在实际的板级开发调试中,内存控制器的初始化过程作为处理器启动的早期阶段,使用汇编语言实现调试信息的输出功能,实现代码较为烦琐。再加上Boot Loader 每次更新程序都需要进行固件烧写等因素,导致这一阶段一旦出现需要软硬件联调的问题,调试手段欠缺,调试过程复杂,有时还需要额外的CPU 仿真器支持调试,增加了开发成本[3]。
高速缓冲存储器(Cache)在嵌入式系统中通常位于CPU 和DRAM 之间,访问速度比DRAM 更快。DRAM 访问时钟频率通常会比CPU 主频低1 ~2 个数量级,Cache 读写访问时钟频率通常介于CPU 主频和DRAM 访问时钟频率之间。通过对高频访问的内存数据提供缓存,提高CPU 系统的整体数据访问速率[4]。
本文提出的设计方案主要基于Cache 的两个特性:首先,Cache 在硬件上通常由静态随机存取存储器(SRAM)实现,与DRAM 相比,SRAM 初始化流程简单,正常通电即可使用,不需要执行复杂的时序配置和刷新操作。其次,很多CPU 提供了缓存锁定功能,可以支持将特定的数据锁定在Cache 中[5]。一些Boot Loader 在启动过程中充分利用了Cache 的上述两个特性,实现了使用高级语言进行内存控制器的配置。李雪峰[6]给出了一个利用Cache 锁定实现通过nandflash 启动Boot Loader 的实例。另一个典型的实例是boot[7],在内存控制器初始化之前,首先执行相对简单的Cache 初始化,并将一个由C 语言编写并单独编译的内存控制器初始化代码映像以及需要的栈空间刷新并固定在Cache 中,然后跳转到编译时指定的程序入口。由于程序的所有代码都固化在Cache 中,代码的执行过程100%命中Cache,相当于将Cache 当作内存来使用,由CPU 和Cache 组成一个临时的最小系统,支持高级语言所编译生成代码的执行。这样做的优点是显而易见的:一是可以使用高级语言编写复杂的内存控制器初始化代码,相对于使用汇编语言编写更易于实现和修改;二是可以使用高级语言实现串口的输入输出,为板级调试提供更丰富的手段。
随着处理器的不断发展以及片上系统(SOC)和多核处理器在嵌入式系统中的普遍应用,越来越多的处理器在芯片内部集成了多级Cache,并且Cache 的容量不断增大。本文基于上述硬件发展趋势,总结并扩展了前述Boot Loader 启动过程中高级语言代码映像所实现的功能,提出了一种Boot Loader启动早期阶段的板级调试交互平台。该板级调试交互平台实现了基于原有Boot Loader 进行功能扩展的方法,充分利用了Cache 系统,采用C 语言编写,通过串口提供基本的用户交互指令,实现了调试信息串口输出、硬件自检、寄存器读写访问等功能,为复杂的嵌入式系统板级调试带来了便利。
板级调试交互平台的代码由C 语言编写,使用代码重定位技术[8],并单独编译为一个映像。该映像与Boot Loader 其他部分编译生成的二进制代码映像一起烧写到用于支持单板启动的非易失存储设备(FLASH、EEPROM 等)中,运行时将被锁定到Cache 中。
板级调试交互平台的启动和执行过程见图1。图1 中左侧所列流程是在正常的Boot Loader 启动流程基础上,增加了启动板级调试交互平台的条件判断。板级调试交互平台的启动通过特定条件进行触发,比如调试串口上接收到某个特殊字符,或者GPIO 检测到通过硬件跳线实现的电平变化等。Bootloader 正常启动过程中,不会满足该触发条件,避免对正常的系统启动流程产生影响。当需要执行板级调试交互平台相关功能时,通过执行满足触发条件的外部动作,启动板级调试交互平台。新增的判断是否启动板级调试交互平台功能,作为Boot Loader 正常启动流程的新增部分,代码通过汇编语言实现。
图1 中右侧所列流程为板级调试交互平台新增功能。板级调试交互平台的执行可具体划分为以下3 个过程。
这部分代码同样作为原有Boot Loader 中新增功能,通过汇编语言实现。具体包含如下工作:(1)在正常启动Boot Loader 流程中Cache 初始化的基础上,初始化并使能多级Cache,获得尽可能大的Cache 存储空间。这里不区分指令和数据Cache,对于只有一级Cache 的情况可以通过分别对指令和数据Cache 进行操作实现本设计相关功能。(2)通过读取存放Boot Loader 的非易失存储器中的数据,将板级调试交互平台映像中的程序指令刷新到Cache 空间低地址。(3)指定栈顶地址为Cache 空间最高地址。通过上述操作,保证CPU 的读指令和栈操作两种内存访问始终命中Cache,实现了由CPU 和Cache 共同构成的最小系统。(4)在CPU 通用寄存器中保存退出调试交互平台返回Boot Loader 正常启动流程时要执行的下一条指令地址。这项工作是为退出板级调试交互平台返回Boot Loader 正常启动流程所做的准备。(5)修改当前指令执行指针,指向Cache 空间低地址中调试交互平台的代码入口,执行跳转,开始调试交互平台代码的执行[9]。
这部分功能由C 语言映像实现,通过串口进行简单的人机交互(菜单或者简单的命令行),实现调试交互主要功能。本平台实现了硬件自检和寄存器读写功能。具体功能的实现参见本文板级调试交互平台的功能分析部分。
这部分代码主要负责Boot Loader 正常启动流程的恢复,由板级调试交互平台执行过程中用户通过菜单或者命令执行的退出操作触发。具体包含如下工作:(1)从CPU 通用寄存器中读出返回Boot Loader 正常启动流程时的指令地址,并跳转到该地址。上述代码作为板级调试交互平台返回正常Boot Loader 启动流程的出口,由C 语言实现。(2)在指令跳转目的地址处,进行Cache 的整体刷新或重新初始化,清除锁定在Cache 中的板级调试交互平台代码。然后继续执行后续Boot Loader 初始化流程。这部分代码由汇编语言实现。
基于嵌入式系统板级调试早期阶段的实际需要,本平台实现了调试信息以函数形式通过串口输出、寄存器读写和硬件自检功能。
板级调试早期阶段由于调试手段的缺乏,导致软件配合硬件定位问题非常复杂。传统的调试手段包括在单板启动的不同阶段控制GPIO 输出不同电平点亮LED,通过LED 状态标识单板启动状态;以及通过汇编语言控制串口寄存器,实现串口字符的输出等。上述调试方法由汇编语言实现的过程较复杂并且不易进行代码移植,使用中有很大局限性。板级调试交互平台代码通过高级语言编写,可以通过对串口寄存器的访问控制实现查询方式的串口字符输入输出。串口字符输出的具体实现过程见图2。考虑到不同硬件平台的差异性,后续描述过程中忽略了访问串口控制寄存器的实现细节。首先循环查询串口输出状态寄存器,直到串口输出缓冲区处于空闲状态为止;在串口空闲状态下向发送寄存器写入要发送的字符;再次循环查询串口输出状态寄存器,直到输出缓冲区处于空闲状态,本次串口字符发送完成。上述通过设置和查询寄存器实现串口字符输出的过程不依赖于硬件中断服务。类似的过程也可以实现串口字符的输入,并且基于单一字符的输入输出,可以通过简单扩展实现字符串的输入输出函数。有了串口字符串输出这一调试手段,可以方便地实现调试数据的输出,为板级调试提供了极大便利。
调试交互平台通过串口输入输出实现了基本的人机交互功能,主要用于实现CPU 寻址空间内的读写访问。考虑到Cache 容量导致的代码总量限制,该人机交互功能只负责接收用户输入的3 个数据,分别是:选择读操作还是写操作;输入访问操作的CPU 寻址空间起始地址;输入要访问的数据长度。获取上述信息后,首先进行输入地址和访问长度的有效性判断,并对有效的地址和长度执行访问,通过串口返回访问结果。上述访问适用于板级调试阶段的CPU 寻址空间操作,可用于进行内存访问或者寄存器读写。该功能的实现进一步增加了嵌入式系统板级交互调试手段,避免了反复烧写Boot Loader。
调试交互平台实现了内存和硬件自检功能[10]。理论上,可以通过CPU 寻址实现读写访问的硬件都能够实现寄存器的读写检测,并设计相应的自检功能。这部分功能既可用于嵌入式系统开发过程中板级调试问题的定位,也可用于生产过程中实现装备测试功能。
实现板级调试交互平台对系统的软硬件设计存在一定要求。
(1)单板上存在用于实现调试过程中人机交互的串口,并且该串口可以在Boot Loader 启动时通过CPU 寻址访问进行操作。
(2)CPU 芯片内部集成或者在板上设计了高速缓存,其容量大于板级调试交互平台编译的映像大小,并有足够的剩余空间作为函数调用栈空间。
(3)由于板级调试交互生成的编译映像与Boot Loader 其他部分一起烧写在非易失存储器上,会导致Boot Loader 总大小变大,硬件上要求Boot Loader 二进制映像的总大小不超过非易失存储器总容量。
(4)在板级调试交互平台运行过程中占用了一个CPU通用寄存器用于保存返回正常启动流程时的指令地址。
上述2、3项要求限制了板级调试交互平台功能的复杂性,决定了板级调试交互平台只适合实现支持软硬件早期调试的关键功能,不能作为整个Boot Loader 功能的完整替代。
综上所述,板级调试交互平台本质上是基于Boot Loader 正常启动流程的功能扩展,可以方便地移植到各种Boot Loader 中。通过实现板级调试交互平台,可以方便地实现调试信息的串口输出以及寄存器和内存的基本读写访问,极大地方便了CPU 系统的早期软硬件联调,并丰富了CPU 板级系统装备生产和问题定位的实现手段。