linux裝置驅動之USB主機控制器驅動分析 (一)
阿新 • • 發佈:2019-02-09
一:前言
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