在生活、工作中經常會接觸到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 1
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;
}
/*在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/event0
0000000 4998 0000 4b9a 000c 0001 0110 0001 0000
0000010 4998 0000 4bb6 000c 0000 0000 0000 0000
0000020 4998 0000 cc2f 000e 0001 0110 0000 0000
0000030 4998 0000 cc4b 000e 0000 0000 0000 0000
0000040 4999 0000 93c0 000d 0001 0111 0001 0000
0000050 4999 0000 93db 000d 0000 0000 0000 0000
0000060 499a 0000 2fe0 0001 0001 0111 0000 0000
0000070 499a 0000 2ff2 0001 0000 0000 0000 0000
0000080 499c 0000 b23e 0006 0002 0000 00ff 0000
0000090 499c 0000 b24e 0006 0002 0001 0001 0000
00000a0 499c 0000 b252 0006 0000 0000 0000 0000
00000b0 499e 0000 cf82 0007 0001 0112 0001 0000
00000c0 499e 0000 cf9d 0007 0000 0000 0000 0000
00000d0 499e 0000 59ad 000b 0001 0112 0000 0000
00000e0 499e 0000 59c2 000b 0000 0000 0000 0000
00000f0 499f 0000 df37 0007 0002 0008 00ff 0000
0000100 499f 0000 df4e 0007 0000 0000 0000 0000
0000110 499f 0000 cacc 000d 0002 0008 00ff 0000
0000120 499f 0000 cadf 000d 0000 0000 0000 0000
事情就是這麼個事情,情況就是這麼個情況。
這就是usb設備驅動的簡單框架,後面有空在把寫操作(數據由主機發送到設備)的驅動模型以及各種傳輸類型的驅動更新下
- - 内核技術中文網 - 構建全國最權威的内核技術交流分享論壇
轉載地址:簡單分析USB設備驅動框架(秒懂~) - 圈點 - 内核技術中文網 - 構建全國最權威的内核技術交流分享論壇
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!