[实战] Petalinux驱动开发以及代码框架解读
目录
- Petalinux驱动开发以及代码框架解读
- 一、引言
- 二、步骤
- 2.1 创建PetaLinux工程
- 2.2 配置硬件描述文件
- 2.3 设备树配置
- 2.4 建立驱动框架
- 2.5 编辑 `.bb` 文件
- 2.6 编写驱动文件
- 2.7 编写 `Makefile`
- 2.8 验证配方配置
- 2.9 集成驱动到 RootFS
- 2.10 全系统编译与部署
- 2.11 启动验证
- 三、框架解读
- 3.1 模块基本信息
- 3.2 模块参数
- 3.3 数据结构:customGpioExport_local
- 3.4 中断处理函数
- 3.5 Probe函数:设备初始化
- **功能**:
- **流程**:
- 3.5 Remove函数:资源释放
- 3.7 设备树匹配
- 3.8 平台驱动结构体
- 3.9 模块初始化和退出
- 3.10 代码执行流程
- 3.11 潜在改进点
- 四、结语
Petalinux驱动开发以及代码框架解读
一、引言
Petalinux是Xilinx提供的Linux开发工具链。通过Petalinux,可以很轻松的完成Xilinx SOC的Linux系统定制,驱动和应用开发。本文主要从驱动开发角度,基于petalinux2022.2版本,介绍如何同通过Petalinux开发自己的驱动。
二、步骤
安装Petalinux以及使用Vivado创建SOC工程,这些基础步骤,网上由太多教程,这里就不累述了。这里假设大家已经安装好了Vivado,Petalinux,并已经通过Vivado工程导出了硬件描述文件(.xsa文件)。
2.1 创建PetaLinux工程
petalinux-create -t project --name zynq_gpio_demo --template zynq
cd zynq_gpio_demo
2.2 配置硬件描述文件
- 将Vivado生成的
system.xsa
文件复制到工程目录。 - 导入硬件配置:
petalinux-config --get-hw-description=.
2.3 设备树配置
在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
中添加GPIO节点:
/ {custom_gpios {compatible = "customGpioExport";status = "okay";gpios = <&gpio0 54 GPIO_ACTIVE_HIGH>, // EMIO GPIO0<&gpio0 55 GPIO_ACTIVE_HIGH>; // EMIO GPIO1};
};
- 关键属性:
compatible
:驱动匹配标识符。gpios
:指定GPIO控制器、引脚号和激活电平。
2.4 建立驱动框架
petalinux为用户开发自己的驱动建立了完整易用的驱动框架,可以通过petalinux-create -t modules
命令创建,如下所示:
petalinux-create -t modules --name customGpioExport --enable
通过上述指令,就会在{petalinux project}/project-spec/meta-user/recipes-modules/
目录下生成custom_gpio_export
目录,在该目录下,自动生成了custom_gpio_export .bb
文件以及files
文件加,在files
文件夹下,成了驱动框架文件customGpioExport .c
以及Makefile
,以下是目录结构图:
{petalinux project}/
└── project-spec/└── meta-user/└── recipes-modules/└── customGpioExport/├── customGpioExport.bb└── files/├── customGpioExport.c└── Makefile
2.5 编辑 .bb
文件
打开{petalinux project}/project-spec/meta-user/recipes-modules/customGpioExport
下的customGpioExport.bb
文件并修改内容如下:
# 配方基础配置
SUMMARY = "Custom GPIO Export Driver"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"# 定义模块名称和源码路径
MODULE_NAME = "customGpioExport"
SRC_URI = " \file://${MODULE_NAME}.c \file://Makefile \
"# 指定源码目录(默认为当前目录)
S = "${WORKDIR}"# 继承内核模块构建类
inherit module# 定义模块编译参数
EXTRA_OEMAKE:append = " \KERNEL_SRC=${STAGING_KERNEL_DIR} \KERNEL_VERSION=${KERNEL_VERSION} \
"# 安装目标(将模块复制到 rootfs 的 /lib/modules/ 目录)
do_install() {install -d ${D}/lib/modules/${KERNEL_VERSION}/extrainstall -m 0644 ${B}/${MODULE_NAME}.ko ${D}/lib/modules/${KERNEL_VERSION}/extra/
}# 定义模块依赖(可选)
# DEPENDS = "virtual/kernel"
关键配置解释
-
SRC_URI
指定驱动源码路径。此处假设源码文件为customGpioExport.c
和Makefile
,位于files
子目录。
若源码文件名为customGpioExport.c
,需同步修改:SRC_URI = " \file://customGpioExport.c \file://Makefile \ "
-
inherit module
继承内核模块构建类,自动处理模块编译和签名(如需 Secure Boot)。 -
EXTRA_OEMAKE
向make
命令传递额外参数:KERNEL_SRC
:指向内核源码目录。KERNEL_VERSION
:内核版本号(自动填充)。
-
do_install
定义模块安装逻辑,将生成的.ko
文件复制到 rootfs 的/lib/modules/$(uname -r)/extra/
目录。
2.6 编写驱动文件
根据自己需要以customGpioExport.c
为基础框架,增加自己模块需要的功能实现即可,不在这里累述。
2.7 编写 Makefile
确保 files/Makefile
内容如下(适配内核模块编译):
obj-m := custom_gpio_export.oKERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KERNEL_SRC) M=$(PWD) modulesclean:$(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean
- 注意:
obj-m
后的目标名必须与.c
文件名一致(例如customGpioExport.c
→customGpioExport.o
)。- 若源码文件名为
customGpioExport.c
,需修改为:obj-m := customGpioExport.o customGpioExport-objs := customGpioExport.o
2.8 验证配方配置
执行以下命令检查语法和路径:
# 进入 PetaLinux 工程根目录
cd zynq_gpio_demo# 运行 BitBake 解析配方
petalinux-build -c customGpioExport --force
-
预期输出:
若配置正确,将输出编译日志并在build/tmp/work/.../customGpioExport/
下生成.ko
文件。 -
常见错误:
- 文件未找到:检查
SRC_URI
和files/
目录中的文件名是否一致。 - 编译错误:查看
build/tmp/work/.../temp/log.do_compile
中的详细日志。
- 文件未找到:检查
2.9 集成驱动到 RootFS
在 petalinux-config
中启用模块自动加载:
petalinux-config -c rootfs
进入菜单:
Filesystem Packages --->module --->customGpioExport --->[*] customGpioExport
2.10 全系统编译与部署
petalinux-build
petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot
2.11 启动验证
系统启动后检查模块加载:
# 查看模块是否加载
lsmod | grep customGpioExport# 手动加载(若未自动加载)
modprobe customGpioExport# 检查 GPIO 导出状态
ls /sys/class/gpio/
三、框架解读
3.1 模块基本信息
代码开头通过宏定义了模块的基本信息:
- MODULE_LICENSE(“GPL”):声明模块遵循GPL协议。
- MODULE_AUTHOR和MODULE_DESCRIPTION:提供作者和模块描述。
- MODULE_DEVICE_TABLE(of, …):用于与设备树中的节点匹配(后文详述)。
3.2 模块参数
通过module_param
定义了两个模块参数:
unsigned myint = 0xdeadbeef;
char *mystr = "default";
module_param(myint, int, S_IRUGO); // 整型参数
module_param(mystr, charp, S_IRUGO); // 字符串参数
- 这些参数可在加载模块时通过命令行指定,例如:
insmod customGpioExport.ko myint=1234 mystr="hello"
- 参数权限为
S_IRUGO
,表示用户空间可读但不可写。
3.3 数据结构:customGpioExport_local
struct customGpioExport_local {int irq; // 中断号unsigned long mem_start; // 内存起始地址unsigned long mem_end; // 内存结束地址void __iomem *base_addr; // 映射后的虚拟地址
};
- 该结构体保存设备的硬件资源信息,如中断号、寄存器物理地址及其映射后的虚拟地址。
3.4 中断处理函数
static irqreturn_t customGpioExport_irq(int irq, void *lp) {printk("customGpioExport interrupt\n");return IRQ_HANDLED;
}
- 当设备触发中断时,此函数被调用。
- 当前仅打印一条日志,实际应用中需添加具体的中断处理逻辑(如读取状态寄存器、处理数据等)。
- 注意:
request_irq
的第三个参数(标志位)为0
,未指定触发方式(如上升沿或电平触发),可能需要根据硬件需求调整。
3.5 Probe函数:设备初始化
static int customGpioExport_probe(struct platform_device *pdev)
功能:
- 在设备树匹配到驱动时调用,负责初始化设备资源(内存、中断等)。
流程:
-
获取内存资源:
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- 从设备树中获取寄存器内存范围(
mem_start
和mem_end
)。
- 从设备树中获取寄存器内存范围(
-
分配结构体内存:
lp = kmalloc(sizeof(struct customGpioExport_local), GFP_KERNEL);
- 为
customGpioExport_local
分配内存,保存设备信息。
- 为
-
请求内存区域:
request_mem_region(lp->mem_start, ...);
- 锁定物理内存区域,防止其他驱动占用。
-
内存映射:
lp->base_addr = ioremap(lp->mem_start, ...);
- 将物理地址映射为内核虚拟地址,后续可通过
iowrite32
/ioread32
等函数访问寄存器。
- 将物理地址映射为内核虚拟地址,后续可通过
-
获取中断资源:
r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- 若设备树中定义了中断,注册中断处理函数:
request_irq(lp->irq, &customGpioExport_irq, 0, ...);
- 若设备树中定义了中断,注册中断处理函数:
-
错误处理:
- 若某步骤失败,依次释放已申请的资源(如
release_mem_region
、iounmap
、kfree
等)。
- 若某步骤失败,依次释放已申请的资源(如
3.5 Remove函数:资源释放
static int customGpioExport_remove(struct platform_device *pdev)
- 在模块卸载或设备移除时调用。
- 释放所有资源:中断、虚拟地址映射、内存区域及结构体内存。
3.7 设备树匹配
static struct of_device_id customGpioExport_of_match[] = {{ .compatible = "vendor,customGpioExport", },{ /* end of list */ },
};
- 通过
compatible
属性与设备树中的节点匹配。例如,设备树中需包含:customGpioExport@0x12340000 {compatible = "vendor,customGpioExport";reg = <0x12340000 0x1000>;interrupts = <0 29 4>; };
3.8 平台驱动结构体
static struct platform_driver customGpioExport_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = customGpioExport_of_match,},.probe = customGpioExport_probe,.remove = customGpioExport_remove,
};
- 定义平台驱动的名称、设备树匹配表、Probe和Remove函数。
3.9 模块初始化和退出
-
初始化:
static int __init customGpioExport_init(void) {platform_driver_register(&customGpioExport_driver);printk("Hello module world.\n"); }
- 注册平台驱动,并打印初始化信息。
-
退出:
static void __exit customGpioExport_exit(void) {platform_driver_unregister(&customGpioExport_driver);printk("Goodbye module world.\n"); }
- 注销驱动并清理资源。
3.10 代码执行流程
-
模块加载:
- 执行
customGpioExport_init
,注册平台驱动。 - 内核扫描设备树,若找到匹配的
compatible
节点,调用customGpioExport_probe
初始化设备。
- 执行
-
设备初始化:
- 申请内存、映射地址、注册中断(如有)。
-
模块卸载:
- 执行
customGpioExport_exit
,注销驱动并调用customGpioExport_remove
释放资源。
- 执行
3.11 潜在改进点
- 中断标志位:
request_irq
未指定触发方式(如IRQF_TRIGGER_RISING
),需根据硬件配置。 - GPIO功能:当前代码未实现GPIO的导出或操作逻辑,需补充
gpio_request
、gpio_direction_output
等函数。 - 模块参数应用:
myint
和mystr
未被使用,可扩展为配置参数(如设置GPIO初始状态)。
总结
此代码是一个典型的内核模块框架,实现了设备树匹配、资源管理、中断处理等基础功能,但具体功能(如GPIO操作)需进一步扩展。开发者可根据实际需求,在Probe函数中添加硬件初始化代码,并在中断处理函数中实现业务逻辑。
四、结语
在petalinux框架下,完成linux驱动开发门槛大大降低,效率提高不少,希望这篇文章能够抛砖引玉,对大家有所帮助。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)