SR-IOV逻辑梳理
PCIE端口虚拟化
SR-IOV 全称 Single Root I/O Virtualization ,是 Intel 在 2007年提出的一种基于硬件的虚拟化解决方案。
在虚拟机中,一切皆虚拟,比如网卡,虚拟机看起来好像有一个真实网卡,但是这个网卡是宿主机虚拟出来的硬件,没有真实的硬件;
在虚拟化场景中,CPU 与内存是最先解决的,但是 I/O 设备一直没有很好的解决办法,Intel 有 VT-d(Virtualization Technology for Directed I/O)可以将物理服务器的 PCIe 设备直接提供给虚拟机使用,也就是我们常说的“直通”(passthrough),但是直通面临一个问题是 PCIe 设备只能给一个虚拟机使用,其他虚拟机就只能干瞪眼,这肯定是不行的,所以有了 SR-IOV,一个物理设备可以虚拟出多个虚拟设备给虚拟机使用。
SR-IOV 是一种规范,使得单根端口下的单个快速外围组件互连 (PCIe) 物理设备显示为管理程序或客户机操作系统的多个单独的物理设备,既有直通设备的性能优势,又可以支持多个虚拟机,一举两得。
SR-IOV是虚拟化的一个重要功能。启用SR-IOV的这个功能,将大大减轻宿主机的CPU负荷,提高网络性能,降低网络延时等。
UEFI下SR-IOV逻辑
SR-IOV分配总线
默认大家对PCIE扫描逻辑都很清楚, 假设一张支持虚拟化网卡设备B挂载在P2P桥A下:
当我们PciScan到桥A时, 发现存在设备, 并给下游设备分配总线号, 然后去扫描对应总线(当然我们最初扫描是从Bus0开始), 当扫描这条总线时确认挂载的是设备B, 然后扫描这个设备(假设是多功能2(MULT_FUNC)设备), 我们在扫描第一个function后, 发现SrIovCapabilityOffset(来自设备的扩展配置空间)存在数据, 就说明该设备支持SR-IOV(网卡),
检测是否支持SR-IOV,为VF预留总线号: 当给IOV设备预留出一条总线后, 将总线号给到Pci桥的Header的Subordinate Bus Number, 用来记录下属的最后一条总线号.
1270 //
1271 // It is device. Check PCI IOV for Bus reservation
1272 // Go through each function, just reserve the MAX ReservedBusNum for one device
1273 //
1274 if (PcdGetBool (PcdSrIovSupport) && PciDevice->SrIovCapabilityOffset != 0) {
1275 if (TempReservedBusNum < PciDevice->ReservedBusNum) {
1276
1277 Status = PciAllocateBusNumber (PciDevice, *SubBusNumber, (UINT8) (PciDevice- >ReservedBusNum - TempReservedBusNum), SubBusNumber);
1278 if (EFI_ERROR (Status)) {
1279 return Status;
1280 }
1281 TempReservedBusNum = PciDevice->ReservedBusNum;
1282
1283 if (Func == 0) {
1284 DEBUG ((EFI_D_INFO, "PCI-IOV ScanBus - SubBusNumber - 0x%x\n", *SubBusNumber));
1285 } else {
1286 DEBUG ((EFI_D_INFO, "PCI-IOV ScanBus - SubBusNumber - 0x%x (Update)\n", * SubBusNumber));
1287 }
1288 }
1289 }
当然在扫描Function时发现支持SR-IOV, 会将VFBAR的Offset(来自于扩展配置空间)也扫描出来, 然后填写到维护的Device数据结构中, 包括VfBar类型与大小等:
533 //
534 // Parse the SR-IOV VF bars
535 //
536 if (PcdGetBool (PcdSrIovSupport) && PciIoDevice->SrIovCapabilityOffset != 0) {
537 for (Offset = PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_BAR0, BarIndex = 0;
538 Offset <= PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_BAR5;
539 BarIndex++) {
540
541 ASSERT (BarIndex < PCI_MAX_BAR);
542 Offset = PciIovParseVfBar (PciIoDevice, Offset, BarIndex);
543 }
544 }
SR-IOV Initialization
关于SR-IOV的Capability的初始化操作如下:
402 #define EFI_PCIE_CAPABILITY_ID_SRIOV_CAPABILITIES 0x04
403 #define EFI_PCIE_CAPABILITY_ID_SRIOV_CONTROL 0x08
404 #define EFI_PCIE_CAPABILITY_ID_SRIOV_STATUS 0x0A
405 #define EFI_PCIE_CAPABILITY_ID_SRIOV_INITIALVFS 0x0C
406 #define EFI_PCIE_CAPABILITY_ID_SRIOV_TOTALVFS 0x0E
407 #define EFI_PCIE_CAPABILITY_ID_SRIOV_NUMVFS 0x10
408 #define EFI_PCIE_CAPABILITY_ID_SRIOV_FUNCTION_DEPENDENCY_LINK 0x12
409 #define EFI_PCIE_CAPABILITY_ID_SRIOV_FIRSTVF 0x14
410 #define EFI_PCIE_CAPABILITY_ID_SRIOV_VFSTRIDE 0x16
411 #define EFI_PCIE_CAPABILITY_ID_SRIOV_VFDEVICEID 0x1A
412 #define EFI_PCIE_CAPABILITY_ID_SRIOV_SUPPORTED_PAGE_SIZE 0x1C
413 #define EFI_PCIE_CAPABILITY_ID_SRIOV_SYSTEM_PAGE_SIZE 0x20
414 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR0 0x24
415 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR1 0x28
416 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR2 0x2C
417 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR3 0x30
418 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR4 0x34
419 #define EFI_PCIE_CAPABILITY_ID_SRIOV_BAR5 0x38
420 #define EFI_PCIE_CAPABILITY_ID_SRIOV_VF_MIGRATION_STATE 0x3C
2253 //
2254 // Initialization for SR-IOV
2255 //
2256
2257 if (PcdGetBool (PcdSrIovSupport)) {
2258 Status = LocatePciExpressCapabilityRegBlock (
2259 PciIoDevice,
2260 EFI_PCIE_CAPABILITY_ID_SRIOV,
2261 &PciIoDevice->SrIovCapabilityOffset,
2262 NULL
2263 );
2264 if (!EFI_ERROR (Status)) {
2265 UINT32 SupportedPageSize;
2266 UINT16 VFStride;
2267 UINT16 FirstVFOffset;
2268 UINT16 Data16;
2269 UINT32 PFRid;
2270 UINT32 LastVF;
2271
2272 //
2273 // If the SR-IOV device is an ARI device, then Set ARI Capable Hierarchy for the device.
2274 //
2275 if (PcdGetBool (PcdAriSupport) && PciIoDevice->AriCapabilityOffset != 0) {
2276 PciIo->Pci.Read (
2277 PciIo,
2278 EfiPciIoWidthUint16,
2279 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_CONTROL,
2280 1,
2281 &Data16
2282 );
2283 Data16 |= EFI_PCIE_CAPABILITY_ID_SRIOV_CONTROL_ARI_HIERARCHY;
2284 PciIo->Pci.Write (
2285 PciIo,
2286 EfiPciIoWidthUint16,
2287 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_CONTROL,
2288 1,
2289 &Data16
2290 );
2291 }
2292
2293 //
2294 // Calculate SystemPageSize
2295 //
2296
2297 PciIo->Pci.Read (
2298 PciIo,
2299 EfiPciIoWidthUint32,
2300 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_SUPPORTED_PAGE_SIZE,
2301 1,
2302 &SupportedPageSize
2303 );
2304 PciIoDevice->SystemPageSize = (PcdGet32 (PcdSrIovSystemPageSize) & SupportedPageSize);
2305 ASSERT (PciIoDevice->SystemPageSize != 0);
2306
2307 PciIo->Pci.Write (
2308 PciIo,
2309 EfiPciIoWidthUint32,
2310 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_SYSTEM_PAGE_SIZE,
2311 1,
2312 &PciIoDevice->SystemPageSize
2313 );
2314 //
2315 // Adjust SystemPageSize for Alignment usage later
2316 //
2317 PciIoDevice->SystemPageSize <<= 12;
2318
2319 //
2320 // Calculate BusReservation for PCI IOV
2321 //
2322
2323 //
2324 // Read First FirstVFOffset, InitialVFs, and VFStride
2325 //
2326 PciIo->Pci.Read (
2327 PciIo,
2328 EfiPciIoWidthUint16,
2329 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_FIRSTVF,
2330 1,
2331 &FirstVFOffset
2332 );
2333 PciIo->Pci.Read (
2334 PciIo,
2335 EfiPciIoWidthUint16,
2336 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_INITIALVFS,
2337 1,
2338 &PciIoDevice->InitialVFs
2339 );
2340 PciIo->Pci.Read (
2341 PciIo,
2342 EfiPciIoWidthUint16,
2343 PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_VFSTRIDE,
2344 1,
2345 &VFStride
2346 );
2347 //
2348 // Calculate LastVF
2349 //
2350 PFRid = EFI_PCI_RID(Bus, Device, Func);
2351 LastVF = PFRid + FirstVFOffset + (PciIoDevice->InitialVFs - 1) * VFStride;
2352
2353 //
2354 // Calculate ReservedBusNum for this PF
2355 //
2356 PciIoDevice->ReservedBusNum = (UINT16)(EFI_PCI_BUS_OF_RID (LastVF) - Bus + 1);
2357
2358 DEBUG ((
2359 EFI_D_INFO,
2360 " SR-IOV: SupportedPageSize = 0x%x; SystemPageSize = 0x%x; FirstVFOffset = 0x%x;\n",
2361 SupportedPageSize, PciIoDevice->SystemPageSize >> 12, FirstVFOffset
2362 ));
2363 DEBUG ((
2364 EFI_D_INFO,
2365 " InitialVFs = 0x%x; ReservedBusNum = 0x%x; CapOffset = 0x%x\n",
2366 PciIoDevice->InitialVFs, PciIoDevice->ReservedBusNum, PciIoDevice->SrIovCapabilityOffset
2367 ));
2368 }
2369 }
- 以上填写修改的主要有: SystemPageSize; (FirstVFOffset; InitialVFs; VFStride)->ReservedBusNum: 计算需要预留的Bus号, 以备内核可以正常分配正常大小个数的虚拟设备.
关于PciScan逻辑可点击查看Segment的实现中PciScan的扫描.
内核下SR-IOV逻辑
目录: drivers/pci/iov.c
Linux系统下开启SR-IOV
- echo 4 > /sys/bus/pci/devices/0000:04:00.0/sriov_numvfs(其中04为网卡设备物理总线):
- lspci查看到Bus5为虚拟网卡, echo 4 为虚拟出4个Function, 其中ixgbevf为Intel网卡的虚拟万兆网卡的驱动,需要我们内核编译时添加(可参考链接内核编译):
- ifconfig 查看虚拟网口:
内核逻辑
在Linux系统使用下我们知道了具体使用操作, 就是向sriov_numvfs 中添加虚拟网卡个数, 其中会调用内核逻辑:
803 /**
804 * pci_sriov_set_totalvfs -- reduce the TotalVFs available
805 * @dev: the PCI PF device
806 * @numvfs: number that should be used for TotalVFs supported
807 *
808 * Should be called from PF driver's probe routine with
809 * device's mutex held.
810 *
811 * Returns 0 if PF is an SRIOV-capable device and
812 * value of numvfs valid. If not a PF return -ENOSYS;
813 * if numvfs is invalid return -EINVAL;
814 * if VFs already enabled, return -EBUSY.
815 */
816 int pci_sriov_set_totalvfs(struct pci_dev *dev, u16 numvfs)
817 {
818 if (!dev->is_physfn)
819 return -ENOSYS;
820
821 if (numvfs > dev->sriov->total_VFs)
822 return -EINVAL;
823
824 /* Shouldn't change if VFs already enabled */
825 if (dev->sriov->ctrl & PCI_SRIOV_CTRL_VFE)
826 return -EBUSY;
827
828 dev->sriov->driver_max_VFs = numvfs;
829 return 0;
830 }
831 EXPORT_SYMBOL_GPL(pci_sriov_set_totalvfs);
832
其中计算虚拟设备的Bus与Devfunc的逻辑与固件类似:
21 int pci_iov_virtfn_bus(struct pci_dev *dev, int vf_id)
22 {
23 if (!dev->is_physfn)
24 return -EINVAL;
25 return dev->bus->number + ((dev->devfn + dev->sriov->offset +
26 dev->sriov->stride * vf_id) >> 8);
27 }
28
29 int pci_iov_virtfn_devfn(struct pci_dev *dev, int vf_id)
30 {
31 if (!dev->is_physfn)
32 return -EINVAL;
33 return (dev->devfn + dev->sriov->offset +
34 dev->sriov->stride * vf_id) & 0xff;
35 }
36
最终: 内核会给每一个虚拟设备都分配一个配置空间, 这样在软件层次我们就可以分别访问,认为每个设备都是独立存在的, 互不影响.
Tip: 不管风吹浪打,胜似闲庭信步, 加油, 朋友们!!!