不能对 vfio pci 设备的文件描述符使用 pread
Can't use pread on a file descriptor for a vfio pci device
所以我使用 qemu kvm 有一段时间了,现在我需要直通 PCI 设备。我做了所有必要的程序来完成这项工作:启用 iommu、modprobed vfio 模块、将设备绑定到 vfio 并检查是否确实创建了 vfio 组,等等......
但是当我用任何 pci 设备启动 qemu 时,我收到错误消息:
vfio: Failed to read device config space
我深入研究了 qemu 的代码以查看问题可能是什么,并发现问题发生在对设备的 pread 上。即使偏移量为 0 时也会发生这种情况,并且对文件描述符进行正常读取也没有问题,因为我更改了代码以对其进行测试。
由于 pread 失败的原因检查 errno 给我一个“非法搜索”错误消息。
我写了一些代码来查看这是否发生在 qemu 上下文之外(认为它可能是 qemu 代码中干扰设备的东西),并且遇到了同样的问题。我还尝试使用 pread 读取一个普通文件,并且效果很好……这是我为测试它而编写的代码,我将其分解了一下以便能够指出更相关的部分:
#define BUF_SIZE 4096
int main(){
char buf[BUF_SIZE], buf1[BUF_SIZE], buf2[BUF_SIZE];
int ret,group_fd, fd, fd2;
size_t nbytes = 4096;
ssize_t bytes_read;
int iommu1, iommu2;
int container, group, device, i;
struct vfio_group_status group_status = { .argsz = sizeof(group_status) };
struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) };
struct vfio_iommu_type1_dma_map dma_map = { .argsz = sizeof(dma_map) };
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
container = open("/dev/vfio/vfio",O_RDWR);
if(ioctl(container,VFIO_GET_API_VERSION)!=VFIO_API_VERSION){
printf("Unknown api version: %m\n");
}
group_fd = open("/dev/vfio/22",O_RDWR); printf("Group fd = %d\n", group_fd);
ioctl(group_fd, VFIO_GROUP_GET_STATUS, &group_status);
if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE)){
printf("Group not viable\n");
return 1;
}
ret = ioctl(group_fd, VFIO_GROUP_SET_CONTAINER,&container);
ret = ioctl(container,VFIO_SET_IOMMU,VFIO_TYPE1_IOMMU);
ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
/* Allocate some space and setup a DMA mapping */
dma_map.vaddr = (unsigned long int) mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
dma_map.size = 1024 * 1024;
dma_map.iova = 0; /* 1MB starting at 0x0 from device view */
dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
printf("\n\nGETTING DEVICE FD\n");
fd = ioctl(group_fd,VFIO_GROUP_GET_DEVICE_FD,"0000:08:00.0");
printf("Fd = %d\n",fd);
printf("VFIO_GROUP_GET_DEV_ID = %lu\n",VFIO_GROUP_GET_DEVICE_FD);
这个读取工作正常,给了我 nbytes 的 ret 代码
ret = read(fd,buf,nbytes);
if(ret<1){
printf("ERROR: %m \n");
}
这个 pread 失败,ret code -1 和 errno 'Illegal seek'
ret = pread(fd,buf,nbytes,0);
if(ret<0){
printf("ERROR: %m \n");
}
这里我尝试读取和读取sysfs中的一个普通文件,看看读取是否失败,在这种情况下读取和读取都正常:
printf("TESTING PREAD ON A COMMON FILE\n");
fd2 = open("/sys/bus/pci/devices/0000:08:00.0/device",O_RDONLY);
ret = read(fd2,buf1,nbytes);
if(ret<0){
printf("ERROR: %m\n");
}
printf("Result from read: ret = %d, content = %s\n",ret,buf1);
ret = pread(fd2,buf2,nbytes,2);
if(ret<0){
printf("ERROR: %m\n"); #
}
printf("Result from pread: ret = %d, content = %s\n",ret,buf2);
close(fd2);
getchar();
close(fd);
close(container);
close(group_fd);
return 0;
}
我正在为嵌入式系统使用使用 uClibc 编译的通用 linux 内核 v4.7.8...。有人知道为什么会发生这种情况吗?我现在一头雾水!! T.T
更新:
我在同一台机器上安装了 ubuntu 16.04(内核 v4.4.0)并重复了这些步骤,pci passthrough 工作正常,我的测试代码上的 pread 也工作得很好。所以我不确定自定义通用内核出了什么问题。
根据 arash 的建议,我尝试了 pread(fd,buf,nbytes,SEEK_CUR) 并且它给了我同样的 'illegal seek' 错误。我从 ftell 得到的偏移量在 ubuntu 和通用内核中都是 0xffffffff。
我发现了问题所在,并且一直想 post 在这里为任何可能碰到这堵墙的人解决这个问题。结果是 uClibc 0.9.33 版的 pread 和 pwrite 函数被破坏,导致这些函数无法在大于 4G 的偏移量上工作。下面 link 中的补丁为我解决了这个问题:
http://uclibc.10924.n7.nabble.com/backport-pread-pwrite-fix-for-0-9-33-branch-td11921.html
所以我使用 qemu kvm 有一段时间了,现在我需要直通 PCI 设备。我做了所有必要的程序来完成这项工作:启用 iommu、modprobed vfio 模块、将设备绑定到 vfio 并检查是否确实创建了 vfio 组,等等...... 但是当我用任何 pci 设备启动 qemu 时,我收到错误消息:
vfio: Failed to read device config space
我深入研究了 qemu 的代码以查看问题可能是什么,并发现问题发生在对设备的 pread 上。即使偏移量为 0 时也会发生这种情况,并且对文件描述符进行正常读取也没有问题,因为我更改了代码以对其进行测试。 由于 pread 失败的原因检查 errno 给我一个“非法搜索”错误消息。
我写了一些代码来查看这是否发生在 qemu 上下文之外(认为它可能是 qemu 代码中干扰设备的东西),并且遇到了同样的问题。我还尝试使用 pread 读取一个普通文件,并且效果很好……这是我为测试它而编写的代码,我将其分解了一下以便能够指出更相关的部分:
#define BUF_SIZE 4096
int main(){
char buf[BUF_SIZE], buf1[BUF_SIZE], buf2[BUF_SIZE];
int ret,group_fd, fd, fd2;
size_t nbytes = 4096;
ssize_t bytes_read;
int iommu1, iommu2;
int container, group, device, i;
struct vfio_group_status group_status = { .argsz = sizeof(group_status) };
struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) };
struct vfio_iommu_type1_dma_map dma_map = { .argsz = sizeof(dma_map) };
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
container = open("/dev/vfio/vfio",O_RDWR);
if(ioctl(container,VFIO_GET_API_VERSION)!=VFIO_API_VERSION){
printf("Unknown api version: %m\n");
}
group_fd = open("/dev/vfio/22",O_RDWR); printf("Group fd = %d\n", group_fd);
ioctl(group_fd, VFIO_GROUP_GET_STATUS, &group_status);
if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE)){
printf("Group not viable\n");
return 1;
}
ret = ioctl(group_fd, VFIO_GROUP_SET_CONTAINER,&container);
ret = ioctl(container,VFIO_SET_IOMMU,VFIO_TYPE1_IOMMU);
ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
/* Allocate some space and setup a DMA mapping */
dma_map.vaddr = (unsigned long int) mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
dma_map.size = 1024 * 1024;
dma_map.iova = 0; /* 1MB starting at 0x0 from device view */
dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
printf("\n\nGETTING DEVICE FD\n");
fd = ioctl(group_fd,VFIO_GROUP_GET_DEVICE_FD,"0000:08:00.0");
printf("Fd = %d\n",fd);
printf("VFIO_GROUP_GET_DEV_ID = %lu\n",VFIO_GROUP_GET_DEVICE_FD);
这个读取工作正常,给了我 nbytes 的 ret 代码
ret = read(fd,buf,nbytes);
if(ret<1){
printf("ERROR: %m \n");
}
这个 pread 失败,ret code -1 和 errno 'Illegal seek'
ret = pread(fd,buf,nbytes,0);
if(ret<0){
printf("ERROR: %m \n");
}
这里我尝试读取和读取sysfs中的一个普通文件,看看读取是否失败,在这种情况下读取和读取都正常:
printf("TESTING PREAD ON A COMMON FILE\n");
fd2 = open("/sys/bus/pci/devices/0000:08:00.0/device",O_RDONLY);
ret = read(fd2,buf1,nbytes);
if(ret<0){
printf("ERROR: %m\n");
}
printf("Result from read: ret = %d, content = %s\n",ret,buf1);
ret = pread(fd2,buf2,nbytes,2);
if(ret<0){
printf("ERROR: %m\n"); #
}
printf("Result from pread: ret = %d, content = %s\n",ret,buf2);
close(fd2);
getchar();
close(fd);
close(container);
close(group_fd);
return 0;
}
我正在为嵌入式系统使用使用 uClibc 编译的通用 linux 内核 v4.7.8...。有人知道为什么会发生这种情况吗?我现在一头雾水!! T.T
更新: 我在同一台机器上安装了 ubuntu 16.04(内核 v4.4.0)并重复了这些步骤,pci passthrough 工作正常,我的测试代码上的 pread 也工作得很好。所以我不确定自定义通用内核出了什么问题。
根据 arash 的建议,我尝试了 pread(fd,buf,nbytes,SEEK_CUR) 并且它给了我同样的 'illegal seek' 错误。我从 ftell 得到的偏移量在 ubuntu 和通用内核中都是 0xffffffff。
我发现了问题所在,并且一直想 post 在这里为任何可能碰到这堵墙的人解决这个问题。结果是 uClibc 0.9.33 版的 pread 和 pwrite 函数被破坏,导致这些函数无法在大于 4G 的偏移量上工作。下面 link 中的补丁为我解决了这个问题: http://uclibc.10924.n7.nabble.com/backport-pread-pwrite-fix-for-0-9-33-branch-td11921.html