通过用户空间I / O(UIO)Linux驱动程序与QEMU edu设备连接

时间:2018-03-15 21:03:37

标签: linux linux-device-driver kernel-module qemu pci

我正在查看QEMU的edu devicesource),它提供了一个基本的"教育" QEMU中的PCI设备,可以从Linux等QEMU客户端作为PCI设备访问。

我一直试图通过UIO PCI通用驱动程序使用UIO驱动程序(用户空间I / O),以便更好地了解QEMU和Linux中的PCI设备。

我的总体目标是为FPGA实现Linux驱动程序。 FPGA作为PCI-E器件连接到ARM Cortex-A53 CPU,提供几个不同的存储器块,这些存储器将被视为器件配置的寄存器。我最初使用x86_64 QEMU熟悉PCI驱动程序,希望是UIO。注意:我已经向我建议vfio,但我相信这依赖于IOMMU的支持,而我目前的平台上并不确定。

我在内存区域映射方面遇到了一些麻烦。 UIO PCI驱动程序(我认为)意味着在/sys/class/uio/uio0/map为每个可寻址区域创建条目,但据我所知,当UIO驱动程序绑定到edu时,没有自动检测或设置区域设备

我开始新编译的QEMU(./configure --target-list=x86_64-softmmu),其中包含yocto生成的#34;相当标准的#34; Linux 4.9 x86_64发行版:

$ ./x86_64-softmmu/qemu-system-x86_64 --device edu -m 512 -nographic -serial mon:stdio -append 'console=ttyS0 root=/dev/hda' -kernel bzImage -hda image-qemu.ext3

然后在guest虚拟机中检测到edu PCI设备:

# lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)
00:04.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10)

加载uio_pci_generic模块并将其绑定到edu设备:

# modprobe uio_pci_generic
# echo "1234 11e8" > /sys/bus/pci/drivers/uio_pci_generic/new_id

# ls -l /sys/bus/pci/devices/0000\:00\:04.0/driver
lrwxrwxrwx 1 root root 0 Mar 15 01:50 /sys/bus/pci/devices/0000:00:04.0/driver -> ../../../bus/pci/drivers/uio_pci_generic

仔细查看设备,注意内存地址fea00000

# lspci -v -s 00:04.0
00:04.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10)
Subsystem: Red Hat, Inc Device 1100
Flags: fast devsel, IRQ 10
Memory at fea00000 (32-bit, non-prefetchable) [size=1M]
Capabilities: [40] MSI: Enable- Count=1/1 Maskable- 64bit+
Kernel driver in use: uio_pci_generic

我从源代码构建了lsuio

# ./lsuio -m -v
uio0: name=uio_pci_generic, version=0.01.0, events=0
Device attributes:
vendor=0x1234
uevent=DRIVER=uio_pci_generic
subsystem_vendor=0x1af4
subsystem_device=0x1100
resource=0x00000000fea00000 0x00000000feafffff 0x0000000000040200
msi_bus=1
modalias=pci:v00001234d000011E8sv00001AF4sd00001100bc00scFFi00
local_cpus=1
local_cpulist=0
irq=10
enable=1
driver_override=(null)
dma_mask_bits=32
device=0x11e8
d3cold_allowed=0
consistent_dma_mask_bits=32
config=4è
class=0x00ff00
broken_parity_status=0

# ls /sys/class/uio/uio0/ -l
total 0
-r--r--r-- 1 root root 4096 Mar 15 01:53 dev
lrwxrwxrwx 1 root root    0 Mar 15 01:53 device -> ../../../0000:00:04.0
-r--r--r-- 1 root root 4096 Mar 15 01:53 event
-r--r--r-- 1 root root 4096 Mar 15 01:53 name
drwxr-xr-x 2 root root    0 Mar 15 01:53 power
lrwxrwxrwx 1 root root    0 Mar 15 01:53 subsystem -> ../../../../../class/uio
-rw-r--r-- 1 root root 4096 Mar 15 01:22 uevent
-r--r--r-- 1 root root 4096 Mar 15 01:53 version

根据这一点,应该有一个可映射的区域,从0xfea00000开始我认为,但没有" map"目录出现,我还没有找到原因。尝试访问/dev/uio0(读取或mmap)会导致错误22:"无效参数"。打开文件并扫描到末尾显示块设备的大小为零。

首先,我是否需要手动创建这些区域映射,或者UIO驱动程序是否应自动设置这些区域映射? edu设备是否需要做一些额外的事情才能实现这一目标?

其次,是否有其他QEMU PCI设备可以与UIO配合使用?理想情况下,有一个有效的Linux驱动程序,所以我可以尝试理解QEMU设备端和相应的Linux驱动程序端。

在最后一点上,是否有人知道edu设备有效的Linux驱动程序?

2 个答案:

答案 0 :(得分:0)

事实证明documentation有点含糊不清,至少让我和其他人混淆:

long and windy thread说明ui_pci_generic驱动程序实际上并未将PCI BAR区域映射到maps目录。相反,目的是使用标准的PCI sysfs接口:

因此,我可以通过/sys/class/uio/uio0/device/resource0的mmap访问PCI设备的内存。

但是,尝试对/dev/uio0执行阻塞读取仍然会导致“无效参数”错误,因此我还不确定如何使用此sysfs接口等待或处理中断。

答案 1 :(得分:0)

要添加到OP答案中,似乎/dev/uio0用于接收和计数uio_pci_generic模块上的中断。
example code of using uio_pci_generic显示如下:

uiofd = open("/dev/uio0", O_RDONLY);
...
/* Wait for next interrupt. */
err = read(uiofd, &icount, 4);

icount是接收到的中断数。

使用qemu's edu device时,可以使用resource0访问映射的IO,并使用/dev/uio0等待中断。

这是一个用户空间示例(上述example code的扩展),该示例使用uio_pci_generic来写入和读取edu设备的“卡片活跃度检查”,该设备反转输入,并通过写入“中断引发寄存器”来触发edu中断:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/mman.h>


#define EDU_IO_SIZE 0x100
#define EDU_CARD_VERSION_ADDR  0x0
#define EDU_CARD_LIVENESS_ADDR 0x1
#define EDU_RAISE_INT_ADDR 0x18
#define EDU_CLEAR_INT_ADDR 0x19

int main()
{
    int uiofd;
    int configfd;
    int bar0fd;
    int resetfd;
    int err;
    int i;
    unsigned icount;
    unsigned char command_high;
    volatile uint32_t *bar0;

    uiofd = open("/dev/uio0", O_RDWR);
    if (uiofd < 0) {
        perror("uio open:");
        return errno;
    }

    configfd = open("/sys/class/uio/uio0/device/config", O_RDWR);
    if (configfd < 0) {
        perror("config open:");
        return errno;
    }

    /* Read and cache command value */
    err = pread(configfd, &command_high, 1, 5);
    if (err != 1) {
        perror("command config read:");
        return errno;
    }
    command_high &= ~0x4;

    /* Map edu's MMIO */
    bar0fd = open("/sys/class/uio/uio0/device/resource0", O_RDWR);
    if (bar0fd < 0) {
        perror("bar0fd open:");
        return errno;
    }

    /* mmap the device's BAR */
    bar0 = (volatile uint32_t *)mmap(NULL, EDU_IO_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, bar0fd, 0);
    if (bar0 == MAP_FAILED) {
        perror("Error mapping bar0!");
        return errno;
    }
    fprintf(stdout, "Version = %08X\n", bar0[EDU_CARD_VERSION_ADDR]);

    /* Test the invertor function */
    i = 0x12345678;
    bar0[EDU_CARD_LIVENESS_ADDR] = i;
    fprintf(stdout, "Inversion: %08X --> %08X\n", i, bar0[EDU_CARD_LIVENESS_ADDR]);

    /* Clear previous interrupt */
    bar0[EDU_CLEAR_INT_ADDR] = 0xABCDABCD;

    /* Raise an interrupt */
    bar0[EDU_RAISE_INT_ADDR] = 0xABCDABCD;

    for(i = 0;; ++i) {
        /* Print out a message, for debugging. */
        if (i == 0)
            fprintf(stderr, "Started uio test driver.\n");
        else
            fprintf(stderr, "Interrupts: %d\n", icount);

        /****************************************/
        /* Here we got an interrupt from the
           device. Do something to it. */
        /****************************************/

        /* Re-enable interrupts. */
        err = pwrite(configfd, &command_high, 1, 5);
        if (err != 1) {
            perror("config write:");
            break;
        }

        /* Clear previous interrupt */
        bar0[EDU_CLEAR_INT_ADDR] = 0xABCDABCD;

        /* Raise an interrupt */
        bar0[EDU_RAISE_INT_ADDR] = 0xABCDABCD;

        /* Wait for next interrupt. */
        err = read(uiofd, &icount, 4);
        if (err != 4) {
            perror("uio read:");
            break;
        }

    }
    return errno;
}

结果看起来像这样:

Version = 010000ED
Inversion: 12345678 --> EDCBA987
Started uio test driver.
Interrupts: 3793548
Interrupts: 3793549
Interrupts: 3793550
Interrupts: 3793551
Interrupts: 3793552
Interrupts: 3793553
Interrupts: 3793554
Interrupts: 3793555
Interrupts: 3793556
...