在生活、工作中经常会接触到USB设备,如鼠标、键盘、摄像头、可移动硬盘、扫码枪等。这些设备通过USB接口连接到电脑上后,电脑会立刻提示“检测到新硬件...”、安装驱动等。这里需要强调下USB设备使用的是USB总线,window或Linux内核中都会自带usb总线驱动,所以接上USB设备后,主机能够立刻检测到,提醒需要安装设备驱动是指安装USB设备驱动。
USB设备驱动使用USB总线,所以很多操作由USB总线驱动帮我们完成了,我们只需要的按照总线、设备、驱动框架来实现USB设备的驱动既可。USB设备数据的读写操作由总线驱动现在,我们可以直接使用总线读取到的数据,然后解析这些数据的含义、再进行相关的操作就可以了(这里需要注意的一点是USB总线驱动只提供USB设备的读写操作函数,这函数是通用的,即里面的数据的含义总线驱动并不知道)。
USB设备驱动的框架图下,具体的代码可以参考内核中的/drivers/hid/usbhid/usbmouse.c
分配/设置usb_driver结构体,填充里面的.id_table、.probe、.disconnect成员。
调用usb_register把usb_driver结构体注册到内核中。
static struct usb_driver usbtouch_driver 电脑= {.name= "myusb",.probe= usbtouch_probe,.disconnect= usbtouch_disconnect,.id_table= usbtouch_devices,}; static int __init usbtouch_init(void){return usb_register(&usbtouch_driver);} static void __exit usbtouch_cleanup(void){usb_deregister(&usbtouch_driver);}
struct usb_driver中的id_table成员是用与和usb设备进行匹配的选项,表示这个驱动支持的设备。
.probe成员为函数指针,就是在设备和驱动匹配成功的时候执行。
.disconnect成员也为函数指针,在设备和驱动关联成功后再断开执行,如拔了USB设备或卸载USB设备驱动。
更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取.
电脑Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
.probe函数中我们需要处理的事情如下(这里以鼠标为例子):
创建input_dev设备
设置input_dev支持的类,需要支持按键类、相对位移类。
设置input_dev支持的事件,能够产生左、中、右、以及侧边附加按键事件,还有xy方向上的相对位移事件和滚轮滑动事件。
注册input_dev到input输入子系统中。
硬件相关的操作,不同的设备的操作存在差异
USB总线为组从结构,USB总线驱动只能轮询去获取USB设备上的数据,USB设备不能主动通知USB主机传输数据。在USB驱动中需要构建urb(usb request block)结构体,然后填充使用。urb中要指定USB设备数据存放到主机上的物理地址以及虚拟地址,还有USB数据传输的方向、长度,和设备通信的端点等。
使用usb_alloc_urb()函数分配urb结构体,结束后使用usb_free_urb()释放这个结构体。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)/*用于分配urb结构体给usb驱动使用*/ /* If the driver want to use this urb for interrupt, control, or bulk * endpoints, pass '0' as the number of iso packets. * The driver must call usb_free_urb() when it is finished with the urb.*/
USB设备的数据要存放在主机上的什么地方,USB设备驱动中需要指明。
void *usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma)/*用于分配usb缓存,用于存放读写的数据*//*@dma: used to return DMA address of buffer 这里是内存的物理地址*函数返回的是虚拟地址*/
找到对应的设备端点和数据长度
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);/*pipe为源地址*/len = endpoint->wMaxPacketSize;/*len 为一次传输的数据的大小,即包的长度*/
现在数据传输的三要素(源、长度、目的)都确定了,剩下的就是要进行填充urb结构体,即告诉主机数据传输的三要素。
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_fill_int_urb - macro to help initialize a interrupt urb * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe * @transfer_buffer: pointer to the transfer buffer * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * @interval: what to set the urb interval to, encoded like *the endpoint descriptor's bInterval value. */mouse->irq->transfer_dma = mouse->data_dma;mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
调用usb_fill_int_urb(),其中complete_fn相当于中断函数,即主机读取完数据会放到指定的内存data_dma中。然后通知CPU处理这些数据,这些数据只有USB设备驱动程序知道其含义。所以要在这个完成函数中处理这些数据,如上报。
最后就是提交usr请求块了。
usb_submit_urb(urb, GFP_ATOMIC);
到这里基本上就结束了,剩下的就是disconnect函数的操作。这个函数和.probe完全相反,申请了的资源需要在这里释放。
看到这里是不是感觉USB驱动很简单啊!框架确实很简单!很简单!很简单!重要的事情要说三遍,要相信你们的实力。
下面的代码是我在ARM板子上的针对usb鼠标写的。
#include <linux/module.h>#include <linux/kernel.h>#include <linux/usb/input.h>#include <linux/hid.h>#include <linux/slab.h> /*USB设备驱动可以使用input输入子系统框架来完成数据的读入即上报*和传统的input子系统存在差异,传统的input输入子系统是在中断函数里面获取数据然后input_event上报数据*在usb设备中,数据的读取由usb总线驱动完成,这个可以直接使用urb模块来实现。然后再urb的中断中上报数据*/ static struct input_dev *input_dev;static struct urb *urb;static dma_addr_t data_dma;static unsigned char *data;static unsigned len; static void usbtouch_irq(struct urb *urb){#if 1int status; switch (urb->status) {case 0:/* success */break;case -ECONNRESET:/* unlink */case -ENOENT:case -ESHUTDOWN:return;/* -EPIPE: should clear the halt */default:/* error */goto resubmit;}/*在usb设备驱动程序中,需要对数据进行解析。即usb总线获取到数据不会解析数据的含义*需要在usb设备驱动中进行解析数据,我的usb数百data[1]为按键的值*data[2]为鼠标在x方向上的相对位移值,data[3]为鼠标在y方向上的相对位移值*data[4]为鼠标滚轮滑动产生的值*/input_report_key(input_dev, BTN_LEFT, data[1] & 0x01);input_report_key(input_dev, BTN_RIGHT, data[1] & 0x02);input_report_key(input_dev, BTN_MIDDLE, data[1] & 0x04);input_report_key(input_dev, BTN_SIDE, data[0] & 0x08);input_report_key(input_dev, BTN_EXTRA, data[0] & 0x10); input_report_rel(input_dev, REL_X, data[2]);input_report_rel(input_dev, REL_Y, data[3]);input_report_rel(input_dev, REL_WHEEL, data[4]); input_sync(input_dev);resubmit:/*上报后,重新提交urb请求*/status = usb_submit_urb (urb, GFP_ATOMIC);#else/*打印USB总线驱动获取到的数据,分析数据。解析其数据含义*/int i =0,status;for(i = 0; i < len;i++){printk("%x ",data[i]);}printk("\n");status = usb_submit_urb (urb, GFP_ATOMIC);#endif} static int usbtouch_probe(struct usb_interface *intf,const struct usb_device_id *id){int pipe;struct usb_device *udev = interface_to_usbdev(intf);struct usb_host_interface *interface;struct usb_endpoint_descriptor *endpoint;interface = intf->cur_altsetting;/*除端点0外的第一个端点,并不是指设备接口配置中的端点0*/endpoint = &interface->endpoint[0].desc;/*1.分配一个input_dev结果体*/input_dev = input_allocate_device();if(!input_dev){return -1;}/*2.设置结构体中的支持的类以及事件。鼠标要支持案件类和相对位移类事件*/input_dev->name = "usb-mouse";input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);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);/*3.注册input_dev到系统中*/input_register_device(input_dev);/*4.硬件相关的操作。主要是usb数据的获取以及上报*/urb=usb_alloc_urb(0, GFP_KERNEL);/*传输数据的来源,设备、端点、方向等组合*/pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);/*和设备驱动交互一次传输的数据长度*/len = endpoint->wMaxPacketSize;/*USB总线驱动获取到的数据存放的内存*/data = usb_buffer_alloc(udev, len,GFP_ATOMIC, &data_dma);urb->dev = udev;urb->transfer_dma = data_dma;urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;/*上下文参数(这里使用的NULL),可以用来给完成函数提供参数*/usb_fill_int_urb(urb, udev,pipe,data, len,usbtouch_irq, NULL, endpoint->bInterval);/*提交urb请求*/ usb_submit_urb(urb, GFP_ATOMIC); return 0;} static void usbtouch_disconnect(struct usb_interface *intf){usb_kill_urb(urb);input_unregister_device(input_dev);usb_free_urb(urb);usb_buffer_free(interface_to_usbdev(intf), len, data,data_dma);input_free_device(input_dev);}/*定义设备驱动支持的设备*/static struct usb_device_id usbtouch_devices [] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) },{ }/* Terminating entry */}; static struct usb_driver usbtouch_driver = {.name= "myusb",.probe= usbtouch_probe,.disconnect= usbtouch_disconnect,.id_table= usbtouch_devices,}; static int __init usbtouch_init(void){return usb_register(&usbtouch_driver);} static void __exit usbtouch_cleanup(void){usb_deregister(&usbtouch_driver);} module_init(usbtouch_init);module_exit(usbtouch_cleanup); MODULE_LICENSE("GPL");
测试如下图,按键和移动鼠标都能产生相应的事件码。这里/dev/event0的格式就不说了,要是不太清楚的可以参考input输入子系统。
usb驱动设备使用了input输入子系统框架,这个和之前的输入子系统(按键、触摸屏)之类的存在一点差异。之前的都是在中断服务函数中直接获取到数据然后调用input_event上报,而USB设备驱动中的数据来源是USB总线驱动,在完成函数中调用input_event上报数据。
# hexdump /dev/event00000000 4998 0000 4b9a 000c 0001 0110 0001 00000000010 4998 0000 4bb6 000c 0000 0000 0000 00000000020 4998 0000 cc2f 000e 0001 0110 0000 00000000030 4998 0000 cc4b 000e 0000 0000 0000 00000000040 4999 0000 93c0 000d 0001 0111 0001 00000000050 4999 0000 93db 000d 0000 0000 0000 00000000060 499a 0000 2fe0 0001 0001 0111 0000 00000000070 499a 0000 2ff2 0001 0000 0000 0000 00000000080 499c 0000 b23e 0006 0002 0000 00ff 00000000090 499c 0000 b24e 0006 0002 0001 0001 000000000a0 499c 0000 b252 0006 0000 0000 0000 000000000b0 499e 0000 cf82 0007 0001 0112 0001 000000000c0 499e 0000 cf9d 0007 0000 0000 0000 000000000d0 499e 0000 59ad 000b 0001 0112 0000 000000000e0 499e 0000 59c2 000b 0000 0000 0000 000000000f0 499f 0000 df37 0007 0002 0008 00ff 00000000100 499f 0000 df4e 0007 0000 0000 0000 00000000110 499f 0000 cacc 000d 0002 0008 00ff 00000000120 499f 0000 cadf 000d 0000 0000 0000 0000
事情就是这么个事情,情况就是这么个情况。
这就是usb设备驱动的简单框架,后面有空在把写操作(数据由主机发送到设备)的驱动模型以及各种传输类型的驱动更新下
- - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
转载地址:简单分析USB设备驱动框架(秒懂~电脑) - 圈点 - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
电脑 电脑