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

一、驱动类型

        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);    // 释放输入设备
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux容器(初学了解)

目录 一、容器 1.1、容器技术 1.2、容器和虚拟机之间的差异 1.3、Rootless 和 Rootful 容器 1.4、设计基于容器的架构 1.5、容器管理工具 1.6、容器镜像和注册表 1.7、配置容器注册表 1.8、使用容器文件构建容器镜像 二、部署容器 2.1、Podman 实用程序 2.2、安装容…

Nginx location 和 proxy_pass 配置详解

概述 Nginx 配置中 location 和 proxy_pass 指令的不同组合方式及其对请求转发路径的影响。 配置效果 1. location 和 proxy_pass 都带斜杠 / location /api/ {proxy_pass http://127.0.0.1:8080/; }访问地址&#xff1a;www.hw.com/api/upload转发地址&#xff1a;http://…

Linux探秘坊-------3.开发工具详解(1)

1 初识vim编辑器 创建第一个vim编辑的代码 1.新建文件 2.使用vim打开 3.打开默认是命令模式&#xff0c;写代码需要在屏幕上输出“i”字符 1.写完代码后要按Esc键退出到指令模式2.再按shift:wq即可保存并退出vim &#xff08;因为不支持鼠标&#xff0c;通常 使用键盘上的箭…

深度学习 | 基于 LSTM 模型的多电池健康状态对比及预测

Hi&#xff0c;大家好&#xff0c;我是半亩花海。在电池管理系统&#xff08;BMS&#xff09;中&#xff0c;电池的健康状态&#xff08;State of Health, SOH&#xff09;是评估电池剩余寿命的重要指标&#xff0c;而准确预测电池的健康状态可以帮助实现电池的高效管理&#x…

人工智能-机器学习之多分类分析(项目实战二-鸢尾花的多分类分析)

Softmax回归听名字&#xff0c;依然好像是做回归任务的算法&#xff0c;但其实它是去做多分类任务的算法。 篮球比赛胜负是二分类&#xff0c;足球比赛胜平负就是多分类 识别手写数字0和1是二分类&#xff0c;识别手写数字0-9就是多分类 Softmax回归算法是一种用于多分类问题…

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

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

日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件

日历热力图&#xff0c;月度数据可视化图表&#xff0c;vue组件 先看效果&#x1f447; 在线体验https://www.guetzjb.cn/calanderViewGraph/ 日历图简单划分为近一年时间&#xff0c;开始时间是 上一年的今天&#xff0c;例如2024/01/01 —— 2025/01/01&#xff0c;跨度刚…

【Linux】常见指令(三)

Linux常见指令 01.nano02.cat03.cp04.mv 我的Linux专栏&#xff1a;【Linux】 本节Linux指令讲解的基本框架如下&#xff1a; 大家可以根据自己的需求&#xff0c;自行进行跳转和学习&#xff01; 01.nano nano Linux 系统中一款简单易用的命令行文本编辑器&#xff0c;适合…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证6)

重新创建WebApi项目&#xff0c;安装Microsoft.AspNetCore.Authentication.JwtBearer包&#xff0c;将之前JwtBearer测试项目中的初始化函数&#xff0c;jwt配置类、token生成类全部挪到项目中。   重新编写login函数&#xff0c;之前测试Cookie和Session认证时用的函数适合m…

图解Git——分布式Git《Pro Git》

分布式工作流程 Centralized Workflow&#xff08;集中式工作流&#xff09; 所有开发者都与同一个中央仓库同步代码&#xff0c;每个人通过拉取、提交来合作。如果两个开发者同时修改了相同的文件&#xff0c;后一个开发者必须在推送之前合并其他人的更改。 Integration-Mana…

小白爬虫——selenium入门超详细教程

目录 一、selenium简介 二、环境安装 2.1、安装Selenium 2.2、浏览器驱动安装 三、基本操作 3.1、对页面进行操作 3.1.1、初始化webdriver 3.1.2、打开网页 3.1.3、页面操作 3.1.4、页面数据提取 3.1.5、关闭页面 ?3.1.6、综合小案例 3.2、对页面元素进行操作 3…

Flutter鸿蒙化中的Plugin

Flutter鸿蒙化中的Plugin 前言鸿蒙项目内PluginFlutter端实现鸿蒙端实现创建Plugin的插件类注册Plugin 开发纯Dart的package为现有插件项目添加ohos平台支持创建插件配置插件编写插件内容 参考资料 前言 大家知道Flutter和鸿蒙通信方式和Flutter和其他平台通信方式都是一样的&…

【Docker】搭建一个功能强大的自托管虚拟浏览器 - n.eko

前言 本教程基于群晖的NAS设备DS423的docker功能进行搭建&#xff0c;DSM版本为 DSM 7.2.2-72806 Update 2。 n.eko 支持多种类型浏览器在其虚拟环境中运行&#xff0c;本次教程使用 Chromium​ 浏览器镜像进行演示&#xff0c;支持访问内网设备和公网地址。 简介 n.eko 是…

AIGC视频生成国产之光:ByteDance的PixelDance模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍ByteDance的视频生成模型PixelDance&#xff0c;论文于2023年11月发布&#xff0c;模型上线于2024年9月&#xff0c;同时期上线的模型还有Seaweed&…

《keras 3 内卷神经网络》

keras 3 内卷神经网络 作者&#xff1a;Aritra Roy Gosthipaty 创建日期&#xff1a;2021/07/25 最后修改时间&#xff1a;2021/07/25 描述&#xff1a;深入研究特定于位置和通道无关的“内卷”内核。 &#xff08;i&#xff09; 此示例使用 Keras 3 在 Colab 中查看 GitHub …

【json_object】mysql中json_object函数过长,显示不全

问题&#xff1a;json只显示部分 解决&#xff1a; SET GLOBAL group_concat_max_len 1000000; -- 设置为1MB&#xff0c;根据需要调整如果当前在navicat上修改&#xff0c;只有效本次连接和后续会话&#xff0c;重新连接还是会恢复默认值1024 在my.ini配置文件中新增或者修…

云消息队列 Kafka 版 V3 系列荣获信通院“云原生技术创新标杆案例”

2024 年 12 月 24 日&#xff0c;由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;主办的“2025 中国信通院深度观察报告会&#xff1a;算力互联网分论坛”&#xff0c;在北京隆重召开。本次论坛以“算力互联网 新质生产力”为主题&#xff0c;全面展示中国…

2024 年度学习总结

目录 1. 前言 2. csdn 对于我的意义 3. 写博客的初衷 3.1 现在的想法 4. 写博客的意义 5. 关于生活和博客创作 5.1 写博客较于纸质笔记的优势 6. 致 2025 1. 前言 不知不觉, 来到 csdn 已经快一年了, 在这一年中, 我通过 csdn 学习到了很多知识, 结识了很多的良师益友…

Spring Boot自动配置原理:如何实现零配置启动

引言 在现代软件开发中&#xff0c;Spring 框架已经成为 Java 开发领域不可或缺的一部分。而 Spring Boot 的出现&#xff0c;更是为 Spring 应用的开发带来了革命性的变化。Spring Boot 的核心优势之一就是它的“自动配置”能力&#xff0c;它极大地简化了 Spring 应用的配置…

1.2.神经网络基础

目录 1.2.神经网络基础 1.2.1.Logistic回归 1.2.2 梯度下降算法 1.2.3 导数 1.2.4 向量化编程 1.2.5 正向传播与反向传播 1.2.6.练习 1.2.神经网络基础 1.2.1.Logistic回归 1.2.1.1.Logistic回归 逻辑回归是一个主要用于二分分类类的算法。那么逻辑回归是给定一个x ,…