USB驅動——鼠標


一 . 驅動程序描述

 

struct usb_device_id 結構體如下:可把USB設備設置爲一種或一類設備。



二 . URB

URB定義:

usb 請求塊(usb request block, urb)是usb設備驅動中用來描述與usb設備通信所用的基本載體和核心數據結構,非常類似於網絡設備驅動中的sk_buff結構體,是usb主機與設備通信的電波。

  1. struct urb {  
  2.     /* private: usb core and host controller only fields in the urb */  
  3.     struct kref kref;        /* URB引用計數 */  
  4.     void *hcpriv;            /* host控制器的私有數據 */  
  5.     atomic_t use_count;        /* 當前提交計數 */  
  6.     atomic_t reject;        /* 提交失敗計數 */  
  7.     int unlinked;            /* 連接失敗代碼 */  
  8.     /* public: documented fields in the urb that can be used by drivers */  
  9.     struct list_head urb_list;    /* list head for use by the urb's 
  10.                      * current owner */  
  11.     struct list_head anchor_list;    /* the URB may be anchored */  
  12.     struct usb_anchor *anchor;  
  13.     struct usb_device *dev;     /* 指向這個 urb 要發送的目標 struct usb_device 的指針,這個變量必須在這個 urb 被髮送到 USB 核心之前被 USB 驅動初始化.*/  
  14.     struct usb_host_endpoint *ep;    /* (internal) pointer to endpoint */  
  15.     unsigned int pipe;        
  16.     int status;           
  17.     unsigned int transfer_flags;    /* 傳輸設置*/  
  18.     void *transfer_buffer;        /* 指向用於發送數據到設備(OUT urb)或者從設備接收數據(IN urb)的緩衝區指針。爲了主機控制器驅動正確訪問這個緩衝, 它必須使用 kmalloc 調用來創建, 不是在堆棧或者靜態內存中。 對控制端點, 這個緩衝區用於數據中轉*/  
  19.     dma_addr_t transfer_dma;    /* 用於以 DMA 方式傳送數據到 USB 設備的緩衝區*/  
  20.     int transfer_buffer_length;    /* transfer_buffer 或者 transfer_dma 變量指向的緩衝區大小。如果這是 0, 傳送緩衝沒有被 USB 核心所使用。對於一個 OUT 端點, 如果這個端點大小比這個變量指定的值小, 對這個 USB 設備的傳輸將被分成更小的塊,以正確地傳送數據。這種大的傳送以連續的 USB 幀進行。在一個 urb 中提交一個大塊數據, 並且使 USB 主機控制器去劃分爲更小的塊, 比以連續地順序發送小緩衝的速度快得多*/  
  21.     int actual_length;        /* 當這個 urb 完成後, 該變量被設置爲這個 urb (對於 OUT urb)發送或(對於 IN urb)接受數據的真實長度.對於 IN urb, 必須是用此變量而非 transfer_buffer_length , 因爲接收的數據可能比整個緩衝小*/  
  22.     unsigned char *setup_packet;    /* 指向控制urb的設置數據包指針.它在傳送緩衝中的數據之前被傳送(用於控制 urb)*/  
  23.     dma_addr_t setup_dma;        /* 控制 urb 用於設置數據包的 DMA 緩衝區地址,它在傳送普通緩衝區中的數據之前被傳送(用於控制 urb)*/  
  24.     int start_frame;        /* 設置或返回初始的幀數量(用於等時urb) */  
  25.     int number_of_packets;          
  26.     int interval;            
  27.     int error_count;        /* 等時urb的錯誤計數,由USB核心設置 */  
  28.     void *context;            /* 指向一個可以被USB驅動模塊設置的數據塊. 當 urb 被返回到驅動時,可在結束處理例程中使用. */  
  29.     usb_complete_t complete;    /* 結束處理例程函數指針, 當 urb 被完全傳送或發生錯誤,它將被 USB 核心調用. 此函數檢查這個 urb, 並決定釋放它或重新提交給另一個傳輸中*/  
  30.     struct usb_iso_packet_descriptor iso_frame_desc[0];  
  31.     };     



URB 處理流程

1, usb設備驅動程序創建初始化一個訪問特定usb設備特定端點的urb,並提交給usb core

2, usb core提交該urb到usb主控制器驅動程序

3, usb主控制器驅動程序根據urb描述的信息,來訪問usb設

4,當設備訪問結束後,usb主控制器驅動程序通知usb core(調用這個函數usb_complete_t complete;然後其再通知usb設備驅動程序


創建URB

struct urb *usb_alloc_urb(int iso_packets, int mem_flags)
參數:

iso_packets: urb 所包含的等時數據包的個數,若不是等時傳輸,則爲0.

mem_flags: 內存分配標識(如GFP_KERNEL),參考kmalloc

初始化URB

對於中斷urb,使用usb_fill_int_urb函數來初始化:

static inline void usb_fill_int_urb (struct urb *urb,要初始化的urb指針。
                     struct usb_device *dev,所要訪問的設備
                     unsigned int      pipe,要訪問的端點所對應的管道,使用usb_sndintpipe()或usb_rcvintpipe()創建
                     void              *transfer_buffer,要傳輸的數據緩衝區
                     int               buffer_length,緩衝區長度
                     usb_complete_t    complete_fn,當完成該urb所請求的操作時,要調用的回調函數
                     void              *context,complet_fn函數所需的上下文,通常取值爲dev
                     int               interval)  urb被調度的時間間隔

管道:驅動程序的數據緩衝區與一個端點的連接,它代表了一種在兩者之間移動數據的能力

對於批量urb,使用usb_fill_bulk_urb函數來初始化。

對於控制urb,使用usb_fill_control_urb函數來初始化。

等時urb沒有像中斷,控制和批量urb那樣的初始化函數,我們只能手動的初始化urb

提交URB

在完成urb的創建和初始化後,urb便可以通過usb_submit_urb函數來提交給usb核心:

int usb_submit_urb(struct urb *urb,gfp_t mem_flags)

參數:

urb:指向urb的指針;

mem_flags:內存分配標識,它用於告知usb核心如何分配內存緩衝區。

處理URB:

URB被提交到USB核心後,usb核心指定usb主控制器驅動程序來處理該urb,在3種情況下,urb會被認爲處理完成:

1 urb被成功發送給設備,並且設備返回成功確認。如果urb->status爲0,意味着對於一個輸出urb,數據被成功發送;對於一個輸入urb,請求的數據被成功收到。

2如果發送數據到設備或從設備接受數據時發生了錯誤,urb->status將記錄錯誤值。

3 urb被“取消”,這發生在驅動通過usb_unlink_urb()或usb_kill_urb()函數取消urb,或urb雖已提交,而usb設備被拔出的情況下

當urb處理完成後,urb完成函數將被調用。


三 . 連接過程

USB 總線引出兩個重要的鏈表!
一個 USB 總線引出兩個重要的鏈表,一個爲了, USB 設備鏈表,一個爲 USB 驅動鏈表。設備鏈表包含各種系統中的 USB 設備以及這些設備的所有接口,驅動鏈表包含 USB 設備驅動程序(usb device driver)和 USB 驅動程序(usb driver)。

 

USB 設備驅動程序(usb device driver)和 USB 驅動程序(usb driver)的區別是什麼?
USB 設備驅動程序包含 USB 設備的一些通用特性,將與所有 USB 設備相匹配。在 USB core 定義了:struct usb_device_driver usb_generic_driver。usb_generic_driver 是 USB 子系統中唯一的一個設備驅動程序對象。而 USB 驅動程序則是與接口相匹配,接口是一個完成特定功能的端點的集合。

 

設備是如何添加到設備鏈表上去的?
在設備插入 USB 控制器之後,USB core 即會將設備在系統中註冊,添加到 USB 設備鏈表上去。

 

USB 設備驅動程序(usb device driver)是如何添加到驅動鏈表上去的?
在系統啓動註冊 USB core 時,USB 設備驅動程序即將被註冊,也就添加到驅動鏈表上去了。

 

接口是如何添加到設備鏈表上去的?
在 USB 設備驅動程序和 USB 設備的匹配之後,USB core 會對設備進行配置,分析設備的結構之後會將設備所有接口都添加到設備鏈表上去。比如鼠標設備中有一個接口,USB core 對鼠標設備配置後,會將這個接口添加到設備鏈表上去。

 

USB 驅動程序(usb driver)是如何添加到驅動鏈表上去的?
在每個 USB 驅動程序的被註冊時,USB 驅動程序即會添加到驅動鏈表上去。比如鼠標驅動程序,usb_mouse_init 函數將通過 usb_register(&usb_mouse_driver) 將鼠標驅動程序註冊到 USB core 中,然後就添加到驅動鏈表中去了。其中 usb_mouse_driver 是描述鼠標驅動程序的結構體。

 

已配置狀態(configured status)之後話
當鼠標的設備、接口都添加到設備鏈表,並且鼠標驅動程序也添加到驅動鏈表上去了,系統就進入一種叫做已配置(configured)的狀態。要達到已配置狀態,將經歷複雜的過程,USB core 爲 USB 設備奉獻着無怨無悔。在這個過程中,系統將會建立起該設備的的設備、配置、接口、設置、端點的描述信息,它們分別被 usb_device、usb_configuration、usb_interface、usb_host_interface、usb_host_endpoint 結構體描述。
設備達到已配置狀態後,首先當然就要進行 USB 驅動程序和相應接口的配對,對於鼠標設備來說則是鼠標驅動程序和鼠標中的接口的配對。USB core 會調用 usb_device_match 函數,通過比較設備中的接口信息和 USB 驅動程序中的 id_table,來初步決定該 USB 驅動程序是不是跟相應接口相匹配。通過這一道關卡後,USB core 會認爲這個設備應該由這個驅動程序負責。
然而,僅僅這一步是不夠的,接着,將會調用 USB 驅動程序中的 probe 函數對相應接口進行進一步檢查。如果該驅動程序確實適合設備接口,對設備做一些初始化工作,分配 urb 準備數據傳輸。
當鼠標設備在用戶空間打開時,將提交 probe 函數構建的 urb 請求塊,urb 將開始爲傳送數據而忙碌了。urb 請求塊就像一個裝東西的“袋子”,USB 驅動程序把“空袋子”提交給 USB core,然後再交給主控制器,主控制器把數據放入這個“袋子”後再將裝滿數據的“袋子”通過 USB core 交還給 USB 驅動程序,這樣一次數據傳輸就完成了。


四 . usbmouse . c

  1.   
  2. /* 
  3.  * 鼠標結構體,用於描述鼠標設備。 
  4.  */  
  5. struct usb_mouse   
  6. {  
  7.     /* 鼠標設備的名稱,包括生產廠商、產品類別、產品等信息 */  
  8.     char name[128];   
  9.     /* 設備節點名稱 */  
  10.     char phys[64];    
  11.     /* USB 鼠標是一種 USB 設備,需要內嵌一個 USB 設備結構體來描述其 USB 屬性 */  
  12.     struct usb_device *usbdev;  
  13.     /* USB 鼠標同時又是一種輸入設備,需要內嵌一個輸入設備結構體來描述其輸入設備的屬性 */  
  14.     struct input_dev *dev;    
  15.     /* URB 請求包結構體,用於傳送數據 */  
  16.     struct urb *irq;  
  17.     /* 普通傳輸用的地址 */  
  18.     signed char *data;  
  19.     /* dma 傳輸用的地址 */  
  20.     dma_addr_t data_dma;          
  21. };  
  22.  


下面我們分析下USB鼠標驅動,鼠標輸入HID類型,其數據傳輸採用中斷URB,鼠標端點類型爲IN。好了,我們先看看這個驅動的模塊加載部分。

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;

}

模塊加載部分仍然是調用usb_register註冊USB驅動,我們跟蹤看看被註冊的usb_mouse_driver

static struct usb_driver usb_mouse_driver = {

.name = "usbmouse", //驅動名

.probe = usb_mouse_probe, //探測

.disconnect = usb_mouse_disconnect,

.id_table = usb_mouse_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) },

{ }

};

再細細看看USB_INTERFACE_INFO宏的定義

#define USB_INTERFACE_INFO(cl, sc, pr) \

.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \

.bInterfaceClass = (cl), \

.bInterfaceSubClass = (sc), \

.bInterfaceProtocol = (pr)

根據宏,我們知道,我們設置的支持項包括接口類,接口子類,接口協議三個匹配項。

好了,我們主要看看usb_driver中定義的probe函數

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

struct usb_device *dev = interface_to_usbdev(intf); //由接口獲取usb_device

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; //獲取設置

if (interface->desc.bNumEndpoints != 1) //鼠標端點只有1

return -ENODEV;

endpoint = &interface->endpoint[0].desc; //獲得端點描述符

if (!usb_endpoint_is_int_in(endpoint)) //檢查該端點是否是中斷輸入端點

return -ENODEV;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //建立中斷輸入端點

//返回端點能傳輸的最大的數據包,鼠標的返回的最大數據包爲4個字節

maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); //分配mouse結構體

input_dev = input_allocate_device(); //分配input設備空間

if (!mouse || !input_dev)

goto fail1;

mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma); //分配緩衝區

if (!mouse->data)

goto fail1;

mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //分配urb

if (!mouse->irq)

goto fail2;

mouse->usbdev = dev; //填充mouseusb_device結構體

mouse->dev = input_dev; //填充mouse input結構體

if (dev->manufacturer) //拷貝廠商ID

strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

if (dev->product) { //拷貝產品ID

if (dev->manufacturer)

strlcat(mouse->name, " ", sizeof(mouse->name));

strlcat(mouse->name, dev->product, sizeof(mouse->name));

}

if (!strlen(mouse->name)) //拷貝產品ID

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結構體

input_dev->phys = mouse->phys; //將鼠標設備節點名賦給內嵌input結構體

usb_to_input_id(dev, &input_dev->id); //usb_driver的支持項拷貝給input

input_dev->dev.parent = &intf->dev;

//evbit表明支持按鍵事件(EV_KEY)和相對座標事件(EV_REL)

input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

//keybit表明按鍵值包括左鍵、右鍵和中鍵

input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |

BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);

//relbit表明相對座標事件值包括X座標和Y座標

input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);

//keybit表明除了左鍵、右鍵和中鍵,還支持其他按鍵

input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |

BIT_MASK(BTN_EXTRA);

//relbit表明除了X座標和Y座標,還支持中鍵滾輪的滾動值

input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);

input_set_drvdata(input_dev, mouse); //mouse設置爲input的私有數據

input_dev->open = usb_mouse_open; //input設備的open

input_dev->close = usb_mouse_close;

usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, (maxp > 8 ? 8 : maxp),

usb_mouse_irq, mouse, endpoint->bInterval); //填充urb

mouse->irq->transfer_dma = mouse->data_dma;

mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //使用transfer_dma

error = input_register_device(mouse->dev); //註冊input設備

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;

}

其實上面這個probe主要是初始化usb設備和input設備,終極目標是爲了完成urb的提交和input設備的註冊。由於註冊爲input設備類型,那麼當用戶層open打開設備時候,最終會調用input中的open實現打開,我們看看inputopen的實現

static int usb_mouse_open(struct input_dev *dev)

{

struct usb_mouse *mouse = input_get_drvdata(dev); //獲取私有數據

mouse->irq->dev = mouse->usbdev; //獲取urb指針

if (usb_submit_urb(mouse->irq, GFP_KERNEL)) //提交urb

return -EIO;

return 0;

}

好了,當用戶層open打開這個USB鼠標後,我們就已經將urb提交給了USB核心,那麼根據USB數據處理流程知道,當處理完畢後,USB核心會通知USB設備驅動程序,這裏我們是響應中斷服務程序,這就相當於該URB的回調函數。我們在提交urb時候定義了中斷服務程序usb_mouse_irq,我們跟蹤看看

static void usb_mouse_irq(struct urb *urb)

{

struct usb_mouse *mouse = urb->context;

signed char *data = mouse->data;

struct input_dev *dev = mouse->dev;

int status;

switch (urb->status) {

case 0: //成功

break;

case -ECONNRESET: //未連接

case -ENOENT:

case -ESHUTDOWN:

return;

default:

goto resubmit; //數據處理沒成功,重新提交urb

}

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); //鼠標SIDE

input_report_key(dev, BTN_EXTRA, data[0] & 0x10); //鼠標EXTRA

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); //再次提交urb,等待下次響應

if (status)

err ("can't resubmit intr, %s-%s/input0, status %d",

mouse->usbdev->bus->bus_name,

mouse->usbdev->devpath, status);

}

根據上面的中斷服務程序,我們應該知道,系統是週期性地獲取鼠標的事件信息,因此在URB回調函數的末尾再次提交URB請求塊,這樣又會調用新的回調函數,周而復始。在回調函數中提交URB只能是GFP_ATOMIC優先級,因爲URB回調函數運行於中斷上下文中禁止導致睡眠的行爲。而在提交URB過程中可能會需要申請內存、保持信號量,這些操作或許會導致USB內核睡眠。

最後我們再看看這個驅動的私有數據mouse的定義

struct usb_mouse {

char name[128]; //名字

char phys[64]; //設備節點

struct usb_device *usbdev; //內嵌usb_device設備

struct input_dev *dev; //內嵌input_dev設備

struct urb *irq; //urb結構體

signed char *data; //transfer_buffer緩衝區

dma_addr_t data_dma; // transfer _dma緩衝區

};

在上面這個結構體中,每一個成員的作用都應該很清楚了,尤其最後兩個的使用區別和作用,前面也已經說過。

如果最終需要測試這個USB鼠標驅動,需要在內核中配置USB支持、對HID接口的支持、對OHCI HCD驅動的支持。另外,將驅動移植到開發板之後,由於採用的是input設備模型,所以還需要開發板帶LCD屏才能測試。



發佈了9 篇原創文章 · 獲贊 8 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章