Linux虚拟化之VFIO框架

Linux虚拟化之VFIO框架

  • 引言
  • 1 用户接口
    • 1.1 container
    • 1.2 group
    • 1.3 device
  • 2 内核实现
    • 2.1 container
      • vfio_fops_open
      • vfio_fops_release
      • vfio_fops_unl_ioctl / vfio_fops_compat_ioctl
    • 2.2 group
      • vfio_create_group
      • vfio_group_release
      • vfio_group_fops_open
      • vfio_group_fops_release
      • vfio_group_fops_unl_ioctl / vfio_group_fops_compat_ioctl
    • 2.3 device

引言

VFIO为多用户在用户态访问设备提供了支持,这为用户态驱动和设备虚拟化奠定了基础。VFIO抽象了三个层次:device, group, container:

  • container : 一个container对应一个虚拟机的域、或者一个用户;
  • group : 相对于intel iommu中的group,是设备直通的最小单位;
  • device :我们访问的设备。

注:device属于group,每个group可以关联到一个container。

1 用户接口

1.1 container

首先,container负责管理内存资源,和IOMMU、DMA及地址空间相关。用户空间通过文件/dev/vfio/vfio获取对应的文件描述符。具体操作如下:

	//打开container文件
	container = open("/dev/vfio/vfio", O_RDWR);
	
	//通过ioctl设置IOMMU。此外,还可以通过ioctl做诸如获取container信息或其他设置
	ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

	//让iommu建立 “dma_map.iova -> dma_map.vaddr对应的物理地址” 的映射
	dma_map.vaddr = 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);

1.2 group

group是透传的最小单位。假设lspci获取需要透传的设备id为05:00.0,操作group前需要做如下准备:

  1. 通过readlink /sys/bus/pci/devices/0000:05:00.0/iommu_group可以获取设备的group id,假设为29;
  2. 通过lspci -n -s 0000:05:00.0输出05:00.0 0200: 10ec:8125 (rev 05)获取到vendor和dev id为10ec:8125;
  3. 通过echo 0000:05:00.0 > /sys/bus/pci/devices/0000:05:00.0/driver/unbind解除绑定到pci bus;
  4. 通过echo 10ec:8125 > /sys/bus/pci/drivers/vfio-pci/new_id将设备交vfio-pci管理。

然后,用户空间就可以通过打开文件/dev/vfio/29访问该group,具体操作如下:

	// 打开group文件
	group = open("/dev/vfio/29", O_RDWR);
	// 通过ioctl将group关联到container,另外还可以作信息获取或其他设置
	ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);

1.3 device

通过group可以打开device文件,然后可以对文件进行读写和mmap。

	// 通过group打开设备
	device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:05:00.0");
	
	ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
	
	for (int i = 0; i < device_info.num_regions; i++) {
		reg.index = i;
		... ...
		ioctl(device, VFIO_DEVICE_GET_REGION_INFO, &reg);
		... ...
	}
	
	for (i = 0; i < device_info.num_irqs; i++) {
		irq.index = i;
		... ...
		ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);
		... ...
	}
	
	ioctl(device, VFIO_DEVICE_RESET);

2 内核实现

2.1 container

在vfio_init函数中,将vfio_dev注册为misc设备,本质上是一个字符设备。vfio_dev的fops回调为:

static const struct file_operations vfio_fops = {
	.owner		= THIS_MODULE,
	.open		= vfio_fops_open,
	.release	= vfio_fops_release,
	.read		= vfio_fops_read,
	.write		= vfio_fops_write,
	.unlocked_ioctl	= vfio_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= vfio_fops_compat_ioctl,
#endif
	.mmap		= vfio_fops_mmap,
};

vfio_fops_open

在vfio_fops_open中,通过kzalloc申请一片内存将其作为struct vfio_container。最后将filep->private_data设置为新申请的struct vfio_container内存指针。

vfio_fops_release

在vfio_fops_release中,释放filep->private_data指向的内存、并将filep->private_data置NULL。

vfio_fops_unl_ioctl / vfio_fops_compat_ioctl

后者是对前者的封装。在vfio_fops_unl_ioctl中,实现了如下的ioctl :

  • VFIO_GET_API_VERSION : 获取VFIO版本信息
  • VFIO_CHECK_EXTENSION : 检测是否支持特定扩展,例如VFIO_TYPE1_IOMMU;
  • VFIO_SET_IOMMU : 设置指定的IOMMU类型, 例如VFIO_TYPE1_IOMMU;

注:当设置了IOMMU类型后,container的iommu_driver和iommu_data也就被设置。然后其他ioctl命令则由container->iommu_driver->ops->ioctl处理。

目前有三种iommu实现,vfio_noiommu_ops、tce_iommu_driver_ops和vfio_iommu_driver_ops_type1。

  • tce_iommu_driver_ops :
  • vfio_iommu_driver_ops_type1 :
  • vfio_noiommu_ops :

2.2 group

在vfio_init函数中,为group字符设备注册了vfio_group_fops文件操作回调:

static const struct file_operations vfio_group_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl	= vfio_group_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= vfio_group_fops_compat_ioctl,
#endif
	.open		= vfio_group_fops_open,
	.release	= vfio_group_fops_release,
};

vfio_create_group

在vfio_add_group_dev中,如果dev的group不存在,则会通过vfio_create_group创建一个group。vfio_create_group逻辑如下:

  • 分配struct vfio_group内存,对其初始化;
  • 通过vfio_alloc_group_minor将group添加到vfio.group_idr中,返回一个id号。这个id号就是group文件的次设备号;
  • 通过device_create创建主设备号为vfio.group_devt,次设备号为上面返回的id号的字符设备。

vfio_group_release

该函数执行了vfio_create_group的反向释放操作,当group引用计数为0时释放。

vfio_group_fops_open

当打开group文件的时候,会调用该回调。其逻辑如下:

  • 通过inode节点获取次设备号,然后通过次设备号找到group指针;
  • 如果group打开原子计数大于0、或者group已经关联到container,则返回失败;
  • 通过系列检查后,将group赋值给filep->private_data;

vfio_group_fops_release

该函数是文件关闭时的释放操作。

vfio_group_fops_unl_ioctl / vfio_group_fops_compat_ioctl

后者是对前者的封装。在vfio_group_fops_unl_ioctl中,实现了如下的ioctl :

  • VFIO_GROUP_GET_STATUS:获取group 的状态信息;
  • VFIO_GROUP_SET_CONTAINER:将group绑定到container;
  • VFIO_GROUP_UNSET_CONTAINER:将group解除绑定到container;
  • VFIO_GROUP_GET_DEVICE_FD:获取group中某个设备的文件ID。

2.3 device

注:vfio可以实现多种不同功能的设备,目前主要实现了三类:mdev、pci、platform设备。后面以vfio-pci设备为例。
首先,在vfio-pci驱动模块初始化的时候,注册了一个pci驱动vfio_pci_driver:

static struct pci_driver vfio_pci_driver = {
	.name		= "vfio-pci",
	.id_table	= NULL, /* only dynamic ids */
	.probe		= vfio_pci_probe,
	.remove		= vfio_pci_remove,
	.err_handler	= &vfio_err_handlers,
};
  1. 可以看到id_table字段为NULL,所以通过echo VendorID:DevID> /sys/bus/pci/drivers/vfio-pci/new_id实现设备匹配。
  2. 在vfio_pci_probe中,创建了struct vfio_pci_device数据结构,通过vfio_add_group_dev将vfio-pci设备对象添加到vfio框架中,并设置了vfio_pci_ops作为设备文件的操作回调。
  3. vfio_add_group_dev函数最终会调用vfio_group_create_device创建一个struct vfio_device对象,并将vfio_pci_ops赋值给struct vfio_device.ops;struct vfio_pci_device指针赋值给struct vfio_device.device_data和其他相关初始化赋值。最后添加到struct vfio_group.device_list链表中。

vfio_pci_ops的文件操作设置如下:

static const struct vfio_device_ops vfio_pci_ops = {
	.name		= "vfio-pci",
	.open		= vfio_pci_open,
	.release	= vfio_pci_release,
	.ioctl		= vfio_pci_ioctl,
	.read		= vfio_pci_read,
	.write		= vfio_pci_write,
	.mmap		= vfio_pci_mmap,
	.request	= vfio_pci_request,
};

对group文件调用ioctl执行VFIO_GROUP_GET_DEVICE_FD命令,将调用vfio_group_get_device_fd从struct vfio_group.device_list链表中查询设备,并创建一个文件对象,将struct vfio_device赋值给file->private_data;然后调用vfio_pci_ops.open方法;同时设置file的操作函数集合为vfio_device_fops:

static const struct file_operations vfio_device_fops = {
	.owner		= THIS_MODULE,
	.release	= vfio_device_fops_release,
	.read		= vfio_device_fops_read,
	.write		= vfio_device_fops_write,
	.unlocked_ioctl	= vfio_device_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= vfio_device_fops_compat_ioctl,
#endif
	.mmap		= vfio_device_fops_mmap,
};

注:上述vfio_device_fops_*函数集合是对vfio_pci_*的包装,或者说代理。

你可能感兴趣的:(Linux内幕,linux,运维,服务器)