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 }