1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
package device
import (
"errors"
"fmt"
"path/filepath"
"github.com/lxc/incus/v6/internal/linux"
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
pcidev "github.com/lxc/incus/v6/internal/server/device/pci"
"github.com/lxc/incus/v6/internal/server/instance"
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
"github.com/lxc/incus/v6/shared/util"
"github.com/lxc/incus/v6/shared/validate"
)
type pci struct {
deviceCommon
}
// validateConfig checks the supplied config for correctness.
func (d *pci) validateConfig(instConf instance.ConfigReader) error {
if !instanceSupported(instConf.Type(), instancetype.VM) {
return ErrUnsupportedDevType
}
rules := map[string]func(string) error{
// gendoc:generate(entity=devices, group=pci, key=address)
//
// ---
// type: string
// required: yes
// shortdesc: PCI address of the device
"address": validate.IsPCIAddress,
}
err := d.config.Validate(rules)
if err != nil {
return fmt.Errorf("Failed to validate config: %w", err)
}
d.config["address"] = pcidev.NormaliseAddress(d.config["address"])
return nil
}
// validateEnvironment checks if the PCI device is available.
func (d *pci) validateEnvironment() error {
if d.inst.Type() == instancetype.VM && util.IsTrue(d.inst.ExpandedConfig()["migration.stateful"]) {
return errors.New("PCI devices cannot be used when migration.stateful is enabled")
}
return validatePCIDevice(d.config["address"])
}
// Start is run when the device is added to the instance.
func (d *pci) Start() (*deviceConfig.RunConfig, error) {
err := d.validateEnvironment()
if err != nil {
return nil, fmt.Errorf("Failed to validate environment: %w", err)
}
runConf := deviceConfig.RunConfig{}
saveData := make(map[string]string)
// Make sure that vfio-pci is loaded.
err = linux.LoadModule("vfio-pci")
if err != nil {
return nil, fmt.Errorf("Error loading %q module: %w", "vfio-pci", err)
}
// Get PCI information about the device.
pciAddress := d.config["address"]
devicePath := filepath.Join("/sys/bus/pci/devices", pciAddress)
pciDev, err := pcidev.ParseUeventFile(filepath.Join(devicePath, "uevent"))
if err != nil {
return nil, fmt.Errorf("Failed to get PCI device info for %q: %w", pciAddress, err)
}
saveData["last_state.pci.slot.name"] = pciDev.SlotName
saveData["last_state.pci.driver"] = pciDev.Driver
pciIOMMUGroup, err := pcidev.DeviceIOMMUGroup(saveData["last_state.pci.slot.name"])
if err != nil {
return nil, err
}
err = pcidev.DeviceDriverOverride(pciDev, "vfio-pci")
if err != nil {
return nil, fmt.Errorf("Failed to override IOMMU group driver: %w", err)
}
runConf.PCIDevice = append(runConf.PCIDevice,
[]deviceConfig.RunConfigItem{
{Key: "devName", Value: d.name},
{Key: "pciSlotName", Value: saveData["last_state.pci.slot.name"]},
{Key: "pciIOMMUGroup", Value: fmt.Sprintf("%d", pciIOMMUGroup)},
}...)
err = d.volatileSet(saveData)
if err != nil {
return nil, err
}
return &runConf, nil
}
// CanHotPlug returns whether the device can be managed whilst the instance is running.
func (d *pci) CanHotPlug() bool {
return true
}
// Stop is run when the device is removed from the instance.
func (d *pci) Stop() (*deviceConfig.RunConfig, error) {
runConf := deviceConfig.RunConfig{
PostHooks: []func() error{d.postStop},
}
return &runConf, nil
}
// postStop is run after the device is removed from the instance.
func (d *pci) postStop() error {
defer func() {
_ = d.volatileSet(map[string]string{
"last_state.pci.slot.name": "",
"last_state.pci.driver": "",
})
}()
v := d.volatileGet()
// Unbind from vfio-pci and bind back to host driver.
if v["last_state.pci.slot.name"] != "" {
pciDev := pcidev.Device{
Driver: "vfio-pci",
SlotName: v["last_state.pci.slot.name"],
}
err := pcidev.DeviceDriverOverride(pciDev, v["last_state.pci.driver"])
if err != nil {
return err
}
}
return nil
}
|