日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

dpdk pci驱动探测

發布時間:2023/12/18 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 dpdk pci驱动探测 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? 上一篇文章已經介紹了pci設備的背景知識, 現在我們來分析下pci設備是如何探測到支持的驅動,進而與驅動進行關聯;pci與驅動的解除綁定;pci設備與uio設備的關聯。

一、pci驅動注冊

? ? ? ??網卡驅動的注冊使用了一種奇技淫巧的方法,使用GCC attribute擴展屬性的constructor屬性,使得網卡驅動的注冊在程序main函數之前就執行了。此時在main函數執行前,就已經把系統支持的驅動通過rte_eal_driver_register注冊到驅動鏈表dev_driver_list中。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

struct rte_driver {TAILQ_ENTRY(rte_driver) next; enum pmd_type type; /**< 驅動類型 */const char *name; /**< 驅動名 */rte_dev_init_t *init; /**< 設備初始化函數 */ }; 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++中的多態,驅動鏈表節點strcuct rte_driver是一個類, 每一種驅動都具體實現這個類,不同的驅動實現方式不一樣,可以自定義init初始化接口。例如igb驅動與ixgbe驅動,這兩種驅動都具體實現了這個類。每一種驅動都實現這個類,相當于一個多態模型。最后通過調用PMD_REGISTER_DRIVER注冊到驅動鏈表dev_driver_list。 調用rte_eal_driver_unregister則可以從驅動鏈表中卸載。

void rte_eal_driver_register(struct rte_driver *driver) {TAILQ_INSERT_TAIL(&dev_driver_list, driver, next); }

? ? ? ? 需要注意的是,這個dev_driver_list驅動鏈表是一個全局的配置結構, 是在預加載的時候創建的鏈表,只是用來說明系統支持哪些pmd驅動而已,是一個過渡鏈表。真實使用的時候,是不會使用這個鏈表的。真實使用時,會把這個鏈表支持的驅動類型注冊到另一個鏈表pci_driver_list中。這個pci_driver_list驅動鏈表有什么用呢? 可以為每一個pci設備探測對應的驅動,實際上就是遍歷這個驅動。將過渡鏈表轉為真實的驅動鏈表這個操作是在rte_eal_dev_init接口中完成的。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

int rte_eal_dev_init(void) {//注冊驅動鏈表TAILQ_FOREACH(driver, &dev_driver_list, next) {//設備初始化接口,用于真正注冊驅動。例如pmd_igb_drv,接口為rte_igb_pmd_initdriver->init(NULL, NULL);} }

? ? ? 以e1000外卡為例,?struct rte_driver驅動對象為pmd_igb_drv,他的init接口為rte_igb_pmd_init。由這個接口真正將驅動注冊到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++中的類,不同的驅動具體實現這個對象,相當于多態模型??梢钥闯鲎罱K是注冊到pci_driver_list鏈表中。這個鏈表中的驅動是后面會被使用的,而不再是一個過渡的結構。驅動注冊這個過程是不是覺得有點繞,感覺兜了一大圈。覺得繞就對了,這個是dpdk實現方式,你也可以改造代碼,只需要一次就注冊到最終的驅動鏈表就好了,沒必要通過中間鏈表來中轉。? ? ? ? ? ??

void rte_eth_driver_register(struct eth_driver *eth_drv) {eth_drv->pci_drv.devinit = rte_eth_dev_init;rte_eal_pci_register(&eth_drv->pci_drv); }void rte_eal_pci_register(struct rte_pci_driver *driver) {TAILQ_INSERT_TAIL(&pci_driver_list, driver, next); }

二、pci驅動探測

? ? ? ? pci設備需要有驅動的支持才能使用, 并不是每一種驅動都適用于所有的pci設備,?例如網卡驅動就不適用于SATA設備。因此需要為每一種pci設備探測支持的驅動。為pci設備探測驅動的入口為rte_eal_pci_probe,來看下這個接口的實現。其實就是遍歷上一篇文章提到的pci設備鏈表, 然后為每一個pci設備查找系統支持的驅動。

int rte_eal_pci_probe(void) { //遍歷pci設備鏈表,為每一個pci設備查找驅動TAILQ_FOREACH(dev, &pci_device_list, next) {ret = pci_probe_all_drivers(dev); } } static int pci_probe_all_drivers(struct rte_pci_device *dev) {//遍歷所有的驅動鏈表,為某個pci設備查找對應的驅動TAILQ_FOREACH(dr, &pci_driver_list, next) {rc = rte_eal_pci_probe_one_driver(dr, dev);} }

? ? ? ? ?每一個驅動都有一個表id_table,表項記錄驅動支持哪些pci設備。表項的內容由pci的廠商id, pci設備id, pci子廠商等信息組成。每個pci設備就是根據自己的廠商id, 設備id等信息在每一個驅動提供的id_table表查找當前驅動是否支持自己,如果這些都相等,則說明pci找到了驅動。找到了驅動,就將驅動保存到pci設備結構中,將pci與驅動進行關聯,接著進行驅動的初始化流程。驅動初始化會在另一個專題中詳細分析,這里就只需要知道整體流程就好了。

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++){//檢查這個驅動是否支持對應的pci設備。要求廠商id, 設備id等必須匹配if (id_table->device_id != dev->id.device_id && id_table->device_id != PCI_ANY_ID){continue;}//需要將pci物理地址映射到出來,使得通過uio訪問pci設備if (dr->drv_flags & RTE_PCI_DRV_NEED_MAPPING) {ret = pci_map_device(dev);}//pci關聯這個驅動dev->driver = dr;//驅動初始化return dr->devinit(dr, dev);} }

? ? ? ? 這里著重分析下pci設備地址映射的過程。

三、pci地址映射

? ? ? ? 所謂的pci地址映射,其實就是將pci設備提供給應用層訪問的物理地址映射成虛擬地址,這樣應用層通過uio就可以訪問這個虛擬地址,進而訪問pci設備。這里引入了uio的概念,就有必要先簡單描述下。當pci設備綁定uio驅動后,uio驅動在探測到有網卡綁定后,會在/dev設備下創建一個uio文件,例如/dev/uio3, 同時也會在pci設備目錄下創建相應的uio目錄,例如/sys/bus/pci/devices/0000:02:06.0/uio目錄,這個uio目錄里面記錄了pci設備的映射信息,也就是上一篇文章提到的BAR寄存器,其實也是//sys/bus/pci/devices/0000:02:06.0/resource文件中記錄的映射信息。這樣uio目錄下的地址映射信息其實就是pci設備的映射信息,因此當通過mmap地址映射到/dev/uio3后,就將pci物理地址映射到虛擬地址中,通過/dev/uio3就可以訪問這個pci設備。

? ? ? ? pci地址映射與uio關聯是在pci_uio_map_resource接口中完成的, 下面來詳細分析下這個接口的實現過程。

1、在pci目錄下查找對應的uio目錄,這個在pci綁定驅動的時候,就會在pci目錄下創建uio目錄,以及在/dev目錄下創建uio文件。例如:? pci設備目錄下創建/sys/bus/pci/devices/0000:02:06.0/uio/uio3目錄;? ? /dev目錄下創建uio文件, /dev/uio3

int pci_uio_map_resource(struct rte_pci_device *dev) {//獲取某個pci設備對應的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接口負責查找到uio目錄文件。需要注意的是,如果應用層指定了需要創建uio文件,則內部會調用pci_mknod_uio_dev接口在/dev目錄下重新創建uio文件,例如重新創建/dev/uio3?

static int pci_get_uio_dev(struct rte_pci_device *dev, char *dstbuf, unsigned int buflen) {//創建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也就是網卡對于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"); //讀取主從設備號ret = fscanf(f, "%d:%d", &major, &minor);//創建/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、創建uio中斷事件,也就是pci網卡中斷事件。這里只負責創建,并沒有加入到epoll事件機制中,此時即便網卡有事件也還不會觸發,直到在驅動初始化接口eth_igb_dev_init那里會將這個dev/uio3中斷源加入到epoll事件機制。注冊到epoll后,如果網卡有事件發生,epoll事件模型就會返回,應用層就能處理網卡中斷。需要注意的是,uio驅動已經將網卡中斷給關閉了,因此dpdk中斷是在應用層實現的,以免頻繁硬件中斷導致上下文切換,占用cpu資源。這里的中斷指定是控制中斷,也就是給網卡的一些控制操作,例如設置全雙工半雙工, 協商速率設置等; 而網卡報文的高速轉發,應用層還是使用輪詢的方式,報文轉發就跟中斷沒有關系了。

int pci_uio_map_resource(struct rte_pci_device *dev) {//創建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中斷源注冊到中斷事件鏈表中,內部會將中斷源添加到epoll事件機制中rte_intr_callback_register(&(pci_dev->intr_handle),eth_igb_interrupt_handler, (void *)eth_dev); }

3、掃描pci目錄下的uio目錄,看下uio目錄下映射了哪些地址給應用層訪問。例如掃描/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps目錄,發現這個目錄下有map0, map1兩個子目錄,這兩個子目錄記錄了兩個不同的地址空間,用于提供給應用層訪問。pci_uio_get_mappings接口要做的事情就是掃描所有的map,將映射的地址信息保存起來。需要注意的是,這些地址是pci設備在物理內存上的真實地址,而不是虛擬地址。

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設備映射了哪些地址訪問給應用層訪問 //devname, pci設備關聯的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設備目錄下的資源文件,例如/sys/bus/pci/devices/0000:02:06.0/resource記錄了pci的BAR寄存器的信息,里面的內容為pci設備提供給應用層訪問的物理地址空間; 而pci設備目錄下的uio文件,例如/sys/bus/pci/devices/0000:02:06.0/uio/uio3/maps也記錄了pci設備提供給應用層訪問的地址空間,這兩者有什么關系呢?

? ? ? ? 通常在系統引導的時候,系統探測到pci設備后,會創建pci目錄,存放pci設備的相關信息,包括resource文件。在pci設備綁定uio驅動時,uio驅動會在pci目錄下創建uio子目錄,同時會將pci里面的部分文件信息拷貝到uio子目錄下。例如會讀取resouce文件的內容,將一部分提供給應用層訪問的物理地址,在uio子目錄下創建map子目錄,記錄這些物理地址信息。例如resouce文件記錄了5個提供給應用層訪問的物理地址空間,則uio驅動有可能會在map子目錄下創建map0,map1,map2三個目錄,用于記錄5個提供給應用層訪問的物理地址中的三個。那為什么不是5個都提供呢?這個我也還沒理清楚。

? ? ? ? 也就是說uio目錄里面的map子目錄記錄提供給應用層訪問的物理地址,和pci設備目錄resource文件記錄提供給應用層訪問的物理地址是相等的。這樣對uio文件進行mmap地址映射后,當訪問/dev/uiox文件,就相當于訪問pci設備提供給應用層訪問的物理空間,進而訪問這個pci設備。通過這種方式應用層直接訪問/dev/uiox文件,就可以訪問網卡資源了。

? ? ? ??pci_uio_map_resource接口負責地址映射的操作,映射后將保存這個映射后的虛擬地址。需要注意的是,不管一個pci設備提供了多少個給應用層訪問的物理地址,都是通過同一個/dev/uiox文件進行映射的。另外還需要注意的是,應用層已經知道了pci提供的物理地址,那為什么還要進行地址映射? 是因為應用層無法直接訪問pci設備的物理內存,只能通過虛擬地址進行訪問。

int pci_uio_map_resource(struct rte_pci_device *dev) {//為pci的每一個提供給應用層訪問的物理地址做映射for (i = 0; i != PCI_MAX_RESOURCE; i++) {//查找pci提供給應用層訪問的物理地址和/sys/bus/pci/devices/0000:02:06.0/uio/uio3/map的物理地址相等的地址進行映射for (j = 0; j != nb_maps && (phaddr != maps[j].phaddr || dev->mem_resource[i].len != maps[j].size); j++);//找到則進行映射if (j != nb_maps){//例如/dev/uioxfd = open(devname, O_RDWR);//共享內存映射,通過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設備地址映射后的資源信息,每一個pci設備都有一個這樣的資源結構,并插入到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資源插入到鏈表,此時已經完成了pci設備的地址映射TAILQ_INSERT_TAIL(pci_res_list, uio_res, next); }

? ? ? ? 這個鏈表有什么作用呢?主要是給dpdk從線程用的, dpdk主線程負責將每個pci設備物理地址映射成虛擬地址,并將映射后的資源信息保存到這個鏈表中。從線程就沒有必要在對每個pci設備進行地址映射了,直接讀取這個鏈表就知道每個pci設備提供給應用層訪問的物理地址是多少,映射后的虛擬地址是多少。這個可以從pci_uio_map_secondary接口看出,這個就是從線程調用的接口。

? ? ? ? 到目前為止,關于pci設備探測驅動以及pci設備與uio的之間的關系已經分析完成了。 至于驅動初始化的邏輯,則在后續會有專門的文章來分析。

總結

以上是生活随笔為你收集整理的dpdk pci驱动探测的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。