申明:本文章是看完韦东山老师的usb滑鼠驱动视频所写的关于usb键盘的驱动,因而假如有相关内容与其他网友相同,敬请宽恕。同时我还是想说本文只是总结自己的学习所得,同时也将自己所学到的知识写出来linux安装教程,所以假如这篇文章对你有帮助,那是我的荣幸。
在介绍驱动程序前我想向你们介绍一下usb_bus_type(usb总线驱动类),内核中有不同的总线类型,不同的总线有不同的匹配方法,如我们上面所学的platform_bus_type是使用名子来匹配的,而这儿要讲的usb_bus_type的匹配是通过id_table来匹配的,而且各类总线的匹配流程大致还是一样的。由于要想将设备和驱动通过总线联接上去就不可防止的用到了match函数。如同你要相亲你就要将你的要求都写下来,而男方也要将自己对另一半的要求写下来,之后大家双方都把各自的恳求交给婚介所,而婚介所所做的事就是将你的要求与每一个男士的恳求进行比较linux usb键盘驱动,注意这儿就用到了比较(match),当她们发觉有一个男士满足你的要求,而你也刚好满足这个男士的要求时,他才会对你说“给你匹配到了合适的男士”,而同时他也会对这位男士说“给你匹配到了合适的女士”,之后就安排大家相亲了。而我们的总线——设备——驱动模型就类似这个相亲模型。其中你是设备,这些男士是驱动,而婚介所就是总线了。
有了前面的事例,我们结合这个事例剖析一下这个匹配流程:
前面这幅图就将,usb_bus_type的框架大致勾勒下来了,下边我们详尽的说一下。如上图所示,总线模型的最主要部份就是坐落下层的总线,总线中有一个match函数,他会将通过usb_new_device向下注册的usb_interface和通过usb_register向下注册的usb_driver中的id_table一一比较(这个过程就类似于你和这些男士分别向婚介所投递个人信息),当发觉设备和驱动匹配时,他都会调用的driver中的probe函数(这就相当于当发觉你与其中一位男士匹配时才会通知大家相亲)。好多同学可能会问“两个人相亲她们匹配的可能是性格,三观,收入等等,而设备和驱动她们匹配的是哪些那?”,我们说了,不同的总线类型匹配的标准不一样,但总要有个可以匹配的吧。是的,在usb总线类型中,我们匹配的是id_table,可能好多人会问可以讲的细点吗?如同你说的相亲的时侯匹配三观,可三观太大了,可以细分一下吗?,这个是可以的我们打开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) },
{ } /* Terminating entry */
};
#define USB_INTERFACE_INFO(cl,sc,pr)
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl),
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
里面就是详尽的比较类型了,她们有插口类,插口泛型,以及插口合同,三部份组成。不过你还可以加附加条件例如:
{USB_DEVICE(设备ID,产品ID)},
这样就可以在原有的基础上缩小范围了。
而具体的代码描述为:
在hub.c的hub_port_connect_change函数中,有udev=usb_alloc_dev(hdev,hdev->bus,port1);而usb_alloc_dev就是分配一个设备结构体,而这个设备中都分配了哪些那?我们可以进去看一下,我们可以在usb_alloc_dev函数中听到这样的代码:
dev->dev.bus = &usb_bus_type;
这行代码就定义了usb_bus_type,这么我们进去瞧瞧这个总线中又定义了哪些:
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match, //非常重要的match函数
.uevent = usb_uevent,
.suspend = usb_suspend,
.resume = usb_resume,
};
我们可以从中看见一个match函数,我们在上面说过,尽管不同的总线她们匹配的数据可能不一样,而且她们的大致过程是一样的。下边我们步入usb_device_match函数中:
static int usb_device_match(struct device *dev, struct device_driver *drv)
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
通过前面的代码我们就可以晓得她们所匹配的是dev的信息和id_table的信息。
有了前面的了解,我们如今来写一个简单的usb键盘驱动,我们的目标是:当按下左键时上报键盘L,当按下右键时上报S,中键ENTRR。
从前面的目标可以看出我们须要用到输入子系统来上报键盘。从usb_bus_type中我们晓得usb的总线和设备部份早已写好,而我们可以做的就是写出驱动程序,下边我们开始写驱动程序。与其他的驱动程序一样,我们还是先搭好这个驱动程序的框架,之后在向其中填入想要做的事情的代码,而usb驱动的框架为:
1.分配/设置usb_driver结构体
2.在入口函数中注册这个结构体,在出口函数中注销这个结构体
而详尽的代码为:
static struct usb_driver usb_mouse_drv = { /* 分配设置usb_driver结构体 */
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
int usb_drv_init(void)
{
usb_register(&usb_mouse_drv); /* 注册usb_driver结构体 */
return 0;
}
void usb_drv_exit(void)
{
usb_deregister(&usb_mouse_drv);/* 注销usb_driver结构体 */
}
通过前面的代码我们可以看出,usb_driver结构体中有id_table(用于匹配设备),probe函数(当匹配成功时调用),disconnect函数(当匹配的设备离开时调用),注意:这三个是必不可少的。而其他的选项则是可以选择的。在里面我早已说过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) },
{ } /* Terminating entry */
};
而其中USB_INTERFACE_INFO是一个宏:
/**
* USB_INTERFACE_INFO - macro used to describe a class of usb interfaces
* @cl: bInterfaceClass value
* @sc: bInterfaceSubClass value
* @pr: bInterfaceProtocol value
*
* This macro is used to create a struct usb_device_id that matches a
* specific class of interfaces.
*/
#define USB_INTERFACE_INFO(cl,sc,pr)
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl),
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
通过介绍里面的注解可以看出,他提供的是插口类,插口泛型,和插口合同。所以你可以提供相应的这三项来进行匹配。同时若果你对厂家ID和设备ID有其他的要求也可以通过id_table中的USB_DEVICE选项设置。
写好id_table后设备就可以与驱动程序进行匹配了,这么我们接出来就应当写匹配成功后要步入的probe函数了。这么步入probe后我们应当干哪些那?我们要想一下自己的目的是通过按下键盘实现键盘功能,既然是键盘功能就要用到输入子系统了,这么输入子系统的框架又是哪些那?
1.分配input_dev结构体
2.设置input_dev结构体
3.注册input_dev结构体
4.硬件相关的操作
此处硬件相关的操作与往年不同,原先的按钮,触摸屏是通过读寄存器或则ADC值,而现今的硬件相关操作是在usb驱动框架中的操作,所以此处应该由usb总线驱动提供usb读写函数来进行数据传输。
下边是probe函数的代码:
int usb_mouse_probe (struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/*1 分配一个input_dev结构体 */
uk_dev = input_allocate_device();
/*2 设置 */
/*2.1 产生哪类事件 */
set_bit(EV_KEY,uk_dev->evbit); //产生按键类事件
set_bit(EV_REP,uk_dev->evbit); //产生重复类事件
/*2.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
/*3 注册 */
input_register_device(uk_dev);
/*4 硬件相关设置:通过使用USB设备总线获取读写函数 */
/* 数据传输三要素:源,目的,长度 */
/* 源:USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度 */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_buffer_alloc(dev,len,GFP_KERNEL,&usb_buf_phys);
/* 使用三要素 */
/* 分配一个urb(USB request block) */
uk_urb = usb_alloc_urb(0,GFP_KERNEL);
/* 设置使用urb */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf,
len,
usb_mouse_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用urb */
usb_submit_urb(uk_urb,GFP_KERNEL);
return 0;
}
里面的程序早已说明了input_dev的框架,只是有些朋友可能会问这代码中的第四部份是如何回事?
这就要介绍另一个特别有用的结构体USB恳求块(USBrequestblock,URB),URB是USB设备驱动中拿来描述与USB设备通讯所用的基本载体和核心数据结构linux usb键盘驱动,与网路设备驱动中的sk_buff结构体类似,是USB主机与设备之间传输数据的封装。
一个urb包含了执行usb传输所须要的所有信息。当要进行数据传输时,须要分配一个urb结构体,对其进行初始化,之后将其递交给usb核心。USB核心对urb进行解析,将控制信息递交给主机控制器linux命令行,由主机控制器负责数据到设备的传输。这时,驱动程序只需等待,当数据回传到主机控制器后,会转发给USB核心,唤起等待的驱动程序,由驱动程序完成剩下的工作。
更为具体地说,Linux中的设备驱动程序只要为每一次恳求打算一个urb结构体,之后把它填充好,就可以调用函数usb_submit_urb()递交给USB核心。之后USB核心将urb传递给USB主机控制器,最终传递给USB设备。USB设备获得urb结构体后,会解析这个结构体,并以相反的路线将数据返回给Linux内核。
注:前面的描述是从百度百科抄来的,我看他写的比较简单就抄出来分享给你们了。
写完那些我们就应当完成usb_fill_int_urb函数中的usb_mouse_irq函数了,或许有些同学可能会问这是中断函数吗?我的回答是这是中断函数,只不过这是主机控制器形成的中断而不是从机设备:
像前面这个示意图一样,主机控制器不断的查询usb设备获得数据后将数据装入buffer中,之后主机控制器还会形成一个中断进而使是里面的这个usb_mouse_irq函数被调用。
而在usb_mouse_irq函数中要做的就是将将获得的键盘值上报:
static void usb_mouse_irq(struct urb *urb)
{
static unsigned char pre_val;
if((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))){
/* 左键发生了变化 */
input_event(uk_dev,EV_KEY,KEY_L,usb_buf[0] & (1<<0) ? 1 : 0);
input_sync(uk_dev);
}
if((pre_val & (1<<1)) != (usb_buf[0] & (1<<1))){
/* 右键发生了变化 */
input_event(uk_dev,EV_KEY,KEY_S,usb_buf[0] & (1<<1) ? 1 : 0);
input_sync(uk_dev);
}
if((pre_val & (1<<2)) != (usb_buf[0] & (1<<2))){
/* 中键发生了变化 */
input_event(uk_dev,EV_KEY,KEY_ENTER,usb_buf[0] & (1<<2) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
/* 从新提交urb */
usb_submit_urb(uk_urb,GFP_KERNEL);
}
里面就是这个驱动的主要部份了,而且当设备离开时会调用相应的disconnect函数,下边我们来写disconcert函数:
void usb_mouse_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev,len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
完成disconnect函数,这个驱动程序即使完成了。
而这儿还有几篇文章是我参考过得:
ARM-Linux开发之USB驱动键盘控制:这篇文章也是按韦东山老师的形式所写的驱动
嵌入式LinuxUSB驱动开发之教你一步步编撰USB驱动程序
嵌入式linux下usb驱动开发方式--看完少走弯路:这篇文章其实文字不多,对学习usb驱动的很有帮助
嵌入式Linux驱动学习之路(二十)USB设备驱动:这篇文章是对老师上课内容的总结,并有完整的代码
LinuxUSB驱动开发(五)——USB驱动程序开发过程简单总结:这篇文章其实不是讲的usb键盘,但就usb驱动的好多的原理做了介绍
嵌入式linux下怎样使用usb鼠标和键盘
:这篇文章是一篇挺好的就是usb键盘的文章。