20.Linux-USB鼠标驱动

在上一章分析完USB总线驱动程序后, 接下来开始写一个USB驱动:

本节目的: 将USB鼠标的左键当作L按键,将USB鼠标的右键当作S按键,中键当作回车按键

参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动)

1.本节需要用到的宏如下:

struct usb_device_id usbmouse_id_table []=USB_INTERFACE_INFO(cl,sc,pr);          

USB_INTERFACE_INFO()设置usb_driver驱动的id_table成员

cl:接口类,我们USB鼠标为HID类,所以填入0X03,也就是USB_INTERFACE_CLASS_HID

sc:接口子类为启动设备,填入USB_INTERFACE_SUBCLASS_BOOT

pr:接口协议为鼠标协议,填入USB_INTERFACE_PROTOCOL_MOUSE 

struct usb_device *dev=interface_to_usbdev(intf);  

 通过usb_ interface接口获取usb_device设备,为后面设置USB数据传输用

pipe=usb_rcvintpipe(dev,endpoint);

创建一个接收(rcv)中断(int)类型的端点管道(pipe),用来端点和数据缓冲区之间的连接,鼠标为接收中断型

dev: usb_device设备结构体

endpoint:为端点描述符的成员endpoint->bEndpointAddress   //端点地址

  • 对于控制类型的端点管道使用: usb_sndctrlpipe()/usb_rcvctrlpipe()
  • 对于实时类型的端点管道使用: usb_sndisocpipe()/usb_sndisocpipe()
  • 对于批量类型的端点管道使用: usb_sndbulkpipe()/usb_rcvbulkpipe()

2.本节需要用到的函数如下:

usb_deregister(struct usb_driver *driver);

注册一个usb_driver驱动,然后内核会通过usb_driver的成员.id_table函数匹配一次USB设备,匹配成功就会调用usb_driver的成员.probe函数

usb_deregister(struct usb_driver *driver);

注销一个usb_driver驱动,在出口函数中写

*usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma);

分配一个usb缓冲区,该缓存区的物理地址会与虚拟地址的数据一致,分配成功返回一个char型缓冲区虚拟地址

*dev: usb_device设备结构体

size:分配的缓冲区大小,这里填端点描述符的成员endpoint->wMaxPacketSize          //端点最大包长

mem_flags:分配内存的参数,这里填GFP_ATOMIC,表示从不睡眠

dma:分配成功则会返回一个DMA缓冲区物理地址

void usb_buffer_free(struct usb_device *dev,size_t size,void *addr,dma_addr_t dma);

注销分配的usb缓冲区,在usb_driver的disconnect成员函数中使用

addr:要注销的缓冲区虚拟地址

dma: 要注销的DMA缓冲区虚拟地址

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

分配一个urb数据结构体, 分配成功返回一个urb结构体

urb全称为usb request block,USB传输数据时,就是打包成urb结构体来传输

iso_packets:表示iso类型的包个数,这里我们不是iso类型包,直接填0

mem_flags:分配内存的参数,这里填入GFP_KERNEL,正常分配

其中urb结构体如下所示:

struct urb
{
 ... ...
 struct usb_device *dev;             //指向usb设备
 struct usb_host_endpoint *ep;    //指向端点的数据结构 
 unsigned int pipe;                  //指向端点管道(pipe), 本节的pipe通过usb_rcvintpipe()宏获取

 int status;                                 //状态,当status==0,表示数据被成功地收到/发送
 
 unsigned int transfer_flags;     //传输状态
 ... ...
/*以下两个缓冲区通过usb_buffer_alloc ()函数获取 */
//urb结构体默认的transfer_flags是URB_NO_SETUP_DMA_MAP ,也就是说没有提供DMA的缓冲区
//就会使用transfer_buffer虚拟地址缓冲区来当缓冲区
//当支持DMA缓冲区时,就需要手动设置transfer_flags =URB_NO_TRANSFER_DMA_MAP,并手动设置transfer_dma等于获取到的DMA物理地址

 void *transfer_buffer;                //虚拟缓冲区
 dma_addr_t transfer_dma;          //DMA物理缓冲区 
... ...
};
void usb_free_urb(struct urb *urb);

释放申请的urb,在usb_driver的disconnect成员函数中使用

static inline 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,int interval);

初始化中断型端点的urb数据结构体

针对批量型端点的urb使用usb_fill_bulk_urb() 针对控制型端点的urb使用usb_fill_control_urb() 针对等时型端点的urb  需要手动初始化。

urb:指向要初始化的urb

dev:指向要传输的usb设备

pipe:要传输的端点管道, 本节的pipe通过usb_rcvintpipe()宏获取

transfer_buffer:指向要传输数据的虚拟地址缓冲区

buffer_length:数据大小, 这里填端点描述符的成员endpoint->wMaxPacketS //端点最大包长

complete_fn:数据传输完成后产生的中断函数

context:会放在urb->context结构成员中,用来给中断函数用,本节不需要,填NULL即可

interval:间隔时间,表示间隔多少时间读一次数据,填入endpoint-> bInterval即可

int usb_submit_urb(struct urb *urb,gfp_t mem_flags);

提交urb到内核,初始化urb和中断函数退出时,都要重新提交一次,告诉内核初始化内存缓存等

void usb_kill_urb(struct urb *urb);

杀掉urb,在usb_driver的disconnect成员函数中使用

3.步骤如下:

首先先定义全局变量:usb_driver结构体,input_dev指针结构体 ,虚拟地址缓存区,DMA地址缓存区

3.1在入口函数中

1)通过usb_register()函数注册usb_driver结构体

3.2在usb_driver的probe函数中

1)分配一个input_dev结构体

2)设置input_dev支持L、S、回车、3个按键事件

3)注册input_dev结构体

4)设置USB数据传输:

 ->4.1)通过usb_rcvintpipe()创建一个接收中断类型的端点管道,用来端点和数据缓冲区之间的连接

 ->4.2)通过usb_buffer_alloc()申请USB缓冲区

 ->4.3)申请并初始化urb结构体,urb:用来传输数据

 ->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址

 ->4.5)使用usb_submit_urb()提交urb

3.3在鼠标中断函数中

1)判断缓存区数据是否改变,若改变则上传鼠标事件

2)使用usb_submit_urb()提交urb

3.4.在usb_driver的disconnect函数中

1)通过usb_kill_urb()杀掉提交到内核中的urb

2)释放urb

3)释放USB缓存区

4)注销input_device,释放input_device

3.5在出口函数中

1)通过usb_deregister ()函数注销usb_driver结构体

4.代码如下:

#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>

static struct input_dev *myusb_mouse_dev;              //input_dev 
static char *myusb_mouse_buf;                         //虚拟地址缓存区
static dma_addr_t myusb_mouse_phyc;                     //DMA缓存区;

static __le16 myusb_mouse_size;                        //数据包长度

static struct urb  *myusb_mouse_urb;                     //urb


static void myusb_mouse_irq(struct urb *urb)               //鼠标中断函数
{
   static char buf1=0;
   //for(i=0;i<myusb_mouse_size;i++)
  // printk("%02x  ",myusb_mouse_buf[i]);
 //  printk("\n");


    /*bit 1-左右中键  0X01:左键   0X02:右键     0x04:中键   */
     if((buf1&(0X01))    !=      (myusb_mouse_buf[1]&(0X01)))
        {         
                input_report_key(myusb_mouse_dev, KEY_L, buf1&(0X01)? 1:0);
                input_sync(myusb_mouse_dev);  
        }
     if((buf1&(0X02))    !=    (myusb_mouse_buf[1]&(0X02)))
        {
                input_report_key(myusb_mouse_dev, KEY_S, buf1&(0X02)? 1:0);
                input_sync(myusb_mouse_dev);  
        }
     if((buf1&(0X04))    !=    (myusb_mouse_buf[1]&(0X04))  )
          {
                input_report_key(myusb_mouse_dev, KEY_ENTER, buf1&(0X04)? 1:0);
                input_sync(myusb_mouse_dev);  
          }

     buf1=myusb_mouse_buf[1];                                   //更新数据

     usb_submit_urb(myusb_mouse_urb, GFP_KERNEL);
}


static int myusb_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;                            
       struct usb_host_interface *interface;                         //当前接口
       int pipe;                                                   //端点管道

       interface=intf->cur_altsetting;                                                                   
       endpoint = &interface->endpoint[0].desc;                                    //当前接口下的端点描述符
   

       printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); //打印VID,PID
 

 /*   1)分配一个input_dev结构体  */
       myusb_mouse_dev=input_allocate_device();

 
 /*   2)设置input_dev支持L、S,回车、3个按键事件*/
       set_bit(EV_KEY, myusb_mouse_dev->evbit);
       set_bit(EV_REP, myusb_mouse_dev->evbit);        //支持重复按功能
       set_bit(KEY_L, myusb_mouse_dev->keybit);       
       set_bit(KEY_S, myusb_mouse_dev->keybit);
       set_bit(KEY_ENTER, myusb_mouse_dev->keybit);    

 /*   3)注册input_dev结构体*/
       input_register_device(myusb_mouse_dev);

 
 /*   4)设置USB数据传输 */
 /*->4.1)通过usb_rcvintpipe()创建一个端点管道*/
       pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); 

  /*->4.2)通过usb_buffer_alloc()申请USB缓冲区*/
       myusb_mouse_size=endpoint->wMaxPacketSize;
       myusb_mouse_buf=usb_buffer_alloc(dev,myusb_mouse_size,GFP_ATOMIC,&myusb_mouse_phyc);

  /*->4.3)通过usb_alloc_urb()和usb_fill_int_urb()申请并初始化urb结构体 */
       myusb_mouse_urb=usb_alloc_urb(0,GFP_KERNEL);
       usb_fill_int_urb (myusb_mouse_urb,                       //urb结构体
                                 dev,                           //usb设备
                                 pipe,                          //端点管道
                                 myusb_mouse_buf,               //缓存区地址
                                 myusb_mouse_size,              //数据长度
                                 myusb_mouse_irq,               //中断函数
                                 0,
                                 endpoint->bInterval);          //中断间隔时间


  /*->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址*/
        myusb_mouse_urb->transfer_dma   =myusb_mouse_phyc;             //设置DMA地址
        myusb_mouse_urb->transfer_flags   =URB_NO_TRANSFER_DMA_MAP;     //设置使用DMA地址

  /*->4.5)使用usb_submit_urb()提交urb*/
        usb_submit_urb(myusb_mouse_urb, GFP_KERNEL);
       return 0;
}

static void myusb_mouse_disconnect(struct usb_interface *intf)
{
    struct usb_device *dev = interface_to_usbdev(intf);                  //设备

    usb_kill_urb(myusb_mouse_urb);
    usb_free_urb(myusb_mouse_urb);
    usb_buffer_free(dev, myusb_mouse_size, myusb_mouse_buf,myusb_mouse_phyc);

    input_unregister_device(myusb_mouse_dev);         //注销内核中的input_dev
    input_free_device(myusb_mouse_dev);             //释放input_dev
} 

static struct usb_device_id myusb_mouse_id_table [] = {
       { USB_INTERFACE_INFO(
              USB_INTERFACE_CLASS_HID,                 //接口类:hid类
              USB_INTERFACE_SUBCLASS_BOOT,             //子类:启动设备类
              USB_INTERFACE_PROTOCOL_MOUSE) },      //USB协议:鼠标协议
};


static struct usb_driver myusb_mouse_drv = {
       .name            = "myusb_mouse",
       .probe           = myusb_mouse_probe,                           
       .disconnect     = myusb_mouse_disconnect,
       .id_table  = myusb_mouse_id_table,
};
 
/*入口函数*/
static int myusb_mouse_init(void)
{
       usb_register(&myusb_mouse_drv);
       return 0;
}

/*出口函数*/
static void myusb_mouse_exit(void)
{
       usb_deregister(&myusb_mouse_drv);
}
 
module_init(myusb_mouse_init);
module_exit(myusb_mouse_exit);
MODULE_LICENSE("GPL");

5.测试运行

5.1 重新设置编译内核(去掉默认的hid_USB驱动)

make menuconfig ,进入menu菜单重新设置内核参数:

进入-> Device Drivers -> HID Devices 

<> USB Human Interface Device (full HID) support //hid:人机交互的USB驱动,比如鼠标,键盘等

然后make uImage 编译内核

将新的触摸屏驱动模块放入nfs文件系统目录中

5.2然后烧写内核,装载触摸屏驱动模块

如下图,当我们插上USB鼠标时,可以看到该VID和PID,和电脑上的鼠标的参数一样

5.3使用hexdump命令来调试

(hexdump命令调试代码详解地址:http://www.cnblogs.com/lifexy/p/7553550.html)

5.4 使用tty1进程测试

未完待续~~~~~~~~~~  下节 依葫芦画瓢 来写出 USB键盘驱动

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木宛城主

开源依旧:再次分享一个进销存系统

开篇 我之前发过一篇博文《两天完成一个小型工程报价系统(三层架构)》,不少朋友向我要源码学习,后来久而久之忘记回复了。今天我再分享一个进销存系统,只为学习,没...

38710
来自专栏数据之美

一例 jvm file.encoding 属性引起的 MapReduce/HBase 乱码问题

1、问题: 最近在往 HBase 写中文的时候,发现 hbase 查出来的数据会有部分中文乱码了,而部分中文又是正常的,按理来说,一般的乱码问题要么全乱,要么不...

2209
来自专栏Android群英传

PWA程序实践

662
来自专栏Kubernetes

containerd源码分析

本文是对containerd v0.2.4的源码分析。 ##Containerd源码流程图 ? 源码接口调用详情 从ctr调用containerd-api #...

5357
来自专栏xingoo, 一个梦想做发明家的程序员

[Logstash-input-redis] 使用详解

redis插件的完整配置 input { redis { batch_count => 1 #返回的事件数量,此属性仅在list模式下起...

28810
来自专栏Spark生态圈

[spark] Task成功执行的结果处理

在文章Task执行流程 中介绍了task是怎么被分配到executor上执行的,本文讲解task成功执行时将结果返回给driver的处理流程。

1294
来自专栏芋道源码1024

熔断器 Hystrix 源码解析 —— 命令合并执行

本文主要基于 Hystrix 1.5.X 版本 1. 概述 2. HystrixCollapser 2.1 构造方法 2.2 执行命令方式 2.3 核心方法 3...

3507
来自专栏JetpropelledSnake

Python入门之Python中的logging模块

基本用法 下面的代码展示了logging最基本的用法。 import logging import sys # 获取logger实例,如果参数为空则返回ro...

2708
来自专栏菩提树下的杨过

linq to sql中的自动缓存(对象跟踪)

这篇东西应该至少一年前就写的,不过因为个人太懒,一直没记下来,今天补上. linq to sql中,对于同一个DataContext上下文环境,根据表主键选择记...

2057
来自专栏JadePeng的技术博客

axios介绍与使用说明 axios中文文档

本周在做一个使用vuejs的前端项目,访问后端服务使用axios库,这里对照官方文档,简单记录下,也方便大家参考。 Axios 是一个基于 Promise 的 ...

1.3K9

扫码关注云+社区