1. 程式人生 > >linux裝置驅動之USB主機控制器驅動分析 (一)

linux裝置驅動之USB主機控制器驅動分析 (一)

一:前言 Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.它定義了USB協議.另外的一個 是USB控制器的規範.有UHCI,EHCI,OHCI三種.其中UHCI是Intel推出的一種USB控制器標準.它將很多功能交給軟體處理.相比之 下,它也是最為複雜的.因此,本文件以UHCI為例分析.另外,在分析的過程中參考了情景分析和fudan_abc的<<Linux那些事兒 之我是UHCI>>.正是因為踩在許多牛人的肩膀上,才使USB這個複雜的工程在我們面前變得越來越清晰. 本文的程式碼分析是基於linux kernel 2.6.25.涉及到的程式碼主要位於linux-2.6.25/drivers/usb目錄下. 二:UHCI的初始化 UHCI主機控制器的程式碼位於linux-2.6.25/drivers/usb/host下面.在配置kernel的時候可以選擇將其編譯進核心或者編譯成模組.模組的入口函式為: uhci_hcd_init().程式碼如下: static int __init uhci_hcd_init(void) {     int retval = -ENOMEM;      printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "%s\n",               ignore_oc ? ", overcurrent ignored" : "");      if (usb_disabled())          return -ENODEV;      if (DEBUG_CONFIGURED) {          errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL);          if (!errbuf)               goto errbuf_failed;          uhci_debugfs_root = debugfs_create_dir("uhci", NULL);          if (!uhci_debugfs_root)               goto debug_failed;      }      uhci_up_cachep = kmem_cache_create("uhci_urb_priv",          sizeof(struct urb_priv), 0, 0, NULL);      if (!uhci_up_cachep)          goto up_failed;      retval = pci_register_driver(&uhci_pci_driver);      if (retval)          goto init_failed;      return 0; init_failed:      kmem_cache_destroy(uhci_up_cachep); up_failed:      debugfs_remove(uhci_debugfs_root); debug_failed:      kfree(errbuf); errbuf_failed:      return retval; } 入口函式比較簡單.其中涉及到的介面在之前都已經詳細的分析過. 在引導系統的時候,可以為kernel指定引數.如果配置了”nousb”,就明確禁止使用USB.該入口函式首先通過 usb_disabled()來檢測使用者指定了nousb引數.然後為struct urb_priv建立了一個cache.然後註冊了一個PCI驅動.struct usb_priv等以後用到的時候再進行分析.UHCI是一個PCI裝置.PCI的驅動架構我們之前已經分析過了,這裡不再贅述. uhci_pci_driver定義如下所示: static struct pci_driver uhci_pci_driver = {      .name =       (char *)hcd_name,      .id_table =   uhci_pci_ids,      .probe = usb_hcd_pci_probe,      .remove = usb_hcd_pci_remove,      .shutdown =   uhci_shutdown, #ifdef   CONFIG_PM      .suspend =    usb_hcd_pci_suspend,      .resume = usb_hcd_pci_resume, #endif   /* PM */ }; 通過之前的對PCI的分析,我們知道對於pci_dev和pci_driver的匹配過程是通過判斷pci_driver的id_table項和pci_dev的相關項是否符合來進行的.在這裡.id_talbe的定義如下所示: static const struct pci_dev_id uhci_pci_ids[] = { {      /* handle any USB UHCI controller */      PCI_DEV_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0),      .driver_data =     (unsigned long) &uhci_driver,      }, { /* end: all zeroes */ } }; 由此,可以看到,只要是屬於PCI_CLASS_SERIAL_USB_UHCI類的裝置,都能匹配到這個驅動.這個巨集的定義如下: #define PCI_CLASS_SERIAL_USB_UHCI    0x0c0300 其實該型別是由UHCI的spec規定的. 另外,id_talbe的私有項(driver_data)被置為了uhci_driver.這個在以後是會被用到的. 如果pci_driver成功匹配到裝置.就會呼叫其probe介面.在這裡.probe介面被置為了usb_hcd_pci_probe.如下所示: int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_dev_id *id) {      struct hc_driver   *driver;      struct usb_hcd         *hcd;      int           retval;      if (usb_disabled())          return -ENODEV;      if (!id)          return -EINVAL;      driver = (struct hc_driver *)id->driver_data;      if (!driver)          return -EINVAL;      if (pci_enable_device(dev) < 0)          return -ENODEV;      dev->current_state = PCI_D0;      dev->dev.power.power_state = PMSG_ON;      if (!dev->irq) {          dev_err(&dev->dev,               "Found HC with no IRQ.  Check BIOS/PCI %s setup!\n",               pci_name(dev));          retval = -ENODEV;          goto err1;      }      //建立usb_hcd      hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));      if (!hcd) {          retval = -ENOMEM;          goto err1;      }      //UCHI的flags沒有定義成HCD_MEMORY      if (driver->flags & HCD_MEMORY) {          /* EHCI, OHCI */          hcd->rsrc_start = pci_resource_start(dev, 0);          hcd->rsrc_len = pci_resource_len(dev, 0);          if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,                    driver->description)) {               dev_dbg(&dev->dev, "controller already in use\n");               retval = -EBUSY;               goto err2;          }          hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);          if (hcd->regs == NULL) {               dev_dbg(&dev->dev, "error mapping memory\n");               retval = -EFAULT;               goto err3;          }      } else {          /* UHCI */          int  region;          //找到一個I/O的緩衝區.UHCI只有一個I/O區間          for (region = 0; region < PCI_ROM_RESOURCE; region++) {               if (!(pci_resource_flags(dev, region) &                        IORESOURCE_IO))                    continue;               hcd->rsrc_start = pci_resource_start(dev, region);               hcd->rsrc_len = pci_resource_len(dev, region);               if (request_region(hcd->rsrc_start, hcd->rsrc_len,                        driver->description))                    break;          }          if (region == PCI_ROM_RESOURCE) {               dev_dbg(&dev->dev, "no i/o regions available\n");               retval = -EBUSY;               goto err1;          }      }      //使用DMA      pci_set_master(dev);      retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);      if (retval != 0)          goto err4;      return retval;  err4:      if (driver->flags & HCD_MEMORY) {          iounmap(hcd->regs);  err3:          release_mem_region(hcd->rsrc_start, hcd->rsrc_len);      } else          release_region(hcd->rsrc_start, hcd->rsrc_len);  err2:      usb_put_hcd(hcd);  err1:      pci_disable_device(dev);      dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);      return retval; } 這段程式碼位於linux-2.6.25/drivers/usb/core下的hcd-pci.c中.該路徑下的程式碼是被所有USB控制器共享 的.因此,我們在程式碼中可以看到usb_hcd_pci_probe()會有區別UHCI還是其它型別的控制器的操作.在USB驅動架構中,有很多程式碼是 關於電源管理的.在這裡我們先忽略電源管理的部份.之後再以單獨章節的形式來分析linux上的電源管理子系統. 首先,會呼叫 pci_enable_device()來啟用PCI裝置.正如在分析PCI裝置的時候.初始化之後的PCI裝置很多功能都是被 禁用的.例如I/O/記憶體空間,IRQ等.其次,OHCI必須要使用中斷.如果對應中斷號不存在,說明此裝置不是一個UHCI.或者出現了錯誤.直接跳 出.不進行後續操作.然後,OHCI必須要使用DMA.所以會呼叫pci_set_master()將開啟裝置的DMA傳輸能力.另外,OHCI SPEC上有定義.在PCI的配置空間中,0x20~0x23定義了OHCI的I/O區間和大小.也就是說OHCI對應的pci_dev中,只有一個I /O資源區間是有效的. 對應到上面的程式碼: id->driver_data的賦值在uhci_hcd_init()中被特別指出過.被賦值為uhci_driver.它的結構如下: static const struct hc_driver uhci_driver = {      .description =         hcd_name,      .product_desc =        "UHCI Host Controller",      .hcd_priv_size =   sizeof(struct uhci_hcd),      /* Generic hardware linkage */      .irq =             uhci_irq,      .flags =      HCD_USB11,      /* Basic lifecycle operations */      .reset =      uhci_init,      .start =      uhci_start, #ifdef CONFIG_PM      .suspend =         uhci_suspend,      .resume =     uhci_resume,      .bus_suspend =         uhci_rh_suspend,      .bus_resume =      uhci_rh_resume, #endif      .stop =            uhci_stop,      .urb_enqueue =         uhci_urb_enqueue,      .urb_dequeue =         uhci_urb_dequeue,      .endpoint_disable =    uhci_hcd_endpoint_disable,      .get_frame_number =    uhci_hcd_get_frame_number,      .hub_status_data = uhci_hub_status_data,      .hub_control =         uhci_hub_control, }; 可以看到,在的結構為struct hc_driver. Hc就是host control的意思.即為主機控制器驅動.該結構包函了很多函式指標,具體的操作我們等能後涉及的時候再回過來分析.另外,從裡面可以看到,它的flags被定義成了HCD_USB1.1. 特別說明一下:UHCI是一個基於usb1.1的裝置.USB1.1和USB2.0的最大區別就是USB2.0中定義有高速裝置.因 此,UHCI是一個不支援高速的USB控制器.只有EHCI才會支援高速.因此,在配置kernel的時候,UHCI和EHCI通常都會選上.如果只選用 UHCI或者只選用EHCI.有很多裝置都是不能夠工作的. 因為flags被定義成HCD_USB1.1.所以程式碼中的if(driver->flags & HCD_MEMORY) … else …流程就轉入到else下面. 然後,我們目光注視到usb_create_hcd()和usb_add_hcd()這兩個函式.看函式名稱,一個是產生struct usb_hcd.另外的一個是將這個hcd新增到系統.hcd就是host control driver的意思.先來分析一下usb_create_hcd的程式碼: struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,          struct device *dev, char *bus_name) {      struct usb_hcd *hcd;      hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);      if (!hcd) {          dev_dbg (dev, "hcd alloc failed\n");          return NULL;      }      dev_set_drvdata(dev, hcd);      kref_init(&hcd->kref);      usb_bus_init(&hcd->self);      hcd->self.controller = dev;      hcd->self.bus_name = bus_name;      hcd->self.uses_dma = (dev->dma_mask != NULL);      init_timer(&hcd->rh_timer);      hcd->rh_timer.function = rh_timer_func;      hcd->rh_timer.data = (unsigned long) hcd; #ifdef CONFIG_PM      INIT_WORK(&hcd->wakeup_work, hcd_resume_work); #endif      hcd->driver = driver;      hcd->product_desc = (driver->product_desc) ? driver->product_desc :               "USB Host Controller";      return hcd; } 函式的三個引數: 1: driver:也就是上面分析的pci_driver的id_table的driver_data項.即struct hc_driver 2: dev: OHCI所對應的pci_dev中內嵌的struct device結構 3: bus_name:OHCI對應的pci_dev的name 在這裡,注意一下hcd記憶體的分配.如下示: hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); 我們知道,struct usb_hcd是一個位於usb_core下的東東,這個東東所有的host control都會用到.那麼hcd就有一個私有區結構,用來表示host control之間不同的資料結構.而其它們相同的結構儲存在struct usb_hcd中.這個hcd_priv成員在struct usb_hcd被定義成了0項陣列的形式,而大小則是由hc_driver的hcd_priv_size項來指定的. struct usb_hcd結構很龐大.這裡不方便將其全部列出.只來說明一下在這裡會用到的成員: 1:self成員: 我們可以這想思考.每條USB總線上只有一個host control.每個host control都對應著一條匯流排. 這個self成員就是表示hcd所對應的USB匯流排. self.controller表示該總線上的控制器,也就是UHCI對應的pci_dev中封裝的struct device. Self. bus_name表示該匯流排的名稱.也就是OHCI對應的pci_dev的名稱.self. uses_dma來表示該總線上的控制器是否使用DMA 2: rh_timer成員:該成員是一個定時器,用來輪詢控制器的根集線器的狀態改變,通常用做電源管理.在這裡不加詳分析. 2: driver成員:表示該hcd對應驅動. 總而言之, usb_create_hcd就是對hcd的各項成員賦值. 相比之下usb_add_hcd()的程式碼就比較繁雜了.下面以分段的形式分析如下: int usb_add_hcd(struct usb_hcd *hcd,          unsigned int irqnum, unsigned long irqflags) {      int retval;      struct usb_device *rhdev;      dev_info(hcd->self.controller, "%s\n", hcd->product_desc);      hcd->authorized_default = hcd->wireless? 0 : 1;      set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);      /* HC is in reset state, but accessible.  Now do the one-time init,       * bottom up so that hcds can customize the root hubs before khubd       * starts talking to them.  (Note, bus id is assigned early too.)       */       //建立pool      if ((retval = hcd_buffer_create(hcd)) != 0) {          dev_dbg(hcd->self.controller, "pool alloc failed\n");          return retval;      } 在我們分析的流程中, Hcd->wireless預設為0.相應的hcd->authorized_default也被置為了0.然後將 hcd->flags置為HCD_FLAG_HW_ACCESSIBLE.表示該USB控制器是可以訪問的.最後在 hcd_buffer_create中,因為hc_driver的flags標誌被末置HCD_LOCAL_MEM.該函式在這裡什麼都不做就返回0了.      //註冊usb_bus      if ((retval = usb_register_bus(&hcd->self)) < 0)          goto err_register_bus;      //分配並初始化root hub      if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {          dev_err(hcd->self.controller, "unable to allocate root hub\n");          retval = -ENOMEM;          goto err_allocate_root_hub;      }      //OHCI定義於usb1.1只能支援全速      rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :               USB_SPEED_FULL;      hcd->self.root_hub = rhdev;      /* wakeup flag init defaults to "everything works" for root hubs,       * but drivers can override it in reset() if needed, along with       * recording the overall controller's system wakeup capability.       */      device_init_wakeup(&rhdev->dev, 1); 在前面.我們看到了在hcd的self成員的賦值過程,而所有的匯流排資訊都要儲存在一個地方,在其它的地方會用到這些匯流排資訊.所以 usb_register_bus()對應的工作就是在全域性變數busmap的點陣圖中找到沒有被使用的位做為usb_bus的序號(我們暫且稱呼它為 USB匯流排號).然後為該匯流排註冊一個屬於usb_host_class類的裝置.以後在/sys/class/host中就可以看到該bus對應的目錄 了.最後,將匯流排連結到usb_bus_list連結串列中. 然後,每一個USB控制器都有一個根集線器.這裡也要為匯流排下的根集錢器建立相應的結構, usb_alloc_dev()用來生成並初始化的usb_device結構.這個函式比較重要,在後面給出這個函式的詳細分析. 因為OHCI是USB1.1的裝置,所以,根集線器的speed會被定義成USB_SPEED_FULL(全速).最後將這個根集線器關聯到匯流排中. device_init_wakeup(&rhdev->dev, 1)是和匯流排相關的,忽略它吧 :-)      /* "reset" is misnamed; its role is now one-time init. the controller       * should already have been reset (and boot firmware kicked off etc).       */      if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {          dev_err(hcd->self.controller, "can't setup\n");          goto err_hcd_driver_setup;      }      /* NOTE: root hub and controller capabilities may not be the same */      if (device_can_wakeup(hcd->self.controller)               && device_can_wakeup(&hcd->self.root_hub->dev))          dev_dbg(hcd->self.controller, "supports USB remote wakeup\n");      /* enable irqs just before we start the controller */      if (hcd->driver->irq) {          snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",                    hcd->driver->description, hcd->self.busnum);          if ((retval = request_irq(irqnum, &usb_hcd_irq, irqflags,                    hcd->irq_descr, hcd)) != 0) {               dev_err(hcd->self.controller,                        "request interrupt %d failed\n", irqnum);               goto err_request_irq;          }          hcd->irq = irqnum;          dev_info(hcd->self.controller, "irq %d, %s 0x%08llx\n", irqnum,                    (hcd->driver->fla