brintos

brintos / linux-shallow public Read only

0
0
Text · 15.5 KiB · b65b212 Raw
317 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3PCI pass-thru devices4=========================5In a Hyper-V guest VM, PCI pass-thru devices (also called6virtual PCI devices, or vPCI devices) are physical PCI devices7that are mapped directly into the VM's physical address space.8Guest device drivers can interact directly with the hardware9without intermediation by the host hypervisor.  This approach10provides higher bandwidth access to the device with lower11latency, compared with devices that are virtualized by the12hypervisor.  The device should appear to the guest just as it13would when running on bare metal, so no changes are required14to the Linux device drivers for the device.15 16Hyper-V terminology for vPCI devices is "Discrete Device17Assignment" (DDA).  Public documentation for Hyper-V DDA is18available here: `DDA`_19 20.. _DDA: https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/plan/plan-for-deploying-devices-using-discrete-device-assignment21 22DDA is typically used for storage controllers, such as NVMe,23and for GPUs.  A similar mechanism for NICs is called SR-IOV24and produces the same benefits by allowing a guest device25driver to interact directly with the hardware.  See Hyper-V26public documentation here: `SR-IOV`_27 28.. _SR-IOV: https://learn.microsoft.com/en-us/windows-hardware/drivers/network/overview-of-single-root-i-o-virtualization--sr-iov-29 30This discussion of vPCI devices includes DDA and SR-IOV31devices.32 33Device Presentation34-------------------35Hyper-V provides full PCI functionality for a vPCI device when36it is operating, so the Linux device driver for the device can37be used unchanged, provided it uses the correct Linux kernel38APIs for accessing PCI config space and for other integration39with Linux.  But the initial detection of the PCI device and40its integration with the Linux PCI subsystem must use Hyper-V41specific mechanisms.  Consequently, vPCI devices on Hyper-V42have a dual identity.  They are initially presented to Linux43guests as VMBus devices via the standard VMBus "offer"44mechanism, so they have a VMBus identity and appear under45/sys/bus/vmbus/devices.  The VMBus vPCI driver in Linux at46drivers/pci/controller/pci-hyperv.c handles a newly introduced47vPCI device by fabricating a PCI bus topology and creating all48the normal PCI device data structures in Linux that would49exist if the PCI device were discovered via ACPI on a bare-50metal system.  Once those data structures are set up, the51device also has a normal PCI identity in Linux, and the normal52Linux device driver for the vPCI device can function as if it53were running in Linux on bare-metal.  Because vPCI devices are54presented dynamically through the VMBus offer mechanism, they55do not appear in the Linux guest's ACPI tables.  vPCI devices56may be added to a VM or removed from a VM at any time during57the life of the VM, and not just during initial boot.58 59With this approach, the vPCI device is a VMBus device and a60PCI device at the same time.  In response to the VMBus offer61message, the hv_pci_probe() function runs and establishes a62VMBus connection to the vPCI VSP on the Hyper-V host.  That63connection has a single VMBus channel.  The channel is used to64exchange messages with the vPCI VSP for the purpose of setting65up and configuring the vPCI device in Linux.  Once the device66is fully configured in Linux as a PCI device, the VMBus67channel is used only if Linux changes the vCPU to be interrupted68in the guest, or if the vPCI device is removed from69the VM while the VM is running.  The ongoing operation of the70device happens directly between the Linux device driver for71the device and the hardware, with VMBus and the VMBus channel72playing no role.73 74PCI Device Setup75----------------76PCI device setup follows a sequence that Hyper-V originally77created for Windows guests, and that can be ill-suited for78Linux guests due to differences in the overall structure of79the Linux PCI subsystem compared with Windows.  Nonetheless,80with a bit of hackery in the Hyper-V virtual PCI driver for81Linux, the virtual PCI device is setup in Linux so that82generic Linux PCI subsystem code and the Linux driver for the83device "just work".84 85Each vPCI device is set up in Linux to be in its own PCI86domain with a host bridge.  The PCI domainID is derived from87bytes 4 and 5 of the instance GUID assigned to the VMBus vPCI88device.  The Hyper-V host does not guarantee that these bytes89are unique, so hv_pci_probe() has an algorithm to resolve90collisions.  The collision resolution is intended to be stable91across reboots of the same VM so that the PCI domainIDs don't92change, as the domainID appears in the user space93configuration of some devices.94 95hv_pci_probe() allocates a guest MMIO range to be used as PCI96config space for the device.  This MMIO range is communicated97to the Hyper-V host over the VMBus channel as part of telling98the host that the device is ready to enter d0.  See99hv_pci_enter_d0().  When the guest subsequently accesses this100MMIO range, the Hyper-V host intercepts the accesses and maps101them to the physical device PCI config space.102 103hv_pci_probe() also gets BAR information for the device from104the Hyper-V host, and uses this information to allocate MMIO105space for the BARs.  That MMIO space is then setup to be106associated with the host bridge so that it works when generic107PCI subsystem code in Linux processes the BARs.108 109Finally, hv_pci_probe() creates the root PCI bus.  At this110point the Hyper-V virtual PCI driver hackery is done, and the111normal Linux PCI machinery for scanning the root bus works to112detect the device, to perform driver matching, and to113initialize the driver and device.114 115PCI Device Removal116------------------117A Hyper-V host may initiate removal of a vPCI device from a118guest VM at any time during the life of the VM.  The removal119is instigated by an admin action taken on the Hyper-V host and120is not under the control of the guest OS.121 122A guest VM is notified of the removal by an unsolicited123"Eject" message sent from the host to the guest over the VMBus124channel associated with the vPCI device.  Upon receipt of such125a message, the Hyper-V virtual PCI driver in Linux126asynchronously invokes Linux kernel PCI subsystem calls to127shutdown and remove the device.  When those calls are128complete, an "Ejection Complete" message is sent back to129Hyper-V over the VMBus channel indicating that the device has130been removed.  At this point, Hyper-V sends a VMBus rescind131message to the Linux guest, which the VMBus driver in Linux132processes by removing the VMBus identity for the device.  Once133that processing is complete, all vestiges of the device having134been present are gone from the Linux kernel.  The rescind135message also indicates to the guest that Hyper-V has stopped136providing support for the vPCI device in the guest.  If the137guest were to attempt to access that device's MMIO space, it138would be an invalid reference. Hypercalls affecting the device139return errors, and any further messages sent in the VMBus140channel are ignored.141 142After sending the Eject message, Hyper-V allows the guest VM14360 seconds to cleanly shutdown the device and respond with144Ejection Complete before sending the VMBus rescind145message.  If for any reason the Eject steps don't complete146within the allowed 60 seconds, the Hyper-V host forcibly147performs the rescind steps, which will likely result in148cascading errors in the guest because the device is now no149longer present from the guest standpoint and accessing the150device MMIO space will fail.151 152Because ejection is asynchronous and can happen at any point153during the guest VM lifecycle, proper synchronization in the154Hyper-V virtual PCI driver is very tricky.  Ejection has been155observed even before a newly offered vPCI device has been156fully setup.  The Hyper-V virtual PCI driver has been updated157several times over the years to fix race conditions when158ejections happen at inopportune times. Care must be taken when159modifying this code to prevent re-introducing such problems.160See comments in the code.161 162Interrupt Assignment163--------------------164The Hyper-V virtual PCI driver supports vPCI devices using165MSI, multi-MSI, or MSI-X.  Assigning the guest vCPU that will166receive the interrupt for a particular MSI or MSI-X message is167complex because of the way the Linux setup of IRQs maps onto168the Hyper-V interfaces.  For the single-MSI and MSI-X cases,169Linux calls hv_compse_msi_msg() twice, with the first call170containing a dummy vCPU and the second call containing the171real vCPU.  Furthermore, hv_irq_unmask() is finally called172(on x86) or the GICD registers are set (on arm64) to specify173the real vCPU again.  Each of these three calls interact174with Hyper-V, which must decide which physical CPU should175receive the interrupt before it is forwarded to the guest VM.176Unfortunately, the Hyper-V decision-making process is a bit177limited, and can result in concentrating the physical178interrupts on a single CPU, causing a performance bottleneck.179See details about how this is resolved in the extensive180comment above the function hv_compose_msi_req_get_cpu().181 182The Hyper-V virtual PCI driver implements the183irq_chip.irq_compose_msi_msg function as hv_compose_msi_msg().184Unfortunately, on Hyper-V the implementation requires sending185a VMBus message to the Hyper-V host and awaiting an interrupt186indicating receipt of a reply message.  Since187irq_chip.irq_compose_msi_msg can be called with IRQ locks188held, it doesn't work to do the normal sleep until awakened by189the interrupt. Instead hv_compose_msi_msg() must send the190VMBus message, and then poll for the completion message. As191further complexity, the vPCI device could be ejected/rescinded192while the polling is in progress, so this scenario must be193detected as well.  See comments in the code regarding this194very tricky area.195 196Most of the code in the Hyper-V virtual PCI driver (pci-197hyperv.c) applies to Hyper-V and Linux guests running on x86198and on arm64 architectures.  But there are differences in how199interrupt assignments are managed.  On x86, the Hyper-V200virtual PCI driver in the guest must make a hypercall to tell201Hyper-V which guest vCPU should be interrupted by each202MSI/MSI-X interrupt, and the x86 interrupt vector number that203the x86_vector IRQ domain has picked for the interrupt.  This204hypercall is made by hv_arch_irq_unmask().  On arm64, the205Hyper-V virtual PCI driver manages the allocation of an SPI206for each MSI/MSI-X interrupt.  The Hyper-V virtual PCI driver207stores the allocated SPI in the architectural GICD registers,208which Hyper-V emulates, so no hypercall is necessary as with209x86.  Hyper-V does not support using LPIs for vPCI devices in210arm64 guest VMs because it does not emulate a GICv3 ITS.211 212The Hyper-V virtual PCI driver in Linux supports vPCI devices213whose drivers create managed or unmanaged Linux IRQs.  If the214smp_affinity for an unmanaged IRQ is updated via the /proc/irq215interface, the Hyper-V virtual PCI driver is called to tell216the Hyper-V host to change the interrupt targeting and217everything works properly.  However, on x86 if the x86_vector218IRQ domain needs to reassign an interrupt vector due to219running out of vectors on a CPU, there's no path to inform the220Hyper-V host of the change, and things break.  Fortunately,221guest VMs operate in a constrained device environment where222using all the vectors on a CPU doesn't happen. Since such a223problem is only a theoretical concern rather than a practical224concern, it has been left unaddressed.225 226DMA227---228By default, Hyper-V pins all guest VM memory in the host229when the VM is created, and programs the physical IOMMU to230allow the VM to have DMA access to all its memory.  Hence231it is safe to assign PCI devices to the VM, and allow the232guest operating system to program the DMA transfers.  The233physical IOMMU prevents a malicious guest from initiating234DMA to memory belonging to the host or to other VMs on the235host. From the Linux guest standpoint, such DMA transfers236are in "direct" mode since Hyper-V does not provide a virtual237IOMMU in the guest.238 239Hyper-V assumes that physical PCI devices always perform240cache-coherent DMA.  When running on x86, this behavior is241required by the architecture.  When running on arm64, the242architecture allows for both cache-coherent and243non-cache-coherent devices, with the behavior of each device244specified in the ACPI DSDT.  But when a PCI device is assigned245to a guest VM, that device does not appear in the DSDT, so the246Hyper-V VMBus driver propagates cache-coherency information247from the VMBus node in the ACPI DSDT to all VMBus devices,248including vPCI devices (since they have a dual identity as a VMBus249device and as a PCI device).  See vmbus_dma_configure().250Current Hyper-V versions always indicate that the VMBus is251cache coherent, so vPCI devices on arm64 always get marked as252cache coherent and the CPU does not perform any sync253operations as part of dma_map/unmap_*() calls.254 255vPCI protocol versions256----------------------257As previously described, during vPCI device setup and teardown258messages are passed over a VMBus channel between the Hyper-V259host and the Hyper-v vPCI driver in the Linux guest.  Some260messages have been revised in newer versions of Hyper-V, so261the guest and host must agree on the vPCI protocol version to262be used.  The version is negotiated when communication over263the VMBus channel is first established.  See264hv_pci_protocol_negotiation(). Newer versions of the protocol265extend support to VMs with more than 64 vCPUs, and provide266additional information about the vPCI device, such as the267guest virtual NUMA node to which it is most closely affined in268the underlying hardware.269 270Guest NUMA node affinity271------------------------272When the vPCI protocol version provides it, the guest NUMA273node affinity of the vPCI device is stored as part of the Linux274device information for subsequent use by the Linux driver. See275hv_pci_assign_numa_node().  If the negotiated protocol version276does not support the host providing NUMA affinity information,277the Linux guest defaults the device NUMA node to 0.  But even278when the negotiated protocol version includes NUMA affinity279information, the ability of the host to provide such280information depends on certain host configuration options.  If281the guest receives NUMA node value "0", it could mean NUMA282node 0, or it could mean "no information is available".283Unfortunately it is not possible to distinguish the two cases284from the guest side.285 286PCI config space access in a CoCo VM287------------------------------------288Linux PCI device drivers access PCI config space using a289standard set of functions provided by the Linux PCI subsystem.290In Hyper-V guests these standard functions map to functions291hv_pcifront_read_config() and hv_pcifront_write_config()292in the Hyper-V virtual PCI driver.  In normal VMs,293these hv_pcifront_*() functions directly access the PCI config294space, and the accesses trap to Hyper-V to be handled.295But in CoCo VMs, memory encryption prevents Hyper-V296from reading the guest instruction stream to emulate the297access, so the hv_pcifront_*() functions must invoke298hypercalls with explicit arguments describing the access to be299made.300 301Config Block back-channel302-------------------------303The Hyper-V host and Hyper-V virtual PCI driver in Linux304together implement a non-standard back-channel communication305path between the host and guest.  The back-channel path uses306messages sent over the VMBus channel associated with the vPCI307device.  The functions hyperv_read_cfg_blk() and308hyperv_write_cfg_blk() are the primary interfaces provided to309other parts of the Linux kernel.  As of this writing, these310interfaces are used only by the Mellanox mlx5 driver to pass311diagnostic data to a Hyper-V host running in the Azure public312cloud.  The functions hyperv_read_cfg_blk() and313hyperv_write_cfg_blk() are implemented in a separate module314(pci-hyperv-intf.c, under CONFIG_PCI_HYPERV_INTERFACE) that315effectively stubs them out when running in non-Hyper-V316environments.317