Processor is ready. Configure programable logic.
在新专栏 Rapid TCP/IP on Zynq 中,将围绕 Xilinx Zynq 系列芯片,从 SDK 驱动,PS-PL 协同加速,嵌入式协议栈 LWIP 分析以及 TCP/IP 硬件加速等方面,一起探求可灵活配置,软件定义,硬件加速的 TCP/IP 协议栈的实现。
作者:李凡
来源: https://zhuanlan.zhihu.com/p/58737537
在 Xilinx SDK 驱动中,每个外设都拥有许多源文件,一个个家财万贯。本文将以私有定时器为例,做一个香港小报记者,一一盘点驱动大佬们的身家。
Location,Location
全部的 SDK 驱动源代码位于赛灵思安装源目录
/SDK/2018.3(SDK版本)/data/embeddedsw/XilinxProcessorIPLib/drivers/ 中。
每个文件夹中存放着一个外设驱动, PS 和 PL 的外设驱动兼有。
3000 多个文件夹,岂不是有 3000 多个外设,疯了疯了。其实也没有这么多,以中断控制器 scugic 为例,这里包括了外设驱动的 6 个版本,所以外设没这么多,估摸着也就千八百个。所有提供的历史版本都可以在 BSP 设置中切换使用。
当你新建一个 SDK 工程的时候,一般都会同时新建一个 BSP,BSP 名为板级支持包,BSP 根据选择的硬件平台,统一管理所有启用的外设驱动函数库,以及包括 LwIP 等第三方函数库。
值得注意的是:所谓驱动管理,就是将硬件平台需要的外设源代码复制到当前 BSP 的目录下。也就是说如果需要修改源代码,需要到 SDK 的路径中修改,对于某个 BSP 代码的修改只对当前 BSP 有效。
我们首先新建一个 BSP 来看看 BSP 的结构。
BSP 分为两个部分:文档与源码。
BSP Doucumentation 中是外设驱动的文档,双击某个外设即可用打开包括外设驱动概述,API等的文档,以网页的形式。在之后的文章中,我们可能会分析一下文档的重点区域。
源码都位于 ps7\_cortexa9\_0 目录下,包括头文件,函数库源码,编译完的函数库,编译函数库的 Makefile 文件以及概述 BSP 信息的 system.mss 文件。Makefile 用于执行将所有的外设驱动函数库源代码编译为 libxil.a 。
所以定时器身家几何?
终于来到了本文的主要部分,定时器的源代码有哪些文件?
在 libsrc/scutimer 下的 src 目录中,共有 7 个文件,基本上所有的外设都是同样的设定,所以了解定时器之后,所有外设的源文件结构也就大抵明了。
xscutimer\_selftest.c :
仅包括一个外设自测试函数 XScuTimer\_SelfTest ,通过对外设寄存器进行读写一致性测试判断外设是否已经就绪。通过测试函数可以发现外设初始化失败,外设寄存器基址有误等错误。
xscutimer\_sinit.c:
仅包括外设配置查找函数 XScuTimer\_LookupConfig,将外设 ID 对应的硬件配置以外设配置结构体的形式返回。外设的硬件信息来自于 vivado 生成并导致出至 SDK 的硬件平台信息。这个函数在上一篇文章中进行了详细的讨论。
xscutimer\_g.c:
本文件为通过硬件信息生成的硬件外设 ID 于寄存器基址映射表数组。数组中每个位置存放有一个外设配置结构体。XScuTimer\_LookupConfig 正是通过匹配 ID 的方式获得对应的外设配置结构体。
/*
* The configuration table for devices
*/
XScuTimer_Config XScuTimer_ConfigTable[XPAR_XSCUTIMER_NUM_INSTANCES] =
{
{
XPAR_PS7_SCUTIMER_0_DEVICE_ID,
XPAR_PS7_SCUTIMER_0_BASEADDR
}
};
xscutimer.c:
本文件中定义了定时器初始化,启动,写入/读取定时器等外设配置控制函数。这些函数体中通过 XScuTimer\_ReadReg/XScuTimer\_WriteReg 函数访问寄存器,完成相应的外设控制和配置操作。
但值得注意的是,xscutimer.c 中仅包括了部分外设管理配置函数,其余的函数以宏定义的形式分别定义于 xscutimer.h 以及 xscutimer\_hw.h 等头文件中。这不禁让人发问:为什么有的函数定义在源文件中,而有些只能以宏定义的形式蜗居于头文件中?是什么让 Xilinx 的程序员厚此薄彼,到底是人性的沦丧还是道德的沉沦?(xilinx 程序员们你们好,手动狗头)
对此问题,我只发现了问题的表面现象,那就是在 xscutimer.c 中定义的函数对传入参数进行了检查,而 xscutimer.h 和 xscutimer\_hw.h 中宏定义的函数则不检查参数。看来是xscutimer.c 中定义的函数比较关键吧,命比较贵,惹不起。
xscutimer.h
作为主要的头文件,定义了外设的数据结构,包括最重要的两个结构体:XScuTimer\_Config,XScuTimer。分别为外设配置结构体,用于存储外设的硬件信息;外设实例结构体,用于存储外设具体的配置参数。
还定义了我们上述讨论的外设配置函数,以及声明了 xscutimer.c 中定义的函数,做了一个头文件应该做的。
xscutimer\_hw.h
该文件的描述中是这么说的:This file contains the hardware interface to the Timer. 定义了寄存器的地址偏移,寄存器位的 mask ,定义了寄存器操作函数 XScuTimer\_ReadReg/XScuTimer\_WriteReg ,这都非常合理地实现了一个硬件接口的工作,专注于寄存器访问。
但还定义了外设配置函数是什么鬼情况?比如:
#define XScuTimer_SetLoadReg(BaseAddr, Value) \
XScuTimer_WriteReg(BaseAddr, XSCUTIMER_LOAD_OFFSET, (Value))
即定义了写寄存器函数,又定义了调用写寄存器函数的外设配置函数。这样层级不就全乱了,定义硬件结构的初衷也不存在了啊。外设配置函数就应该定义在 xscutimer.c/h 中啊,然后调用 xscutimer\_hw.h 中的寄存器操作函数啊。
我冷静了一下,打开了别的外设 xyyyy\_hw.h 文件观察了一下:我只能说大部分的外设驱动代码遵从了分层的概念:只在 xyyyy\_hw.h 文件中定义了读写寄存器操作,但有一小部分外设驱动还定义了外设配置函数。
然后又冷静了一下:发现了 xscutimer\_hw.h 中函数的特点,那就是以外设基址作为参数,而 xscutimer.c/h 中的函数,则以外设实例结构体作为参数,接收参数后再从中取出外设基址。所以这是出于什么考虑呢,暂时不清楚。
对此我只能说两点:一,作为一个外设驱动库,统一,分层的源码文件架构应该是必须的。二,读写寄存器操作其实没有必要再每个外设中定义,因为不同的外设实质上都使用统一的方式访问寄存器,即下层的 Xil\_In32/ Xil\_Out32 函数,完全可以抽象出一个寄存器层次。
#define XScuWdt_ReadReg(BaseAddr, RegOffset) \
Xil_In32((BaseAddr) + ((u32)RegOffset))
至于 Makefile 我很想强行分析一下,无奈之前学的语法已经基本上忘了,但应该会在后续的文章中专门分析 SDK 中外设驱动的编译过程。
结语
至此,本文分析了 SDK 中外设源码存放的路径,BSP 的组成以及以定时器为例,分析了外设的源文件的组成与功能。此外,还对 Xilinx SDK 的设计提出了一点小疑问。
推荐阅读
关注此系列,请关注专栏FPGA的逻辑