VFIO Mediated Device(vfio-mdev)内核文档(翻译)

原文为vfio mediated device内核文档[5],我翻译一下。

1. Virtual Function I/O (VFIO) Mediated devices[1]

对没有内置SR-IOV功能的设备进行DMA虚拟化的需求越来越多。以前,为了虚拟化一个这样的设备,开发者需要自己开发管理接口和API,然后把它们集成到用户态应用中。为了简化这种用户空间软件集成,我们找出了这种设备公共需求然后开发了一种统一的接口。

VFIO驱动框架为直接设备访问提供了统一的API。它将设备直接访问安全地(以一种IOMMU保护的环境)暴露给用户,是一种IOMMU/设备无关的框架。此框架用于多种设备,如GPU、网卡和计算加速器等。有了这种直接设备访问,虚拟机或者用户态应用可以直接访问物理设备。Mdeiated devices便是重用了VFIO这种框架。

Mediated core driver为mdiated device提供了一个公共的管理接口,它可以被不同类型的设备驱动所利用。这个模块提供了的通用接口可以进行如下操作:

  • 创建和删除mediated device
  • 把一个mediated deivce加入或移出某个总线驱动
  • 把一个mediated device加入或移出某个IOMMU group

Mediated core driver也提供注册总线驱动的接口。比如,一个mediated VFIO mdev驱动就是为mediated devices设计的,并且支持VFIO的API。Mediated bus driver可以将一个mediated device加入或者移出一个VFIO group。

以下的上层图展示了VFIO mediated driver框架的主要组件和接口。这张图展示了NVIDIA、Intel和IBM设备,因为这些设备是首先使用这些模块的。

     +---------------+
     |               |
     | +-----------+ |  mdev_register_driver() +--------------+
     | |           | +<------------------------+              |
     | |  mdev     | |                         |              |
     | |  bus      | +------------------------>+ vfio_mdev.ko |<-> VFIO user
     | |  driver   | |     probe()/remove()    |              |    APIs
     | |           | |                         +--------------+
     | +-----------+ |
     |               |
     |  MDEV CORE    |
     |   MODULE      |
     |   mdev.ko     |
     | +-----------+ |  mdev_register_device() +--------------+
     | |           | +<------------------------+              |
     | |           | |                         |  nvidia.ko   |<-> physical
     | |           | +------------------------>+              |    device
     | |           | |        callbacks        +--------------+
     | | Physical  | |
     | |  device   | |  mdev_register_device() +--------------+
     | | interface | |<------------------------+              |
     | |           | |                         |  i915.ko     |<-> physical
     | |           | +------------------------>+              |    device
     | |           | |        callbacks        +--------------+
     | |           | |
     | |           | |  mdev_register_device() +--------------+
     | |           | +<------------------------+              |
     | |           | |                         | ccw_device.ko|<-> physical
     | |           | +------------------------>+              |    device
     | |           | |        callbacks        +--------------+
     | +-----------+ |
     +---------------+

2. Registration(注册)接口

Mediated core driver提供了如下类型的注册接口:

  • mediated总线驱动的注册接口
  • 物理设备驱动接口

Mediated总线驱动注册接口

为mediated总线驱动设计的注册接口提供了如下接口来表示mediated设备的驱动:

/*
* struct mdev_driver [2] - Mediated device's driver
* @name: driver name
* @probe: called when new device created
* @remove: called when device removed
* @driver: device driver structure
*/
struct mdev_driver {
const char *name;
int (*probe) (struct device *dev);
void (*remove) (struct device *dev);
struct device_driver driver;
};

一个mdev的dediated总线驱动应该在函数调用中使用这个结构来从mediated core driver中注册和注销(unregister)他自己:

  • 注册:
extern int mdev_register_driver(struct mdev_driver *drv,
struct module *owner);
  • 注销:
extern void mdev_unregister_driver(struct mdev_driver *drv);

这个mediated总线驱动是负责从VFIO group中添加(设备bound时)和删除(设备unbound时)mediated设备(mdev)。

物理设备驱动接口

物理设备驱动接口提供了mdev_parent_ops[3]结构来定义API,用于管理mediated core driver中和物理设备相关的工作。

The structures in the mdev_parent_ops structure are as follows:

mdev_parent_ops结构中的数据结构如下:

  • dev_attr_groups: parent device的属性
  • mdev_attr_groups: mediated device的属性
  • supported_config: 定义所支持配置的属性

The functions in the mdev_parent_ops structure are as follows:

mdev_parent_ops结构中的函数如下:

  • create: 在driver中为一个mdev分配基本的资源
  • remove: 当一个mdev被销毁时在driver中free掉相关资源

(Note that mdev-core provides no implicit serialization of create/remove
callbacks per mdev parent device, per mdev type, or any other categorization.
Vendor drivers are expected to be fully asynchronous in this respect or
provide their own internal resource protection.)

(注意,mdev-core不为每个mdev parent device、每个mdev类型或者任何其他配置提供create/remove回调的隐式序列化。提供商驱动被期望完全得同步或者提供他们自己的内部资源保护。)

mdev_parent_ops结构中的回调如下:

  • open: 打开mdev的回调
  • close: 关闭mdev的回调
  • ioctl: mdev的ioctl回调 callback of mediated device
  • read : read模拟回调
  • write: write模拟回调
  • mmap: mmap模拟回调

一个驱动应该用在注册到mdev core driver时用mdev_parent_ops这个结构

extern int mdev_register_device(struct device *dev,
const struct mdev_parent_ops *ops);

However, the mdev_parent_ops structure is not required in the function call
that a driver should use to unregister itself with the mdev core driver::

但是,mdev_parent_ops结构在从mdev core driver注销时并不需要:

extern void mdev_unregister_device(struct device *dev);

3. Mediated设备管理接口sysfs

The management interface through sysfs enables user space software, such as
libvirt, to query and configure mediated devices in a hardware-agnostic fashion.
This management interface provides flexibility to the underlying physical
device’s driver to support features such as:

管理接口是通过sysfs来让用户态软件(如libvirt)进行请求和配置mdevs的,这种管理是一种硬件无关的形式。这种管理接口给底层硬件设备驱动提供了灵活的特性支持,比如:

  • mdev的热插拔
  • 多个mdev在一个虚拟机中
  • 来自不同物理设备的多个mdev

mdev_bus类目录中的链接

/sys/class/mdev_bus/这个目录包含到已注册到mdev core driver设备的链接。

sysfs下每个物理设备的目录和文件

|- [parent physical device]
|--- Vendor-specific-attributes [optional]
|--- [mdev_supported_types]
| |--- [<type-id>]
| | |--- create
| | |--- name
| | |--- available_instances
| | |--- device_api
| | |--- description
| | |--- [devices]
| |--- [<type-id>]
| | |--- create
| | |--- name
| | |--- available_instances
| | |--- device_api
| | |--- description
| | |--- [devices]
| |--- [<type-id>]
| |--- create
| |--- name
| |--- available_instances
| |--- device_api
| |--- description
| |--- [devices]

其中:

  • [mdev_supported_types]:列出了单签支持的mdev的种类(mediated device type)和详细信息,type-id, device_api, 和available_instances应给被厂商驱动提供的信息:
  • type-id: type-id这个名字是用设备驱动字符串作为厂商驱动字符串前缀的名。这个名字的格式如下:
sprintf(buf, "%s-%s", dev_driver_string(parent->dev), group->name);

(或者用mdev_parent_dev(mdev)来访问core mdev代码外的parent device)

  • device_api:这个属性用来指明什么设备API被创建了,比如,”vfio-pci”是给PCI设备用的。
  • available_instances:这个属性展示了可以被创建的type-id类设备数。
  • [device]:这个目录包含了到被创建的type-id设备的链接。
  • name:展示了人类能看懂的名字,此项可选。
  • description:展示简单说明的类型特性、描述,此项可选

### sysfs下每个mdev设备的目录和文件

```bash
|- [parent phy device]
|--- [$MDEV_UUID]
|--- remove
|--- mdev_type {link to its type}
|--- vendor-specific-attributes [optional]

其中:

  • remove (只写):往其中写”1″会销毁mdev设备。如果设备处于活跃状态并且厂商的驱动不支持热插拔,那么厂商的驱动可以让remove回调失败。

例子:

echo 1 > /sys/bus/mdev/devices/$mdev_UUID/remove

mdev设备的热插拔

mdev设备可以在运行时进行创建和绑定。热插拔mdev的步骤和热插拔PCI设备的步骤相同。

4. mdev的转换(Translation)API

以下API用于提供在VFIO驱动中从User PFN到Host PFN的转换:

extern int vfio_pin_pages(struct device *dev, unsigned long *user_pfn,
int npage, int prot, unsigned long *phys_pfn);

extern int vfio_unpin_pages(struct device *dev, unsigned long *user_pfn,
int npage);

这些函数会回调后端IOMMU模块(struct vfio_iommu_driver_ops[4]结构中的pin_pages函数和unpin_pages函数)。挡墙这些回调在TYPE1 IOMMU模块中被支持。其他IOMMU后端模块中(如PPC64 sPAPR模块)若想支持,他们就需要提过这两个回调函数的实现。

5. 简单代码样例

samples/vfio-mdev/文件夹中的mtty.c是一个展示mdev框架怎么用的简单驱动程序。

这个简单驱动创建了一个mdev设备来模拟一个PCI串口设备。

Step 1 创建和加载mtty.ko模块:

这步会创建一个dummy设备/sys/devices/virtual/mtty/mtty/

sysfs中的设备目录如下:

# tree /sys/devices/virtual/mtty/mtty/
/sys/devices/virtual/mtty/mtty/
|-- mdev_supported_types
| |-- mtty-1
| | |-- available_instances
| | |-- create
| | |-- device_api
| | |-- devices
| | `-- name
| `-- mtty-2
| |-- available_instances
| |-- create
| |-- device_api
| |-- devices
| `-- name
|-- mtty_dev
| `-- sample_mtty_dev
|-- power
| |-- autosuspend_delay_ms
| |-- control
| |-- runtime_active_time
| |-- runtime_status
| `-- runtime_suspended_time
|-- subsystem -&gt; ../../../../class/mtty
`-- uevent
Step 2 用这个dummy设备创建一个mdev设备:
# echo "83b8f4f2-509f-382f-3c1e-e6bfe0fa1001" &gt; \
/sys/devices/virtual/mtty/mtty/mdev_supported_types/mtty-2/create
Step 3 为qemu-kvm添加如下参数:
-device vfio-pci,\
sysfsdev=/sys/bus/mdev/devices/83b8f4f2-509f-382f-3c1e-e6bfe0fa1001
Step 4 启动虚拟机

In the Linux guest VM, with no hardware on the host, the device appears
as follows::

在Linux为GuestOS的虚拟机中,设备会被这样显示:

# lspci -s 00:05.0 -xxvv
00:05.0 Serial controller: Device 4348:3253 (rev 10) (prog-if 02 [16550])
Subsystem: Device 4348:3253
Physical Slot: 5
Control: I/O+ Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr-
Stepping- SERR- FastB2B- DisINTx-
Status: Cap- 66MHz- UDF- FastB2B- ParErr- DEVSEL=medium &gt;TAbort-
&lt;TAbort- &lt;MAbort- &gt;SERR- &lt;PERR- INTx-
Interrupt: pin A routed to IRQ 10
Region 0: I/O ports at c150 [size=8]
Region 1: I/O ports at c158 [size=8]
Kernel driver in use: serial
00: 48 43 53 32 01 00 00 02 10 02 00 07 00 00 00 00
10: 51 c1 00 00 59 c1 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 48 43 53 32
30: 00 00 00 00 00 00 00 00 00 00 00 00 0a 01 00 00

在Linux为GuestOS的虚拟机中,dmesg会有如下输出:

serial 0000:00:05.0: PCI INT A -&gt; Link[LNKA] -&gt; GSI 10 (level, high) -&gt; IRQ 10
0000:00:05.0: ttyS1 at I/O 0xc150 (irq = 10) is a 16550A
0000:00:05.0: ttyS2 at I/O 0xc158 (irq = 10) is a 16550A
Step 5 在Linux为guestOS的虚拟机中,查看串口设备:
# setserial -g /dev/ttyS*
/dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4
/dev/ttyS1, UART: 16550A, Port: 0xc150, IRQ: 10
/dev/ttyS2, UART: 16550A, Port: 0xc158, IRQ: 10
Step 6 用minicom或者其他终端模拟程序,打开串口/dev/ttyS1或者/dev/ttyS2并禁用硬件流控制:
Step 7 向minicom终端打字或者发数据给终端模拟程序并读数据

数据会在host mtty驱动回显。

Step 8 销毁所创建的mdev:
# echo 1 > /sys/bus/mdev/devices/83b8f4f2-509f-382f-3c1e-e6bfe0fa1001/remove

参考

[1] 内核中的VFIO文档Documentation/vfio.txt有VFIO的更多信息,我也在以前的博客中翻译过。
[2] include/linux/mdev.h文件中的struct mdev_driver结构
[3] include/linux/mdev.h文件中的struct mdev_parent_ops结构
[4] include/linux/vfio.h文件中的struct vfio_iommu_driver_ops结构
[5] https://github.com/torvalds/linux/blob/master/Documentation/vfio-mediated-device.txt

Leave a Reply

Your email address will not be published. Required fields are marked *