USB驱动——鼠标


一 . 驱动程序描述

 

struct usb_device_id 结构体如下:可把USB设备设置为一种或一类设备。



二 . URB

URB定义:

usb 请求块(usb request block, urb)是usb设备驱动中用来描述与usb设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体,是usb主机与设备通信的电波。

  1. struct urb {  
  2.     /* private: usb core and host controller only fields in the urb */  
  3.     struct kref kref;        /* URB引用计数 */  
  4.     void *hcpriv;            /* host控制器的私有数据 */  
  5.     atomic_t use_count;        /* 当前提交计数 */  
  6.     atomic_t reject;        /* 提交失败计数 */  
  7.     int unlinked;            /* 连接失败代码 */  
  8.     /* public: documented fields in the urb that can be used by drivers */  
  9.     struct list_head urb_list;    /* list head for use by the urb's 
  10.                      * current owner */  
  11.     struct list_head anchor_list;    /* the URB may be anchored */  
  12.     struct usb_anchor *anchor;  
  13.     struct usb_device *dev;     /* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.*/  
  14.     struct usb_host_endpoint *ep;    /* (internal) pointer to endpoint */  
  15.     unsigned int pipe;        
  16.     int status;           
  17.     unsigned int transfer_flags;    /* 传输设置*/  
  18.     void *transfer_buffer;        /* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。 对控制端点, 这个缓冲区用于数据中转*/  
  19.     dma_addr_t transfer_dma;    /* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/  
  20.     int transfer_buffer_length;    /* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个 USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/  
  21.     int actual_length;        /* 当这个 urb 完成后, 该变量被设置为这个 urb (对于 OUT urb)发送或(对于 IN urb)接受数据的真实长度.对于 IN urb, 必须是用此变量而非 transfer_buffer_length , 因为接收的数据可能比整个缓冲小*/  
  22.     unsigned char *setup_packet;    /* 指向控制urb的设置数据包指针.它在传送缓冲中的数据之前被传送(用于控制 urb)*/  
  23.     dma_addr_t setup_dma;        /* 控制 urb 用于设置数据包的 DMA 缓冲区地址,它在传送普通缓冲区中的数据之前被传送(用于控制 urb)*/  
  24.     int start_frame;        /* 设置或返回初始的帧数量(用于等时urb) */  
  25.     int number_of_packets;          
  26.     int interval;            
  27.     int error_count;        /* 等时urb的错误计数,由USB核心设置 */  
  28.     void *context;            /* 指向一个可以被USB驱动模块设置的数据块. 当 urb 被返回到驱动时,可在结束处理例程中使用. */  
  29.     usb_complete_t complete;    /* 结束处理例程函数指针, 当 urb 被完全传送或发生错误,它将被 USB 核心调用. 此函数检查这个 urb, 并决定释放它或重新提交给另一个传输中*/  
  30.     struct usb_iso_packet_descriptor iso_frame_desc[0];  
  31.     };     



URB 处理流程

1, usb设备驱动程序创建初始化一个访问特定usb设备特定端点的urb,并提交给usb core

2, usb core提交该urb到usb主控制器驱动程序

3, usb主控制器驱动程序根据urb描述的信息,来访问usb设

4,当设备访问结束后,usb主控制器驱动程序通知usb core(调用这个函数usb_complete_t complete;然后其再通知usb设备驱动程序


创建URB

struct urb *usb_alloc_urb(int iso_packets, int mem_flags)
参数:

iso_packets: urb 所包含的等时数据包的个数,若不是等时传输,则为0.

mem_flags: 内存分配标识(如GFP_KERNEL),参考kmalloc

初始化URB

对于中断urb,使用usb_fill_int_urb函数来初始化:

static inline void usb_fill_int_urb (struct urb *urb,要初始化的urb指针。
                     struct usb_device *dev,所要访问的设备
                     unsigned int      pipe,要访问的端点所对应的管道,使用usb_sndintpipe()或usb_rcvintpipe()创建
                     void              *transfer_buffer,要传输的数据缓冲区
                     int               buffer_length,缓冲区长度
                     usb_complete_t    complete_fn,当完成该urb所请求的操作时,要调用的回调函数
                     void              *context,complet_fn函数所需的上下文,通常取值为dev
                     int               interval)  urb被调度的时间间隔

管道:驱动程序的数据缓冲区与一个端点的连接,它代表了一种在两者之间移动数据的能力

对于批量urb,使用usb_fill_bulk_urb函数来初始化。

对于控制urb,使用usb_fill_control_urb函数来初始化。

等时urb没有像中断,控制和批量urb那样的初始化函数,我们只能手动的初始化urb

提交URB

在完成urb的创建和初始化后,urb便可以通过usb_submit_urb函数来提交给usb核心:

int usb_submit_urb(struct urb *urb,gfp_t mem_flags)

参数:

urb:指向urb的指针;

mem_flags:内存分配标识,它用于告知usb核心如何分配内存缓冲区。

处理URB:

URB被提交到USB核心后,usb核心指定usb主控制器驱动程序来处理该urb,在3种情况下,urb会被认为处理完成:

1 urb被成功发送给设备,并且设备返回成功确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请求的数据被成功收到。

2如果发送数据到设备或从设备接受数据时发生了错误,urb->status将记录错误值。

3 urb被“取消”,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或urb虽已提交,而usb设备被拔出的情况下

当urb处理完成后,urb完成函数将被调用。


三 . 连接过程

USB 总线引出两个重要的链表!
一个 USB 总线引出两个重要的链表,一个为了, USB 设备链表,一个为 USB 驱动链表。设备链表包含各种系统中的 USB 设备以及这些设备的所有接口,驱动链表包含 USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)。

 

USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)的区别是什么?
USB 设备驱动程序包含 USB 设备的一些通用特性,将与所有 USB 设备相匹配。在 USB core 定义了:struct usb_device_driver usb_generic_driver。usb_generic_driver 是 USB 子系统中唯一的一个设备驱动程序对象。而 USB 驱动程序则是与接口相匹配,接口是一个完成特定功能的端点的集合。

 

设备是如何添加到设备链表上去的?
在设备插入 USB 控制器之后,USB core 即会将设备在系统中注册,添加到 USB 设备链表上去。

 

USB 设备驱动程序(usb device driver)是如何添加到驱动链表上去的?
在系统启动注册 USB core 时,USB 设备驱动程序即将被注册,也就添加到驱动链表上去了。

 

接口是如何添加到设备链表上去的?
在 USB 设备驱动程序和 USB 设备的匹配之后,USB core 会对设备进行配置,分析设备的结构之后会将设备所有接口都添加到设备链表上去。比如鼠标设备中有一个接口,USB core 对鼠标设备配置后,会将这个接口添加到设备链表上去。

 

USB 驱动程序(usb driver)是如何添加到驱动链表上去的?
在每个 USB 驱动程序的被注册时,USB 驱动程序即会添加到驱动链表上去。比如鼠标驱动程序,usb_mouse_init 函数将通过 usb_register(&usb_mouse_driver) 将鼠标驱动程序注册到 USB core 中,然后就添加到驱动链表中去了。其中 usb_mouse_driver 是描述鼠标驱动程序的结构体。

 

已配置状态(configured status)之后话
当鼠标的设备、接口都添加到设备链表,并且鼠标驱动程序也添加到驱动链表上去了,系统就进入一种叫做已配置(configured)的状态。要达到已配置状态,将经历复杂的过程,USB core 为 USB 设备奉献着无怨无悔。在这个过程中,系统将会建立起该设备的的设备、配置、接口、设置、端点的描述信息,它们分别被 usb_device、usb_configuration、usb_interface、usb_host_interface、usb_host_endpoint 结构体描述。
设备达到已配置状态后,首先当然就要进行 USB 驱动程序和相应接口的配对,对于鼠标设备来说则是鼠标驱动程序和鼠标中的接口的配对。USB core 会调用 usb_device_match 函数,通过比较设备中的接口信息和 USB 驱动程序中的 id_table,来初步决定该 USB 驱动程序是不是跟相应接口相匹配。通过这一道关卡后,USB core 会认为这个设备应该由这个驱动程序负责。
然而,仅仅这一步是不够的,接着,将会调用 USB 驱动程序中的 probe 函数对相应接口进行进一步检查。如果该驱动程序确实适合设备接口,对设备做一些初始化工作,分配 urb 准备数据传输。
当鼠标设备在用户空间打开时,将提交 probe 函数构建的 urb 请求块,urb 将开始为传送数据而忙碌了。urb 请求块就像一个装东西的“袋子”,USB 驱动程序把“空袋子”提交给 USB core,然后再交给主控制器,主控制器把数据放入这个“袋子”后再将装满数据的“袋子”通过 USB core 交还给 USB 驱动程序,这样一次数据传输就完成了。


四 . usbmouse . c

  1.   
  2. /* 
  3.  * 鼠标结构体,用于描述鼠标设备。 
  4.  */  
  5. struct usb_mouse   
  6. {  
  7.     /* 鼠标设备的名称,包括生产厂商、产品类别、产品等信息 */  
  8.     char name[128];   
  9.     /* 设备节点名称 */  
  10.     char phys[64];    
  11.     /* USB 鼠标是一种 USB 设备,需要内嵌一个 USB 设备结构体来描述其 USB 属性 */  
  12.     struct usb_device *usbdev;  
  13.     /* USB 鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */  
  14.     struct input_dev *dev;    
  15.     /* URB 请求包结构体,用于传送数据 */  
  16.     struct urb *irq;  
  17.     /* 普通传输用的地址 */  
  18.     signed char *data;  
  19.     /* dma 传输用的地址 */  
  20.     dma_addr_t data_dma;          
  21. };  
  22.  


下面我们分析下USB鼠标驱动,鼠标输入HID类型,其数据传输采用中断URB,鼠标端点类型为IN。好了,我们先看看这个驱动的模块加载部分。

static int __init usb_mouse_init(void)

{

int retval = usb_register(&usb_mouse_driver);

if (retval == 0)

printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"

DRIVER_DESC "\n");

return retval;

}

模块加载部分仍然是调用usb_register注册USB驱动,我们跟踪看看被注册的usb_mouse_driver

static struct usb_driver usb_mouse_driver = {

.name = "usbmouse", //驱动名

.probe = usb_mouse_probe, //探测

.disconnect = usb_mouse_disconnect,

.id_table = usb_mouse_id_table, //支持项

};

关于设备支持项我们前面已经讨论过了

static struct usb_device_id usb_mouse_id_table [] = {

{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,

USB_INTERFACE_PROTOCOL_MOUSE) },

{ }

};

再细细看看USB_INTERFACE_INFO宏的定义

#define USB_INTERFACE_INFO(cl, sc, pr) \

.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \

.bInterfaceClass = (cl), \

.bInterfaceSubClass = (sc), \

.bInterfaceProtocol = (pr)

根据宏,我们知道,我们设置的支持项包括接口类,接口子类,接口协议三个匹配项。

好了,我们主要看看usb_driver中定义的probe函数

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

struct usb_device *dev = interface_to_usbdev(intf); //由接口获取usb_device

struct usb_host_interface *interface; //设置

struct usb_endpoint_descriptor *endpoint; //端点描述符

struct usb_mouse *mouse //本驱动私有结构体

struct input_dev *input_dev; //输入结构体

int pipe, maxp;

int error = -ENOMEM;

interface = intf->cur_altsetting; //获取设置

if (interface->desc.bNumEndpoints != 1) //鼠标端点只有1

return -ENODEV;

endpoint = &interface->endpoint[0].desc; //获得端点描述符

if (!usb_endpoint_is_int_in(endpoint)) //检查该端点是否是中断输入端点

return -ENODEV;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //建立中断输入端点

//返回端点能传输的最大的数据包,鼠标的返回的最大数据包为4个字节

maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); //分配mouse结构体

input_dev = input_allocate_device(); //分配input设备空间

if (!mouse || !input_dev)

goto fail1;

mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma); //分配缓冲区

if (!mouse->data)

goto fail1;

mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //分配urb

if (!mouse->irq)

goto fail2;

mouse->usbdev = dev; //填充mouseusb_device结构体

mouse->dev = input_dev; //填充mouse input结构体

if (dev->manufacturer) //拷贝厂商ID

strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

if (dev->product) { //拷贝产品ID

if (dev->manufacturer)

strlcat(mouse->name, " ", sizeof(mouse->name));

strlcat(mouse->name, dev->product, sizeof(mouse->name));

}

if (!strlen(mouse->name)) //拷贝产品ID

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(dev, mouse->phys, sizeof(mouse->phys));

strlcat(mouse->phys, "/input0", sizeof(mouse->phys));

input_dev->name = mouse->name; //将鼠标名赋给内嵌input结构体

input_dev->phys = mouse->phys; //将鼠标设备节点名赋给内嵌input结构体

usb_to_input_id(dev, &input_dev->id); //usb_driver的支持项拷贝给input

input_dev->dev.parent = &intf->dev;

//evbit表明支持按键事件(EV_KEY)和相对坐标事件(EV_REL)

input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

//keybit表明按键值包括左键、右键和中键

input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |

BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);

//relbit表明相对坐标事件值包括X坐标和Y坐标

input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);

//keybit表明除了左键、右键和中键,还支持其他按键

input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |

BIT_MASK(BTN_EXTRA);

//relbit表明除了X坐标和Y坐标,还支持中键滚轮的滚动值

input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);

input_set_drvdata(input_dev, mouse); //mouse设置为input的私有数据

input_dev->open = usb_mouse_open; //input设备的open

input_dev->close = usb_mouse_close;

usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, (maxp > 8 ? 8 : maxp),

usb_mouse_irq, mouse, endpoint->bInterval); //填充urb

mouse->irq->transfer_dma = mouse->data_dma;

mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //使用transfer_dma

error = input_register_device(mouse->dev); //注册input设备

if (error)

goto fail3;

usb_set_intfdata(intf, mouse);

return 0;

fail3:

usb_free_urb(mouse->irq);

fail2:

usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);

fail1:

input_free_device(input_dev);

kfree(mouse);

return error;

}

其实上面这个probe主要是初始化usb设备和input设备,终极目标是为了完成urb的提交和input设备的注册。由于注册为input设备类型,那么当用户层open打开设备时候,最终会调用input中的open实现打开,我们看看inputopen的实现

static int usb_mouse_open(struct input_dev *dev)

{

struct usb_mouse *mouse = input_get_drvdata(dev); //获取私有数据

mouse->irq->dev = mouse->usbdev; //获取urb指针

if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //提交urb

return -EIO;

return 0;

}

好了,当用户层open打开这个USB鼠标后,我们就已经将urb提交给了USB核心,那么根据USB数据处理流程知道,当处理完毕后,USB核心会通知USB设备驱动程序,这里我们是响应中断服务程序,这就相当于该URB的回调函数。我们在提交urb时候定义了中断服务程序usb_mouse_irq,我们跟踪看看

static void usb_mouse_irq(struct urb *urb)

{

struct usb_mouse *mouse = urb->context;

signed char *data = mouse->data;

struct input_dev *dev = mouse->dev;

int status;

switch (urb->status) {

case 0: //成功

break;

case -ECONNRESET: //未连接

case -ENOENT:

case -ESHUTDOWN:

return;

default:

goto resubmit; //数据处理没成功,重新提交urb

}

input_report_key(dev, BTN_LEFT, data[0] & 0x01); //鼠标左键

input_report_key(dev, BTN_RIGHT, data[0] & 0x02); //鼠标右键

input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); //鼠标中键

input_report_key(dev, BTN_SIDE, data[0] & 0x08); //鼠标SIDE

input_report_key(dev, BTN_EXTRA, data[0] & 0x10); //鼠标EXTRA

input_report_rel(dev, REL_X, data[1]); //鼠标的水平位移

input_report_rel(dev, REL_Y, data[2]); //鼠标的垂直位移

input_report_rel(dev, REL_WHEEL, data[3]); //鼠标的滚轮滚动值

input_sync(dev);

resubmit:

status = usb_submit_urb (urb, GFP_ATOMIC); //再次提交urb,等待下次响应

if (status)

err ("can't resubmit intr, %s-%s/input0, status %d",

mouse->usbdev->bus->bus_name,

mouse->usbdev->devpath, status);

}

根据上面的中断服务程序,我们应该知道,系统是周期性地获取鼠标的事件信息,因此在URB回调函数的末尾再次提交URB请求块,这样又会调用新的回调函数,周而复始。在回调函数中提交URB只能是GFP_ATOMIC优先级,因为URB回调函数运行于中断上下文中禁止导致睡眠的行为。而在提交URB过程中可能会需要申请内存、保持信号量,这些操作或许会导致USB内核睡眠。

最后我们再看看这个驱动的私有数据mouse的定义

struct usb_mouse {

char name[128]; //名字

char phys[64]; //设备节点

struct usb_device *usbdev; //内嵌usb_device设备

struct input_dev *dev; //内嵌input_dev设备

struct urb *irq; //urb结构体

signed char *data; //transfer_buffer缓冲区

dma_addr_t data_dma; // transfer _dma缓冲区

};

在上面这个结构体中,每一个成员的作用都应该很清楚了,尤其最后两个的使用区别和作用,前面也已经说过。

如果最终需要测试这个USB鼠标驱动,需要在内核中配置USB支持、对HID接口的支持、对OHCI HCD驱动的支持。另外,将驱动移植到开发板之后,由于采用的是input设备模型,所以还需要开发板带LCD屏才能测试。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值