Skip to main content

Проброс PCIe в qemu

Излюбленная фишка в LDOMs на Solaris – разнообразные способы подключение всяких карточек и путей ввода-вывода в логические домены, порой весьма извращенными способами. На Linux так тоже хочу. Можно. Делаем (почти) то же самое.

Несколько лет назад подобное проворачивал для использования vGPU от Nvidia, вполне себе рабочая конструкция получалась, какая-то безумная облачная HPC рабочая станция, с поддержкой RemoteFX и ништяков, решение было заточено на dassault/solid, было некоторое количество “граблей”, которые, впрочем, удалось быстро “разрулить” = и оно прилично работает. До сих пор. Собираем домашний стенд (Z490+i5+16Gb RAM+1xPCIe16 (с ним засада, но о ней не сейчас никаких засад, ASUS Z490M PRIME совершенно честная материнка, как выяснилось)+1xPCIe4 (честный порт, всё, что угодно можно делать) – он-то нам и нужен). В него-то и вставляем 4х портовой адаптер 4 x 1GbE):

host$ lspci -d 8086:10bc
05:00.0 Ethernet controller: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) (rev 06)
05:00.1 Ethernet controller: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) (rev 06)
06:00.0 Ethernet controller: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) (rev 06)
06:00.1 Ethernet controller: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) (rev 06)

Читаем очень внимательно PCI passthrough via OVMF (вот этот скрипт весьма полезен, поселился в bin/iommu_show.bsh, использую постоянно при настройке). Сделано лишь несколько вещей. Во-первых, включаем VT-d в BIOS (systemctl reboot –firmware-setup – ага, липукс умеет общатсья с EFI). Во-сторых, создаём новую запись загрузчика, в моём случае – systemd-bootd = модно, современно, практично, лаконично – /boot/loader/entries/arch-iommu.conf:

cat << EOF | sudo tee /boot/loader/entries/arch-iommu.conf
title   Arch Linux VFio
linux   /vmlinuz-linux
initrd  /intel-ucode.img
initrd  /initramfs-linux.img
options root="LABEL=arch" rw audit=0 intel_iommu=on iommu=pt
EOF

Так как я не пробрасываю видеокарточки (почти правда, см. в самом конце) – больше мне ничего делать, в общем-то, и не надо – никаких правок mkinitcpio.conf/modprobe.conf. Проверяем, перегружаемся: systemctl reboot –boot-loader-entry=arch-iommu.conf. Вроде всё, сверяемся с документом, смотрим в dmesg.

Заполучаем реально полезный скрипт vfio-pci-bind, сильно облегчает дальнейшие шаги (sysfs, тем не менее, ручками “править” никто не запрещает, и для понимания разок стоит пройти по тернистому пути).

Если всё получилось, то наш ранее сохранённый скрипт iommu_show.bsh со страничек archwiki начнёт показывать много полезного (обрезано, для понимания):

IOMMU Group 12:
        04:02.0 PCI bridge [0604]: Microsemi / PMC / IDT PES12N3A 12-lane 3-Port PCI Express Switch [111d:8018] (rev 0e)
        05:00.0 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
        05:00.1 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
IOMMU Group 13:
        04:04.0 PCI bridge [0604]: Microsemi / PMC / IDT PES12N3A 12-lane 3-Port PCI Express Switch [111d:8018] (rev 0e)
        06:00.0 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
        06:00.1 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)

Привязывается ВЕСЬ iommu, никак иначе, если не понятно – внимательно читайте archwiki, до полного просветления 😉

Далее дело техники. До настройки было:

host$ lspci -s 05:00.0 -nk
05:00.0 0200: 8086:10bc (rev 06)
        Subsystem: 8086:11bc
        Kernel driver in use: e1000e
        Kernel modules: e1000e
$ ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 4074 qdisc fq_codel master br0 state UP mode DEFAULT group default qlen 1000
    link/ether 3c:7c:3f:be:04:a6 brd ff:ff:ff:ff:ff:ff
    altname enp0s31f6
3: enp5s0f0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:cd brd ff:ff:ff:ff:ff:ff
4: enp5s0f1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:cc brd ff:ff:ff:ff:ff:ff
5: enp6s0f0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:cf brd ff:ff:ff:ff:ff:ff
6: enp6s0f1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 4074 qdisc fq_codel master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:ce brd ff:ff:ff:ff:ff:ff
7: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 4074 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 3c:7c:3f:be:04:a6 brd ff:ff:ff:ff:ff:ff
8: vb-scada@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000
    link/ether 32:21:2e:3d:a9:21 brd ff:ff:ff:ff:ff:ff link-netnsid 0

Откусываем наши искомые устройства (sudo vfio-pci-bind.sh 05:00.0), проверяем, что вышло:

host$ lspci -s 05:00.0 -nk
05:00.0 0200: 8086:10bc (rev 06)
        Subsystem: 8086:11bc
        Kernel driver in use: vfio-pci
        Kernel modules: e1000e
host$ ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 4074 qdisc fq_codel master br0 state UP mode DEFAULT group default qlen 1000
    link/ether 3c:7c:3f:be:04:a6 brd ff:ff:ff:ff:ff:ff
    altname enp0s31f6
5: enp6s0f0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:cf brd ff:ff:ff:ff:ff:ff
6: enp6s0f1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 4074 qdisc fq_codel master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:15:17:d9:69:ce brd ff:ff:ff:ff:ff:ff
7: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 4074 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 3c:7c:3f:be:04:a6 brd ff:ff:ff:ff:ff:ff
8: vb-scada@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000
    link/ether 32:21:2e:3d:a9:21 brd ff:ff:ff:ff:ff:ff link-netnsid 0

Вышла магия, устройство благополучно испарилось из вывода ip l, чтд.

Ну, дальше дело техники, qemu на минималках (screen – это я в мультиплексоре сижу, так удобнее, у нормальных людей можно без него, или вообще tmux использовать):

host$ screen qemu-system-x86_64 \
-m 2G \
-enable-kvm -cpu host,kvm=off \
-smp 2,sockets=1,cores=2,threads=1 \
-device vfio-pci,host=05:00.0 \
-device vfio-pci,host=05:00.1 \
-nic none \
-nographic \
/storarray/local/vms/qemu/deb-vfio/deb-vfio.img

Собственно, и всё, больше делать особо нечего.

Если появляется ошибка при запуске qemu про что-то-там-выделение-памяти и в dmesg сообщения:

vfio_pin_pages_remote: RLIMIT_MEMLOCK (9663676416) exceeded
vfio_pin_pages_remote: RLIMIT_MEMLOCK (9663676416) exceeded

то правим (создаём) файл /etc/security/limits.d/20-vfio.conf, заменив username на что-то вменяемое по месту:

deb-vfio$ cat << EOF | sudo tee  /etc/security/limits.d/20-vfio.conf
username           hard    memlock    20000000
username           soft    memlock    20000000
EOF

В консоли Debian сразу вносим ряд изменений:

deb-vfio$ echo 'GRUB_CMDLINE_LINUX_DEFAULT="text nomodeset console=tty0 console=ttyS0 apparmor=0 audit=0"' \
| sudo tee /etc/default/grub.d/vmsettings.cfg
deb-vfio$ sudo update-grub

Ну, далее по вкусу, можно, например, “прикрутить” OVMF+virtlib, причесав установку (ынтырпрайзность будет переть изо всех щелей, типа как у людей в редхатах ж) ). Мне это не очень нужно, хватает systemd для запуска искомых скриптов.

Для разнообразия, real life example, GRID K2 в “домене” для расчёта Einstein@home, если не “дурить” c KVM – в NVidia работают не дураки, и не дают vGPU работать нормально (имейте, кстати, ввиду, что Kepler GK104 – это не совсем то, что ожидают “старые” 390xx драйвера – нужны именно 367xx ветка, специально под GRID):

host$ iommu_show.bsh | egrep "GK104GL|82571EB/82571GB"
        03:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK104GL [GRID K2] [10de:11bf] (rev a1)
        04:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK104GL [GRID K2] [10de:11bf] (rev a1)
        09:00.0 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
        09:00.1 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
        0a:00.0 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
        0a:00.1 Ethernet controller [0200]: Intel Corporation 82571EB/82571GB Gigabit Ethernet Controller (Copper) [8086:10bc] (rev 06)
host$ iommu_show.bsh | egrep "GK104GL|82571EB/82571GB" \
| awk '{print $1}' \
| while read domainbus ; do \
echo sudo vfio-pci-bind.sh $domainbus ; done  \
| sh -x
host$ screen qemu-system-x86_64 \
-m 512M \
-enable-kvm \
-cpu host,kvm=off \
-smp 2,sockets=1,cores=2,threads=1 \
-device vfio-pci,host=09:00.0 \
-device vfio-pci,host=09:00.1 \
-device vfio-pci,host=03:00.0 \
-device vfio-pci,host=04:00.0 \
-nic none \
-nographic \
-boot order=c \
-drive discard=unmap,detect-zeroes=unmap,cache=none,file=/storarray/local/vms/qemu/deb-vfio/deb-vfio.raw,format=raw,if=none,id=hd  \
-device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd
deb-vfio$ nvidia-smi 
Sun Jan 31 21:46:55 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 367.134                Driver Version: 367.134                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GRID K2             On   | 0000:00:05.0     Off |                  Off |
| N/A   68C    P0    66W /  85W |    756MiB /  4036MiB |     97%      Default |
+-------------------------------+----------------------+----------------------+
|   1  GRID K2             On   | 0000:00:06.0     Off |                  Off |
| N/A   61C    P0    63W /  85W |    756MiB /  4036MiB |     89%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    0      1793    C   ..._x86_64-pc-linux-gnu__FGRPopencl1K-nvidia   756MiB |
|    1      1803    C   ..._x86_64-pc-linux-gnu__FGRPopencl1K-nvidia   756MiB |
+-----------------------------------------------------------------------------+

Карточку от греха подальше посадил на диету (снизил power limit), консоль приспособил под nvidia-smi (не особо интересно, автологин для root’a “ииии так сойдёт” (с) в виртуалке:

nvidia-smi -pm 1
nvidia-smi -pl 85
nvidia-smi -ac 324,324
nvidia-smi -rac
watch nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader
 

Вкалывает, считает, куда себя приткнуть после seti@home – понятия не имею 😉 Монетки помайнить не получится, если (очевидно, но – нет).

Финальный скрипт, в рабочем виде, выглядит пока так (нужно докидать переменных, потянуть таймауты, придумать, как привязать вентилятор машины (FAN1 Chassis) к температуре внутри qemu, кратко говоря – обычный колхоз и сахарок):

#!/bin/bash

sudo chown ilyxa /storarray/local/vms/qemu/deb-vfio/deb-vfio.raw

sudo modprobe -i vfio-pci
echo 0000:03:00.0 | sudo tee "/sys/bus/pci/devices/0000:03:00.0/driver/unbind"
echo vfio-pci | sudo tee "/sys/bus/pci/devices/0000:03:00.0/driver_override"
echo 0000:03:00.0 | sudo tee "/sys/bus/pci/drivers_probe"
sudo chown ilyxa /dev/vfio/1
echo 0000:04:00.0 | sudo tee "/sys/bus/pci/devices/0000:04:00.0/driver/unbind"
echo vfio-pci | sudo tee "/sys/bus/pci/devices/0000:04:00.0/driver_override"
echo 0000:04:00.0 | sudo tee "/sys/bus/pci/drivers_probe"
sudo chown ilyxa /dev/vfio/1

qemu-system-x86_64 \
 -nodefaults -machine type=q35,accel=kvm,kernel_irqchip -rtc base=localtime -k en-us \
 -nographic \
 -serial mon:telnet:localhost:5003,server,nowait \
 -drive file=/storarray/local/vms/qemu/blank.iso,index=1,media=cdrom,cache=none,readonly \
 -vga std \
 -m 2048 \
 -cpu host,kvm=off,hv_time,hv_relaxed,hv_spinlocks=0x1fff,hv_vpindex,hv_reset,hv_runtime,hv_crash,hv_vendor_id=freyja \
 -smp 4,sockets=2,cores=2,threads=1 \
 -netdev user,id=deb-vfio0,net=192.168.76.0/24,dhcpstart=192.168.76.9,hostfwd=tcp::22023-:22 \
 -device virtio-net-pci,netdev=deb-vfio0,speed=100,duplex=full,mac=DE:AD:BE:EF:29:49 \
 -device vfio-pci,host=0000:03:00.0 \
 -device vfio-pci,host=0000:04:00.0 \
 -device virtio-scsi-pci,id=scsi0 \
 -drive file=/storarray/local/vms/qemu/deb-vfio/deb-vfio.raw,if=none,id=bootdisk,index=0,format=raw,discard=unmap,aio=native,cache=none,throttling.bps-total=50331648 \
 -device scsi-hd,drive=bootdisk,bus=scsi0.0

Подключение диска по сложному пути через странное устройство = мне нужен discard поверх ZFS для нормальной работы:

host$ zfs get compressratio storarray/local/vms/qemu/deb-vfio
NAME                               PROPERTY       VALUE  SOURCE
storarray/local/vms/qemu/deb-vfio  compressratio  1.73x  -


root@deb-vfio:~# fstrim -av
/: 24.7 GiB (26477387776 bytes) trimmed on /dev/sda1


host$ zfs get compressratio storarray/local/vms/qemu/deb-vfio
NAME                               PROPERTY       VALUE  SOURCE
storarray/local/vms/qemu/deb-vfio  compressratio  2.37x  -

Feel the difference – ну или правильнее сказать fill the difference, в моём случае 😉 После обработки маломальского объема на диске 20-25 Гб лишними не будут. Да, это – жадность. Знаю. Но я до сих пор не понимаю, зачем нужны диски овермноготерабайт, если что.

Скрипты для запуска виртуалок генерируются автоматом, поверх userprops (Open)ZFS – лежит здесь https://github.com/ilyxa/zfs4qemu/blob/main/generate_runvm.bsh. Зачем – да не спрашивайте, чистый Just For Fun, bash рулит, короче 😉