1. Zynq平台与Linux驱动概述

做嵌入式Linux驱动开发这么多年,我接触过不少平台。但Zynq这玩意儿,说实话,第一次拿到手的时候还是让我琢磨了好一阵子。它跟传统的ARM单片机或者纯FPGA方案都不一样——说白了,它把一颗ARM处理器和一个FPGA逻辑单元塞进了同一颗芯片里。

嗯,咱们这门课要讲的就是怎么在这个混合体上把Linux驱动跑起来。但动手之前,得先把Zynq的底子摸清楚。

1.1 Zynq架构简介

Zynq内部其实分成两大块:PS(Processing System,处理系统)和PL(Programmable Logic,可编程逻辑)。这两部分通过内部高速总线连在一起,数据交换延迟极低。

核心要点:PS是ARM硬核,PL是FPGA逻辑。PS上跑Linux,PL上跑你自定义的硬件逻辑。两者协同工作,才是Zynq的真正价值。

PS部分包含什么?我列个表,大家看得清楚些:

组件 说明 我的备注
ARM Cortex-A9 双核 主频最高1GHz,带NEON和VFPv3 跑Linux完全够用,别指望它跟服务器CPU比
L1/L2 Cache 32KB指令+32KB数据L1,512KB共享L2 驱动优化时要注意cache一致性
DDR控制器 支持DDR3/DDR3L/LPDDR2 我踩过坑,PL访问DDR的路径跟PS不一样
外设接口 UART、I2C、SPI、CAN、USB、GigE、SD/SDIO 这些外设的驱动移植是咱们课程的重点
内部互联 AXI总线,包括ACP、HP、GP端口 驱动里操作PL寄存器全靠AXI

PL部分呢?它就是一块标准的FPGA fabric。你可以用Verilog或VHDL在里面搭各种硬件模块——自定义加速器、视频处理管线、高速数据采集接口,什么都行。然后通过AXI总线把这些模块挂到PS的地址空间里,Linux驱动就能像访问普通内存一样操作它们。

1.2 PS与PL协同

PS和PL怎么配合?我画了张图,把关系理清楚:

Zynq PS与PL协同架构 PS(处理系统) ARM Cortex-A9 L1/L2 Cache DDR控制器 外设接口 AXI互联(ACP/HP/GP) Linux内核 + 驱动 PL(可编程逻辑) 自定义IP核 DMA引擎 高速接口 视频处理 AXI从机接口 驱动通过AXI总线访问PL中的寄存器地址,实现PS与PL的数据交互

PS和PL之间有三类AXI接口,用途完全不同:

  • ACP(Accelerator Coherency Port):PL可以直接访问PS的L2 Cache,实现缓存一致性。我做过一个视频采集项目,用ACP端口把摄像头数据直接送进CPU缓存,延迟只有几十纳秒。
  • HP(High Performance)端口:PL访问DDR内存的高速通道,适合大数据量传输。记得有一次调试,发现HP端口带宽死活上不去,最后查出来是AXI总线位宽配置错了。
  • GP(General Purpose)端口:通用控制通道,速度一般,但配置简单。我习惯用它来读写PL里的控制寄存器。

1.3 Linux驱动在Zynq中的角色

Linux驱动在Zynq平台上到底干什么活?说白了就三件事:

  1. 管理PS外设:UART、I2C、SPI这些标准外设,驱动跟普通ARM平台没太大区别。但要注意,Zynq的时钟和复位管理比较特殊,驱动里要处理好。
  2. 操作PL硬件:把PL里的自定义IP核映射到Linux的地址空间,让用户态程序能通过驱动访问硬件寄存器。这是Zynq驱动开发的核心难点。
  3. 实现数据通路:PS和PL之间的数据搬运,要么用CPU直接读写,要么用DMA。我建议能用DMA就别用CPU,否则CPU被占死,系统响应就慢了。

我的经验:刚开始做Zynq驱动时,我总想把所有逻辑都塞进驱动里。后来发现,驱动只做硬件控制,数据处理交给用户态程序,这样调试和维护都轻松得多。

1.4 移植前的准备工作

动手移植驱动之前,有几样东西必须准备好。我曾经因为漏了其中一步,浪费了整整两天时间。

硬件准备

项目 要求 备注
Zynq开发板 至少带UART和JTAG接口 我用的是ZedBoard和ZC706
串口线 USB转UART,3.3V电平 别买成RS232的,电平不匹配
JTAG调试器 Xilinx Platform Cable或兼容品 调试PL bitstream必备
SD卡 至少4GB,Class 10 存放bootloader、内核、根文件系统
电源 12V/2A以上 PL逻辑跑起来功耗不低

软件工具链

  • Vivado + SDK:Xilinx官方工具,用来生成硬件配置和FSBL(First Stage Boot Loader)。版本建议用2018.3以上,我目前用2022.2。
  • 交叉编译工具链:arm-linux-gnueabihf- 系列。我习惯用Linaro的版本,稳定且更新及时。
  • Linux内核源码:Xilinx官方维护的linux-xlnx分支,或者主线内核。我个人推荐用官方分支,驱动支持更全。
  • U-Boot:引导加载程序,Xilinx也有维护分支。
  • 根文件系统:可以用Buildroot或Yocto生成,也可以直接用现成的ramdisk。

注意:交叉编译工具链的版本必须跟内核版本匹配。我曾经用gcc 9.x编译4.19内核,结果内核模块加载时符号版本对不上,折腾了一天才发现是工具链版本问题。

知识储备

嗯,这里我得说句实话——如果你对下面这些概念还不熟,建议先补补课:

  • Linux设备模型(platform bus、device tree)
  • 字符设备驱动框架(file_operations、cdev)
  • 中断处理(request_irq、tasklet、workqueue)
  • DMA引擎API(dmaengine框架)
  • 设备树语法(dts、dtsi、绑定文档)

不过别担心,咱们这门课会从基础讲起,每个概念都会结合实际案例。你跟着我一步步做,肯定能上手。

开发环境搭建步骤

  1. 安装Vivado和SDK,配置好JTAG驱动。
  2. 下载并解压linux-xlnx内核源码。
  3. 配置交叉编译工具链,加到PATH环境变量里。
  4. 编译U-Boot,生成boot.bin。
  5. 编译内核,生成uImage和设备树dtb文件。
  6. 制作根文件系统,拷贝到SD卡。
  7. 上电,通过串口观察启动日志。

避坑指南:我曾经在第一步就卡住了——Vivado安装后JTAG驱动没装好,导致一直检测不到开发板。解决办法是手动安装cable drivers,路径在Vivado安装目录下的 data/xicom/cable_drivers/ 里。