Manager can start CVM with NVIDIA GPU support (#595)
CI / lint (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled

This commit is contained in:
Jovan Djukic
2026-05-25 12:32:00 +02:00
committed by GitHub
parent 02aa7d7d85
commit 1f0eccfae7
3 changed files with 142 additions and 0 deletions
+36
View File
@@ -81,6 +81,14 @@ type DiskConfig struct {
SCSIID string `env:"DISK_SCSI_ID" envDefault:"scsi0"`
}
type GPUConfig struct {
EnableGPU bool
GPUBDF string `env:"GPU_BDF" envDefault:""`
PCIeRootPort string `env:"GPU_PCIE_ROOT_PORT" envDefault:"pci.1"`
PCIeBus string `env:"GPU_PCIE_BUS" envDefault:"pcie.0"`
FWCfgPciMmio string `env:"GPU_FW_CFG_MMIO_MB" envDefault:"262144"`
}
type Config struct {
EnableSEVSNP bool
EnableTDX bool
@@ -121,6 +129,9 @@ type Config struct {
// vTPM
IGVMConfig
// GPU passthrough
GPUConfig
// display
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
Monitor string `env:"MONITOR" envDefault:"pty"`
@@ -227,6 +238,23 @@ func (config Config) ConstructQemuArgs() []string {
config.DiskConfig.SCSIID))
}
// GPU passthrough via VFIO
if config.GPUConfig.EnableGPU {
args = append(args, "-device",
fmt.Sprintf("pcie-root-port,id=%s,bus=%s",
config.GPUConfig.PCIeRootPort,
config.GPUConfig.PCIeBus))
args = append(args, "-device",
fmt.Sprintf("vfio-pci,host=%s,bus=%s",
config.GPUConfig.GPUBDF,
config.GPUConfig.PCIeRootPort))
args = append(args, "-fw_cfg",
fmt.Sprintf("name=opt/ovmf/X-PciMmio64Mb,string=%s",
config.GPUConfig.FWCfgPciMmio))
}
// SEV-SNP
if config.EnableSEVSNP {
sevSnpType := "sev-snp-guest"
@@ -317,5 +345,13 @@ func NewConfig() (*Config, error) {
cfg.EnableSEVSNP = SEVSNPEnabledOnHost()
cfg.EnableTDX = TDXEnabledOnHost()
bdf, detected := GPUPassthroughAvailable()
if cfg.GPUConfig.GPUBDF != "" {
cfg.GPUConfig.EnableGPU = true
} else if detected {
cfg.GPUConfig.EnableGPU = true
cfg.GPUConfig.GPUBDF = bdf
}
return &cfg, nil
}
+73
View File
@@ -151,6 +151,79 @@ func TestConstructQemuArgs(t *testing.T) {
"-monitor", "pty",
},
},
{
name: "GPU passthrough configuration",
config: Config{
QemuBinPath: "qemu-system-x86_64",
EnableKVM: true,
Machine: "q35",
CPU: "EPYC",
SMPCount: 4,
MaxCPUs: 64,
MemID: "ram1",
MemoryConfig: MemoryConfig{
Size: "2048M",
Slots: 5,
Max: "30G",
},
OVMFCodeConfig: OVMFCodeConfig{
If: "pflash",
Format: "raw",
Unit: 0,
File: "/usr/share/OVMF/OVMF_CODE.fd",
ReadOnly: "on",
},
OVMFVarsConfig: OVMFVarsConfig{
If: "pflash",
Format: "raw",
Unit: 1,
File: "/usr/share/OVMF/OVMF_VARS.fd",
},
NetDevConfig: NetDevConfig{
ID: "vmnic",
HostFwdAgent: 7020,
GuestFwdAgent: 7002,
},
VirtioNetPciConfig: VirtioNetPciConfig{
DisableLegacy: "on",
IOMMUPlatform: true,
Addr: "0x2",
},
KernelConfig: KernelConfig{
KernelFile: "img/bzImage",
RootFsFile: "img/rootfs.cpio.gz",
},
GPUConfig: GPUConfig{
EnableGPU: true,
GPUBDF: "0000:02:00.0",
PCIeRootPort: "pci.1",
PCIeBus: "pcie.0",
FWCfgPciMmio: "262144",
},
KernelCommandLine: "quiet console=null",
NoGraphic: true,
Monitor: "pty",
},
expected: []string{
"-enable-kvm",
"-machine", "q35",
"-cpu", "EPYC",
"-smp", "4,maxcpus=64",
"-m", "2048M,slots=5,maxmem=30G",
"-drive", "if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on",
"-drive", "if=pflash,format=raw,unit=1,file=/usr/share/OVMF/OVMF_VARS.fd",
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
"-device", "pcie-root-port,id=pci.1,bus=pcie.0",
"-device", "vfio-pci,host=0000:02:00.0,bus=pci.1",
"-fw_cfg", "name=opt/ovmf/X-PciMmio64Mb,string=262144",
"-kernel", "img/bzImage",
"-append", "quiet console=null",
"-initrd", "img/rootfs.cpio.gz",
"-nographic",
"-monitor", "pty",
},
},
}
for _, tt := range tests {
+33
View File
@@ -319,3 +319,36 @@ func GetVirtualSizeGB(path string) (int, error) {
gb := (bytes + (1<<30 - 1)) >> 30
return int(gb), nil
}
// GPUPassthroughAvailable scans for NVIDIA GPU devices bound to the vfio-pci
// driver and returns the BDF of the first one found.
func GPUPassthroughAvailable() (string, bool) {
const vfioPCIPath = "/sys/bus/pci/drivers/vfio-pci"
entries, err := os.ReadDir(vfioPCIPath)
if err != nil {
return "", false
}
for _, entry := range entries {
bdf := entry.Name()
if !strings.Contains(bdf, ":") {
continue
}
vendor, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/vendor", bdf))
if err != nil || strings.TrimSpace(string(vendor)) != "0x10de" {
continue
}
class, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/class", bdf))
if err != nil {
continue
}
classStr := strings.TrimSpace(string(class))
// 0x0302xx = 3D Controller (e.g. H100), 0x0300xx = VGA Compatible Controller
if strings.HasPrefix(classStr, "0x0302") || strings.HasPrefix(classStr, "0x0300") {
return bdf, true
}
}
return "", false
}