gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/virtcontainers/device/drivers/vfio.go (about)

     1  // Copyright (c) 2017-2018 Intel Corporation
     2  // Copyright (c) 2018-2019 Huawei Corporation
     3  //
     4  // SPDX-License-Identifier: Apache-2.0
     5  //
     6  
     7  package drivers
     8  
     9  import (
    10  	"fmt"
    11  	"io/ioutil"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/sirupsen/logrus"
    17  
    18  	"github.com/kata-containers/runtime/virtcontainers/device/api"
    19  	"github.com/kata-containers/runtime/virtcontainers/device/config"
    20  	persistapi "github.com/kata-containers/runtime/virtcontainers/persist/api"
    21  	"github.com/kata-containers/runtime/virtcontainers/utils"
    22  )
    23  
    24  // bind/unbind paths to aid in SRIOV VF bring-up/restore
    25  const (
    26  	pciDriverUnbindPath = "/sys/bus/pci/devices/%s/driver/unbind"
    27  	pciDriverBindPath   = "/sys/bus/pci/drivers/%s/bind"
    28  	vfioNewIDPath       = "/sys/bus/pci/drivers/vfio-pci/new_id"
    29  	vfioRemoveIDPath    = "/sys/bus/pci/drivers/vfio-pci/remove_id"
    30  	pcieRootPortPrefix  = "rp"
    31  )
    32  
    33  var (
    34  	AllPCIeDevs = map[string]bool{}
    35  )
    36  
    37  // VFIODevice is a vfio device meant to be passed to the hypervisor
    38  // to be used by the Virtual Machine.
    39  type VFIODevice struct {
    40  	*GenericDevice
    41  	VfioDevs []*config.VFIODev
    42  }
    43  
    44  // NewVFIODevice create a new VFIO device
    45  func NewVFIODevice(devInfo *config.DeviceInfo) *VFIODevice {
    46  	return &VFIODevice{
    47  		GenericDevice: &GenericDevice{
    48  			ID:         devInfo.ID,
    49  			DeviceInfo: devInfo,
    50  		},
    51  	}
    52  }
    53  
    54  // Attach is standard interface of api.Device, it's used to add device to some
    55  // DeviceReceiver
    56  func (device *VFIODevice) Attach(devReceiver api.DeviceReceiver) (retErr error) {
    57  	skip, err := device.bumpAttachCount(true)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	if skip {
    62  		return nil
    63  	}
    64  
    65  	defer func() {
    66  		if retErr != nil {
    67  			device.bumpAttachCount(false)
    68  		}
    69  	}()
    70  
    71  	vfioGroup := filepath.Base(device.DeviceInfo.HostPath)
    72  	iommuDevicesPath := filepath.Join(config.SysIOMMUPath, vfioGroup, "devices")
    73  
    74  	deviceFiles, err := ioutil.ReadDir(iommuDevicesPath)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	// Pass all devices in iommu group
    80  	for i, deviceFile := range deviceFiles {
    81  		//Get bdf of device eg 0000:00:1c.0
    82  		deviceBDF, deviceSysfsDev, vfioDeviceType, err := getVFIODetails(deviceFile.Name(), iommuDevicesPath)
    83  		if err != nil {
    84  			return err
    85  		}
    86  		vfio := &config.VFIODev{
    87  			ID:       utils.MakeNameID("vfio", device.DeviceInfo.ID+strconv.Itoa(i), maxDevIDSize),
    88  			Type:     vfioDeviceType,
    89  			BDF:      deviceBDF,
    90  			SysfsDev: deviceSysfsDev,
    91  			IsPCIe:   isPCIeDevice(deviceBDF),
    92  			Class:    getPCIDeviceProperty(deviceBDF, PCISysFsDevicesClass),
    93  		}
    94  		device.VfioDevs = append(device.VfioDevs, vfio)
    95  		if vfio.IsPCIe {
    96  			vfio.Bus = fmt.Sprintf("%s%d", pcieRootPortPrefix, len(AllPCIeDevs))
    97  			AllPCIeDevs[vfio.BDF] = true
    98  		}
    99  	}
   100  
   101  	// hotplug a VFIO device is actually hotplugging a group of iommu devices
   102  	if err := devReceiver.HotplugAddDevice(device, config.DeviceVFIO); err != nil {
   103  		deviceLogger().WithError(err).Error("Failed to add device")
   104  		return err
   105  	}
   106  
   107  	deviceLogger().WithFields(logrus.Fields{
   108  		"device-group": device.DeviceInfo.HostPath,
   109  		"device-type":  "vfio-passthrough",
   110  	}).Info("Device group attached")
   111  	return nil
   112  }
   113  
   114  // Detach is standard interface of api.Device, it's used to remove device from some
   115  // DeviceReceiver
   116  func (device *VFIODevice) Detach(devReceiver api.DeviceReceiver) (retErr error) {
   117  	skip, err := device.bumpAttachCount(false)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	if skip {
   122  		return nil
   123  	}
   124  
   125  	defer func() {
   126  		if retErr != nil {
   127  			device.bumpAttachCount(true)
   128  		}
   129  	}()
   130  
   131  	// hotplug a VFIO device is actually hotplugging a group of iommu devices
   132  	if err := devReceiver.HotplugRemoveDevice(device, config.DeviceVFIO); err != nil {
   133  		deviceLogger().WithError(err).Error("Failed to remove device")
   134  		return err
   135  	}
   136  
   137  	deviceLogger().WithFields(logrus.Fields{
   138  		"device-group": device.DeviceInfo.HostPath,
   139  		"device-type":  "vfio-passthrough",
   140  	}).Info("Device group detached")
   141  	return nil
   142  }
   143  
   144  // DeviceType is standard interface of api.Device, it returns device type
   145  func (device *VFIODevice) DeviceType() config.DeviceType {
   146  	return config.DeviceVFIO
   147  }
   148  
   149  // GetDeviceInfo returns device information used for creating
   150  func (device *VFIODevice) GetDeviceInfo() interface{} {
   151  	return device.VfioDevs
   152  }
   153  
   154  // Save converts Device to DeviceState
   155  func (device *VFIODevice) Save() persistapi.DeviceState {
   156  	ds := device.GenericDevice.Save()
   157  	ds.Type = string(device.DeviceType())
   158  
   159  	devs := device.VfioDevs
   160  	for _, dev := range devs {
   161  		if dev != nil {
   162  			ds.VFIODevs = append(ds.VFIODevs, &persistapi.VFIODev{
   163  				ID:       dev.ID,
   164  				Type:     uint32(dev.Type),
   165  				BDF:      dev.BDF,
   166  				SysfsDev: dev.SysfsDev,
   167  			})
   168  		}
   169  	}
   170  	return ds
   171  }
   172  
   173  // Load loads DeviceState and converts it to specific device
   174  func (device *VFIODevice) Load(ds persistapi.DeviceState) {
   175  	device.GenericDevice = &GenericDevice{}
   176  	device.GenericDevice.Load(ds)
   177  
   178  	for _, dev := range ds.VFIODevs {
   179  		device.VfioDevs = append(device.VfioDevs, &config.VFIODev{
   180  			ID:       dev.ID,
   181  			Type:     config.VFIODeviceType(dev.Type),
   182  			BDF:      dev.BDF,
   183  			SysfsDev: dev.SysfsDev,
   184  		})
   185  	}
   186  }
   187  
   188  // It should implement GetAttachCount() and DeviceID() as api.Device implementation
   189  // here it shares function from *GenericDevice so we don't need duplicate codes
   190  func getVFIODetails(deviceFileName, iommuDevicesPath string) (deviceBDF, deviceSysfsDev string, vfioDeviceType config.VFIODeviceType, err error) {
   191  	vfioDeviceType = GetVFIODeviceType(deviceFileName)
   192  
   193  	switch vfioDeviceType {
   194  	case config.VFIODeviceNormalType:
   195  		// Get bdf of device eg. 0000:00:1c.0
   196  		deviceBDF = getBDF(deviceFileName)
   197  		// Get sysfs path used by cloud-hypervisor
   198  		deviceSysfsDev = filepath.Join(config.SysBusPciDevicesPath, deviceFileName)
   199  	case config.VFIODeviceMediatedType:
   200  		// Get sysfsdev of device eg. /sys/devices/pci0000:00/0000:00:02.0/f79944e4-5a3d-11e8-99ce-479cbab002e4
   201  		sysfsDevStr := filepath.Join(iommuDevicesPath, deviceFileName)
   202  		deviceSysfsDev, err = getSysfsDev(sysfsDevStr)
   203  	default:
   204  		err = fmt.Errorf("Incorrect tokens found while parsing vfio details: %s", deviceFileName)
   205  	}
   206  
   207  	return deviceBDF, deviceSysfsDev, vfioDeviceType, err
   208  }
   209  
   210  // getBDF returns the BDF of pci device
   211  // Expected input string format is [<domain>]:[<bus>][<slot>].[<func>] eg. 0000:02:10.0
   212  func getBDF(deviceSysStr string) string {
   213  	tokens := strings.SplitN(deviceSysStr, ":", 2)
   214  	return tokens[1]
   215  }
   216  
   217  // getSysfsDev returns the sysfsdev of mediated device
   218  // Expected input string format is absolute path to the sysfs dev node
   219  // eg. /sys/kernel/iommu_groups/0/devices/f79944e4-5a3d-11e8-99ce-479cbab002e4
   220  func getSysfsDev(sysfsDevStr string) (string, error) {
   221  	return filepath.EvalSymlinks(sysfsDevStr)
   222  }
   223  
   224  // BindDevicetoVFIO binds the device to vfio driver after unbinding from host.
   225  // Will be called by a network interface or a generic pcie device.
   226  func BindDevicetoVFIO(bdf, hostDriver, vendorDeviceID string) error {
   227  
   228  	// Unbind from the host driver
   229  	unbindDriverPath := fmt.Sprintf(pciDriverUnbindPath, bdf)
   230  	deviceLogger().WithFields(logrus.Fields{
   231  		"device-bdf":  bdf,
   232  		"driver-path": unbindDriverPath,
   233  	}).Info("Unbinding device from driver")
   234  
   235  	if err := utils.WriteToFile(unbindDriverPath, []byte(bdf)); err != nil {
   236  		return err
   237  	}
   238  
   239  	// Add device id to vfio driver.
   240  	deviceLogger().WithFields(logrus.Fields{
   241  		"vendor-device-id": vendorDeviceID,
   242  		"vfio-new-id-path": vfioNewIDPath,
   243  	}).Info("Writing vendor-device-id to vfio new-id path")
   244  
   245  	if err := utils.WriteToFile(vfioNewIDPath, []byte(vendorDeviceID)); err != nil {
   246  		return err
   247  	}
   248  
   249  	// Bind to vfio-pci driver.
   250  	bindDriverPath := fmt.Sprintf(pciDriverBindPath, "vfio-pci")
   251  
   252  	api.DeviceLogger().WithFields(logrus.Fields{
   253  		"device-bdf":  bdf,
   254  		"driver-path": bindDriverPath,
   255  	}).Info("Binding device to vfio driver")
   256  
   257  	// Device may be already bound at this time because of earlier write to new_id, ignore error
   258  	utils.WriteToFile(bindDriverPath, []byte(bdf))
   259  
   260  	return nil
   261  }
   262  
   263  // BindDevicetoHost binds the device to the host driver driver after unbinding from vfio-pci.
   264  func BindDevicetoHost(bdf, hostDriver, vendorDeviceID string) error {
   265  	// Unbind from vfio-pci driver
   266  	unbindDriverPath := fmt.Sprintf(pciDriverUnbindPath, bdf)
   267  	api.DeviceLogger().WithFields(logrus.Fields{
   268  		"device-bdf":  bdf,
   269  		"driver-path": unbindDriverPath,
   270  	}).Info("Unbinding device from driver")
   271  
   272  	if err := utils.WriteToFile(unbindDriverPath, []byte(bdf)); err != nil {
   273  		return err
   274  	}
   275  
   276  	// To prevent new VFs from binding to VFIO-PCI, remove_id
   277  	if err := utils.WriteToFile(vfioRemoveIDPath, []byte(vendorDeviceID)); err != nil {
   278  		return err
   279  	}
   280  
   281  	// Bind back to host driver
   282  	bindDriverPath := fmt.Sprintf(pciDriverBindPath, hostDriver)
   283  	api.DeviceLogger().WithFields(logrus.Fields{
   284  		"device-bdf":  bdf,
   285  		"driver-path": bindDriverPath,
   286  	}).Info("Binding back device to host driver")
   287  
   288  	return utils.WriteToFile(bindDriverPath, []byte(bdf))
   289  }