Linux USB 鼠标驱动程序详解.docx
《Linux USB 鼠标驱动程序详解.docx》由会员分享,可在线阅读,更多相关《Linux USB 鼠标驱动程序详解.docx(14页珍藏版)》请在冰豆网上搜索。
![Linux USB 鼠标驱动程序详解.docx](https://file1.bdocx.com/fileroot1/2022-11/21/ca327c76-07be-4bf9-9df9-d25e142f8325/ca327c76-07be-4bf9-9df9-d25e142f83251.gif)
LinuxUSB鼠标驱动程序详解
LinuxUSB鼠标驱动程序详解
USB总线引出两个重要的链表!
一个USB总线引出两个重要的链表,一个为USB设备链表,一个为USB驱动链表。
设备链表包含各种系统中的USB设备以及这些设备的所有接口,驱动链表包含USB设备驱动程序(usbdevicedriver)和USB驱动程序(usbdriver)。
USB设备驱动程序(usbdevicedriver)和USB驱动程序(usbdriver)的区别是什么?
USB设备驱动程序包含USB设备的一些通用特性,将与所有USB设备相匹配。
在USBcore定义了:
structusb_device_driverusb_generic_driver。
usb_generic_driver是USB子系统中唯一的一个设备驱动程序对象。
而USB驱动程序则是与接口相匹配,接口是一个完成特定功能的端点的集合。
设备是如何添加到设备链表上去的?
在设备插入USB控制器之后,USBcore即会将设备在系统中注册,添加到USB设备链表上去。
USB设备驱动程序(usbdevicedriver)是如何添加到驱动链表上去的?
在系统启动注册USBcore时,USB设备驱动程序即将被注册,也就添加到驱动链表上去了。
接口是如何添加到设备链表上去的?
在USB设备驱动程序和USB设备的匹配之后,USBcore会对设备进行配置,分析设备的结构之后会将设备所有接口都添加到设备链表上去。
比如鼠标设备中有一个接口,USBcore对鼠标设备配置后,会将这个接口添加到设备链表上去。
USB驱动程序(usbdriver)是如何添加到驱动链表上去的?
在每个USB驱动程序的被注册时,USB驱动程序即会添加到驱动链表上去。
比如鼠标驱动程序,usb_mouse_init函数将通过usb_register(&usb_mouse_driver)将鼠标驱动程序注册到USBcore中,然后就添加到驱动链表中去了。
其中usb_mouse_driver是描述鼠标驱动程序的结构体。
已配置状态(configuredstatus)之后话
当鼠标的设备、接口都添加到设备链表,并且鼠标驱动程序也添加到驱动链表上去了,系统就进入一种叫做已配置(configured)的状态。
要达到已配置状态,将经历复杂的过程,USBcore为USB设备奉献着无怨无悔。
在这个过程中,系统将会建立起该设备的的设备、配置、接口、设置、端点的描述信息,它们分别被usb_device、usb_configuration、usb_interface、usb_host_interface、usb_host_endpoint结构体描述。
设备达到已配置状态后,首先当然就要进行USB驱动程序和相应接口的配对,对于鼠标设备来说则是鼠标驱动程序和鼠标中的接口的配对。
USBcore会调用usb_device_match函数,通过比较设备中的接口信息和USB驱动程序中的id_table,来初步决定该USB驱动程序是不是跟相应接口相匹配。
通过这一道关卡后,USBcore会认为这个设备应该由这个驱动程序负责。
然而,仅仅这一步是不够的,接着,将会调用USB驱动程序中的probe函数对相应接口进行进一步检查。
如果该驱动程序确实适合设备接口,对设备做一些初始化工作,分配urb准备数据传输。
当鼠标设备在用户空间打开时,将提交probe函数构建的urb请求块,urb将开始为传送数据而忙碌了。
urb请求块就像一个装东西的“袋子”,USB驱动程序把“空袋子”提交给USBcore,然后再交给主控制器,主控制器把数据放入这个“袋子”后再将装满数据的“袋子”通过USBcore交还给USB驱动程序,这样一次数据传输就完成了。
以下是完全注释后的鼠标驱动程序代码usbmouse.c
/*
*$Id:
usbmouse.c,v1.152001/12/2710:
37:
41vojtechExp$
*
*Copyright(c)1999-2001VojtechPavlik
*
*USBHIDBPMousesupport
*/
#include
#include
#include
#include
#include
#include
/*
*VersionInformation
*/
#defineDRIVER_VERSION"v1.6"
#defineDRIVER_AUTHOR"VojtechPavlik"
#defineDRIVER_DESC"USBHIDBootProtocolmousedriver"
#defineDRIVER_LICENSE"GPL"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);
/*
*鼠标结构体,用于描述鼠标设备。
*/
structusb_mouse
{
/*鼠标设备的名称,包括生产厂商、产品类别、产品等信息*/
charname[128];
/*设备节点名称*/
charphys[64];
/*USB鼠标是一种USB设备,需要内嵌一个USB设备结构体来描述其USB属性*/
structusb_device*usbdev;
/*USB鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性*/
structinput_dev*dev;
/*URB请求包结构体,用于传送数据*/
structurb*irq;
/*普通传输用的地址*/
signedchar*data;
/*dma传输用的地址*/
dma_addr_tdata_dma;
};
/*
*urb回调函数,在完成提交urb后,urb回调函数将被调用。
*此函数作为usb_fill_int_urb函数的形参,为构建的urb制定的回调函数。
*/
staticvoidusb_mouse_irq(structurb*urb)
{
/*
*urb中的context指针用于为USB驱动程序保存一些数据。
比如在这个回调函数的形参没有传递在probe
*中为mouse结构体分配的那块内存的地址指针,而又需要用到那块内存区域中的数据,context指针则帮了
*大忙了!
*在填充urb时将context指针指向mouse结构体数据区,在这又创建一个局部mouse指针指向在probe
*函数中为mouse申请的那块内存,那块内存保存着非常重要数据。
*当urb通过USBcore提交给hc之后,如果结果正常,mouse->data指向的内存区域将保存着鼠标的按键
*和移动坐标信息,系统则依靠这些信息对鼠标的行为作出反应。
*mouse中内嵌的dev指针,指向input_dev所属于的内存区域。
*/
structusb_mouse*mouse=urb->context;
signedchar*data=mouse->data;
structinput_dev*dev=mouse->dev;
intstatus;
/*
*status值为0表示urb成功返回,直接跳出循环把鼠标事件报告给输入子系统。
*ECONNRESET出错信息表示urb被usb_unlink_urb函数给unlink了,ENOENT出错信息表示urb被
*usb_kill_urb函数给kill了。
usb_kill_urb表示彻底结束urb的生命周期,而usb_unlink_urb则
*是停止urb,这个函数不等urb完全终止就会返回给回调函数。
这在运行中断处理程序时或者等待某自旋锁
*时非常有用,在这两种情况下是不能睡眠的,而等待一个urb完全停止很可能会出现睡眠的情况。
*ESHUTDOWN这种错误表示USB主控制器驱动程序发生了严重的错误,或者提交完urb的一瞬间设备被拔出。
*遇见除了以上三种错误以外的错误,将申请重传urb。
*/
switch(urb->status)
{
case0:
/*success*/
break;
case-ECONNRESET:
/*unlink*/
case-ENOENT:
case-ESHUTDOWN:
return;
/*-EPIPE:
shouldclearthehalt*/
default:
/*error*/
gotoresubmit;
}
/*
*向输入子系统汇报鼠标事件情况,以便作出反应。
*data数组的第0个字节:
bit0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况;
*data数组的第1个字节:
表示鼠标的水平位移;
*data数组的第2个字节:
表示鼠标的垂直位移;
*data数组的第3个字节:
REL_WHEEL位移。
*/
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);
input_report_key(dev,BTN_EXTRA,data[0]&0x10);
input_report_rel(dev,REL_X,data[1]);
input_report_rel(dev,REL_Y,data[2]);
input_report_rel(dev,REL_WHEEL,data[3]);
/*
*这里是用于事件同步。
上面几行是一次完整的鼠标事件,包括按键信息、绝对坐标信息和滚轮信息,输入子
*系统正是通过这个同步信号来在多个完整事件报告中区分每一次完整事件报告。
示意如下:
*按键信息坐标位移信息滚轮信息EV_SYC|按键信息坐标位移信息滚轮信息EV_SYC...
*/
input_sync(dev);
/*
*系统需要周期性不断地获取鼠标的事件信息,因此在urb回调函数的末尾再次提交urb请求块,这样又会
*调用新的回调函数,周而复始。
*在回调函数中提交urb一定只能是GFP_ATOMIC优先级的,因为urb回调函数运行于中断上下文中,在提
*交urb过程中可能会需要申请内存、保持信号量,这些操作或许会导致USBcore睡眠,一切导致睡眠的行
*为都是不允许的。
*/
resubmit:
status=usb_submit_urb(urb,GFP_ATOMIC);
if(status)
err("can'tresubmitintr,%s-%s/input0,status%d",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath,status);
}
/*
*打开鼠标设备时,开始提交在probe函数中构建的urb,进入urb周期。
*/
staticintusb_mouse_open(structinput_dev*dev)
{
structusb_mouse*mouse=dev->private;
mouse->irq->dev=mouse->usbdev;
if(usb_submit_urb(mouse->irq,GFP_KERNEL))
return-EIO;
return0;
}
/*
*关闭鼠标设备时,结束urb生命周期。
*/
staticvoidusb_mouse_close(structinput_dev*dev)
{
structusb_mouse*mouse=dev->private;
usb_kill_urb(mouse->irq);
}
/*
*驱动程序的探测函数
*/
staticintusb_mouse_probe(structusb_interface*intf,conststructusb_device_id*id)
{
/*
*接口结构体包含于设备结构体中,interface_to_usbdev是通过接口结构体获得它的设备结构体。
*usb_host_interface是用于描述接口设置的结构体,内嵌在接口结构体usb_interface中。
*usb_endpoint_descriptor是端点描述符结构体,内嵌在端点结构体usb_host_endpoint中,而端点
*结构体内嵌在接口设置结构体中。
*/
structusb_device*dev=interface_to_usbdev(intf);
structusb_host_interface*interface;
structusb_endpoint_descriptor*endpoint;
structusb_mouse*mouse;
structinput_dev*input_dev;
intpipe,maxp;
interface=intf->cur_altsetting;
/*鼠标仅有一个interrupt类型的in端点,不满足此要求的设备均报错*/
if(interface->desc.bNumEndpoints!
=1)
return-ENODEV;
endpoint=&interface->endpoint[0].desc;
if(!
usb_endpoint_is_int_in(endpoint))
return-ENODEV;
/*
*返回对应端点能够传输的最大的数据包,鼠标的返回的最大数据包为4个字节,数据包具体内容在urb
*回调函数中有详细说明。
*/
pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress);
maxp=usb_maxpacket(dev,pipe,usb_pipeout(pipe));
/*为mouse设备结构体分配内存*/
mouse=kzalloc(sizeof(structusb_mouse),GFP_KERNEL);
/*input_dev*/
input_dev=input_allocate_device();
if(!
mouse||!
input_dev)
gotofail1;
/*
*申请内存空间用于数据传输,data为指向该空间的地址,data_dma则是这块内存空间的dma映射,
*即这块内存空间对应的dma地址。
在使用dma传输的情况下,则使用data_dma指向的dma区域,
*否则使用data指向的普通内存区域进行传输。
*GFP_ATOMIC表示不等待,GFP_KERNEL是普通的优先级,可以睡眠等待,由于鼠标使用中断传输方式,
*不允许睡眠状态,data又是周期性获取鼠标事件的存储区,因此使用GFP_ATOMIC优先级,如果不能
*分配到内存则立即返回0。
*/
mouse->data=usb_buffer_alloc(dev,8,GFP_ATOMIC,&mouse->data_dma);
if(!
mouse->data)
gotofail1;
/*
*为urb结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为0。
*申请的内存将通过下面即将见到的usb_fill_int_urb函数进行填充。
*/
mouse->irq=usb_alloc_urb(0,GFP_KERNEL);
if(!
mouse->irq)
gotofail2;
/*填充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),
"USBHIDBPMouse%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);
/*cdev是设备所属类别(classdevice)*/
input_dev->cdev.dev=&intf->dev;
/*evbit用来描述事件,EV_KEY是按键事件,EV_REL是相对坐标事件*/
input_dev->evbit[0]=BIT(EV_KEY)|BIT(EV_REL);
/*keybit表示键值,包括左键、右键和中键*/
input_dev->keybit[LONG(BTN_MOUSE)]=BIT(BTN_LEFT)|BIT(BTN_RIGHT)|BIT(BTN_MIDDLE);
/*relbit用于表示相对坐标值*/
input_dev->relbit[0]=BIT(REL_X)|BIT(REL_Y);
/*有的鼠标还有其它按键*/
input_dev->keybit[LONG(BTN_MOUSE)]|=BIT(BTN_SIDE)|BIT(BTN_EXTRA);
/*中键滚轮的滚动值*/
input_dev->relbit[0]|=BIT(REL_WHEEL);
/*input_dev的private数据项用于表示当前输入设备的种类,这里将鼠标结构体对象赋给它*/
input_dev->private=mouse;
/*填充输入设备打开函数指针*/
input_dev->open=usb_mouse_open;
/*填充输入设备关闭函数指针*/
input_dev->close=usb_mouse_close;
/*
*填充构建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;
/*向系统注册输入设备*/
input_register_device(mouse->dev);
/*
*一般在probe函数中,都需要将设备相关信息保存在一个usb_interface结构体中,以便以后通过
*usb_get_intfdata获取使用。
这里鼠标设备结构体信息将保存在intf接口结构体内嵌的设备结构体中
*的driver_data数据成员中,即intf->dev->dirver_data=mouse。
*/
usb_set_intfdata(intf,mouse);
return0;
fail2:
usb_buffer_free(dev,8,mouse->data,mouse->da