• Linux驱动开发(十三)---USB驱动HID开发学习(鼠标)


    前文回顾

    《Linux驱动开发(一)—环境搭建与hello world》
    《Linux驱动开发(二)—驱动与设备的分离设计》
    《Linux驱动开发(三)—设备树》
    《Linux驱动开发(四)—树莓派内核编译》
    《Linux驱动开发(五)—树莓派设备树配合驱动开发》
    《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
    《Linux驱动开发(七)—树莓派按键驱动开发》
    《Linux驱动开发(八)—树莓派SR04驱动开发》
    《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
    《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
    《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
    《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》

    继续宣传一下韦老师的视频

    70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

    在这里插入图片描述
    韦老师的视频基本学习完毕了,后续的知识,自己开始学习补充。先从常见的USB设备驱动开始,那手里最常见的,还是USB的鼠标键盘。

    在这里插入图片描述

    基础知识

    关于USB的知识与数据相比之前的总线复杂的多了,这里推荐一篇博客,后续的代码也很大程度参考了里面的内容。
    推荐DS小龙哥的《Linux驱动开发: USB驱动开发》
    在这里插入图片描述

    设备信息

    在正常的树莓派操作系统下,我们插入两个usb设备,一个键盘一个鼠标
    通过dmesg命令可以看到设备的相关信息。

    [ 1027.622400] usb 1-1.3: new low-speed USB device number 6 using dwc_otg
    [ 1027.757393] usb 1-1.3: New USB device found, idVendor=093a, idProduct=2510, bcdDevice= 1.00
    [ 1027.757430] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 1027.757450] usb 1-1.3: Product: USB Optical Mouse
    [ 1027.757467] usb 1-1.3: Manufacturer: PixArt
    [ 1027.762689] input: PixArt USB Optical Mouse as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0/0003:093A:2510.0002/input/input5
    [ 1027.763582] hid-generic 0003:093A:2510.0002: input,hidraw0: USB HID v1.11 Mouse [PixArt USB Optical Mouse] on usb-3f980000.usb-1.3/input0
    [ 4087.784819] usb 1-1.1.2: new low-speed USB device number 7 using dwc_otg
    [ 4087.931407] usb 1-1.1.2: New USB device found, idVendor=0c45, idProduct=760b, bcdDevice= 1.05
    [ 4087.931448] usb 1-1.1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 4087.931469] usb 1-1.1.2: Product: USB Keyboard
    [ 4087.931486] usb 1-1.1.2: Manufacturer: SONiX
    [ 4087.945438] input: SONiX USB Keyboard as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.1/1-1.1.2/1-1.1.2:1.0/0003:0C45:760B.0003/input/input6
    [ 4088.016241] hid-generic 0003:0C45:760B.0003: input,hidraw1: USB HID v1.11 Keyboard [SONiX USB Keyboard] on usb-3f980000.usb-1.1.2/input0
    [ 4088.026334] input: SONiX USB Keyboard Consumer Control as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.1/1-1.1.2/1-1.1.2:1.1/0003:0C45:760B.0004/input/input7
    [ 4088.095217] input: SONiX USB Keyboard System Control as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.1/1-1.1.2/1-1.1.2:1.1/0003:0C45:760B.0004/input/input8
    [ 4088.095656] hid-generic 0003:0C45:760B.0004: input,hidraw2: USB HID v1.11 Device [SONiX USB Keyboard] on usb-3f980000.usb-1.1.2/input1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看到
    鼠标:idVendor=093a, idProduct=2510, bcdDevice= 1.00
    键盘:idVendor=0c45, idProduct=760b, bcdDevice= 1.05
    这两组ID后续测试要使用。
    在这里插入图片描述

    设备树

    这里就不需要设备树参与了,就像IIC一样,是挂载到现有的总线上,所以这里只需要注册usb设备就可以了。

    代码框架(HID)

    先来看一个通用的框架

    #include 
    #include 
    #include 
     
     
    //定义USB的IDTAB
    static const struct usb_device_id my_usb_ids[] = 
    {
        {USB_DEVICE(0x093a,0x2510)},
        {USB_DEVICE(0x0c45,0x760b)},
        {}
    };
     
    /*
    MODULE_DEVICE_TABLE 有两个功能。
    一是:将设备加入到外设队列中,
    二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。
    该宏定义在下
    这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table
    局部变量,这个变量指向第二个参数
    */
    MODULE_DEVICE_TABLE (usb,my_usb_ids);
     
    //USB设备信息与驱动端匹配成功的时候调用。
    static int myusb_probe(struct usb_interface *intf,const struct usb_device_id *id)  //资源探索函数
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        return 0;
    }
     
    //USB断开的时候调用
    static void myusb_disconnect(struct usb_interface *intf)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    }
     
    //定义USB驱动结构体 
    static struct usb_driver myusb_driver = 
    {
        .name = "myusb_drv",
        .id_table = my_usb_ids,
        .probe = myusb_probe,
        .disconnect = myusb_disconnect
    };
     
    static int __init myusb_init(void)
    {
        //注册USB设备驱动
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        usb_register(&myusb_driver);
        return 0;
    }
     
    static void __exit myusb_exit(void)
    {
         //注销USB设备驱动
    	 printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
         usb_deregister(&myusb_driver);
    }
     
    module_init(myusb_init);
    module_exit(myusb_exit);
    MODULE_AUTHOR("PGG");
    MODULE_LICENSE("GPL");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    我们来试一下这个框架能否起作用,在不卸载内核USB HID驱动下,通过ID是否能提前加载相关驱动。
    不过重新插拔之后,设备加载的还是内核原有的驱动程序。所以这里先卸载内核相关驱动。
    在这里插入图片描述

    卸载内核USB HID驱动

    通过重新编译内核
    Device Drivers —>
    HID support —>
    USB HID support —>
    原有配置
    在这里插入图片描述
    修改为
    在这里插入图片描述
    然后更新内核。

    驱动框架测试

    重新安装刚才的模块,然后插入USB鼠标,查看打印

    [   67.588846] drivers/char/myusbmouse.c myusb_init 49
    [   67.589037] usbcore: registered new interface driver myusb_drv
    [   82.983774] usb 1-1.3: new low-speed USB device number 5 using dwc_otg
    [   83.118950] usb 1-1.3: New USB device found, idVendor=093a, idProduct=2510, bcdDevice= 1.00
    [   83.118987] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [   83.119007] usb 1-1.3: Product: USB Optical Mouse
    [   83.119024] usb 1-1.3: Manufacturer: PixArt
    [   83.125584] drivers/char/myusbmouse.c myusb_probe 27
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    顺利执行到了probe函数。
    在这里插入图片描述

    注册中断

    修改probe函数

    static int size;
    static unsigned char *buf =NULL;
    static struct urb *myurb=NULL;
    static dma_addr_t buf_phy;
    
    static int myusb_probe(struct usb_interface *intf,const struct usb_device_id *id)  //资源探索函数
    {
    	struct usb_device *dev = NULL;
    	struct usb_host_interface *interface = NULL;
    	struct usb_endpoint_descriptor *endpoint = NULL;
    	int pipe;
    
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    	printk("USB驱动匹配成功! ID: 0x%X,0x%X\n",id->idVendor,id->idProduct);
    	 
    	/*通过接口获取设备信息*/
    	dev = interface_to_usbdev(intf);
    	/*获取当前接口设置*/
    	interface=intf->cur_altsetting;
    	/*获取端点描述符*/
    	endpoint = &interface->endpoint[0].desc;
    
    	
    	/*中断传输:创建输入管道*/
    	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
    	/*从端点描述符中获取传输的数据大小 */
    	size = endpoint->wMaxPacketSize;
    	printk("设备传输数据包大小:%d\n",size);
    	/*分配数据传输缓冲区*/
    	buf = usb_alloc_coherent(dev,size,GFP_ATOMIC,&buf_phy);
    
    	
    	/*分配新的urb,urb是usb设备驱动中用来描述与usb设备通信所用的基本载体和核心数据结构*/
    	myurb = usb_alloc_urb(0,GFP_KERNEL);
    	/*中断方式初始化urb*/
    	usb_fill_int_urb(myurb,dev,pipe,buf,size,usb_complete,NULL,endpoint->bInterval);
    	myurb->transfer_dma = buf_phy;
    	myurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    	
    	/*为端点提交异步传输请求*/
    	usb_submit_urb(myurb, GFP_KERNEL);	
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    大概过程就是

    Created with Raphaël 2.2.0 usb_rcvintpipe 创建输入管道 usb_alloc_urb 分配新的urb usb_fill_int_urb 中断方式初始化urb usb_submit_urb 提交异步传输请求

    当中断设备有数据发送上来的时候,会触发中断,数据就在缓冲区中。
    可以在回调中查看数据,按照缓存大小,输出一下数据看一下。

    static void usb_irq_work(struct urb *urb)
    {
    	int i;
    	for(i=0;i<size;i++)
    	{
    		printk("0x%x ",buf[i]);
    	}
    	printk("\n");
    	/* 重新提交异步请求*/
    	usb_submit_urb(myurb, GFP_KERNEL);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后断开的时候会调用disconnect函数,需要释放资源

    static void myusb_disconnect(struct usb_interface *intf)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    	
    	struct usb_device *dev = interface_to_usbdev(intf);
    	usb_kill_urb(myurb);
    	usb_free_urb(myurb);
    	usb_free_coherent(dev,size,buf, buf_phy);
    	printk("USB 设备释放成功!\n"); 
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后测试一下模块驱动。
    先装载驱动,再插上usb鼠标

    root@raspberrypi:/home/pgg/work/driver# insmod myusbmouse.ko 
    root@raspberrypi:/home/pgg/work/driver# dmesg 
    [ 5684.233197] drivers/char/myusbmouse.c myusb_init 116
    [ 5684.233391] usbcore: registered new interface driver myusb_drv
    root@raspberrypi:/home/pgg/work/driver# dmesg 
    [ 5684.233197] drivers/char/myusbmouse.c myusb_init 116
    [ 5684.233391] usbcore: registered new interface driver myusb_drv
    [ 5702.442546] usb 1-1.3: new low-speed USB device number 8 using dwc_otg
    [ 5702.577376] usb 1-1.3: New USB device found, idVendor=093a, idProduct=2510, bcdDevice= 1.00
    [ 5702.577413] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
    [ 5702.577433] usb 1-1.3: Product: USB Optical Mouse
    [ 5702.577450] usb 1-1.3: Manufacturer: PixArt
    [ 5702.578716] drivers/char/myusbmouse.c myusb_probe 56
    [ 5702.578738] USB驱动匹配成功! ID: 0x93A,0x2510
    [ 5702.578752] 设备传输数据包大小:4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看到驱动匹配成功,数据包大小为4。

    移动一下鼠标看看。打印了很多数据。

    [ 5771.634961] 0x0 
    [ 5771.635024] 0xff 
    [ 5771.635044] 0xff 
    [ 5771.635061] 0x0 
    
    [ 5771.683970] 0x0 
    [ 5771.684017] 0xff 
    [ 5771.684036] 0xff 
    [ 5771.684053] 0x0 
    
    [ 5772.644962] 0x0 
    [ 5772.645013] 0xfe 
    [ 5772.645033] 0x0 
    [ 5772.645050] 0x0 
    
    [ 5773.925960] 0x0 
    [ 5773.926013] 0xff 
    [ 5773.926033] 0x0 
    [ 5773.926050] 0x0 
    
    [ 5796.702962] 0x0 
    [ 5796.703014] 0xff 
    [ 5796.703035] 0xff 
    [ 5796.703052] 0x0 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    是不是看不懂,别急
    在这里插入图片描述

    数据解析

    那么这4个字节都是啥呢,我们在这篇文章中找到了答案
    《Linux USB驱动学习总结(三)---- USB鼠标的加载、初始化和通信过程》

    input_report_key(dev, BTN_LEFT,   data[0] & 0x01);提交按键信息,data[0] 的第 0 位为 1,表示左键按下
    input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);提交按键信息,data[0] 的第 1 位为 1,表示右键按下
    input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);提交按键信息,data[0] 的第 2 位为 1,表示中键按下
    input_report_key(dev, BTN_SIDE,   data[0] & 0x08);提交按键信息,data[0] 的第 3 位为 1,表示边键按下
    input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);提交按键信息,data[0] 的第 4 位为 1,表示额外键按下
     
    input_report_rel(dev, REL_X,     data[1]);///提交鼠标相对坐标值,data[1] 为 X 坐标
    input_report_rel(dev, REL_Y,     data[2]);///提交鼠标相对坐标值,data[2] 为 Y 坐标
    input_report_rel(dev, REL_WHEEL, data[3]);///提交鼠标滚轮相对值,data[3] 为 滚轮相对值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • data[0]:按键信息,
      第 0 位为 1,表示左键按下
      第 1 位为 1,表示右键按下
      第 2 位为 1,表示中键按下
      第 3 位为 1,表示边键按下
      第 4 位为 1,表示外键按下
    • data[1~3]:鼠标相对坐标值,
      data[1] 为 X 坐标
      data[2] 为 Y 坐标
      data[3] 为 滚轮相对值

    在这里插入图片描述

    所以我们修改一下

    static void usb_irq_work(struct urb *urb)
    {
    	/*int i;
    	for(i=0;i
    
    	if(data[0]&0x01)
    		printk("左键按下");
    	if(data[0] & 0x02)
    		printk("右键按下");
    	if(data[0] & 0x04)
    		printk("中键按下");
    	if(data[0] & 0x08)
    		printk("边键按下");
    	if(data[0] & 0x10)
    		printk("额外键按下");
     
    	printk("鼠标相对坐标值x:[%x],y:[%x]",data[1],data[2]);
    	printk("滚轮相对值:[%x]", data[3]);
    	
    	/* 重新提交异步请求*/
    	usb_submit_urb(myurb, GFP_KERNEL);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    再次测试。

    测试结果

    横移
    在这里插入图片描述
    竖移,有点歪
    在这里插入图片描述
    左右中键
    在这里插入图片描述
    没毛病。
    在这里插入图片描述

    结合输入子系统

    我们在前面的《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》中已经讲过了输入子系统。
    我们就可以将鼠标的消息,通过子系统,注册到系统当中去,然后再中断的时候,将消息发送给系统。这样就能通过event来实现输入了。
    这块后续再实践一下。
    源码中的usbmouse,已经完整的实现了这个,可以参考一下源码。
    源码是我们学习linux最好的老师。
    在这里插入图片描述

    知识点

    通配ID

    通用的usb设备id可以写成如下写法。就可以识别所有的USB鼠标

    //通用ID
    static const 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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    查看usb设备

    通过命令lsusb

    root@raspberrypi:/home/pgg/work/driver# lsusb 
    Bus 001 Device 010: ID 093a:2510 Pixart Imaging, Inc. Optical Mouse
    Bus 001 Device 004: ID 0424:7800 Microchip Technology, Inc. (formerly SMSC) 
    Bus 001 Device 003: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
    Bus 001 Device 002: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    加上-v参数可以获得更多细节
    在这里插入图片描述

    结束语

    夫人和孩子今天回来了,还是家人都在一起的感觉好,给孩子买的书,他很喜欢,在屋里自己看还不停的笑。
    在这里插入图片描述
    昨天看了《汉江怪物》,感觉不太值那么高的分,强行和ZZ挂钩的电影,有点不太令人理解。
    今天就不看电影了,晚上打算学一下《瘟疫危机》,学习一下当今小朋友玩的新桌游,尽量跟一下年轻人的步伐。
    在这里插入图片描述

  • 相关阅读:
    滚动菜单 flutter
    CY3/CY5/CY7标记牛血清白蛋白/人血清白蛋白,CY3/CY5/CY7-BSA/HSA
    There is no getter for property
    趋势的转换,数字化转型的基本逻辑经历了怎样的变化?
    DOPE修饰岩藻多糖 Fucoidan-DOPE 岩藻多糖-二油酰基磷脂酰乙醇胺
    色氨酸乙酯双三氟甲基磺酰亚胺[TrpC2][Tf2N]离子液体
    淘宝npm镜像源换新地址
    1.4 计算机网络在我国的发展
    行为型设计模式之模板方法模式
    java计算机毕业设计基于springboo+vue的电脑城销售系统
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/126096687