ESC
输入关键词搜索文章标题和内容

USB驱动开发:从协议描述符到URB传输的完整实战

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

USB驱动开发:从协议描述符到URB传输的完整实战

USB是嵌入式Linux里最复杂的总线之一,协议层次深、描述符嵌套多、传输类型分四种,再加上URB异步机制,新手很容易绕晕。但USB驱动的套路其实很固定——搞清描述符层次、写对id_table匹配、管好URB生命周期,剩下的就是填空。本文基于Linux 6.8内核,从USB协议基础讲起,过一遍核心数据结构,再用完整示例把probe/disconnect/URB操作串起来,最后给调试技巧和常见坑点。

一、USB基础知识

USB拓扑结构

USB是树形拓扑,一根总线上的设备分三层角色:

Root Hub (主机控制器内置
  ├── Hub
    ├── Device A
    └── Device B
  └── Device C
  • 主机(Host):发起所有传输,只有一个,由主机控制器(HCI)实现。- 集线器(Hub):扩展端口,级联最多7层(含Root Hub)。- **设备(Device):最终的功能设备,每个设备最多16个端点(0号端点固定为控制端点。
    主机和设备之间是主从关系——设备永远不能主动发数据,只能被动响应主机的请求,中断传输和等时传输看起来是设备"主动"上报,实际是主机在周期性轮询。

四种传输类型

传输类型 方向 可靠性 带宽保证 典型应用
控制传输 双向 有(重试) 无 枚举、配置、类请求
批量传输 单向 有(重试) 无 U盘、打印机
中断传输 单向(IN为主) 有(重试) 有(周期保证) 键盘、鼠标
等时传输 单向 无(不重试) 有(固定带宽) 音频、视频

控制传输是USB的基础——设备枚举、配置切换、类特定命令全走它,批量传输追求可靠但不保证时效,适合U盘这种大数据量场景,中断传输名字有误导性,它不是硬件中断,而是主机按固定间隔轮询设备,等时传输牺牲可靠性换带宽,丢包不重传,音视频流必须用它。

USB描述符层次

USB设备通过描述符向主机报告自己的能力,描述符是严格嵌套的:

设备描述符(Device Descriptor)
  └── 配置描述符(Configuration Descriptor)
        ├── 接口描述符(Interface Descriptor)
            └── 端点描述符(Endpoint Descriptor)
        └── 接口描述符(Interface Descriptor)
              └── 端点描述符(Endpoint Descriptor)

一个设备可以有多个配置(实际几乎只有1个),每个配置下有多个接口(代表不同功能),每个接口下有多个端点(数据传输的通道)。

四种描述符关键字:

描述符 长度 关键字段 说明
设备描述符 18字节 bDeviceClass、idVendor、idProduct、bNumConfigurations 设备级分类、VID/PID、配置数量
配置描述符 9字节 bConfigurationValue、bmAttributes、bMaxPower 配置值、供电方式、最大功能
接口描述符 9字节 bInterfaceClass、bInterfaceNumber、bNumEndpoints 接口类、接口编号、端点数量
端点描述符 7字节 bEndpointAddress、bmAttributes、wMaxPacketSize 端点地址+方向、传输类型、最大包长

端点地址的最高位表示方向:0=OUT(主机→设备),1=IN(设备→主机)。低4位是端点号。比如0x81表示端点1、IN方向0x02表示端点2、OUT方向。

二、USB核心数据结构

USB驱动开发打交道最多的就四个结构体:usb_driver、usb_device_id、urb、usb_interface。

usb_driver:驱动注册入。

// linux/usb.h(简化)
struct usb_driver {
    const char *name;
    int  (*probe)(struct usb_interface *intf,
                  const struct usb_device_id *id);
    void (*disconnect)(struct usb_interface *intf);
    int  (*suspend)(struct usb_interface *intf, pm_message_t msg);
    int  (*resume)(struct usb_interface *intf);
    const struct usb_device_id *id_table;
    struct device_driver driver;  // 内嵌device_driver。.8统一设备模型
};
  • name:驱动名,出现在/sys/bus/usb/drivers/。- probe:匹配成功后调用,做初始化- disconnect:设备拔出时调用,做清理
  • id_table:匹配表,决定这个驱动管哪些设备

注册用usb_register_driver(),注销用usb_deregister()。内核提供了便捷宏:

module_usb_driver(my_usb_driver);
// 等价于 module_init + usb_register + module_exit + usb_deregister

usb_device_id:设备匹配规。

// linux/mod_devicetable.h(简化)
struct usb_device_id {
    __u16 match_flags;       // 哪些字段参与匹配
    __u16 idVendor;          // VID
    __u16 idProduct;         // PID
    __u16 bDeviceClass;      // 设备。    __u16 bDeviceSubClass;   // 设备子类
    __u16 bDeviceProtocol;   // 设备协议
    __u16 bInterfaceClass;   // 接口。    __u16 bInterfaceSubClass;// 接口子类
    __u16 bInterfaceProtocol;// 接口协议
    kernel_ulong_t driver_info; // 驱动私有数据
};

match_flags决定匹配策略,常用标志:

标志 含义
USB_DEVICE_ID_MATCH_VENDOR 匹配VID
USB_DEVICE_ID_MATCH_PRODUCT 匹配PID
USB_DEVICE_ID_MATCH_DEVICE VID+PID同时匹配
USB_DEVICE_ID_MATCH_INT_CLASS 匹配接口类

实际写驱动时用宏定义匹配表更直观。

// 精确匹配VID/PID
{ USB_DEVICE(0x1234, 0x5678) }

// 匹配某类设备(如HID类)
{ USB_INTERFACE_INFO(USB_CLASS_HID, 0, 0) }

// 匹配某VID的所有设。{ USB_DEVICE_VENDOR(0x1234) }

urb:USB请求。

urb(USB Request Block)是USB传输的核心数据结构,所有USB数据传输都通过urb完成。

// linux/usb.h(简化)
struct urb {
    struct usb_device *dev;       // 目标设备
    unsigned int pipe;            // 管道(端点+方向+类型编码)    void *transfer_buffer;        // 传输缓冲区    u32 transfer_buffer_length;   // 缓冲区长度    u32 actual_length;            // 实际传输长度
    usb_complete_t complete;      // 完成回调
    void *context;                // 回调上下。    int status;                   // 传输状态    unsigned int transfer_flags;  // 传输标志
    int interval;                 // 轮询间隔(中断/等时)    // ... 更多字段
};

urb是异步的——提交后函数立刻返回,传输完成后内核调用complete回调。这跟同步的usb_bulk_msg不同,后面会对比

usb_device / usb_interface / usb_host_endpoint

结构体 代表 获取方式
usb_device 整个USB设备 interface_to_usbdev(intf)
usb_interface 一个功能接口 probe参数直接拿到
usb_host_endpoint 一个端点 usb_pipe_endpoint(dev, pipe)

驱动绑定的对象是usb_interface而不是usb_device。一个多功能设备(如带摄像头的麦克风)有多个接口,每个接口由不同驱动管理。

三、USB驱动开发流程

flowchart TB A(["开销"]) --> B["定义usb_device_id匹配置"] B --> C["实现usb_driver<br/>probe/disconnect/id_table"] C --> D["module_usb_driver注册"] D --> E{"USB核心匹配设备"} E -->|"匹配成功"| F["probe回调"] F --> G["获取interface<br/>查找端点描述"] G --> H["分配urb和缓冲区"] H --> I["填充urb<br/>usb_fill_bulk_urb"] I --> J["提交urb<br/>usb_submit_urb"] J --> K["完成回调处理数据"] K --> L{"继续传输?"} L -->|"| I L -->|"| M(["传输完成"]) E -->|"匹配失败"| N(["等待新设备"]) O["设备拔出"] --> P["disconnect回调"] P --> Q["usb_kill_urb"] Q --> R["释放urb和缓冲区"] style A fill:#E8F5E9 style E fill:#FFF8E1 style F fill:#E3F2FD style L fill:#FFF8E1 style P fill:#FFEBEE style M fill:#E8F5E9 style N fill:#F3E5F5

MODULE_DEVICE_TABLE

匹配表定义后必须加这行宏,否则modprobe找不到驱动:

static struct usb_device_id my_usb_table[] = {
    { USB_DEVICE(0x1234, 0x5678) },
    { }  // 末尾空项,必。};
MODULE_DEVICE_TABLE(usb, my_usb_table);

这个宏的作用是把匹配表导出到模块的__mod_usb_device_table段,depmod生成modules.alias时读取它。用户空间udev收到USB设备热插拔事件后,根据modules.alias找到对应驱动并自动加载。不加这行,驱动能编译能insmod,但插上设备不会自动probe。

四、USB驱动完整示例

下面是一个基于Linux 6.8的USB批量传输驱动,功能是向设备的OUT端点发送数据,从IN端点接收数据。

// my_usb_drv.c - Linux 6.8 USB批量传输驱动示例
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/slab.h>

#define VENDOR_ID  0x1234
#define PRODUCT_ID 0x5678

// 驱动私有数据
struct my_usb_dev {
    struct usb_device  *udev;       // USB设备
    struct usb_interface *intf;     // USB接口
    __u8               bulk_in_addr;  // IN端点地址
    __u8               bulk_out_addr; // OUT端点地址
    size_t             bulk_in_size;  // IN端点最大包。    struct urb         *bulk_in_urb;  // IN端点urb
    void               *bulk_in_buf;  // IN端点缓冲。    struct mutex        io_mutex;     // IO互斥。};

// 匹配置static struct usb_device_id my_usb_table[] = {
    { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, my_usb_table);

// IN端点urb完成回调
static void my_bulk_in_callback(struct urb *urb)
{
    struct my_usb_dev *dev = urb->context;

    switch (urb->status) {
    case 0:
        // 传输成功,处理数。        dev_info(&dev->intf->dev, "收到 %d 字节\n",
                 urb->actual_length);
        break;
    case -ECONNRESET:   // urb被kill
    case -ENOENT:       // urb被unlink
    case -ESHUTDOWN:    // 设备已断开
        // 这些是正常断开,不报错
        return;
    default:
        dev_err(&dev->intf->dev, "urb错误: %d\n", urb->status);
        break;
    }

    // 重新提交urb,持续接。    usb_submit_urb(dev->bulk_in_urb, GFP_ATOMIC);
}

// 查找批量端点
static int find_bulk_endpoints(struct my_usb_dev *dev,
                                struct usb_interface *intf)
{
    struct usb_host_interface *alts;
    struct usb_endpoint_descriptor *epd;
    int i;

    // 使用当前altsetting
    alts = intf->cur_altsetting;

    for (i = 0; i < alts->desc.bNumEndpoints; i++) {
        epd = &alts->endpoint[i].desc;

        if (!usb_endpoint_is_bulk_in(epd) &&
            !usb_endpoint_is_bulk_out(epd))
            continue;

        if (usb_endpoint_is_bulk_in(epd)) {
            dev->bulk_in_addr = epd->bEndpointAddress;
            dev->bulk_in_size = usb_endpoint_maxp(epd);
        } else {
            dev->bulk_out_addr = epd->bEndpointAddress;
        }
    }

    if (!dev->bulk_in_addr || !dev->bulk_out_addr) {
        dev_err(&intf->dev, "找不到批量端点\n");
        return -ENODEV;
    }

    return 0;
}

// probe回调
static int my_usb_probe(struct usb_interface *intf,
                         const struct usb_device_id *id)
{
    struct my_usb_dev *dev;
    struct usb_device *udev = interface_to_usbdev(intf);
    int ret;

    dev_info(&intf->dev, "probe: VID=%04x PID=%04x\n",
             id->idVendor, id->idProduct);

    // 分配私有数据
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    dev->udev = udev;
    dev->intf = intf;
    mutex_init(&dev->io_mutex);

    // 查找端点
    ret = find_bulk_endpoints(dev, intf);
    if (ret)
        goto fail_find_ep;

    // 分配IN端点urb
    dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->bulk_in_urb) {
        ret = -ENOMEM;
        goto fail_alloc_urb;
    }

    // 分配IN端点缓冲。    dev->bulk_in_buf = kzalloc(dev->bulk_in_size, GFP_KERNEL);
    if (!dev->bulk_in_buf) {
        ret = -ENOMEM;
        goto fail_alloc_buf;
    }

    // 填充IN端点urb
    usb_fill_bulk_urb(dev->bulk_in_urb,
                      udev,
                      usb_rcvbulkpipe(udev, dev->bulk_in_addr),
                      dev->bulk_in_buf,
                      dev->bulk_in_size,
                      my_bulk_in_callback,
                      dev);

    // 提交IN端点urb,开始接。    ret = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
    if (ret) {
        dev_err(&intf->dev, "提交urb失败: %d\n", ret);
        goto fail_submit;
    }

    // 保存私有数据到interface
    usb_set_intfdata(intf, dev);

    return 0;

fail_submit:
    kfree(dev->bulk_in_buf);
fail_alloc_buf:
    usb_free_urb(dev->bulk_in_urb);
fail_alloc_urb:
fail_find_ep:
    kfree(dev);
    return ret;
}

// disconnect回调
static void my_usb_disconnect(struct usb_interface *intf)
{
    struct my_usb_dev *dev;

    dev = usb_get_intfdata(intf);
    if (!dev)
        return;

    // 先清空interface私有数据,防止其他地方还在用
    usb_set_intfdata(intf, NULL);

    // 杀掉urb,等回调执行。    usb_kill_urb(dev->bulk_in_urb);

    // 释放资源
    usb_free_urb(dev->bulk_in_urb);
    kfree(dev->bulk_in_buf);
    mutex_destroy(&dev->io_mutex);
    kfree(dev);

    dev_info(&intf->dev, "设备已断开\n");
}

// usb_driver定义
static struct usb_driver my_usb_driver = {
    .name       = "my_usb_drv",
    .id_table   = my_usb_table,
    .probe      = my_usb_probe,
    .disconnect = my_usb_disconnect,
};

module_usb_driver(my_usb_driver);

MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("USB批量传输驱动示例");
MODULE_LICENSE("GPL");

关键点说明

probe里做的事。
1. interface_to_usbdev(intf)拿到usb_device
2. 遍历intf->cur_altsetting->endpoint[]找批量端。3. usb_alloc_urb(0, GFP_KERNEL)分配urb。表示不创建iso帧描述符号4. usb_fill_bulk_urb()填充urb的管道、缓冲区、回调5. usb_submit_urb()提交urb
6. usb_set_intfdata()把私有数据挂到interface。
disconnect里做的事。
1. usb_get_intfdata()取出私有数据
2. usb_set_intfdata(intf, NULL)先清空,防止竞。3. usb_kill_urb()杀掉urb并等回调执行完(不能用usb_unlink_urb,它不等。4. 释放urb、缓冲区、私有数。
*管道编码。
| | 用途 |
|:---|:-----|
| usb_rcvbulkpipe(dev, ep) | 构造批量IN管道 |
| usb_sndbulkpipe(dev, ep) | 构造批量OUT管道 |
| usb_rcvintpipe(dev, ep) | 构造中断IN管道 |
| usb_sndintpipe(dev, ep) | 构造中断OUT管道 |
| usb_rcvisopipe(dev, ep) | 构造等时IN管道 |
| usb_sndisopipe(dev, ep) | 构造等时OUT管道 |
| usb_rcvctrlpipe(dev, ep) | 构造控制IN管道 |
| usb_sndctrlpipe(dev, ep) | 构造控制OUT管道 |

管道把设备地址、端点号、方向、传输类型编码成一个unsigned int,urb提交时内核用它找到目标端点。

五、URB操作详解

urb生命周期

flowchart TB A["usb_alloc_urb()"] --> B["填充urb<br/>usb_fill_bulk_urb"] B --> C["usb_submit_urb()"] C --> D["USB核心调度"] D --> E["主机控制器执行传递"] E --> F["完成回调 complete()"] F --> G{"继续使用?"} G -->|"| B G -->|"| H["usb_free_urb()"] style A fill:#E8F5E9 style C fill:#E3F2FD style F fill:#FFF8E1 style H fill:#FFEBEE

urb的生命周期是:分配→填充→提交→完成回调→回收或重新提交。urb可以复用——回调里重新填充并提交就行,不用每次都alloc/free。

来自 linuxros.cn · linuxROS

三种填充函数

// 批量传输
void usb_fill_bulk_urb(struct urb *urb,
                       struct usb_device *dev,
                       unsigned int pipe,
                       void *transfer_buffer,
                       int buffer_length,
                       usb_complete_t complete_fn,
                       void *context);

// 中断传输
void usb_fill_int_urb(struct urb *urb,
                      struct usb_device *dev,
                      unsigned int pipe,
                      void *transfer_buffer,
                      int buffer_length,
                      usb_complete_t complete_fn,
                      void *context,
                      int interval);

// 控制传输(需要手动设置setup_packet。// 没有usb_fill_control_urb,手动填充:
urb->setup_packet = (u8 *)dr;  // struct usb_ctrlrequest
urb->transfer_buffer = buf;
urb->transfer_buffer_length = len;

中断传输的interval是轮询间隔,单位取决于设备速度:低速/全速是帧(1ms为单位),高速是微帧(125μs为单位)。

URB状态码

status 含义 处理方式
-EINPROGRESS urb正在传输 正常,回调还没被调用
0 传输成功 读取actual_length获取数据
-ECONNRESET urb被usb_unlink_urb取消 回调中直接return
-ENOENT urb被usb_kill_urb取消 回调中直接return
-ESHUTDOWN 设备已断开或控制器已停止 回调中直接return,不要再提交urb
-EPIPE 端点stall 需要发送USB_REQ_CLEAR_FEATURE清除
-EOVERFLOW 数据溢出 检查缓冲区大小和wMaxPacketSize
-ETIME 超时 等时传输常见,检查interval设置

回调里判断status时,-ECONNRESET、-ENOENT、-ESHUTDOWN这三种是正常的断开/取消场景,不应该当错误处理,也不应该重新提交urb。

同步传输API

不想搞异步urb回调?内核提供了同步版本的传输函数:

// 同步批量传输
int usb_bulk_msg(struct usb_device *dev,
                 unsigned int pipe,
                 void *data,
                 int len,
                 int *actual_length,
                 int timeout);  // 超时ms0=无限等待

// 同步控制传输
int usb_control_msg(struct usb_device *dev,
                    unsigned int pipe,
                    __u8 request,
                    __u8 requesttype,
                    __u16 value,
                    __u16 index,
                    void *data,
                    __u16 size,
                    int timeout);

同步API简单直接,但有严格限制。
| 限制 | 原因 |
|:-----|:-----|
| 不能在中断上下文调用 | 内部会睡眠等待 |
| 不能在持有自旋锁时调用 | 同上 |
| timeout=0可能永远阻塞 | 设备不响应就卡死 |
| 不支持等时传输 | 等时必须用urb |

实际项目中,probe里初始化设备用usb_control_msg很方便,运行时持续收数据用urb异步更合适配

六、USB设备信息获取

lsusb命令

# 列出所有USB设备
lsusb

# 查看设备详细描述符
lsusb -v -d 1234:5678

# 只看设备树拓扑
lsusb -t

lsusb -v输出量大,重点关注:

  • idVendor / idProduct:写id_table要用
  • bInterfaceClass:接口类,决定用哪个驱动
  • bEndpointAddress:端点地址,写驱动时要。- wMaxPacketSize:最大包长,分配缓冲区要。

/sys/bus/usb/devices/

内核通过sysfs暴露USB设备信息,比lsusb更底层:

# 查看USB设备
ls /sys/bus/usb/devices/

# 查看特定设备的VID/PID
cat /sys/bus/usb/devices/1-1/idVendor
cat /sys/bus/usb/devices/1-1/idProduct

# 查看设备配置
cat /sys/bus/usb/devices/1-1/bConfigurationValue

# 查看接口驱动绑定
cat /sys/bus/usb/devices/1-1:1.0/driver_override
ls -l /sys/bus/usb/devices/1-1:1.0/driver

sysfs路径格式:1-1:1.0表示总线1-端口1:配置1-接口0。冒号前面是设备拓扑路径,冒号后面是接口编号。

usbfs:用户空间USB驱动

不想写内核驱动?libusb提供了用户空间USB操作接口。

// 用户空间USB操作示例(libusb。#include <libusb.h>

libusb_device_handle *handle;
libusb_init(NULL);
handle = libusb_open_device_with_vid_pid(NULL, 0x1234, 0x5678);
libusb_claim_interface(handle, 0);

unsigned char buf[64];
int actual;
libusb_bulk_transfer(handle, 0x81, buf, sizeof(buf), &actual, 1000);

libusb_release_interface(handle, 0);
libusb_close(handle);
libusb_exit(NULL);

用户空间驱动的适用场景:设备原型验证、临时测试工具、不需要高性能的简单设备。生产环境还是建议写内核驱动,中断延迟和吞吐量都更好。

七、调试技。

usbmon:内核USB抓包

usbmon是内核自带的USB抓包工具,相当于USB版的tcpdump。

# 1. 挂载debugfs
mount -t debugfs none /sys/kernel/debug

# 2. 查看可用总线
cat /sys/kernel/debug/usb/usbmon/0t

# 3. 抓取总线1的数据
cat /sys/kernel/debug/usb/usbmon/1t > usb_trace.log

# 4. 也可以用二进制格式(信息更全)
cat /sys/kernel/debug/usb/usbmon/1u > usb_trace.bin

输出格式(文本模式)。

ee998280 2765.123456 S Co:001:00 s 80 06 0100 0000 0012 18
ee998280 2765.123789 C Co:001:00 0 18
  • S = Submit(提交urb),C = Complete(urb完成。- Co = 控制传输,Bo = 批量OUT,Bi = 批量IN
  • s 80 06 0100 0000 0012 = 控制传输的setup包内存- 末尾数字 = 数据长度

Wireshark + usbmon

Wireshark可以直接解析usbmon的二进制输出,比看文本省事:

# 1. 生成二进制抓包
cat /sys/kernel/debug/usb/usbmon/1u | socat - UDP4-DATAGRAM:127.0.0.1:5555 &

# 或者直接用Wireshark。# Wireshark 4.x原生支持usbmon接口

更简单的方式——Wireshark 4.x直接选择USB捕获接口,不用手动cat。

动态调试

# 开启usbcore的所有调试输出
echo 'module usbcore +p' > /sys/kernel/debug/dynamic_debug/control

# 开启特定驱动的调试
echo 'file my_usb_drv.c +p' > /sys/kernel/debug/dynamic_debug/control

# 开启USB核心的枚举调试echo 'file hub.c +p' > /sys/kernel/debug/dynamic_debug/control

# 关闭调试
echo 'module usbcore -p' > /sys/kernel/debug/dynamic_debug/control

动态调试不需要重新编译内核,运行时开关,生产环境也能用。+p表示开启printk,-p表示关闭。

八、常见问题

Q1:probe不被调用。

最常见的原因是id_table匹配规则不对。排查步骤:

  1. lsusb -v -d VID:PID确认设备的VID/PID
  2. 检查MODULE_DEVICE_TABLE宏有没有。3. cat /sys/kernel/debug/usb/devices看内核识别到的设备信息4. ls /sys/bus/usb/drivers/确认驱动已注。5. 检查是否有其他驱动先匹配上了——USB核心按注册顺序匹配,先到先得

如果设备是HID类(键盘鼠标),内核的usbhid驱动会先抢走。需要给设备添加quirks或者写driver_override。

# 强制指定驱动
echo "my_usb_drv" > /sys/bus/usb/devices/1-1:1.0/driver_override
echo "1-1:11.0" > /sys/bus/usb/drivers_probe

Q2:urb提交返回-ENODEV。

设备已经物理断开,但驱动还没收到disconnect回调。这是竞态窗口——设备拔出和urb提交之间没有同步保护。解决方法:

  1. disconnect里先usb_set_intfdata(intf, NULL)
  2. 提交urb前检查usb_get_intfdata()是否为NULL
  3. 用io_mutex保护提交操作
// 安全的urb提交模式
mutex_lock(&dev->io_mutex);
if (dev->intf)  // disconnect还没被调试    ret = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
mutex_unlock(&dev->io_mutex);

Q3:批量传输丢数据。

几个排查方向。
1. 端点方向搞反*:usb_sndbulkpipe和usb_rcvbulkpipe别用。2. 缓冲区太:必须≥wMaxPacketSize,批量传输一次最4096字节(高速)。4字节(全速)
3.
回调里没重新提交urb:urb是一次性的,回调执行完就结束了,想持续接收必须重新submit
4.
GFP_ATOMIC vs GFP_KERNEL*:回调里必须用GFP_ATOMIC,因为回调可能在中断上下文执行

九、总结

USB驱动开发速查看

操作 API 注意事项
定义匹配规则 USB_DEVICE(vid, pid) 末尾空项{}
导出匹配置 MODULE_DEVICE_TABLE(usb, tbl) 不加modprobe找不
注册驱动 module_usb_driver(drv) 替代module_init/exit
获取usb_device interface_to_usbdev(intf) probe参数是interface
查找端点 遍历intf->cur_altsetting->endpoint[] 用usb_endpoint_is_bulk_*判断
构造管理 usb_rcvbulkpipe(dev, ep) IN用rcv,OUT用snd
分配urb usb_alloc_urb(0, GFP_KERNEL) 0=非等
填充urb usb_fill_bulk_urb(...) 设置回调函数
提交urb usb_submit_urb(urb, GFP_KERNEL) 回调里用GFP_ATOMIC
杀掉urb usb_kill_urb(urb) 等回调执行完
释放urb usb_free_urb(urb) kill之后才能free
同步控制传输 usb_control_msg(...) 不能在中断上下文
同步批量传输 usb_bulk_msg(...) timeout别设0
保存私有数据 usb_set_intfdata(intf, dev) probe里设
获取私有数据 usb_get_intfdata(intf) disconnect里取

本文首发于linuxros.cn,转载请注明出处。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页