# PCI通透
[TOC]
## 簡介
為了架設基於[Proxmox VE](https://pve.proxmox.com/wiki/Main_Page)的伺服器,並讓[Proxmox VE](https://pve.proxmox.com/wiki/Main_Page)所建立的VM能夠存取伺服器的顯示卡資源,我們必須要啟用 **PCI通透(PCI Passthrough)** 的功能來將PCI的通訊傳遞至VM之中。
## 前置準備
在開始設定`IOMMU`之前,我們必須先完成幾項前置作業:
1. 於BIOS啟動CPU虛擬化功能。[^1]
2. 於BIOS開啟記憶體虛擬化以及相關設置。[^2]
3. 確認PVE是使用`GRUB`還是`systemd-boot`作為開機引導。[^3]
## 流程圖
首先,我們透過下方的流程圖來了解設定PCI通透並將GPU分配給VM該如何進行。整個流程主要分成四大部分
1. 開機引導設置
2. IOMMU重映射設定
3. 模組設定
4. 虛擬機配置設定

## 詳細流程解說
### 開機引導設定
根據PVE所使用的開機引導不同,需要進行的設置也有所不同。開機引導主要有兩種:`GRUB`以及`systemd-boot`。
---
#### GRUB
針對`GRUB`的設定,需要修改`/etc/default/grub`中的`GRUB_CMDLINE_LINUX_DEFAULT`參數。根據CPU供應商的不同,設置上也有些微的差異。
##### AMD CPU
針對AMD CPU,需要在`GRUB_CMDLINE_LINUX_DEFAULT`加入`amd_iommu=on`的參數。
```=
#修改前
GRUB_CMDLINE_LINUX_DEFAULT='quiet'
#修改後
GRUB_CMDLINE_LINUX_DEFAULT='quiet amd_iommu=on'
```
##### Intel CPU
針對Intel CPU,需要在`GRUB_CMDLINE_LINUX_DEFAULT`加入`intel_iommu=on`的參數。
```=
#修改前
GRUB_CMDLINE_LINUX_DEFAULT='quiet'
#修改後
GRUB_CMDLINE_LINUX_DEFAULT='quiet intel_iommu=on'
```
---
#### systemd-boot
針對`systemd-boot`的設定,需要在`/etc/kernel/cmdline`的第一行增加額外的參數。根據CPU供應商的不同,設置上也有些微的差異。
##### AMD CPU
針對AMD CPU,需要在第一行後方加入`amd_iommu=on`的參數。
```=
#修改前
root=ZFS=rpool/ROOT/pve-1 boot=zfs
#修改後
root=ZFS=rpool/ROOT/pve-1 boot=zfs amd_iommu=on
```
##### Intel CPU
針對Intel CPU,需要在第一行後方加入`intel_iommu=on`的參數。
```=
#修改前
root=ZFS=rpool/ROOT/pve-1 boot=zfs
#修改後
root=ZFS=rpool/ROOT/pve-1 boot=zfs intel_iommu=on
```
---
#### 更新設定
在完成上方修改之後,需要執行以下指令來更新設定:
```bash=
proxmox-boot-tool refresh
```
這些設定將在重新啟動後被套用。
### IOMMU重映射設定
對於使用PCI通透來說,設置中斷重映射(Interrupt Remapping)是必要的,若沒有設定這個機制,將無法正確的使用PCI通透的相關功能,並且可能出現以下錯誤訊息:
```
Failed to assign device "[device name]": Operation not
permitted' or 'Interrupt Remapping hardware not found,
passing devices to unprivileged domains is insecure.
```
大部分的狀況下,無論是AMD或是Intel的CPU都有支援中斷重映射的機制。我們可以透過`dmesg | grep 'remapping'`來檢查。若有支援,將會跳出以下訊息:
* AMD CPU: "AMD-Vi: Interrupt remapping enabled"
* Intel CPU: "DMAR-IR: Enabled IRQ remapping in x2apic mode" ('x2apic' can be different on old CPUs, but should still work)
若檢查後並沒有跳出以上訊息,則需要手動設置。設置方法為將`options vfio_iommu_type1 allow_unsafe_interrupts=1`參數加入到`/etc/modprobe.d/iommu_unsafe_interrupts.conf`之中。
```bash=
echo "options vfio_iommu_type1 allow_unsafe_interrupts=1" > /etc/modprobe.d/iommu_unsafe_interrupts.conf
```
此改動的目的是在CPU平台未提供中斷重映射的機制時,允許使用不安全的中斷方式來進行重映射的中斷。
### 模組設定
#### 啟用VFIO模組
為了讓硬體設備可以被虛擬機直接取用,還需要額外更改`/etc/modules`的設定來啟用`VFIO`模組。只要在`/etc/modules`中加入以下四行文字即可。
```=
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
```
#### 將驅動程式加入黑名單避免PVE系統取用顯示卡資源
為了避免PVE系統使用顯示卡資源並排擠到虛擬機的資源,我們需要編輯`/etc/modprobe.d/blacklist.conf`來使主系統的部分驅動程式無法取用顯示卡資源。
```bash=
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
echo "blacklist nvidia" >> /etc/modprobe.d/blacklist.conf
```
:::warning
上方列為黑名單的是Nvidia以及與Nvidia顯示卡有關的驅動程式。對於Intel以及AMD顯示卡的部分,可以參考此篇[文章](https://wiki.freedomstu.com/books/proxmox-ve-%E8%99%9B%E6%93%AC%E7%B3%BB%E7%B5%B1%E8%A8%98%E9%8C%84/page/%E5%9C%A8-vm-%E8%A3%A1%E9%9D%A2%E4%BD%BF%E7%94%A8%E9%A1%AF%E7%A4%BA%E5%8D%A1)中的設定。
:::
#### 將顯示卡資訊寫入於VFIO設定中
為了讓VFIO模組了解哪一個PCI設備是顯示卡,我們需要以下步驟:
1. 找出顯示卡對應ID
為了找出顯示卡對應的ID,我們可以使用以下指令:
```bash=
lspci | grep VGA
```
以我自己的工作站而言,會有以下結果:
```
01:00.0 VGA compatible controller: NVIDIA Corporation GA102 [GeForce RTX 3090] (rev a1)
```
這時,我可以得知我的顯示卡ID為`01:00.0`。
2. 獲取顯示卡規格
在取得了顯示卡ID之後,我們便可以利用以下指令取得顯示卡的細部規格:
```bash=
lspci -n -s 01:00
```
以我的工作站來說,結果如以下所示:
```=
root@pve:~# lspci -n -s 01:00
01:00.0 0300: 10de:2204 (rev a1)
01:00.1 0403: 10de:1aef (rev a1)
```
上方可以看現兩個結果。其中`10de:2204`就是我們在上一步驟中所找到的顯示卡;而`10de:1aef`則是顯示卡所附帶的音效卡。在PCI通透中我們只需要設定顯示卡的部分即可。
3. 將顯示卡ID以及相關設定寫入`/etc/modprobe.d/vfio.conf`檔案中
在上一步中,我們取得了顯示卡ID對應的規格以及編號,接下來我們只需要將上一步取得的`10de:2204`搭配必要的參數寫入`vfio.conf`檔案中即可。以下是用以將設定寫入的指令:
```bash=
echo "options vfio-pci ids=10de:2204" > /etc/modprobe.d/vfio.conf
```
如果想要關閉VGA功能,可以在後方加入`disable_vga=1`參數:
```bash=
echo "options vfio-pci ids=10de:2204 disable_vga=1" > /etc/modprobe.d/vfio.conf
```
在寫入完成後,執行下方指令並重新開機即可套用前述的所有設定。
```bash=
update-initramfs -u
```
:::warning
在此我們只有示範了最基礎的設定,對於更完整以及更多的設定選擇,可以參考[PVE官網](https://pve.proxmox.com/wiki/Pci_passthrough#Required_Modules)所提供的介紹。
:::
## 虛擬機配置設定
在前幾個部分中,我們已經完成了PCI通透大部分所需要的系統設置。接下來,我們將進行對虛擬機的設定,讓虛擬機可以順利的取用顯示卡資源。
1. 創建並設定虛擬機的配置
首先,我們先利用網頁中的`Create VM`按鈕來建立一個新的虛擬機。

接下來,將BIOS設定為`OVMF(UEFI)`模式,並將`Machine`選項設定為q35。

在硬碟設定上,可以使用`IDE`或`VirtIO Block`(但需要額外設定VirtIO driver)。

在CPU的設定上,核心數量可以自訂,但是必須將`Type`設置為`host`以避免出錯。[^4]

完成上述的必要設定之後,只要儲存設定即可完成初步設定。
:::warning
完成此步驟後,請先啟動虛擬機,並完成基礎的系統安裝。後續將顯示卡指定至虛擬機時,可能因為`disable_vga=1`的設定導致PVE預設使用的`noVNC`無法連線至虛擬機,進而導致無法進行系統安裝。
:::
2. 指定PCI裝置至虛擬機
在虛擬機建立後,我們可以到`Harware`中點選`Add`來新增`PCI device`。

然後在選單中選取對應的顯示卡。

將`All Functions`、`Primary GPU`以及`PCI Express`選項打勾。

接下來只要點選`Add`並啟動虛擬機即可。
## 參考資料及相關資料
1. PVE官網對於PCI通透的[設定手冊](https://pve.proxmox.com/wiki/Pci_passthrough#Required_Modules)。
2. PVE官網對於Host Bootloader的[相關手冊](https://pve.proxmox.com/wiki/Host_Bootloader)。
3. IOMMU技術[介紹](https://zhuanlan.zhihu.com/p/348826888)。
4. Intel虛擬化技術[介紹](https://www.intel.com.tw/content/www/tw/zh/virtualization/virtualization-technology/intel-virtualization-technology.html)。
5. [在 VM 裡面使用顯示卡
](https://wiki.freedomstu.com/books/proxmox-ve-%E8%99%9B%E6%93%AC%E7%B3%BB%E7%B5%B1%E8%A8%98%E9%8C%84/page/%E5%9C%A8-vm-%E8%A3%A1%E9%9D%A2%E4%BD%BF%E7%94%A8%E9%A1%AF%E7%A4%BA%E5%8D%A1)
6. VFIO[介紹](https://www.kernel.org/doc/html/latest/driver-api/vfio.html)
7. OSSLab的PVE設定[教學文章](https://www.osslab.com.tw/proxmox-ve-pci-e-pass/)
[^1]: 於兩大x86平台中分別被稱為Intel VT-X以及AMD SVM。啟用方式可以參考此份[介紹](https://bce.berkeley.edu/enabling-virtualization-in-your-pc-bios.html)。
[^2]: 記憶體虛擬化係指Intel VT-d以及AMD IOMMU技術,其設定依主機板供應商不同而有所差異。AMD在ASRock主機板平台上的設定可以參考此份[介紹](https://www.reddit.com/r/VFIO/comments/cm6xme/comment/ew0r5x7/?utm_source=share&utm_medium=web2x&context=3)。
[^3]: 關於如何辨別,可以使用`proxmox-boot-tool status`指令進行查看。詳細可以參考此篇[介紹](https://pve.proxmox.com/wiki/Host_Bootloader)。
[^4]: 以Tensorflow為例,預設的KVM選項會讓Tensorflow編譯出現錯誤`The TensorFlow library was compiled to use SSE4.2 instructions, but these aren't available on your machine.
Aborted (core dumped)`。