编写Linux下usb设备驱动方法:probe函数中要完成的任务
一. 简介
前一篇文章简单学习了 Linux下usb设备驱动实现流程,文章如下:
编写Linux下usb设备驱动方法:usb设备驱动实现流程-CSDN博客
本文来学习一下 usb设备驱动的 probe函数要完成的任务。
当usb主控制器检测到设备与 驱动相匹配时,就会执行usb的 probe函数。
二. 如何编写Linux下usb设备驱动
在 Linux 下编写 USB 设备驱动,是嵌入式开发和硬件交互中的核心技能。Linux 提供了完善的 USB 子系统,采用分层架构,开发者只需编写 设备驱动(Client Driver) 层,注册到 USB Core,即可实现与设备的通信。
usb应用与usb驱动框架调用如下图:
1. usb设备驱动的核心概念
(1) USB 设备模型:Linux 内核将 USB 设备抽象为「设备(device)」和「驱动(driver)」,通过总线(bus)进行匹配(基于厂商 ID、产品 ID 等)。
(2) 核心结构体
struct usb_driver
:驱动的核心结构体,包含驱动名称、匹配规则、探测 / 断开函数等。struct usb_driver
结构体只是负责找到usb设备,管理usb设备连接和断开的作用。
struct usb_interface
:表示 USB 设备的一个接口(一个设备可能有多个接口)。struct usb_endpoint_descriptor
:端点描述符,描述 USB 设备的通信端点(数据传输的通道)。urb
(USB Request Block):USB 数据传输的核心结构,用于异步传输数据。
2. 了解目标 USB 设备的信息
在编写驱动前,需通过工具获取设备的关键信息(用于驱动匹配):
- 厂商 ID(idVendor)和产品 ID(idProduct):通过
lsusb
命令查看(例如:Bus 001 Device 005: ID 1234:5678 VendorName
)。
- 端点配置:通过 "
lsusb -v -d 厂商ID:产品ID"
查看端点类型(控制 / 批量 / 中断 / 等时)、地址、最大包大小等。
注意:因为在编写 usb设备驱动时,需要配置 id_table,具体配置usb设备的 PID和 VID值。
三. 编写Linux下usb设备驱动的方法
1. 定义 usb驱动元数据:struct usb_driver
这是驱动的入口,它告诉 这个USB设备驱动驱动支持哪些设备,以及回调函数是什么:
static struct usb_driver my_usb_driver = {.name = "my_usb_driver", //usb驱动名称.probe = my_probe, //当驱动匹配时调用的函数.disconnect = my_disconnect,//当设备断开时调用的函数.id_table = my_id_table, //该驱动支持的设备ID列表
};
2. 定义支持的设备:id_table
这是一个 struct usb_device_id
数组,用于列出驱动程序支持的所有 USB 设备。内核通过 Vendor ID (VID) 和 Product ID (PID) 来匹配设备:
static struct usb_device_id my_id_table[] = {{ USB_DEVICE(0x1234, 0xa1a2) }, //支持的usb设备VID和PID{} //列表结束标志
};
//将设备ID表导出到内核,用于模块加载时内核识别该驱动支持的设备
MODULE_DEVICE_TABLE(usb, my_id_table);
3. 实现 probe函数
当 USB主控制器驱动发现一个设备与你的 id_table
匹配时,就会调用 probe函数。probe函数的主要任务:
(1) 分配与初始化驱动私有数据结构: 使用 kzalloc
或 devm_kzalloc
。
- 分配私有数据内存:使用
kzalloc
分配内存(确保初始化为 0,避免野指针)。 - 关联核心对象:将
usb_device
、usb_interface
等核心结构体指针存入私有数据(需通过usb_get_dev
增加设备引用计数,确保设备不被意外释放)。 - 初始化关键成员:如数据传输缓冲区大小、URB 指针初始化为
NULL
等。
(2) 初始化数据传输机制(URB 准备)
USB 设备的核心功能是数据传输,probe
函数需为传输准备好 URB(USB Request Block,USB 请求块)—— 内核中 USB 传输的 “载体”:
- 分配 URB:通过
usb_alloc_urb
分配与端点类型匹配的 URB(如中断端点用usb_alloc_urb(0, ...)
,等时端点需指定 iso 包数量)。 - 填充 URB:根据端点类型调用对应的填充函数(如中断端点用
usb_fill_int_urb
,批量端点用usb_fill_bulk_urb
),设置传输方向、缓冲区、完成回调函数等。 - 关联 URB 与私有数据:将私有数据设为 URB 的
context
,便于回调函数中访问设备资源。
(3) 注册用户空间接口(可选)
若驱动需要与用户空间程序交互(如通过 ioctl 发送命令、读写数据),probe 函数需注册字符设备、sysfs 节点或其他接口:
字符设备注册:通过 cdev_init、cdev_add 注册字符设备,关联文件操作结构体(file_operations),让用户空间可通过 /dev/xxx 访问。
sysfs 节点创建:通过 sysfs_create_group 等函数创建属性文件,暴露设备状态或配置参数(如 echo 1 > /sys/class/myusb/enable 开启设备)。
(4) 启动数据传输(可选)
对于需要 “设备插入后立即开始工作” 的场景(如传感器设备周期性上报数据),probe函数可在初始化完成后直接提交 URB,启动首次数据传输: