DPDK設備驅動的匹配和初始化


前言:DPDK使用了UIO(用戶空間I/O)的機制,跳過內核態的網卡驅動,轉而使用用戶態的收發包驅動,從驅動到內存和數據包,繼而到數據包的處理,這是一個完整的收發包流程。這篇主要介紹設備驅動的初始化,和收發包的處理。所選代碼以DPDK-17.02版本為依據。


數據包的驅動初始化是在rte_eal_init()進行的,總體上分為2個階段進行。

  • 1.第一階段是rte_eal_pci_init(),主要是獲取系統中的設備PCI。
  • 2.第二階段是rte_eal_pci_probe(),這個階段做的事比較多,匹配對應的設備驅動,分配設備,並對設備進行初始化。

我們就按照這個順序進行介紹。

  • <1>.先看rte_eal_init()這個函數,了解這一階段的處理過程。

    在函數中,調用了rte_eal_pci_scan(),來掃描系統目錄里的PCI設備。默認的掃描目錄是#define SYSFS_PCI_DEVICES "/sys/bus/pci/devices",就是依次讀取目錄下的每一個文件名,解析PCI的地址信息,填充地址信息。

    然后調用了pci_scan_one()來進行設備信息的填充,掛接設備。先分配了一個PCI設備結構,注意:此處分配的是PCI設備結構,並不是rte_eth_dev設備。前者標識一個PCI設備,后者標識一個網卡設備。然后依次讀取每個PCI設備目錄下的vendor,device等文件,填充剛分配出來的PCI設備結構。接下來使用pci_get_kernel_driver_by_path()獲取設備驅動的類型。比如使用82599的網卡,就會看到類型為igb_uio,設置對應的驅動類型。

    if (!ret) {
    	if (!strcmp(driver, "vfio-pci"))
    		dev->kdrv = RTE_KDRV_VFIO;
    	else if (!strcmp(driver, "igb_uio"))
    		dev->kdrv = RTE_KDRV_IGB_UIO;
    	else if (!strcmp(driver, "uio_pci_generic"))
    		dev->kdrv = RTE_KDRV_UIO_GENERIC;
    	else
    		dev->kdrv = RTE_KDRV_UNKNOWN;
    } else
    	dev->kdrv = RTE_KDRV_NONE;
    

    最后,把PCI設備掛在pci_device_list中:如果設備隊列是空的,則直接掛上,如果不是空的,則按照PCI地址排序后掛接在隊列中。

    這樣,第一階段的工作就做完了,主要是掃描PCI的所有設備,填充設備信息,掛接在隊列中。

  • <2>.rte_eal_pci_probe()函數進入了第二階段的初始化。
    先進行了一個設備參數類型的檢查,rte_eal_devargs_type_count(),在這里又涉及到另一個變量---devargs_list,這個全局變量記錄着哪些設備的PCI是在白或者黑名單里面,如果是在黑名單里,后面就不進行初始化。這個devargs_list的添加注冊是在參數解析部分,-w,-b參數指定的名單。

    然后依次遍歷隊列中的每個PCI設備,和devargs_list比較,查看是否有設備在列表中,如果在黑名單中,就不進行初始化。

    之后調用pci_probe_all_drivers()對每個允許的設備進行初始化。

    TAILQ_FOREACH(dr, &pci_driver_list, next) {
    	rc = rte_eal_pci_probe_one_driver(dr, dev);
    	if (rc < 0)
    		/* negative value is an error */
    		return -1;
    	if (rc > 0)
    		/* positive value means driver doesn't support it */
    		continue;
    	return 0;
    }
    

    和每個注冊的驅動進行比較,注冊的驅動都掛接在pci_driver_list中,驅動的注冊是通過下面的一段代碼實現的

    #define RTE_PMD_REGISTER_PCI(nm, pci_drv) \
    RTE_INIT(pciinitfn_ ##nm); \
    static void pciinitfn_ ##nm(void) \
    {\
    	(pci_drv).driver.name = RTE_STR(nm);\
    	rte_eal_pci_register(&pci_drv); \
    } \
    RTE_PMD_EXPORT_NAME(nm, __COUNTER__)
    

    這里注意注冊函數的類型為析構函數,gcc的補充。它是在main函數之前就執行的,所以,在main之前,驅動就已經注冊好了。

    #define RTE_INIT(func) \
    static void __attribute__((constructor, used)) func(void)
    

    進一步查看的話,發現系統注冊了這么幾種類型的驅動:
    (1).rte_igb_pmd
    (2).rte_igbvf_pmd
    (3).rte_ixgbe_pmd
    .....

    如rte_ixgbe_pmd驅動

    static struct eth_driver rte_ixgbe_pmd = {
    .pci_drv = {
    	.id_table = pci_id_ixgbe_map,
    	.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_INTR_LSC,
    	.probe = rte_eth_dev_pci_probe,
    	.remove = rte_eth_dev_pci_remove,
    },
    .eth_dev_init = eth_ixgbe_dev_init,
    .eth_dev_uninit = eth_ixgbe_dev_uninit,
    .dev_private_size = sizeof(struct ixgbe_adapter),
    };
    

    其中的id_table表中就存放了各種支持的ixgbe設備的vendor號等詳細信息。

    接下來,自然的,如果匹配上了,就調用對應的驅動probe函數。進入rte_eal_pci_probe_one_driver()函數進行匹配。
    當匹配成功后,對PCI資源進行映射--rte_eal_pci_map_device(),這個函數就不進行細細分析了。
    最重要的地方到了,匹配成功后,就調用了dr->probe函數,對於ixgbe驅動,就是rte_eth_dev_pci_probe()函數,我們跳進去看看這個probe函數。
    首先檢查進程如果為RTE_PROC_PRIMARY類型的,那么就分配一個rte_eth_dev設備,調用rte_eth_dev_allocate(),分配可用的port_id,然后如果rte_eth_dev_data沒有分 配,則一下子分配RTE_MAX_ETHPORTS個這個結構,這個結構描述了每個網卡的數據信息,並把對應port_id的rte_eth_dev_data[port_id]關聯到新分配的設備上。

    設備創建好了以后,就給設備的私有數據分配空間,

    eth_dev->data->dev_private = rte_zmalloc("ethdev private structure",
    			  eth_drv->dev_private_size,
    			  RTE_CACHE_LINE_SIZE);
    

    然后填充設備的device,driver信息等。最后調用設備的初始化函數--eth_drv->eth_dev_init,這在ixgbe驅動中,是eth_ixgbe_dev_init()

    從這個初始化函數,進入最后的初始化環節。
    要知道的一點是:在這個函數中,很多的工作肯定還是填充分配的設備結構體。先填充了設備的操作函數,以及非常重要的收發包函數

    eth_dev->dev_ops = &ixgbe_eth_dev_ops;
    eth_dev->rx_pkt_burst = &ixgbe_recv_pkts;
    eth_dev->tx_pkt_burst = &ixgbe_xmit_pkts;
    eth_dev->tx_pkt_prepare = &ixgbe_prep_pkts;
    

    再檢查如果不是RTE_PROC_PRIMARY進程,則只要檢查一下收發函數,並不進一步設置。
    然后拷貝一下pci設備的相關信息

    rte_eth_copy_pci_info(eth_dev, pci_dev);
    eth_dev->data->dev_flags |= RTE_ETH_DEV_DETACHABLE;
    
    /* Vendor and Device ID need to be set before init of shared code */
    hw->device_id = pci_dev->id.device_id;
    hw->vendor_id = pci_dev->id.vendor_id;
    hw->hw_addr = (void *)pci_dev->mem_resource[0].addr;
    hw->allow_unsupported_sfp = 1;
    

    接下來針對對應的設備,調用ixgbe_init_shared_code()根據hw->device_id來初始化特定的設備的MAC層操作函數集,ixgbe_mac_operations,如82599設備。

    上面的操作都完成后,就可以調用ixgbe_init_hw()對硬件進行初始化了,初始化的函數在上一步MAC層函數操作集已經初始化。

    然后重置設備的硬件統計,分配MAC地址,最后初始化一下各種過濾條件。

    Done!!整個PCI驅動的匹配和初始化過程就完成了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM