요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
==================================
VFIO - "Virtual Function I/O" [1]_
==================================
Many modern systems now provide DMA and interrupt remapping facilities
to help ensure I/O devices behave within the boundaries they've been
allotted. This includes x86 hardware with AMD-Vi and Intel VT-d,
POWER systems with Partitionable Endpoints (PEs) and embedded PowerPC
systems such as Freescale PAMU. The VFIO driver is an IOMMU/device
agnostic framework for exposing direct device access to userspace, in
a secure, IOMMU protected environment. In other words, this allows
safe [2]_, non-privileged, userspace drivers.
Why do we want that? Virtual machines often make use of direct device
access ("device assignment") when configured for the highest possible
I/O performance. From a device and host perspective, this simply
turns the VM into a userspace driver, with the benefits of
significantly reduced latency, higher bandwidth, and direct use of
bare-metal device drivers [3]_.
Some applications, particularly in the high performance computing
field, also benefit from low-overhead, direct device access from
userspace. Examples include network adapters (often non-TCP/IP based)
and compute accelerators. Prior to VFIO, these drivers had to either
go through the full development cycle to become proper upstream
driver, be maintained out of tree, or make use of the UIO framework,
which has no notion of IOMMU protection, limited interrupt support,
and requires root privileges to access things like PCI configuration
space.
The VFIO driver framework intends to unify these, replacing both the
KVM PCI specific device assignment code as well as provide a more
secure, more featureful userspace driver environment than UIO.
Groups, Devices, and IOMMUs
---------------------------
Devices are the main target of any I/O driver. Devices typically
create a programming interface made up of I/O access, interrupts,
and DMA. Without going into the details of each of these, DMA is
by far the most critical aspect for maintaining a secure environment
as allowing a device read-write access to system memory imposes the
greatest risk to the overall system integrity.
To help mitigate this risk, many modern IOMMUs now incorporate
isolation properties into what was, in many cases, an interface only
meant for translation (ie. solving the addressing problems of devices
with limited address spaces). With this, devices can now be isolated
from each other and from arbitrary memory access, thus allowing
things like secure direct assignment of devices into virtual machines.
This isolation is not always at the granularity of a single device
though. Even when an IOMMU is capable of this, properties of devices,
interconnects, and IOMMU topologies can each reduce this isolation.
For instance, an individual device may be part of a larger multi-
function enclosure. While the IOMMU may be able to distinguish
between devices within the enclosure, the enclosure may not require
transactions between devices to reach the IOMMU. Examples of this
could be anything from a multi-function PCI device with backdoors
between functions to a non-PCI-ACS (Access Control Services) capable
bridge allowing redirection without reaching the IOMMU. Topology
can also play a factor in terms of hiding devices. A PCIe-to-PCI
bridge masks the devices behind it, making transaction appear as if
from the bridge itself. Obviously IOMMU design plays a major factor
as well.
Therefore, while for the most part an IOMMU may have device level
granularity, any system is susceptible to reduced granularity. The
IOMMU API therefore supports a notion of IOMMU groups. A group is
a set of devices which is isolatable from all other devices in the
system. Groups are therefore the unit of ownership used by VFIO.
While the group is the minimum granularity that must be used to
ensure secure user access, it's not necessarily the preferred
granularity. In IOMMUs which make use of page tables, it may be
possible to share a set of page tables between different groups,
reducing the overhead both to the platform (reduced TLB thrashing,
reduced duplicate page tables), and to the user (programming only
a single set of translations). For this reason, VFIO makes use of
a container class, which may hold one or more groups. A container
is created by simply opening the /dev/vfio/vfio character device.
On its own, the container provides little functionality, with all
but a couple version and extension query interfaces locked away.
The user needs to add a group into the container for the next level
of functionality. To do this, the user first needs to identify the
group associated with the desired device. This can be done using
the sysfs links described in the example below. By unbinding the
device from the host driver and binding it to a VFIO driver, a new
VFIO group will appear for the group as /dev/vfio/$GROUP, where
$GROUP is the IOMMU group number of which the device is a member.
If the IOMMU group contains multiple devices, each will need to
be bound to a VFIO driver before operations on the VFIO group
are allowed (it's also sufficient to only unbind the device from
host drivers if a VFIO driver is unavailable; this will make the
group available, but not that particular device). TBD - interface
for disabling driver probing/locking a device.
Once the group is ready, it may be added to the container by opening
the VFIO group character device (/dev/vfio/$GROUP) and using the
VFIO_GROUP_SET_CONTAINER ioctl, passing the file descriptor of the
previously opened container file. If desired and if the IOMMU driver
supports sharing the IOMMU context between groups, multiple groups may
be set to the same container. If a group fails to set to a container
with existing groups, a new empty container will need to be used
instead.
With a group (or groups) attached to a container, the remaining
ioctls become available, enabling access to the VFIO IOMMU interfaces.
Additionally, it now becomes possible to get file descriptors for each
device within a group using an ioctl on the VFIO group file descriptor.
The VFIO device API includes ioctls for describing the device, the I/O
regions and their read/write/mmap offsets on the device descriptor, as
well as mechanisms for describing and registering interrupt
notifications.
VFIO Usage Example
------------------
Assume user wants to access PCI device 0000:06:0d.0::
$ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group
../../../../kernel/iommu_groups/26
This device is therefore in IOMMU group 26. This device is on the
pci bus, therefore the user will make use of vfio-pci to manage the
group::
# modprobe vfio-pci
Binding this device to the vfio-pci driver creates the VFIO group
character devices for this group::
$ lspci -n -s 0000:06:0d.0
06:0d.0 0401: 1102:0002 (rev 08)
# echo 0000:06:0d.0 > /sys/bus/pci/devices/0000:06:0d.0/driver/unbind
# echo 1102 0002 > /sys/bus/pci/drivers/vfio-pci/new_id
Now we need to look at what other devices are in the group to free
it for use by VFIO::
$ ls -l /sys/bus/pci/devices/0000:06:0d.0/iommu_group/devices
total 0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:00:1e.0 ->
../../../../devices/pci0000:00/0000:00:1e.0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.0 ->
../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.1 ->
../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.1
This device is behind a PCIe-to-PCI bridge [4]_, therefore we also
need to add device 0000:06:0d.1 to the group following the same
procedure as above. Device 0000:00:1e.0 is a bridge that does
not currently have a host driver, therefore it's not required to
bind this device to the vfio-pci driver (vfio-pci does not currently
support PCI bridges).
The final step is to provide the user with access to the group if
unprivileged operation is desired (note that /dev/vfio/vfio provides
no capabilities on its own and is therefore expected to be set to
mode 0666 by the system)::
# chown user:user /dev/vfio/26
The user now has full access to all the devices and the iommu for this
group and can access them as follows::
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) };
/* Create a new container */
container = open("/dev/vfio/vfio", O_RDWR);
if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
/* Unknown API version */
if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU))
/* Doesn't support the IOMMU driver we want. */
/* Open the group */
group = open("/dev/vfio/26", O_RDWR);
/* Test the group is viable and available */
ioctl(group, VFIO_GROUP_GET_STATUS, &group_status);
if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE))
/* Group is not viable (ie, not all devices bound for vfio) */
/* Add the group to the container */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
/* Enable the IOMMU model we want */
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
/* Get addition IOMMU info */
ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
/* Allocate some space and setup a DMA mapping */
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);
/* Get a file descriptor for the device */
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
/* Test and setup the device */
ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
for (i = 0; i < device_info.num_regions; i++) {
struct vfio_region_info reg = { .argsz = sizeof(reg) };
reg.index = i;
ioctl(device, VFIO_DEVICE_GET_REGION_INFO, ®);
/* Setup mappings... read/write offsets, mmaps
* For PCI devices, config space is a region */
}
for (i = 0; i < device_info.num_irqs; i++) {
struct vfio_irq_info irq = { .argsz = sizeof(irq) };
irq.index = i;
ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);
/* Setup IRQs... eventfds, VFIO_DEVICE_SET_IRQS */
}
/* Gratuitous device reset and go... */
ioctl(device, VFIO_DEVICE_RESET);
IOMMUFD and vfio_iommu_type1
----------------------------
IOMMUFD is the new user API to manage I/O page tables from userspace.
It intends to be the portal of delivering advanced userspace DMA
features (nested translation [5]_, PASID [6]_, etc.) while also providing
a backwards compatibility interface for existing VFIO_TYPE1v2_IOMMU use
cases. Eventually the vfio_iommu_type1 driver, as well as the legacy
vfio container and group model is intended to be deprecated.
The IOMMUFD backwards compatibility interface can be enabled two ways.
In the first method, the kernel can be configured with
CONFIG_IOMMUFD_VFIO_CONTAINER, in which case the IOMMUFD subsystem
transparently provides the entire infrastructure for the VFIO
container and IOMMU backend interfaces. The compatibility mode can
also be accessed if the VFIO container interface, ie. /dev/vfio/vfio is
simply symlink'd to /dev/iommu. Note that at the time of writing, the
compatibility mode is not entirely feature complete relative to
VFIO_TYPE1v2_IOMMU (ex. DMA mapping MMIO) and does not attempt to
provide compatibility to the VFIO_SPAPR_TCE_IOMMU interface. Therefore
it is not generally advisable at this time to switch from native VFIO
implementations to the IOMMUFD compatibility interfaces.
Long term, VFIO users should migrate to device access through the cdev
interface described below, and native access through the IOMMUFD
provided interfaces.
VFIO Device cdev
----------------
Traditionally user acquires a device fd via VFIO_GROUP_GET_DEVICE_FD
in a VFIO group.
With CONFIG_VFIO_DEVICE_CDEV=y the user can now acquire a device fd
by directly opening a character device /dev/vfio/devices/vfioX where
"X" is the number allocated uniquely by VFIO for registered devices.
cdev interface does not support noiommu devices, so user should use
the legacy group interface if noiommu is wanted.
The cdev only works with IOMMUFD. Both VFIO drivers and applications
must adapt to the new cdev security model which requires using
VFIO_DEVICE_BIND_IOMMUFD to claim DMA ownership before starting to
actually use the device. Once BIND succeeds then a VFIO device can
be fully accessed by the user.
VFIO device cdev doesn't rely on VFIO group/container/iommu drivers.
Hence those modules can be fully compiled out in an environment
where no legacy VFIO application exists.
So far SPAPR does not support IOMMUFD yet. So it cannot support device
cdev either.
vfio device cdev access is still bound by IOMMU group semantics, ie. there
can be only one DMA owner for the group. Devices belonging to the same
group can not be bound to multiple iommufd_ctx or shared between native
kernel and vfio bus driver or other driver supporting the driver_managed_dma
flag. A violation of this ownership requirement will fail at the
VFIO_DEVICE_BIND_IOMMUFD ioctl, which gates full device access.
Device cdev Example
-------------------
Assume user wants to access PCI device 0000:6a:01.0::
$ ls /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/
vfio0
This device is therefore represented as vfio0. The user can verify
its existence::
$ ls -l /dev/vfio/devices/vfio0
crw------- 1 root root 511, 0 Feb 16 01:22 /dev/vfio/devices/vfio0
$ cat /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/vfio0/dev
511:0
$ ls -l /dev/char/511\:0
lrwxrwxrwx 1 root root 21 Feb 16 01:22 /dev/char/511:0 -> ../vfio/devices/vfio0
Then provide the user with access to the device if unprivileged
operation is desired::
$ chown user:user /dev/vfio/devices/vfio0
Finally the user could get cdev fd by::
cdev_fd = open("/dev/vfio/devices/vfio0", O_RDWR);
An opened cdev_fd doesn't give the user any permission of accessing
the device except binding the cdev_fd to an iommufd. After that point
then the device is fully accessible including attaching it to an
IOMMUFD IOAS/HWPT to enable userspace DMA::
struct vfio_device_bind_iommufd bind = {
.argsz = sizeof(bind),
.flags = 0,
};
struct iommu_ioas_alloc alloc_data = {
.size = sizeof(alloc_data),
.flags = 0,
};
struct vfio_device_attach_iommufd_pt attach_data = {
.argsz = sizeof(attach_data),
.flags = 0,
};
struct iommu_ioas_map map = {
.size = sizeof(map),
.flags = IOMMU_IOAS_MAP_READABLE |
IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_FIXED_IOVA,
.__reserved = 0,
};
iommufd = open("/dev/iommu", O_RDWR);
bind.iommufd = iommufd;
ioctl(cdev_fd, VFIO_DEVICE_BIND_IOMMUFD, &bind);
ioctl(iommufd, IOMMU_IOAS_ALLOC, &alloc_data);
attach_data.pt_id = alloc_data.out_ioas_id;
ioctl(cdev_fd, VFIO_DEVICE_ATTACH_IOMMUFD_PT, &attach_data);
/* Allocate some space and setup a DMA mapping */
map.user_va = (int64_t)mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
map.iova = 0; /* 1MB starting at 0x0 from device view */
map.length = 1024 * 1024;
map.ioas_id = alloc_data.out_ioas_id;
ioctl(iommufd, IOMMU_IOAS_MAP, &map);
/* Other device operations as stated in "VFIO Usage Example" */
VFIO User API
-------------------------------------------------------------------------------
Please see include/uapi/linux/vfio.h for complete API documentation.
VFIO bus driver API
-------------------------------------------------------------------------------
VFIO bus drivers, such as vfio-pci make use of only a few interfaces
into VFIO core. When devices are bound and unbound to the driver,
Following interfaces are called when devices are bound to and
unbound from the driver::
int vfio_register_group_dev(struct vfio_device *device);
int vfio_register_emulated_iommu_dev(struct vfio_device *device);
void vfio_unregister_group_dev(struct vfio_device *device);
The driver should embed the vfio_device in its own structure and use
vfio_alloc_device() to allocate the structure, and can register
@init/@release callbacks to manage any private state wrapping the
vfio_device::
vfio_alloc_device(dev_struct, member, dev, ops);
void vfio_put_device(struct vfio_device *device);
vfio_register_group_dev() indicates to the core to begin tracking the
iommu_group of the specified dev and register the dev as owned by a VFIO bus
driver. Once vfio_register_group_dev() returns it is possible for userspace to
start accessing the driver, thus the driver should ensure it is completely
ready before calling it. The driver provides an ops structure for callbacks
similar to a file operations structure::
struct vfio_device_ops {
char *name;
int (*init)(struct vfio_device *vdev);
void (*release)(struct vfio_device *vdev);
int (*bind_iommufd)(struct vfio_device *vdev,
struct iommufd_ctx *ictx, u32 *out_device_id);
void (*unbind_iommufd)(struct vfio_device *vdev);
int (*attach_ioas)(struct vfio_device *vdev, u32 *pt_id);
void (*detach_ioas)(struct vfio_device *vdev);
int (*open_device)(struct vfio_device *vdev);
void (*close_device)(struct vfio_device *vdev);
ssize_t (*read)(struct vfio_device *vdev, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*write)(struct vfio_device *vdev, const char __user *buf,
size_t count, loff_t *size);
long (*ioctl)(struct vfio_device *vdev, unsigned int cmd,
unsigned long arg);
int (*mmap)(struct vfio_device *vdev, struct vm_area_struct *vma);
void (*request)(struct vfio_device *vdev, unsigned int count);
int (*match)(struct vfio_device *vdev, char *buf);
void (*dma_unmap)(struct vfio_device *vdev, u64 iova, u64 length);
int (*device_feature)(struct vfio_device *device, u32 flags,
void __user *arg, size_t argsz);
};
Each function is passed the vdev that was originally registered
in the vfio_register_group_dev() or vfio_register_emulated_iommu_dev()
call above. This allows the bus driver to obtain its private data using
container_of().
::
- The init/release callbacks are issued when vfio_device is initialized
and released.
- The open/close device callbacks are issued when the first
instance of a file descriptor for the device is created (eg.
via VFIO_GROUP_GET_DEVICE_FD) for a user session.
- The ioctl callback provides a direct pass through for some VFIO_DEVICE_*
ioctls.
- The [un]bind_iommufd callbacks are issued when the device is bound to
and unbound from iommufd.
- The [de]attach_ioas callback is issued when the device is attached to
and detached from an IOAS managed by the bound iommufd. However, the
attached IOAS can also be automatically detached when the device is
unbound from iommufd.
- The read/write/mmap callbacks implement the device region access defined
by the device's own VFIO_DEVICE_GET_REGION_INFO ioctl.
- The request callback is issued when device is going to be unregistered,
such as when trying to unbind the device from the vfio bus driver.
- The dma_unmap callback is issued when a range of iovas are unmapped
in the container or IOAS attached by the device. Drivers which make
use of the vfio page pinning interface must implement this callback in
order to unpin pages within the dma_unmap range. Drivers must tolerate
this callback even before calls to open_device().
PPC64 sPAPR implementation note
-------------------------------
This implementation has some specifics:
1) On older systems (POWER7 with P5IOC2/IODA1) only one IOMMU group per
container is supported as an IOMMU table is allocated at the boot time,
one table per a IOMMU group which is a Partitionable Endpoint (PE)
(PE is often a PCI domain but not always).
Newer systems (POWER8 with IODA2) have improved hardware design which allows
to remove this limitation and have multiple IOMMU groups per a VFIO
container.
2) The hardware supports so called DMA windows - the PCI address range
within which DMA transfer is allowed, any attempt to access address space
out of the window leads to the whole PE isolation.
3) PPC64 guests are paravirtualized but not fully emulated. There is an API
to map/unmap pages for DMA, and it normally maps 1..32 pages per call and
currently there is no way to reduce the number of calls. In order to make
things faster, the map/unmap handling has been implemented in real mode
which provides an excellent performance which has limitations such as
inability to do locked pages accounting in real time.
4) According to sPAPR specification, A Partitionable Endpoint (PE) is an I/O
subtree that can be treated as a unit for the purposes of partitioning and
error recovery. A PE may be a single or multi-function IOA (IO Adapter), a
function of a multi-function IOA, or multiple IOAs (possibly including
switch and bridge structures above the multiple IOAs). PPC64 guests detect
PCI errors and recover from them via EEH RTAS services, which works on the
basis of additional ioctl commands.
So 4 additional ioctls have been added:
VFIO_IOMMU_SPAPR_TCE_GET_INFO
returns the size and the start of the DMA window on the PCI bus.
VFIO_IOMMU_ENABLE
enables the container. The locked pages accounting
is done at this point. This lets user first to know what
the DMA window is and adjust rlimit before doing any real job.
VFIO_IOMMU_DISABLE
disables the container.
VFIO_EEH_PE_OP
provides an API for EEH setup, error detection and recovery.
The code flow from the example above should be slightly changed::
struct vfio_eeh_pe_op pe_op = { .argsz = sizeof(pe_op), .flags = 0 };
.....
/* Add the group to the container */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
/* Enable the IOMMU model we want */
ioctl(container, VFIO_SET_IOMMU, VFIO_SPAPR_TCE_IOMMU)
/* Get addition sPAPR IOMMU info */
vfio_iommu_spapr_tce_info spapr_iommu_info;
ioctl(container, VFIO_IOMMU_SPAPR_TCE_GET_INFO, &spapr_iommu_info);
if (ioctl(container, VFIO_IOMMU_ENABLE))
/* Cannot enable container, may be low rlimit */
/* Allocate some space and setup a DMA mapping */
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;
/* Check here is .iova/.size are within DMA window from spapr_iommu_info */
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
/* Get a file descriptor for the device */
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
....
/* Gratuitous device reset and go... */
ioctl(device, VFIO_DEVICE_RESET);
/* Make sure EEH is supported */
ioctl(container, VFIO_CHECK_EXTENSION, VFIO_EEH);
/* Enable the EEH functionality on the device */
pe_op.op = VFIO_EEH_PE_ENABLE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* You're suggested to create additional data struct to represent
* PE, and put child devices belonging to same IOMMU group to the
* PE instance for later reference.
*/
/* Check the PE's state and make sure it's in functional state */
pe_op.op = VFIO_EEH_PE_GET_STATE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Save device state using pci_save_state().
* EEH should be enabled on the specified device.
*/
....
/* Inject EEH error, which is expected to be caused by 32-bits
* config load.
*/
pe_op.op = VFIO_EEH_PE_INJECT_ERR;
pe_op.err.type = EEH_ERR_TYPE_32;
pe_op.err.func = EEH_ERR_FUNC_LD_CFG_ADDR;
pe_op.err.addr = 0ul;
pe_op.err.mask = 0ul;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
....
/* When 0xFF's returned from reading PCI config space or IO BARs
* of the PCI device. Check the PE's state to see if that has been
* frozen.
*/
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Waiting for pending PCI transactions to be completed and don't
* produce any more PCI traffic from/to the affected PE until
* recovery is finished.
*/
/* Enable IO for the affected PE and collect logs. Usually, the
* standard part of PCI config space, AER registers are dumped
* as logs for further analysis.
*/
pe_op.op = VFIO_EEH_PE_UNFREEZE_IO;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/*
* Issue PE reset: hot or fundamental reset. Usually, hot reset
* is enough. However, the firmware of some PCI adapters would
* require fundamental reset.
*/
pe_op.op = VFIO_EEH_PE_RESET_HOT;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
pe_op.op = VFIO_EEH_PE_RESET_DEACTIVATE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Configure the PCI bridges for the affected PE */
pe_op.op = VFIO_EEH_PE_CONFIGURE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Restored state we saved at initialization time. pci_restore_state()
* is good enough as an example.
*/
/* Hopefully, error is recovered successfully. Now, you can resume to
* start PCI traffic to/from the affected PE.
*/
....
5) There is v2 of SPAPR TCE IOMMU. It deprecates VFIO_IOMMU_ENABLE/
VFIO_IOMMU_DISABLE and implements 2 new ioctls:
VFIO_IOMMU_SPAPR_REGISTER_MEMORY and VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY
(which are unsupported in v1 IOMMU).
PPC64 paravirtualized guests generate a lot of map/unmap requests,
and the handling of those includes pinning/unpinning pages and updating
mm::locked_vm counter to make sure we do not exceed the rlimit.
The v2 IOMMU splits accounting and pinning into separate operations:
- VFIO_IOMMU_SPAPR_REGISTER_MEMORY/VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY ioctls
receive a user space address and size of the block to be pinned.
Bisecting is not supported and VFIO_IOMMU_UNREGISTER_MEMORY is expected to
be called with the exact address and size used for registering
the memory block. The userspace is not expected to call these often.
The ranges are stored in a linked list in a VFIO container.
- VFIO_IOMMU_MAP_DMA/VFIO_IOMMU_UNMAP_DMA ioctls only update the actual
IOMMU table and do not do pinning; instead these check that the userspace
address is from pre-registered range.
This separation helps in optimizing DMA for guests.
6) sPAPR specification allows guests to have an additional DMA window(s) on
a PCI bus with a variable page size. Two ioctls have been added to support
this: VFIO_IOMMU_SPAPR_TCE_CREATE and VFIO_IOMMU_SPAPR_TCE_REMOVE.
The platform has to support the functionality or error will be returned to
the userspace. The existing hardware supports up to 2 DMA windows, one is
2GB long, uses 4K pages and called "default 32bit window"; the other can
be as big as entire RAM, use different page size, it is optional - guests
create those in run-time if the guest driver supports 64bit DMA.
VFIO_IOMMU_SPAPR_TCE_CREATE receives a page shift, a DMA window size and
a number of TCE table levels (if a TCE table is going to be big enough and
the kernel may not be able to allocate enough of physically contiguous
memory). It creates a new window in the available slot and returns the bus
address where the new window starts. Due to hardware limitation, the user
space cannot choose the location of DMA windows.
VFIO_IOMMU_SPAPR_TCE_REMOVE receives the bus start address of the window
and removes it.
-------------------------------------------------------------------------------
.. [1] VFIO was originally an acronym for "Virtual Function I/O" in its
initial implementation by Tom Lyon while as Cisco. We've since
outgrown the acronym, but it's catchy.
.. [2] "safe" also depends upon a device being "well behaved". It's
possible for multi-function devices to have backdoors between
functions and even for single function devices to have alternative
access to things like PCI config space through MMIO registers. To
guard against the former we can include additional precautions in the
IOMMU driver to group multi-function PCI devices together
(iommu=group_mf). The latter we can't prevent, but the IOMMU should
still provide isolation. For PCI, SR-IOV Virtual Functions are the
best indicator of "well behaved", as these are designed for
virtualization usage models.
.. [3] As always there are trade-offs to virtual machine device
assignment that are beyond the scope of VFIO. It's expected that
future IOMMU technologies will reduce some, but maybe not all, of
these trade-offs.
.. [4] In this case the device is below a PCI bridge, so transactions
from either function of the device are indistinguishable to the iommu::
-[0000:00]-+-1e.0-[06]--+-0d.0
\-0d.1
00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev 90)
.. [5] Nested translation is an IOMMU feature which supports two stage
address translations. This improves the address translation efficiency
in IOMMU virtualization.
.. [6] PASID stands for Process Address Space ID, introduced by PCI
Express. It is a prerequisite for Shared Virtual Addressing (SVA)
and Scalable I/O Virtualization (Scalable IOV).
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
VFIO의 목적과 userspace 직접 장치 접근
1-34현대 시스템은 I/O device가 할당된 경계를 벗어나지 않도록 DMA와 interrupt remapping 기능을 제공합니다. 예로 x86의 AMD-Vi와 Intel VT-d, POWER의 Partitionable Endpoint(PE), Freescale PAMU 같은 embedded PowerPC 기술이 있습니다.
VFIO driver는 안전하고 IOMMU로 보호된 환경에서 userspace에 direct device access를 노출하는 IOMMU/device-agnostic framework입니다. 즉 권한이 없는 userspace driver도 장치를 안전하게 사용할 수 있게 합니다. 여기서 안전성은 각주 [2]의 device 동작 가정을 함께 전제로 합니다.
Virtual machine은 최고 수준의 I/O 성능이 필요할 때 direct device access, 즉 device assignment를 자주 사용합니다. Device와 host 관점에서 VM이 userspace driver 역할을 하게 되며, latency 감소, bandwidth 향상, bare-metal device driver의 직접 사용이라는 이점을 얻습니다.
High-performance computing application도 network adapter와 compute accelerator에 대한 overhead가 낮은 userspace 직접 접근으로 이익을 얻습니다. VFIO 이전에는 driver를 upstream에 넣는 전체 개발 절차를 거치거나 out-of-tree로 유지하거나 UIO를 사용해야 했습니다. 하지만 UIO는 IOMMU protection 개념이 없고 interrupt 지원이 제한적이며 PCI configuration space 접근 등에 root 권한이 필요합니다.
VFIO framework는 KVM의 PCI 전용 device-assignment code를 대체하고, UIO보다 안전하고 기능이 풍부한 userspace driver 환경을 제공해 이러한 용도를 통합하려 합니다.
==================================
VFIO - "Virtual Function I/O" [1]_
==================================
Many modern systems now provide DMA and interrupt remapping facilities
to help ensure I/O devices behave within the boundaries they've been
allotted. This includes x86 hardware with AMD-Vi and Intel VT-d,
POWER systems with Partitionable Endpoints (PEs) and embedded PowerPC
systems such as Freescale PAMU. The VFIO driver is an IOMMU/device
agnostic framework for exposing direct device access to userspace, in
a secure, IOMMU protected environment. In other words, this allows
safe [2]_, non-privileged, userspace drivers.
Why do we want that? Virtual machines often make use of direct device
access ("device assignment") when configured for the highest possible
I/O performance. From a device and host perspective, this simply
turns the VM into a userspace driver, with the benefits of
significantly reduced latency, higher bandwidth, and direct use of
bare-metal device drivers [3]_.
Some applications, particularly in the high performance computing
field, also benefit from low-overhead, direct device access from
userspace. Examples include network adapters (often non-TCP/IP based)
and compute accelerators. Prior to VFIO, these drivers had to either
go through the full development cycle to become proper upstream
driver, be maintained out of tree, or make use of the UIO framework,
which has no notion of IOMMU protection, limited interrupt support,
and requires root privileges to access things like PCI configuration
space.
The VFIO driver framework intends to unify these, replacing both the
KVM PCI specific device assignment code as well as provide a more
secure, more featureful userspace driver environment than UIO.
Device, IOMMU group과 격리 단위
35-71I/O driver의 주 대상은 device이며, 일반적으로 I/O access, interrupt, DMA로 programming interface를 구성합니다. 이 가운데 DMA는 device에 system memory read-write 권한을 주므로 system integrity에 가장 큰 위험을 만들며 안전한 환경 유지에 가장 중요합니다.
현대 IOMMU는 제한된 address space를 가진 장치의 주소 변환만 하던 기능에 isolation property를 결합합니다. 이에 따라 device끼리 격리하고 임의 memory access를 차단할 수 있어 VM에 장치를 안전하게 직접 할당할 수 있습니다.
그러나 격리 granularity가 항상 단일 device인 것은 아닙니다. Device 특성, interconnect, IOMMU topology가 격리를 약화할 수 있습니다. Multi-function enclosure 내부 transaction이 IOMMU까지 가지 않거나, function 사이 backdoor가 있거나, PCI ACS를 지원하지 않는 bridge가 transaction을 우회시킬 수 있습니다.
PCIe-to-PCI bridge는 뒤쪽 device를 숨기고 transaction이 bridge 자체에서 온 것처럼 보이게 합니다. IOMMU 설계 역시 실제 격리 수준을 크게 좌우합니다.
따라서 IOMMU가 대체로 device-level granularity를 제공하더라도 시스템에 따라 더 거친 단위가 필요합니다. IOMMU API의 IOMMU group은 시스템의 다른 모든 device로부터 함께 격리할 수 있는 device 집합이며, VFIO는 이 group을 ownership의 기본 단위로 사용합니다.
Topology와 device 내부 경로를 모두 고려해 실제로 격리 가능한 최소 집합을 IOMMU group으로 묶습니다.
Groups, Devices, and IOMMUs
---------------------------
Devices are the main target of any I/O driver. Devices typically
create a programming interface made up of I/O access, interrupts,
and DMA. Without going into the details of each of these, DMA is
by far the most critical aspect for maintaining a secure environment
as allowing a device read-write access to system memory imposes the
greatest risk to the overall system integrity.
To help mitigate this risk, many modern IOMMUs now incorporate
isolation properties into what was, in many cases, an interface only
meant for translation (ie. solving the addressing problems of devices
with limited address spaces). With this, devices can now be isolated
from each other and from arbitrary memory access, thus allowing
things like secure direct assignment of devices into virtual machines.
This isolation is not always at the granularity of a single device
though. Even when an IOMMU is capable of this, properties of devices,
interconnects, and IOMMU topologies can each reduce this isolation.
For instance, an individual device may be part of a larger multi-
function enclosure. While the IOMMU may be able to distinguish
between devices within the enclosure, the enclosure may not require
transactions between devices to reach the IOMMU. Examples of this
could be anything from a multi-function PCI device with backdoors
between functions to a non-PCI-ACS (Access Control Services) capable
bridge allowing redirection without reaching the IOMMU. Topology
can also play a factor in terms of hiding devices. A PCIe-to-PCI
bridge masks the devices behind it, making transaction appear as if
from the bridge itself. Obviously IOMMU design plays a major factor
as well.
Therefore, while for the most part an IOMMU may have device level
granularity, any system is susceptible to reduced granularity. The
IOMMU API therefore supports a notion of IOMMU groups. A group is
a set of devices which is isolatable from all other devices in the
system. Groups are therefore the unit of ownership used by VFIO.
Group과 legacy container 모델
72-117안전한 user access를 위해 반드시 지켜야 하는 최소 단위는 group이지만, page table을 사용하는 IOMMU에서는 여러 group이 하나의 page-table set을 공유할 수 있습니다. 그러면 platform의 TLB thrashing과 중복 page table이 줄고, userspace도 translation set 하나만 programming하면 됩니다.
이를 위해 VFIO는 하나 이상의 group을 담는 container class를 사용합니다. Legacy container는 `/dev/vfio/vfio` character device를 열어 생성합니다. Container만 연 상태에서는 version과 extension query 몇 개를 제외한 기능이 잠겨 있습니다.
사용자는 원하는 device의 sysfs link에서 IOMMU group을 식별하고, host driver에서 unbind한 뒤 VFIO driver에 bind해야 합니다. 그러면 group number가 `$GROUP`인 `/dev/vfio/$GROUP`이 나타납니다.
Group에 device가 여러 개면 group operation을 허용하기 전에 모두 VFIO driver에 bind해야 합니다. 특정 device용 VFIO driver가 없다면 host driver에서 unbind만 해도 group 자체는 사용 가능해지지만 그 device에는 접근할 수 없습니다. Driver probing 비활성화나 device locking interface는 원문에서 TBD로 남아 있습니다.
준비된 `/dev/vfio/$GROUP`을 열고 `VFIO_GROUP_SET_CONTAINER` ioctl에 기존 container fd를 넘겨 group을 추가합니다. IOMMU driver가 context 공유를 지원하면 여러 group을 같은 container에 둘 수 있고, 기존 group이 있는 container에 추가하지 못하면 새 빈 container를 사용해야 합니다.
Group이 container에 붙으면 VFIO IOMMU interface ioctl이 활성화되고 group fd를 통해 각 device fd를 얻을 수 있습니다. VFIO device API는 device 정보, I/O region의 read/write/mmap offset, interrupt notification 설명과 등록 기능을 제공합니다.
IOMMU group을 완전히 준비한 뒤 container에 연결해야 IOMMU와 device ioctl이 열립니다.
While the group is the minimum granularity that must be used to
ensure secure user access, it's not necessarily the preferred
granularity. In IOMMUs which make use of page tables, it may be
possible to share a set of page tables between different groups,
reducing the overhead both to the platform (reduced TLB thrashing,
reduced duplicate page tables), and to the user (programming only
a single set of translations). For this reason, VFIO makes use of
a container class, which may hold one or more groups. A container
is created by simply opening the /dev/vfio/vfio character device.
On its own, the container provides little functionality, with all
but a couple version and extension query interfaces locked away.
The user needs to add a group into the container for the next level
of functionality. To do this, the user first needs to identify the
group associated with the desired device. This can be done using
the sysfs links described in the example below. By unbinding the
device from the host driver and binding it to a VFIO driver, a new
VFIO group will appear for the group as /dev/vfio/$GROUP, where
$GROUP is the IOMMU group number of which the device is a member.
If the IOMMU group contains multiple devices, each will need to
be bound to a VFIO driver before operations on the VFIO group
are allowed (it's also sufficient to only unbind the device from
host drivers if a VFIO driver is unavailable; this will make the
group available, but not that particular device). TBD - interface
for disabling driver probing/locking a device.
Once the group is ready, it may be added to the container by opening
the VFIO group character device (/dev/vfio/$GROUP) and using the
VFIO_GROUP_SET_CONTAINER ioctl, passing the file descriptor of the
previously opened container file. If desired and if the IOMMU driver
supports sharing the IOMMU context between groups, multiple groups may
be set to the same container. If a group fails to set to a container
with existing groups, a new empty container will need to be used
instead.
With a group (or groups) attached to a container, the remaining
ioctls become available, enabling access to the VFIO IOMMU interfaces.
Additionally, it now becomes possible to get file descriptors for each
device within a group using an ioctl on the VFIO group file descriptor.
The VFIO device API includes ioctls for describing the device, the I/O
regions and their read/write/mmap offsets on the device descriptor, as
well as mechanisms for describing and registering interrupt
notifications.
PCI device의 legacy VFIO 준비 예제
118-167예제는 PCI device `0000:06:0d.0`의 `iommu_group` symlink를 읽어 group 26임을 확인합니다. PCI bus device이므로 `vfio-pci` module을 적재한 뒤 기존 driver에서 unbind하고 vendor/device ID `1102:0002`를 `vfio-pci/new_id`에 씁니다.
Group의 다른 device도 확인해야 합니다. 이 topology에는 `0000:00:1e.0`, `0000:06:0d.0`, `0000:06:0d.1`이 함께 있습니다. 대상 device가 PCIe-to-PCI bridge 뒤에 있으므로 `0000:06:0d.1`도 같은 절차로 준비해야 합니다.
`0000:00:1e.0`은 현재 host driver가 없는 bridge이므로 `vfio-pci`에 bind할 필요가 없습니다. 당시 `vfio-pci`가 PCI bridge를 지원하지 않는다는 점도 함께 명시됩니다.
비권한 사용자에게 operation을 허용하려면 `/dev/vfio/26` ownership을 넘깁니다. `/dev/vfio/vfio` 자체는 capability를 제공하지 않으므로 system에서 mode `0666`으로 설정할 것으로 기대합니다. 이 작업 뒤 사용자는 group의 모든 device와 IOMMU에 접근할 수 있습니다.
VFIO Usage Example
------------------
Assume user wants to access PCI device 0000:06:0d.0::
$ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group
../../../../kernel/iommu_groups/26
This device is therefore in IOMMU group 26. This device is on the
pci bus, therefore the user will make use of vfio-pci to manage the
group::
# modprobe vfio-pci
Binding this device to the vfio-pci driver creates the VFIO group
character devices for this group::
$ lspci -n -s 0000:06:0d.0
06:0d.0 0401: 1102:0002 (rev 08)
# echo 0000:06:0d.0 > /sys/bus/pci/devices/0000:06:0d.0/driver/unbind
# echo 1102 0002 > /sys/bus/pci/drivers/vfio-pci/new_id
Now we need to look at what other devices are in the group to free
it for use by VFIO::
$ ls -l /sys/bus/pci/devices/0000:06:0d.0/iommu_group/devices
total 0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:00:1e.0 ->
../../../../devices/pci0000:00/0000:00:1e.0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.0 ->
../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.0
lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.1 ->
../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.1
This device is behind a PCIe-to-PCI bridge [4]_, therefore we also
need to add device 0000:06:0d.1 to the group following the same
procedure as above. Device 0000:00:1e.0 is a bridge that does
not currently have a host driver, therefore it's not required to
bind this device to the vfio-pci driver (vfio-pci does not currently
support PCI bridges).
The final step is to provide the user with access to the group if
unprivileged operation is desired (note that /dev/vfio/vfio provides
no capabilities on its own and is therefore expected to be set to
mode 0666 by the system)::
# chown user:user /dev/vfio/26
The user now has full access to all the devices and the iommu for this
group and can access them as follows::
Legacy group/container programming 순서
168-241사용자 program은 container와 group fd를 열고 API version과 `VFIO_TYPE1_IOMMU` extension을 검사합니다. `VFIO_GROUP_GET_STATUS`로 group이 viable한지 확인한 뒤 `VFIO_GROUP_SET_CONTAINER`와 `VFIO_SET_IOMMU`를 차례로 호출합니다.
그 다음 `VFIO_IOMMU_GET_INFO`로 IOMMU 정보를 얻고 userspace memory 1MiB를 할당하여 IOVA 0부터 read/write 가능한 DMA mapping을 `VFIO_IOMMU_MAP_DMA`로 만듭니다.
`VFIO_GROUP_GET_DEVICE_FD`로 `0000:06:0d.0`의 device fd를 얻은 뒤 `VFIO_DEVICE_GET_INFO`를 호출합니다. 모든 region에 대해 `VFIO_DEVICE_GET_REGION_INFO`를 조회하고 read/write offset과 mmap을 설정합니다. PCI configuration space도 region 하나로 표현됩니다.
모든 IRQ index에는 `VFIO_DEVICE_GET_IRQ_INFO`를 사용하고 eventfd와 `VFIO_DEVICE_SET_IRQS`로 interrupt를 설정할 수 있습니다. 마지막 예제 단계는 `VFIO_DEVICE_RESET`으로 device를 reset하는 것입니다.
Container, group, IOMMU, DMA, device region과 IRQ 순서로 권한과 기능을 단계적으로 활성화합니다.
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) };
/* Create a new container */
container = open("/dev/vfio/vfio", O_RDWR);
if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
/* Unknown API version */
if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU))
/* Doesn't support the IOMMU driver we want. */
/* Open the group */
group = open("/dev/vfio/26", O_RDWR);
/* Test the group is viable and available */
ioctl(group, VFIO_GROUP_GET_STATUS, &group_status);
if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE))
/* Group is not viable (ie, not all devices bound for vfio) */
/* Add the group to the container */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
/* Enable the IOMMU model we want */
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
/* Get addition IOMMU info */
ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
/* Allocate some space and setup a DMA mapping */
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);
/* Get a file descriptor for the device */
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
/* Test and setup the device */
ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
for (i = 0; i < device_info.num_regions; i++) {
struct vfio_region_info reg = { .argsz = sizeof(reg) };
reg.index = i;
ioctl(device, VFIO_DEVICE_GET_REGION_INFO, ®);
/* Setup mappings... read/write offsets, mmaps
* For PCI devices, config space is a region */
}
for (i = 0; i < device_info.num_irqs; i++) {
struct vfio_irq_info irq = { .argsz = sizeof(irq) };
irq.index = i;
ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);
/* Setup IRQs... eventfds, VFIO_DEVICE_SET_IRQS */
}
/* Gratuitous device reset and go... */
ioctl(device, VFIO_DEVICE_RESET);
IOMMUFD와 vfio_iommu_type1 호환성
242-268IOMMUFD는 userspace에서 I/O page table을 관리하는 새로운 user API입니다. Nested translation과 PASID 같은 고급 userspace DMA 기능을 제공하는 portal을 목표로 하면서 기존 `VFIO_TYPE1v2_IOMMU` 사용 사례를 위한 backward-compatibility interface도 제공합니다.
장기적으로 `vfio_iommu_type1` driver와 legacy VFIO container/group model은 deprecated될 예정입니다. 호환 interface는 kernel의 `CONFIG_IOMMUFD_VFIO_CONTAINER`를 켜 IOMMUFD가 container와 IOMMU backend infrastructure를 투명하게 제공하게 하거나, `/dev/vfio/vfio`를 `/dev/iommu`로 symlink하여 사용할 수 있습니다.
작성 시점의 compatibility mode는 DMA mapping MMIO 등에서 `VFIO_TYPE1v2_IOMMU`와 완전히 같은 기능을 제공하지 않으며 `VFIO_SPAPR_TCE_IOMMU` 호환성도 제공하지 않습니다. 따라서 native VFIO 구현에서 IOMMUFD compatibility interface로 일반적인 전환은 아직 권장되지 않습니다.
장기적으로 VFIO 사용자는 아래의 cdev interface를 통한 device access와 IOMMUFD native interface로 이동해야 합니다.
IOMMUFD and vfio_iommu_type1
----------------------------
IOMMUFD is the new user API to manage I/O page tables from userspace.
It intends to be the portal of delivering advanced userspace DMA
features (nested translation [5]_, PASID [6]_, etc.) while also providing
a backwards compatibility interface for existing VFIO_TYPE1v2_IOMMU use
cases. Eventually the vfio_iommu_type1 driver, as well as the legacy
vfio container and group model is intended to be deprecated.
The IOMMUFD backwards compatibility interface can be enabled two ways.
In the first method, the kernel can be configured with
CONFIG_IOMMUFD_VFIO_CONTAINER, in which case the IOMMUFD subsystem
transparently provides the entire infrastructure for the VFIO
container and IOMMU backend interfaces. The compatibility mode can
also be accessed if the VFIO container interface, ie. /dev/vfio/vfio is
simply symlink'd to /dev/iommu. Note that at the time of writing, the
compatibility mode is not entirely feature complete relative to
VFIO_TYPE1v2_IOMMU (ex. DMA mapping MMIO) and does not attempt to
provide compatibility to the VFIO_SPAPR_TCE_IOMMU interface. Therefore
it is not generally advisable at this time to switch from native VFIO
implementations to the IOMMUFD compatibility interfaces.
Long term, VFIO users should migrate to device access through the cdev
interface described below, and native access through the IOMMUFD
provided interfaces.
VFIO device cdev와 DMA ownership
269-300전통적으로 userspace는 VFIO group의 `VFIO_GROUP_GET_DEVICE_FD`를 통해 device fd를 얻습니다. `CONFIG_VFIO_DEVICE_CDEV=y`이면 `/dev/vfio/devices/vfioX` character device를 직접 열 수 있으며, X는 등록 device에 VFIO가 고유하게 배정한 번호입니다.
Cdev interface는 noiommu device를 지원하지 않으므로 noiommu가 필요하면 legacy group interface를 사용해야 합니다.
Cdev는 IOMMUFD와만 동작합니다. VFIO driver와 application은 실제 device 사용 전에 `VFIO_DEVICE_BIND_IOMMUFD`로 DMA ownership을 claim해야 하는 새 security model에 맞춰야 합니다. BIND가 성공한 뒤에만 userspace가 VFIO device 전체에 접근할 수 있습니다.
Device cdev는 VFIO group/container/iommu driver에 의존하지 않으므로 legacy application이 없는 환경에서는 해당 module을 완전히 compile out할 수 있습니다. SPAPR는 아직 IOMMUFD를 지원하지 않아 device cdev도 지원할 수 없습니다.
Cdev 접근도 IOMMU group semantics에 묶입니다. Group에는 DMA owner가 하나만 있을 수 있으므로 같은 group의 device를 여러 `iommufd_ctx`에 bind하거나 native kernel driver와 VFIO bus driver, `driver_managed_dma` flag를 지원하는 다른 driver 사이에서 공유할 수 없습니다.
Ownership 요구를 위반하면 full device access를 gate하는 `VFIO_DEVICE_BIND_IOMMUFD` ioctl이 실패합니다.
Cdev를 여는 것만으로 device 권한을 얻지 못하며 IOMMUFD DMA ownership bind가 반드시 성공해야 합니다.
VFIO Device cdev
----------------
Traditionally user acquires a device fd via VFIO_GROUP_GET_DEVICE_FD
in a VFIO group.
With CONFIG_VFIO_DEVICE_CDEV=y the user can now acquire a device fd
by directly opening a character device /dev/vfio/devices/vfioX where
"X" is the number allocated uniquely by VFIO for registered devices.
cdev interface does not support noiommu devices, so user should use
the legacy group interface if noiommu is wanted.
The cdev only works with IOMMUFD. Both VFIO drivers and applications
must adapt to the new cdev security model which requires using
VFIO_DEVICE_BIND_IOMMUFD to claim DMA ownership before starting to
actually use the device. Once BIND succeeds then a VFIO device can
be fully accessed by the user.
VFIO device cdev doesn't rely on VFIO group/container/iommu drivers.
Hence those modules can be fully compiled out in an environment
where no legacy VFIO application exists.
So far SPAPR does not support IOMMUFD yet. So it cannot support device
cdev either.
vfio device cdev access is still bound by IOMMU group semantics, ie. there
can be only one DMA owner for the group. Devices belonging to the same
group can not be bound to multiple iommufd_ctx or shared between native
kernel and vfio bus driver or other driver supporting the driver_managed_dma
flag. A violation of this ownership requirement will fail at the
VFIO_DEVICE_BIND_IOMMUFD ioctl, which gates full device access.
Device cdev와 IOMMUFD 사용 예제
301-372PCI device `0000:6a:01.0`의 `vfio-dev` sysfs directory에서 `vfio0`을 확인합니다. `/dev/vfio/devices/vfio0`, sysfs의 `dev` 값 `511:0`, `/dev/char/511:0` symlink를 비교해 같은 character device임을 검증합니다.
비권한 사용이 필요하면 `/dev/vfio/devices/vfio0` ownership을 사용자에게 넘기고 해당 cdev를 `O_RDWR`로 엽니다. 열린 `cdev_fd`만으로는 IOMMUFD에 bind할 권한 외에 device 접근 권한이 생기지 않습니다.
Program은 `/dev/iommu`를 열고 `VFIO_DEVICE_BIND_IOMMUFD`로 cdev를 bind합니다. 이어 `IOMMU_IOAS_ALLOC`으로 IO Address Space를 만들고 반환된 `out_ioas_id`를 `VFIO_DEVICE_ATTACH_IOMMUFD_PT`의 `pt_id`로 넘겨 device를 IOAS/HWPT에 attach합니다.
마지막으로 userspace memory 1MiB를 mmap하고 `IOMMU_IOAS_MAP_READABLE`, `IOMMU_IOAS_MAP_WRITEABLE`, `IOMMU_IOAS_MAP_FIXED_IOVA` flag를 지정해 IOVA 0에 `IOMMU_IOAS_MAP`으로 mapping합니다. 이후 device operation은 앞의 VFIO usage example과 같습니다.
Character device 식별 뒤 IOMMUFD bind, IOAS allocation, page-table attach, DMA map 순서로 진행합니다.
Device cdev Example
-------------------
Assume user wants to access PCI device 0000:6a:01.0::
$ ls /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/
vfio0
This device is therefore represented as vfio0. The user can verify
its existence::
$ ls -l /dev/vfio/devices/vfio0
crw------- 1 root root 511, 0 Feb 16 01:22 /dev/vfio/devices/vfio0
$ cat /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/vfio0/dev
511:0
$ ls -l /dev/char/511\:0
lrwxrwxrwx 1 root root 21 Feb 16 01:22 /dev/char/511:0 -> ../vfio/devices/vfio0
Then provide the user with access to the device if unprivileged
operation is desired::
$ chown user:user /dev/vfio/devices/vfio0
Finally the user could get cdev fd by::
cdev_fd = open("/dev/vfio/devices/vfio0", O_RDWR);
An opened cdev_fd doesn't give the user any permission of accessing
the device except binding the cdev_fd to an iommufd. After that point
then the device is fully accessible including attaching it to an
IOMMUFD IOAS/HWPT to enable userspace DMA::
struct vfio_device_bind_iommufd bind = {
.argsz = sizeof(bind),
.flags = 0,
};
struct iommu_ioas_alloc alloc_data = {
.size = sizeof(alloc_data),
.flags = 0,
};
struct vfio_device_attach_iommufd_pt attach_data = {
.argsz = sizeof(attach_data),
.flags = 0,
};
struct iommu_ioas_map map = {
.size = sizeof(map),
.flags = IOMMU_IOAS_MAP_READABLE |
IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_FIXED_IOVA,
.__reserved = 0,
};
iommufd = open("/dev/iommu", O_RDWR);
bind.iommufd = iommufd;
ioctl(cdev_fd, VFIO_DEVICE_BIND_IOMMUFD, &bind);
ioctl(iommufd, IOMMU_IOAS_ALLOC, &alloc_data);
attach_data.pt_id = alloc_data.out_ioas_id;
ioctl(cdev_fd, VFIO_DEVICE_ATTACH_IOMMUFD_PT, &attach_data);
/* Allocate some space and setup a DMA mapping */
map.user_va = (int64_t)mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
map.iova = 0; /* 1MB starting at 0x0 from device view */
map.length = 1024 * 1024;
map.ioas_id = alloc_data.out_ioas_id;
ioctl(iommufd, IOMMU_IOAS_MAP, &map);
/* Other device operations as stated in "VFIO Usage Example" */
VFIO userspace UAPI 문서 위치
373-377VFIO userspace API의 완전한 설명은 `include/uapi/linux/vfio.h`를 참조해야 합니다. 이 header가 ioctl number, structure, flag와 userspace ABI 계약의 기준입니다.
VFIO User API
-------------------------------------------------------------------------------
Please see include/uapi/linux/vfio.h for complete API documentation.
VFIO bus driver 등록과 vfio_device_ops
378-433`vfio-pci` 같은 VFIO bus driver는 VFIO core interface를 소수만 사용합니다. Device bind와 unbind 시 `vfio_register_group_dev()`, `vfio_register_emulated_iommu_dev()`, `vfio_unregister_group_dev()`를 호출합니다.
Driver는 자신의 structure 안에 `vfio_device`를 embed하고 `vfio_alloc_device()`로 전체 structure를 할당해야 합니다. `@init`과 `@release` callback을 등록해 `vfio_device`를 감싼 private state를 관리하며 마지막에는 `vfio_put_device()`를 사용합니다.
`vfio_register_group_dev()`는 core가 지정 device의 `iommu_group` tracking을 시작하고 해당 device가 VFIO bus driver 소유임을 등록하게 합니다. 함수가 반환되면 userspace가 즉시 driver에 접근할 수 있으므로, 호출 전에 driver가 완전히 준비되어 있어야 합니다.
Driver는 file-operations 구조와 비슷한 `struct vfio_device_ops`를 제공합니다. 여기에는 `init`, `release`, IOMMUFD bind/unbind, IOAS attach/detach, open/close, read/write/ioctl/mmap, request, match, DMA unmap, device feature callback이 포함됩니다.
각 callback에는 등록 당시의 `vdev`가 전달되므로 bus driver는 `container_of()`로 자신의 private data를 복구할 수 있습니다.
VFIO bus driver API
-------------------------------------------------------------------------------
VFIO bus drivers, such as vfio-pci make use of only a few interfaces
into VFIO core. When devices are bound and unbound to the driver,
Following interfaces are called when devices are bound to and
unbound from the driver::
int vfio_register_group_dev(struct vfio_device *device);
int vfio_register_emulated_iommu_dev(struct vfio_device *device);
void vfio_unregister_group_dev(struct vfio_device *device);
The driver should embed the vfio_device in its own structure and use
vfio_alloc_device() to allocate the structure, and can register
@init/@release callbacks to manage any private state wrapping the
vfio_device::
vfio_alloc_device(dev_struct, member, dev, ops);
void vfio_put_device(struct vfio_device *device);
vfio_register_group_dev() indicates to the core to begin tracking the
iommu_group of the specified dev and register the dev as owned by a VFIO bus
driver. Once vfio_register_group_dev() returns it is possible for userspace to
start accessing the driver, thus the driver should ensure it is completely
ready before calling it. The driver provides an ops structure for callbacks
similar to a file operations structure::
struct vfio_device_ops {
char *name;
int (*init)(struct vfio_device *vdev);
void (*release)(struct vfio_device *vdev);
int (*bind_iommufd)(struct vfio_device *vdev,
struct iommufd_ctx *ictx, u32 *out_device_id);
void (*unbind_iommufd)(struct vfio_device *vdev);
int (*attach_ioas)(struct vfio_device *vdev, u32 *pt_id);
void (*detach_ioas)(struct vfio_device *vdev);
int (*open_device)(struct vfio_device *vdev);
void (*close_device)(struct vfio_device *vdev);
ssize_t (*read)(struct vfio_device *vdev, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*write)(struct vfio_device *vdev, const char __user *buf,
size_t count, loff_t *size);
long (*ioctl)(struct vfio_device *vdev, unsigned int cmd,
unsigned long arg);
int (*mmap)(struct vfio_device *vdev, struct vm_area_struct *vma);
void (*request)(struct vfio_device *vdev, unsigned int count);
int (*match)(struct vfio_device *vdev, char *buf);
void (*dma_unmap)(struct vfio_device *vdev, u64 iova, u64 length);
int (*device_feature)(struct vfio_device *device, u32 flags,
void __user *arg, size_t argsz);
};
Each function is passed the vdev that was originally registered
in the vfio_register_group_dev() or vfio_register_emulated_iommu_dev()
call above. This allows the bus driver to obtain its private data using
container_of().
VFIO bus-driver callback 호출 시점
434-466`init`과 `release` callback은 `vfio_device`가 초기화되고 해제될 때 호출됩니다. `open_device`와 `close_device`는 user session에서 첫 device fd가 생성되거나 마지막 fd가 닫힐 때의 device 수명을 관리합니다. Legacy 경로의 예는 `VFIO_GROUP_GET_DEVICE_FD`입니다.
`ioctl` callback은 일부 `VFIO_DEVICE_*` ioctl을 driver에 직접 전달합니다. IOMMUFD bind/unbind 시 `[un]bind_iommufd`, bound IOMMUFD가 관리하는 IOAS에 attach/detach할 때 `[de]attach_ioas`가 호출됩니다. IOMMUFD에서 unbind하면 attached IOAS도 자동 detach될 수 있습니다.
`read`, `write`, `mmap`은 device 자체의 `VFIO_DEVICE_GET_REGION_INFO` ioctl이 정의한 region access를 구현합니다.
`request`는 VFIO bus driver에서 device를 unbind하려는 경우처럼 device unregister 직전에 호출됩니다.
`dma_unmap`은 device가 연결한 container 또는 IOAS에서 IOVA range가 unmap될 때 호출됩니다. VFIO page-pinning interface를 쓰는 driver는 해당 range의 page를 unpin하도록 반드시 이 callback을 구현해야 하며, `open_device()`가 호출되기 전에도 이 callback이 올 수 있음을 허용해야 합니다.
Registration 준비부터 userspace session, DMA mapping 정리, unregister까지 callback 순서를 구분합니다.
::
- The init/release callbacks are issued when vfio_device is initialized
and released.
- The open/close device callbacks are issued when the first
instance of a file descriptor for the device is created (eg.
via VFIO_GROUP_GET_DEVICE_FD) for a user session.
- The ioctl callback provides a direct pass through for some VFIO_DEVICE_*
ioctls.
- The [un]bind_iommufd callbacks are issued when the device is bound to
and unbound from iommufd.
- The [de]attach_ioas callback is issued when the device is attached to
and detached from an IOAS managed by the bound iommufd. However, the
attached IOAS can also be automatically detached when the device is
unbound from iommufd.
- The read/write/mmap callbacks implement the device region access defined
by the device's own VFIO_DEVICE_GET_REGION_INFO ioctl.
- The request callback is issued when device is going to be unregistered,
such as when trying to unbind the device from the vfio bus driver.
- The dma_unmap callback is issued when a range of iovas are unmapped
in the container or IOAS attached by the device. Drivers which make
use of the vfio page pinning interface must implement this callback in
order to unpin pages within the dma_unmap range. Drivers must tolerate
this callback even before calls to open_device().
PPC64 sPAPR 구현 특성
467-515구형 POWER7 P5IOC2/IODA1 시스템은 boot 때 PE인 IOMMU group마다 IOMMU table 하나를 할당하므로 container 하나에 group 하나만 지원합니다. PE는 흔히 PCI domain이지만 항상 그런 것은 아닙니다. POWER8 IODA2 이후 hardware는 이 제한을 없애 container 하나에 여러 IOMMU group을 둘 수 있습니다.
Hardware는 DMA transfer가 허용되는 PCI address range인 DMA window를 지원합니다. Window 밖 address space에 접근하려 하면 PE 전체가 isolation됩니다.
PPC64 guest는 paravirtualized이지만 완전히 emulated되지는 않습니다. DMA page map/unmap API는 보통 호출당 1~32 page를 처리하고 호출 수를 줄일 방법이 없습니다. 성능을 위해 real mode에서 처리하지만, 그 결과 locked-page accounting을 실시간으로 할 수 없는 제한이 있습니다.
sPAPR에서 PE는 partitioning과 error recovery의 단위로 다룰 수 있는 I/O subtree입니다. Single/multi-function IOA, multi-function IOA의 한 function, 여러 IOA와 그 위 switch/bridge 구조가 PE가 될 수 있습니다. PPC64 guest는 추가 ioctl 기반 EEH RTAS service로 PCI error를 감지하고 복구합니다.
추가 ioctl은 네 가지입니다. `VFIO_IOMMU_SPAPR_TCE_GET_INFO`는 PCI bus DMA window 시작과 크기를 반환합니다. `VFIO_IOMMU_ENABLE`은 container를 enable하면서 locked-page accounting을 수행해 사용자가 DMA window를 확인하고 실제 작업 전 rlimit을 조정하게 합니다. `VFIO_IOMMU_DISABLE`은 container를 disable합니다. `VFIO_EEH_PE_OP`는 EEH setup, error detection, recovery API를 제공합니다.
PPC64 sPAPR implementation note
-------------------------------
This implementation has some specifics:
1) On older systems (POWER7 with P5IOC2/IODA1) only one IOMMU group per
container is supported as an IOMMU table is allocated at the boot time,
one table per a IOMMU group which is a Partitionable Endpoint (PE)
(PE is often a PCI domain but not always).
Newer systems (POWER8 with IODA2) have improved hardware design which allows
to remove this limitation and have multiple IOMMU groups per a VFIO
container.
2) The hardware supports so called DMA windows - the PCI address range
within which DMA transfer is allowed, any attempt to access address space
out of the window leads to the whole PE isolation.
3) PPC64 guests are paravirtualized but not fully emulated. There is an API
to map/unmap pages for DMA, and it normally maps 1..32 pages per call and
currently there is no way to reduce the number of calls. In order to make
things faster, the map/unmap handling has been implemented in real mode
which provides an excellent performance which has limitations such as
inability to do locked pages accounting in real time.
4) According to sPAPR specification, A Partitionable Endpoint (PE) is an I/O
subtree that can be treated as a unit for the purposes of partitioning and
error recovery. A PE may be a single or multi-function IOA (IO Adapter), a
function of a multi-function IOA, or multiple IOAs (possibly including
switch and bridge structures above the multiple IOAs). PPC64 guests detect
PCI errors and recover from them via EEH RTAS services, which works on the
basis of additional ioctl commands.
So 4 additional ioctls have been added:
VFIO_IOMMU_SPAPR_TCE_GET_INFO
returns the size and the start of the DMA window on the PCI bus.
VFIO_IOMMU_ENABLE
enables the container. The locked pages accounting
is done at this point. This lets user first to know what
the DMA window is and adjust rlimit before doing any real job.
VFIO_IOMMU_DISABLE
disables the container.
VFIO_EEH_PE_OP
provides an API for EEH setup, error detection and recovery.
sPAPR DMA와 EEH 복구 예제
516-628앞선 legacy 예제의 흐름은 sPAPR에 맞게 조금 바뀝니다. Group을 container에 넣고 IOMMU model을 `VFIO_SPAPR_TCE_IOMMU`로 설정한 뒤 `VFIO_IOMMU_SPAPR_TCE_GET_INFO`로 DMA window를 조회하고 `VFIO_IOMMU_ENABLE`로 container를 활성화합니다. 실패하면 낮은 rlimit이 원인일 수 있습니다.
DMA mapping의 `iova`와 `size`가 조회한 window 안에 있는지 확인한 뒤 mapping하고 device fd를 얻습니다. `VFIO_CHECK_EXTENSION`으로 `VFIO_EEH`를 검사하고 `VFIO_EEH_PE_ENABLE`로 device의 EEH 기능을 켭니다.
같은 IOMMU group의 child device를 하나의 PE instance에 모아 추후 참조하는 별도 data structure를 만드는 것이 권장됩니다. `VFIO_EEH_PE_GET_STATE`로 PE가 functional state인지 확인하고 EEH가 활성화된 상태에서 `pci_save_state()`에 해당하는 device state를 저장합니다.
예제는 32-bit configuration load로 발생할 것으로 기대되는 EEH error를 `VFIO_EEH_PE_INJECT_ERR`로 주입합니다. PCI config space나 I/O BAR read가 `0xFF`를 반환하면 PE state를 조회해 freeze 여부를 확인하고, pending PCI transaction 완료를 기다린 뒤 recovery가 끝날 때까지 해당 PE와의 traffic을 멈춥니다.
`VFIO_EEH_PE_UNFREEZE_IO`로 I/O를 enable하고 standard PCI config space와 AER register log를 수집합니다. 이어 hot reset 또는 필요한 경우 fundamental reset을 수행하고 `VFIO_EEH_PE_RESET_DEACTIVATE`로 reset을 해제합니다.
`VFIO_EEH_PE_CONFIGURE`로 영향받은 PE의 PCI bridge를 구성하고 초기화 때 저장한 state를 `pci_restore_state()` 방식으로 복원합니다. 복구가 성공하면 해당 PE의 PCI traffic을 재개할 수 있습니다.
Window 확인과 EEH 활성화 뒤 freeze 감지, traffic 정지, log 수집, reset, bridge 재구성, state 복원 순서로 복구합니다.
The code flow from the example above should be slightly changed::
struct vfio_eeh_pe_op pe_op = { .argsz = sizeof(pe_op), .flags = 0 };
.....
/* Add the group to the container */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
/* Enable the IOMMU model we want */
ioctl(container, VFIO_SET_IOMMU, VFIO_SPAPR_TCE_IOMMU)
/* Get addition sPAPR IOMMU info */
vfio_iommu_spapr_tce_info spapr_iommu_info;
ioctl(container, VFIO_IOMMU_SPAPR_TCE_GET_INFO, &spapr_iommu_info);
if (ioctl(container, VFIO_IOMMU_ENABLE))
/* Cannot enable container, may be low rlimit */
/* Allocate some space and setup a DMA mapping */
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;
/* Check here is .iova/.size are within DMA window from spapr_iommu_info */
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
/* Get a file descriptor for the device */
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
....
/* Gratuitous device reset and go... */
ioctl(device, VFIO_DEVICE_RESET);
/* Make sure EEH is supported */
ioctl(container, VFIO_CHECK_EXTENSION, VFIO_EEH);
/* Enable the EEH functionality on the device */
pe_op.op = VFIO_EEH_PE_ENABLE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* You're suggested to create additional data struct to represent
* PE, and put child devices belonging to same IOMMU group to the
* PE instance for later reference.
*/
/* Check the PE's state and make sure it's in functional state */
pe_op.op = VFIO_EEH_PE_GET_STATE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Save device state using pci_save_state().
* EEH should be enabled on the specified device.
*/
....
/* Inject EEH error, which is expected to be caused by 32-bits
* config load.
*/
pe_op.op = VFIO_EEH_PE_INJECT_ERR;
pe_op.err.type = EEH_ERR_TYPE_32;
pe_op.err.func = EEH_ERR_FUNC_LD_CFG_ADDR;
pe_op.err.addr = 0ul;
pe_op.err.mask = 0ul;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
....
/* When 0xFF's returned from reading PCI config space or IO BARs
* of the PCI device. Check the PE's state to see if that has been
* frozen.
*/
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Waiting for pending PCI transactions to be completed and don't
* produce any more PCI traffic from/to the affected PE until
* recovery is finished.
*/
/* Enable IO for the affected PE and collect logs. Usually, the
* standard part of PCI config space, AER registers are dumped
* as logs for further analysis.
*/
pe_op.op = VFIO_EEH_PE_UNFREEZE_IO;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/*
* Issue PE reset: hot or fundamental reset. Usually, hot reset
* is enough. However, the firmware of some PCI adapters would
* require fundamental reset.
*/
pe_op.op = VFIO_EEH_PE_RESET_HOT;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
pe_op.op = VFIO_EEH_PE_RESET_DEACTIVATE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Configure the PCI bridges for the affected PE */
pe_op.op = VFIO_EEH_PE_CONFIGURE;
ioctl(container, VFIO_EEH_PE_OP, &pe_op);
/* Restored state we saved at initialization time. pci_restore_state()
* is good enough as an example.
*/
/* Hopefully, error is recovered successfully. Now, you can resume to
* start PCI traffic to/from the affected PE.
*/
....
sPAPR TCE IOMMU v2 memory 등록
629-651SPAPR TCE IOMMU v2는 `VFIO_IOMMU_ENABLE`과 `VFIO_IOMMU_DISABLE`을 deprecated하고, v1에서 지원하지 않는 `VFIO_IOMMU_SPAPR_REGISTER_MEMORY`와 `VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY` ioctl을 추가합니다.
PPC64 paravirtualized guest는 많은 map/unmap request를 만들며, 기존 처리는 page pin/unpin과 `mm::locked_vm` counter 갱신을 함께 수행해 rlimit 초과를 막았습니다. V2는 accounting과 pinning을 별도 operation으로 분리합니다.
REGISTER/UNREGISTER ioctl은 pin할 userspace block의 address와 size를 받습니다. Range를 일부만 잘라 해제하는 bisection은 지원하지 않으므로 unregister 때 등록에 사용한 정확한 address와 size를 전달해야 합니다. 자주 호출할 API는 아니며 range는 VFIO container의 linked list에 저장됩니다.
`VFIO_IOMMU_MAP_DMA`와 `VFIO_IOMMU_UNMAP_DMA`는 실제 IOMMU table만 갱신하고 pinning은 하지 않습니다. 대신 userspace address가 미리 등록된 range에 속하는지 확인합니다. 이 분리는 guest DMA 최적화에 도움이 됩니다.
5) There is v2 of SPAPR TCE IOMMU. It deprecates VFIO_IOMMU_ENABLE/
VFIO_IOMMU_DISABLE and implements 2 new ioctls:
VFIO_IOMMU_SPAPR_REGISTER_MEMORY and VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY
(which are unsupported in v1 IOMMU).
PPC64 paravirtualized guests generate a lot of map/unmap requests,
and the handling of those includes pinning/unpinning pages and updating
mm::locked_vm counter to make sure we do not exceed the rlimit.
The v2 IOMMU splits accounting and pinning into separate operations:
- VFIO_IOMMU_SPAPR_REGISTER_MEMORY/VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY ioctls
receive a user space address and size of the block to be pinned.
Bisecting is not supported and VFIO_IOMMU_UNREGISTER_MEMORY is expected to
be called with the exact address and size used for registering
the memory block. The userspace is not expected to call these often.
The ranges are stored in a linked list in a VFIO container.
- VFIO_IOMMU_MAP_DMA/VFIO_IOMMU_UNMAP_DMA ioctls only update the actual
IOMMU table and do not do pinning; instead these check that the userspace
address is from pre-registered range.
This separation helps in optimizing DMA for guests.
추가 sPAPR DMA window 생성과 제거
652-670sPAPR specification은 PCI bus에 variable page size를 쓰는 추가 DMA window를 guest가 가질 수 있게 합니다. 이를 위해 `VFIO_IOMMU_SPAPR_TCE_CREATE`와 `VFIO_IOMMU_SPAPR_TCE_REMOVE` ioctl이 추가되었으며 platform이 기능을 지원하지 않으면 userspace에 error를 반환합니다.
기존 hardware는 DMA window를 최대 두 개 지원합니다. 하나는 길이 2GB와 4K page를 사용하는 `default 32bit window`입니다. 다른 하나는 optional이며 전체 RAM만큼 클 수 있고 다른 page size를 사용할 수 있습니다. Guest driver가 64-bit DMA를 지원하면 runtime에 이 window를 만듭니다.
CREATE ioctl은 page shift, DMA window size, TCE table level 수를 받습니다. 큰 TCE table에 필요한 physically contiguous memory를 kernel이 충분히 할당하지 못할 가능성까지 표현하기 위한 값입니다. 사용 가능한 slot에 새 window를 만들고 시작 bus address를 반환합니다. Hardware 제한 때문에 userspace가 window 위치를 선택할 수는 없습니다.
REMOVE ioctl은 window의 bus start address를 받아 해당 window를 제거합니다.
Platform capability와 slot을 확인해 window를 만들고 반환된 bus address로 나중에 제거합니다.
6) sPAPR specification allows guests to have an additional DMA window(s) on
a PCI bus with a variable page size. Two ioctls have been added to support
this: VFIO_IOMMU_SPAPR_TCE_CREATE and VFIO_IOMMU_SPAPR_TCE_REMOVE.
The platform has to support the functionality or error will be returned to
the userspace. The existing hardware supports up to 2 DMA windows, one is
2GB long, uses 4K pages and called "default 32bit window"; the other can
be as big as entire RAM, use different page size, it is optional - guests
create those in run-time if the guest driver supports 64bit DMA.
VFIO_IOMMU_SPAPR_TCE_CREATE receives a page shift, a DMA window size and
a number of TCE table levels (if a TCE table is going to be big enough and
the kernel may not be able to allocate enough of physically contiguous
memory). It creates a new window in the available slot and returns the bus
address where the new window starts. Due to hardware limitation, the user
space cannot choose the location of DMA windows.
VFIO_IOMMU_SPAPR_TCE_REMOVE receives the bus start address of the window
and removes it.
안전성 주의사항과 참고 각주
671-707[1] VFIO는 Cisco의 Tom Lyon이 만든 초기 구현에서 `Virtual Function I/O`의 약자였지만, 이후 범위가 약어보다 넓어졌어도 기억하기 쉬운 이름이라 유지했습니다.
[2] `safe`는 device가 `well behaved`해야 한다는 조건에도 의존합니다. Multi-function device는 function 사이 backdoor를 가질 수 있고 single-function device도 MMIO register를 통해 PCI config space 같은 곳으로 alternative access를 제공할 수 있습니다.
전자의 위험은 IOMMU driver가 multi-function PCI device를 함께 group하도록 `iommu=group_mf` 같은 추가 예방책을 둘 수 있습니다. 후자는 막을 수 없지만 IOMMU가 나머지 isolation은 제공해야 합니다. PCI에서는 virtualization usage model을 위해 설계된 SR-IOV Virtual Function이 `well behaved`의 가장 좋은 지표입니다.
[3] VM device assignment에는 VFIO 범위를 넘어서는 trade-off가 항상 있습니다. 향후 IOMMU 기술이 일부를 줄일 것으로 기대하지만 전부 없애지는 못할 수 있습니다.
[4] 예제 device는 PCI bridge 아래에 있어 두 function의 transaction을 IOMMU가 구분할 수 없습니다. `0000:00:1e.0` bridge 아래 `06:0d.0`과 `06:0d.1`이 함께 나타나므로 한 group으로 다뤄야 합니다.
[5] Nested translation은 두 단계 address translation을 지원하는 IOMMU 기능이며 IOMMU virtualization의 address-translation 효율을 높입니다.
[6] PASID는 PCI Express가 도입한 Process Address Space ID의 약자이며 Shared Virtual Addressing(SVA)과 Scalable I/O Virtualization(Scalable IOV)의 전제 조건입니다.
Bridge가 두 function의 transaction source를 가리므로 IOMMU는 이들을 독립 device로 격리할 수 없습니다.
-------------------------------------------------------------------------------
.. [1] VFIO was originally an acronym for "Virtual Function I/O" in its
initial implementation by Tom Lyon while as Cisco. We've since
outgrown the acronym, but it's catchy.
.. [2] "safe" also depends upon a device being "well behaved". It's
possible for multi-function devices to have backdoors between
functions and even for single function devices to have alternative
access to things like PCI config space through MMIO registers. To
guard against the former we can include additional precautions in the
IOMMU driver to group multi-function PCI devices together
(iommu=group_mf). The latter we can't prevent, but the IOMMU should
still provide isolation. For PCI, SR-IOV Virtual Functions are the
best indicator of "well behaved", as these are designed for
virtualization usage models.
.. [3] As always there are trade-offs to virtual machine device
assignment that are beyond the scope of VFIO. It's expected that
future IOMMU technologies will reduce some, but maybe not all, of
these trade-offs.
.. [4] In this case the device is below a PCI bridge, so transactions
from either function of the device are indistinguishable to the iommu::
-[0000:00]-+-1e.0-[06]--+-0d.0
\-0d.1
00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev 90)
.. [5] Nested translation is an IOMMU feature which supports two stage
address translations. This improves the address translation efficiency
in IOMMU virtualization.
.. [6] PASID stands for Process Address Space ID, introduced by PCI
Express. It is a prerequisite for Shared Virtual Addressing (SVA)
and Scalable I/O Virtualization (Scalable IOV).
요약·해설
vfio.rst:1-707VFIO는 IOMMU group을 실제 격리와 ownership의 최소 단위로 삼아 VM과 userspace driver에 안전한 direct device access를 제공합니다. Legacy group/container API는 IOMMUFD와 device cdev로 이동 중이며, 새 모델에서는 `VFIO_DEVICE_BIND_IOMMUFD`가 DMA ownership과 full device access를 여는 핵심 security gate입니다.
Kernel bus driver는 `vfio_device`를 embed하고 registration 전에 모든 상태를 준비해야 합니다. IOMMUFD와 IOAS callback, region access, unregister request, DMA unmap 시 page unpin까지 수명 주기를 정확히 구현해야 합니다. PPC64 sPAPR는 DMA window, TCE, EEH error recovery와 별도 memory-registration 규칙을 추가합니다.