Linux内核编程(二十一)USB驱动开发

news2025/2/23 4:54:28

一、驱动类型

        USB 驱动开发主要分为两种:主机侧的驱动程序和设备侧的驱动程序。一般我们编写的都是主机侧的USB驱动程序。

        主机侧驱动程序用于控制插入到主机中的 USB 设备,而设备侧驱动程序则负责控制 USB 设备如何与主机通信。由于设备侧驱动程序通常与设备功能紧密相关,因此常常被称为 USB gadget 驱动程序。USB gadget 驱动程序的作用是定义如何通过 USB 协议栈与主机端进行通信,确保设备能够正确响应主机的请求。

        在 USB 系统中,主机侧和设备侧有各自不同的控制器。主机侧使用的是主机控制器(Host Controller),它负责在主机和设备之间建立数据传输连接,管理设备的连接和断开。设备侧则使用 USB 设备控制器(UDC),它用于控制设备如何响应主机的请求和进行数据传输。主机控制器和设备控制器分别在各自的系统中充当着至关重要的角色,确保 USB 设备和主机之间的有效通信。

        这两种驱动程序通过操作系统中的 USB 子系统进行协同工作,提供了广泛的设备支持,从键盘、鼠标等简单外设到复杂的存储设备、音频设备等多种类型的 USB 设备。

二、USB传输介质-URB

        USB通信和IIC类似,都要先构建数据包,然后使用对应的API函数进行传输。URB就是USB传输的介质。URB(USB请求块)是Linux内核中用于管理USB数据传输的结构体。在Linux中,USB数据传输的核心就是通过URB来进行的,它相当于I2C中的数据包封装,承担着数据传输的“容器”角色。URB用于描述一次USB传输的请求,包括传输方向、数据长度、目标端点等信息。

1. 根据数据传输的方式和协议类型,URB有不同的类型。

(1)控制传输URB:用于管理设备控制请求,如设备的初始化、配置等。使用usb_fill_control_urb()来填充控制传输的URB。

(2)批量传输URB:用于较大数据量的传输,通常用于数据的读取和写入。使用usb_fill_bulk_urb()来填充批量传输URB。

(3)等时传输URB:用于对实时性要求较高的传输,如音频、视频流等。使用usb_fill_int_urb()来填充等时传输URB。

(4)中断传输URB:用于短数据的周期性传输,如键盘、鼠标等设备。使用usb_fill_int_urb()来填充中断传输URB。

2. 结构体如下所示。

struct urb {
    struct list_head urb_list;          // 用于管理URB队列的链表
    unsigned int pipe;                  // 传输通道,即端点
    void *context;                      // 用户定义的上下文,用于回调函数中传递信息
    unsigned char *transfer_buffer;     // 数据缓冲区指针,指向传输数据的内存
    dma_addr_t transfer_dma;            // 用于DMA传输的物理地址
    unsigned int transfer_flags;        // 标志,指示URB的某些属性(例如同步、异步等)
    unsigned int status;                // 传输状态,成功或失败
    unsigned int actual_length;         // 实际传输的字节数
    unsigned int number_of_packets;     // 分包传输时的数据包数
    unsigned int timeout;               // 传输超时时间(毫秒)
    unsigned int start_frame;           // 传输开始的帧号
    unsigned int interval;              // 传输间隔时间(仅对中断传输有效)
    unsigned char *setup_packet;        // 指向控制传输的请求包(如果是控制传输时)
    struct urb *next;                   // 下一个URB,供链表使用
    unsigned int transfer_buffer_length; // 缓冲区的长度(即传输数据的最大字节数)
    struct usb_device *dev;             // USB设备指针,指向传输目标设备
    void (*complete)(struct urb *urb);   // 传输完成后的回调函数
    struct mutex *lock;                 // 用于同步URB访问的互斥锁
    unsigned int pipe_flags;            // 端点标志,描述端点的类型和特性
};

3. 相关API。

(1)构建URB对象。

        返回一个指向分配的URB对象的指针。如果分配失败,返回 NULL

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
/*
iso_packets:表示如果URB是用于等时传输,这个参数指定URB包含的数据包数量。
             如果不是等时传输,此参数应为0。
mem_flags:内存分配标志,通常使用 GFP_KERNEL 来分配内存。
*/

(2)释放一个URB对象。 

void usb_free_urb(struct urb *urb);

(3)填充控制传输URB。

void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
                          unsigned int pipe, unsigned char *setup_packet,
                          void *transfer_buffer, int buffer_length,
                          usb_complete_t complete_fn, void *context);
/*
    urb:要填充的URB对象。
    dev:目标USB设备。
    pipe:USB管道(端点),可以通过usb_sndctrlpipe()和usb_rcvctrlpipe()等函数获取。
    setup_packet:指向控制请求的setup包指针,包含请求的控制信息(如请求码、值、索引等)。
    transfer_buffer:指向传输数据缓冲区的指针。如果是控制传输的输入数据,数据会存储在这            
                     里;输出数据也通过此缓冲区发送。
    buffer_length:传输缓冲区的长度。
    complete_fn:传输完成后的回调函数。
    context:用于回调函数的用户定义的上下文数据。可以在回调函数中使用。
*/

 (4)填充批量传输URB。

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);
/*
    urb:要填充的URB对象。
    dev:目标USB设备。
    pipe:USB管道(端点),可以通过usb_sndbulkpipe()和usb_rcvbulkpipe()等函数获取。
    transfer_buffer:指向传输数据缓冲区的指针。
    buffer_length:传输缓冲区的长度。
    complete_fn:传输完成后的回调函数。
    context:用于回调函数的用户定义的上下文数据。
*/

 (5)填充中断传输URB。

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, unsigned int interval);
/*
    urb:要填充的URB对象。
    dev:目标USB设备。
    pipe:USB管道(端点),可以通过usb_sndintpipe()和usb_rcvintpipe()等函数获取。
    transfer_buffer:指向传输数据缓冲区的指针。
    buffer_length:传输缓冲区的长度。
    complete_fn:传输完成后的回调函数。
    context:用于回调函数的用户定义的上下文数据。
    interval:传输间隔,单位为帧(适用于中断传输)。
*/

 (6)提交URB进行传输。

        如果提交成功,返回 0;如果失败,返回一个负数错误码。

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
/*
    urb:要提交的URB对象。
    mem_flags:内存分配标志,通常使用 GFP_KERNEL。
*/

(7)取消一个URB的传输。

void usb_kill_urb(struct urb *urb);

三、主机侧驱动开发框架

1. 操作流程。

(1)编写USB驱动框架。

(2)完善probe函数。

(3)在退出函数中释放掉内存以及相关注销。

2. 编写USB驱动框架。

①在函数入口函数中注册usb驱动,在出口函数中注销usb驱动。

②填充usb驱动信息,例如名称、probe函数等。

#include <linux/module.h>
#include <linux/usb.h>
#include <linux/init.h>
//设备连接时执行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {
    return 0;
}

//设备断开时执行
static void my_usb_disconnect(struct usb_interface *intf) {

}

static const struct usb_device_id my_usb_id_table[] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) },
    { }, // 空结构体,表示数组结束
};

static struct usb_driver my_usb_driver = {
    .name = "my_usb",
    .probe = my_usb_probe,
    .disconnect = my_usb_disconnect,
    .id_table = my_usb_id_table,
};

static int my_usb_init(void) {
    int ret = usb_register(&my_usb_driver);
    return 0;
}

static void my_usb_exit(void) {
    usb_deregister(&my_usb_driver);
}

module_init(my_usb_init);
module_exit(my_usb_exit);
MODULE_LICENSE("GPL");

3. 完善probe函数。

①由于键盘属于输入设备,则在probe函数中先为输入子系统开辟空间,并填充信息。

②设置键盘的按键事件以及键值。

③注册输入子系统到驱动。

④构建URB对象,开辟空间并填充中断传输URB函数中的参数。

⑤提交URB对象,用于数据传输。

⑥在填充URB的回调函数中,上报事件。

⑦在退出函数中释放内存。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/slab.h>

struct input_dev *myusb_inputdev = NULL;  // 输入设备
struct urb *myusb_urb = NULL;  // USB请求块(URB)
unsigned char *myusb_buf = NULL;  // 数据缓冲区
int myusb_size = 0;  // 缓冲区大小
dma_addr_t myusb_dma;  // DMA地址

// 键盘的键码表,包含标准键盘的按键映射
static const unsigned char usb_keyboardcode[256] = {
    0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 0, 50,
    49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 5,
    6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 4, 27,
    43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65,
    66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106, 105,
    108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72,
    73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190, 191,
    192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113, 115,
    114, 0, 0, 0, 121, 0, 89, 93, 124, 92, 94, 95, 0, 0, 6, 0,
    122, 123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

//回调函数执行的条件是 URB传输完成,无论是成功还是失败。
static void myusb_func(struct urb *urb)  //填充中断URB的回调函数
{
    if (urb->status) {
        pr_err("URB transfer failed with status %d\n", urb->status);
    } else {
        pr_info("URB transfer completed successfully\n");
    }
}

// 设备连接时执行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {
    int i;
    int ret;
    struct usb_device *myusb_dev = interface_to_usbdev(intf); // 获取USB设备指针
    struct usb_endpoint_descriptor *endpoint;
    
// 1. 为输入设备分配内存
    myusb_inputdev = input_allocate_device();
    if (!myusb_inputdev) {
        pr_err("Failed to allocate input device\n");
        return -ENOMEM;
    }
    myusb_inputdev->name = "myusb_input";   // 设置设备名称

// 2. 设置事件类型:按键事件和重复事件
    set_bit(EV_KEY, myusb_inputdev->evbit);
    set_bit(EV_REP, myusb_inputdev->evbit);
    for (i = 0; i < 255; i++) {  // 设置按键位图
        set_bit(usb_keyboardcode[i], myusb_inputdev->keybit);
    }
    clear_bit(0, myusb_inputdev->keybit);  // 清除无效的按键位

// 3. 注册输入设备
    ret = input_register_device(myusb_inputdev);
    if (ret) {
        input_free_device(myusb_inputdev);
        return ret;
    }
// 4. URB分配内存
    myusb_urb = usb_alloc_urb(0, GFP_KERNEL); // 分配一个URB,ISO数据包数为0,内存分配标志为GFP_KERNEL
    // 获取端点描述符并获取端点最大数据包大小
    endpoint = &intf->cur_altsetting->endpoint[0].desc;
    myusb_size = endpoint->wMaxPacketSize;
    // 分配一致性内存缓冲区用于数据传输
    myusb_buf = usb_alloc_coherent(myusb_dev, myusb_size, GFP_ATOMIC, &myusb_dma);
    // 获取接收中断管道
    unsigned int pipe = usb_rcvintpipe(myusb_dev, endpoint->bEndpointAddress);
    // 填充URB
    usb_fill_int_urb(myusb_urb, myusb_dev, pipe, myusb_buf, myusb_size, myusb_func, 0, endpoint->bInterval);

// 5. 提交URB进行数据传输
    ret = usb_submit_urb(myusb_urb, GFP_KERNEL);

    mykbd_urb->transfer_dma = mykbd_dma;  // 设置URB的DMA地址
    mykbd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; // 设置URB的标志:不使用DMA映射
    return 0;
}

// 设备断开时执行
void myusb_disconnect(struct usb_interface *intf) {
    usb_kill_urb(myusb_urb);  // 取消URB传输
    usb_free_urb(myusb_urb);  // 释放URB
    usb_free_coherent(myusb_dev, myusb_size, myusb_buf, myusb_dma);  // 释放一致性内存
    input_unregister_device(myusb_inputdev);    // 注销输入设备
    input_free_device(myusb_inputdev);    // 释放输入设备
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI Agent:深度解析与未来展望

一、AI Agent的前世&#xff1a;从概念到萌芽 &#xff08;一&#xff09;早期探索 AI Agent的概念可以追溯到20世纪50年代&#xff0c;早期的AI研究主要集中在简单的规则系统上&#xff0c;这些系统的行为是确定性的&#xff0c;输出由输入决定。随着时间的推移&#xff0c;…

SuperMap iClient3D for WebGL选中抬升特效

在大屏展示系统中&#xff0c;对行政区划数据制作了立体效果&#xff0c;如果希望选中某一行政区划进行重点介绍&#xff0c;目前常见的方式是通过修改选中对象色彩、边线等方式进行实现&#xff1b;这里提供另外一种偏移动效的思路&#xff0c;并提供下钻功能&#xff0c;让地…

领域算法 - 字符串匹配算法

字符串匹配算法 文章目录 字符串匹配算法一&#xff1a;KMP算法1&#xff1a;算法概述2&#xff1a;部分匹配表3&#xff1a;算法实现 二&#xff1a;Moore算法1&#xff1a;算法概述2&#xff1a;代码实现3&#xff1a;完整实现 三&#xff1a;马拉车算法1&#xff1a;算法概述…

小红书用户作品列表 API 系列,返回值说明

item_search_shop_video-获得某书用户作品列表 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_sea…

LeetCode hot 力扣热题100 排序链表

归并忘了 直接抄&#xff01; class Solution { // 定义一个 Solution 类&#xff0c;包含链表排序的相关方法。// 使用快慢指针找到链表的中间节点&#xff0c;并断开链表为两部分ListNode* middleNode(ListNode* head) { ListNode* slow head; // 慢指针 slow 初始化为链表…

JavaScript正则表达式解析:模式、方法与实战案例

目录 一、什么是正则表达式 1.创建正则表达式 2.标志&#xff08;Flags&#xff09; 3.基本模式 &#xff08;1&#xff09;字符匹配 &#xff08;2&#xff09;位置匹配 &#xff08;3&#xff09;数量匹配 二、常用的正则表达式方法和属性 1.test()‌ 2.match()‌ …

Nginx在Linux中的最小化安装方式

1. 安装依赖 需要安装的东西&#xff1a; wget​&#xff0c;方便我们下载Nginx的包。如果是在Windows下载&#xff0c;然后使用SFTP上传到服务器中&#xff0c;那么可以不安装这个软件包。gcc g​&#xff0c;Nginx是使用C/C开发的服务器&#xff0c;等一下安装会用到其中的…

【大模型】ChatGPT 高效处理图片技巧使用详解

目录 一、前言 二、ChatGPT 4 图片处理介绍 2.1 ChatGPT 4 图片处理概述 2.1.1 图像识别与分类 2.1.2 图像搜索 2.1.3 图像生成 2.1.4 多模态理解 2.1.5 细粒度图像识别 2.1.6 生成式图像任务处理 2.1.7 图像与文本互动 2.2 ChatGPT 4 图片处理应用场景 三、文生图操…

基于python+Django+mysql鲜花水果销售商城网站系统设计与实现

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

-bash: /java: cannot execute binary file

在linux安装jdk报错 -bash: /java: cannot execute binary file 原因是jdk安装包和linux的不一致 程序员的面试宝典&#xff0c;一个免费的刷题平台

免费为企业IT规划WSUS:Windows Server 更新服务 (WSUS) 之快速入门教程(一)

哈喽大家好&#xff0c;欢迎来到虚拟化时代君&#xff08;XNHCYL&#xff09;&#xff0c;收不到通知请将我点击星标&#xff01;“ 大家好&#xff0c;我是虚拟化时代君&#xff0c;一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利…

面试--你的数据库中密码是如何存储的?

文章目录 三种分类使用 MD5 加密存储加盐存储Base64 编码:常见的对称加密算法常见的非对称加密算法https 传输加密 在开发中需要存储用户的密码&#xff0c;这个密码一定是加密存储的&#xff0c;如果是明文存储那么如果数据库被攻击了&#xff0c;密码就泄露了。 我们要对数据…

模型部署工具01:Docker || 用Docker打包模型 Build Once Run Anywhere

Docker 是一个开源的容器化平台&#xff0c;可以让开发者和运维人员轻松构建、发布和运行应用程序。Docker 的核心概念是通过容器技术隔离应用及其依赖项&#xff0c;使得软件在不同的环境中运行时具有一致性。无论是开发环境、测试环境&#xff0c;还是生产环境&#xff0c;Do…

Restormer: Efficient Transformer for High-Resolution Image Restoration解读

论文地址&#xff1a;Restormer: Efficient Transformer for High-Resolution Image Restoration。 摘要 由于卷积神经网络&#xff08;CNN&#xff09;在从大规模数据中学习可推广的图像先验方面表现出色&#xff0c;这些模型已被广泛应用于图像复原及相关任务。近年来&…

四、CSS效果

一、box-shadow box-shadow:在元素的框架上添加阴影效果 /* x 偏移量 | y 偏移量 | 阴影颜色 */ box-shadow: 60px -16px teal; /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影颜色 */ box-shadow: 10px 5px 5px black; /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半…

火狐浏览器Firefox一些配置

没想到还会开这个…都是Ubuntu的错 一些个人习惯吧 标签页设置 常规-标签页 1.按最近使用顺序切换标签页 2.打开新标签而非新窗口&#xff08;讨厌好多窗口&#xff09; 3.打开新链接不直接切换过去&#xff08;很打断思路诶&#xff09; 4.关闭多个标签页时不向我确认 启动…

MECD+: 视频推理中事件级因果图推理--VLM长视频因果推理

论文链接&#xff1a;https://arxiv.org/pdf/2501.07227v1 1. 摘要及主要贡献点 摘要&#xff1a; 视频因果推理旨在从因果角度对视频内容进行高层次的理解。然而&#xff0c;目前的研究存在局限性&#xff0c;主要表现为以问答范式执行&#xff0c;关注包含孤立事件和基本因…

Python基于Django的社区爱心养老管理系统设计与实现【附源码】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

Docker 单机快速部署大数据各组件

文章目录 一、Spark1.1 NetWork 网络1.2 安装 Java81.3 安装 Python 环境1.4 Spark 安装部署 二、Kafka三、StarRocks四、Redis五、Rabbitmq六、Emqx6.1 前言6.2 安装部署 七、Flink八、Nacos九、Nginx 一、Spark 1.1 NetWork 网络 docker network lsdocker network create -…

【MySQL】:Linux 环境下 MySQL 使用全攻略

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;MySQL学习 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 1. 背景 &#x1f680; 世界上主…