VFIO - “虛擬函式 I/O”[1]¶
許多現代系統現在提供 DMA 和中斷重對映機制,以幫助確保 I/O 裝置在其分配的邊界內執行。 這包括具有 AMD-Vi 和 Intel VT-d 的 x86 硬體、具有可分割槽端點 (PE) 的 POWER 系統以及嵌入式 PowerPC 系統,例如 Freescale PAMU。 VFIO 驅動程式是一個 IOMMU/裝置無關框架,用於在安全、IOMMU 保護的環境中向用戶空間公開直接裝置訪問。 換句話說,這允許安全的[2]、非特權的、使用者空間驅動程式。
我們為什麼需要它? 虛擬機器在配置為儘可能高的 I/O 效能時,通常會使用直接裝置訪問(“裝置分配”)。 從裝置和主機的角度來看,這只是將 VM 變成使用者空間驅動程式,具有顯著減少延遲、更高頻寬和直接使用裸機裝置驅動程式的優勢[3]。
某些應用程式,尤其是在高效能計算領域,也受益於來自使用者空間的低開銷、直接裝置訪問。 示例包括網路介面卡(通常不是基於 TCP/IP 的)和計算加速器。 在 VFIO 之前,這些驅動程式要麼必須經過完整的開發週期才能成為合適的上游驅動程式,要麼在樹外維護,要麼使用 UIO 框架,該框架沒有 IOMMU 保護的概念,中斷支援有限,並且需要 root 許可權才能訪問 PCI 配置空間之類的內容。
VFIO 驅動程式框架旨在統一這些,取代 KVM PCI 特定裝置分配程式碼,並提供比 UIO 更安全、功能更豐富的使用者空間驅動程式環境。
組、裝置和 IOMMU¶
裝置是任何 I/O 驅動程式的主要目標。 裝置通常建立一個由 I/O 訪問、中斷和 DMA 組成的程式設計介面。 在不詳細說明每一個細節的情況下,DMA 是迄今為止維護安全環境最關鍵的方面,因為它允許裝置對系統記憶體進行讀寫訪問,從而對整體系統完整性構成最大的風險。
為了幫助減輕這種風險,許多現代 IOMMU 現在將隔離屬性納入到許多情況下僅用於轉換的介面中(即,解決地址空間有限的裝置定址問題)。 這樣,裝置現在可以彼此隔離,並與任意記憶體訪問隔離,從而允許安全地將裝置直接分配到虛擬機器中。
但是,這種隔離並不總是以單個裝置為粒度的。 即使 IOMMU 能夠做到這一點,裝置、互連和 IOMMU 拓撲的屬性也可能會降低這種隔離。 例如,單個裝置可能是更大的多功能外殼的一部分。 雖然 IOMMU 可能能夠區分外殼內的裝置,但外殼可能不需要裝置之間的事務到達 IOMMU。 這方面的例子可以是具有功能之間後門的多功能 PCI 裝置,也可以是不需要到達 IOMMU 即可允許重定向的非 PCI-ACS(訪問控制服務)功能的橋接器。 拓撲結構也可能在隱藏裝置方面發揮作用。 PCIe 到 PCI 橋接器會遮蔽其後面的裝置,使事務看起來好像來自橋接器本身。 顯然,IOMMU 設計也起著重要作用。
因此,雖然在大多數情況下,IOMMU 可能具有裝置級別的粒度,但任何系統都容易降低粒度。 因此,IOMMU API 支援 IOMMU 組的概念。 組是一組可以與系統中所有其他裝置隔離的裝置。 因此,組是 VFIO 使用的所有權單位。
雖然組是必須使用的最小粒度,以確保安全的使用者訪問,但它不一定是首選粒度。 在使用頁表的 IOMMU 中,可以在不同組之間共享一組頁表,從而降低平臺開銷(減少 TLB 抖動,減少重複的頁表)和使用者開銷(僅程式設計一組轉換)。 因此,VFIO 使用容器類,該類可以容納一個或多個組。 只需開啟 /dev/vfio/vfio 字元裝置即可建立容器。
容器本身提供的功能很少,除了幾個版本和擴充套件查詢介面外,所有功能都被鎖定。 使用者需要將一個組新增到容器中才能獲得下一級別的功能。 為此,使用者首先需要識別與所需裝置關聯的組。 這可以使用下面示例中描述的 sysfs 連結來完成。 透過將裝置從主機驅動程式取消繫結並將其繫結到 VFIO 驅動程式,將為該組顯示一個新的 VFIO 組,如 /dev/vfio/$GROUP,其中 $GROUP 是該裝置所屬的 IOMMU 組號。 如果 IOMMU 組包含多個裝置,則每個裝置都需要繫結到 VFIO 驅動程式,然後才能對 VFIO 組執行操作(如果 VFIO 驅動程式不可用,則僅取消繫結裝置與主機驅動程式也足夠;這將使該組可用,但不包括特定裝置)。 TBD - 用於停用驅動程式探測/鎖定裝置的介面。
一旦組準備就緒,可以透過開啟 VFIO 組字元裝置 (/dev/vfio/$GROUP) 並使用 VFIO_GROUP_SET_CONTAINER ioctl 將其新增到容器中,並傳遞先前開啟的容器檔案的檔案描述符。 如果需要,並且如果 IOMMU 驅動程式支援在組之間共享 IOMMU 上下文,則可以將多個組設定為同一容器。 如果一個組無法設定為具有現有組的容器,則需要改用一個新的空容器。
透過將一個(或多個)組附加到容器,其餘的 ioctl 變為可用,從而可以訪問 VFIO IOMMU 介面。 此外,現在可以使用 VFIO 組檔案描述符上的 ioctl 獲取組中每個裝置的檔案描述符。
VFIO 裝置 API 包括用於描述裝置的 ioctl,I/O 區域及其在裝置描述符上的讀/寫/mmap 偏移量,以及用於描述和註冊中斷通知的機制。
VFIO 使用示例¶
假設使用者想要訪問 PCI 裝置 0000:06:0d.0
$ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group
../../../../kernel/iommu_groups/26
因此,該裝置位於 IOMMU 組 26 中。該裝置位於 pci 總線上,因此使用者將使用 vfio-pci 來管理該組
# modprobe vfio-pci
將此裝置繫結到 vfio-pci 驅動程式會為此組建立 VFIO 組字元裝置
$ 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
現在我們需要檢視組中還有哪些其他裝置,以便將其釋放以供 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
該裝置位於 PCIe 到 PCI 橋接器後面[4],因此我們還需要按照與上述相同的步驟將裝置 0000:06:0d.1 新增到組中。 裝置 0000:00:1e.0 是一個當前沒有主機驅動程式的橋接器,因此不需要將此裝置繫結到 vfio-pci 驅動程式(vfio-pci 目前不支援 PCI 橋接器)。
最後一步是在需要非特權操作時為使用者提供對組的訪問許可權(請注意,/dev/vfio/vfio 本身不提供任何功能,因此預計系統會將其設定為模式 0666)
# chown user:user /dev/vfio/26
使用者現在可以完全訪問該組的所有裝置和 iommu,並且可以按如下方式訪問它們
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¶
IOMMUFD 是新的使用者 API,用於從使用者空間管理 I/O 頁表。 它旨在成為提供高階使用者空間 DMA 功能(巢狀轉換[5]、PASID[6] 等)的門戶,同時還為現有 VFIO_TYPE1v2_IOMMU 用例提供向後相容介面。 最終,vfio_iommu_type1 驅動程式以及傳統的 vfio 容器和組模型都將被棄用。
可以透過兩種方式啟用 IOMMUFD 向後相容介面。 在第一種方法中,可以使用 CONFIG_IOMMUFD_VFIO_CONTAINER 配置核心,在這種情況下,IOMMUFD 子系統透明地為 VFIO 容器和 IOMMU 後端介面提供整個基礎設施。 如果 VFIO 容器介面(即 /dev/vfio/vfio)只是符號連結到 /dev/iommu,也可以訪問相容模式。 請注意,在編寫本文時,相對於 VFIO_TYPE1v2_IOMMU,相容模式並非完全功能完整(例如,DMA 對映 MMIO),並且不嘗試提供與 VFIO_SPAPR_TCE_IOMMU 介面的相容性。 因此,目前通常不建議從本機 VFIO 實現切換到 IOMMUFD 相容介面。
從長遠來看,VFIO 使用者應遷移到透過下面描述的 cdev 介面進行裝置訪問,以及透過 IOMMUFD 提供的介面進行本機訪問。
VFIO 裝置 cdev¶
傳統上,使用者透過 VFIO 組中的 VFIO_GROUP_GET_DEVICE_FD 獲取裝置 fd。
使用 CONFIG_VFIO_DEVICE_CDEV=y,使用者現在可以透過直接開啟字元裝置 /dev/vfio/devices/vfioX 獲取裝置 fd,其中“X”是 VFIO 為註冊裝置唯一分配的數字。 cdev 介面不支援 noiommu 裝置,因此如果需要 noiommu,使用者應使用傳統的組介面。
cdev 僅適用於 IOMMUFD。 VFIO 驅動程式和應用程式都必須適應新的 cdev 安全模型,該模型要求在使用 VFIO_DEVICE_BIND_IOMMUFD 宣告 DMA 所有權,然後才能真正開始使用裝置。 一旦 BIND 成功,使用者就可以完全訪問 VFIO 裝置。
VFIO 裝置 cdev 不依賴於 VFIO 組/容器/iommu 驅動程式。 因此,可以在不存在傳統 VFIO 應用程式的環境中完全編譯出這些模組。
到目前為止,SPAPR 還不支援 IOMMUFD。 因此,它也不能支援裝置 cdev。
vfio 裝置 cdev 訪問仍然受 IOMMU 組語義的約束,即,該組只能有一個 DMA 所有者。 屬於同一組的裝置不能繫結到多個 iommufd_ctx 或在原生核心和 vfio 匯流排驅動程式或其他支援 driver_managed_dma 標誌的驅動程式之間共享。 違反此所有權要求將在 VFIO_DEVICE_BIND_IOMMUFD ioctl 處失敗,該 ioctl 控制對裝置的完全訪問。
裝置 cdev 示例¶
假設使用者想要訪問 PCI 裝置 0000:6a:01.0
$ ls /sys/bus/pci/devices/0000:6a:01.0/vfio-dev/
vfio0
因此,此裝置表示為 vfio0。 使用者可以驗證它的存在
$ 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
然後,如果需要非特權操作,則為使用者提供對裝置的訪問許可權
$ chown user:user /dev/vfio/devices/vfio0
最後,使用者可以透過以下方式獲取 cdev fd
cdev_fd = open("/dev/vfio/devices/vfio0", O_RDWR);
開啟的 cdev_fd 不授予使用者任何訪問裝置的許可權,除非將 cdev_fd 繫結到 iommufd。 在那之後,裝置完全可訪問,包括將其附加到 IOMMUFD IOAS/HWPT 以啟用使用者空間 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 使用者 API¶
有關完整的 API 文件,請參見 include/uapi/linux/vfio.h。
VFIO 匯流排驅動程式 API¶
VFIO 匯流排驅動程式(例如 vfio-pci)僅使用幾個到 VFIO 核心的介面。 當裝置繫結和解除繫結到驅動程式時,在裝置繫結和解除繫結到驅動程式時,將呼叫以下介面
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);
驅動程式應在其自己的結構中嵌入 vfio_device 並使用 vfio_alloc_device() 來分配該結構,並且可以註冊 @init/@release 回撥以管理包裝 vfio_device 的任何私有狀態
vfio_alloc_device(dev_struct, member, dev, ops);
void vfio_put_device(struct vfio_device *device);
vfio_register_group_dev() 指示核心開始跟蹤指定 dev 的 iommu_group 並將 dev 註冊為 VFIO 匯流排驅動程式所有。 一旦 vfio_register_group_dev() 返回,使用者空間就可以開始訪問驅動程式,因此驅動程式應確保在呼叫它之前已完全準備好。 該驅動程式提供了一個 ops 結構,用於類似於檔案操作結構的回撥
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);
};
每個函式都傳遞了最初在上面的 vfio_register_group_dev() 或 vfio_register_emulated_iommu_dev() 呼叫中註冊的 vdev。 這允許匯流排驅動程式使用 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 實現注意事項¶
此實現有一些特殊性
在較舊的系統上(帶有 P5IOC2/IODA1 的 POWER7),每個容器僅支援一個 IOMMU 組,因為 IOMMU 表是在啟動時分配的,每個 IOMMU 組一張表,該 IOMMU 組是可分割槽端點 (PE)(PE 通常是 PCI 域,但並非總是如此)。
較新的系統(帶有 IODA2 的 POWER8)改進了硬體設計,可以消除此限制,並且每個 VFIO 容器有多個 IOMMU 組。
硬體支援所謂的 DMA 視窗 - 允許 DMA 傳輸的 PCI 地址範圍,任何嘗試訪問視窗外地址空間的行為都會導致整個 PE 隔離。
PPC64 客戶機是半虛擬化的,但不是完全模擬的。 有一個 API 用於對映/取消對映 DMA 的頁面,它通常每次呼叫對映 1..32 頁,並且目前無法減少呼叫次數。 為了加快速度,對映/取消對映處理已在真實模式下實現,這提供了出色的效能,但存在一些限制,例如無法即時執行鎖定的頁面記帳。
根據 sPAPR 規範,可分割槽端點 (PE) 是一個 I/O 子樹,可以將其視為分割槽和錯誤恢復目的的單元。 PE 可能是單個或多功能 IOA(IO 介面卡)、多功能 IOA 的功能或多個 IOA(可能包括多個 IOA 上方的交換機和橋接器結構)。 PPC64 客戶機檢測 PCI 錯誤並透過 EEH RTAS 服務從中恢復,該服務基於附加的 ioctl 命令工作。
因此,添加了 4 個附加的 ioctl
- VFIO_IOMMU_SPAPR_TCE_GET_INFO
返回 PCI 總線上 DMA 視窗的大小和起始位置。
- VFIO_IOMMU_ENABLE
啟用容器。 鎖定的頁面記帳在此處完成。 這讓使用者首先知道 DMA 視窗是什麼,並在執行任何實際工作之前調整 rlimit。
- VFIO_IOMMU_DISABLE
停用容器。
- VFIO_EEH_PE_OP
提供用於 EEH 設定、錯誤檢測和恢復的 API。
上面示例中的程式碼流程應略有更改
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. */ ....有 v2 版本的 SPAPR TCE IOMMU。 它棄用了 VFIO_IOMMU_ENABLE/VFIO_IOMMU_DISABLE,並實現了 2 個新的 ioctl:VFIO_IOMMU_SPAPR_REGISTER_MEMORY 和 VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY(v1 IOMMU 中不支援)。
PPC64 半虛擬化客戶機生成大量對映/取消對映請求,並且這些請求的處理包括固定/取消固定頁面以及更新 mm::locked_vm 計數器以確保我們不超過 rlimit。 v2 IOMMU 將記帳和固定分成單獨的操作
VFIO_IOMMU_SPAPR_REGISTER_MEMORY/VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY ioctl 接收要固定的塊的使用者空間地址和大小。 不支援二分,並且 VFIO_IOMMU_UNREGISTER_MEMORY 應該使用用於註冊記憶體塊的精確地址和大小來呼叫。 預計使用者空間不會經常呼叫這些。 這些範圍儲存在 VFIO 容器中的連結列表中。
VFIO_IOMMU_MAP_DMA/VFIO_IOMMU_UNMAP_DMA ioctl 僅更新實際的 IOMMU 表,不進行固定;相反,它們檢查使用者空間地址是否來自預註冊的範圍。
這種分離有助於最佳化客戶機的 DMA。
sPAPR 規範允許客戶機在 PCI 總線上具有額外的 DMA 視窗,頁面大小可變。 添加了兩個 ioctl 來支援此功能:VFIO_IOMMU_SPAPR_TCE_CREATE 和 VFIO_IOMMU_SPAPR_TCE_REMOVE。 平臺必須支援該功能,否則會向用戶空間返回錯誤。 現有硬體最多支援 2 個 DMA 視窗,一個是 2GB 長,使用 4K 頁面,稱為“預設 32 位視窗”;另一個可以和整個 RAM 一樣大,使用不同的頁面大小,它是可選的 - 如果客戶機驅動程式支援 64 位 DMA,則客戶機在執行時建立這些視窗。
VFIO_IOMMU_SPAPR_TCE_CREATE 接收頁面偏移、DMA 視窗大小和 TCE 表級別數(如果 TCE 表足夠大,並且核心可能無法分配足夠的物理連續記憶體)。 它在可用插槽中建立一個新視窗,並返回新視窗啟動的匯流排地址。 由於硬體限制,使用者空間無法選擇 DMA 視窗的位置。
VFIO_IOMMU_SPAPR_TCE_REMOVE 接收視窗的匯流排起始地址並將其刪除。