Linux驱动之USB鼠标驱动编写-程序员宅基地

技术标签: 数据结构与算法  

本篇博客分以下几部分讲解

1、介绍USB四大描述

2、介绍USB鼠标驱动程序功能及框架

3、介绍程序用到的结构体

4、介绍程序用到的函数

5、编写程序

6、测试程序

 

1、介绍USB四大描述符

USB设备驱动程序里定义了许多与驱动程序密切相关的描述符。这里介绍一下四种比较关键的描述符:设备描述符、配置描述符、接口描述符、端点描述符。这几个描述符都位于include\linux\usb\ch9.h中,先看一下每个描述直接的关系,从图中可以看出每一个查到USB主机上的USB设备都有一个设备描述符,设备描述符下面可以接多个配置描述符,配置描述符下面又可以接多个


当USB设备接到USB控制器上后,USB控制器第一次读取到的数据包,总共8字节
/*当USB设备接到USB控制器上后,USB控制器第一次读取到的数据包,总共8字节*/
struct usb_ctrlrequest {
    __u8 bRequestType;
    __u8 bRequest;
    __le16 wValue;
    __le16 wIndex;
    __le16 wLength;
} __attribute__ ((packed));

设备描述符是在设备连接时,主机第一个读取的描述符,包含了主机需要从设备读取的基本信息。设备描述符有14个字段,如下所示。依照功能来分,设备描述符的字段包含了描述符本身、设备、配置以及类别4大类。

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength;          //描述符长度
    __u8  bDescriptorType;  //描述符类型

    __le16 bcdUSB;          //USB规范版本号码,BCD码表示
    __u8  bDeviceClass;     //USB设备类别
    __u8  bDeviceSubClass;  //USB设备子类别
    __u8  bDeviceProtocol;  //USB设备协议码
    __u8  bMaxPacketSize0;  //端点0的最大信息包大小(端点0用于控制传输,既能输出也能输入)
    __le16 idVendor;        //厂商ID
    __le16 idProduct;    //产品ID
    __le16 bcdDevice;       //设备版本编号,BCD码表示
    __u8  iManufacturer;    //制造者的字符串描述符的索引值
    __u8  iProduct;         //产品的字符串描述符的索引值
    __u8  iSerialNumber;    //序号的字符串描述符的索引值
    __u8  bNumConfigurations;//可能配置的数目
} __attribute__ ((packed));

在读取设备描述符后,主机可以读取该设备的配置、接口以及端点描述符。每一个设备都至少有一个配置描述符,用来描述该设备的特性与能力。通常一个设置配置就已经足够,不过多用途或模式的设备可以支持多个设置配置,在同一时间只能有一个作用。 每一个设置配置都需要一个描述符,此描述符包含设备中的电源使用以及支持的接口数目。每一个配置描述符都有附属的描述符,包含一个或多个接口描述符,以及选择性的端点描述符。

struct usb_config_descriptor {
    __u8  bLength;             //描述符长度
    __u8  bDescriptorType;     //描述符类型02

    __le16 wTotalLength;       //此配置传回的所有数据大小(字节)
    __u8  bNumInterfaces;      //此配置支持的接口数目
    __u8  bConfigurationValue; //Set_configuration与get_configuration要求的标识符
    __u8  iConfiguration;      //此配置的字符串描述符的索引值
    __u8  bmAttributes;        //自身电源/总线电源以及远程唤醒设置
    __u8  bMaxPower;           //需要总线电源,标识法为(最大mA/2)
} __attribute__ ((packed));

接口表示被设备的特性或功能所使用的端点、配置的接口描述符,包含该接口所支持的端点信息。每一个设置配置必须支持一个接口,对许多设备来说,一个接口就已经足够,不过一个设置配置,可以同时又多个作用中的接口。每一个接口有它自己的接口描述符,此接口所支持的所有端点又各有一个附属描述符。如果一个设备拥有同时多个作用中接口的设置配置,它就是一个复合设备,主机会为每一个接口,加载一个驱动程序。

/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
    __u8  bLength;                     //描述符长度
    __u8  bDescriptorType;   //描述符类型04

    __u8  bInterfaceNumber;  //识别此接口的数量
    __u8  bAlternateSetting; //用来选择一个替代设置的数值
    __u8  bNumEndpoints;     //除了端点0外,支持的端点数量
    __u8  bInterfaceClass;   //接口类别码
    __u8  bInterfaceSubClass;//接口子类别码
    __u8  bInterfaceProtocol;//接口协议码
    __u8  iInterface;                 //此接口的字符串描述符的索引值
} __attribute__ ((packed));

每一个指定在接口描述符内的端点,都有一个端点描述符。端点0没有端点描述符,因为每一个端点都必须支持断点0。设备描述符包含最大信息包大小的信息,而端点描述符则是定义端点的其他信息。

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
    __u8  bLength;                //描述符长度
    __u8  bDescriptorType;//描述符类型05

    __u8  bEndpointAddress;//端点数目与方向
    __u8  bmAttributes;         //支持的传输类型
    __le16 wMaxPacketSize; //支持的最大信息包大小
    __u8  bInterval;             //最大延迟/轮询时距/NAK速率

    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));

 

 2、介绍USB鼠标驱动程序功能及框架

USB鼠标一共有三个按键:左键、右键、中键。在这里把这三个分别作为l、s、enter键。这就是这个USB设备驱动程序的功能。

要实现这个功能,还是需要用到输入子系统的框架,与触摸屏驱动一样,再回顾一下输入系统的框架

 输入子系统按框架可以分为设备驱动层、事件层、以及核心层。

整个调用过程如下:

app_read->evdev_read->kbtab_irq->input_report_key->input_event->evdev_event->evdev_read

应用层           事件层          设备层         核心层                   核心层            事件层          事件层

如果要自己添加一个输入子系统的设备,只需要添加设备层的文件即可。

1、在里面添加设备层input_dev结构并初始化

2、编写中断处理程序

 在USB驱动程序中,中断处理程序就不是真正的CPU中断的处理程序了。而是USB总线驱动程序接收完成一包数据后的回调函数。

编写程序步骤:其中硬件相关的设置就是设置USB驱动设备传输的数据来源、数据存放地址、数据长度、怎么样处理数据

/* a、分配一个 input_dev结构体*/

/* b、设置 */
/* b.1 能产生哪类事件 */
/* b.2 能产生哪些事件 */

/* c、注册 */

/* d、硬件相关的设置 */

 

3、介绍程序用到的结构体

 1、struct input_dev结构体

struct input_dev {

    void *private;

    const char *name;//设备名字
    const char *phys;//文件路径,比如 input/buttons
    const char *uniq;
    struct input_id id;

    unsigned long evbit[NBITS(EV_MAX)];//表示支持哪类事件,常用于以下几种事件(可以多选)
    //EV_SYN      同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
    //EV_KEY       键盘事件
    //EV_REL       (relative)相对坐标事件,比如鼠标
    //EV_ABS       (absolute)绝对坐标事件,比如摇杆、触摸屏感应
    
    unsigned long keybit[NBITS(KEY_MAX)];//存放支持的键盘按键值
    //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)、BTN_TOUCH(触摸屏的按键)
    
    
    unsigned long relbit[NBITS(REL_MAX)];//存放支持的相对坐标值
    unsigned long absbit[NBITS(ABS_MAX)];//存放支持的绝对坐标值
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    unsigned long swbit[NBITS(SW_MAX)];

    ...
    ...

    int absmax[ABS_MAX + 1];//绝对坐标的最大值
    int absmin[ABS_MAX + 1];//绝对坐标的最小值
    int absfuzz[ABS_MAX + 1];//绝对坐标的干扰值,默认为0,
    int absflat[ABS_MAX + 1];//绝对坐标的平焊位置,默认为0
    
    ...
    ...
};

2、struct usb_device结构体,描述整个USB设备的结构体,一般不会用到里面的变量,这里不做详细注释。

3、struct usb_interface结构体,这个结构体是由USB核心传递给USB驱动程序的,用它来描述USB接口。USB驱动程序负责后续的控制。

struct usb_interface {
    /* array of alternate settings for this interface,
     * stored in no particular order */
    struct usb_host_interface *altsetting;//一个接口结构体数组,包含了所有可能用于该接口的可选设置

    struct usb_host_interface *cur_altsetting;    /* the currently active alternate setting *///表示该接口的当前活动设置
    unsigned num_altsetting;    /* number of alternate settings *///可选设置数量

    int minor;            /* minor number this interface is bound to */      //USB核心分配的次设备号
    enum usb_interface_condition condition;        /* state of binding */   
    unsigned is_active:1;        /* the interface is not suspended */
    unsigned needs_remote_wakeup:1;    /* driver requires remote wakeup */

    struct device dev;        /* interface specific device info */
    struct device *usb_dev;        /* pointer to the usb class's device, if any */
    int pm_usage_cnt;        /* usage counter for autosuspend */
};

4、struct usb_host_interface结构体,主要用来描述USB接口

struct usb_host_interface {
    struct usb_interface_descriptor    desc;//接口描述符

    /* array of desc.bNumEndpoint endpoints associated with this
     * interface setting.  these will be in no particular order.
     */
    struct usb_host_endpoint *endpoint;//端点描述符指针

    char *string;        /* iInterface string, if present *///接口描述符的名字字符指针
    unsigned char *extra;   /* Extra descriptors *///额外的描述字符指针
    int extralen;//额外描述大小
};

5、usb_host_endpoint ,主要用来描述USB端点

struct usb_host_endpoint {
    struct usb_endpoint_descriptor    desc;//端点描述符
    struct list_head        urb_list;//端点描述符列表指针,可以根据这个结构体找到所有的处于同一指针链表的usb_host_endpoint结构
    void                *hcpriv;
    struct ep_device         *ep_dev;    /* For sysfs info */

    unsigned char *extra;   /* Extra descriptors */
    int extralen;
};

6、struct usb_endpoint_descriptor结构体,端点描述符

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
    __u8  bLength;                //描述符长度
    __u8  bDescriptorType;//描述符类型05

    __u8  bEndpointAddress;//端点数目与方向
    __u8  bmAttributes;         //支持的传输类型
    __le16 wMaxPacketSize; //支持的最大信息包大小
    __u8  bInterval;             //最大延迟/轮询时距/NAK速率

    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));

7、struct urb结构体,是一个USB请求块,作用是和所有的USB设备通讯。

struct urb
{
    /* private: usb core and host controller only fields in the urb */
    struct kref kref;        /* reference count of the URB */
    spinlock_t lock;        /* lock for the URB */
    void *hcpriv;            /* private data for host controller */
    atomic_t use_count;        /* concurrent submissions counter */
    u8 reject;            /* submissions will fail */

    /* public: documented fields in the urb that can be used by drivers */
    struct list_head urb_list;    /* list head for use by the urb's
                     * current owner */
    struct usb_device *dev;     /* (in) pointer to associated device *///urb所发送的目标usb_devices指针
    unsigned int pipe;        /* (in) pipe information */                //端点信息,可以设置为控制、批量、中断、等时等端点输入或输出
    int status;            /* (return) non-ISO status */                    //当urb结束后或者正在被USB核心处理时,该变量被设置为当前的状态
    unsigned int transfer_flags;    /* (in) URB_SHORT_NOT_OK | ...*/   //USB数据传输标志,可以通过这个标志判断数据传输情况
    void *transfer_buffer;        /* (in) associated data buffer */      //以DMA方式传输数据到USB设备的缓存区指针,必须用kmalloc来创建
    dma_addr_t transfer_dma;    /* (in) dma addr for transfer_buffer *///以DMA方式传输数据到USB设备的地址
    int transfer_buffer_length;    /* (in) data buffer length */        //以DMA方式传输数据到USB设备的缓冲区的长度
    int actual_length;        /* (return) actual transfer length */             //当urb结束后,实际接收到的或者发送的数据长度
    unsigned char *setup_packet;    /* (in) setup packet (control only) *///
    dma_addr_t setup_dma;        /* (in) dma addr for setup_packet */
    int start_frame;        /* (modify) start frame (ISO) */
    int number_of_packets;        /* (in) number of ISO packets */
    int interval;            /* (modify) transfer interval  * (INT/ISO) */  //urb被轮询的时间间隔,仅对中断或者等时urb有效
    int error_count;        /* (return) number of ISO errors */          //用于等时urb结束后,报告的类型错误的数量
    void *context;            /* (in) context for completion */  //指向一个可用被USB驱动程序设置的数据块
    usb_complete_t complete;    /* (in) completion routine *///指向一个结束处理例程的指针,当urb被完全传输或者错误时调用这个函数
    struct usb_iso_packet_descriptor iso_frame_desc[0];    //
                    /* (in) ISO ONLY */
};

8、struct usb_device_id结构体,提供不同类型的该驱动程序支持的USB设备,USB核心使用该列表来判断对于一个设备改使用哪一个驱动程序

struct usb_device_id {
    /* which fields to match against? */
    __u16        match_flags;//在设备插上后需要匹配下面的哪几个参数来匹配驱动

    /* Used for product specific matches; range is inclusive */
    __u16        idVendor;//USB制造商ID
    __u16        idProduct;//USB产品ID
    __u16        bcdDevice_lo;//产品版本号最低值
    __u16        bcdDevice_hi;//产品版本号最高值

    /* Used for device class matches */
    __u8        bDeviceClass;//设备的类型
    __u8        bDeviceSubClass;//设备的子类型
    __u8        bDeviceProtocol;//设备的协议

    /* Used for interface class matches */
    __u8        bInterfaceClass;//接口类型
    __u8        bInterfaceSubClass;//接口子类型
    __u8        bInterfaceProtocol;//接口协议

    /* not matched against */
    kernel_ulong_t    driver_info;//
};

9、struct usb_driver结构体,USB驱动程序必须创建的主要结构体,它向USB核心代码描述USB驱动程序。

struct usb_driver {
    const char *name;//执行驱动程序名字的指针

    int (*probe) (struct usb_interface *intf,
              const struct usb_device_id *id);//指向USB驱动程序中的探测函数的指针

    void (*disconnect) (struct usb_interface *intf);//指向USB驱动程序中的断开函数的指针

    int (*ioctl) (struct usb_interface *intf, unsigned int code,
            void *buf);//执行USB驱动程序中的ioctl指针

    int (*suspend) (struct usb_interface *intf, pm_message_t message);//指向USB驱动程序中的挂起函数指针
    int (*resume) (struct usb_interface *intf);//指向USB驱动程序中的恢复函数指针

    void (*pre_reset) (struct usb_interface *intf);//
    void (*post_reset) (struct usb_interface *intf);

    const struct usb_device_id *id_table;//指向struct usb_device_id表的指针

    struct usb_dynids dynids;
    struct usbdrv_wrap drvwrap;
    unsigned int no_dynamic_id:1;
    unsigned int supports_autosuspend:1;
};

 

4、介绍程序用到的函数

1、输入子系统相关的函数

struct input_dev *input_allocate_device(void);//分配一个struct input_dev结构体,返回的是struct input_dev *
inline void set_bit(int nr, volatile unsigned long *addr);//这是一个内联函数,在调用的时候展开,功能为设置*addr的nr位为1
int input_register_device(struct input_dev *dev);//注册输入子系统设备驱动,输入参数为struct input_dev *
void input_unregister_device(struct input_dev *dev);//反注册输入子系统的设备驱动,输入参数为struct input_dev *
void input_free_device(s3c_ts_input);//释放分配的input_dev结构,,输入参数为struct input_dev *

static inline void input_sync(struct input_dev *dev);//上传同步事件,表示这次事件数据已经传送完成了
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);//上传输入事件
//struct input_dev *dev表示哪个输入子系统设备的事件上传
//unsigned int type,表示上传的事件类型
//unsigned int code,表示事件类型中的哪类事件
//value表示事件的值

 

2、USB核心相关的函数

static inline int usb_register(struct usb_driver *driver);//注册一个usb驱动结构体driver到USB核心
void usb_deregister(struct usb_driver *driver);//从USB核心释放一个usb驱动结构体driver
usb_rcvintpipe(dev,endpoint);//这是一个宏。设置usb设备dev的端点endpoint为中断IN端点
void *usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma);//分配一个地址作为USB接收到的数据,返回分配地址的首地址
//struct usb_device *dev是USB设备结构体
//size_t size分配的内存的大小
//gfp_t mem_flags是分配的标志
//dma_addr_t *dma是分配完成后返回的物理地址
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);//分配一个urb结构体
//int iso_packets是等时数据包
//gfp_t mem_flags内存分配标志
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);//初始化即将被发送到USB设备的中断端点的urb
//struct urb *urb是指向需要初始化的urb的指针
//struct usb_device *dev是该urb所发送的目标USB设备
//unsigned int pipe是该urb所发送的目标USB设备的特点端点。该值由usb_sndintpipe或usb_rcvintpipe函数创建
//void *transfer_buffer用于保存外发数据或接收数据的缓冲区的指针
//int buffer_length是transfer_buffer指针所指向缓存区的大小
//usb_complete_t complete_fn指向当该urb结束之后调用的结束处理例程的指针
//void *context指向一个小数据块,该块被添加到urb结构体中以便进行结束处理例程后面的查找
//int interval该urb应该被调度的间隔
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);//提交urb
//struct urb *urb表示需要提交的urb控制块
//gfp_t mem_flags内存分配标志
void usb_buffer_free(struct usb_device *dev,size_t size,void *addr,dma_addr_t dma);//释放分配的usb缓存数据
//struct usb_device *dev表示目标USB设备
//size_t size释放的缓冲区的大小
//void *addr指向释放的缓冲区的指针
//dma_addr_t dma表示释放的缓冲区的物理地址
int usb_unlink_urb(struct urb *urb);//释放urb请求块
//struct urb *urb表示指向释放的urb请求块的指针

 

5、编写程序

直接放出程序源码,可以看到这个程序的结构和输入子系统的结构差不多。

1、首先加载这个模块后,会调用usb_mouse_as_key_init函数,然后usb_mouse_as_key_driver 结构体被注册到USB核心

2、当插上USB鼠标设备后,如果此设备和此驱动的接口类、接口子类、接口协议相同(位于usb_mouse_as_key_id_table ),那么usb_mouse_as_key_probe函数被调用

3、usb_mouse_as_key_probe函数是核心函数,在里面做许多事情,具体看代码

/* a、分配一个 input_dev结构体*/

/* b、设置 */
/* b.1 能产生哪类事件 */
/* b.2 能产生哪些事件 */

/* c、注册 */

/* d、硬件相关的设置 */

4、当按下USB按键后在第三步中设置的usb_mouse_as_key_irq函数被调用

5、数据包会在usb_mouse_as_key_irq回调函数被处理。至于数据的含义需要自己根据USB设备来判断然后定义,我用的鼠标按键的值位于usb_buf[1]中,所以可以根据这个值判断是哪一个按键被按下或松开,然后上传事件到输入子系统。

详细的解释可以参考如下代码:

#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 *uk_dev; //定义一个输入子系统设备结构体
static dma_addr_t usb_buf_phys; //物理地址
static char *usb_buf;                  //从USB主控制器接收到的数据存放的导致
static int len;                             //从USB主控制器接收到的数据的长度
static struct urb *uk_urb;           //定义一个USB请求块



/* 一包数据接收完成后处理函数*/
 static void usb_mouse_as_key_irq(struct urb *urb)
 {
//    int i;
//    static int cnt = 0;

//    printk("data cnt %d: ", ++cnt);
//    for (i = 0; i < len; i++)
//    {
//        printk("%02x ", usb_buf[i]);
//    }
//    printk("\n");

    /* 
      * USB鼠标数据含义
     *  data[0]: bit0-左键, 1-按下, 0-松开
     *          bit1-右键, 1-按下, 0-松开
     *          bit2-中键, 1-按下, 0-松开 
     */
     
    static unsigned char pre_val;//前一个按键的按键值,每当按键值变化才上传

    if((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))//左键发生变化
    {
         input_event(uk_dev,EV_KEY, KEY_L, (usb_buf[1]?1:0));
         input_sync(uk_dev);                                                    //上传同步事件
    }

    if((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))//右键发生变化
    {
         input_event(uk_dev,EV_KEY, KEY_S, (usb_buf[1]?1:0));
         input_sync(uk_dev);                                                    //上传同步事件
    }

    if((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))//中键发生变化
    {
         input_event(uk_dev,EV_KEY, KEY_ENTER, (usb_buf[1]?1:0));
         input_sync(uk_dev);                                                    //上传同步事件
    }
    pre_val = usb_buf[1];
    
     /* 重新提交urb */
    usb_submit_urb(uk_urb, GFP_KERNEL);//提交URB,将URB的控制还给USB核心处理程序
 }
 
static int usb_mouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_device *dev  = interface_to_usbdev(intf);//根据usb接口,取得usb设备
    struct usb_host_interface *interface;                        //定义一个USB主机控制器接口描述符
    struct usb_endpoint_descriptor *endpoint;                 //定义一个端点描述符
    int pipe;
    
    interface = intf->cur_altsetting;                               //获得usb控制器的接口描述符

    endpoint = &interface->endpoint[0].desc;//取得usb 控制器的第一个端点描述符
    
    
    printk("found usbmouse!\n");

    printk("bcdUSB = %x\n",dev->descriptor.bcdUSB);  //从USB设备描述符中获取USB版本
    printk("vidUSB = %x\n",dev->descriptor.idVendor); //从USB设备描述符中获取厂商ID
    printk("pidUSB = %x\n",dev->descriptor.idProduct);//从USB设备描述符中获取产品ID

    printk("bdcUSB = %x\n",intf->cur_altsetting->desc.bInterfaceClass);//从USB设备获取设备类
    printk("bdsUSB = %x\n",intf->cur_altsetting->desc.bInterfaceSubClass);//从USB设备获取设备从类
    printk("bdpUSB = %x\n",intf->cur_altsetting->desc.bInterfaceProtocol);//从USB设备获取设备协议

    /* a、分配一个 input_dev结构体*/
    uk_dev = input_allocate_device();//分配一个input_dev结构体

    /* b、设置 */
        /* b.1 能产生哪类事件 */
    set_bit(EV_KEY, uk_dev->evbit);//产生按键事件
    set_bit(EV_REP, uk_dev->evbit);//产生重复事件
        /* b.2 能产生哪些事件 */
    set_bit(KEY_L, uk_dev->keybit);//产生按键事件的L事件
    set_bit(KEY_S, uk_dev->keybit);//产生按键事件的S事件
    set_bit(KEY_ENTER, uk_dev->keybit);//产生按键事件的ENTER时间
    /* c、注册 */
    input_register_device(uk_dev);//注册一个输入设备
    
    /* d、硬件相关的设置 */
    /* 数据传输三要素: 源、目的、长度*/
    /* 源:USB设备某个端点 */
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//设置端点为中断IN端点
    
    /* 长度 */
    len = endpoint->wMaxPacketSize;//长度为最大包长度
    
    /* 目的 */
    usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);//分配一个地址作为USB接收到的数据
    
    /* 使用三要素*/
    uk_urb= usb_alloc_urb(0, GFP_KERNEL);              //分配一个USB请求块
    
    /* 使用三要素,设置urb */
    usb_fill_int_urb(uk_urb, dev, pipe, usb_buf,
                len,usb_mouse_as_key_irq, NULL, endpoint->bInterval);//初始化即将被发送到USB设备的中断端点的URB
    
    uk_urb->transfer_dma = usb_buf_phys;            //usb控制器完成数据接收后将数据存放的物理地址
    uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //当URB包含一个即将传输的DMA缓冲区时应该设置URB_NO_TRANSFER_DMA_MAP
    
    /* 使用URB */
    ret = usb_submit_urb(uk_urb, GFP_KERNEL);//提交urb
    if(ret)
        return -1;
    return 0;
}

static void usb_mouse_as_key_disconnect(struct usb_interface *intf)
{
    struct usb_device *dev = interface_to_usbdev(intf);
    
    input_free_device(uk_dev);//释放一个input_dev结构体
    input_unregister_device(uk_dev);//反注册一个输入设备
    usb_buffer_free(dev, len, usb_buf, usb_buf_phys);//释放分配的usb缓存数据
    usb_unlink_urb(uk_urb);//不使用urb控制块
    
    printk("disconnetc usbmouse\n");
}

static struct usb_device_id usb_mouse_as_key_id_table [] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE) },
    { }    /* Terminating entry *///终止入口项*/
};




static struct usb_driver usb_mouse_as_key_driver = {
    .name        = "usbmouse_askey",
    .probe        = usb_mouse_as_key_probe,
    .disconnect    = usb_mouse_as_key_disconnect,
    .id_table    = usb_mouse_as_key_id_table,
};

static int __init usb_mouse_as_key_init(void)
{
    int retval = usb_register(&usb_mouse_as_key_driver);//注册一个usb驱动
    return retval;
}

static void __exit usb_mouse_as_key_exit(void)
{
    usb_deregister(&usb_mouse_as_key_driver);
}

module_init(usb_mouse_as_key_init);
module_exit(usb_mouse_as_key_exit);

MODULE_LICENSE("GPL");

 

6、测试程序

测试流程如下:

1、insmod 11th_usbmouse_as_key_drv.ko

2、ls /dev/event*

3、接上USB鼠标

4、ls /dev/event*后可以看到新增了一个event1

5、cat dev/tty1 然后按鼠标按键,左键、右键、中键分别为l、s、enter

6、测试成功

 

转载于:https://www.cnblogs.com/andyfly/p/9593199.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30363981/article/details/98236695

智能推荐

linux centos7安装redis_linux7安装redis客户端-程序员宅基地

文章浏览阅读312次。linux安装redis_linux7安装redis客户端

动态规划入门感悟_动态规划的基本思想及感悟-程序员宅基地

文章浏览阅读357次,点赞2次,收藏3次。不会动态规划的可以看看我博客里动态规划入门一文哦http://blog.csdn.net/qq_39670434/article/details/77414362在这里为了帮助萌新们学习动态规划,调用几篇大佬的好文章http://blog.csdn.net/baidu_28312631/article/details/47418773http://blog.csdn.net_动态规划的基本思想及感悟

单片机上电不断重启复位_stc8f单片机勾选低压复位单片机不断复位怎么解决-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏15次。单片机不断重启复位的原因主要是一个原因;就是单片机在不断复位造成复位的原因有几个;1、单片机硬件复位,这个要检查硬件电路中的复位电路是不是有错误2、单片机程序中有代码造成软件复位3、单片机在不断上电断电循环过程造成不断复位主要讲一下第三种情况,这种情况的主要原因的因为外部电源供电不足导致单片机不断复位。你先检查一下自己的电路中是否有很多需要供电的模块,比如电机,制冷发热类的模块等等需要..._stc8f单片机勾选低压复位单片机不断复位怎么解决

哈尔滨工业大学计算机科学与技术学院许博文,王轩-哈尔滨工业大学(深圳)计算机科学与技术学院...-程序员宅基地

文章浏览阅读573次。一、近三年发表期刊论文[1]第一作者及通讯作者论文1)Yulin Wu, Xuan Wang, Zoe L. Jiang, etc. Efficient Server-Aided Secure Two-Party Computation in Heterogeneous Mobile Cloud Computing, IEEE Transactions on Dependable and Secu..._ansactions on dependable and secure computing

Prometheus源码学习(8) scrape总体流程_promethus scrapes-程序员宅基地

文章浏览阅读1.2k次。1. main 函数中初始化 scrapeManager 实例// 初始化 scrapeManager,fanout Storage 是一个读写多个底层存储的代理scrapeManager = scrape.NewManager(log.With(logger, "component", "scrape manager"), fanoutStorage)fanoutStorage 是读写多个底层存储的代理,实现了 storage.Appendable 接口。scrape.Manager 结构体._promethus scrapes

Windows10 Atom安装和运行Python的使用教程(详细)-程序员宅基地

文章浏览阅读2.6w次,点赞19次,收藏85次。目录一、下载Atom二、Atom安装Python相关组件1.检查Python库支持2.安装Python的适合Atom的IDE、UI、Server和运行工具*三、运行代码范例(爬取以杉原杏璃为关键字的百度图片)一、下载Atom1.官网:Atom官网2.打开这个网页,可以看到Atom针对于操作系统Windows7或以上的版本3.下载完成,双击exe4.加..._atom安装

随便推点

ubuntu怎么卸载matlab,卸载Ubuntu下Matlab (uninstall matlab2009 for linux)-程序员宅基地

文章浏览阅读422次。2.在/var目录搜索lm,我只找到以lm-sensors开头的文件,而lm-sensors是一个硬件状况监视器,用来得到温度、电压、风扇速度传感器信我在matlab官网搜到如下帮助文档Problem Description:I would like to uninstall MATLAB on a UNIX or Linux machine.Solution:There is not unins..._ubantu 无法移除matlab

运算放大器的关键指标详解二(噪声)_运放pid电路的噪声-程序员宅基地

文章浏览阅读2.1w次,点赞43次,收藏192次。噪声指标(Noise)一个正常工作的放大电路,当输入端接地时,用示波器观察输出,你看到的可能不是平直的细线,而是在一定幅度之内的杂乱无章的波形。这就是噪声。 你在示波器上看到线越粗,就说明噪声幅度越大。放大电路的输出端噪声,小至 μV 以下,大至百 mV 以上,完全取决于电路设计,能否在示波器上看见,则取决于示波器选择和设置。噪声定义:1) 它的波形在任意时刻都是不确定的,因此它是广谱的,有低频也有高频;2) 它的幅度又是有限制的,这与数学上的高斯分布近似但不完全一致;3) 它具有无限积分趋零性_运放pid电路的噪声

数据分析案例分析:日化公司社群营销场景,产品SKU,用户转化率,用户流转地图_leads数-程序员宅基地

文章浏览阅读3k次。近期参加了一个业务数据分析的3天课程,锻炼自己的业务实例数据分析能力。接下来的内容是第一天课程的作业,里面涉及到许多自己的知识盲区(社群营销,产品SKU,用户转化率,用户流转地图)。分享在这里,期待感兴趣的同志们多多指点~目录作业要求问题分析背景分析概念厘清1. 社群营销是什么?拉群卖货吗?2. 产品SKU3. 用户转化率Q2为什么展示后4张图(图6-图9)【作业点评总结Q2】Q1 从图中看,存在什么问题,出现这些问题的原因是什么Step1. 分..._leads数

数据库多维度水平切分设想 --- 分库,分表,多维度,水平切分,mysql,负载均衡_ssas 加入维度后数据没有切分-程序员宅基地

文章浏览阅读3k次。随着互联网应用的普及,海量数据存储早已经成了大型网站技术人员关注的焦点。每天上亿的访问量对数据库的压力可想而知。因此,为了维持数据库的稳定性和可扩展性,我们常常选择在业务垂直切分的基础上(抽象出业务中心单元如用户中心),再进行数据库的水平切分。 那么为什么要进行数据库的水平切分呢?有些人会说,如果单台数据库服务器过载过高,则可以采用replication的机制,分别指定读写服务器实现读写_ssas 加入维度后数据没有切分

eclipse中导航栏名称主要功能_echarls的导航栏意思-程序员宅基地

文章浏览阅读375次。A-mapper 里面主要写数据库的A-parent 类,父类方法A-pojo POJO实质上可以理解为简单的实体类,顾名思义POJO类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将POJO类当做对象来进行使用,当然也是可以方便的调用其get,set方法。POJO类也给我们在struts框架中的配置带来了很大的方便。A-service 提供与数据..._echarls的导航栏意思

WebSocket 的 PHP 实现 - phpwebsocket_websocket chr(1)-程序员宅基地

文章浏览阅读1.3k次。从名字上也可看出,这是一个 WebSocket 的 PHP 实现。示例客户端代码:1var host = "ws://localhost:12345/websocket/server.php";2try{3 socket = new WebSocket(host);_websocket chr(1)

推荐文章

热门文章

相关标签