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

news2025/3/10 14:50:17

一、驱动类型

        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

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

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

相关文章

RV1126画面质量四:GOP改善画质

一&#xff0e; 什么是 GOP GOP 实际上就是两个 I 帧的间隔&#xff0c;比方说分辨率是 1920 * 1080 50 帧&#xff0c;假设 GOP 为 5&#xff0c;那就是大概 2s 插入一个 I 帧。我们再 回顾下&#xff0c;H264/H265 的帧结构。H264/H265 分别分为三种帧类型&#xff1a;I 帧、…

【2025年数学建模美赛F题】(顶刊论文绘图)模型代码+论文

全球网络犯罪与网络安全政策的多维度分析及效能评估 摘要1 Introduction1.1 Problem Background1.2Restatement of the Problem1.3 Literature Review1.4 Our Work 2 Assumptions and Justifications数据完整性与可靠性假设&#xff1a;法律政策独立性假设&#xff1a;人口统计…

Vivado生成X1或X4位宽mcs文件并固化到flash

1.生成mcs文件 01.在vivado里的菜单栏选择"tools"工具栏 02.在"tools"里选择"生成内存配置文件" 03.配置参数 按照FPGA板上的flash型号进行选型&#xff0c;相关配置步骤可参考下图。 注意&#xff1a;Flash数据传输位宽如果需要选择X4位宽&am…

idea plugin插件开发——入门级教程(IntelliJ IDEA Plugin)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;idea plugin插件开发——入门级教程&#xff08;IntelliJ IDEA Plugin&#xff09;-CSDN博客 目录 前言 官方 官方文档 代码示例 开发前必读 Intellij、Gradle、JDK 版本关系 plu…

Linux的常用指令的用法

目录 Linux下基本指令 whoami ls指令&#xff1a; 文件&#xff1a; touch clear pwd cd mkdir rmdir指令 && rm 指令 man指令 cp mv cat more less head tail 管道和重定向 1. 重定向&#xff08;Redirection&#xff09; 2. 管道&#xff08;Pipes&a…

docker 简要笔记

文章目录 一、前提内容1、docker 环境准备2、docker-compose 环境准备3、流程说明 二、打包 docker 镜像1、基础镜像2、国内镜像源3、基础的dockerfile4、打包镜像 四、构建运行1、docker 部分2、docker-compose 部分2.1、构建docker-compose.yml2.1.1、同目录构建2.1.2、利用镜…

Windows 与 Linux 文件权限的对比与转换

Windows和Linux在文件权限管理方面存在显著差异。了解它们的对比和转换方法对于跨平台操作和管理文件非常重要。以下是详细的对比和转换方法&#xff1a; 一、Windows 文件权限 1. 权限类型 Windows使用基于用户和组的权限模型&#xff0c;常见的权限类型包括&#xff1a; 读…

FireFox | Google Chrome | Microsoft Edge 禁用更新 final版

之前的方式要么失效&#xff0c;要么对设备有要求&#xff0c;这次梳理一下对设备、环境几乎没有要求的通用方式&#xff0c;universal & final 版。 1.Firefox 方式 FireFox火狐浏览器企业策略禁止更新_火狐浏览器禁止更新-CSDN博客 这应该是目前最好用的方式。火狐也…

华硕笔记本装win10哪个版本好用分析_华硕笔记本装win10专业版图文教程

华硕笔记本装win10哪个版本好用&#xff1f;华硕笔记本还是建议安装win10专业版。Win分为多个版本&#xff0c;其中家庭版&#xff08;Home&#xff09;和专业版&#xff08;Pro&#xff09;是用户选择最多的两个版本。win10专业版在功能以及安全性方面有着明显的优势&#xff…

Android多语言开发自动化生成工具

在做 Android 开发的过程中&#xff0c;经常会遇到多语言开发的场景&#xff0c;尤其在车载项目中&#xff0c;多语言开发更为常见。对应多语言开发&#xff0c;通常都是在中文版本的基础上开发其他国家语言&#xff0c;这里我们会拿到中-外语言对照表&#xff0c;这里的工作难…

Java数据结构 (链表反转(LinkedList----Leetcode206))

1. 链表的当前结构 每个方框代表一个节点&#xff0c;每个节点包含两个部分&#xff1a; 左侧的数字&#xff1a;节点存储的值&#xff0c;例如 45、34 等。右侧的地址&#xff08;如 0x90&#xff09;&#xff1a;表示该节点 next 指针指向的下一个节点的内存地址。 例子中&a…

LabVIEW 太阳能光伏发电系统智能监控

本文介绍了基于 LabVIEW 的太阳能光伏发电监控系统的设计与实现&#xff0c;着重探讨了其硬件配置、软件架构以及系统的实现方法。该系统能够有效提高太阳能光伏发电的监控效率和精确性&#xff0c;实现了远程监控和数据管理的智能化。 ​ 项目背景 在当前能源紧张与环境污染…

记录让cursor帮我给ruoyi-vue后台管理项目整合mybatis-plus

自己整合过程中会出现 work.web.exception.GlobalExceptionHandler :100 | 请求地址/admin/device/install/detail/1,发生未知异常. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.fire.mapper.DeviceInstallMapper.selectById at o…

Prometheus+grafana实践:Doris数据库的监控

文章来源&#xff1a;乐维社区 Doris数据库背景 Doris&#xff08;Apache Doris&#xff09;是一个现代化的MPP&#xff08;Massive Parallel Processing&#xff0c;大规模并行处理&#xff09;数据库&#xff0c;主要用于在线分析处理&#xff08;OLAP&#xff09;场景。 D…

CYT3BB_4BB:Clock system

CYT3BB/4BB的时钟系统包括8-MHz IMO、2个ILO、4个看门狗计时器、4个PLL、一个FLL、5个时钟监控器(CSV)、一个8-33.34MHzECO和一个32.768-kHz WCO。   该时钟系统支持三个主时钟域: CLK_HF、CLK_SLOW和CLK_LF。 - CLK_HFx: CLK_HFx是活动模式的时钟。每个人都可以使用任…

神经网络|(四)概率论基础知识-古典概型

【1】引言 前序学习了线性回归的基础知识&#xff0c;了解到最小二乘法可以做线性回归分析&#xff0c;但为何最小二乘法如此准确&#xff0c;这需要从概率论的角度给出依据。 因此从本文起&#xff0c;需要花一段时间来回顾概率论的基础知识。 【2】古典概型 古典概型是我…

OpenFGA

1.什么是OpenFGA Fine-Grained Authorization 细粒度关系型授权 2.什么是细粒度授权 细粒度授权 (FGA) 意味着能够授予特定用户在特定资源中执行特定操作的权限。 精心设计的 FGA 系统允许您管理数百万个对象和用户的权限。随着系统不断添加对象并更新用户的访问权限&#…

C语言程序设计:算法程序的灵魂

文章目录 C语言程序设计&#xff1a;算法程序的灵魂算法数据结构程序数据结构算法数值运算算法非数值运算算法 简单的算法举例【例2.1】求12345【例2.2】有50个学生&#xff0c;要求输出成绩在80分以上的学生的学号和成绩 简单的算法举例【例2.3】判定2000—2500年中的每一年是…

React和Vue有什么区别,如何选择?

React和Vue有什么区别&#xff0c;如何选择&#xff1f; React 和 Vue 是当前最受欢迎的前端框架之一&#xff0c;两者在开发者中都有极高的声誉。它们都旨在帮助开发人员构建用户界面&#xff0c;但在实现方式和适用场景上有所不同。如果你正考虑在项目中选择 React 或 Vue&a…

寒假1.23

题解 web&#xff1a;[极客大挑战 2019]Secret File&#xff08;文件包含漏洞&#xff09; 打开链接是一个普通的文字界面 查看一下源代码 发现一个链接&#xff0c;点进去看看 再点一次看看&#xff0c;没什么用 仔细看&#xff0c;有一个问题&#xff0c;当点击./action.ph…