dpdk pci驱动探测
? ? ? ? 上一篇文章已經(jīng)介紹了pci設(shè)備的背景知識(shí), 現(xiàn)在我們來(lái)分析下pci設(shè)備是如何探測(cè)到支持的驅(qū)動(dòng),進(jìn)而與驅(qū)動(dòng)進(jìn)行關(guān)聯(lián);pci與驅(qū)動(dòng)的解除綁定;pci設(shè)備與uio設(shè)備的關(guān)聯(lián)。
一、pci驅(qū)動(dòng)注冊(cè)
? ? ? ??網(wǎng)卡驅(qū)動(dòng)的注冊(cè)使用了一種奇技淫巧的方法,使用GCC attribute擴(kuò)展屬性的constructor屬性,使得網(wǎng)卡驅(qū)動(dòng)的注冊(cè)在程序main函數(shù)之前就執(zhí)行了。此時(shí)在main函數(shù)執(zhí)行前,就已經(jīng)把系統(tǒng)支持的驅(qū)動(dòng)通過(guò)rte_eal_driver_register注冊(cè)到驅(qū)動(dòng)鏈表dev_driver_list中。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
struct rte_driver {TAILQ_ENTRY(rte_driver) next; enum pmd_type type; /**< 驅(qū)動(dòng)類型 */const char *name; /**< 驅(qū)動(dòng)名 */rte_dev_init_t *init; /**< 設(shè)備初始化函數(shù) */ }; static struct rte_driver pmd_igb_drv =? {.type = PMD_PDEV,.init = rte_igb_pmd_init, }; static struct rte_driver rte_ixgbe_driver = {.type = PMD_PDEV,.init = rte_ixgbe_pmd_init, };? ? ? ? 類似于c++中的多態(tài),驅(qū)動(dòng)鏈表節(jié)點(diǎn)strcuct rte_driver是一個(gè)類, 每一種驅(qū)動(dòng)都具體實(shí)現(xiàn)這個(gè)類,不同的驅(qū)動(dòng)實(shí)現(xiàn)方式不一樣,可以自定義init初始化接口。例如igb驅(qū)動(dòng)與ixgbe驅(qū)動(dòng),這兩種驅(qū)動(dòng)都具體實(shí)現(xiàn)了這個(gè)類。每一種驅(qū)動(dòng)都實(shí)現(xiàn)這個(gè)類,相當(dāng)于一個(gè)多態(tài)模型。最后通過(guò)調(diào)用PMD_REGISTER_DRIVER注冊(cè)到驅(qū)動(dòng)鏈表dev_driver_list。 調(diào)用rte_eal_driver_unregister則可以從驅(qū)動(dòng)鏈表中卸載。
void rte_eal_driver_register(struct rte_driver *driver) {TAILQ_INSERT_TAIL(&dev_driver_list, driver, next); }? ? ? ? 需要注意的是,這個(gè)dev_driver_list驅(qū)動(dòng)鏈表是一個(gè)全局的配置結(jié)構(gòu), 是在預(yù)加載的時(shí)候創(chuàng)建的鏈表,只是用來(lái)說(shuō)明系統(tǒng)支持哪些pmd驅(qū)動(dòng)而已,是一個(gè)過(guò)渡鏈表。真實(shí)使用的時(shí)候,是不會(huì)使用這個(gè)鏈表的。真實(shí)使用時(shí),會(huì)把這個(gè)鏈表支持的驅(qū)動(dòng)類型注冊(cè)到另一個(gè)鏈表pci_driver_list中。這個(gè)pci_driver_list驅(qū)動(dòng)鏈表有什么用呢? 可以為每一個(gè)pci設(shè)備探測(cè)對(duì)應(yīng)的驅(qū)動(dòng),實(shí)際上就是遍歷這個(gè)驅(qū)動(dòng)。將過(guò)渡鏈表轉(zhuǎn)為真實(shí)的驅(qū)動(dòng)鏈表這個(gè)操作是在rte_eal_dev_init接口中完成的。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
int rte_eal_dev_init(void) {//注冊(cè)驅(qū)動(dòng)鏈表TAILQ_FOREACH(driver, &dev_driver_list, next) {//設(shè)備初始化接口,用于真正注冊(cè)驅(qū)動(dòng)。例如pmd_igb_drv,接口為rte_igb_pmd_initdriver->init(NULL, NULL);} }? ? ? 以e1000外卡為例,?struct rte_driver驅(qū)動(dòng)對(duì)象為pmd_igb_drv,他的init接口為rte_igb_pmd_init。由這個(gè)接口真正將驅(qū)動(dòng)注冊(cè)到pci_driver_list鏈表中。
static struct eth_driver rte_igb_pmd = {{.name = "rte_igb_pmd",.id_table = pci_id_igb_map,.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_INTR_LSC,},.eth_dev_init = eth_igb_dev_init,.dev_private_size = sizeof(struct e1000_adapter), };static int rte_igb_pmd_init(const char *name __rte_unused, const char *params __rte_unused) {rte_eth_driver_register(&rte_igb_pmd);return 0; }? ? ? ? ? ?可以看出struct eth_driver也類似于c++中的類,不同的驅(qū)動(dòng)具體實(shí)現(xiàn)這個(gè)對(duì)象,相當(dāng)于多態(tài)模型。可以看出最終是注冊(cè)到pci_driver_list鏈表中。這個(gè)鏈表中的驅(qū)動(dòng)是后面會(huì)被使用的,而不再是一個(gè)過(guò)渡的結(jié)構(gòu)。驅(qū)動(dòng)注冊(cè)這個(gè)過(guò)程是不是覺(jué)得有點(diǎn)繞,感覺(jué)兜了一大圈。覺(jué)得繞就對(duì)了,這個(gè)是dpdk實(shí)現(xiàn)方式,你也可以改造代碼,只需要一次就注冊(cè)到最終的驅(qū)動(dòng)鏈表就好了,沒(méi)必要通過(guò)中間鏈表來(lái)中轉(zhuǎn)。? ? ? ? ? ??
void rte_eth_driver_register(struct eth_driver *eth_drv) {eth_drv->pci_drv.devinit = rte_eth_dev_init;rte_eal_pci_register(ð_drv->pci_drv); }void rte_eal_pci_register(struct rte_pci_driver *driver) {TAILQ_INSERT_TAIL(&pci_driver_list, driver, next); }二、pci驅(qū)動(dòng)探測(cè)
? ? ? ? pci設(shè)備需要有驅(qū)動(dòng)的支持才能使用, 并不是每一種驅(qū)動(dòng)都適用于所有的pci設(shè)備,?例如網(wǎng)卡驅(qū)動(dòng)就不適用于SATA設(shè)備。因此需要為每一種pci設(shè)備探測(cè)支持的驅(qū)動(dòng)。為pci設(shè)備探測(cè)驅(qū)動(dòng)的入口為rte_eal_pci_probe,來(lái)看下這個(gè)接口的實(shí)現(xiàn)。其實(shí)就是遍歷上一篇文章提到的pci設(shè)備鏈表, 然后為每一個(gè)pci設(shè)備查找系統(tǒng)支持的驅(qū)動(dòng)。
int rte_eal_pci_probe(void) { //遍歷pci設(shè)備鏈表,為每一個(gè)pci設(shè)備查找驅(qū)動(dòng)TAILQ_FOREACH(dev, &pci_device_list, next) {ret = pci_probe_all_drivers(dev); } } static int pci_probe_all_drivers(struct rte_pci_device *dev) {//遍歷所有的驅(qū)動(dòng)鏈表,為某個(gè)pci設(shè)備查找對(duì)應(yīng)的驅(qū)動(dòng)TAILQ_FOREACH(dr, &pci_driver_list, next) {rc = rte_eal_pci_probe_one_driver(dr, dev);} }? ? ? ? ?每一個(gè)驅(qū)動(dòng)都有一個(gè)表id_table,表項(xiàng)記錄驅(qū)動(dòng)支持哪些pci設(shè)備。表項(xiàng)的內(nèi)容由pci的廠商id, pci設(shè)備id, pci子廠商等信息組成。每個(gè)pci設(shè)備就是根據(jù)自己的廠商id, 設(shè)備id等信息在每一個(gè)驅(qū)動(dòng)提供的id_table表查找當(dāng)前驅(qū)動(dòng)是否支持自己,如果這些都相等,則說(shuō)明pci找到了驅(qū)動(dòng)。找到了驅(qū)動(dòng),就將驅(qū)動(dòng)保存到pci設(shè)備結(jié)構(gòu)中,將pci與驅(qū)動(dòng)進(jìn)行關(guān)聯(lián),接著進(jìn)行驅(qū)動(dòng)的初始化流程。驅(qū)動(dòng)初始化會(huì)在另一個(gè)專題中詳細(xì)分析,這里就只需要知道整體流程就好了。
int rte_eal_pci_probe_one_driver(struct rte_pci_driver *dr, struct rte_pci_device *dev) {for (id_table = dr->id_table ; id_table->vendor_id != 0; id_table++){//檢查這個(gè)驅(qū)動(dòng)是否支持對(duì)應(yīng)的pci設(shè)備。要求廠商id, 設(shè)備id等必須匹配if (id_table->device_id != dev->id.device_id && id_table->device_id != PCI_ANY_ID){continue;}//需要將pci物理地址映射到出來(lái),使得通過(guò)uio訪問(wèn)pci設(shè)備if (dr->drv_flags & RTE_PCI_DRV_NEED_MAPPING) {ret = pci_map_device(dev);}//pci關(guān)聯(lián)這個(gè)驅(qū)動(dòng)dev->driver = dr;//驅(qū)動(dòng)初始化return dr->devinit(dr, dev);} }? ? ? ? 這里著重分析下pci設(shè)備地址映射的過(guò)程。
三、pci地址映射
? ? ? ? 所謂的pci地址映射,其實(shí)就是將pci設(shè)備提供給應(yīng)用層訪問(wèn)的物理地址映射成虛擬地址,這樣應(yīng)用層通過(guò)uio就可以訪問(wèn)這個(gè)虛擬地址,進(jìn)而訪問(wèn)pci設(shè)備。這里引入了uio的概念,就有必要先簡(jiǎn)單描述下。當(dāng)pci設(shè)備綁定uio驅(qū)動(dòng)后,uio驅(qū)動(dòng)在探測(cè)到有網(wǎng)卡綁定后,會(huì)在/dev設(shè)備下創(chuàng)建一個(gè)uio文件,例如/dev/uio3, 同時(shí)也會(huì)在pci設(shè)備目錄下創(chuàng)建相應(yīng)的uio目錄,例如/sys/bus/pci/devices/0000:02:06.0/uio目錄,這個(gè)uio目錄里面記錄了pci設(shè)備的映射信息,也就是上一篇文章提到的BAR寄存器,其實(shí)也是//sys/bus/pci/devices/0000:02:06.0/resource文件中記錄的映射信息。這樣uio目錄下的地址映射信息其實(shí)就是pci設(shè)備的映射信息,因此當(dāng)通過(guò)mmap地址映射到/dev/uio3后,就將pci物理地址映射到虛擬地址中,通過(guò)/dev/uio3就可以訪問(wèn)這個(gè)pci設(shè)備。
? ? ? ? pci地址映射與uio關(guān)聯(lián)是在pci_uio_map_resource接口中完成的, 下面來(lái)詳細(xì)分析下這個(gè)接口的實(shí)現(xiàn)過(guò)程。
1、在pci目錄下查找對(duì)應(yīng)的uio目錄,這個(gè)在pci綁定驅(qū)動(dòng)的時(shí)候,就會(huì)在pci目錄下創(chuàng)建uio目錄,以及在/dev目錄下創(chuàng)建uio文件。例如:? pci設(shè)備目錄下創(chuàng)建/sys/bus/pci/devices/0000:02:06.0/uio/uio3目錄;? ? /dev目錄下創(chuàng)建uio文件, /dev/uio3
int pci_uio_map_resource(struct rte_pci_device *dev) {//獲取某個(gè)pci設(shè)備對(duì)應(yīng)的uio目錄,例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3uio_num = pci_get_uio_dev(dev, dirname, sizeof(dirname));//dev/uio3snprintf(devname, sizeof(devname), "/dev/uio%u", uio_num); }? ? ? ? pci_get_uio_dev接口負(fù)責(zé)查找到uio目錄文件。需要注意的是,如果應(yīng)用層指定了需要?jiǎng)?chuàng)建uio文件,則內(nèi)部會(huì)調(diào)用pci_mknod_uio_dev接口在/dev目錄下重新創(chuàng)建uio文件,例如重新創(chuàng)建/dev/uio3?
static int pci_get_uio_dev(struct rte_pci_device *dev, char *dstbuf, unsigned int buflen) {//創(chuàng)建dev/uiox文件if (internal_config.create_uio_dev && pci_mknod_uio_dev(dstbuf, uio_num) < 0){RTE_LOG(WARNING, EAL, "Cannot create /dev/uio%u\n", uio_num);} } //sysfs_uio_path也就是網(wǎng)卡對(duì)于uio文件路徑,例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3 static int pci_mknod_uio_dev(const char *sysfs_uio_path, unsigned uio_num) {例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3f = fopen(filename, "r"); //讀取主從設(shè)備號(hào)ret = fscanf(f, "%d:%d", &major, &minor);//創(chuàng)建/dev/uiox文件,例如/dev/uio3snprintf(filename, sizeof(filename), "/dev/uio%u", uio_num);dev = makedev(major, minor);ret = mknod(filename, S_IFCHR | S_IRUSR | S_IWUSR, dev); }2、創(chuàng)建uio中斷事件,也就是pci網(wǎng)卡中斷事件。這里只負(fù)責(zé)創(chuàng)建,并沒(méi)有加入到epoll事件機(jī)制中,此時(shí)即便網(wǎng)卡有事件也還不會(huì)觸發(fā),直到在驅(qū)動(dòng)初始化接口eth_igb_dev_init那里會(huì)將這個(gè)dev/uio3中斷源加入到epoll事件機(jī)制。注冊(cè)到epoll后,如果網(wǎng)卡有事件發(fā)生,epoll事件模型就會(huì)返回,應(yīng)用層就能處理網(wǎng)卡中斷。需要注意的是,uio驅(qū)動(dòng)已經(jīng)將網(wǎng)卡中斷給關(guān)閉了,因此dpdk中斷是在應(yīng)用層實(shí)現(xiàn)的,以免頻繁硬件中斷導(dǎo)致上下文切換,占用cpu資源。這里的中斷指定是控制中斷,也就是給網(wǎng)卡的一些控制操作,例如設(shè)置全雙工半雙工, 協(xié)商速率設(shè)置等; 而網(wǎng)卡報(bào)文的高速轉(zhuǎn)發(fā),應(yīng)用層還是使用輪詢的方式,報(bào)文轉(zhuǎn)發(fā)就跟中斷沒(méi)有關(guān)系了。
int pci_uio_map_resource(struct rte_pci_device *dev) {//創(chuàng)建uio中斷源/dev/uio3dev->intr_handle.fd = open(devname, O_RDWR);dev->intr_handle.type = RTE_INTR_HANDLE_UIO; } int eth_igb_dev_init(__attribute__((unused)) struct eth_driver *eth_drv, struct rte_eth_dev *eth_dev) {//pci中斷源注冊(cè)到中斷事件鏈表中,內(nèi)部會(huì)將中斷源添加到epoll事件機(jī)制中rte_intr_callback_register(&(pci_dev->intr_handle),eth_igb_interrupt_handler, (void *)eth_dev); }3、掃描pci目錄下的uio目錄,看下uio目錄下映射了哪些地址給應(yīng)用層訪問(wèn)。例如掃描/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps目錄,發(fā)現(xiàn)這個(gè)目錄下有map0, map1兩個(gè)子目錄,這兩個(gè)子目錄記錄了兩個(gè)不同的地址空間,用于提供給應(yīng)用層訪問(wèn)。pci_uio_get_mappings接口要做的事情就是掃描所有的map,將映射的地址信息保存起來(lái)。需要注意的是,這些地址是pci設(shè)備在物理內(nèi)存上的真實(shí)地址,而不是虛擬地址。
root@apelife:/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps# ls map0 map1 root@apelife:/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps/map0# ls addr name offset size?
int pci_uio_map_resource(struct rte_pci_device *dev) {//獲取pci映射的地址范圍nb_maps = pci_uio_get_mappings(dirname, uio_res->maps,RTE_DIM(uio_res->maps));uio_res->nb_maps = nb_maps; }//獲取pci映射的地址范圍, 也就是掃描/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps目錄,看下 //pci設(shè)備映射了哪些地址訪問(wèn)給應(yīng)用層訪問(wèn) //devname, pci設(shè)備關(guān)聯(lián)的uio路徑,例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3 static int pci_uio_get_mappings(const char *devname, struct pci_map maps[], int nb_maps) {for (i = 0; i != nb_maps; i++){snprintf(dirname, sizeof(dirname), "%s/maps/map%u", devname, i);//獲取地址偏移snprintf(filename, sizeof(filename),"%s/offset", dirname);pci_parse_sysfs_value(filename, &offset);//獲取地址大小snprintf(filename, sizeof(filename), "%s/size", dirname);pci_parse_sysfs_value(filename, &size);//獲取物理地址的位置snprintf(filename, sizeof(filename), "%s/addr", dirname);pci_parse_sysfs_value(filename, &maps[i].phaddr);//保存地址大小與偏移maps[i].offset = offset;maps[i].size = size;} }4、pci設(shè)備目錄下的資源文件,例如/sys/bus/pci/devices/0000:02:06.0/resource記錄了pci的BAR寄存器的信息,里面的內(nèi)容為pci設(shè)備提供給應(yīng)用層訪問(wèn)的物理地址空間; 而pci設(shè)備目錄下的uio文件,例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps也記錄了pci設(shè)備提供給應(yīng)用層訪問(wèn)的地址空間,這兩者有什么關(guān)系呢?
? ? ? ? 通常在系統(tǒng)引導(dǎo)的時(shí)候,系統(tǒng)探測(cè)到pci設(shè)備后,會(huì)創(chuàng)建pci目錄,存放pci設(shè)備的相關(guān)信息,包括resource文件。在pci設(shè)備綁定uio驅(qū)動(dòng)時(shí),uio驅(qū)動(dòng)會(huì)在pci目錄下創(chuàng)建uio子目錄,同時(shí)會(huì)將pci里面的部分文件信息拷貝到uio子目錄下。例如會(huì)讀取resouce文件的內(nèi)容,將一部分提供給應(yīng)用層訪問(wèn)的物理地址,在uio子目錄下創(chuàng)建map子目錄,記錄這些物理地址信息。例如resouce文件記錄了5個(gè)提供給應(yīng)用層訪問(wèn)的物理地址空間,則uio驅(qū)動(dòng)有可能會(huì)在map子目錄下創(chuàng)建map0,map1,map2三個(gè)目錄,用于記錄5個(gè)提供給應(yīng)用層訪問(wèn)的物理地址中的三個(gè)。那為什么不是5個(gè)都提供呢?這個(gè)我也還沒(méi)理清楚。
? ? ? ? 也就是說(shuō)uio目錄里面的map子目錄記錄提供給應(yīng)用層訪問(wèn)的物理地址,和pci設(shè)備目錄resource文件記錄提供給應(yīng)用層訪問(wèn)的物理地址是相等的。這樣對(duì)uio文件進(jìn)行mmap地址映射后,當(dāng)訪問(wèn)/dev/uiox文件,就相當(dāng)于訪問(wèn)pci設(shè)備提供給應(yīng)用層訪問(wèn)的物理空間,進(jìn)而訪問(wèn)這個(gè)pci設(shè)備。通過(guò)這種方式應(yīng)用層直接訪問(wèn)/dev/uiox文件,就可以訪問(wèn)網(wǎng)卡資源了。
? ? ? ??pci_uio_map_resource接口負(fù)責(zé)地址映射的操作,映射后將保存這個(gè)映射后的虛擬地址。需要注意的是,不管一個(gè)pci設(shè)備提供了多少個(gè)給應(yīng)用層訪問(wèn)的物理地址,都是通過(guò)同一個(gè)/dev/uiox文件進(jìn)行映射的。另外還需要注意的是,應(yīng)用層已經(jīng)知道了pci提供的物理地址,那為什么還要進(jìn)行地址映射? 是因?yàn)閼?yīng)用層無(wú)法直接訪問(wèn)pci設(shè)備的物理內(nèi)存,只能通過(guò)虛擬地址進(jìn)行訪問(wèn)。
int pci_uio_map_resource(struct rte_pci_device *dev) {//為pci的每一個(gè)提供給應(yīng)用層訪問(wèn)的物理地址做映射for (i = 0; i != PCI_MAX_RESOURCE; i++) {//查找pci提供給應(yīng)用層訪問(wèn)的物理地址和/sys/bus/pci/devices/0000:02:06.0/uio/uio3/map的物理地址相等的地址進(jìn)行映射for (j = 0; j != nb_maps && (phaddr != maps[j].phaddr || dev->mem_resource[i].len != maps[j].size); j++);//找到則進(jìn)行映射if (j != nb_maps){//例如/dev/uioxfd = open(devname, O_RDWR);//共享內(nèi)存映射,通過(guò)mmap方式mapaddr = pci_map_resource(pci_map_addr, fd, (off_t)offset, (size_t)maps[j].size);//保存映射后的虛擬地址maps[j].addr = mapaddr;maps[j].offset = offset;dev->mem_resource[i].addr = mapaddr;}} }5、最后就是保存pci設(shè)備地址映射后的資源信息,每一個(gè)pci設(shè)備都有一個(gè)這樣的資源結(jié)構(gòu),并插入到pci資源鏈表pci_res_list。
int pci_uio_map_resource(struct rte_pci_device *dev) {uio_res = rte_zmalloc("UIO_RES", sizeof(*uio_res), 0);//將uio資源插入到鏈表,此時(shí)已經(jīng)完成了pci設(shè)備的地址映射TAILQ_INSERT_TAIL(pci_res_list, uio_res, next); }? ? ? ? 這個(gè)鏈表有什么作用呢?主要是給dpdk從線程用的, dpdk主線程負(fù)責(zé)將每個(gè)pci設(shè)備物理地址映射成虛擬地址,并將映射后的資源信息保存到這個(gè)鏈表中。從線程就沒(méi)有必要在對(duì)每個(gè)pci設(shè)備進(jìn)行地址映射了,直接讀取這個(gè)鏈表就知道每個(gè)pci設(shè)備提供給應(yīng)用層訪問(wèn)的物理地址是多少,映射后的虛擬地址是多少。這個(gè)可以從pci_uio_map_secondary接口看出,這個(gè)就是從線程調(diào)用的接口。
? ? ? ? 到目前為止,關(guān)于pci設(shè)備探測(cè)驅(qū)動(dòng)以及pci設(shè)備與uio的之間的關(guān)系已經(jīng)分析完成了。 至于驅(qū)動(dòng)初始化的邏輯,則在后續(xù)會(huì)有專門的文章來(lái)分析。
總結(jié)
以上是生活随笔為你收集整理的dpdk pci驱动探测的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux的PCI驱动介绍(入门)
- 下一篇: 前端的长度单位有哪些