DPDK — IGB_UIO,與 UIO Framework 進行交互的內核模塊


目錄

前文列表

DPDK — 安裝部署
DPDK — 數據平面開發技術
DPDK — 架構解析

IGB_UIO

PMD 是 DPDK 在用戶態實現的網卡驅動程序,但實際上還是會依賴於內核提供的支持。其中 UIO 內核模塊,是內核提供的用戶態驅動框架,而 IGB_UIO(igb_uio.ko)是 DPDK 用於與 UIO 交互的內核模塊,通過 IGB_UIO 來 bind 指定的 PCI 網卡設備給到用戶態的 PMD 使用。IGB_UIO 借助 UIO 技術來截獲中斷,並重設中斷回調行為,從而繞過內核協議棧后續的處理流程。並且 IGB_UIO 會在內核初始化的過程中將網卡硬件寄存器映射到用戶態。

IGB_UIO 內核模塊主要功能之一就是用於注冊一個 PCI 設備。通過 DPDK 提供個 Python 腳本 dpdk-devbind 來完成,當執行 dpdk-devbind 來 bind 網卡時,會通過 sysfs 與內核交互,讓內核使用指定的驅動程序(e.g. igb_uio)來綁定網卡。

在 Linux 中,將設備(Device)與驅動(Driver)進行綁定的方法有兩種:

  1. 配置設備,讓其選擇驅動:向 /sys/bus/pci/devices/{pci id}/driver_override 寫入指定驅動的名稱。
  2. 配置驅動,讓其支持新的 PCI 設備:向 /sys/bus/pci/drivers/igb_uio/new_id 寫入要 bind 的網卡設備的 PCI ID(e.g. 8086 10f5,格式為:設備廠商號 設備號)。

按照內核的文檔 https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci 中提到,這兩個動作都會促使驅動程序 bind 新的網卡設備,而 DPDK 顯然是使用了第 2 種方式。

IGB_UIO 內核模塊的另一個主要功能就是讓用於態的 PMD 網卡驅動程序得以與 UIO 進行交互

  1. 調用 igbuio_setup_bars,設置 uio_info 的 uio_mem 和 uio_port。
  2. 設置 uio_info 的其他成員。
  3. 調用 uio_register_device,注冊 UIO 設備。
  4. 打開 UIO 設備並注冊中斷。
  5. 調用 uio_event_notify,將注冊的 UIO 設備的 “內存空間” 映射到用戶態的應用空間。其 mmap 的函數為 uio_mmap。至此,UIO 就可以讓 PMD 驅動程序在用戶態應用層訪問設備的大部分資源了。
  6. 應用層 UIO 初始化。同時,DPDK 還需要把 PCI 設備的 BAR 映射到應用層。在 pci_uio_map_resource 函數中會調用 pci_uio_map_resource_by_index 做資源映射。
  7. 在 PMD 驅動程序中,DPDK 應用程序,會調用 rte_eth_rx_burst 讀取數據報文。如果網卡接收 Buffer 的描述符表示已經完成一個報文的接收(e.g. 有 E1000_RXD_STAT_DD 標志),則 rte_mbuf_raw_alloc 一個 mbuf 進行處理。
  8. 對應 RTC 模型的 DPDK 應用程序來說,就是不斷的調用 rte_eth_rx_burst 去詢問網卡是否有新的報文。如果有,就取走所有的報文或達到參數 nb_pkts 的上限。然后進行報文處理,處理完畢,再次循環。

IGB_UIO 是如何注冊 PCI 設備的?

Linux 中的 PCI 設備

在這里插入圖片描述

  • config:PCI 設備的配置空間,二進制,可讀寫。
  • device:PCI Device ID,只讀。PCI 設備的標識,很重要。
  • vendor:PCI vendor ID,比如:Intel 為 0x8086。
  • driver:為 PCI 設備采用的驅動目錄的軟連接,真正的目錄位於 /sys/bus/pci/drivers/ 下。上圖可以參數當前這個 PCI 設備使用的是 igb_uio 驅動。
  • enable:設備是否正常使能,可讀寫;
  • irq:被分到的中斷號,只讀;
  • local_cpulist:這個網卡的內存空間位於和同處於一個 NUMA 節點上的 CPUs 有哪些,只讀。協助 NUMA 親和性的實現。
  • local_cpu:和 local_cpulist 的作用一樣,不過是以掩碼的方式給出。
  • numa_node:說明 PCI 設備屬於哪一個 NUMA node,只讀。
  • resource:PCI 設備的 BAR 記錄,只讀。
  • resource0…N:某一個 PCI BAR 空間,二進制,只讀,可進行 mmap() 映射。如果用戶進程希望操作 PCI 設備就必須通過 mmap() 這個resource0…N。
  • sriov_numfs:用於設定 SR-IOV 網卡的 VF 數量。
  • sriov_totalvfs:作用與 sriov_numfs 相同,設定這個 PCI 設備一共可以申請多少個 VF。
  • subsystem_device:PCI 子系統設備 ID,只讀。
  • subsystem_vendor:PCI 子系統生產商 ID,只讀。

PCI 的 BAR(基地址)

程序要操作一個外設,首先需要找到它的寄存器並對其進行配置,而找到寄存器的前提是拿到外設的基地址,即:通過 “基地址+寄存器偏移” 就能找到寄存器所在的地址,然后就可以配置了。

下圖為一個 PCI 設備的配置空間,其中,基地址(Bare Address Registers,BAR)是最重要的部分,在 0x0010 ~ 0x0028 這 24Byte 中,分布着 6 個 PCI BAR。PCI 設備配置空間的信息,在系統啟動時,就已經被解析完成了,並以文件系統的方式供用戶態程序讀取。
在這里插入圖片描述
首先思考一個問題:為什么 PCI 設備需要 6 個 BAR 呢?

在這里插入圖片描述
上圖中藍色部分進行了說明,PCF 設備的 6 個 BAR 允許設備為不同的目的提供不同的區域(Region)。結合 Intel 82599 型號網卡的 datasheet 可以得知:

在這里插入圖片描述

其擁有的 6 個 BAR 被分成了三塊區域:

  1. Memory BAR:內存 BAR,標志着這塊 BAR 空間位於的內存空間。這段內存空間在通過 mmap() 映射后,用戶進程即可以直接訪問。
  2. I/O BAR:IO BAR 空間,標志着這塊 BAR 空間位於的 IO 空間,用戶進程對其的訪問不能像 Memory BAR 那樣 mmap() 映射之后即可直接訪問。而是必須要通過專門的操作來進行讀寫。
  3. MSI-X BAR:這個 BAR 空間主要是用來配置 MSI-X 中斷向量。

那么,為什么 6 個 BAR 只有 3 個 Region 呢?

這是因為每個 BAR 為 32bit,而 Intel 82599 的一個 Region 需要 64bit。

在這里插入圖片描述

相對的,再看一款低端網卡 I350 的 datasheet:

在這里插入圖片描述

對於 I350 這種低端的千兆網卡,可以將其配置為工作在 32bit 或 64bit 模式下,而 Intel 82599 這種萬兆的卡,就只能工作在 64bit 模式下了。

再一個問題:程序訪問 PCI 的時候是以 Memory BAR 還是 I/O BAR 為基准的呢?

首先我們要知道為什么會有 Memory 空間和 I/O 空間的區別:

在這里插入圖片描述

在 x86 體系架構下,外設是進行獨立編址的,如上圖所示,因此就出現了 Memory 空間 和 IO 空間的區別。另外,也可以看出訪問外設其實也具有兩種方式:

  1. 一種是通過 I/O 空間用專有的指令進行訪問。例如:in、out 指令,而端口號表示了外設的寄存器地址。Intel x86 語法中的 in、out 指令格式如下:
IN 累加器, {端口號 | DX}
OUT {端口號 | DX}, 累加器
  1. 另外一種便是訪問內存空間,而訪問內存空間就相對而言要容易的多。

那么為什么外設需要擁有兩個不同的空間呢?這里是由於外設通常會自帶一個 “存儲器”,即外設寄存器,對應了上述的 I/O 空間。這樣通過內存映射之后 CPU 就可以通過 I/O 指令來操作外設的寄存器了。

綜上,我們就知道了:想要操作一個 PCI 設備,那么就需要獲取到 PCI 設備的 Memory BAR 或 IO BAR。其中 Memory BAR 是必須的,而 IO BAR 是可選的。

IGB_UIO 如何獲得 PCI 的 Memory BAR?

這個問題的答案其實在上文中已有提到:當執行 dpdk-devbind 來 bind 網卡時,會通過 sysfs 與內核交互,讓內核使用指定的驅動程序(e.g. igb_uio)來綁定網卡。

打開 dpdk-18.08/drivers/bus/pci/linux/pci.c 可以看到以下內容:

#define PCI_MAX_RESOURCE 6
/* * PCI 掃描文件系統下的 resource 文件 * @param filename: 通常為 /sys/bus/pci/devices/{pci_addr}/resource 文件 * @param dev[out]: dpdk 中對一個 PCI 設備的抽象 */
static int
pci_parse_sysfs_resource(const char *filename, struct rte_pci_device *dev)
{
    FILE *f;
    char buf[BUFSIZ];
    int i;
    uint64_t phys_addr, end_addr, flags;

    f = fopen(filename, "r"); // 先打開 resource 文件,只讀
    if (f == NULL) {
        RTE_LOG(ERR, EAL, "Cannot open sysfs resource\n");
        return -1;
    }
    // 掃描 6 次,因為 PCI 最多有 6 個 BAR
    for (i = 0; i<PCI_MAX_RESOURCE; i++) {

        if (fgets(buf, sizeof(buf), f) == NULL) {
            RTE_LOG(ERR, EAL,
                "%s(): cannot read resource\n", __func__);
            goto error;
        }
        // 掃描 resource 文件拿到 BAR
        if (pci_parse_one_sysfs_resource(buf, sizeof(buf), &phys_addr,
                &end_addr, &flags) < 0)
            goto error;
        // 如果是 Memory BAR,則進行記錄
        if (flags & IORESOURCE_MEM) {
            dev->mem_resource[i].phys_addr = phys_addr;
            dev->mem_resource[i].len = end_addr - phys_addr + 1;
            /* not mapped for now */
            dev->mem_resource[i].addr = NULL;
        }
    }
    fclose(f);
    return 0;

error:
    fclose(f);
    return -1;
}

/* * 掃描 PCI resource 文件中的某一行 * @param line: 某一行 * @param len: 長度,為第一個參數字符串的長度 * @param phys_addr[out]: PCI BAR 的起始地址,這個地址要 mmap() 才能用 * @param end_addr[out]: PCI BAR 的結束地址 * @param flags[out]: PCI BAR 的標志 */
int
pci_parse_one_sysfs_resource(char *line, size_t len, uint64_t *phys_addr,
    uint64_t *end_addr, uint64_t *flags)
{
    union pci_resource_info {
        struct {
            char *phys_addr;
            char *end_addr;
            char *flags;
        };
        char *ptrs[PCI_RESOURCE_FMT_NVAL];
    } res_info;
    // 字符串處理
    if (rte_strsplit(line, len, res_info.ptrs, 3, ' ') != 3) {
        RTE_LOG(ERR, EAL,
            "%s(): bad resource format\n", __func__);
        return -1;
    }
    errno = 0;
    // 字符串處理,拿到 PCI BAR 起始地址、PCI BAR 結束地址、PCI BAR 標志
    *phys_addr = strtoull(res_info.phys_addr, NULL, 16);
    *end_addr = strtoull(res_info.end_addr, NULL, 16);
    *flags = strtoull(res_info.flags, NULL, 16);
    if (errno != 0) {
        RTE_LOG(ERR, EAL,
            "%s(): bad resource format\n", __func__);
        return -1;
    }

    return 0;
}

這段代碼的邏輯很簡單,就是掃描某個 PCI 設備的 resource 文件並獲得 Memory BAR。e.g.

$ cat /sys/bus/pci/devices/0000:00:08.0/resource
0x0000000000001000 0x000000000000103f 0x0000000000040101
0x00000000c0040000 0x00000000c0040fff 0x0000000000040200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000440000000 0x0000000440003fff 0x000000000014220c
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000c0000000 0x00000000c003ffff 0x000000000004e200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000

前 6 行為 PCI 設備的 6 個 BAR,還是以 Intel 82599 為例,前兩個 BAR 為 Memory BAR,中間兩個 BAR 為 IO BAR,最后兩個 BAR 為 MSI-X BAR。其中,每個 BAR 又分為 3 列:

  1. 第 1 列為 PCI BAR 的起始地址
  2. 第 2 列為 PCI BAR 的終止地址
  3. 第 3 列為 PCI BAR 的標識

再看一段代碼 dpdk-18.08/drivers/bus/pci/linux/pci_uio.c:

/* * 用於映射 resource 資源,並獲取 PCI BAR * @param dev:DPDK 中關於某一個 PCI 設備的抽象實例 * @param res_id:說明要獲取第幾個 BAR * @param uio_res:用來存放 PCI BAR 資源的結構 * @param map_idx、uio_res:數組的計數器 */

int
pci_uio_map_resource_by_index(struct rte_pci_device *dev, int res_idx,
        struct mapped_pci_resource *uio_res, int map_idx)
{
    ..... // 省略
    // 打開 /dev/bus/pci/devices/{pci_addr}/resource0..N 文件
    if (!wc_activate || fd < 0) {
        snprintf(devname, sizeof(devname),
            "%s/" PCI_PRI_FMT "/resource%d",
            rte_pci_get_sysfs_path(),
            loc->domain, loc->bus, loc->devid,
            loc->function, res_idx);

        /* then try to map resource file */
        fd = open(devname, O_RDWR);
        if (fd < 0) {
            RTE_LOG(ERR, EAL, "Cannot open %s: %s\n",
                devname, strerror(errno));
            goto error;
        }
    }

    /* try mapping somewhere close to the end of hugepages */
    if (pci_map_addr == NULL)
        pci_map_addr = pci_find_max_end_va();
    // 進行 mmap() 映射,拿到 PCI BAR 在進程虛擬空間下的地址
    mapaddr = pci_map_resource(pci_map_addr, fd, 0,
            (size_t)dev->mem_resource[res_idx].len, 0);
    close(fd);
    if (mapaddr == MAP_FAILED)
        goto error;

    pci_map_addr = RTE_PTR_ADD(mapaddr,
            (size_t)dev->mem_resource[res_idx].len);
        // 將拿到的 PCI BAR 映射至進程虛擬空間內的地址存起來
    maps[map_idx].phaddr = dev->mem_resource[res_idx].phys_addr;
    maps[map_idx].size = dev->mem_resource[res_idx].len;
    maps[map_idx].addr = mapaddr;
    maps[map_idx].offset = 0;
    strcpy(maps[map_idx].path, devname);
    dev->mem_resource[res_idx].addr = mapaddr;

    return 0;

error:
    rte_free(maps[map_idx].path);
    return -1;
}


/* * 對 pci/resource0..N 進行 mmap(),將 PCI BAR 空間通過 mmap 的方式映射到進程內部的虛擬空間,供用戶態應用來操作設備 */
void *
pci_map_resource(void *requested_addr, int fd, off_t offset, size_t size,
         int additional_flags)
{
    void *mapaddr;

    // 核心便是這句 mmap,其中要注意的是 offset 必須為 0
    mapaddr = mmap(requested_addr, size, PROT_READ | PROT_WRITE,
            MAP_SHARED | additional_flags, fd, offset);
    if (mapaddr == MAP_FAILED) {
        RTE_LOG(ERR, EAL,
            "%s(): cannot mmap(%d, %p, 0x%zx, 0x%llx): %s (%p)\n",
            __func__, fd, requested_addr, size,
            (unsigned long long)offset,
            strerror(errno), mapaddr);
    } else
        RTE_LOG(DEBUG, EAL, " PCI memory mapped at %p\n", mapaddr);

    return mapaddr;
}

其實,關於用戶進程通過內存映射 resource0…N 的方法來得到 PCI BAR 空間的操作在 Linux kernel doc(https://www.kernel.org/doc/Documentation/filesystems/sysfs-pci.txt)中早有說明:

在這里插入圖片描述

綜上所述,我們知道雖然 UIO 框架提供了 PCI BAR 訪問方式,但 DPDK 實際上沒有使用,而是直接 Kernel 提供的 mmap() 方式來訪問 PCI 設備的 resource,讓用戶進程(e.g. PMD Driver)得以訪問 PCI 設備。

IGB_UIO 的注冊 PCI 設備的流程

igb_uio 內核模塊主要做的事情就是注冊一個 PCI 設備。但是 igbuio_pci_driver 對應保存的 PCI 設備信息的 id_table 指針初始是為空的,這樣在內核注冊此 PCI 設備時,就會找不到匹配的設備,也就不會調用 igb_uio 驅動中的 probe() 來探測 PCI 設備。只是單存的在 /sys 目錄下創建了 igb_uio 相應的目錄。

所以,實際上在加載 igb_uio.ko 模塊到內核時,igb_uio 是 probe(探測)不到 PCI 設備的,而是在執行 Python 腳本 dpdk_nic_bind.py 的時候完成。dpdk-devbind 具體的步驟如下:

  1. 獲取腳本執行參數指定的網卡(e.g. eth1)設備的 PCI 信息。實際是執行指令 lspci–Dvmmn 查看,主要關注 Slot、Vendor ID 以及 Device ID 信息。
Slot: 0000:06:00.1
Class: 0200
Vendor: 8086
Device: 1521
SVendor: 15d9
SDevice: 1521
Rev: 01
  1. unbind 網卡設備之前的 igb 模塊,將 Step 1 中獲取到的 eth1 對應的 Slot 信息 0000:06:00.1 值寫入 igb 驅動的 unbind 文件。e.g. echo 0000:06:00.1 > /sys/bus/pci/drivers/igb/unbind。從內核代碼分析此動作就是將 igb 模塊信息和該 PCI 設備去關聯。

  2. bind 網卡設備到新的 igb_uio 模塊,將 eth1 的 Vendor 和 Device ID 信息寫入 igb_uio 驅動的 new_id 文件。e.g. echo 0x8086 0x1521 > /sys/bus/pci/drivers/igb_uio/new_id

在這里插入圖片描述
一個設備的驅動要實現的功能根據實際的需要可能千差萬別,但是究其本質來說無非兩件事情:

  1. 一個是內存的操作
  2. 另外一個就是中斷的處理

igb_uio 驅動和 igb 驅動都是網卡這個 PCI 設備的驅動程序,相同點都是能夠使能 PCI 設備、分配內存等,而不同的就在於對內存和中斷的處理方式的差異。

PCI 設備探測

sysfs 文件系統是一種內存文件系統,它提供了一種方法用於導出內核的數據結構、屬性以及兩者之間的聯系到用戶空間。用戶空間可以通過修改 sysfs 中的文件來修改設備的屬性值。上訴 new_id、driver_override、bind、unbind 文件對應 igb_uio 驅動的各屬性值。new_id 主要就是將 PCI 設備的 ID 加入至驅動動態設備 ID 列表,供后面驅動設備 probe 時使用。

在這里插入圖片描述

  1. 在操作任何 PCI 設備的寄存器之前,需要調用 pci_enable_device 函數啟動設備,此函數主要做了以下工作:
    1. wake up the device if it was in suspended state
    2. allocate I/O and memory regions of the device (if BIOS did not)
    3. allocate an IRQ (if BIOS did not)
  2. pci_set_master 設置 PCI COMMAND 寄存器 bus master 位,啟動 DMA。
  3. igb_setup_bars 將 BAR 空間導入 uio_info 結構,供后面用戶層使用(PMD)。
  4. pci_set_dma_mask 和 pci_set_consistent_dma_mask 設置 DMA 地址掩碼位。因為 PCI 設備是有尋址限制的,所以需要標識那些位的地址可用。
  5. uio_register_device 注冊 uioX 設備。此處會創建 /dev/uiox 設備文件供后面應用層使用。
  6. pci_set_drvdata 設置私有數據。

在完成上述步驟之后,這時 dmesg 就會看到 igb_uio 內核模塊的 probe 函數執行了,也就是意味着掃描到了相應的 PCI 設備。接下來就是進入到 PCI 設備的注冊流程。

記錄設備的資源

igb_uio 驅動會遍歷該 PCI 設備的 BAR 空間,對於類型為存儲器空間 IORESOURCE_MEM 的 BAR(Memory BAR),將其物理地址、大小等信息保存到 uio_info 結構的 mem 數組中;將類型為寄存器空間 IORESOURCE_IO 的 BAR(IO BAR),將其物理地址、大小等信息保存到 uio_info 結構的 port 數組中。

在這里插入圖片描述

而 igb 驅動同樣也會遍歷 BAR 空間,但是它不會記錄空間的物理地址,而是調用 ioremap() 將物理地址映射為虛擬地址,然后驅動就可以在內核態中讀寫映射出來的虛擬地址,而不是像 igb_uio 驅動似的在用戶態中進行讀寫。

注冊一個 uio 設備

Linux 上的驅動設備一般都是運行在內核態的,提供接口函數給用戶態函數調用即可。而 UIO 技術則是將驅動的大部分事情移到了用戶態。之所以能夠實現,正如前面所說,是因為 igb_uio 將 PCI BAR 空間的物理地址、大小等信息都記錄下來並傳給了用戶態。

除了記錄 BAR 空間資源信息,UIO 框架還會在內核態實現中斷處理相關的初始化工作。如下 igbuio_pci_probe 的代碼片段:

* fill uio infos */  
udev->info.name = "igb_uio"; 
udev->info.version = "0.1"; 
udev->info.handler = igbuio_pci_irqhandler; 
udev->info.irqcontrol = igbuio_pci_irqcontrol;          

注冊的 uio 設備名為 igb_uio,內核態中斷處理函數為 igbuio_pci_irqhandler,中斷控制函數 igbuio_pci_irqcontrol。

$ ls -l /dev/uio*
crw------- 1 root root 243, 0 5月   8 00:18 /dev/uio0
switch (igbuio_intr_mode_preferred) { 
	case RTE_INTR_MODE_MSIX:  
		msix_entry.entry =0; 
		if (pci_enable_msix(dev,&msix_entry,1)==0) {                                                                        
			udev->info.irq =msix_entry.vector; 
			udev->mode =RTE_INTR_MODE_MSIX; 
			break; 
		}  

	case RTE_INTR_MODE_LEGACY:  
		if (pci_intx_mask_supported(dev)) { 
			udev->info.irq_flags =IRQF_SHARED; 
			udev->info.irq =dev->irq; 
			udev->mode =RTE_INTR_MODE_LEGACY; 
			break; 
		}

變量 igbuio_intr_mode_preferred 表示中斷的模式,它由 igb_uio 驅動的參數 intr_mode 決定,有 MSI-X 中斷和 Legacy 中斷兩種模式,默認為 MSI-X 中斷模式。

  • 如果是 MSI-X 中斷模式,則調用 pci_enable_msix 函數向 PCI 子系統申請分配一個 MSI-X 中斷。若分配成功就會初始化 uio_info 的 irq 為申請到的中斷號。
  • 如果是傳統的 Intx 中斷模式,則調用 pci_intx_mask_supported 函數讀取 PCI 配置空間,檢查是否支持 Intx 中斷。

在對 uio_info 內存和中斷相關的成員初始化之后,就開始調用 uio_register_device 函數來注冊 uio 設備了。

idev->owner = owner; 
idev->info = info; 

init_waitqueue_head(&idev->wait); 
atomic_set(&idev->event, 0);

idev->dev =device_create(&uio_class,parent,
						 MKDEV(uio_major, idev->minor),
						 idev, 
						 "uio%d",
						 idev->minor); 

ret =uio_dev_add_attributes(idev); 
info->uio_dev =idev; 

if (info->irq &&(info->irq !=UIO_IRQ_CUSTOM)) { 
	ret =devm_request_irq(idev->dev,info->irq,uio_interrupt,                                                      
	info->irq_flags,info->name,idev); 
}  
  1. 初始化 uio_device 結構體指針 idev,主要包括等待隊列 wait、中斷事件計數 event、次設備號 minor 等。
  2. 在 /dev 目錄下創建了一個 uio 設備,設備名為 uio%d,%d 為次設備號 minor。
$ ls -l /dev/uio*
crw------- 1 root root 243, 0 5月   8 00:18 /dev/uio0
  1. 接着就是調用 uio_dev_add_attributes 函數在 /sys/class/uio/uioX/ 目錄下創建 maps 和 portio 接口。前面講到會遍歷此 PCI 設備的 BAR 空間,將存儲器空間類型的 BAR 的物理地址等信息存儲在 uio_info 的 mem 數組中,這里就會根據此 mem 數組在 maps 目錄下為每個寄存器類型的 BAR 創建一個目錄。
[root@c-dev ~]# ls -l /sys/class/uio/uio0/maps/map0/
總用量 0
-r--r--r-- 1 root root 4096 5月   8 00:19 addr
-r--r--r-- 1 root root 4096 5月   8 00:19 name
-r--r--r-- 1 root root 4096 5月   8 00:19 offset
-r--r--r-- 1 root root 4096 5月   8 00:19 size
[root@c-dev ~]# ls -l /sys/class/uio/uio0/maps/map1/
總用量 0
-r--r--r-- 1 root root 4096 5月   8 00:19 addr
-r--r--r-- 1 root root 4096 5月   8 00:19 name
-r--r--r-- 1 root root 4096 5月   8 00:19 offset
-r--r--r-- 1 root root 4096 5月   8 00:19 size

可以看出,igb_uio 網卡有兩個類型為 IORESOURCE_MEM 的 BAR,分別為 BAR1 和 BAR4,這里就創建了 map0 和 map1 兩個子目錄分別對應 BAR1 和 BAR1。

[root@c-dev ~]# cat /sys/class/uio/uio0/maps/map1/name
BAR4
[root@c-dev ~]# cat /sys/class/uio/uio0/maps/map1/addr
0x0000000440000000
  1. 最后就是注冊中斷了,中斷的中斷號、中斷標志等在前面有講到,這里看下注冊的中斷處理函數 uio_interrupt。
static irqreturn_t uio_interrupt(intirq,void *dev_id) 
{ 
    struct uio_device *idev =(struct uio_device *)dev_id;                                                           
    irqreturn_t ret =idev->info->handler(irq,idev->info); 
    
    if (ret==IRQ_HANDLED) 
        uio_event_notify(idev->info); 
    return ret; 
} 

此函數首先調用 igb_uio 驅動中設置的中斷處理函數 igbuio_pci_irqhandler 來檢查中斷是不是此設備的中斷,如果是就返回 IRQ_HANDLED 表示需要處理,接着調用函數 uio_event_notify 來喚醒等待隊列 wait 上進程來處理中斷事宜。

總結

  1. igb_uio 負責創建 uio 設備(e.g. /dev/uio0)並加載 igb_uio 驅動,負責將原先被內核驅動接管的網卡轉移到 igb_uio 驅動,以此來屏蔽掉原生的內核驅動以及內核協議棧;
  2. igb_uio 負責一個橋梁的作用,銜接中斷信號以及用戶態應用,因為中斷只能在內核態處理,所以 igb_uio 相當於提供了一個接口,銜接用戶態與內核態的驅動。

在這里插入圖片描述

參考文章

https://www.cnblogs.com/jungle1996/p/12398915.html
https://blog.csdn.net/weijitao/article/details/52949454


免責聲明!

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



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