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怎么配合?我画了张图,把关系理清楚:
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平台上到底干什么活?说白了就三件事:
- 管理PS外设:UART、I2C、SPI这些标准外设,驱动跟普通ARM平台没太大区别。但要注意,Zynq的时钟和复位管理比较特殊,驱动里要处理好。
- 操作PL硬件:把PL里的自定义IP核映射到Linux的地址空间,让用户态程序能通过驱动访问硬件寄存器。这是Zynq驱动开发的核心难点。
- 实现数据通路: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、绑定文档)
不过别担心,咱们这门课会从基础讲起,每个概念都会结合实际案例。你跟着我一步步做,肯定能上手。
开发环境搭建步骤
- 安装Vivado和SDK,配置好JTAG驱动。
- 下载并解压linux-xlnx内核源码。
- 配置交叉编译工具链,加到PATH环境变量里。
- 编译U-Boot,生成boot.bin。
- 编译内核,生成uImage和设备树dtb文件。
- 制作根文件系统,拷贝到SD卡。
- 上电,通过串口观察启动日志。
避坑指南:我曾经在第一步就卡住了——Vivado安装后JTAG驱动没装好,导致一直检测不到开发板。解决办法是手动安装cable drivers,路径在Vivado安装目录下的 data/xicom/cable_drivers/ 里。