news 2026/4/11 15:42:44

PetaLinux下Platform驱动编写完整示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PetaLinux下Platform驱动编写完整示例

从零开始:在 PetaLinux 中编写一个真正可用的 Platform 驱动

你有没有遇到过这种情况?FPGA 里写好了一个自定义 IP,比如 ADC 控制器、音频采集模块或者 GPIO 扩展器,逻辑都跑通了,但到了 Linux 系统里却“看不见”它——没法读寄存器、不能启停设备、调试全靠打印。裸机开发太原始,直接塞进内核又怕出错难维护。

这时候你需要的不是又一份手册抄写,而是一个能跑、能调、能改的真实驱动案例。

本文就带你从头到尾走一遍:如何在一个基于 Xilinx Zynq 的 PetaLinux 工程中,为 PL 端的一个自定义外设编写完整的 Platform 驱动,并通过字符设备暴露给用户空间。我们不讲空话,只说实战。


为什么是 Platform 驱动?

在 Linux 内核里,设备要被管理,就得“挂”在某种总线上。USB 设备挂在 USB 总线,PCIe 挂在 PCI 总线……但我们的 FPGA 自定义 IP 呢?它们通常接在 AXI 或 APB 上,没有标准协议,地址固定映射在内存空间中。

这类设备就归Platform 总线管。

Platform 总线是个“虚拟”的总线,它不像物理总线那样有数据传输功能,而是作为 SoC 内部片上外设的统一注册机制。它的核心思想很简单:

“这个设备一直存在,只要硬件描述对得上,我就把它管起来。”

而这个“硬件描述”,就是设备树(Device Tree)

设备树说了算

你可以把设备树理解成一张“硬件地图”。它告诉内核:“在0x43c00000这个地址有个设备,叫my_custom_ip,兼容性是xlnx,my-custom-ip,用的是第 89 号中断。”

驱动则拿着自己的“匹配表”去查这张地图:“有没有谁 compatible 是xlnx,my-custom-ip?”
有,那就 probe!没,那就等下次。

这种解耦让同一个驱动可以在不同板子上运行,只需换个 DTS 文件,代码不动。


实战:一步步写出你的第一个 Platform 驱动

我们来做一个最典型的场景:你在 Vivado 里设计了一个简单的寄存器型 IP,基地址是0x43c00000,大小 4KB,支持读写控制字和状态字。现在你想在 Linux 下通过/dev/my_platform_dev访问它。

目标明确:实现一个可加载的内核模块,完成资源映射、设备节点创建和基本 I/O 操作。

第一步:准备 PetaLinux 工程

假设你已经有了 Vivado 生成的.hdf文件(包含硬件地址、中断等信息),接下来创建 PetaLinux 工程:

petalinux-create -t project -n audio_plat_drv --src-uri system.hdf cd audio_plat_drv

这会自动生成 BSP 结构,包括内核、设备树、rootfs 等。

进入配置界面确保内核支持模块:

petalinux-config -c kernel

勾选:
-Enable loadable module support
-Device Tree Support

保存退出。


第二步:创建驱动模板

使用 PetaLinux 提供的模块生成功能:

petalinux-create -t modules -n my_platform_driver --enable

此时会在project-spec/meta-user/recipes-modules/my_platform_driver/下生成:

Makefile files/ └── my_platform_driver.c Kconfig

我们将用我们自己的驱动代码替换默认的.c文件内容。


第三步:写驱动代码——别跳过每一行注释

下面是经过实战打磨的完整驱动代码,每一行都有意义,每一个函数都有作用。

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/of.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/slab.h> #define DEVICE_NAME "my_platform_dev" #define CLASS_NAME "plat_class" #define REG_SIZE 0x1000 // 4KB 寄存器空间 static dev_t dev_num; static struct cdev my_cdev; static struct class *dev_class; // 用于保存设备私有数据(多个实例时尤其重要) struct my_dev_data { void __iomem *reg_base; struct device *dev; }; // 用户空间 read/write 接口 static ssize_t plat_read(struct file *file, char __user *buf, size_t len, loff_t *offset) { struct my_dev_data *data = file->private_data; uint32_t val; if (*offset >= REG_SIZE) return 0; val = ioread32(data->reg_base + *offset); if (copy_to_user(buf, &val, min(len, sizeof(val)))) return -EFAULT; return min(len, sizeof(val)); } static ssize_t plat_write(struct file *file, const char __user *buf, size_t len, loff_t *offset) { struct my_dev_data *data = file->private_data; uint32_t val; if (*offset >= REG_SIZE) return -EINVAL; if (copy_from_user(&val, buf, min(len, sizeof(val)))) return -EFAULT; iowrite32(val,>static int my_platform_probe(struct platform_device *pdev) { struct resource *res; struct my_dev_data *data; int ret; // 分配设备数据结构 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; >static int my_platform_remove(struct platform_device *pdev) { struct my_dev_data *data = platform_get_drvdata(pdev); device_destroy(dev_class, dev_num); class_destroy(dev_class); cdev_del(&data->my_cdev); unregister_chrdev_region(dev_num, 1); // reg_base 和 data 都由 devm 管理,无需手动释放 dev_info(&pdev->dev, "Driver removed successfully\n"); return 0; }

看到没?一行 free 都不用写。这就是devm_的威力。

最后是匹配表和驱动注册:

static const struct of_device_id my_platform_of_match[] = { { .compatible = "xlnx,my-custom-ip" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, my_platform_of_match); static struct platform_driver my_platform_driver = { .probe = my_platform_probe, .remove = my_platform_remove, .driver = { .name = DEVICE_NAME, .of_match_table = my_platform_of_match, .owner = THIS_MODULE, }, }; module_platform_driver(my_platform_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Engineer"); MODULE_DESCRIPTION("A practical Platform driver for custom IP in PetaLinux");

注意这里用了module_platform_driver()宏,它自动帮你处理module_initmodule_exit,更简洁安全。


第四步:修改 Kconfig 和 Makefile

编辑project-spec/meta-user/recipes-modules/my_platform_driver/Kconfig

config MY_PLATFORM_DRIVER tristate "My Custom Platform Driver" default m help This driver supports a custom AXI IP in PL. Say 'm' to build as module.

编辑Makefile

obj-m += my_platform_driver.o

第五步:添加设备树节点

编辑project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

&amba { my_custom_ip: my_custom_ip@43c00000 { compatible = "xlnx,my-custom-ip"; reg = <0x43c00000 0x1000>; /* interrupt-parent 和 interrupts 可选 */ // interrupt-parent = <&gic>; // interrupts = <0 89 4>; // SPI, level-high }; };

⚠️ 地址必须与 Vivado 中 IP 的实际基地址一致!

PetaLinux 构建时会将此文件合并到最终.dtb中,覆盖自动生成的部分。


第六步:构建并部署

petalinux-build petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga --u-boot

烧录 SD 卡,启动系统后:

cd /lib/modules/$(uname -r)/extra insmod my_platform_driver.ko dmesg | tail -5

如果一切正常,你会看到类似输出:

[ 12.345678] my_platform_dev: Probed: mapped 0x43c00000..0x43c00fff to f0012000 [ 12.345679] plat_class: registered new class device

然后就可以测试读写了:

echo 0x12345678 > /sys/class/plat_class/my_platform_dev/device_attr # 不行,还没做 sysfs # 改用程序测试

写个小程序试试:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() { int fd = open("/dev/my_platform_dev", O_RDWR); uint32_t val = 0xdeadbeef; write(fd, &val, 4); lseek(fd, 0, 0); read(fd, &val, 4); printf("Read back: 0x%08x\n", val); close(fd); return 0; }

交叉编译后运行,就能看到值被正确读写。


调试技巧:当 probe 失败了怎么办?

新手最常见的问题是:insmod 后什么反应都没有。

记住这条命令组合:

dmesg | grep -i "my_platform"

看看有没有错误日志。常见问题如下:

问题原因解决方法
No matching node foundDTS 中compatible字符串不匹配检查驱动和 DTS 是否一致
Failed to get memory resourceDTS 中reg缺失或格式错误检查地址是否正确,是否有< >
ioremap failed地址已被占用或越界检查其他驱动是否冲突,确认 IP 地址唯一
Module not found没有执行depmod执行depmod -a更新模块依赖

还有一个隐藏坑:如果你把驱动编进了内核(y而不是m),那insmod是无效的,必须看启动日志。


最佳实践总结:老司机的经验

  1. 永远优先使用devm_函数
    devm_kzalloc,devm_ioremap_resource,devm_request_irq……它们是你防止内存泄漏的第一道防线。

  2. compatible 字符串要有意义
    推荐格式:厂商,功能-版本,例如xlnx,i2s-audio-v1,方便未来扩展兼容旧版。

  3. 不要硬编码地址
    所有资源都从platform_get_resource()获取,保持驱动可移植性。

  4. 模块卸载必须干净
    即使你不打算卸载,也要写完整的 remove 函数,否则内核可能报错。

  5. 加调试信息,但发布时关掉
    开发阶段多用dev_dbg(),配合pr_debug(),需要时通过dynamic_debug控制开关。

  6. 考虑多实例支持
    如果将来可能有多个同类 IP,记得用container_ofplatform_set_drvdata管理上下文。


这个驱动能做什么?不止是读写寄存器

你现在有了一个基础框架,接下来可以轻松扩展:

  • 添加中断处理:在 probe 中用devm_request_irq()注册 IRQ handler。
  • 支持 DMA:结合dmaengineAPI 实现高速数据搬运。
  • 暴露控制接口:通过sysfs提供启停、复位等操作。
  • 封装为 ALSA 驱动:如果你做的是音频,可以对接 ALSA SoC 框架。

但所有这一切,都建立在你能先把 Platform 驱动跑通的基础上。


写在最后:别再复制粘贴了

网上太多教程只是把内核文档翻译一遍,或者直接贴一段不完整的代码。真正的嵌入式开发,是在 PetaLinux 工程里一步步构建、编译、烧录、调试出来的。

本文提供的不是一个“例子”,而是一个可复用、可调试、可扩展的起点模板。你可以把它用在下一个项目中,只需要改三个地方:

  1. compatible字符串
  2. DTS 节点地址
  3. 读写逻辑(根据你的 IP 寄存器定义)

剩下的,交给 Platform 驱动模型和 PetaLinux 工具链。

掌握这套方法论,你就不再是“调驱动的人”,而是“写驱动的人”。

如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/5 18:08:46

multisim14.3安装与破解步骤:初学者实用教程

Multisim 14.3 安装与配置实战指南&#xff1a;从零构建稳定仿真环境当你的电路还没焊上电烙铁&#xff0c;它已经在虚拟世界里跑起来了你有没有过这样的经历&#xff1f;花了一周时间设计一个滤波器&#xff0c;制板、焊接、通电……结果一测&#xff0c;频率响应完全不对。回…

作者头像 李华
网站建设 2026/3/31 17:44:45

Keil与ST-Link连接配置:新手友好型指南

Keil与ST-Link连接配置&#xff1a;从“连不上”到“一按就跑”的实战指南 你有没有过这样的经历&#xff1f; 代码写得信心满满&#xff0c;点击Keil的“Download”按钮——结果弹出一个无情提示&#xff1a;“ Cortex-M device not responding. ” 或者更糟&#xff1a;…

作者头像 李华
网站建设 2026/4/4 2:54:48

Netty入门详解:高性能网络编程框架深度解析

第1章&#xff1a;Netty概述与核心价值1.1 Netty是什么&#xff1f;Netty是一个异步事件驱动的网络应用程序框架&#xff0c;用于快速开发可维护的高性能协议服务器和客户端。它本质上是Java NIO的封装与增强&#xff0c;提供了一套简洁而强大的API&#xff0c;使开发者能够更专…

作者头像 李华
网站建设 2026/4/8 18:33:33

Proteus元器件大全:Proteus 8.0库文件全面讲解

Proteus元器件大全&#xff1a;从零读懂Proteus 8.0的元件世界你有没有遇到过这样的场景&#xff1f;电路图已经画好&#xff0c;仿真一启动&#xff0c;运放输出直接“冲顶”&#xff0c;MCU不运行&#xff0c;电机狂转不止……最后发现——用错了模型。在电子设计中&#xff…

作者头像 李华
网站建设 2026/4/5 16:35:27

利用sbit实现位寻址:高效寄存器配置方法

用 sbit 直达硬件&#xff1a;让8051位操作像写逻辑一样自然 你有没有过这样的经历&#xff1f;在调试一个LED闪烁程序时&#xff0c;看着这行代码发愣&#xff1a; P1 | 1 << 0;“这是点亮P1.0吗&#xff1f;还是清零&#xff1f;”——哪怕是有经验的工程师&#x…

作者头像 李华
网站建设 2026/4/11 9:35:21

bindkey 介绍

bindkey 是 Zsh Shell 中用于管理键盘绑定的命令&#xff0c;类似 Bash 的 bind 命令但功能更强大。它允许你自定义快捷键绑定、修改键盘映射。&#x1f4da; 一、基础用法1. 查看当前绑定# 查看所有绑定 bindkey# 查看指定按键序列的绑定 bindkey ^R # 查看 CtrlR 的绑定#…

作者头像 李华