2026/4/16 19:51:48
网站建设
项目流程
自己做平台网站,网站设计与推广,长沙网站建设有限公司,温州网页设计制作从零开始#xff1a;在 PetaLinux 中编写一个真正可用的 Platform 驱动你有没有遇到过这种情况#xff1f;FPGA 里写好了一个自定义 IP#xff0c;比如 ADC 控制器、音频采集模块或者 GPIO 扩展器#xff0c;逻辑都跑通了#xff0c;但到了 Linux 系统里却“看不见”它——…从零开始在 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_init和module_exit更简洁安全。第四步修改 Kconfig 和 Makefile编辑project-spec/meta-user/recipes-modules/my_platform_driver/Kconfigconfig 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.编辑Makefileobj-m my_platform_driver.o第五步添加设备树节点编辑project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsiamba { my_custom_ip: my_custom_ip43c00000 { 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是无效的必须看启动日志。最佳实践总结老司机的经验永远优先使用devm_函数devm_kzalloc,devm_ioremap_resource,devm_request_irq……它们是你防止内存泄漏的第一道防线。compatible 字符串要有意义推荐格式厂商,功能-版本例如xlnx,i2s-audio-v1方便未来扩展兼容旧版。不要硬编码地址所有资源都从platform_get_resource()获取保持驱动可移植性。模块卸载必须干净即使你不打算卸载也要写完整的 remove 函数否则内核可能报错。加调试信息但发布时关掉开发阶段多用dev_dbg()配合pr_debug()需要时通过dynamic_debug控制开关。考虑多实例支持如果将来可能有多个同类 IP记得用container_of和platform_set_drvdata管理上下文。这个驱动能做什么不止是读写寄存器你现在有了一个基础框架接下来可以轻松扩展添加中断处理在 probe 中用devm_request_irq()注册 IRQ handler。支持 DMA结合dmaengineAPI 实现高速数据搬运。暴露控制接口通过sysfs提供启停、复位等操作。封装为 ALSA 驱动如果你做的是音频可以对接 ALSA SoC 框架。但所有这一切都建立在你能先把 Platform 驱动跑通的基础上。写在最后别再复制粘贴了网上太多教程只是把内核文档翻译一遍或者直接贴一段不完整的代码。真正的嵌入式开发是在 PetaLinux 工程里一步步构建、编译、烧录、调试出来的。本文提供的不是一个“例子”而是一个可复用、可调试、可扩展的起点模板。你可以把它用在下一个项目中只需要改三个地方compatible字符串DTS 节点地址读写逻辑根据你的 IP 寄存器定义剩下的交给 Platform 驱动模型和 PetaLinux 工具链。掌握这套方法论你就不再是“调驱动的人”而是“写驱动的人”。如果你在实现过程中遇到了其他挑战欢迎在评论区分享讨论。