Documentation/driver-api/vfio.rst GitHub 원문 ↗

Linux 6.18.37 · Driver API

VFIO - "Virtual Function I/O"

VFIO의 IOMMU group·legacy container 모델, IOMMUFD device cdev, bus-driver callback, PPC64 sPAPR TCE와 EEH 복구를 설명하는 한국어 전문 번역입니다.

Source pathDocumentation/driver-api/vfio.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

vfio.rst:1-707

VFIO는 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 규칙을 추가합니다.

문서 구성
원문 줄핵심 내용
1-117VFIO 목적, IOMMU group, legacy container
118-241Legacy PCI 설정과 userspace programming
242-372IOMMUFD, device cdev와 DMA mapping
373-466VFIO UAPI와 bus-driver callback 계약
467-670PPC64 sPAPR, TCE, EEH와 DMA window
671-707안전성 각주와 PCI bridge topology

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ==================================
2 VFIO - "Virtual Function I/O" [1]_
3 ==================================
4
5 Many modern systems now provide DMA and interrupt remapping facilities
6 to help ensure I/O devices behave within the boundaries they've been
7 allotted. This includes x86 hardware with AMD-Vi and Intel VT-d,
8 POWER systems with Partitionable Endpoints (PEs) and embedded PowerPC
9 systems such as Freescale PAMU. The VFIO driver is an IOMMU/device
10 agnostic framework for exposing direct device access to userspace, in
11 a secure, IOMMU protected environment. In other words, this allows
12 safe [2]_, non-privileged, userspace drivers.
13
14 Why do we want that? Virtual machines often make use of direct device
15 access ("device assignment") when configured for the highest possible
16 I/O performance. From a device and host perspective, this simply
17 turns the VM into a userspace driver, with the benefits of
18 significantly reduced latency, higher bandwidth, and direct use of
19 bare-metal device drivers [3]_.
20
21 Some applications, particularly in the high performance computing
22 field, also benefit from low-overhead, direct device access from
23 userspace. Examples include network adapters (often non-TCP/IP based)
24 and compute accelerators. Prior to VFIO, these drivers had to either
25 go through the full development cycle to become proper upstream
26 driver, be maintained out of tree, or make use of the UIO framework,
27 which has no notion of IOMMU protection, limited interrupt support,
28 and requires root privileges to access things like PCI configuration
29 space.
30
31 The VFIO driver framework intends to unify these, replacing both the
32 KVM PCI specific device assignment code as well as provide a more
33 secure, more featureful userspace driver environment than UIO.
34
35 Groups, Devices, and IOMMUs
36 ---------------------------
37
38 Devices are the main target of any I/O driver. Devices typically
39 create a programming interface made up of I/O access, interrupts,
40 and DMA. Without going into the details of each of these, DMA is
41 by far the most critical aspect for maintaining a secure environment
42 as allowing a device read-write access to system memory imposes the
43 greatest risk to the overall system integrity.
44
45 To help mitigate this risk, many modern IOMMUs now incorporate
46 isolation properties into what was, in many cases, an interface only
47 meant for translation (ie. solving the addressing problems of devices
48 with limited address spaces). With this, devices can now be isolated
49 from each other and from arbitrary memory access, thus allowing
50 things like secure direct assignment of devices into virtual machines.
51
52 This isolation is not always at the granularity of a single device
53 though. Even when an IOMMU is capable of this, properties of devices,
54 interconnects, and IOMMU topologies can each reduce this isolation.
55 For instance, an individual device may be part of a larger multi-
56 function enclosure. While the IOMMU may be able to distinguish
57 between devices within the enclosure, the enclosure may not require
58 transactions between devices to reach the IOMMU. Examples of this
59 could be anything from a multi-function PCI device with backdoors
60 between functions to a non-PCI-ACS (Access Control Services) capable
61 bridge allowing redirection without reaching the IOMMU. Topology
62 can also play a factor in terms of hiding devices. A PCIe-to-PCI
63 bridge masks the devices behind it, making transaction appear as if
64 from the bridge itself. Obviously IOMMU design plays a major factor
65 as well.
66
67 Therefore, while for the most part an IOMMU may have device level
68 granularity, any system is susceptible to reduced granularity. The
69 IOMMU API therefore supports a notion of IOMMU groups. A group is
70 a set of devices which is isolatable from all other devices in the
71 system. Groups are therefore the unit of ownership used by VFIO.
72
73 While the group is the minimum granularity that must be used to
74 ensure secure user access, it's not necessarily the preferred
75 granularity. In IOMMUs which make use of page tables, it may be
76 possible to share a set of page tables between different groups,
77 reducing the overhead both to the platform (reduced TLB thrashing,
78 reduced duplicate page tables), and to the user (programming only
79 a single set of translations). For this reason, VFIO makes use of
80 a container class, which may hold one or more groups. A container
81 is created by simply opening the /dev/vfio/vfio character device.
82
83 On its own, the container provides little functionality, with all
84 but a couple version and extension query interfaces locked away.
85 The user needs to add a group into the container for the next level
86 of functionality. To do this, the user first needs to identify the
87 group associated with the desired device. This can be done using
88 the sysfs links described in the example below. By unbinding the
89 device from the host driver and binding it to a VFIO driver, a new
90 VFIO group will appear for the group as /dev/vfio/$GROUP, where
91 $GROUP is the IOMMU group number of which the device is a member.
92 If the IOMMU group contains multiple devices, each will need to
93 be bound to a VFIO driver before operations on the VFIO group
94 are allowed (it's also sufficient to only unbind the device from
95 host drivers if a VFIO driver is unavailable; this will make the
96 group available, but not that particular device). TBD - interface
97 for disabling driver probing/locking a device.
98
99 Once the group is ready, it may be added to the container by opening
100 the VFIO group character device (/dev/vfio/$GROUP) and using the
101 VFIO_GROUP_SET_CONTAINER ioctl, passing the file descriptor of the
102 previously opened container file. If desired and if the IOMMU driver
103 supports sharing the IOMMU context between groups, multiple groups may
104 be set to the same container. If a group fails to set to a container
105 with existing groups, a new empty container will need to be used
106 instead.
107
108 With a group (or groups) attached to a container, the remaining
109 ioctls become available, enabling access to the VFIO IOMMU interfaces.
110 Additionally, it now becomes possible to get file descriptors for each
111 device within a group using an ioctl on the VFIO group file descriptor.
112
113 The VFIO device API includes ioctls for describing the device, the I/O
114 regions and their read/write/mmap offsets on the device descriptor, as
115 well as mechanisms for describing and registering interrupt
116 notifications.
117
118 VFIO Usage Example
119 ------------------
120
121 Assume user wants to access PCI device 0000:06:0d.0::
122
123 $ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group
124 ../../../../kernel/iommu_groups/26
125
126 This device is therefore in IOMMU group 26. This device is on the
127 pci bus, therefore the user will make use of vfio-pci to manage the
128 group::
129
130 # modprobe vfio-pci
131
132 Binding this device to the vfio-pci driver creates the VFIO group
133 character devices for this group::
134
135 $ lspci -n -s 0000:06:0d.0
136 06:0d.0 0401: 1102:0002 (rev 08)
137 # echo 0000:06:0d.0 > /sys/bus/pci/devices/0000:06:0d.0/driver/unbind
138 # echo 1102 0002 > /sys/bus/pci/drivers/vfio-pci/new_id
139
140 Now we need to look at what other devices are in the group to free
141 it for use by VFIO::
142
143 $ ls -l /sys/bus/pci/devices/0000:06:0d.0/iommu_group/devices
144 total 0
145 lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:00:1e.0 ->
146 ../../../../devices/pci0000:00/0000:00:1e.0
147 lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.0 ->
148 ../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.0
149 lrwxrwxrwx. 1 root root 0 Apr 23 16:13 0000:06:0d.1 ->
150 ../../../../devices/pci0000:00/0000:00:1e.0/0000:06:0d.1
151
152 This device is behind a PCIe-to-PCI bridge [4]_, therefore we also
153 need to add device 0000:06:0d.1 to the group following the same
154 procedure as above. Device 0000:00:1e.0 is a bridge that does
155 not currently have a host driver, therefore it's not required to
156 bind this device to the vfio-pci driver (vfio-pci does not currently
157 support PCI bridges).
158
159 The final step is to provide the user with access to the group if
160 unprivileged operation is desired (note that /dev/vfio/vfio provides
161 no capabilities on its own and is therefore expected to be set to
162 mode 0666 by the system)::
163
164 # chown user:user /dev/vfio/26
165
166 The user now has full access to all the devices and the iommu for this
167 group and can access them as follows::
168
169 int container, group, device, i;
170 struct vfio_group_status group_status =
171 { .argsz = sizeof(group_status) };
172 struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) };
173 struct vfio_iommu_type1_dma_map dma_map = { .argsz = sizeof(dma_map) };
174 struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
175
176 /* Create a new container */
177 container = open("/dev/vfio/vfio", O_RDWR);
178
179 if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
180 /* Unknown API version */
181
182 if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU))
183 /* Doesn't support the IOMMU driver we want. */
184
185 /* Open the group */
186 group = open("/dev/vfio/26", O_RDWR);
187
188 /* Test the group is viable and available */
189 ioctl(group, VFIO_GROUP_GET_STATUS, &group_status);
190
191 if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE))
192 /* Group is not viable (ie, not all devices bound for vfio) */
193
194 /* Add the group to the container */
195 ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
196
197 /* Enable the IOMMU model we want */
198 ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
199
200 /* Get addition IOMMU info */
201 ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
202
203 /* Allocate some space and setup a DMA mapping */
204 dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
205 MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
206 dma_map.size = 1024 * 1024;
207 dma_map.iova = 0; /* 1MB starting at 0x0 from device view */
208 dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
209
210 ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
211
212 /* Get a file descriptor for the device */
213 device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
214
215 /* Test and setup the device */
216 ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
217
218 for (i = 0; i < device_info.num_regions; i++) {
219 struct vfio_region_info reg = { .argsz = sizeof(reg) };
220
221 reg.index = i;
222
223 ioctl(device, VFIO_DEVICE_GET_REGION_INFO, &reg);
224
225 /* Setup mappings... read/write offsets, mmaps
226 * For PCI devices, config space is a region */
227 }
228
229 for (i = 0; i < device_info.num_irqs; i++) {
230 struct vfio_irq_info irq = { .argsz = sizeof(irq) };
231
232 irq.index = i;
233
234 ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);
235
236 /* Setup IRQs... eventfds, VFIO_DEVICE_SET_IRQS */
237 }
238
239 /* Gratuitous device reset and go... */
240 ioctl(device, VFIO_DEVICE_RESET);
241
242 IOMMUFD and vfio_iommu_type1
243 ----------------------------
244
245 IOMMUFD is the new user API to manage I/O page tables from userspace.
246 It intends to be the portal of delivering advanced userspace DMA
247 features (nested translation [5]_, PASID [6]_, etc.) while also providing
248 a backwards compatibility interface for existing VFIO_TYPE1v2_IOMMU use
249 cases. Eventually the vfio_iommu_type1 driver, as well as the legacy
250 vfio container and group model is intended to be deprecated.
251
252 The IOMMUFD backwards compatibility interface can be enabled two ways.
253 In the first method, the kernel can be configured with
254 CONFIG_IOMMUFD_VFIO_CONTAINER, in which case the IOMMUFD subsystem
255 transparently provides the entire infrastructure for the VFIO
256 container and IOMMU backend interfaces. The compatibility mode can
257 also be accessed if the VFIO container interface, ie. /dev/vfio/vfio is
258 simply symlink'd to /dev/iommu. Note that at the time of writing, the
259 compatibility mode is not entirely feature complete relative to
260 VFIO_TYPE1v2_IOMMU (ex. DMA mapping MMIO) and does not attempt to
261 provide compatibility to the VFIO_SPAPR_TCE_IOMMU interface. Therefore
262 it is not generally advisable at this time to switch from native VFIO
263 implementations to the IOMMUFD compatibility interfaces.
264
265 Long term, VFIO users should migrate to device access through the cdev
266 interface described below, and native access through the IOMMUFD
267 provided interfaces.
268
269 VFIO Device cdev
270 ----------------
271
272 Traditionally user acquires a device fd via VFIO_GROUP_GET_DEVICE_FD
273 in a VFIO group.
274
275 With CONFIG_VFIO_DEVICE_CDEV=y the user can now acquire a device fd
276 by directly opening a character device /dev/vfio/devices/vfioX where
277 "X" is the number allocated uniquely by VFIO for registered devices.
278 cdev interface does not support noiommu devices, so user should use
279 the legacy group interface if noiommu is wanted.
280
281 The cdev only works with IOMMUFD. Both VFIO drivers and applications
282 must adapt to the new cdev security model which requires using
283 VFIO_DEVICE_BIND_IOMMUFD to claim DMA ownership before starting to
284 actually use the device. Once BIND succeeds then a VFIO device can
285 be fully accessed by the user.
286
287 VFIO device cdev doesn't rely on VFIO group/container/iommu drivers.
288 Hence those modules can be fully compiled out in an environment
289 where no legacy VFIO application exists.
290
291 So far SPAPR does not support IOMMUFD yet. So it cannot support device
292 cdev either.
293
294 vfio device cdev access is still bound by IOMMU group semantics, ie. there
295 can be only one DMA owner for the group. Devices belonging to the same
296 group can not be bound to multiple iommufd_ctx or shared between native
297 kernel and vfio bus driver or other driver supporting the driver_managed_dma
298 flag. A violation of this ownership requirement will fail at the
299 VFIO_DEVICE_BIND_IOMMUFD ioctl, which gates full device access.
300
301 Device cdev Example
302 -------------------
303
304 Assume user wants to access PCI device 0000:6a:01.0::
305
306 $ ls /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/
307 vfio0
308
309 This device is therefore represented as vfio0. The user can verify
310 its existence::
311
312 $ ls -l /dev/vfio/devices/vfio0
313 crw------- 1 root root 511, 0 Feb 16 01:22 /dev/vfio/devices/vfio0
314 $ cat /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/vfio0/dev
315 511:0
316 $ ls -l /dev/char/511\:0
317 lrwxrwxrwx 1 root root 21 Feb 16 01:22 /dev/char/511:0 -> ../vfio/devices/vfio0
318
319 Then provide the user with access to the device if unprivileged
320 operation is desired::
321
322 $ chown user:user /dev/vfio/devices/vfio0
323
324 Finally the user could get cdev fd by::
325
326 cdev_fd = open("/dev/vfio/devices/vfio0", O_RDWR);
327
328 An opened cdev_fd doesn't give the user any permission of accessing
329 the device except binding the cdev_fd to an iommufd. After that point
330 then the device is fully accessible including attaching it to an
331 IOMMUFD IOAS/HWPT to enable userspace DMA::
332
333 struct vfio_device_bind_iommufd bind = {
334 .argsz = sizeof(bind),
335 .flags = 0,
336 };
337 struct iommu_ioas_alloc alloc_data = {
338 .size = sizeof(alloc_data),
339 .flags = 0,
340 };
341 struct vfio_device_attach_iommufd_pt attach_data = {
342 .argsz = sizeof(attach_data),
343 .flags = 0,
344 };
345 struct iommu_ioas_map map = {
346 .size = sizeof(map),
347 .flags = IOMMU_IOAS_MAP_READABLE |
348 IOMMU_IOAS_MAP_WRITEABLE |
349 IOMMU_IOAS_MAP_FIXED_IOVA,
350 .__reserved = 0,
351 };
352
353 iommufd = open("/dev/iommu", O_RDWR);
354
355 bind.iommufd = iommufd;
356 ioctl(cdev_fd, VFIO_DEVICE_BIND_IOMMUFD, &bind);
357
358 ioctl(iommufd, IOMMU_IOAS_ALLOC, &alloc_data);
359 attach_data.pt_id = alloc_data.out_ioas_id;
360 ioctl(cdev_fd, VFIO_DEVICE_ATTACH_IOMMUFD_PT, &attach_data);
361
362 /* Allocate some space and setup a DMA mapping */
363 map.user_va = (int64_t)mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
364 MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
365 map.iova = 0; /* 1MB starting at 0x0 from device view */
366 map.length = 1024 * 1024;
367 map.ioas_id = alloc_data.out_ioas_id;
368
369 ioctl(iommufd, IOMMU_IOAS_MAP, &map);
370
371 /* Other device operations as stated in "VFIO Usage Example" */
372
373 VFIO User API
374 -------------------------------------------------------------------------------
375
376 Please see include/uapi/linux/vfio.h for complete API documentation.
377
378 VFIO bus driver API
379 -------------------------------------------------------------------------------
380
381 VFIO bus drivers, such as vfio-pci make use of only a few interfaces
382 into VFIO core. When devices are bound and unbound to the driver,
383 Following interfaces are called when devices are bound to and
384 unbound from the driver::
385
386 int vfio_register_group_dev(struct vfio_device *device);
387 int vfio_register_emulated_iommu_dev(struct vfio_device *device);
388 void vfio_unregister_group_dev(struct vfio_device *device);
389
390 The driver should embed the vfio_device in its own structure and use
391 vfio_alloc_device() to allocate the structure, and can register
392 @init/@release callbacks to manage any private state wrapping the
393 vfio_device::
394
395 vfio_alloc_device(dev_struct, member, dev, ops);
396 void vfio_put_device(struct vfio_device *device);
397
398 vfio_register_group_dev() indicates to the core to begin tracking the
399 iommu_group of the specified dev and register the dev as owned by a VFIO bus
400 driver. Once vfio_register_group_dev() returns it is possible for userspace to
401 start accessing the driver, thus the driver should ensure it is completely
402 ready before calling it. The driver provides an ops structure for callbacks
403 similar to a file operations structure::
404
405 struct vfio_device_ops {
406 char *name;
407 int (*init)(struct vfio_device *vdev);
408 void (*release)(struct vfio_device *vdev);
409 int (*bind_iommufd)(struct vfio_device *vdev,
410 struct iommufd_ctx *ictx, u32 *out_device_id);
411 void (*unbind_iommufd)(struct vfio_device *vdev);
412 int (*attach_ioas)(struct vfio_device *vdev, u32 *pt_id);
413 void (*detach_ioas)(struct vfio_device *vdev);
414 int (*open_device)(struct vfio_device *vdev);
415 void (*close_device)(struct vfio_device *vdev);
416 ssize_t (*read)(struct vfio_device *vdev, char __user *buf,
417 size_t count, loff_t *ppos);
418 ssize_t (*write)(struct vfio_device *vdev, const char __user *buf,
419 size_t count, loff_t *size);
420 long (*ioctl)(struct vfio_device *vdev, unsigned int cmd,
421 unsigned long arg);
422 int (*mmap)(struct vfio_device *vdev, struct vm_area_struct *vma);
423 void (*request)(struct vfio_device *vdev, unsigned int count);
424 int (*match)(struct vfio_device *vdev, char *buf);
425 void (*dma_unmap)(struct vfio_device *vdev, u64 iova, u64 length);
426 int (*device_feature)(struct vfio_device *device, u32 flags,
427 void __user *arg, size_t argsz);
428 };
429
430 Each function is passed the vdev that was originally registered
431 in the vfio_register_group_dev() or vfio_register_emulated_iommu_dev()
432 call above. This allows the bus driver to obtain its private data using
433 container_of().
434
435 ::
436
437 - The init/release callbacks are issued when vfio_device is initialized
438 and released.
439
440 - The open/close device callbacks are issued when the first
441 instance of a file descriptor for the device is created (eg.
442 via VFIO_GROUP_GET_DEVICE_FD) for a user session.
443
444 - The ioctl callback provides a direct pass through for some VFIO_DEVICE_*
445 ioctls.
446
447 - The [un]bind_iommufd callbacks are issued when the device is bound to
448 and unbound from iommufd.
449
450 - The [de]attach_ioas callback is issued when the device is attached to
451 and detached from an IOAS managed by the bound iommufd. However, the
452 attached IOAS can also be automatically detached when the device is
453 unbound from iommufd.
454
455 - The read/write/mmap callbacks implement the device region access defined
456 by the device's own VFIO_DEVICE_GET_REGION_INFO ioctl.
457
458 - The request callback is issued when device is going to be unregistered,
459 such as when trying to unbind the device from the vfio bus driver.
460
461 - The dma_unmap callback is issued when a range of iovas are unmapped
462 in the container or IOAS attached by the device. Drivers which make
463 use of the vfio page pinning interface must implement this callback in
464 order to unpin pages within the dma_unmap range. Drivers must tolerate
465 this callback even before calls to open_device().
466
467 PPC64 sPAPR implementation note
468 -------------------------------
469
470 This implementation has some specifics:
471
472 1) On older systems (POWER7 with P5IOC2/IODA1) only one IOMMU group per
473 container is supported as an IOMMU table is allocated at the boot time,
474 one table per a IOMMU group which is a Partitionable Endpoint (PE)
475 (PE is often a PCI domain but not always).
476
477 Newer systems (POWER8 with IODA2) have improved hardware design which allows
478 to remove this limitation and have multiple IOMMU groups per a VFIO
479 container.
480
481 2) The hardware supports so called DMA windows - the PCI address range
482 within which DMA transfer is allowed, any attempt to access address space
483 out of the window leads to the whole PE isolation.
484
485 3) PPC64 guests are paravirtualized but not fully emulated. There is an API
486 to map/unmap pages for DMA, and it normally maps 1..32 pages per call and
487 currently there is no way to reduce the number of calls. In order to make
488 things faster, the map/unmap handling has been implemented in real mode
489 which provides an excellent performance which has limitations such as
490 inability to do locked pages accounting in real time.
491
492 4) According to sPAPR specification, A Partitionable Endpoint (PE) is an I/O
493 subtree that can be treated as a unit for the purposes of partitioning and
494 error recovery. A PE may be a single or multi-function IOA (IO Adapter), a
495 function of a multi-function IOA, or multiple IOAs (possibly including
496 switch and bridge structures above the multiple IOAs). PPC64 guests detect
497 PCI errors and recover from them via EEH RTAS services, which works on the
498 basis of additional ioctl commands.
499
500 So 4 additional ioctls have been added:
501
502 VFIO_IOMMU_SPAPR_TCE_GET_INFO
503 returns the size and the start of the DMA window on the PCI bus.
504
505 VFIO_IOMMU_ENABLE
506 enables the container. The locked pages accounting
507 is done at this point. This lets user first to know what
508 the DMA window is and adjust rlimit before doing any real job.
509
510 VFIO_IOMMU_DISABLE
511 disables the container.
512
513 VFIO_EEH_PE_OP
514 provides an API for EEH setup, error detection and recovery.
515
516 The code flow from the example above should be slightly changed::
517
518 struct vfio_eeh_pe_op pe_op = { .argsz = sizeof(pe_op), .flags = 0 };
519
520 .....
521 /* Add the group to the container */
522 ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
523
524 /* Enable the IOMMU model we want */
525 ioctl(container, VFIO_SET_IOMMU, VFIO_SPAPR_TCE_IOMMU)
526
527 /* Get addition sPAPR IOMMU info */
528 vfio_iommu_spapr_tce_info spapr_iommu_info;
529 ioctl(container, VFIO_IOMMU_SPAPR_TCE_GET_INFO, &spapr_iommu_info);
530
531 if (ioctl(container, VFIO_IOMMU_ENABLE))
532 /* Cannot enable container, may be low rlimit */
533
534 /* Allocate some space and setup a DMA mapping */
535 dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
536 MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
537
538 dma_map.size = 1024 * 1024;
539 dma_map.iova = 0; /* 1MB starting at 0x0 from device view */
540 dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
541
542 /* Check here is .iova/.size are within DMA window from spapr_iommu_info */
543 ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
544
545 /* Get a file descriptor for the device */
546 device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");
547
548 ....
549
550 /* Gratuitous device reset and go... */
551 ioctl(device, VFIO_DEVICE_RESET);
552
553 /* Make sure EEH is supported */
554 ioctl(container, VFIO_CHECK_EXTENSION, VFIO_EEH);
555
556 /* Enable the EEH functionality on the device */
557 pe_op.op = VFIO_EEH_PE_ENABLE;
558 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
559
560 /* You're suggested to create additional data struct to represent
561 * PE, and put child devices belonging to same IOMMU group to the
562 * PE instance for later reference.
563 */
564
565 /* Check the PE's state and make sure it's in functional state */
566 pe_op.op = VFIO_EEH_PE_GET_STATE;
567 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
568
569 /* Save device state using pci_save_state().
570 * EEH should be enabled on the specified device.
571 */
572
573 ....
574
575 /* Inject EEH error, which is expected to be caused by 32-bits
576 * config load.
577 */
578 pe_op.op = VFIO_EEH_PE_INJECT_ERR;
579 pe_op.err.type = EEH_ERR_TYPE_32;
580 pe_op.err.func = EEH_ERR_FUNC_LD_CFG_ADDR;
581 pe_op.err.addr = 0ul;
582 pe_op.err.mask = 0ul;
583 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
584
585 ....
586
587 /* When 0xFF's returned from reading PCI config space or IO BARs
588 * of the PCI device. Check the PE's state to see if that has been
589 * frozen.
590 */
591 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
592
593 /* Waiting for pending PCI transactions to be completed and don't
594 * produce any more PCI traffic from/to the affected PE until
595 * recovery is finished.
596 */
597
598 /* Enable IO for the affected PE and collect logs. Usually, the
599 * standard part of PCI config space, AER registers are dumped
600 * as logs for further analysis.
601 */
602 pe_op.op = VFIO_EEH_PE_UNFREEZE_IO;
603 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
604
605 /*
606 * Issue PE reset: hot or fundamental reset. Usually, hot reset
607 * is enough. However, the firmware of some PCI adapters would
608 * require fundamental reset.
609 */
610 pe_op.op = VFIO_EEH_PE_RESET_HOT;
611 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
612 pe_op.op = VFIO_EEH_PE_RESET_DEACTIVATE;
613 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
614
615 /* Configure the PCI bridges for the affected PE */
616 pe_op.op = VFIO_EEH_PE_CONFIGURE;
617 ioctl(container, VFIO_EEH_PE_OP, &pe_op);
618
619 /* Restored state we saved at initialization time. pci_restore_state()
620 * is good enough as an example.
621 */
622
623 /* Hopefully, error is recovered successfully. Now, you can resume to
624 * start PCI traffic to/from the affected PE.
625 */
626
627 ....
628
629 5) There is v2 of SPAPR TCE IOMMU. It deprecates VFIO_IOMMU_ENABLE/
630 VFIO_IOMMU_DISABLE and implements 2 new ioctls:
631 VFIO_IOMMU_SPAPR_REGISTER_MEMORY and VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY
632 (which are unsupported in v1 IOMMU).
633
634 PPC64 paravirtualized guests generate a lot of map/unmap requests,
635 and the handling of those includes pinning/unpinning pages and updating
636 mm::locked_vm counter to make sure we do not exceed the rlimit.
637 The v2 IOMMU splits accounting and pinning into separate operations:
638
639 - VFIO_IOMMU_SPAPR_REGISTER_MEMORY/VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY ioctls
640 receive a user space address and size of the block to be pinned.
641 Bisecting is not supported and VFIO_IOMMU_UNREGISTER_MEMORY is expected to
642 be called with the exact address and size used for registering
643 the memory block. The userspace is not expected to call these often.
644 The ranges are stored in a linked list in a VFIO container.
645
646 - VFIO_IOMMU_MAP_DMA/VFIO_IOMMU_UNMAP_DMA ioctls only update the actual
647 IOMMU table and do not do pinning; instead these check that the userspace
648 address is from pre-registered range.
649
650 This separation helps in optimizing DMA for guests.
651
652 6) sPAPR specification allows guests to have an additional DMA window(s) on
653 a PCI bus with a variable page size. Two ioctls have been added to support
654 this: VFIO_IOMMU_SPAPR_TCE_CREATE and VFIO_IOMMU_SPAPR_TCE_REMOVE.
655 The platform has to support the functionality or error will be returned to
656 the userspace. The existing hardware supports up to 2 DMA windows, one is
657 2GB long, uses 4K pages and called "default 32bit window"; the other can
658 be as big as entire RAM, use different page size, it is optional - guests
659 create those in run-time if the guest driver supports 64bit DMA.
660
661 VFIO_IOMMU_SPAPR_TCE_CREATE receives a page shift, a DMA window size and
662 a number of TCE table levels (if a TCE table is going to be big enough and
663 the kernel may not be able to allocate enough of physically contiguous
664 memory). It creates a new window in the available slot and returns the bus
665 address where the new window starts. Due to hardware limitation, the user
666 space cannot choose the location of DMA windows.
667
668 VFIO_IOMMU_SPAPR_TCE_REMOVE receives the bus start address of the window
669 and removes it.
670
671 -------------------------------------------------------------------------------
672
673 .. [1] VFIO was originally an acronym for "Virtual Function I/O" in its
674 initial implementation by Tom Lyon while as Cisco. We've since
675 outgrown the acronym, but it's catchy.
676
677 .. [2] "safe" also depends upon a device being "well behaved". It's
678 possible for multi-function devices to have backdoors between
679 functions and even for single function devices to have alternative
680 access to things like PCI config space through MMIO registers. To
681 guard against the former we can include additional precautions in the
682 IOMMU driver to group multi-function PCI devices together
683 (iommu=group_mf). The latter we can't prevent, but the IOMMU should
684 still provide isolation. For PCI, SR-IOV Virtual Functions are the
685 best indicator of "well behaved", as these are designed for
686 virtualization usage models.
687
688 .. [3] As always there are trade-offs to virtual machine device
689 assignment that are beyond the scope of VFIO. It's expected that
690 future IOMMU technologies will reduce some, but maybe not all, of
691 these trade-offs.
692
693 .. [4] In this case the device is below a PCI bridge, so transactions
694 from either function of the device are indistinguishable to the iommu::
695
696 -[0000:00]-+-1e.0-[06]--+-0d.0
697 \-0d.1
698
699 00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev 90)
700
701 .. [5] Nested translation is an IOMMU feature which supports two stage
702 address translations. This improves the address translation efficiency
703 in IOMMU virtualization.
704
705 .. [6] PASID stands for Process Address Space ID, introduced by PCI
706 Express. It is a prerequisite for Shared Virtual Addressing (SVA)
707 and Scalable I/O Virtualization (Scalable IOV).
708

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가 통합하는 직접 장치 접근
환경VFIO가 제공하는 가치
Virtual machine낮은 latency, 높은 bandwidth, bare-metal driver 사용
HPC applicationNetwork adapter와 accelerator의 low-overhead access
IOMMU platformDMA와 interrupt remapping을 통한 격리
UIO 대체비권한 접근, 강화된 interrupt와 IOMMU protection

==================================
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-71

I/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의 기본 단위로 사용합니다.

VFIO 격리 단위 결정
Device가 I/O, interrupt, DMA를 발생IOMMU가 address translation과 isolation 수행ACS, bridge, multi-function backdoor를 평가서로 분리할 수 없는 device를 한 IOMMU group으로 묶음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 설명과 등록 기능을 제공합니다.

Legacy VFIO container 사용 흐름
`/dev/vfio/vfio`를 열어 container 생성Sysfs에서 target device의 IOMMU group 확인Group 내 모든 device를 VFIO에 bind 또는 host에서 unbind`/dev/vfio/$GROUP` 열기`VFIO_GROUP_SET_CONTAINER`로 group 연결IOMMU 설정과 device fd 획득

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에 접근할 수 있습니다.

Group 26 준비 단계
단계경로 또는 명령
Group 확인`.../0000:06:0d.0/iommu_group` → 26
VFIO PCI 적재`modprobe vfio-pci`
Host driver 해제`.../driver/unbind`
ID 등록`.../vfio-pci/new_id`에 `1102 0002`
동일 group 확인`iommu_group/devices` 목록 검사
사용자 권한`chown user:user /dev/vfio/26`

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하는 것입니다.

Legacy userspace VFIO API sequence
Open `/dev/vfio/vfio` and check API versionOpen `/dev/vfio/26` and verify VIABLESet group container and `VFIO_TYPE1_IOMMU`Map userspace memory with `VFIO_IOMMU_MAP_DMA`Get device fd and inspect regionsConfigure IRQ eventfdsReset and run device

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, &reg);

        /* 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-268

IOMMUFD는 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 전환 경로
방식상태와 용도
`CONFIG_IOMMUFD_VFIO_CONTAINER`Legacy container/backend를 IOMMUFD가 투명하게 제공
`/dev/vfio/vfio` → `/dev/iommu`Symlink 기반 compatibility access
Native IOMMUFD + cdev장기 권장 device-access model
sPAPR TCECompatibility mode에서 지원하지 않음

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이 실패합니다.

VFIO cdev 보안 gate
Open `/dev/vfio/devices/vfioX`Check IOMMU group single-owner conditionCall `VFIO_DEVICE_BIND_IOMMUFD`Claim DMA ownership for the groupEnable full VFIO device access

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-372

PCI 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과 같습니다.

Cdev와 IOMMUFD DMA 설정
Resolve sysfs `vfio-dev/vfio0`Open `/dev/vfio/devices/vfio0`Open `/dev/iommu``VFIO_DEVICE_BIND_IOMMUFD``IOMMU_IOAS_ALLOC``VFIO_DEVICE_ATTACH_IOMMUFD_PT``IOMMU_IOAS_MAP`

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-377

VFIO userspace API의 완전한 설명은 `include/uapi/linux/vfio.h`를 참조해야 합니다. 이 header가 ioctl number, structure, flag와 userspace ABI 계약의 기준입니다.

VFIO User API 기준
항목위치
UAPI header`include/uapi/linux/vfio.h`
정의 범위ioctl, structure, flag, ABI semantics

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_device_ops 역할
Callback 그룹책임
`init` / `release`vfio_device 수명과 private state
`bind_iommufd` / `unbind_iommufd`IOMMUFD context 연결
`attach_ioas` / `detach_ioas`IO address space 연결
`open_device` / `close_device`User session device lifetime
`read` / `write` / `mmap` / `ioctl`Device region과 command 구현
`request` / `dma_unmap`Unregister 요청과 pinned-page 정리

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이 올 수 있음을 허용해야 합니다.

VFIO callback lifecycle
Allocate and initialize `vfio_device`Register group or emulated-IOMMU deviceBind IOMMUFD and attach IOASOpen device and serve region operationsHandle `dma_unmap` whenever mapping disappearsRequest unregister, close, detach, release

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를 제공합니다.

sPAPR 전용 ioctl
ioctl기능
`VFIO_IOMMU_SPAPR_TCE_GET_INFO`DMA window 시작과 크기 조회
`VFIO_IOMMU_ENABLE`Container enable과 locked-page accounting
`VFIO_IOMMU_DISABLE`Container disable
`VFIO_EEH_PE_OP`EEH 설정, 오류 감지와 복구

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을 재개할 수 있습니다.

sPAPR EEH recovery sequence
Query DMA window and enable containerMap DMA inside windowEnable EEH and save device stateDetect or inject PE errorStop traffic and unfreeze I/O for logsReset and deactivate resetConfigure bridges and restore stateResume 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-651

SPAPR 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 최적화에 도움이 됩니다.

sPAPR TCE v2 역할 분리
Operation책임
REGISTER_MEMORYRange accounting과 page pin 준비
UNREGISTER_MEMORY동일 address·size range 해제
MAP_DMAPre-registered range를 IOMMU table에 map
UNMAP_DMAIOMMU table entry 제거, page pin은 유지

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-670

sPAPR 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를 제거합니다.

추가 DMA window lifecycle
Check platform TCE-window supportChoose page shift, size, table levelsCall `VFIO_IOMMU_SPAPR_TCE_CREATE`Kernel selects available hardware slotUse returned bus start addressCall `VFIO_IOMMU_SPAPR_TCE_REMOVE`

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)의 전제 조건입니다.

PCI bridge 아래의 IOMMU 가시성
PCI domain `0000:00`PCI bridge `00:1e.0`Secondary bus `06`Function `06:0d.0`Function `06:0d.1`One IOMMU isolation group

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).