編寫USB 驅動程序

轉自:http://blog.csdn.NET/zqixiao_09/article/details/50986965

    前面學習了USB驅動的一些基礎概念與重要的數據結構,那麼究竟如何編寫一個USB 驅動程序呢?編寫與一個USB設備驅動程序的方法和其他總線驅動方式類似,驅動程序把驅動程序對象註冊到USB子系統中,稍後再使用製造商和設備標識來判斷是否安裝了硬件。當然,這些製造商和設備標識需要我們編寫進USB 驅動程序中。

  USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和I2C 總線設備驅動編寫一樣,所有的USB驅動程序都必須創建的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。但這是個外殼,只是實現設備和總線的掛接,具體的USB 設備是什麼樣的,如何實現的,比如一個字符設備,我們還需填寫相應的文件操作接口 ,下面我們從外到裏進行剖析,學習如何搭建這樣的一個USB驅動外殼框架:


一、註冊USB驅動程序

  Linux的設備驅動,特別是這種hotplug的USB設備驅動,會被編譯成模塊,然後在需要時掛在到內核。所以USB驅動和註冊與正常的模塊註冊、卸載是一樣的,下面是USB驅動的註冊與卸載:
  1. static int __init usb_skel_init(void)     
  2. {     
  3.      int result;     
  4.      /* register this driver with the USB subsystem */     
  5.      result = usb_register(&skel_driver);     
  6.      if (result)     
  7.          err("usb_register failed. Error number %d", result);     
  8.     
  9.      return result;     
  10. }     
  11.     
  12. static void __exit usb_skel_exit(void)     
  13. {     
  14.      /* deregister this driver with the USB subsystem */     
  15.      usb_deregister(&skel_driver);     
  16. }     
  17.     
  18. module_init (usb_skel_init);     
  19. module_exit (usb_skel_exit);     
  20. MODULE_LICENSE("GPL");  
  1. static int __init usb_skel_init(void)     
  2. {     
  3.      int result;     
  4.      /* register this driver with the USB subsystem */     
  5.      result = usb_register(&skel_driver);     
  6.      if (result)     
  7.          err("usb_register failed. Error number %d", result);     
  8.     
  9.      return result;     
  10. }     
  11.     
  12. static void __exit usb_skel_exit(void)     
  13. {     
  14.      /* deregister this driver with the USB subsystem */     
  15.      usb_deregister(&skel_driver);     
  16. }     
  17.     
  18. module_init (usb_skel_init);     
  19. module_exit (usb_skel_exit);     
  20. MODULE_LICENSE("GPL");  

      USB設備驅動的模塊加載函數通用的方法是在I2C設備驅動的模塊加載函數中使用usb_register(struct *usb_driver)函數添加usb_driver的工作,而在模塊卸載函數中利用usb_deregister(struct *usb_driver)做相反的工作。 對比I2C設備驅動中的 i2c_add_driver(&i2c_driver)i2c_del_driver(&i2c_driver)

    struct usb_driver是USB設備驅動,我們需要實現其成員函數:

  1. static struct usb_driver skel_driver = {     
  2.      .owner = THIS_MODULE,      
  3.      .name = "skeleton",    
  4.      .id_table = skel_table,         
  5.      .probe = skel_probe,       
  6.      .disconnect = skel_disconnect,       
  7. };      
  1. static struct usb_driver skel_driver = {     
  2.      .owner = THIS_MODULE,      
  3.      .name = "skeleton",    
  4.      .id_table = skel_table,         
  5.      .probe = skel_probe,       
  6.      .disconnect = skel_disconnect,       
  7. };      
從代碼看來,usb_driver需要初始化五個字段:

模塊的所有者 THIS_MODULE
模塊的名字  skeleton
probe函數   skel_probe
disconnect函數skel_disconnect

id_table

    最重要的當然是probe函數與disconnect函數,這個在後面詳細介紹,先談一下id_table:

    id_table 是struct usb_device_id 類型,包含了一列該驅動程序可以支持的所有不同類型的USB設備。如果沒有設置該變量,USB驅動程序中的探測回調該函數將不會被調用。對比I2C中struct i2c_device_id *id_table,一個驅動程序可以對應多個設備,i2c 示例:

  1. static const struct i2c_device_id mpu6050_id[] = {      
  2.     { "mpu6050", 0},      
  3.     {}      
  4. };    
  1. static const struct i2c_device_id mpu6050_id[] = {      
  2.     { "mpu6050", 0},      
  3.     {}      
  4. };    

    usb子系統通過設備的production ID和vendor ID的組合或者設備的class、subclass跟protocol的組合來識別設備,並調用相關的驅動程序作處理。我們可以看看這個id_table到底是什麼東西:

  1. static struct usb_device_id skel_table [] = {       
  2.      { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },       
  3.      { }                    /* Terminating entry */       
  4. };       
  5.       
  6. MODULE_DEVICE_TABLE (usb, skel_table);     
  1. static struct usb_device_id skel_table [] = {       
  2.      { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },       
  3.      { }                    /* Terminating entry */       
  4. };       
  5.       
  6. MODULE_DEVICE_TABLE (usb, skel_table);     
   MODULE_DEVICE_TABLE的第一個參數是設備的類型,如果是USB設備,那自然是usb。後面一個參數是設備表這個設備表的最後一個元素是空的,用於標識結束。代碼定義了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當有一個設備接到集線器時,usb子系統就會檢查這個設備的vendor ID和product ID,如果它們的值是0xfff0時,那麼子系統就會調用這個skeleton模塊作爲設備的驅動。

   當USB設備接到USB控制器接口時,usb_core就檢測該設備的一些信息,例如生產廠商ID和產品的ID,或者是設備所屬的class、subclass跟protocol,以便確定應該調用哪一個驅動處理該設備
  
 

     我們下面所要做的就是對probe函數與disconnect函數的填充了,但是在對probe函數與disconnect函數填充之前,有必要先學習三個重要的數據結構,這在我們後面probe函數與disconnect函數中有很大的作用:

二、USB驅動程序中重要數據結構

1、usb-skeleton

       usb-skeleton 是一個局部結構體,用於與端點進行通信。下面先看一下Linux內核源碼中的一個usb-skeleton(就是usb驅動的骨架咯),其定義的設備結構體就叫做usb-skel:

  1. struct usb_skel {     
  2.      struct usb_device *udev;                 /* the usb device for this device */     
  3.      struct usb_interface  *interface;            /* the interface for this device */     
  4.      struct semaphore limit_sem;         /* limiting the number of writes in progress */     
  5.      unsigned char *bulk_in_buffer;     /* the buffer to receive data */     
  6.      size_t         bulk_in_size;                  /* the size of the receive buffer */     
  7.      __u8          bulk_in_endpointAddr;        /* the address of the bulk in endpoint */     
  8.      __u8          bulk_out_endpointAddr;      /* the address of the bulk out endpoint */     
  9.      struct kref   kref;     
  10. };    
  1. struct usb_skel {     
  2.      struct usb_device *udev;                 /* the usb device for this device */     
  3.      struct usb_interface  *interface;            /* the interface for this device */     
  4.      struct semaphore limit_sem;         /* limiting the number of writes in progress */     
  5.      unsigned char *bulk_in_buffer;     /* the buffer to receive data */     
  6.      size_t         bulk_in_size;                  /* the size of the receive buffer */     
  7.      __u8          bulk_in_endpointAddr;        /* the address of the bulk in endpoint */     
  8.      __u8          bulk_out_endpointAddr;      /* the address of the bulk out endpoint */     
  9.      struct kref   kref;     
  10. };    

他擁有:

描述usb設備的結構體udev
一個接口interface
用於併發訪問控制的semaphore(信號量) limit_sem
用於接收數據的緩衝bulk_in_buffer
用於接收數據的緩衝尺寸bulk_in_size
批量輸入端口地址bulk_in_endpointAddr
批量輸出端口地址bulk_out_endpointAddr

內核使用的引用計數器


     從開發人員的角度看,每一個usb設備有若干個配置(configuration)組成,每個配置又可以有多個接口(interface)(我理解就是USB設備的一項功能),每個接口又有多個設置,而接口本身可能沒有端點或者多個端點(end point)

2、USB 接口數據結構 struct usb_interface

  1. struct usb_interface    
  2. {             
  3.          struct usb_host_interface *altsetting;     
  4.          struct usb_host_interface *cur_altsetting;          
  5.          unsigned num_altsetting;             
  6.          int minor;                          
  7.          enum usb_interface_condition condition;             
  8.          unsigned is_active:1;               
  9.          unsigned needs_remote_wakeup:1;      
  10.          struct device dev;                  
  11.          struct device *usb_dev;             
  12.          int pm_usage_cnt;                   
  13. };    
  1. struct usb_interface    
  2. {             
  3.          struct usb_host_interface *altsetting;     
  4.          struct usb_host_interface *cur_altsetting;          
  5.          unsigned num_altsetting;             
  6.          int minor;                          
  7.          enum usb_interface_condition condition;             
  8.          unsigned is_active:1;               
  9.          unsigned needs_remote_wakeup:1;      
  10.          struct device dev;                  
  11.          struct device *usb_dev;             
  12.          int pm_usage_cnt;                   
  13. };    

       在邏輯上,一個USB設備的功能劃分是通過接口來完成的。比如說一個USB揚聲器,可能會包括有兩個接口:一個用於鍵盤控制,另外一個用於音頻流傳輸。而事實上,這種設備需要用到不同的兩個驅動程序來操作,一個控制鍵盤,一個控制音頻流。但也有例外,比如藍牙設備,要求有兩個接口,第一用於ACL跟EVENT的傳輸,另外一個用於SCO鏈路,但兩者通過一個驅動控制。在Linux上,接口使用struct usb_interface來描述,以下是該結構體中比較重要的字段:

a -- struct usb_host_interface *altsetting(注意不是usb_interface)

       其實據我理解,他應該是每個接口的設置,雖然名字上有點奇怪。該字段是一個設置的數組(一個接口可以有多個設置),每個usb_host_interface都包含一套由struct usb_host_endpoint定義的端點配置。但這些配置次序是不定的。

b -- struct usb_host_interface *cur_altsetting

       當前活動的設置,指向altsetting數組中的一個

struct usb_host_interface數據結構:

  1. struct usb_host_interface     
  2. {    
  3.          struct usb_interface_descriptor desc;//usb描述符,主要有四種usb描述符,設備描述符,配置描述符,接口描述符和端點描述符,協議裏規定一個usb設備是必須支持這四大描述符的。    
  4.                                  //usb描述符放在usb設備的eeprom裏邊    
  5.          /* array of desc.bNumEndpoint endpoints associated with this  
  6.           * interface setting. these will be in no particular order.  
  7.           */    
  8.          struct usb_host_endpoint *endpoint;//這個設置所使用的端點    
  9.     
  10.          char *string;           /* iInterface string, if present */    
  11.          unsigned char *extra;   /* Extra descriptors */關於額外描述符    
  12.          int extralen;    
  13. };    
  1. struct usb_host_interface     
  2. {    
  3.          struct usb_interface_descriptor desc;//usb描述符,主要有四種usb描述符,設備描述符,配置描述符,接口描述符和端點描述符,協議裏規定一個usb設備是必須支持這四大描述符的。    
  4.                                  //usb描述符放在usb設備的eeprom裏邊    
  5.          /* array of desc.bNumEndpoint endpoints associated with this  
  6.           * interface setting. these will be in no particular order.  
  7.           */    
  8.          struct usb_host_endpoint *endpoint;//這個設置所使用的端點    
  9.     
  10.          char *string;           /* iInterface string, if present */    
  11.          unsigned char *extra;   /* Extra descriptors */關於額外描述符    
  12.          int extralen;    
  13. };    

c -- unsigned num_altstting

      可選設置的數量,即altsetting所指數組的元素個數

d -- int minor

     當捆綁到該接口的USB驅動程序使用USB主設備號時,USB core分配的次設備號。僅在成功調用usb_register_dev之後纔有效。  


3、USB 端點 struct usb_host_endpoint

      Linux中用struct usb_host_endpoint 來描述USB端點

  1. struct usb_host_endpoint     
  2. {    
  3.          struct usb_endpoint_descriptor desc;    
  4.          struct list_head                urb_list;//端點要處理的urb隊列.urb是usb通信的主角,設備中的每個端點都可以處理一個urb隊列.要想和你的usb通信,就得創建一個urb,並且爲它賦好值,    
  5.                                    //交給咱們的usb core,它會找到合適的host controller,從而進行具體的數據傳輸    
  6.          void                            *hcpriv;//這是提供給HCD(host controller driver)用的    
  7.          struct ep_device                *ep_dev;        /* For sysfs info */    
  8.     
  9.          unsigned char *extra;   /* Extra descriptors */    
  10.          int extralen;    
  11. };    
  1. struct usb_host_endpoint     
  2. {    
  3.          struct usb_endpoint_descriptor desc;    
  4.          struct list_head                urb_list;//端點要處理的urb隊列.urb是usb通信的主角,設備中的每個端點都可以處理一個urb隊列.要想和你的usb通信,就得創建一個urb,並且爲它賦好值,    
  5.                                    //交給咱們的usb core,它會找到合適的host controller,從而進行具體的數據傳輸    
  6.          void                            *hcpriv;//這是提供給HCD(host controller driver)用的    
  7.          struct ep_device                *ep_dev;        /* For sysfs info */    
  8.     
  9.          unsigned char *extra;   /* Extra descriptors */    
  10.          int extralen;    
  11. };    

     每個usb_host_endpoint中包含一個struct usb_endpoint_descriptor結構體,當中包含該端點的信息以及設備自定義的各種信息,這些信息包括:

a -- bEndpointAddress(b for byte)

       8位端點地址,其地址還隱藏了端點方向的信息(之前說過,端點是單向的),可以用掩碼USB_DIR_OUT和USB_DIR_IN來確定。

b -- bmAttributes

       端點的類型,結合USB_ENDPOINT_XFERTYPE_MASK可以確定端點是USB_ENDPOINT_XFER_ISOC(等時)、USB_ENDPOINT_XFER_BULK(批量)還是USB_ENDPOINT_XFER_INT(中斷)。

c -- wMaxPacketSize

       端點一次處理的最大字節數。發送的BULK包可以大於這個數值,但會被分割傳送。

d -- bInterval

       如果端點是中斷類型,該值是端點的間隔設置,以毫秒爲單位


三、探測和斷開函數分析

        USB驅動程序指定了兩個USB核心在適當時間調用的函數。

1、探測函數

        當一個設備被安裝而USB核心認爲該驅動程序應該處理時,探測函數被調用;

        探測函數應該檢查傳遞給他的設備信息確定驅動程序是否真的適合該設備。當驅動程序因爲某種原因不應控制設備時,斷開函數被調用,它可以做一些清潔的工作。

      系統會傳遞給探測函數的信息是什麼呢?一個usb_interface * 跟一個struct usb_device_id *作爲參數。他們分別是該USB設備的接口描述(一般會是該設備的第0號接口,該接口的默認設置也是第0號設置)跟它的設備ID描述(包括Vendor ID、Production ID等)。

       USB驅動程序應該初始化任何可能用於控制USB設備的局部結構體,它還應該把所需的任何設備相關信息保存到局部結構體中。例如,USB驅動程序通常需要探測設備對的端點地址和緩衝區大小,因爲需要他們才能和端點通信

       下面具體分析探測函數做了哪些事情:

a -- 探測設備的端點地址、緩衝區大小,初始化任何可能用於控制USB設備的數據結構

       下面是一個實例代碼,他們探測批量類型的IN和OUT端點,把相關信息保存到一個局部設備結構體中

  1. /* set up the endpoint information */     
  2.      /* use only the first bulk-in and bulk-out endpoints */     
  3.      iface_desc = interface->cur_altsetting;     
  4.      for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {     
  5.          endpoint = &iface_desc->endpoint[i].desc;   
  6.     
  7.          if ( !dev->bulk_in_endpointAddr &&     
  8.                 ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
  9.              ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {     
  10.              /* we found a bulk in endpoint */     
  11.               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);     
  12.               dev->bulk_in_size = buffer_size;     
  13.               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;     
  14.               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);     
  15.               if (!dev->bulk_in_buffer) {     
  16.                   err("Could not allocate bulk_in_buffer");     
  17.                    goto error;                   
  18.               }  
  19.          }     
  20.     
  21.          if (!dev->bulk_out_endpointAddr &&     
  22.             ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&     
  23.                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {     
  24.               /* we found a bulk out endpoint */     
  25.               dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;     
  26.          }     
  27.      }     
  28.     
  29.      if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {     
  30.          err("Could not find both bulk-in and bulk-out endpoints");     
  31.          goto error;     
  32.      }    
  1. /* set up the endpoint information */     
  2.      /* use only the first bulk-in and bulk-out endpoints */     
  3.      iface_desc = interface->cur_altsetting;     
  4.      for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {     
  5.          endpoint = &iface_desc->endpoint[i].desc;   
  6.     
  7.          if ( !dev->bulk_in_endpointAddr &&     
  8.                 ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
  9.              ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {     
  10.              /* we found a bulk in endpoint */     
  11.               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);     
  12.               dev->bulk_in_size = buffer_size;     
  13.               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;     
  14.               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);     
  15.               if (!dev->bulk_in_buffer) {     
  16.                   err("Could not allocate bulk_in_buffer");     
  17.                    goto error;                   
  18.               }  
  19.          }     
  20.     
  21.          if (!dev->bulk_out_endpointAddr &&     
  22.             ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&     
  23.                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {     
  24.               /* we found a bulk out endpoint */     
  25.               dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;     
  26.          }     
  27.      }     
  28.     
  29.      if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {     
  30.          err("Could not find both bulk-in and bulk-out endpoints");     
  31.          goto error;     
  32.      }    

具體流程如下:   

     該代碼塊首先循環訪問該接口中存在的每一個端點,賦予該端點結構體的局部指針以使稍後的訪問更加容易

  1. for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {     
  2.          endpoint = &iface_desc->endpoint[i].desc;   
  1. for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {     
  2.          endpoint = &iface_desc->endpoint[i].desc;   

         然後,我們有了一個端點,而還沒有發現批量IN類型的端點時,查看該端點的方向是否爲IN。這可以通過檢查位掩碼 USB_DIR_IN 是否包含在bEndpointAddress 端點變量中來確定。如果是的話,我們測定該端點類型是否批量,這首先通過USB_ENDPOINT_XFERTYPE_MASK 位掩碼來取bmAttributes變量的值,然後檢查它是否和USB_ENDPOINT_XFER_BULK 的值匹配來完成

  1. if ( !dev->bulk_in_endpointAddr &&     
  2.                ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
  3.             ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {     
  1. if ( !dev->bulk_in_endpointAddr &&     
  2.                ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
  3.             ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {     

       如果這些都通過了,驅動程序就知道它已經發現了正確的端點類型,可以把該端點相關的信息保存到一個局部結構體中,就是我們前面的usb_skel ,以便稍後使用它和端點進行通信

  1. /* we found a bulk in endpoint */     
  2.               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);     
  3.               dev->bulk_in_size = buffer_size;     
  4.               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;     
  5.               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);     
  6.               if (!dev->bulk_in_buffer) {     
  7.                   err("Could not allocate bulk_in_buffer");     
  8.                    goto error;                 
  9.           }     
  1. /* we found a bulk in endpoint */     
  2.               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);     
  3.               dev->bulk_in_size = buffer_size;     
  4.               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;     
  5.               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);     
  6.               if (!dev->bulk_in_buffer) {     
  7.                   err("Could not allocate bulk_in_buffer");     
  8.                    goto error;                 
  9.           }     


b -- 把已經初始化數據結構的指針保存到接口設備中

      接下來的工作是向系統註冊一些以後會用的的信息。首先我們來說明一下usb_set_intfdata()他向內核註冊一個data,這個data的結構可以是任意的,這段程序向內核註冊了一個usb_skel結構,就是我們剛剛看到的被初始化的那個,這個data可以在以後用usb_get_intfdata來得到

usb_set_intfdata(interface, dev);


c -- 註冊USB設備

       如果USB驅動程序沒有和處理設備與用戶交互(例如輸入、tty、視頻等)的另一種類型的子系統相關聯,驅動程序可以使用USB主設備號,以便在用戶空間使用傳統的字符驅動程序接口。如果要這樣做,USB驅動程序必須在探測函數中調用 usb_resgister_dev 函數來把設備註冊到USB核心。只要該函數被調用,就要確保設備和驅動陳旭都處於可以處理用戶訪問設備的要求的恰當狀態

retval = usb_register_dev(interface, &skel_class);

skel_class結構。這個結構又是什麼?我們就來看看這到底是個什麼東西:

  1. static struct usb_class_driver skel_class = {     
  2.      .name =       "skel%d",     
  3.      .fops =       &skel_fops,     
  4.      .minor_base = USB_SKEL_MINOR_BASE,     
  5. };    
  1. static struct usb_class_driver skel_class = {     
  2.      .name =       "skel%d",     
  3.      .fops =       &skel_fops,     
  4.      .minor_base = USB_SKEL_MINOR_BASE,     
  5. };    

    它其實是一個系統定義的結構,裏面包含了一名字、一個文件操作結構體還有一個次設備號的基準值。事實上它纔是定義真正完成對設備IO操作的函數。所以他的核心內容應該是skel_fops。

   因爲usb設備可以有多個interface,每個interface所定義的IO操作可能不一樣,所以向系統註冊的usb_class_driver要求註冊到某一個interface,而不是device,因此,usb_register_dev的第一個參數纔是interface,而第二個參數就是某一個usb_class_driver。

   通常情況下,linux系統用主設備號來識別某類設備的驅動程序,用次設備號管理識別具體的設備,驅動程序可以依照次設備號來區分不同的設備,所以,這裏的次設備好其實是用來管理不同的interface的,但由於這個範例只有一個interface,在代碼上無法求證這個猜想。

  1. static struct file_operations skel_fops = {     
  2.      .owner = THIS_MODULE,     
  3.      .read =       skel_read,     
  4.      .write =   skel_write,     
  5.      .open =       skel_open,     
  6.      .release =    skel_release,     
  7. };    
  1. static struct file_operations skel_fops = {     
  2.      .owner = THIS_MODULE,     
  3.      .read =       skel_read,     
  4.      .write =   skel_write,     
  5.      .open =       skel_open,     
  6.      .release =    skel_release,     
  7. };    


2、斷開函數

      當設備被拔出集線器時,usb子系統會自動地調用disconnect,他做的事情不多,最重要的是註銷class_driver(交還次設備號)和interface的data:

  1. dev = usb_get_intfdata(interface);    
  2. usb_set_intfdata(interface, NULL);    
  3.     
  4. /* give back our minor */    
  5. usb_deregister_dev(interface, &skel_class);    
  1. dev = usb_get_intfdata(interface);    
  2. usb_set_intfdata(interface, NULL);    
  3.     
  4. /* give back our minor */    
  5. usb_deregister_dev(interface, &skel_class);    


四、USB請求塊

       USB 設備驅動代碼通過urb和所有的 USB 設備通訊。urb用 struct urb 結構描述(include/linux/usb.h )。

       urb 以一種異步的方式同一個特定USB設備的特定端點發送或接受數據。一個 USB 設備驅動可根據驅動的需要,分配多個 urb 給一個端點或重用單個 urb 給多個不同的端點。設備中的每個端點都處理一個 urb 隊列, 所以多個 urb 可在隊列清空之前被髮送到相同的端點。

 一個 urb 的典型生命循環如下:

 (1)被創建;
 (2)被分配給一個特定 USB 設備的特定端點;
 (3)被提交給 USB 核心;
 (4)被 USB 核心提交給特定設備的特定 USB 主機控制器驅動;
 (5)被 USB 主機控制器驅動處理, 並傳送到設備;
 (6)以上操作完成後,USB主機控制器驅動通知 USB 設備驅動。

 
   urb 也可被提交它的驅動在任何時間取消;如果設備被移除,urb 可以被USB核心取消。urb 被動態創建幷包含一個內部引用計數,使它們可以在最後一個用戶釋放它們時被自動釋放。

  1. struct urb  
  2. {  
  3.     /* 私有的:只能由usb核心和主機控制器訪問的字段 */  
  4.     struct kref kref; /*urb引用計數 */  
  5.     spinlock_t lock; /* urb鎖 */  
  6.     void *hcpriv; /* 主機控制器私有數據 */  
  7.     int bandwidth; /* int/iso請求的帶寬 */  
  8.     atomic_t use_count; /* 併發傳輸計數 */  
  9.     u8 reject; /* 傳輸將失敗*/  
  10.       
  11.     /* 公共的: 可以被驅動使用的字段 */  
  12.     struct list_head urb_list; /* 鏈表頭*/  
  13.     struct usb_device *dev; /* 關聯的usb設備 */  
  14.     unsigned int pipe; /* 管道信息 */  
  15.     int status; /* urb的當前狀態 */  
  16.     unsigned int transfer_flags; /* urb_short_not_ok | ...*/  
  17.     void *transfer_buffer; /* 發送數據到設備或從設備接收數據的緩衝區 */  
  18.     dma_addr_t transfer_dma; /*用來以dma方式向設備傳輸數據的緩衝區 */  
  19.     int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩衝區的大小 */  
  20.                            
  21.     int actual_length; /* urb結束後,發送或接收數據的實際長度 */  
  22.     unsigned char *setup_packet; /* 指向控制urb的設置數據包的指針*/  
  23.     dma_addr_t setup_dma; /*控制urb的設置數據包的dma緩衝區*/  
  24.     int start_frame; /*等時傳輸中用於設置或返回初始幀*/  
  25.     int number_of_packets; /*等時傳輸中等時緩衝區數據 */  
  26.     int interval; /* urb被輪詢到的時間間隔(對中斷和等時urb有效) */  
  27.     int error_count;  /* 等時傳輸錯誤數量 */  
  28.     void *context; /* completion函數上下文 */  
  29.     usb_complete_t complete; /* 當urb被完全傳輸或發生錯誤時,被調用 */  
  30.     struct usb_iso_packet_descriptor iso_frame_desc[0];  
  31.     /*單個urb一次可定義多個等時傳輸時,描述各個等時傳輸 */  
  32. };  
  1. struct urb  
  2. {  
  3.     /* 私有的:只能由usb核心和主機控制器訪問的字段 */  
  4.     struct kref kref; /*urb引用計數 */  
  5.     spinlock_t lock; /* urb鎖 */  
  6.     void *hcpriv; /* 主機控制器私有數據 */  
  7.     int bandwidth; /* int/iso請求的帶寬 */  
  8.     atomic_t use_count; /* 併發傳輸計數 */  
  9.     u8 reject; /* 傳輸將失敗*/  
  10.       
  11.     /* 公共的: 可以被驅動使用的字段 */  
  12.     struct list_head urb_list; /* 鏈表頭*/  
  13.     struct usb_device *dev; /* 關聯的usb設備 */  
  14.     unsigned int pipe; /* 管道信息 */  
  15.     int status; /* urb的當前狀態 */  
  16.     unsigned int transfer_flags; /* urb_short_not_ok | ...*/  
  17.     void *transfer_buffer; /* 發送數據到設備或從設備接收數據的緩衝區 */  
  18.     dma_addr_t transfer_dma; /*用來以dma方式向設備傳輸數據的緩衝區 */  
  19.     int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩衝區的大小 */  
  20.                            
  21.     int actual_length; /* urb結束後,發送或接收數據的實際長度 */  
  22.     unsigned char *setup_packet; /* 指向控制urb的設置數據包的指針*/  
  23.     dma_addr_t setup_dma; /*控制urb的設置數據包的dma緩衝區*/  
  24.     int start_frame; /*等時傳輸中用於設置或返回初始幀*/  
  25.     int number_of_packets; /*等時傳輸中等時緩衝區數據 */  
  26.     int interval; /* urb被輪詢到的時間間隔(對中斷和等時urb有效) */  
  27.     int error_count;  /* 等時傳輸錯誤數量 */  
  28.     void *context; /* completion函數上下文 */  
  29.     usb_complete_t complete; /* 當urb被完全傳輸或發生錯誤時,被調用 */  
  30.     struct usb_iso_packet_descriptor iso_frame_desc[0];  
  31.     /*單個urb一次可定義多個等時傳輸時,描述各個等時傳輸 */  
  32. };  


1、創建和註銷 urb

      struct urb 結構不能靜態創建,必須使用 usb_alloc_urb 函數創建. 函數原型:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等時數據包的數目。如果不使用等時urb,則爲0
//gfp_t mem_flags : 與傳遞給 kmalloc 函數調用來從內核分配內存的標誌類型相同

//返回值          : 如果成功分配足夠內存給 urb , 返回值爲指向 urb 的指針. 如果返回值是 NULL, 則在 USB 核心中發生了錯誤, 且驅動需要進行適當清理


如果驅動已經對 urb 使用完畢, 必須調用 usb_free_urb 函數,釋放urb。函數原型:

void usb_free_urb(struct urb *urb);
//struct urb *urb : 要釋放的 struct urb 指針


2、初始化 urb

  1. static inline void usb_fill_int_urb(struct urb *urb,                                                                                                         
  2.                  struct usb_device *dev,  
  3.                  unsigned int pipe,  
  4.                  void *transfer_buffer,  
  5.                  int buffer_length,  
  6.                  usb_complete_t complete_fn,  
  7.                  void *context,  
  8.                  int interval);  
  9.   
  10. static inline void usb_fill_bulk_urb(struct urb *urb,  
  11.                  struct usb_device *dev,  
  12.                  unsigned int pipe,  
  13.                  void *transfer_buffer,  
  14.                  int buffer_length,  
  15.                  usb_complete_t complete_fn,  
  16.                  void *context);  
  17.   
  18. static inline void usb_fill_control_urb(struct urb *urb,  
  19.                     struct usb_device *dev,  
  20.                     unsigned int pipe,  
  21.                     unsigned char *setup_packet,  
  22.                     void *transfer_buffer,  
  23.                     int buffer_length,  
  24.                     usb_complete_t complete_fn,  
  25.                     void *context);  
  26.   
  27.   
  28. //struct urb *urb :指向要被初始化的 urb 的指針  
  29. //struct usb_device *dev :指向 urb 要發送到的 USB 設備.  
  30. //unsigned int pipe : urb 要被髮送到的 USB 設備的特定端點. 必須使用前面提過的 usb_******pipe 函數創建  
  31. //void *transfer_buffer :指向外發數據或接收數據的緩衝區的指針.注意:不能是靜態緩衝,必須使用 kmalloc 來創建.  
  32. //int buffer_length :transfer_buffer 指針指向的緩衝區的大小  
  33. //usb_complete_t complete :指向 urb 結束處理例程函數指針  
  34. //void *context :指向一個小數據塊的指針, 被添加到 urb 結構中,以便被結束處理例程函數獲取使用.  
  35. //int interval :中斷 urb 被調度的間隔.  
  36. //函數不設置 urb 中的 transfer_flags 變量, 因此對這個成員的修改必須由驅動手動完成  
  37.   
  38. /*等時 urb 沒有初始化函數,必須手動初始化,以下爲一個例子*/  
  39. urb->dev = dev;  
  40. urb->context = uvd;  
  41. urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);  
  42. urb->interval = 1;  
  43. urb->transfer_flags = URB_ISO_ASAP;  
  44. urb->transfer_buffer = cam->sts_buf[i];  
  45. urb->complete = konicawc_isoc_irq;  
  46. urb->number_of_packets = FRAMES_PER_DESC;  
  47. urb->transfer_buffer_length = FRAMES_PER_DESC;  
  48. for (j=0; j < FRAMES_PER_DESC; j++) {  
  49.         urb->iso_frame_desc[j].offset = j;  
  50.         urb->iso_frame_desc[j].length = 1;  
  51. }  
  1. static inline void usb_fill_int_urb(struct urb *urb,                                                                                                         
  2.                  struct usb_device *dev,  
  3.                  unsigned int pipe,  
  4.                  void *transfer_buffer,  
  5.                  int buffer_length,  
  6.                  usb_complete_t complete_fn,  
  7.                  void *context,  
  8.                  int interval);  
  9.   
  10. static inline void usb_fill_bulk_urb(struct urb *urb,  
  11.                  struct usb_device *dev,  
  12.                  unsigned int pipe,  
  13.                  void *transfer_buffer,  
  14.                  int buffer_length,  
  15.                  usb_complete_t complete_fn,  
  16.                  void *context);  
  17.   
  18. static inline void usb_fill_control_urb(struct urb *urb,  
  19.                     struct usb_device *dev,  
  20.                     unsigned int pipe,  
  21.                     unsigned char *setup_packet,  
  22.                     void *transfer_buffer,  
  23.                     int buffer_length,  
  24.                     usb_complete_t complete_fn,  
  25.                     void *context);  
  26.   
  27.   
  28. //struct urb *urb :指向要被初始化的 urb 的指針  
  29. //struct usb_device *dev :指向 urb 要發送到的 USB 設備.  
  30. //unsigned int pipe : urb 要被髮送到的 USB 設備的特定端點. 必須使用前面提過的 usb_******pipe 函數創建  
  31. //void *transfer_buffer :指向外發數據或接收數據的緩衝區的指針.注意:不能是靜態緩衝,必須使用 kmalloc 來創建.  
  32. //int buffer_length :transfer_buffer 指針指向的緩衝區的大小  
  33. //usb_complete_t complete :指向 urb 結束處理例程函數指針  
  34. //void *context :指向一個小數據塊的指針, 被添加到 urb 結構中,以便被結束處理例程函數獲取使用.  
  35. //int interval :中斷 urb 被調度的間隔.  
  36. //函數不設置 urb 中的 transfer_flags 變量, 因此對這個成員的修改必須由驅動手動完成  
  37.   
  38. /*等時 urb 沒有初始化函數,必須手動初始化,以下爲一個例子*/  
  39. urb->dev = dev;  
  40. urb->context = uvd;  
  41. urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);  
  42. urb->interval = 1;  
  43. urb->transfer_flags = URB_ISO_ASAP;  
  44. urb->transfer_buffer = cam->sts_buf[i];  
  45. urb->complete = konicawc_isoc_irq;  
  46. urb->number_of_packets = FRAMES_PER_DESC;  
  47. urb->transfer_buffer_length = FRAMES_PER_DESC;  
  48. for (j=0; j < FRAMES_PER_DESC; j++) {  
  49.         urb->iso_frame_desc[j].offset = j;  
  50.         urb->iso_frame_desc[j].length = 1;  
  51. }  


3、提交 urb

      一旦 urb 被正確地創建並初始化, 它就可以提交給 USB 核心以發送出到 USB 設備. 這通過調用函數 usb_submit_urb 實現:

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指針 
//gfp_t mem_flags :使用傳遞給 kmalloc 調用同樣的參數, 用來告訴 USB 核心如何及時分配內存緩衝

/*因爲函數 usb_submit_urb 可被在任何時候被調用(包括從一箇中斷上下文), mem_flags 變量必須正確設置. 根據 usb_submit_urb 被調用的時間,只有 3 個有效值可用:
GFP_ATOMIC 
只要滿足以下條件,就應當使用此值:
1.調用者處於一個 urb 結束處理例程,中斷處理例程,底半部,tasklet或者一個定時器回調函數.
2.調用者持有自旋鎖或者讀寫鎖. 注意如果正持有一個信號量, 這個值不必要.
3.current->state 不是 TASK_RUNNING. 除非驅動已自己改變 current 狀態,否則狀態應該一直是 TASK_RUNNING .

GFP_NOIO 
驅動處於塊 I/O 處理過程中. 它還應當用在所有的存儲類型的錯誤處理過程中.

GFP_KERNEL 
所有不屬於之前提到的其他情況
*/


在 urb 被成功提交給 USB 核心之後, 直到結束處理例程函數被調用前,都不能訪問 urb 結構的任何成員.


4、urb結束處理例程

      如果 usb_submit_urb 被成功調用, 並把對 urb 的控制權傳遞給 USB 核心, 函數返回 0; 否則返回一個負的錯誤代碼. 如果函數調用成功, 當 urb 被結束的時候結束處理例程會被調用一次.當這個函數被調用時, USB 核心就完成了這個urb, 並將它的控制權返回給設備驅動.

只有 3 種結束urb並調用結束處理例程的情況:

(1)urb 被成功發送給設備, 且設備返回正確的確認.如果這樣, urb 中的status變量被設置爲 0.
(2)發生錯誤, 錯誤值記錄在 urb 結構中的 status 變量.
(3)urb 從 USB 核心unlink. 這發生在要麼當驅動通過調用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一個已提交的 urb,或者在一個 urb 已經被提交給它時設備從系統中去除.

5、取消 urb

使用以下函數停止一個已經提交給 USB 核心的 urb:

void usb_kill_urb(struct urb *urb)
int usb_unlink_urb(struct urb *urb);

如果調用usb_kill_urb函數,則 urb 的生命週期將被終止. 這通常在設備從系統移除時,在斷開回調函數(disconnect callback)中調用.

對一些驅動, 應當調用 usb_unlink_urb 函數來使 USB 核心停止 urb. 這個函數不會等待 urb 完全停止才返回. 這對於在中斷處理例程中或者持有一個自旋鎖時去停止 urb 是很有用的, 因爲等待一個 urb 完全停止需要 USB 核心有使調用進程休眠的能力(wait_event()函數).
發佈了3 篇原創文章 · 獲贊 12 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章