LJgibbs · 2020年06月02日

Zynq SDK 驱动探求(三):论一个外设驱动的全部身家·Xilinx SDK 驱动源码结构

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 的结构。
62.jpg

BSP 分为两个部分:文档源码

BSP Doucumentation 中是外设驱动的文档,双击某个外设即可用打开包括外设驱动概述,API等的文档,以网页的形式。在之后的文章中,我们可能会分析一下文档的重点区域。

源码都位于 ps7\_cortexa9\_0 目录下,包括头文件,函数库源码,编译完的函数库,编译函数库的 Makefile 文件以及概述 BSP 信息的 system.mss 文件。Makefile 用于执行将所有的外设驱动函数库源代码编译为 libxil.a 。
62.jpg

所以定时器身家几何?

终于来到了本文的主要部分,定时器的源代码有哪些文件?

在 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的逻辑
推荐阅读
关注数
10506
内容数
512
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息