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_oldusb_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);// 填充URBusb_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);  // 释放URBusb_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.rhkb.cn/news/5380.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

< OS 有关 > 阿里云:轻量应用服务器 的使用 安装 Tailscale 后DNS 出错, 修复并替换 apt 数据源

VPS 配置 主机&#xff1a;vCPU x2, 512MB, 20GB位置&#xff1a;阿里云&#xff0c;日本.东京OS&#xff1a; ubuntu24.20 原因&#xff1a; 这篇是操作过程的记录文章。 2 个月前&#xff0c; 在阿里云买了台 vps 。当时本想放到韩国&#xff0c;因为它离北京近。 但最便…

【STM32-学习笔记-11-】RTC实时时钟

文章目录 RTC实时时钟一、RTC简介二、RTC框图三、RTC基本结构四、RTC操作注意事项五、RTC函数六、配置RTCMyRTC.c 七、示例&#xff1a;实时时钟①、main.c②、MyRTC.c③、MyRTC.h RTC实时时钟 一、RTC简介 RTC&#xff08;Real Time Clock&#xff09;实时时钟 RTC是一个独立…

Hadoop•搭建完全分布式集群

听说这里是目录哦 一、安装Hadoop&#x1f955;二、配置Hadoop系统环境变量&#x1f96e;三、验证Hadoop系统环境变量是否配置成功&#x1f9c1;四、修改Hadoop配置文件&#x1f36d;五、分发Hadoop安装目录&#x1f9cb;六、分发系统环境变量文件&#x1f368;七、格式化HDFS文…

网络通信---MCU移植LWIP

使用的MCU型号为STM32F429IGT6&#xff0c;PHY为LAN7820A 目标是通过MCU的ETH给LWIP提供输入输出从而实现基本的Ping应答 OK废话不多说我们直接开始 下载源码 LWIP包源码&#xff1a;lwip源码 -在这里下载 ST官方支持的ETH包&#xff1a;ST-ETH支持包 这里下载 创建工程 …

将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(3.纯python的实惠版)

前情&#xff1a; 将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch&#xff08;1.标准版&#xff09;-CSDN博客 将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch&#xff08;2.换掉付费的Event Hubs&#xff09;-CSDN博客 python脚本实现 厉害的…

Kafka-常见的问题解答

讲一讲分布式消息中间件 问题 什么是分布式消息中间件&#xff1f;消息中间件的作用是什么&#xff1f;消息中间件的使用场景是什么&#xff1f;消息中间件选型&#xff1f; 分布式消息是一种通信机制&#xff0c;和 RPC、HTTP、RMI 等不一样&#xff0c;消息中间件采用分布式…

Android系统开发(六):从Linux到Android:模块化开发,GKI内核的硬核科普

引言&#xff1a; 今天我们聊聊Android生态中最“硬核”的话题&#xff1a;通用内核镜像&#xff08;GKI&#xff09;与内核模块接口&#xff08;KMI&#xff09;。这是内核碎片化终结者的秘密武器&#xff0c;解决了内核和供应商模块之间无尽的兼容性问题。为什么重要&#x…

数据结构-二叉树

树的相关概念&#xff1a; 1、节点的度&#xff1a;树中一个节点的孩子个数称为该节点的度&#xff0c; 所有节点的度的最大值是树的度 2、分支节点&#xff1a;度大于0的节点称为分支节点 3、叶子结点&#xff1a;度为0的节点称为叶子结点 4、节点的层次&#xff08;深度&…

他把智能科技引入现代农业领域

江苏田倍丰农业科技有限公司&#xff08;以下简称“田倍丰”&#xff09;是一家专注于粮油种植的农业科技公司&#xff0c;为拥有300亩以上田地的大户提供全面的解决方案。田倍丰通过与当地政府合作&#xff0c;将土地承包给大户&#xff0c;并提供农资和技术&#xff0c;实现利…

python进程池、线程池

Python广为使用的并发处理库futures使用入门与内部原理_concurrent.futures-CSDN博客 ThreadPoolExecutor(max_workers1) 池中至多创建max_workers个线程的池来同时异步执行&#xff0c;返回Executor实例、支持上下文&#xff0c;进入时返回自己&#xff0c;退出时调用 submit(…

51c~SLAM~合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/12327374 #GSLAM 自动驾驶相关~~~ 一个通用的SLAM架构和基准 GSLAM&#xff1a;A General SLAM Framework and Benchmark 开源代码&#xff1a;https://github.com/zdzhaoyong/GSLAM SLAM技术最近取得了许多成功&am…

Node.js 完全教程:从入门到精通

Node.js 完全教程&#xff1a;从入门到精通 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;允许开发者在服务器端使用 JavaScript。它的非阻塞 I/O 和事件驱动架构使得 Node.js 非常适合于构建高性能的网络应用。本文将详细介绍 Node.js 的安装、基本语…

【JVM-9】Java性能调优利器:jmap工具使用指南与应用案例

在Java应用程序的性能调优和故障排查中&#xff0c;jmap&#xff08;Java Memory Map&#xff09;是一个不可或缺的工具。它可以帮助开发者分析Java堆内存的使用情况&#xff0c;生成堆转储文件&#xff08;Heap Dump&#xff09;&#xff0c;并查看内存中的对象分布。无论是内…

(二叉树)

我们今天就开始引进一个新的数据结构了&#xff1a;我们所熟知的&#xff1a;二叉树&#xff1b; 但是我们在引进二叉树之前我们先了解一下树&#xff1b; 树 树的概念和结构&#xff1a; 树是⼀种⾮线性的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09; …

电脑如何访问手机文件?

手机和电脑已经深深融入了我们的日常生活&#xff0c;无时无刻不在为我们提供服务。除了电脑远程操控电脑外&#xff0c;我们还可以在电脑上轻松地访问Android或iPhone手机上的文件。那么&#xff0c;如何使用电脑远程访问手机上的文件呢&#xff1f; 如何使用电脑访问手机文件…

ABP - 缓存模块(1)

ABP - 缓存模块&#xff08;1&#xff09; 1. 与 .NET Core 缓存的关系和差异2. Abp 缓存的使用2.1 常规使用2.2 非字符串类型的 Key2.3 批量操作 3. 额外功能 1. 与 .NET Core 缓存的关系和差异 ABP 框架中的缓存系统核心包是 Volo.Abp.Caching &#xff0c;而对于分布式缓存…

【RAG落地利器】向量数据库Chroma入门教程

安装部署 官方有pip安装的方式&#xff0c;为了落地使用&#xff0c;我们还是采用Docker部署的方式&#xff0c;参考链接来自官方部署: https://cookbook.chromadb.dev/running/running-chroma/#docker-compose-cloned-repo 我们在命令终端运行&#xff1a; docker run -d --…

基于Python django的音乐用户偏好分析及可视化系统设计与实现

1.1 论文背景 随着信息技术的快速发展&#xff0c;在线音乐服务已成为日常生活的重要组成部分。QQ音乐&#xff0c;凭借其创新的音乐推荐算法和独特的社交特性&#xff0c;成功在竞争激烈的市场中获得一席之地。该平台的歌单文化和评论文化不仅满足了用户自尊和自我实现的需求…

以Python构建ONE FACE管理界面:从基础至进阶的实战探索

一、引言 1.1 研究背景与意义 在人工智能技术蓬勃发展的当下,面部识别技术凭借其独特优势,于安防、金融、智能终端等众多领域广泛应用。在安防领域,可助力监控系统精准识别潜在威胁人员,提升公共安全保障水平;金融行业中,实现刷脸支付、远程开户等便捷服务,优化用户体…

以单用户模式启动 Linux 的方法

注&#xff1a;本文为 “Linux 启动单用户模式” 相关文章合辑。 未整理去重。 以单用户模式启动 linux 的三种方法 作者&#xff1a; Magesh Maruthamuthu 译者&#xff1a; LCTT Xiaobin.Liu 2020-05-03 23:01 单用户模式&#xff0c;也被称为维护模式&#xff0c;超级用户…