USB裝置驅動之滑鼠
1. 概述
USB滑鼠驅動程式大致分為兩大部分:USB裝置驅動部分和輸入裝置驅動部分,USB裝置驅動只起了掛接匯流排和傳輸資料的作用,而具體的裝置類型的驅動仍然是工作的主體。
USB裝置驅動部分:負責註冊USB裝置驅動,實現probe和disconnect函數,初始化驅動支援的USB滑鼠裝置類型表(id_table);初始化和提交URB;根據HID規範,滑鼠只有一個端點(不包括端點0),且屬於中斷傳輸類型。
輸入裝置驅動部分:負責註冊輸入裝置和該裝置所支援的事件類型;向輸入核心層上報滑鼠事件。
這裡有一個概念需要注意,這裡的中斷傳輸與硬體中斷那個中斷是不一樣的,這個中斷傳輸實際是靠USB hostcontrol輪詢usb device來實現的,而USBhost control對於CPU則是基於中斷的機制。拿USB滑鼠為例,USB host control對USB滑鼠不斷請求,這個請求的間隔是很短的,端點描述符中的bInterval域中指定的,當滑鼠發生過了事件之後,滑鼠會發送資料回host,這時USB
host control中斷通知CPU,於是usb_mouse_irq被調用,在usb_mouse_irq裡,就可以讀取滑鼠發回來的資料,當讀完之後,驅動再次調用usb_submit_urb發出請求,就這麼一直重複下去,一個usb滑鼠的驅動也就完成了。
我們在進行硬體訪問的時候,只需調用URB相關的介面,而不需要關心底層USB主機控制器的具體細節,因此,USB裝置也變得與平台無關,同樣的驅動可應用於不同的SOC。
2.程式碼分析
#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>/* for apple IDs */#ifdef CONFIG_USB_HID_MODULE#include "../hid-ids.h"#endif/* * Version Information */#define DRIVER_VERSION "v1.6"#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"#define DRIVER_DESC "USB HID Boot Protocol mouse driver"#define DRIVER_LICENSE "GPL"MODULE_AUTHOR(DRIVER_AUTHOR);MODULE_DESCRIPTION(DRIVER_DESC);MODULE_LICENSE(DRIVER_LICENSE);/* */struct usb_mouse {char name[128];char phys[64];struct usb_device *usbdev;struct input_dev *dev;struct urb *irq;signed char *data;dma_addr_t data_dma;};static void usb_mouse_irq(struct urb *urb){struct usb_mouse *mouse = urb->context; /* context 指向USB驅動 程式分 配 的 資料 塊*/signed char *data = mouse->data;struct input_dev *dev = mouse->dev;int 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;}/* 上報事件 */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]);input_sync(dev);/* 提交下次傳輸 */resubmit:status = usb_submit_urb (urb, GFP_ATOMIC);if (status)err ("can't resubmit intr, %s-%s/input0, status %d",mouse->usbdev->bus->bus_name,mouse->usbdev->devpath, status);}static int usb_mouse_open(struct input_dev *dev){struct usb_mouse *mouse = input_get_drvdata(dev);mouse->irq->dev = mouse->usbdev;/* 向USB核心層提交URB */if (usb_submit_urb(mouse->irq, GFP_KERNEL))return -EIO;return 0;}static void usb_mouse_close(struct input_dev *dev){struct usb_mouse *mouse = input_get_drvdata(dev);usb_kill_urb(mouse->irq);}/* USB裝置與驅動匹配上時調用 */static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id){/* 裝置描述 usb_device *//* 介面描述 usb_interface */struct usb_device *dev = interface_to_usbdev(intf); /* 介面設定描述 */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; /* 根據HID規範,滑鼠只有一個端點(不包含0號控制端點)*/if (interface->desc.bNumEndpoints != 1)return -ENODEV;/* 擷取端點0描述符 */endpoint = &interface->endpoint[0].desc;/* 根據HID規範,滑鼠唯一的端點應為中斷端點 */if (!usb_endpoint_is_int_in(endpoint))return -ENODEV;/* 產生中斷管道 */pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);/* 返回該端點能夠傳輸的最大的包長度,滑鼠的返回的最大資料包為4個位元組。*/ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));/* 建立input裝置 */mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);input_dev = input_allocate_device();if (!mouse || !input_dev)goto fail1; /* 申請記憶體空間用於資料轉送,data 為指向該空間的地址*/mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma);if (!mouse->data)goto fail1;/* 分配URB */mouse->irq = usb_alloc_urb(0, GFP_KERNEL);if (!mouse->irq)goto fail2;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), "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_dev->phys = mouse->phys;usb_to_input_id(dev, &input_dev->id);input_dev->dev.parent = &intf->dev;/* 設定輸入裝置支援的事件和事件代碼 */input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |BIT_MASK(BTN_EXTRA);input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);input_set_drvdata(input_dev, mouse);input_dev->open = usb_mouse_open;input_dev->close = usb_mouse_close;/* 初始化中斷URB */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;/* 向輸入核心層註冊輸入裝置 */error = input_register_device(mouse->dev);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;}/* USB裝置移除時調用 */static void usb_mouse_disconnect(struct usb_interface *intf){struct usb_mouse *mouse = usb_get_intfdata (intf);usb_set_intfdata(intf, NULL);if (mouse) {usb_kill_urb(mouse->irq);input_unregister_device(mouse->dev);usb_free_urb(mouse->irq);usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);kfree(mouse);}}/* USB驅動支援的滑鼠類型表 */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 */};MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);static struct usb_driver usb_mouse_driver = {.name= "usbmouse", /* 驅動名 */.probe= usb_mouse_probe, /* 捕獲函數 */.disconnect= usb_mouse_disconnect, /* 卸載函數 */.id_table= usb_mouse_id_table, /* 裝置列表 */};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;}static void __exit usb_mouse_exit(void){usb_deregister(&usb_mouse_driver);}module_init(usb_mouse_init);module_exit(usb_mouse_exit);