| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- .. SPDX-License-Identifier: GPL-2.0
- VMBus
- =====
- VMBus is a software construct provided by Hyper-V to guest VMs. It
- consists of a control path and common facilities used by synthetic
- devices that Hyper-V presents to guest VMs. The control path is
- used to offer synthetic devices to the guest VM and, in some cases,
- to rescind those devices. The common facilities include software
- channels for communicating between the device driver in the guest VM
- and the synthetic device implementation that is part of Hyper-V, and
- signaling primitives to allow Hyper-V and the guest to interrupt
- each other.
- VMBus is modeled in Linux as a bus, with the expected /sys/bus/vmbus
- entry in a running Linux guest. The VMBus driver (drivers/hv/vmbus_drv.c)
- establishes the VMBus control path with the Hyper-V host, then
- registers itself as a Linux bus driver. It implements the standard
- bus functions for adding and removing devices to/from the bus.
- Most synthetic devices offered by Hyper-V have a corresponding Linux
- device driver. These devices include:
- * SCSI controller
- * NIC
- * Graphics frame buffer
- * Keyboard
- * Mouse
- * PCI device pass-thru
- * Heartbeat
- * Time Sync
- * Shutdown
- * Memory balloon
- * Key/Value Pair (KVP) exchange with Hyper-V
- * Hyper-V online backup (a.k.a. VSS)
- Guest VMs may have multiple instances of the synthetic SCSI
- controller, synthetic NIC, and PCI pass-thru devices. Other
- synthetic devices are limited to a single instance per VM. Not
- listed above are a small number of synthetic devices offered by
- Hyper-V that are used only by Windows guests and for which Linux
- does not have a driver.
- Hyper-V uses the terms "VSP" and "VSC" in describing synthetic
- devices. "VSP" refers to the Hyper-V code that implements a
- particular synthetic device, while "VSC" refers to the driver for
- the device in the guest VM. For example, the Linux driver for the
- synthetic NIC is referred to as "netvsc" and the Linux driver for
- the synthetic SCSI controller is "storvsc". These drivers contain
- functions with names like "storvsc_connect_to_vsp".
- VMBus channels
- --------------
- An instance of a synthetic device uses VMBus channels to communicate
- between the VSP and the VSC. Channels are bi-directional and used
- for passing messages. Most synthetic devices use a single channel,
- but the synthetic SCSI controller and synthetic NIC may use multiple
- channels to achieve higher performance and greater parallelism.
- Each channel consists of two ring buffers. These are classic ring
- buffers from a university data structures textbook. If the read
- and writes pointers are equal, the ring buffer is considered to be
- empty, so a full ring buffer always has at least one byte unused.
- The "in" ring buffer is for messages from the Hyper-V host to the
- guest, and the "out" ring buffer is for messages from the guest to
- the Hyper-V host. In Linux, the "in" and "out" designations are as
- viewed by the guest side. The ring buffers are memory that is
- shared between the guest and the host, and they follow the standard
- paradigm where the memory is allocated by the guest, with the list
- of GPAs that make up the ring buffer communicated to the host. Each
- ring buffer consists of a header page (4 Kbytes) with the read and
- write indices and some control flags, followed by the memory for the
- actual ring. The size of the ring is determined by the VSC in the
- guest and is specific to each synthetic device. The list of GPAs
- making up the ring is communicated to the Hyper-V host over the
- VMBus control path as a GPA Descriptor List (GPADL). See function
- vmbus_establish_gpadl().
- Each ring buffer is mapped into contiguous Linux kernel virtual
- space in three parts: 1) the 4 Kbyte header page, 2) the memory
- that makes up the ring itself, and 3) a second mapping of the memory
- that makes up the ring itself. Because (2) and (3) are contiguous
- in kernel virtual space, the code that copies data to and from the
- ring buffer need not be concerned with ring buffer wrap-around.
- Once a copy operation has completed, the read or write index may
- need to be reset to point back into the first mapping, but the
- actual data copy does not need to be broken into two parts. This
- approach also allows complex data structures to be easily accessed
- directly in the ring without handling wrap-around.
- On arm64 with page sizes > 4 Kbytes, the header page must still be
- passed to Hyper-V as a 4 Kbyte area. But the memory for the actual
- ring must be aligned to PAGE_SIZE and have a size that is a multiple
- of PAGE_SIZE so that the duplicate mapping trick can be done. Hence
- a portion of the header page is unused and not communicated to
- Hyper-V. This case is handled by vmbus_establish_gpadl().
- Hyper-V enforces a limit on the aggregate amount of guest memory
- that can be shared with the host via GPADLs. This limit ensures
- that a rogue guest can't force the consumption of excessive host
- resources. For Windows Server 2019 and later, this limit is
- approximately 1280 Mbytes. For versions prior to Windows Server
- 2019, the limit is approximately 384 Mbytes.
- VMBus channel messages
- ----------------------
- All messages sent in a VMBus channel have a standard header that includes
- the message length, the offset of the message payload, some flags, and a
- transactionID. The portion of the message after the header is
- unique to each VSP/VSC pair.
- Messages follow one of two patterns:
- * Unidirectional: Either side sends a message and does not
- expect a response message
- * Request/response: One side (usually the guest) sends a message
- and expects a response
- The transactionID (a.k.a. "requestID") is for matching requests &
- responses. Some synthetic devices allow multiple requests to be in-
- flight simultaneously, so the guest specifies a transactionID when
- sending a request. Hyper-V sends back the same transactionID in the
- matching response.
- Messages passed between the VSP and VSC are control messages. For
- example, a message sent from the storvsc driver might be "execute
- this SCSI command". If a message also implies some data transfer
- between the guest and the Hyper-V host, the actual data to be
- transferred may be embedded with the control message, or it may be
- specified as a separate data buffer that the Hyper-V host will
- access as a DMA operation. The former case is used when the size of
- the data is small and the cost of copying the data to and from the
- ring buffer is minimal. For example, time sync messages from the
- Hyper-V host to the guest contain the actual time value. When the
- data is larger, a separate data buffer is used. In this case, the
- control message contains a list of GPAs that describe the data
- buffer. For example, the storvsc driver uses this approach to
- specify the data buffers to/from which disk I/O is done.
- Three functions exist to send VMBus channel messages:
- 1. vmbus_sendpacket(): Control-only messages and messages with
- embedded data -- no GPAs
- 2. vmbus_sendpacket_pagebuffer(): Message with list of GPAs
- identifying data to transfer. An offset and length is
- associated with each GPA so that multiple discontinuous areas
- of guest memory can be targeted.
- 3. vmbus_sendpacket_mpb_desc(): Message with list of GPAs
- identifying data to transfer. A single offset and length is
- associated with a list of GPAs. The GPAs must describe a
- single logical area of guest memory to be targeted.
- Historically, Linux guests have trusted Hyper-V to send well-formed
- and valid messages, and Linux drivers for synthetic devices did not
- fully validate messages. With the introduction of processor
- technologies that fully encrypt guest memory and that allow the
- guest to not trust the hypervisor (AMD SEV-SNP, Intel TDX), trusting
- the Hyper-V host is no longer a valid assumption. The drivers for
- VMBus synthetic devices are being updated to fully validate any
- values read from memory that is shared with Hyper-V, which includes
- messages from VMBus devices. To facilitate such validation,
- messages read by the guest from the "in" ring buffer are copied to a
- temporary buffer that is not shared with Hyper-V. Validation is
- performed in this temporary buffer without the risk of Hyper-V
- maliciously modifying the message after it is validated but before
- it is used.
- Synthetic Interrupt Controller (synic)
- --------------------------------------
- Hyper-V provides each guest CPU with a synthetic interrupt controller
- that is used by VMBus for host-guest communication. While each synic
- defines 16 synthetic interrupts (SINT), Linux uses only one of the 16
- (VMBUS_MESSAGE_SINT). All interrupts related to communication between
- the Hyper-V host and a guest CPU use that SINT.
- The SINT is mapped to a single per-CPU architectural interrupt (i.e,
- an 8-bit x86/x64 interrupt vector, or an arm64 PPI INTID). Because
- each CPU in the guest has a synic and may receive VMBus interrupts,
- they are best modeled in Linux as per-CPU interrupts. This model works
- well on arm64 where a single per-CPU Linux IRQ is allocated for
- VMBUS_MESSAGE_SINT. This IRQ appears in /proc/interrupts as an IRQ labelled
- "Hyper-V VMbus". Since x86/x64 lacks support for per-CPU IRQs, an x86
- interrupt vector is statically allocated (HYPERVISOR_CALLBACK_VECTOR)
- across all CPUs and explicitly coded to call vmbus_isr(). In this case,
- there's no Linux IRQ, and the interrupts are visible in aggregate in
- /proc/interrupts on the "HYP" line.
- The synic provides the means to demultiplex the architectural interrupt into
- one or more logical interrupts and route the logical interrupt to the proper
- VMBus handler in Linux. This demultiplexing is done by vmbus_isr() and
- related functions that access synic data structures.
- The synic is not modeled in Linux as an irq chip or irq domain,
- and the demultiplexed logical interrupts are not Linux IRQs. As such,
- they don't appear in /proc/interrupts or /proc/irq. The CPU
- affinity for one of these logical interrupts is controlled via an
- entry under /sys/bus/vmbus as described below.
- VMBus interrupts
- ----------------
- VMBus provides a mechanism for the guest to interrupt the host when
- the guest has queued new messages in a ring buffer. The host
- expects that the guest will send an interrupt only when an "out"
- ring buffer transitions from empty to non-empty. If the guest sends
- interrupts at other times, the host deems such interrupts to be
- unnecessary. If a guest sends an excessive number of unnecessary
- interrupts, the host may throttle that guest by suspending its
- execution for a few seconds to prevent a denial-of-service attack.
- Similarly, the host will interrupt the guest via the synic when
- it sends a new message on the VMBus control path, or when a VMBus
- channel "in" ring buffer transitions from empty to non-empty due to
- the host inserting a new VMBus channel message. The control message stream
- and each VMBus channel "in" ring buffer are separate logical interrupts
- that are demultiplexed by vmbus_isr(). It demultiplexes by first checking
- for channel interrupts by calling vmbus_chan_sched(), which looks at a synic
- bitmap to determine which channels have pending interrupts on this CPU.
- If multiple channels have pending interrupts for this CPU, they are
- processed sequentially. When all channel interrupts have been processed,
- vmbus_isr() checks for and processes any messages received on the VMBus
- control path.
- The guest CPU that a VMBus channel will interrupt is selected by the
- guest when the channel is created, and the host is informed of that
- selection. VMBus devices are broadly grouped into two categories:
- 1. "Slow" devices that need only one VMBus channel. The devices
- (such as keyboard, mouse, heartbeat, and timesync) generate
- relatively few interrupts. Their VMBus channels are all
- assigned to interrupt the VMBUS_CONNECT_CPU, which is always
- CPU 0.
- 2. "High speed" devices that may use multiple VMBus channels for
- higher parallelism and performance. These devices include the
- synthetic SCSI controller and synthetic NIC. Their VMBus
- channels interrupts are assigned to CPUs that are spread out
- among the available CPUs in the VM so that interrupts on
- multiple channels can be processed in parallel.
- The assignment of VMBus channel interrupts to CPUs is done in the
- function init_vp_index(). This assignment is done outside of the
- normal Linux interrupt affinity mechanism, so the interrupts are
- neither "unmanaged" nor "managed" interrupts.
- The CPU that a VMBus channel will interrupt can be seen in
- /sys/bus/vmbus/devices/<deviceGUID>/ channels/<channelRelID>/cpu.
- When running on later versions of Hyper-V, the CPU can be changed
- by writing a new value to this sysfs entry. Because VMBus channel
- interrupts are not Linux IRQs, there are no entries in /proc/interrupts
- or /proc/irq corresponding to individual VMBus channel interrupts.
- An online CPU in a Linux guest may not be taken offline if it has
- VMBus channel interrupts assigned to it. Starting in kernel v6.15,
- any such interrupts are automatically reassigned to some other CPU
- at the time of offlining. The "other" CPU is chosen by the
- implementation and is not load balanced or otherwise intelligently
- determined. If the CPU is onlined again, channel interrupts previously
- assigned to it are not moved back. As a result, after multiple CPUs
- have been offlined, and perhaps onlined again, the interrupt-to-CPU
- mapping may be scrambled and non-optimal. In such a case, optimal
- assignments must be re-established manually. For kernels v6.14 and
- earlier, any conflicting channel interrupts must first be manually
- reassigned to another CPU as described above. Then when no channel
- interrupts are assigned to the CPU, it can be taken offline.
- The VMBus channel interrupt handling code is designed to work
- correctly even if an interrupt is received on a CPU other than the
- CPU assigned to the channel. Specifically, the code does not use
- CPU-based exclusion for correctness. In normal operation, Hyper-V
- will interrupt the assigned CPU. But when the CPU assigned to a
- channel is being changed via sysfs, the guest doesn't know exactly
- when Hyper-V will make the transition. The code must work correctly
- even if there is a time lag before Hyper-V starts interrupting the
- new CPU. See comments in target_cpu_store().
- VMBus device creation/deletion
- ------------------------------
- Hyper-V and the Linux guest have a separate message-passing path
- that is used for synthetic device creation and deletion. This
- path does not use a VMBus channel. See vmbus_post_msg() and
- vmbus_on_msg_dpc().
- The first step is for the guest to connect to the generic
- Hyper-V VMBus mechanism. As part of establishing this connection,
- the guest and Hyper-V agree on a VMBus protocol version they will
- use. This negotiation allows newer Linux kernels to run on older
- Hyper-V versions, and vice versa.
- The guest then tells Hyper-V to "send offers". Hyper-V sends an
- offer message to the guest for each synthetic device that the VM
- is configured to have. Each VMBus device type has a fixed GUID
- known as the "class ID", and each VMBus device instance is also
- identified by a GUID. The offer message from Hyper-V contains
- both GUIDs to uniquely (within the VM) identify the device.
- There is one offer message for each device instance, so a VM with
- two synthetic NICs will get two offers messages with the NIC
- class ID. The ordering of offer messages can vary from boot-to-boot
- and must not be assumed to be consistent in Linux code. Offer
- messages may also arrive long after Linux has initially booted
- because Hyper-V supports adding devices, such as synthetic NICs,
- to running VMs. A new offer message is processed by
- vmbus_process_offer(), which indirectly invokes vmbus_add_channel_work().
- Upon receipt of an offer message, the guest identifies the device
- type based on the class ID, and invokes the correct driver to set up
- the device. Driver/device matching is performed using the standard
- Linux mechanism.
- The device driver probe function opens the primary VMBus channel to
- the corresponding VSP. It allocates guest memory for the channel
- ring buffers and shares the ring buffer with the Hyper-V host by
- giving the host a list of GPAs for the ring buffer memory. See
- vmbus_establish_gpadl().
- Once the ring buffer is set up, the device driver and VSP exchange
- setup messages via the primary channel. These messages may include
- negotiating the device protocol version to be used between the Linux
- VSC and the VSP on the Hyper-V host. The setup messages may also
- include creating additional VMBus channels, which are somewhat
- mis-named as "sub-channels" since they are functionally
- equivalent to the primary channel once they are created.
- Finally, the device driver may create entries in /dev as with
- any device driver.
- The Hyper-V host can send a "rescind" message to the guest to
- remove a device that was previously offered. Linux drivers must
- handle such a rescind message at any time. Rescinding a device
- invokes the device driver "remove" function to cleanly shut
- down the device and remove it. Once a synthetic device is
- rescinded, neither Hyper-V nor Linux retains any state about
- its previous existence. Such a device might be re-added later,
- in which case it is treated as an entirely new device. See
- vmbus_onoffer_rescind().
- For some devices, such as the KVP device, Hyper-V automatically
- sends a rescind message when the primary channel is closed,
- likely as a result of unbinding the device from its driver.
- The rescind causes Linux to remove the device. But then Hyper-V
- immediately reoffers the device to the guest, causing a new
- instance of the device to be created in Linux. For other
- devices, such as the synthetic SCSI and NIC devices, closing the
- primary channel does *not* result in Hyper-V sending a rescind
- message. The device continues to exist in Linux on the VMBus,
- but with no driver bound to it. The same driver or a new driver
- can subsequently be bound to the existing instance of the device.
|