Linux下USB滑鼠驅動完全注釋

來源:互聯網
上載者:User
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.cview plaincopy to clipboardprint?/*   * $Id: usbmouse.c,v 1.15 2001/12/27 10:37:41 vojtech Exp $   *   *  Copyright (c) 1999-2001 Vojtech Pavlik   *   *  USB HIDBP Mouse support   */    #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>      /*   * 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];         /* USB 滑鼠是一種 USB 裝置,需要內嵌一個 USB 裝置結構體來描述其 USB 屬性 */      struct usb_device  *usbdev;       /* USB 滑鼠同時又是一種輸入裝置,需要內嵌一個輸入裝置結構體來描述其輸入裝置的屬性 */      struct input_dev *dev;         /* URB 請求包結構體,用於傳送資料 */      struct urb *irq;       /* 普通傳輸用的地址 */      signed char *data;       /* dma 傳輸用的地址 */      dma_addr_t data_dma;           };     /*   * urb 回呼函數,在完成提交 urb 後,urb 回呼函數將被調用。   * 此函數作為 usb_fill_int_urb 函數的形參,為構建的 urb 制定的回呼函數。   */  static void usb_mouse_irq(struct urb *urb)   {       /* urb 中的 context 指標用於為 USB 驅動程式儲存一些資料。比如在這個回呼函數的形參沒有傳遞在 probe中為 mouse 結構體分配的那塊記憶體的地址指標,而又需要用到那塊記憶體地區中的資料,context 指標則幫了大忙了!在填充 urb 時將 context 指標指向 mouse 結構體資料區,在這又建立一個局部 mouse 指標指向在 probe函數中為 mouse 申請的那塊記憶體,那塊記憶體儲存著非常重要資料。當 urb 通過 USB core 提交給 hc 之後,如果結果正常,mouse->data 指向的記憶體地區將儲存著滑鼠的按鍵和移動座標資訊,系統則依靠這些資訊對滑鼠的行為作出反應。mouse 中內嵌的 dev 指標,指向 input_dev 所屬於的記憶體地區*/      struct usb_mouse *mouse = urb->context;       signed char *data = mouse->data;       struct input_dev *dev = mouse->dev;       int status;         /*       * status 值為 0 表示 urb 成功返回,直接跳出迴圈把滑鼠附隨報告給輸入子系統。       * ECONNRESET 出錯資訊表示 urb 被 usb_unlink_urb 函數給 unlink 了,ENOENT 出錯資訊表示 urb 被        * usb_kill_urb 函數給 kill 了。usb_kill_urb 表示徹底結束 urb 的生命週期,而 usb_unlink_urb 則       * 是停止 urb,這個函數不等 urb 完全終止就會返回給回呼函數。這在運行中斷處理常式時或者等待某自旋鎖       * 時非常有用,在這兩種情況下是不能睡眠的,而等待一個 urb 完全停止很可能會出現睡眠的情況。       * ESHUTDOWN 這種錯誤表示 USB 主控制器驅動程式發生了嚴重的錯誤,或者提交完 urb 的一瞬間裝置被拔出。       * 遇見除了以上三種錯誤以外的錯誤,將申請重傳 urb。       */      switch (urb->status)       {       case 0:     /* success */          break;       case -ECONNRESET:   /* unlink */      case -ENOENT:       case -ESHUTDOWN:           return;       /* -EPIPE:  should clear the halt */      default:        /* error */          goto resubmit;       }         /*       * 向輸入子系統彙報滑鼠事件情況,以便作出反應。       * data 數組的第0個位元組:bit 0、1、2、3、4分別代表左、右、中、SIDE、EXTRA鍵的按下情況;       * data 數組的第1個位元組:表示滑鼠的水平位移;       * data 數組的第2個位元組:表示滑鼠的垂直位移;       * data 數組的第3個位元組:REL_WHEEL位移。       */      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]);         /*       * 這裡是用於事件同步。上面幾行是一次完整的滑鼠事件,包括按鍵資訊、絕對座標資訊和滾輪資訊,輸入子       * 系統正是通過這個同步訊號來在多個完整附隨報告中區分每一次完整附隨報告。示意如下:       * 按鍵資訊 座標位移資訊 滾輪資訊 EV_SYC | 按鍵資訊 座標位移資訊 滾輪資訊 EV_SYC ...       */      input_sync(dev);         /*       * 系統需要周期性不斷地擷取滑鼠的事件資訊,因此在 urb 回呼函數的末尾再次提交 urb 請求塊,這樣又會       * 調用新的回呼函數,周而復始。       * 在回呼函數中提交 urb 一定只能是 GFP_ATOMIC 優先順序的,因為 urb 回呼函數運行於中斷上下文中,在提       * 交 urb 過程中可能會需要申請記憶體、保持訊號量,這些操作或許會導致 USB core 睡眠,一切導致睡眠的行       * 為都是不允許的。       */  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);   }     /*   * 開啟滑鼠裝置時,開始提交在 probe 函數中構建的 urb,進入 urb 周期。   */  static int usb_mouse_open(struct input_dev *dev)   {       struct usb_mouse *mouse = dev->private;         mouse->irq->dev = mouse->usbdev;       if (usb_submit_urb(mouse->irq, GFP_KERNEL))           return -EIO;         return 0;   }     /*   * 關閉滑鼠裝置時,結束 urb 生命週期。   */  static void usb_mouse_close(struct input_dev *dev)   {       struct usb_mouse *mouse = dev->private;         usb_kill_urb(mouse->irq);   }     /*   * 驅動程式的探測函數   */  static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)   {       /*        * 介面結構體包含於裝置結構體中,interface_to_usbdev 是通過介面結構體獲得它的裝置結構體。       * usb_host_interface 是用於描述介面設定的結構體,內嵌在介面結構體 usb_interface 中。       * usb_endpoint_descriptor 是端點描述符結構體,內嵌在端點結構體 usb_host_endpoint 中,而端點       * 結構體內嵌在介面設定結構體中。       */      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;         interface = intf->cur_altsetting;         /* 滑鼠僅有一個 interrupt 類型的 in 端點,不滿足此要求的裝置均報錯 */      if (interface->desc.bNumEndpoints != 1)           return -ENODEV;         endpoint = &interface->endpoint[0].desc;       if (!usb_endpoint_is_int_in(endpoint))           return -ENODEV;         /*       * 返回對應端點能夠傳輸的最大的資料包,滑鼠的返回的最大資料包為4個位元組,資料包具體內容在 urb       * 回呼函數中有詳細說明。       */      pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);       maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));         /* 為 mouse 裝置結構體分配記憶體 */      mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);       /* input_dev */      input_dev = input_allocate_device();       if (!mouse || !input_dev)           goto fail1;         /*       * 申請記憶體空間用於資料轉送,data 為指向該空間的地址,data_dma 則是這塊記憶體空間的 dma 映射,       * 即這塊記憶體空間對應的 dma 地址。在使用 dma 傳輸的情況下,則使用 data_dma 指向的 dma 地區,       * 否則使用 data 指向的普通記憶體地區進行傳輸。       * GFP_ATOMIC 表示不等待,GFP_KERNEL 是普通的優先順序,可以睡眠等待,由於滑鼠使用中斷傳輸方式,       * 不允許睡眠狀態,data 又是周期性擷取滑鼠事件的儲存區,因此使用 GFP_ATOMIC 優先順序,如果不能       * 分配到記憶體則立即返回 0。       */      mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma);       if (!mouse->data)           goto fail1;         /*       * 為 urb 結構體申請記憶體空間,第一個參數表示等時傳輸時需要傳送包的數量,其它傳輸方式則為0。       * 申請的記憶體將通過下面即將見到的 usb_fill_int_urb 函數進行填充。        */      mouse->irq = usb_alloc_urb(0, GFP_KERNEL);       if (!mouse->irq)           goto fail2;         /* 填充 usb 裝置結構體和輸入裝置結構體 */      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 用來擷取 USB 裝置在 Sysfs 中的路徑,格式       * 為:usb-usb 匯流排號-路徑名。       */      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;       /*       * input_dev 中的 input_id 結構體,用來儲存廠商、裝置類型和裝置的編號,這個函數是將裝置描述符       * 中的編號賦給內嵌的輸入子系統結構體       */      usb_to_input_id(dev, &input_dev->id);       /* cdev 是裝置所屬類別(class device) */      input_dev->cdev.dev = &intf->dev;         /* evbit 用來描述事件,EV_KEY 是按鍵事件,EV_REL 是相對座標事件 */      input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);       /* keybit 表示索引值,包括左鍵、右鍵和中鍵 */      input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);       /* relbit 用於表示相對座標值 */      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);         /* input_dev 的 private 資料項目用於表示當前輸入裝置的種類,這裡將滑鼠結構體對象賦給它 */      input_dev->private = mouse;       /* 填充輸入裝置開啟函數指標 */      input_dev->open = usb_mouse_open;       /* 填充輸入裝置關閉函數指標 */      input_dev->close = usb_mouse_close;         /*       * 填充構建 urb,將剛才填充好的 mouse 結構體的資料填充進 urb 結構體中,在 open 中遞交 urb。       * 當 urb 包含一個即將傳輸的 DMA 緩衝區時應該設定 URB_NO_TRANSFER_DMA_MAP。USB核心使用       * transfer_dma變數所指向的緩衝區,而不是transfer_buffer變數所指向的。       * URB_NO_SETUP_DMA_MAP 用於 Setup 包,URB_NO_TRANSFER_DMA_MAP 用於所有 Data 包。       */      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;         /* 向系統註冊輸入裝置 */      input_register_device(mouse->dev);         /*       * 一般在 probe 函數中,都需要將裝置相關資訊儲存在一個 usb_interface 結構體中,以便以後通過       * usb_get_intfdata 擷取使用。這裡滑鼠裝置結構體資訊將儲存在 intf 介面結構體內嵌的裝置結構體中       * 的 driver_data 資料成員中,即 intf->dev->dirver_data = mouse。       */      usb_set_intfdata(intf, mouse);       return 0;     fail2:  usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);   fail1:  input_free_device(input_dev);       kfree(mouse);       return -ENOMEM;   }     /*   * 滑鼠裝置拔出時的處理函數   */  static void usb_mouse_disconnect(struct usb_interface *intf)   {       /* 擷取滑鼠裝置結構體 */      struct usb_mouse *mouse = usb_get_intfdata (intf);         /* intf->dev->dirver_data = NULL,將介面結構體中的滑鼠裝置指標置空。*/      usb_set_intfdata(intf, NULL);       if (mouse)       {           /* 結束 urb 生命週期 */          usb_kill_urb(mouse->irq);           /* 將滑鼠裝置從輸入子系統中登出 */          input_unregister_device(mouse->dev);           /* 釋放 urb 儲存空間 */          usb_free_urb(mouse->irq);           /* 釋放存放滑鼠事件的 data 儲存空間 */          usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);           /* 釋放存放滑鼠結構體的儲存空間 */          kfree(mouse);       }   }     /*   * usb_device_id 結構體用於表示該驅動程式所支援的裝置,USB_INTERFACE_INFO 可以用來匹配特定類型的介面,   * 這個宏的參數意思為 (類別, 子類別, 協議)。   * USB_INTERFACE_CLASS_HID 表示是一種 HID (Human Interface Device),即人機互動裝置類別;   * USB_INTERFACE_SUBCLASS_BOOT 是子類別,表示是一種 boot 階段使用的 HID;   * USB_INTERFACE_PROTOCOL_MOUSE 表示是滑鼠裝置,遵循滑鼠的協議。   */  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 驅動程式來說,第一個參數必須   * 是 usb。   */  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,   };     /*   * 驅動程式生命週期的開始點,向 USB core 註冊這個滑鼠驅動程式。   */  static int __init usb_mouse_init(void)   {       int retval = usb_register(&usb_mouse_driver);       if (retval == 0)           info(DRIVER_VERSION ":" DRIVER_DESC);       return retval;   }     /*   * 驅動程式生命週期的結束點,向 USB core 登出這個滑鼠驅動程式。   */  static void __exit usb_mouse_exit(void)   {       usb_deregister(&usb_mouse_driver);   }     module_init(usb_mouse_init);   module_exit(usb_mouse_exit); 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.