Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/devices/mdev_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

package devices

import "context"
import (
"context"
"fmt"
)

// SetGPUProfileCacheTTL is a no-op on macOS.
func SetGPUProfileCacheTTL(ttl string) {
Expand Down Expand Up @@ -30,6 +33,10 @@ func ListMdevDevices() ([]MdevDevice, error) {
return []MdevDevice{}, nil
}

func CreateVGPU(ctx context.Context, profileName, instanceID string) (*VGPUDevice, error) {
return nil, ErrVGPUNotSupportedOnMacOS
}

// CreateMdev returns an error on macOS as mdev is not supported.
func CreateMdev(ctx context.Context, profileName, instanceID string) (*MdevDevice, error) {
return nil, ErrVGPUNotSupportedOnMacOS
Expand All @@ -45,6 +52,13 @@ func IsMdevInUse(mdevUUID string) bool {
return false
}

func DestroyVGPU(ctx context.Context, framework VGPUFramework, devicePath, mdevUUID string) error {
if framework != VGPUFrameworkNone && framework != VGPUFrameworkMdev {
return fmt.Errorf("unknown vGPU framework %q", framework)
}
return nil
}

// ReconcileMdevs is a no-op on macOS.
func ReconcileMdevs(ctx context.Context, instanceInfos []MdevReconcileInfo) error {
return nil
Expand Down
6 changes: 3 additions & 3 deletions lib/devices/mdev_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func DiscoverVFs() ([]VirtualFunction, error) {
vfs = append(vfs, VirtualFunction{
PCIAddress: vfAddr,
ParentGPU: parentGPU,
HasMdev: hasMdev,
Allocated: hasMdev,
})
}

Expand Down Expand Up @@ -253,7 +253,7 @@ func countAvailableVFsForProfilesParallel(vfs []VirtualFunction, profiles []prof
// Group free VFs by parent GPU (done once, shared by all goroutines)
freeVFsByParent := make(map[string][]VirtualFunction)
for _, vf := range vfs {
if vf.HasMdev {
if vf.Allocated {
continue
}
freeVFsByParent[vf.ParentGPU] = append(freeVFsByParent[vf.ParentGPU], vf)
Expand Down Expand Up @@ -453,7 +453,7 @@ func selectLeastLoadedVF(ctx context.Context, vfs []VirtualFunction, profileType
allGPUs := make(map[string]bool)
for _, vf := range vfs {
allGPUs[vf.ParentGPU] = true
if !vf.HasMdev {
if !vf.Allocated {
freeVFsByGPU[vf.ParentGPU] = append(freeVFsByGPU[vf.ParentGPU], vf)
}
}
Expand Down
16 changes: 15 additions & 1 deletion lib/devices/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ func ValidateDeviceName(name string) bool {
// GPUMode represents the host's GPU configuration mode
type GPUMode string

type VGPUFramework string

const (
VGPUFrameworkNone VGPUFramework = ""
VGPUFrameworkMdev VGPUFramework = "mdev"

// GPUModePassthrough indicates whole GPU VFIO passthrough
GPUModePassthrough GPUMode = "passthrough"
// GPUModeVGPU indicates SR-IOV + mdev based vGPU
Expand All @@ -73,7 +78,16 @@ const (
type VirtualFunction struct {
PCIAddress string `json:"pci_address"` // e.g., "0000:82:00.4"
ParentGPU string `json:"parent_gpu"` // e.g., "0000:82:00.0"
HasMdev bool `json:"has_mdev"` // true if an mdev is created on this VF
Allocated bool `json:"allocated"` // true if a vGPU is assigned to this VF
}

type VGPUDevice struct {
Framework VGPUFramework
VFAddress string
ProfileType string
ProfileName string
SysfsPath string
MdevUUID string
}

// MdevDevice represents an active mediated device (vGPU instance)
Expand Down
37 changes: 37 additions & 0 deletions lib/devices/vgpu_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build linux

package devices

import (
"context"
"fmt"
"path/filepath"
)

func CreateVGPU(ctx context.Context, profileName, instanceID string) (*VGPUDevice, error) {
mdev, err := CreateMdev(ctx, profileName, instanceID)
if err != nil {
return nil, err
}
return &VGPUDevice{
Framework: VGPUFrameworkMdev,
VFAddress: mdev.VFAddress,
ProfileType: mdev.ProfileType,
ProfileName: mdev.ProfileName,
SysfsPath: mdev.SysfsPath,
MdevUUID: mdev.UUID,
}, nil
}

func DestroyVGPU(ctx context.Context, framework VGPUFramework, devicePath, mdevUUID string) error {
if framework != VGPUFrameworkNone && framework != VGPUFrameworkMdev {
return fmt.Errorf("unknown vGPU framework %q", framework)
}
if mdevUUID == "" {
if devicePath == "" {
return nil
}
mdevUUID = filepath.Base(devicePath)
}
return DestroyMdev(ctx, mdevUUID)
}
11 changes: 9 additions & 2 deletions lib/hypervisor/cloudhypervisor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,20 @@ func ToVMConfig(cfg hypervisor.VMConfig) vmm.VmConfig {

// Device passthrough configuration
var devices *[]vmm.DeviceConfig
if len(cfg.PCIDevices) > 0 {
deviceConfigs := make([]vmm.DeviceConfig, 0, len(cfg.PCIDevices))
deviceCount := len(cfg.PCIDevices)
if cfg.VGPUDevicePath != "" {
deviceCount++
}
if deviceCount > 0 {
deviceConfigs := make([]vmm.DeviceConfig, 0, deviceCount)
for _, path := range cfg.PCIDevices {
deviceConfigs = append(deviceConfigs, vmm.DeviceConfig{
Path: path,
})
}
if cfg.VGPUDevicePath != "" {
deviceConfigs = append(deviceConfigs, vmm.DeviceConfig{Path: cfg.VGPUDevicePath})
}
devices = &deviceConfigs
}

Expand Down
10 changes: 10 additions & 0 deletions lib/hypervisor/cloudhypervisor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import (
"github.com/stretchr/testify/require"
)

func TestToVMConfigIncludesVGPUDevice(t *testing.T) {
path := "/sys/bus/mdev/devices/aa618089-8b16-4d01-a136-25a0f3c73123"

vmCfg := ToVMConfig(hypervisor.VMConfig{VGPUDevicePath: path})

require.NotNil(t, vmCfg.Devices)
require.Len(t, *vmCfg.Devices, 1)
assert.Equal(t, path, (*vmCfg.Devices)[0].Path)
}

func TestToVMConfig_GuestMemoryBalloon(t *testing.T) {
cfg := hypervisor.VMConfig{
VCPUs: 1,
Expand Down
3 changes: 2 additions & 1 deletion lib/hypervisor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ type VMConfig struct {
VsockSocket string

// PCI device passthrough (GPU, etc.)
PCIDevices []string
PCIDevices []string
VGPUDevicePath string

// Boot configuration
KernelPath string
Expand Down
11 changes: 6 additions & 5 deletions lib/hypervisor/qemu/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,10 @@ func buildArgs(cfg hypervisor.VMConfig, machine MachineType) []string {
args = append(args, "-device", fmt.Sprintf("%s,guest-cid=%d", virtioDevice(microvm, "vhost-vsock"), cfg.VsockCID))
}

// PCI device passthrough (GPU, mdev vGPU, etc.)
// Whole-device PCI passthrough (vGPU attaches via VGPUDevicePath below)
for _, devicePath := range cfg.PCIDevices {
var deviceArg string
if strings.HasPrefix(devicePath, "/sys/bus/mdev/devices/") {
// mdev device (vGPU) - use sysfsdev parameter
deviceArg = fmt.Sprintf("vfio-pci,sysfsdev=%s", devicePath)
} else if strings.HasPrefix(devicePath, "/sys/bus/pci/devices/") {
if strings.HasPrefix(devicePath, "/sys/bus/pci/devices/") {
// Full sysfs path for regular PCI device - extract the PCI address
// Using filepath.Base is more robust than manual string splitting
pciAddr := filepath.Base(strings.TrimSuffix(devicePath, "/"))
Expand All @@ -112,6 +109,10 @@ func buildArgs(cfg hypervisor.VMConfig, machine MachineType) []string {
args = append(args, "-device", deviceArg)
}

if cfg.VGPUDevicePath != "" {
args = append(args, "-device", fmt.Sprintf("vfio-pci,sysfsdev=%s", cfg.VGPUDevicePath))
}
Comment thread
cursor[bot] marked this conversation as resolved.

// Serial console output to file. Use a chardev with append=on so QEMU
// opens the file with O_APPEND. Without it, QEMU writes at its internal
// fd offset; if the file is externally truncated (e.g. log rotation via
Expand Down
43 changes: 43 additions & 0 deletions lib/hypervisor/qemu/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,49 @@ func TestBuildArgs_Vsock(t *testing.T) {
assert.Contains(t, args, "vhost-vsock-pci,guest-cid=123")
}

func TestBuildArgs_VGPU(t *testing.T) {
t.Parallel()

for _, path := range []string{
"/sys/bus/mdev/devices/aa618089-8b16-4d01-a136-25a0f3c73123",
"/sys/bus/pci/devices/0000:82:00.4",
} {
path := path
t.Run(path, func(t *testing.T) {
t.Parallel()
args := BuildArgs(hypervisor.VMConfig{
VCPUs: 1,
MemoryBytes: 512 * 1024 * 1024,
VGPUDevicePath: path,
})
assert.Contains(t, args, "vfio-pci,sysfsdev="+path)
})
}
}

func TestBuildArgs_VGPUAfterPCIDevices(t *testing.T) {
args := BuildArgs(hypervisor.VMConfig{
VCPUs: 1,
MemoryBytes: 512 * 1024 * 1024,
PCIDevices: []string{"0000:01:00.0"},
VGPUDevicePath: "/sys/bus/mdev/devices/aa618089-8b16-4d01-a136-25a0f3c73123",
})

pciDeviceIndex := -1
vgpuDeviceIndex := -1
for i, arg := range args {
switch arg {
case "vfio-pci,host=0000:01:00.0":
pciDeviceIndex = i
case "vfio-pci,sysfsdev=/sys/bus/mdev/devices/aa618089-8b16-4d01-a136-25a0f3c73123":
vgpuDeviceIndex = i
}
}

assert.Greater(t, pciDeviceIndex, -1)
assert.Greater(t, vgpuDeviceIndex, pciDeviceIndex)
}

func TestBuildArgs_PCIPassthrough(t *testing.T) {
cfg := hypervisor.VMConfig{
VCPUs: 1,
Expand Down
13 changes: 13 additions & 0 deletions lib/hypervisor/qemu/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ func TestMicroVMCapabilitiesExcludePCIPassthrough(t *testing.T) {
assert.True(t, (MicroVMProfile{}).capabilities().RequiresHostSnapshotVersion)
}

func TestMicroVMValidateConfigRejectsVFIODevices(t *testing.T) {
t.Parallel()
err := (MicroVMProfile{}).validateConfig(hypervisor.VMConfig{
PCIDevices: []string{"0000:82:00.4"},
})
require.ErrorContains(t, err, "microvm does not support PCI devices")

err = (MicroVMProfile{}).validateConfig(hypervisor.VMConfig{
VGPUDevicePath: "/sys/bus/mdev/devices/aa618089-8b16-4d01-a136-25a0f3c73123",
})
require.ErrorContains(t, err, "microvm does not support PCI devices")
}

func TestValidateConfigMicroVM(t *testing.T) {
t.Parallel()
if _, err := microVMMachineType(); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion lib/hypervisor/qemu/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (MicroVMProfile) validateConfig(cfg hypervisor.VMConfig) error {
if cfg.HotplugBytes > 0 {
return fmt.Errorf("microvm does not support hotplug memory")
}
if len(cfg.PCIDevices) > 0 {
if len(cfg.PCIDevices) > 0 || cfg.VGPUDevicePath != "" {
return fmt.Errorf("microvm does not support PCI devices")
}

Expand Down
Loading
Loading