【北京迅为】iTOP-4412全能版使用手册-第六十七章 USB鼠标驱动详解
iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、陀螺仪、CAN总线、RS485总线、500万摄像头等模块,稳定运行Android 4.0.3/Android 4.4操作,系统通用Linux-3.0.15+Qt操作系统(QT支持5.7版本),Ubuntu版本:12.04,接口智能分配 方便好用。
第六十七章 USB鼠标驱动详解
67.1 鼠标驱动注册
在 Linux 的 USB 驱动中,鼠标驱动是最简单,最适合用来学习的。USB 鼠标驱动是内核源码目录“drivers/hid/usbhid/”下的“usbmouse.c”文件,这个驱动在任意版本的内核中都有。
我们将“usbmouse.c”文件进行分解,提取其中一部分来分析,本文档主要分析 USB 鼠标驱动的注册部分。
USB 鼠标驱动的配置
我们要分析的 USB 鼠标驱动源码是“usbmouse.c”,这里要注意的是要支持 USB 鼠标驱动有多种配置方法。
这里给大家介绍一个概念“人机接口”HMI,人机接口在 linux 中包含很多设备,例如: 鼠标、键盘、显示屏...,凡是用来直接和用户直接通信的设备都可以叫做人机接口。从原始的DOS 控制台,到手机触屏,未来的人脑控制等等也都是人机接口。
在 linux 中,默认使用的是 HID,也就是人机接口设备,内核中默认配置的是 HID 驱动,并没有配置 USB 鼠标驱动。配置部分涉及到 Makefile、menuconfig 和 Kconfig,我们一起来研究一下。
在内核配置编译之后,如下图所示,使用命令“ls drivers/hid/usbhid/ ”,可以看到“usbmouse.c”并没有被编译,可以我们的内核明显是可以支持 USB 鼠标的。
接着使用命令“vim drivers/hid/usbhid/Makefile”,打开 Makefile 文件,可以看到编译条件是宏“CONFIG_USB_MOUSE”。
然后使用“vim drivers/hid/usbhid/Kconfig”命令,可以看到“config USB_MOUSE”Kconfig 的配置,如下图所示。
接着我们使用命令“make menuconfig”,然后在其中搜索“USB_MOUSE”宏,如下图所示。
如下图所示,我们进入到“HID Devices”配置界面之后,却没有发现有“USB HID Boot Protocol drivers”这个菜单。
那么到底是什么原因呢?我们回到“drivers/hid/usbhid/Kconfig”文件,如下图所示, 可以看到“USB_MOUSE”是在菜单“USB HID Boot Protocol drivers”下。
如上图所示,注意以下的提示,这部分意思是,菜单“USB HID Boot Protocol drivers”需要依赖“USB!=n && USB_HID!=y && EXPERT”,也就是必须定义“USB”, 没有定义“USB_HID”,这部分菜单才会显示出来。请注意这种配置在内核中经常会遇到。
menu "USB HID Boot Protocol drivers"
depends on USB!=n && USB_HID!=y && EXPERT
我们到 menuconfig 中将 USB_HID 取消配置,然后就可以看到 "USB HID Boot Protocol drivers"配置菜单了,如下图所示。
如上图所示,接着进入"USB HID Boot Protocol drivers",如下图所示,USB 鼠标和键盘都是没有配置的。
前面介绍这么多,主要是给大家介绍更多的缺省文件配置的知识。
为了进行后面的实验,只需要去掉“USB Human Interface Device (full HID) support ”就可以了,如下图所示,其它地方都不需要动,这样是为了我们动态加载 USB 鼠标驱动,一步一步的实现 USB 鼠标驱动。
取消配置“USB Human Interface Device (full HID) support”,然后退出保存,重新编译内核,烧写到开发板中,准备后续的实验。
USB 鼠标驱动的注册
做好准备工作之后,我们来查看一下 USB 鼠标驱动的注册部分。
USB 设备驱动的驱动注册的函数为 usb_register 和 usb_deregister,如下图所示。
这部分内容就不再赘述,它和前面介绍的字符驱动以及 I2C、SPI 驱动等等类似,由内核提供专门的注册和卸载函数。
我们需要注意的是,USB 驱动是可以热拔插的,再插入和拔掉设备的时候,肯定都需要对应的代码,如下图所示,可以看到 struct usb_driver usb_mouse_driver 中有定义usb_mouse_probe 和 usb_mouse_disconnect。
插入 USB 设备,进行初始化则进入“usb_mouse_probe”;拔掉 USB 卸载则进入“usb_mouse_disconnect”;其中的参数 name,则是驱动名称“usbmouse”,既然有驱动名称,那一定有设备名称,请注意前面介绍过的 USB 描述符,USB 描述符的具体内容是在USB 设备中的,相当于设备注册是在实体的“USB 设备”中!另外 usb_mouse_id_table, 这部分用来匹配,在初始化 probe 的代码中我们再进行打印测试,这里我们先使用默认的配置即可。
我们将代码进行简化,只进行 USB 设备的初始化和卸载部分,代码如下所示。
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
printk("usb mouse probe!\n");
return 0;
}static void usb_mouse_disconnect(struct usb_interface *intf)
{
printk("usb mouse disconnect!\n");
}static struct usb_device_id usb_mouse_id_table [] = {
{
USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID,
USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);static struct usb_driver my_usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};static int init my_usb_mouse_init(void)
{
//注册 usb 驱动
return usb_register(&my_usb_mouse_driver);
}static void exit my_usb_mouse_exit(void)
{
//卸载 USB 驱动
usb_deregister(&my_usb_mouse_driver);
}module_init(my_usb_mouse_init);
module_exit(my_usb_mouse_exit);
上面这段代码要实现的功能很简单,就是加载驱动之后,如果有 USB 鼠标插入,则会打印“usb mouse probe!”,如果有 USB 鼠标拔出,则会打印“usb mouse disconnect!”。
如下图所示,我们加载该驱动,串口控制台提示如下。
如上图所示,这部分是 usbcore 打印的,和我们的驱动中的代码没有直接的关系,在加载 usb 鼠标驱动之后,打印“注册了新的驱动,叫 usbmouse”。
接着我们接上鼠标,看到打印信息如下。
如上图所示,红色框中的就是驱动中的打印信息“usb mouse probe!”,其它部分全部是由 USB 主控制器完成。这说明匹配成功进入了 usb 鼠标的 probe。
接着我们拔掉 USB 鼠标,如下图所示,打印了“usb mouse disconnect!”这部分信息,是和我们 disconnect 函数中的打印对应。
如上图所示,其它部分打印信息也是由主控制器来完成。
主控制器会统一管理每一个 usb hub 以及每一个 usb 接口,大部分工作其实都是有主控制器完成的!
至此,本文档要介绍的内容完成,后面的文档我们继续分析进入 probe 之后,我们需要进行的配置等工作。
67.2 USB鼠标设备信息
在前面的文档中,我们知道 USB 的设备注册信息是在“真实的设备”中保存,在 USB 设备被检测到之后,主控制器将设备信息读取到驱动中。本篇的内容比较简单,主要是验证这部分内容。
在内核中没有 USB 鼠标驱动的时候插入 USB 鼠标,可以看到一些打印信息,这些信息是主控制器部分来完成的,我们在 probe 中打印 USB 设备的一些信息,来做个验证。
请注意,“USB 的设备信息”的标准术语严格来说,应该叫 USB 描述符,我这里把它称为 USB 的设备信息,主要是为了和前面驱动中的设备注册对应起来,便于大家理解。因为我们在前面所有的设备驱动中,都有设备注册这部分,在 USB 驱动中,可以将主控制器获取描述符的过程类比为“设备注册”。
在不加载 USB 鼠标驱动的情况下,插上 USB 鼠标,也是可以看到打印信息的,如下图所示。可以看到 idVendor, idProduct, bcdDevice 等信息,我们后面就在 probe 中添加这几个参数的打印信息,对比验证下。
在代码中,我们添加如下函数。
static void check_usb_device_descriptor(struct usb_device *dev)
{
printk("dev->descriptor.idVendor is %4x!\n\
dev->descriptor.idProduct is %4x!\n\
dev->descriptor.bcdDevice is %4x!\n\
dev->descriptor.iSerialNumber is %2x!\n",\
dev->descriptor.idVendor,dev->descriptor.idProduct,dev->descriptor.bcdDevice,dev->descriptor.iSerialNumber);
}
然后在 probe 中调用,如下图所示。
struct usb_device *dev = interface_to_usbdev(intf);
check_usb_device_descriptor(dev);
完成代码请参考打包的程序,加载驱动之后,插入 USB 鼠标,如下图所示。
如上图所示,可以看到在 probe 中获取的数据,和主控制器中打印的信息是一模一样的。
关于主控制器获取描述符信息,可以参考前面的“iTOP-4412-驱动-usb 文档 05-usb 枚举流程”这个文档,它经过了一个复杂的通信过程,将信息读取到内核中,然后在初始化的时候,会将其传递到 probe 函数中。
67.3 鼠标URB请求块的使用
本章主要介绍在 probe 中,urb 的初始化。USB 中所有的对底层的通信,都是围绕urb 来做的,所有的工作都是一个套路。先带大家分析下内核自带 USB 鼠标驱动部分代码,然后再改写为简单模式,对urb 进行监测。
先结合前面介绍理论部分,对内核中自带的 USB 鼠标驱动代码进行分析,后面我们的例程会进行一些简化,让大家对其更容易理解。
初始化和 usb_interface 分析
下面是进入 probe 之后的一段匹配的代码,用于验证接入的设备是否和驱动匹配。
interface = intf->cur_altsetting
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
在请求块理论部分,我们了解到鼠标驱动是“一个中断 IN 端点给带有特定端点号的特定USB 设备”。在 USB 设备描述符的文档中,我们了解到“设备描述符→配置描述符→接口描述符→端点描述符”这几个概念,用户可以直接看下“usb_interface”这个结构体,里面内嵌了接口描述符信息和端点描述符等。
在上面的代码“interface = intf->cur_altsetting;”中,获取鼠标硬件传输过来的描述符信息。
在“if (interface->desc.bNumEndpoints != 1) return -ENODEV;”中,判断端点是否只有一个,鼠标设备只有一个端点,如果不是,则报错。
关于static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)中的变量“struct usb_interface *intf”,看到这里很多人可能不太理解。前面我们介绍描述符的时候,并没有 usb_interface 这个结构体,实际上是这样的,它本质上是个“配置描述符”,它是从从机(鼠标)中传递过来的信息,配置描述符的组织方式可能有点不一样。可以这样类比,当我们要到大学报到的时候,我们要填写学籍档案,假设学籍档案中信息都在户口簿上,例如:姓名、籍贯以及身份证号等等,这些信息从户口簿传递到学籍档案中,信息其实是一样的,只是在户口簿和学籍档案中组织形式不一样,名称不一样,实际包含的信息是一样的。我们也可以看下“usb_interface”这个结构体,部分代码如下所示。
struct usb_interface {
/* array of alternate settings for this interface,
* stored in no particular order */
struct usb_host_interface *altsetting;
接着看下“usb_host_interface”结构体,如下所示,可以看到结构体中包含了接口描述符和端点描述符。
struct usb_host_interface {
struct usb_interface_descriptordesc;
/* array of desc.bNumEndpoint endpoints associated with this
* interface setting. these will be in no particular order.
*/
struct usb_host_endpoint *endpoint;
在“endpoint = &interface->endpoint[0].desc;”代码中,我们的驱动获取了端点描述符信息。
在我们的例程中,可以简化为“interface = intf->cur_altsetting;”和“endpoint = &interface->endpoint[0].desc;”,不做判断。
管道和管道数据包
代码如下所示。
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
前面介绍过,要和 USB 设备通信,唯一的方式是通过管道,每个驱动和 USB 设备通信, 都必须创建管道,使用管道进行通信,而且管道必须使用内核统一的函数来申请!
初始化USB设备结构体和输入子系统设备结构体
如下所示,代码有详细的注释。
//为 mouse 分配内存,申请输入子系统mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
input_dev = input_allocate_device();
if (!mouse || !input_dev)
goto fail1;//申请内存空间用于数据传输,data 为指向该空间的地址,data_dma 则是这块内存空间的 dma 映射,即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域,否则使用 data 指向的普通内存区域进行传输。 GFP_ATOMIC 表示不等待,GFP_KERNEL 是普通的优先级,可以睡眠等待,由于鼠标使用中断传输方式,不允许睡眠状态,data 又是周期性获取鼠标事件的存储区,因此使用 GFP_ATOMIC 优先级,如果不能 分配到内存则立即返回 0
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
if (!mouse->data)
goto fail1;//为 urb 结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为 0。申请的内存将通过下面即将见到的 usb_fill_int_urb 函数进行填充。
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;//填充 usb 设备结构体和输入设备结构体
mouse->usbdev = dev;
mouse->dev = input_dev;//填充鼠标设备的名称if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));//填充鼠标设备结构体中的节点名。usb_make_path 用来获取 USB 设备在 Sysfs 中的路径,格式为:usb-usb
总线号-路径名
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));//将鼠标设备的名称赋给鼠标设备内嵌的输入子系统结构体
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;//input_dev 中的 input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符中的编号赋给内嵌的输入子系统结构体
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;//input_dev 进行初始化。EV_KEY 是按键事件,EV_REL 是相对坐标事件;keybit 表示键值,包括左键、右键和中键;relbit 用于表示相对坐标值;有的鼠标还有其它按键;中键滚轮的滚动值。
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA);
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);//设置当前输入设备的种类
input_set_drvdata(input_dev, mouse);//为输入子系统设备添加打开,关闭等
input_dev->open = usb_mouse_open;
input_dev->close = usb_mouse_close;
设置 urb 等
如下所示,可以看注释来理解。
/*填充构建 urb,将刚才填充好的 mouse 结构体的数据填充进 urb 结构体中,在 open 中递交 urb。当 urb 包含一个即将传输的 DMA 缓冲区时应该设置 URB_NO_TRANSFER_DMA_MAP。USB 核心使用transfer_dma 变量所指向的缓冲区,而不是 transfer_buffer 变量所指向的。URB_NO_SETUP_DMA_MAP 用于 Setup 包,URB_NO_TRANSFER_DMA_MAP 用于所有 Data 包。*/
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
(maxp > 8 ? 8 : maxp),usb_mouse_irq, mouse, endpoint->bInterval);
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//注册输入子系统
error = input_register_device(mouse->dev);
if (error)
goto fail3;//一般在 probe 函数中,都需要将设备相关信息保存在一个 usb_interface 结构体中,以便以后通过usb_get_intfdata 获取使用。这里鼠标设备结构体信息将保存在 intf 接口结构体内嵌的设备结构体中
的 driver_data 数据成员中,即 intf->dev->dirver_data = mouse。
usb_set_intfdata(intf, mouse);
return 0;
usb_mouse_disconnect 操作
代码如下,拔出之后会进行以下操作。现获取鼠标设备结构体,然后清空;kill 掉 urb, 删掉输入子系统;删掉 urb;释放内存等等。
static void usb_mouse_disconnect(struct usb_interface *intf)
{
struct usb_mouse *mouse = usb_get_intfdata (intf);
usb_set_intfdata(intf, NULL);
if (mouse) {
usb_kill_urb(mouse->irq);
input_unregister_device(mouse->dev);
usb_free_urb(mouse->irq);
usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
kfree(mouse);
}
}
请求块 URB 的使用
在通信原理中,有信源、信道和信宿的概念。虽然这个概念更多的是指代硬件方面,不过我觉得放在这里来理解 USB 的几个概念,非常好。
类比生活中的例子,小 A 对小 B 说了一句“您好”,小 B 听到了。小 A 就是信源,空气就是信道,小 B 就是信宿。当然,也有数据源、传输通道和数据终端的说法,实质是差不多的。
那么我给大家说这几个概念有什么用呢?不知道大家还记不记的前面的文档中介绍过的几个概念。主机只能和 USB 设备的“端点”通信;通信是通过“管道”实现;USB 通信是通过请求块实现。最后数据终端就是我们的驱动程序了,我们要在驱动程序中获得 USB 传输来的数据,这个实验就成功了一多半。
接着,我们来看一下将要在 probe 中添加的代码,其中去掉了输入子系统的代码,简化了 urb 部分。
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;//鉴于我们在中断处理函数中,需要监听是否有数据传输,这几个变量设置为 static 变量
/*int pipe, maxp; signed char *data;
dma_addr_t data_dma;
struct urb *mouse_urb;*/printk("usb mouse probe!\n");//check_usb_device_descriptor(dev);//在 usb 的数据传输中,"数据源"是端点,“传输通道"是管道,"数据终端"是内核“USB 驱动”中的buffer//数据源,数据通道,数据终端全部是在 urb 中设置endpoint = &intf->cur_altsetting->endpoint[0].desc; //数据源pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //传输通道
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &data_dma);//数据终端//urb 必须使用内核函数申请,以便内核同一管理mouse_urb = usb_alloc_urb(0, GFP_KERNEL);//通过 urb 设置数据源,传输通道,数据终端
usb_fill_int_urb(mouse_urb, dev, pipe, data,(maxp > 8 ? 8 : maxp),check_usb_data, NULL, endpoint-
>bInterval);mouse_urb->transfer_dma = data_dma;
mouse_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;//提交 urb
usb_submit_urb(mouse_urb, GFP_KERNEL);
return 0;
}
然后看一下,中断处理函数。
//监测是否有数据传入
static void check_usb_data(struct urb *urb)
{
int i = 0;
printk("count is %d time!\n",++count);
//重新提交
urb usb_submit_urb(mouse_urb, GFP_KERNEL);
}
完整的代码,请参考代码文件。
驱动编译加载之后,插上 USB 鼠标,如下图所示。
接着,尝试按鼠标左键,右键,滚轮,滑动操作等等,如下图所示。USB 鼠标进行操作之后,驱动进入了中断。
那么传输过来的数据在哪里呢?其实就在 data 这个数据指针中,在下一篇文档中,我们增加输入子系统部分,在应用层打印数据。
67.4 鼠标驱动与Linux输入子系统的接口
本章在驱动中添加输入子系统相关的部分代码,在鼠标左键,右键等操作之后,能够打印对应的信息。
输入子系统初始化
在前面,已经学习过输入子系统,这里就不再重复,代码中有详细的注释,如下所示。
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *endpoint;
int ret;//鉴于我们在中断处理函数中,需要监听是否有数据传输,这几个变量设置为 static 变量
#if 0
int pipe, maxp;
signed char *data;
dma_addr_t data_dma;
struct urb *mouse_urb;*/
#endifprintk("usb mouse probe!\n");//check_usb_device_descriptor(dev);
//申请 input_devinput_dev = input_allocate_device();//设置 能产生哪类事件和具体的哪些时间
set_bit(EV_KEY, input_dev->evbit);
set_bit(EV_REP, input_dev->evbit);
set_bit(KEY_A, input_dev->keybit);
set_bit(KEY_B, input_dev->keybit);//注册输入子系统
ret = input_register_device(input_dev);
//在 usb 的数据传输中,"数据源"是端点,“传输通道"是管道,"数据终端"是内核“USB 驱动”中的buffer
//数据源,数据通道,数据终端全部是在 urb 中设置endpoint = &intf->cur_altsetting->endpoint[0].desc; //数据源
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //传输通道
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &data_dma);//数据终端//urb 必须使用内核函数申请,以便内核同一管理
mouse_urb = usb_alloc_urb(0, GFP_KERNEL);//通过 urb 设置数据源,传输通道,数据终端
usb_fill_int_urb(mouse_urb, dev, pipe, data,(maxp > 8 ? 8 : maxp),check_usb_data, NULL, endpoint->bInterval);
mouse_urb->transfer_dma = data_dma;
mouse_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
//提交 urb
usb_submit_urb(mouse_urb, GFP_KERNEL);
return 0;
}
在 probe 函数中,申请输入子系统,设置能产生哪类事件和具体的哪些事件,注册输入子系统。我们设置了键盘 key 类事件,能够产生 KEY_A 和 KEY_B 事件。
中断处理函数
在前一节式样中,我们已经成功实现了,鼠标操作进入中断处理函数。这里我们将采集数据和输入子系统对应,具体代码如下。
static void check_usb_data(struct urb *urb)
{
unsigned char val = 0;//printk("count is %d time!\n",++count);
printk("check_usb_data!\n");//USB 鼠标数据含义 data[0]: bit0 左键, 1 按下, 0 松开;bit1 右键,1 按下,0 松开
//按下之后,val 获取按键值
val = data[0];
if(val == 0x01){
//左键
input_report_key(input_dev, KEY_A, 1);
//通知上层,本次事件结束; KEY_A=30
input_sync(input_dev);input_report_key(input_dev, KEY_A, 0);
//通知上层,本次事件结束
input_sync(input_dev);
}if(val == 0x02){
//右键
input_report_key(input_dev, KEY_B, 1);//通知上层,本次事件结束;KEY_B=48
input_sync(input_dev);input_report_key(input_dev, KEY_B, 0);
//通知上层,本次事件结束
input_sync(input_dev);
}
val = 0;
// 重 新 提 交
urb usb_submit_urb(mouse_urb, GFP_KERNEL);
}
鼠标左键会打印 A,鼠标右键打印 B,然后其它鼠标操作会打印“check_usb_data!”。这部分要和后面的实验结果对照。
usb_mouse_disconnect
断开 USB 鼠标之后,需要添加释放输入子系统的代码,如下所示。
static void usb_mouse_disconnect(struct usb_interface *intf)
{
printk("usb mouse disconnect!\n");usb_kill_urb(mouse_urb);
usb_free_urb(mouse_urb);
input_unregister_device(input_dev);
input_free_device(input_dev);
usb_free_coherent(interface_to_usbdev(intf), 8, data,data_dma);
}
67.5 测试
驱动编译之后,加载驱动,接上鼠标,如下图所示。
操作鼠标,会打印如下图所示的信息。
接着在控制台使用命令“hexdump /dev/input/event”,鼠标左键和右键打印如下图所示信息。
如上图所示,打印了 0x1e 和 0x30,转化为十进制为 30 和 48,如下图所示,KEY_A 和KEY_B 的数值为 30 和 48,和驱动代码中的设置一一对应。
相关文章:
【北京迅为】iTOP-4412全能版使用手册-第六十七章 USB鼠标驱动详解
iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、…...
MFEM源码分析:代数库
数值计算引擎通常需要将表征物理模型的数学模型转化为线性/非线性方程组,进而求解这些线性/非线性方程组来获取数值解。因此,代数库自然成为数值计算引擎不可或取的模块。 而且,普遍认为,代数库的性能在很大程度上决定数值计算引…...
全景图相关算法学习笔记
目录 ldm3d CVPR 2024 Highlight 文本生360全景!PanFusion 全景图光流学习 一张2D全景图可合成高质量的360度3D场景 全景图生成3d ldm3d https://huggingface.co/Intel/ldm3d-pano CVPR 2024 Highlight 文本生360全景!PanFusion 文生图࿰…...
3D 目标检测:从萌芽到前沿的技术演进之路
亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、JAVA 、PYTHON与SAP 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在…...
泷羽sec:shell编程(9)不同脚本的互相调用和重定向操作
声明: 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&#…...
Java Web 3 Axios Vue组件库
一 Ajax 1 同步 异步 2 原生Ajax 比较繁琐 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Documen…...
探索未来驾驶:全面解析现代汽车高级辅助驾驶系统功能(APA 、SDA 、TBA、RPA、HPA、PEB)
随着科技的不断进步,汽车行业正在经历一场革命,自动驾驶技术逐渐成为现实。从自适应巡航控制到完全自动驾驶,各种高级驾驶辅助系统(ADAS)正在改变我们的驾驶方式。今天,我们将深入探讨几种关键的高级驾驶辅…...
MYSQL - 索引详解
一 什么是索引? 实际上在上一篇介绍MYSQL的体系结构当中我们稍微提及了一点,在引擎层,我们提到不同的引擎对应的索引的实现方式,选择是不一样的。 简单理解,索引(index)其实就是一种帮助MYSQL高…...
AI智能体Prompt预设词指令大全+GPTs应用使用
AI智能体使用指南 直接复制在AI工具助手中使用(提问前) 可前往SparkAi系统用户官网进行直接使用 SparkAI系统介绍文档:Docs 常见AI智能体GPTs应用大全在线使用 自定义添加制作AI智能体进行使用: 文章润色器 你是一位具有敏锐洞察…...
美团一面,有点难度
前几天分享过一篇训练营的朋友在阿里的一面面经,挺简单的她也是很轻松的过了,感兴趣的可以看一下我之前发的文章。 今天要分享的还是她的面经,美团的一面,感觉比阿里的难一些,各位观众老爷你怎么看? 自我介…...
axios取消请求
Axios 使用 cancel token 取消请求 1、先在axios请求中加上cancelToken import request from ../utils/request // 配置过的Axios 对象 import axios from axios export function getDetail(params, that) { return request({url: /api/indexlineage/detail, method: get,par…...
Spark简介
Spark简介 菜鸟弹性分布式数据集 (RDDs)参考 Spark通过两种方式使用Hadoop:一种是存储,另一种是处理。由于Spark具有自己的集群管理计算,因此仅将Hadoop用于存储目的。 Spark 的关键思想是- [Resilient Distributed Datasets(RDD…...
visual studio2017版本的安装下载
绝绝子,官网找了半天都不知道在哪里下载的,和当初下载旧版本的qt一样难受,还是用百度云吧!!!!!!!!!! 参考文章࿱…...
【Linux】ubuntu下一键配置vim
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹:Linux权限(超详细彻底搞懂Linux的权限) 🔖流水不争,争的是滔滔…...
[光源控制] UI调节光源亮度参数失效
📢博客主页:https://loewen.blog.csdn.net📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨文章预览: 一. 前言二. 串口调试助手辅助排查接线问题二. …...
C语言学习:速通指针(2)
这里要学习的有以下内容 1. const修饰指针 2. 野指针 3. assert断⾔ 4. 指针的使⽤和传址调⽤ 那么从这里开始 1. const 修饰指针 const修饰变量 首先我们知道变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变…...
方案拆解 | 打击矩阵新规频出!2025矩阵营销该怎么玩?
社媒平台的矩阵营销又要“变天”了?! 11月18日,小红书官方发表了被安全薯 称为“小红书史上最严打击黑灰产专项”新规,其中就包括黑灰产矩阵号的公告。 ▲ 图源:小红书 实际上,不包括这次,今年…...
Ubuntu22.04深度学习环境安装【cuda+cudnn】
为了复现一篇深度学习论文,特意安装了Linux系统。前一天已经安装Linux显卡驱动,现在需要安装cuda、cudnn等。 论文代码 论文PDF 确定包版本: 根据论文提供的代码。在requirements.txt中发现cuda版本为11.7,cudnn为8.5.0,python没…...
etcd分布式存储系统快速入门指南
在分布式系统的复杂世界中,确保有效的数据管理至关重要。分布式可靠的键值存储在维护跨分布式环境的数据一致性和可伸缩性方面起着关键作用。 在这个全面的教程中,我们将深入研究etcd,这是一个开源的分布式键值存储。我们将探索其基本概念、特…...
dataTable
在 C# 中,DataTable 是 .NET Framework 中用于处理数据表格的一个类,属于 System.Data 命名空间。它是一种内存中表示数据表的结构,通常用于临时存储和操作数据,类似于数据库中的表。DataTable 的主要特点是行列结构,其…...
Flink学习连载文章11--双流Join
双流 Join 和两个流合并是不一样的 两个流合并:两个流变为 1 个流 union connect 双流 join: 两个流 join,其实这两个流还是原来的,只是满足条件的数据会变为一个新的流。 可以结合 sql 语句中的 union 和 join 的区别。 在离线 Hive 中&…...
RayLink远程控制技术助力教育领域的创新应用研究
远程控制技术在教育领域的应用确实改变了传统教学模式。想象一下,无论身处何地,只要有网络连接,就能通过远程软件加入课堂,这种体验是不是很吸引人?虽然远程控制听起来很技术化,但别担心,小编今…...
【Qt移植LVGL】QWidget手搓LVGL软件仿真模拟器(非直接运行图形库)
【Qt移植LVGL】QWidget手搓LVGL软件仿真模拟器(非直接运行图形库) 打包开源地址: Qt函数库gitee地址 更新以gitee为准 移植后的demo工程: gitee 有些没实现的 后续我会继续优化 文章目录 别碰瓷看清楚:是移植&#…...
PostgreSQL UNION 操作符
PostgreSQL UNION 操作符 引言 PostgreSQL 是一种功能强大的开源对象关系型数据库管理系统,它以其稳定性、可靠性和先进的特性而闻名。在处理数据库查询时,我们经常需要合并来自不同表的数据,或者合并同一表的不同查询结果。这时,UNION 操作符就变得非常有用。本文将详细…...
spring boot 测试 mybatis mapper类
spring boot 测试 mybatis mapper类 针对 mybatis plus不启动 webserver指定加载 xml 【过滤 “classpath*:/mapper/**/*.xml” 下的xml】, mapper xml文件名和mapper java文件名称要一样,是根据文件名称过滤的。默认情况加载和解析所有mapper.xml 自定义 MapperT…...
Python发kafka消息
要在Python中向Kafka发送消息,你可以使用kafka-python库。首先,你需要安装这个库。如果你还没有安装它,可以通过pip来安装: bash pip install kafka-python 接下来是一个简单的例子,展示如何创建一个生产者࿰…...
zipkin 引申一:如何遍历jar目录下的exec.jar并选择对应序号的jar启动
文章目录 一、Zipin概述二、如何下载三、需求描述四、代码实现1. pom设置2. 相关工具类3. 核心代码 五、打包部署1. 打包,在项目目录执行mvn clean package2. 部署3. 运行以及停止 六、源码放送 一、Zipin概述 Zipkin是Twitter开源的分布式跟踪系统,基于…...
使用 httputils + protostuff 实现高性能 rpc
1、先讲讲 protobuf protobuf 一直是高性能序列化的代表之一(google 出品)。但是用起来,可难受了,你得先申明 “.proto” 配置文件,并且要把这个配置文件编译转成类。所以必然要学习新语法、新工具。 可能真的太难受…...
Facebook广告文案流量秘诀
Facebook 广告文案是制作有效 Facebook 广告的关键方面。它侧重于伴随广告视觉元素的文本内容。今天我们的博客将深入探讨成功的 Facebook 广告文案的秘密! 一、广告文案怎么写? 正文:这是帖子的正文,出现在您姓名的正下方。它可…...
在Vue.js中生成二维码(将指定的url+参数 生成二维码)
在Vue.js中生成二维码,你可以使用JavaScript库如qrcode或qr.js。以下是一个简单的例子,展示如何在Vue组件中使用qrcode库将指定的URL加上参数生成二维码。 首先,你需要安装qrcode库。如果你使用的是npm或yarn,可以通过命令行安装…...
Face2QR:可根据人脸图像生成二维码,还可以扫描,以后个人名片就这样用了!
今天给大家介绍的是一种专为生成个性化二维码而设计的新方法Face2QR,可以将美观、人脸识别和可扫描性完美地融合在一起。 下图展示为Face2QR 生成的面部图像(第一行)和二维码图像(第二行)。生成的二维码不仅忠实地保留…...
【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、TcpServerMain.cc 2、TcpServer.hpp 2.1、TcpServer类基本结构 2.2、构造析构函数 2.3、InitServer(…...
XML 语言随笔
XML的含义 XML(eXtensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言。XML与HTML(HyperText Markup Language,超文本标记语言)类似,但XML的设计目的是描述数据&…...
flex: 1 display:flex 导致的宽度失效问题
flex: 1 & display:flex 导致的宽度失效问题 问题复现 有这样的一个业务场景,详情项每行三项分别占33%宽度,每项有label字数不固定所以宽度不固定,还有content 占满标签剩余宽度,文字过多显示省略号, 鼠标划入展示…...
65页PDF | 企业IT信息化战略规划(限免下载)
一、前言 这份报告是企业IT信息化战略规划,报告详细阐述了企业在面对新兴技术成熟和行业竞争加剧的背景下,如何通过三个阶段的IT战略规划(IT 1.0基础建设、IT 2.0运营效率、IT 3.0持续发展),系统地构建IT管理架构、应…...
15.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 1&am…...
【Notepad++】---设置背景为护眼色(豆沙绿)最新最详细
在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。 【Notepad】---设置背景为护眼色…...
项目代码第2讲:从0实现LoginController.cs,UsersController.cs、User相关的后端接口对应的前端界面
一、User 1、使用数据注解设置主键和外键 设置主键:在User类的U_uid属性上使用[Key]注解。 设置外键:在Order类中,创建一个表示外键的属性(例如UserU_uid),并使用[ForeignKey]注解指定它引用User类的哪个…...
电子商务人工智能指南 3/6 - 聊天机器人和客户服务
介绍 81% 的零售业高管表示, AI 至少在其组织中发挥了中等至完全的作用。然而,78% 的受访零售业高管表示,很难跟上不断发展的 AI 格局。 近年来,电子商务团队加快了适应新客户偏好和创造卓越数字购物体验的需求。采用 AI 不再是一…...
vue.js组件开发的所有流程
1. 设计组件架构 首先,你需要考虑应用的结构,决定哪些部分应该成为独立的组件。这包括: 提取重复的UI元素:如按钮、输入框、卡片等。 功能模块:例如登录框、导航栏、数据表格等。 2. 设置开发环境 安装Node.js…...
从零开始学TiDB(1) 核心组件架构概述
首先TiDB深度兼容MySQL 5.7 1. TiDB Server SQL语句的解析与编译:首先一条SQL语句最先到达的地方是TiDB Server集群,TiDB Server是无状态的,不存储数据,SQL 发过来之后TiDB Server 负责 解析,优化,编译 这…...
VsCode运行Ts文件
1. 生成package.json文件 npm init 2. 生成tsconfig.json文件 tsc --init 3. Vscode运行ts文件 在ts文件点击右键执行Run Code,执行ts文件...
初始化webpack应用示例
1、初始化npm mkdir webpack_test cd webpack_test npm init 2、安装webpack依赖 npm install webpack webpack-cli -D 3、添加文件夹,入口文件 mkdir src touch index.js touch add-content.js 文件夹结构 index.js import addContent from "./add-cont…...
liunx docker 部署 nacos seata sentinel
部署nacos 1.按要求创建好数据库 2.创建docker 容器 docker run -d --name nacos-server -p 8848:8848 -e MODEstandalone -e SPRING_DATASOURCE_PLATFORMmysql -e MYSQL_SERVICE_HOST172.17.251.166 -e MYSQL_SERVICE_DB_NAMEry-config -e MYSQL_SERVICE_PORT3306 -e MYSQL…...
MySQL面试
文章目录 事务隔离级别需要解决的问题事务隔离级别 MySQL 中是如何实现事务隔离的实现可重复读 什么是存储引擎如何定位慢查询分析慢查询原因MySQL超大分页怎么处理索引失效什么时候建立唯一索引、前缀索引、联合索引?redolog与binlog是如何保证一致的redolog刷盘时…...
linux运维之shell编程
Shell 编程在系统运维中及其重要 1. Shell 编程概述 Shell 是一种命令行解释器,能够执行操作系统的命令。Shell 脚本是一个包含一系列 Shell 命令的文件,它可以被执行,以自动化和批量处理任务。常用的 Shell 类型包括 bash、sh、zsh 等。Shel…...
ssm 多数据源 注解版本
application.xml 配置如下 <!-- 使用 DruidDataSource 数据源 --><bean id"primaryDataSource" class"com.alibaba.druid.pool.DruidDataSource" init-method"init" destroy-method"close"></bean> <!-- 使用 数…...
Nginx核心配置详解
一、配置文件说明 nginx官方帮助文档:nginx documentation nginx的配置文件的组成部分: 主配置文件:nginx.conf子配置文件: include conf.d/*.conffastcgi, uwsgi,scgi 等协议相关的配置文件mime.types:…...
十六(AJAX3)、XMLHttpRequest、Promise、简易axios封装、案例天气预报、lodash-debounce防抖
1. XMLHttpRequest 1.1 XMLHttpRequest-基本使用 /* 定义:XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更…...
12.06 深度学习-预训练
# 使用更深的神经网络 经典神经网络 import torch import cv2 from torchvision.models import resnet18,ResNet18_Weights from torch import optim,nn from torch.utils.data import DataLoader from torchvision.datasets import CIFAR10 from torchvision import tr…...