ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Petalinux 2018.3内核打补丁添加自定义驱动完整指南

2026/9/17 2:48:22 拓冰建站 浏览量
Petalinux 2018.3内核打补丁添加自定义驱动完整指南 Petalinux 2018.3环境下给linux-xlnx内核源码打补丁往里面加一个自己写的驱动这件事做起来不算难但坑确实不少。很多刚接触Xilinx这套工具链的朋友卡在“不知道源码去哪了”“补丁放了但没生效”“驱动编译进去了设备节点却不出现”这类问题上一折腾就是两三天。这篇文章就把我从工程创建、源码提取、驱动编写、补丁生成、配置联动到打包烧写的完整流程写清楚基于Zynq-7000平台和Petalinux 2018.3照着操作基本能一次跑通。适合正在用Petalinux开发Zynq、想给内核增加自定义驱动的工程师也适合刚开始接触linux-xlnx的嵌入式Linux学习者。1. 为什么要在linux-xlnx源码层打补丁添加驱动1.1 先搞明白Petalinux的内核源码到底在哪用Petalinux做过一两次完整编译的人都会有这种感觉整个工程里源码藏得特别深你明明知道它在build目录下但每次重新编译就可能被清理掉想手动改点东西特别别扭。这是因为Petalinux的构建系统是基于Yocto的它把内核当成一个recipe来管理源码在构建时从本地缓存的layer里解包出来并不是一开始就铺在工程里。具体到2018.3版本内核recipe名字就是linux-xlnx对应的源码仓库是Xilinx维护的内核分支基于4.14主线再叠加Xilinx的BSP改动。每次执行petalinux-build -c kernel构建系统会先做unpack、patch、configure、compile这一套任务链。也就是说你对源码做的直接改动如果没有落地成补丁文件并交给构建系统下一次clean之后一切归零。这也是“打补丁”这套流程存在的根本原因源码是临时的补丁是持久的。想找源码的话常规命令是petalinux-build -c kernel -x unpack这条命令执行之后内核源码会被解包到工程目录下的build/linux-xlnx/里具体路径通常是工程目录/build/linux-xlnx/linux-xlnx/这里才是你改代码的地方。直接在这个目录里改源码当然可以但改完要通过git生成补丁再放进Petalinux能识别的位置否则成果随时会丢。1.2 什么场景需要走源码打补丁这条路很多驱动其实不需要改内核源码Petalinux提供了menuconfig配置界面大部分外设驱动只要在配置里勾选启用就行设备树里把节点加上驱动就能跑起来。那什么时候必须打补丁呢我的经验是三类情况。第一类是官方内核里根本没有这个驱动必须自己写比如给某个国产传感器、自定义硬件逻辑、私有IP核写驱动这种代码只能进源码树。第二类是官方驱动有bug或行为不符合需求你要在现有源文件上做修改比如改某个网卡驱动的PHY复位逻辑。第三类是驱动作为模块编译时依赖某些未导出的符号需要临时修改内核源码导出接口。还有一种情况容易被忽视驱动的兼容性匹配表、class命名、甚至在drivers目录下新增一个子目录和Kconfig入口这些都不属于单纯的配置必须动源码。只要动了源码最规范的做法就是打成补丁走recipe机制管理。1.3 补丁方案与直接改源码的取舍有些朋友图省事直接在build/linux-xlnx/linux-xlnx/里改完代码然后不生成补丁每次重新编译时都手动重复一遍同样的修改。短时间看是省事但一旦工程被清理、换电脑、或者和同事协作就全乱套了。补丁方案的核心价值是“可复现”。把补丁文件放进工程后任何人拿到这个工程只要编译一次改动就自动应用不需要口头交代“你记得把那三行代码加上”。我自己习惯把所有补丁统一放到工程里用git管理整个Petalinux工程目录补丁文件本身也就纳入版本控制了。后续升级内核版本时这些补丁还能单独评估哪些需要重做。Petalinux的recipe机制其实很灵活它支持在bbappend文件里用SRC_URI指定补丁列表也支持把补丁文件直接放在特定目录下自动加载。理解了机制之后你完全可以把补丁当成工程的一部分而不是临时操作。2. 环境准备和工程基建2.1 版本对应关系是第一步硬门槛Petalinux 2018.3不是独立存在的它必须和Vivado 2018.3配套使用。硬件描述文件HDF由Vivado生成Petalinux导入这个文件之后才知道FPGA里挂了什么IP、内存映射是什么、时钟频率是多少。我见过有人用Vivado 2019.1导出的HDF去配2018.3的Petalinux结果U-Boot阶段就起不来报各种奇怪的时序错误根本原因就是版本不匹配。如果你现在用的是新版本Vivado比如2023.1或者2024.x那建议直接用对应版本的Petalinux不要再碰2018.3了。老版本虽然稳定但新工具链对device tree overlay、新IP的支持更好。2018.3至今还在一些量产项目里服役主要是因为它配合Zynq-7000非常成熟很多老代码和老BSP都在这套组合上验证过。版本确认做好之后再确认内核版本。2018.3对应的linux-xlnx分支是xlnx_rebase_v4.14_2018.3编译完成之后可以通过/proc/version查看确认。如果你的驱动依赖某些新内核特性那2018.3可能不太适合趁早换新版本。2.2 创建Petalinux工程并导入硬件平台假设你已经装好了Petalinux 2018.3环境变量也source过了。第一步创建一个基于zynq模板的工程petalinux-create -t project --template zynq --name petalinux_drv_demo cd petalinux_drv_demo创建好之后导入Vivado导出的硬件描述文件petalinux-config --get-hw-description/path/to/your/hdf_dir这里的路径指的是包含system.hdf文件的目录。导入之后配置界面会显示平台信息内核版本、U-Boot版本、rootfs类型都在这里设置。我们做内核驱动实验保持默认配置就好不需要做额外修改。工程的目录结构有几个关键路径需要提前熟悉petalinux_drv_demo/ ├── build/ # 构建产物和源码解包目录 ├── components/ # Yocto layer集合 ├── images/linux/ # 最终镜像输出目录 ├── project-spec/meta-user/ # 用户自定义layer └── project-spec/configs/ # 工程配置文件其中project-spec/meta-user就是往Petalinux工程里加“私货”的地方后面补丁和设备树都放这里。2.3 内核源码目录的关键结构执行过unpack之后进到build/linux-xlnx/linux-xlnx/这就是一个完整的内核源码树。drivers目录下面按子系统分类char、net、i2c、spi、misc这些都在。我们要添加的驱动按用途放到合适的位置。比如一个简单的字符设备驱动可以放drivers/char下面一个杂项设备驱动放drivers/misc更省事因为它不用自己注册字符设备号misc子系统正好适合demo类驱动。我自己更推荐misc方式代码量少不涉及主设备号冲突验证方便。源码树里还涉及两个关键文件Kconfig和Makefile。Kconfig管的是“这个驱动能不能在menuconfig里被看到”Makefile管的是“这个驱动在什么条件下编译成什么”。这两个文件改不好驱动代码写得再好也编不进去。2.4 用git管理源码和补丁在内核源码目录里初始化一个git仓库是我做打补丁流程时固定的一步cd build/linux-xlnx/linux-xlnx/ git init git add . git commit -m baseline: linux-xlnx 2018.3 clean source为什么要这么做因为打补丁的本质是“生成一个格式规范的diff文件”。有了git基线你可以随时对比自己改了什么还可以用git format-patch生成带提交信息的补丁比git diff生成的裸diff更规范。Petalinux的patch机制两种格式都支持但我习惯用git format-patch风格因为补丁头部带了commit message和作者信息团队协作时一眼就能看出谁改的、为什么改。另外建议把源码目录里的.git目录备份一份。因为源码目录随时可能因为clean而消失git仓库本身不占多少空间备份之后可以直接在新的解包目录里恢复历史修改能省很多事。3. 打补丁的完整实操流程3.1 把源码提取出来并确认基线先从干净状态出发把内核源码重新解包一次确保没有历史残留petalinux-build -c kernel -x distclean petalinux-build -c kernel -x unpackdistclean会清掉之前的所有构建产物和补丁应用记录是一个很好的“重置”操作。unpack之后源码目录就是最原始的干净状态。此时再做一次git init和初始提交作为补丁基线后面所有改动都能追踪。有的老手会直接跳过distclean只unpack然后在旧源码基础上继续改。我建议第一次操作别省这一步因为如果之前有补丁已经应用进去了你摸不清当前源码处于什么状态后面生成补丁时会混入莫名其妙的差异。3.2 写一个最简单的misc驱动作为示例下面这个驱动是我的日常样板功能极简注册一个misc设备open时打印一句提示release时再打印一句。重点不在功能而在验证整个打补丁流程通不通。#include linux/module.h #include linux/miscdevice.h #include linux/fs.h #include linux/uaccess.h static int drv_open(struct inode *inode, struct file *file) { pr_info(petalinux drv open\n); return 0; } static int drv_release(struct inode *inode, struct file *file) { pr_info(petalinux drv release\n); return 0; } static const struct file_operations drv_fops { .owner THIS_MODULE, .open drv_open, .release drv_release, }; static struct miscdevice drv_device { .minor MISC_DYNAMIC_MINOR, .name petalinux_drv, .fops drv_fops, }; static int __init drv_init(void) { return misc_register(drv_device); } static void __exit drv_exit(void) { misc_deregister(drv_device); } module_init(drv_init); module_exit(drv_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Petalinux patch demo driver);把它保存为drivers/misc/petalinux_drv.c。这个文件就是我们要通过补丁加入内核源码树的东西。没有设备树节点也能加载因为misc设备不强制要求设备树匹配属于最简验证路径。3.3 修改Kconfig和Makefile光有C文件还不够需要让内核构建系统认识它。修改drivers/misc/Kconfig在合适的区域添加config PETALINUX_DRV tristate Petalinux patch demo driver default y help This is a demo driver used to verify the petalinux kernel patch flow. If unsure, say Y.这里tristate表示可以编译进内核y、编译成模块m或者完全不编译n。default y的意思是默认编进内核这样我们后面验证的时候不用额外modprobe启动就有设备节点。再修改drivers/misc/Makefile在合适位置添加一行obj-$(CONFIG_PETALINUX_DRV) petalinux_drv.o这个语法的含义是当CONFIG_PETALINUX_DRV的值是y时编译并链接进内核镜像值是m时编译成独立的petalinux_drv.ko模块。由于Kconfig里default y正常情况下这个文件会被编译进去。改完这两个文件三处改动就构成了完整的补丁内容新增一个.c文件、Kconfig增加条目、Makefile增加编译规则。3.4 生成规范补丁文件确认代码无误后查看当前改动状态git status git diff --stat改动应该包括一个新文件和两个修改文件。生成补丁用git format-patch它会为当前未提交的改动生成一个带commit信息的补丁文件git add drivers/misc/petalinux_drv.c drivers/misc/Kconfig drivers/misc/Makefile git commit -m add petalinux demo driver git format-patch -1执行后会生成一个类似0001-add-petalinux-demo-driver.patch的文件。这个补丁文件的内容包含完整的diff信息以及提交说明、作者、日期。把它复制到一个稳定位置比如工程下的补丁专用目录方便后续接入构建系统。如果你不想提交commit也可以用git diff直接生成裸补丁git diff 0001-add-petalinux-demo-driver.patch但是裸补丁没有commit message如果将来做内核版本升级patch应用的语义信息会不足。我还是推荐format-patch。3.5 把补丁接入Petalinux构建系统Petalinux的meta-user layer里有一个专门放内核补丁的地方。正确路径是project-spec/meta-user/recipes-kernel/linux/linux-xlnx/把补丁文件放进去之后还需要确认有没有对应的bbappend文件。如果之前没手动改过这个目录下会有一个linux-xlnx_%.bbappend或者需要新建一个。Petalinux在创建工程时通常会生成一个默认的bbappend文件里面已经有SRC_URI_append的示例。打开这个bbappend文件追加一行SRC_URI_append file://0001-add-petalinux-demo-driver.patch注意file://后面的文件名必须和实际放入的文件名完全一致包括后缀。如果同时有多个补丁按顺序逐行追加Petalinux会按文件名字母顺序或者按SRC_URI中的顺序依次应用。补丁之间如果有依赖关系建议把文件名加上数字前缀比如0001-xxx.patch、0002-xxx.patch这个习惯能避免不少麻烦。补丁文件位置和bbappend之间的关系是Petalinux的固定约定不需要显式指定路径只要文件在linux-xlnx目录下且bbappend里引用了文件名构建系统就会自动找到它。3.6 重建内核并验证效果补丁接入之后重新编译内核petalinux-build -c kernel如果补丁应用失败这里会直接报错。成功的话编译日志里能看到Applying patch的相关信息。编译完成后内核镜像会更新到images/linux/目录下。接下来验证驱动是否编进去了。可以先查内核镜像里的符号信息grep -r petalinux_drv images/linux/更直接的办法是生成启动镜像后烧到板子上看。或者在host上临时解包rootfs并chroot不过对内核模块来说最可靠的验证还是上板。我习惯先在menuconfig里确认一下配置项是否生效petalinux-config -c kernel进入Device Drivers - Misc devices能看到Petalinux patch demo driver这一项并且默认是选中的。这一步能确认Kconfig的改动确实生效了配置系统已经识别到这个新驱动。4. 内核配置与设备树联动4.1 配置项到底怎么管才不容易丢Petalinux工程的内核配置最初来自linux-xlnx的默认配置加上Xilinx的BSP配置。用petalinux-config -c kernel修改之后配置保存在工程配置里但只靠menuconfig手动勾选可移植性比较差。更可靠的方式有两种。一种是直接在bbappend里用SRC_URI追加一个defconfig片段比如放一个文件里面写好需要的CONFIG_*项。另一种是在工程根目录下用petalinux-config -c kernel生成最终配置之后把.config里的关键项提取出来整理成自己的配置片段。对这个demo驱动来说由于Kconfig里写了default y只要补丁应用成功配置项默认就是开启的。但在实际项目中很多驱动要由开发者主动打开那就需要显式管理CONFIG项。下面这个就是必知的基础驱动编译进内核CONFIG_PETALINUX_DRVy驱动编译成模块CONFIG_PETALINUX_DRVm完全不编译不配置这一项如果想把驱动编成模块同时在系统启动时自动加载还需要关注rootfs里是否包含了模块文件以及启动脚本里是否有modprobe操作。相比之下y的方式省心很多demo阶段强烈建议编进内核。4.2 设备树节点怎么加如果你写的是platform_driver类型的驱动光靠misc设备自动注册还不够需要在设备树里加节点否则驱动的probe函数不会被调用。设备树修改的入口文件是project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi这个文件专门用来覆盖和添加设备树内容。给驱动加一个简单节点假设驱动在probe里读取一个寄存器地址amba { petalinux_drv: petalinux-drv43c00000 { compatible xlnx,petalinux-drv; reg 0x43c00000 0x10000; interrupt-parent intc; interrupts 0 29 4; }; };compatible字段必须和驱动里of_match_table中定义的字符串一致这是设备树匹配的核心。很多初学者驱动加载不出来查到最后都是compatible写的不一致。reg字段对应硬件寄存器地址和长度interrupts按实际中断连接填写数量不对或者类型不对驱动申请中断时也可能出问题。改完设备树之后重新编译设备树petalinux-build -c device-tree这一步会生成新的dtb后续打包进image.ub时生效。如果你在验证阶段不确定设备树是否更新可以把生成的dtb反编译一下dtc -I dtb -O dts system.dtb | grep petalinux能搜到节点信息说明设备树改动生效了。4.3 从内核到BOOT.BIN和image.ub的完整打包驱动有了、设备树有了下一步是把它们打包成最终的启动镜像。Petalinux 2018.3的启动流程里BOOT.BIN和image.ub是两大核心文件。BOOT.BIN包含FSBL、FPGA bitstream和U-Bootimage.ub则通常包含内核、设备树有时还包括ramdisk。先生成BOOT.BINpetalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot images/linux/u-boot.elf --forcefsbl、bit、u-boot三个文件缺一不可顺序也不能乱。如果只是改内核和设备树BOOT.BIN其实可以复用之前的不用重新打包。但如果FPGA逻辑有变动或者U-Boot配置有变动就必须重新生成。image.ub的生成方式在2018.3里可以直接用petalinux-build生成不需要额外命令最终产物在images/linux/image.ub。内核和设备树有改动时只要重新执行petalinux-build或者更精确地petalinux-build -c kernel petalinux-build -c device-tree petalinux-build -c bootloader然后确认images/linux/下的image.ub时间戳是最新的。如果希望手动打包image.ub也可以使用petalinux-package --image -c kernel --format uImage但2018.3默认的fit image方式其实已经够用image.ub里已经包含了内核和设备树。4.4 SD卡制作与启动验证镜像都生成好之后制作SD卡也是固定动作。SD卡需要两个分区第一个分区FAT32用来放BOOT.BIN、image.ub和boot.scr第二个分区EXT4放rootfs。boot.scr是U-Boot的启动脚本Petalinux在构建时会生成不要手动删除。分区和文件系统的命令可以这样操作sudo fdisk /dev/sdb # 删除旧分区新建两个分区1号分区类型c(FAT32)2号分区类型83(Linux) sudo mkfs.vfat -F 32 /dev/sdb1 sudo mkfs.ext4 /dev/sdb2格式化好之后把BOOT.BIN、image.ub、boot.scr拷贝到第一个分区rootfs的内容拷贝到第二个分区sudo mount /dev/sdb1 /mnt/boot sudo cp images/linux/BOOT.BIN images/linux/image.ub images/linux/boot.scr /mnt/boot/ sudo umount /mnt/boot sudo mount /dev/sdb2 /mnt/rootfs sudo tar -xf images/linux/rootfs.tar.gz -C /mnt/rootfs sudo umount /mnt/rootfs上电启动之后用串口登录系统执行ls /dev/petalinux_drv dmesg | grep petalinux如果能看到设备节点说明驱动已经成功加载整个打补丁流程走通了。在我的使用经验里这一步看到设备节点的瞬间前面所有折腾都值了。5. 常见问题与排查技巧实录5.1 patch无法应用这是最常遇到的第一道坎。补丁应用失败时构建日志里会显示类似“patch failed”的提示然后整个内核构建中断。常见原因有三类。第一类补丁是基于已经被修改过的源码生成的目标源码的状态和生成补丁时的基线不一致。这种问题多是因为在同一个工程里重复应用了同一个补丁。解决办法是先把源码恢复干净再应用一次。如果用的是git管理检查一下源码目录里是否有已经应用的痕迹。第二类补丁文件格式不对。用git format-patch生成的补丁基本不会有问题但如果你手动拼了一个补丁或者从Windows环境拷贝过来行尾符可能变成CRLF导致应用失败。建议统一用git生成补丁拷贝时保持LF行尾。第三类补丁文件路径和源码解包结构不匹配。有的补丁在diff头部写了a/drivers/misc/xxx.c但源码解包后的实际目录不是drivers/misc而是别的路径这就会失败。用git format-patch从源码目录直接生成通常不会出现这个问题。排查时可以手动测试补丁是否可用cd build/linux-xlnx/linux-xlnx/ git apply --check /path/to/0001-xxx.patch如果没有任何输出说明补丁可以应用如果报错会提示具体哪个文件、哪一行失败。5.2 驱动编译进内核却找不到设备节点补丁应用成功了内核编译也过了但启动后/dev下没有设备节点。这种情况probe没跑或者驱动没注册成功。先查dmesgdmesg | grep petalinux一点输出都没有说明驱动要么没编进去要么初始化函数没执行。先确认内核里真的有这个驱动cat /proc/misc | grep petalinux如果/proc/misc里有说明misc_register已经执行了设备节点可能只是udev还没创建可以手动创建设备节点验证。如果没有回到host侧检查.config里CONFIG_PETALINUX_DRV是否等于y。如果dmesg里有注册失败的报错常见原因是minor号冲突或者misc设备名重复。你可以把设备名改一个更独特的试试。还有就是在内核启动早期misc子系统还没初始化但这种情况在标准内核启动顺序下基本不会发生。5.3 编译报错却定位不到原因驱动代码编译报错时问题多半出在头文件或者API使用上。4.14内核的API和现在的新内核差异很大如果你从网上抄了一段适配新内核的驱动代码放到2018.3里编译报错是很正常的。比如copy_to_user、copy_from_user的用法在新旧内核差异不大但某些procfs API、gpio API变化很大。查代码的时候务必确认API的上下文是4.14时代的。一个实用的方法在源码目录里用grep搜其他驱动是怎么调用相同API的。如果报错信息指向头文件找不到比如linux/of_device.h不存在那很可能代码里include了不存在的头文件或者这个头文件在新版本里被合并了。4.14时代很多设备树相关的接口头文件还算稳定按老代码改一般没问题。5.4 模块加载不了或者modprobe找不到如果驱动是编译成模块的启动后modprobe petalinux_drv提示找不到第一反应是module文件根本没进rootfs。Petalinux构建时模块文件默认放在rootfs的/lib/modules/ /路径下但如果rootfs没有执行depmod模块依赖关系不会建立modprobe就找不到。手动执行depmod -a modprobe petalinux_drv能加载的话说明rootfs只是缺少depmod步骤。如果想每次启动都自动加载可以把modprobe命令写进启动脚本或者把配置项改回y直接编进内核一步到位。5.5 常见问题速查表汇总一下我反复遇到的一些坑做成表格方便快速对照现象可能原因排查/解决方向patch failed源码基线不对、补丁格式错误git apply --check 验证重新基于干净源码生成补丁内核编译通过但驱动没生效CONFIG项未开启或未编进镜像menuconfig确认写入了配置编译后检查.config/dev下无设备节点misc注册失败、设备树无节点、udev未创建dmesg查日志cat /proc/misc手动mknod验证probe没被调用compatible不匹配、设备树节点缺失检查of_match_table和设备树compatible是否一致modprobe找不到模块模块没进rootfs、依赖没建立depmod -a确认/lib/modules路径image.ub里看不到新驱动打包的是旧内核重新petalinux-build -c kernel生成镜像编译报错API不存在代码基于新内核编写按4.14 API重新适配这些坑我都踩过不止一次尤其是补丁应用失败和设备树compatible不一致占了七成以上问题。养成“先手动验证补丁再查设备树匹配”的习惯能省下大量时间。写在最后的一点体会这套Petalinux 2018.3打补丁流程本质上是理解“Yocto管理的源码是临时的补丁才是长久的”这一件事。内核源码随意改、改了不生成补丁一定能跑通一次但维持不了几天。把改动落成补丁、放进meta-user、提交到版本库后续谁拿到工程都能复现这才是正确的工程习惯。我个人还有一个建议每完成一个驱动的补丁提交就顺手更新一下文档或提交信息写清楚这个驱动解决什么问题、依赖哪些硬件资源。因为补丁文件多了以后单看文件名根本记不住是干嘛的。另外Petalinux新版本在源码管理机制上已经有了一些变化比如部分版本用kernel-srcrev的机制但“补丁驱动内核”的核心思路始终没变源码是基座patch是增量设备树是连接硬件和驱动的桥梁。把这个链路理解透了不管工具链版本怎么换你都能快速上手。