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);

三、USB主机侧驱动开发(键盘驱动)

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;  // 数据缓冲区
unsigned char myusb_buf_old[8];  // 数据缓冲区

int myusb_size = 0;  // 缓冲区大小
dma_addr_t myusb_dma;  // DMA地址

// 键盘的键码表,包含标准键盘的按键映射
static const unsigned char usb_keyboardcode[256] = {
    0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
    3, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2,
    4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
    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, 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, 29, 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115,
    114, 113, 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140
};

//回调函数执行的条件是 URB传输完成,无论是成功还是失败。
static void myusb_func(struct urb *urb)  //填充中断URB的回调函数
{
  //上报特殊按键
    for (i = 0; i < 8; i++) {
        input_report_key(myusb_inputdev, usb_keyboardcode[i + 224], (myusb_buf[0] >> i) & 1);
    }

  //上报普通按键
    for (i = 2; i < 8; i++) {
        if (myusb_buf_old[i] > 3 && memscan(myusb_buf + 2, myusb_buf_old[i], 6) == myusb_buf + 8) {
            if (usb_keyboardcode[myusb_buf_old[i]]) {
                input_report_key(myusb_inputdev, usb_keyboardcode[myusb_buf_old[i]], 0);
            }
        }
        if (myusb_buf[i] > 3 && memscan(myusb_buf_old+ 2, myusb_buf[i], 6) == myusb_buf_old+ 8) {
            if (usb_keyboardcode[myusb_buf[i]]) {
                input_report_key(myusb_inputdev, usb_keyboardcode[myusb_buf[i]], 1);
            }
        }
    }

    input_sync(myusb_inputdev); //同步事件,即上报完毕。
    memcpy(myusb_buf_old, myusb_buf, 8); //myusb_buf复制给myusb_buf_old
    usb_submit_urb(myusb_urb , GFP_ATOMIC); //提交URB
}

// 设备连接时执行
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);    // 释放输入设备
}

 问题1:上述代码中按键数组中的数据代表什么呢?

答:如下图所示不同的键值对应不同的按键,数组中的0元素就是保留位,也就是未分配的值。

 问题2:URB回调函数中的代码是什么意思?

答:是用于上报键盘上各个按键事件。USB每次传输都是8个字节的数据!对于键盘来说,这8个字节的数据分别对应下面的按键类型。 如下面两张图所示:

这张图是USB键盘按下后读取的8个字节的值。

下面这张图是读取的8个字节的值的解析。

4.验证

(1) 取消开发板上之前默认的USB驱动(键盘)。将内核源码重新配置编译到开发板。

(2)将自己写的驱动的ko文件上传至开发板,并将USB键盘连接开发板。

(3)使用命令:exec 0</dev/ttyl,按下按键如果没有反应,则证明此时没有键盘驱动了。由于按键现在用不了,所以重新启动开发板即可。

(4)安装ko驱动模块,然后重新使用命令:exec 0</dev/ttyl,这时按下按键就会有响应,如下所示:

五、USB设备侧驱动开发

31.Gadget驱动:把开发板模拟成USB网卡_哔哩哔哩_bilibili

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

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

相关文章

1.21学习记录

misc 2023isctf 你说爱我尊嘟假嘟 这题有点脑洞&#xff0c;需要把你说爱我换成Ook.将尊嘟换为Ook&#xff01;假嘟换成Ook&#xff1f;&#xff08;根据语气进行猜测吧&#xff09;用在线工具解密最后用base64解密即可 2023isctf 杰伦可是流量明星 解压后是一个MP3文件&am…

BaseCTF_Misc_week3

目录 broken.mp4 白丝上的flag 这是一个压缩包 纯鹿人 外星信号 我要吃火腿 Base Revenge broken.mp4 附件两个MP4文件&#xff0c;第一个可以播放&#xff0c;内容是视频受损的修复啥的。第二个破损了&#xff0c;那么就根据第一个视频的网页名称搜索找到相应的网页&…

Flutter项目和鸿蒙平台的通信

Flutter项目和鸿蒙平台的通信 前言Flutter和Harmonyos通信MethodChannelBasicMessageChannelEventChannel 前言 大家在使用Flutter开发项目的时候&#xff0c; Flutter提供了Platfrom Channel API来和个个平台进行交互。 Flutter官方目前提供了一下三种方式来和个个平台交互&…

@TransactionEventListener的关键源码整理

前言&#xff1a;本篇文章不属于保姆级的&#xff0c;主要是方便自己回忆用的&#xff0c;所以需要阅读者具有一定的Spring源码基础。 总结&#xff1a; TransactionEventListener本质上还是EventListener&#xff0c;事件的发布还是Spring通用的那一套事件发布机制。EventLis…

StarRocks强大的实时数据分析

代码仓库&#xff1a;https://github.com/StarRocks/starrocks?tabreadme-ov-file StarRocks | A High-Performance Analytical Database 快速开始&#xff1a;StarRocks | StarRocks StarRocks 是一款高性能分析型数据仓库&#xff0c;使用向量化、MPP 架构、CBO、智能物化…

SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用

SpringBoot实现定时任务&#xff0c;使用自带的定时任务以及调度框架quartz的配置使用 文章目录 SpringBoot实现定时任务&#xff0c;使用自带的定时任务以及调度框架quartz的配置使用一. 使用SpringBoot自带的定时任务&#xff08;适用于小型应用&#xff09;二. 使用调度框架…

蓝桥与力扣刷题(73 矩阵置零)

题目&#xff1a;给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&…

源码分析之Openlayers中样式篇Text类

访问Openlayers网站(https://jinuss.github.io/Openlayers_map_pages/&#xff0c;网站是基于Vue3 Openlayers&#xff0c;里面有大量的实践和案例。觉得还不错&#xff0c;可以 给个小星星Star&#xff0c;鼓励一波 https://github.com/Jinuss/OpenlayersMap哦~ 概述 Text 类…

uniapp开发 swiper 上下滚动

一、效果图 二、代码: 在uni-app中使用swiper组件实现上下滚动(垂直滚动)的功能可以通过设置vertical属性来实现。swiper组件默认是水平滚动的,通过将vertical属性设置为true,可以改变滚动方向为垂直。 <template><view><swiper

OSI5GWIFI自组网协议层次对比

目录 5G网络5G与其他协议栈各层映射 5G网络 物理层 (PHY) 是 5G 基站协议架构的最底层&#xff0c;负责将数字数据转换为适合无线传输的信号&#xff0c;并将接收到的无线信号转换为数字数据。实现数据的编码、调制、多天线处理、资源映射等操作。涉及使用新的频段&#xff08…

VSCode最新离线插件拓展下载方式

之前在vscode商店有以下类似的download按钮&#xff0c;但是2025年更新之后这个按钮就不提供了&#xff0c;所以需要使用新的方式下载 ps:给自己的网站推广下~~&#xff08;国内直连GPT/Claude&#xff09; 新的下载方式1 首先打开vscode商店官网&#xff1a;vscode插件下载…

Maven多环境打包方法配置

简单记录一下SpringBoot多环境打包配置方法&#xff0c;分部署环境和是否包含lib依赖包两个维度 目录 一、需求说明二、目录结构三、配置方案四、验证示例 一、需求说明 基于Spring Boot框架的项目分开发&#xff0c;测试&#xff0c;生产等编译部署环境&#xff08;每一个环境…

异或和之和

题目&#xff1a; 0异或和之和 - 蓝桥云课 异或和之和 题目描述 给定一个数组 Ai​&#xff0c;分别求其每个子段的异或和&#xff0c;并求出它们的和。或者说&#xff0c;对于每组满足 1≤L≤R≤n 的 L,R&#xff0c;求出数组中第 L 至第 R 个元素的异或和。然后输出每组 …

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介 本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念&#xff0c;实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 &#xff08;Deferred shading&#xff09;技术。 按照本文代码实现后&#xff0c;可以实现以下…

c++ 与 Matlab 程序的数据比对

文章目录 背景环境数据保存数据加载 背景 ***避免数据精度误差&#xff0c;快速对比变量 *** 环境 c下载 https://github.com/BlueBrain/HighFive 以及hdf5库 在vs 中配置库 数据保存 #include <highfive/highfive.hpp> using namespace HighFive;std::string fil…

Java基础——概念和常识(语言特点、JVM、JDK、JRE、AOT/JIT等介绍)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

Java设计模式:创建型模式→建造者模式

Java 建造者模式详解 1. 定义 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;允许使用多个简单的对象一步步构建一个复杂的对象。该模式使用一个建造者对象来构造一个最终的对象&#xff0c;提供清晰的分步构建流程&#xff0c;从而使得…

从CRUD到高级功能:EF Core在.NET Core中全面应用(三)

目录 IQueryable使用 原生SQL使用 实体状态跟踪 全局查询筛选器 并发控制使用 IQueryable使用 在EFCore中IQueryable是一个接口用于表示可查询的集合&#xff0c;它继承自IEnumerable但具有一些关键的区别&#xff0c;使得它在处理数据库查询时非常有用&#xff0c;普通集…

C语言之小型成绩管理系统

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 C语言之小型成绩管理系统 目录 设计题目设计目的设计任务描述设计要求输入和输出要求验收要…

Linux中DataX使用第一期

简介 DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、Hologres、DRDS, databen…