Virtual Interrupt¶
This section introduces ACRN guest virtual interrupt management, which includes:
- VCPU request for virtual interrupt kick off,
- vPIC/vIOAPIC/vLAPIC for virtual interrupt injection interfaces,
- physical-to-virtual interrupt mapping for a pass-thru device, and
- the process of VMX interrupt/exception injection.
A guest VM never owns any physical interrupts. All interrupts received by Guest OS come from a virtual interrupt injected by vLAPIC, vIOAPIC or vPIC. Such virtual interrupts are triggered either from a pass-through device or from I/O mediators in SOS via hypercalls. Section 3.8.6 introduces how the hypervisor manages the mapping between physical and virtual interrupts for pass-through devices.
Emulation for devices is inside SOS user space device model, i.e., acrn-dm. However for performance consideration: vLAPIC, vIOAPIC, and vPIC are emulated inside HV directly.
From guest OS point of view, vPIC is Virtual Wire Mode via vIOAPIC. The symmetric I/O Mode is shown in Figure 55 later in this section.
The following command line options to guest Linux affects whether it uses PIC or IOAPIC:
- Kernel boot param with vPIC: add “maxcpu=0” Guest OS will use PIC
- Kernel boot param with vIOAPIC: add “maxcpu=1” (as long as not “0”) Guest OS will use IOAPIC. And Keep IOAPIC pin2 as source of PIC.
vCPU Request for Interrupt Injection¶
The vCPU request mechanism (described in Pending Request Handlers) is leveraged to inject interrupts to a certain vCPU. As mentioned in IPI Management, physical vector 0xF0 is used to kick VCPU out of its VMX non-root mode, used to make a request for virtual interrupt injection or other requests such as flush EPT.
The eventid supported for virtual interrupt injection includes:
#define ACRN_REQUEST_EXCP 0 /* request for exception injection */
#define ACRN_REQUEST_EVENT 1 /* vLAPIC event */
#define ACRN_REQUEST_EXTINT 2 /* external interrupt from vPIC */
#define ACRN_REQUEST_NMI 3 /* non-maskable interrupt */
The vcpu_make_request is necessary for a virtual interrupt injection. If the target vCPU is running under VMX non-root mode, it will send an IPI to kick it out, which leads to an external-interrupt VM-Exit. For some cases there is no need to send IPI when making a request, because the CPU making the request itself is the target VCPU. For example, the #GP exception request always happens on the current CPU when it finds an invalid emulation has happened. An external interrupt for a pass-thru device always happens on the VCPUs this device belonging to, so after it triggers an external-interrupt VM-Exit, the current CPU is also the target VCPU.
Virtual LAPIC¶
LAPIC is virtualized for all Guest types: SOS and UOS. Given support by the physical processor, APICv Virtual Interrupt Delivery (VID) is enabled and will support Posted-Interrupt feature. Otherwise, it will fall back to legacy virtual interrupt injection mode.
vLAPIC provides the same features as the native LAPIC:
- Vector mask/unmask
- Virtual vector injections (Level or Edge trigger mode) to vCPU
- vIOAPIC notification of EOI processing
- TSC Timer service
- vLAPIC support CR8 to update TPR
- INIT/STARTUP handling
vLAPIC APIs¶
APIs are provided when an interrupt source from vLAPIC needs to inject an interrupt, for example:
- from LVT like LAPIC timer
- from vIOAPIC for a pass-thru device interrupt
- from an emulated device for a MSI
These APIs will finish by making a request for ACRN_REQUEST_EVENT.
-
static void
vlapic_intr_level
(struct acrn_vcpu *vcpu, uint32_t vector)¶ Pend level-trigger mode virtual interrupt to vCPU.
- Parameters
vcpu
: Pointer to target vCPU data structurevector
: Vector to be injected.
-
static void
vlapic_intr_edge
(struct acrn_vcpu *vcpu, uint32_t vector)¶ Pend edge-trigger mode virtual interrupt to vCPU.
- Parameters
vcpu
: Pointer to target vCPU data structurevector
: Vector to be injected.
-
void
vlapic_intr_accepted
(struct acrn_vlapic *vlapic, uint32_t vector)¶ Accept virtual interrupt.
Transition ‘vector’ from IRR to ISR. This function is called with the vector returned by ‘vlapic_pending_intr()’ when the guest is able to accept this interrupt (i.e. RFLAGS.IF = 1 and no conditions exist that block interrupt delivery).
- Return
- None
- Pre
- vlapic != NULL
- Parameters
vlapic
: Pointer to target vLAPIC data structurevector
: Target virtual interrupt vector
-
int
vlapic_pending_intr
(const struct acrn_vlapic *vlapic, uint32_t *vecptr)¶ Get pending virtual interrupts for vLAPIC.
- Remark
- The vector does not automatically transition to the ISR as a result of calling this function.
- Parameters
vlapic
: Pointer to target vLAPIC data structurevecptr
: Pointer to vector buffer and will be filled with eligible vector if any.
- Return Value
0
: There is no eligible pending vector.1
: There is pending vector.
-
int32_t
vlapic_set_local_intr
(struct acrn_vm *vm, uint16_t vcpu_id_arg, uint32_t vector)¶ Triggers LAPIC local interrupt(LVT).
- Pre
- vm != NULL
- Parameters
vm
: Pointer to VM data structurevcpu_id_arg
: ID of vCPU, BROADCAST_CPU_ID means triggering interrupt to all vCPUs.vector
: Vector to be fired.
- Return Value
0
: on success.-EINVAL
: on error that vcpu_id_arg or vector is invalid.
-
int32_t
vlapic_intr_msi
(struct acrn_vm *vm, uint64_t addr, uint64_t msg)¶ Inject MSI to target VM.
- Pre
- vm != NULL
- Parameters
vm
: Pointer to VM data structureaddr
: MSI address.msg
: MSI data.
- Return Value
0
: on success.-1
: on error that addr is invalid.
-
void
vlapic_post_intr
(uint16_t dest_pcpu_id)¶ Send notification vector to target pCPU.
If APICv Posted-Interrupt is enabled and target pCPU is in non-root mode, pCPU will sync pending virtual interrupts from PIR to vIRR automatically, without VM exit. If pCPU in root-mode, virtual interrupt will be injected in next VM entry.
- Return
- None
- Parameters
dest_pcpu_id
: Target CPU ID.
-
uint64_t
apicv_get_pir_desc_paddr
(struct acrn_vcpu *vcpu)¶ Get physical address to PIR description.
If APICv Posted-interrupt is supported, this address will be configured to VMCS “Posted-interrupt descriptor address” field.
- Return
- physicall address to PIR
- Pre
- vcpu != NULL
- Parameters
vcpu
: Target vCPU
EOI processing¶
EOI virtualization is enabled if APICv virtual interrupt delivery is supported. Except for level triggered interrupts, VM will not exit in case of EOI.
In case of no APICv virtual interrupt delivery support, vLAPIC requires EOI from Guest OS whenever a vector was acknowledged and processed by guest. vLAPIC behavior is the same as HW LAPIC. Once an EOI is received, it clears the highest priority vector in ISR and TMR, and updates PPR status. vLAPIC will then notify vIOAPIC if the corresponding vector comes from vIOAPIC. This only occurs for the level triggered interrupts.
Virtual IOAPIC¶
vIOAPIC is emulated by HV when Guest accesses MMIO GPA range: 0xFEC00000-0xFEC01000. vIOAPIC for SOS should match to the native HW IOAPIC Pin numbers. vIOAPIC for UOS provides 48 Pins. As the vIOAPIC is always associated with vLAPIC, the virtual interrupt injection from vIOAPIC will finally trigger a request for vLAPIC event by calling vLAPIC APIs.
Supported APIs:
-
void
vioapic_set_irq
(struct acrn_vm *vm, uint32_t irq, uint32_t operation)¶ Set vIOAPIC IRQ line status.
- Pre
- irq < vioapic_pincount(vm)
- Return
- None
- Parameters
vm
: Pointer to target VMirq
: Target IRQ numberoperation
: Action options: GSI_SET_HIGH/GSI_SET_LOW/ GSI_RAISING_PULSE/GSI_FALLING_PULSE
-
void
vioapic_set_irq_nolock
(struct acrn_vm *vm, uint32_t irq, uint32_t operation)¶ Set vIOAPIC IRQ line status.
Similar with vioapic_set_irq(),but would not make sure operation be done with ioapic lock.
- Pre
- irq < vioapic_pincount(vm)
- Return
- None
- Parameters
vm
: Pointer to target VMirq
: Target IRQ numberoperation
: Action options: GSI_SET_HIGH/GSI_SET_LOW/ GSI_RAISING_PULSE/GSI_FALLING_PULSE
Virtual PIC¶
vPIC is required for TSC calculation. Normally UOS will boot with vIOAPIC and vPIC as the source of external interrupts to Guest. On every VM Exit, HV will check if there are any pending external PIC interrupts. vPIC APIs usage are similar to vIOAPIC.
ACRN hypervisor emulates a vPIC for each VM based on IO range 0x20~0x21, 0xa0~0xa1 and 0x4d0~0x4d1.
If an interrupt source from vPIC need to inject an interrupt, the following APIs need be called, which will finally make a request for ACRN_REQUEST_EXTINT or ACRN_REQUEST_EVENT:
-
void
vpic_set_irq
(struct acrn_vm *vm, uint32_t irq, uint32_t operation)¶ Set vPIC IRQ line status.
- Return
- None
- Parameters
vm
: Pointer to target VMirq
: Target IRQ numberoperation
: action options:GSI_SET_HIGH/GSI_SET_LOW/ GSI_RAISING_PULSE/GSI_FALLING_PULSE
The following APIs are used to query the vector needed to be injected and ACK the service (means move the interrupt from request service - IRR to in service - ISR):
-
void
vpic_pending_intr
(struct acrn_vm *vm, uint32_t *vecptr)¶ Get pending virtual interrupts for vPIC.
- Return
- None
- Parameters
vm
: Pointer to target VMvecptr
: Pointer to vector buffer and will be filled with eligible vector if any.
-
void
vpic_intr_accepted
(struct acrn_vm *vm, uint32_t vector)¶ Accept virtual interrupt for vPIC.
- Return
- None
- Pre
- vm != NULL
- Parameters
vm
: Pointer to target VMvector
: Target virtual interrupt vector
Virtual Exception¶
When doing emulation, an exception may need to be triggered in hypervisor, for example:
- if guest accesses an invalid vMSR register,
- hypervisor needs to inject a #GP, or
- during instruction emulation, an instruction fetch may access a non-exist page from rip_gva, at that time a #PF need be injected.
ACRN hypervisor implements virtual exception injection using these APIs:
-
int
vcpu_queue_exception
(struct acrn_vcpu *vcpu, uint32_t vector, uint32_t err_code)¶ Queue exception to guest.
This exception may be injected immediately or later, depends on the exeception class.
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.vector
: Vector of the exeception.err_code
: Error Code to be injected.
- Return Value
0
: on success-EINVAL
: on error that vector is invalid.
-
void
vcpu_inject_extint
(struct acrn_vcpu *vcpu)¶ Inject external interrupt to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.
-
void
vcpu_inject_nmi
(struct acrn_vcpu *vcpu)¶ Inject NMI to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.
-
void
vcpu_inject_gp
(struct acrn_vcpu *vcpu, uint32_t err_code)¶ Inject general protection exeception(GP) to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.err_code
: Error Code to be injected.
-
void
vcpu_inject_pf
(struct acrn_vcpu *vcpu, uint64_t addr, uint32_t err_code)¶ Inject page fault exeception(PF) to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.addr
: Address that result in PF.err_code
: Error Code to be injected.
-
void
vcpu_inject_ud
(struct acrn_vcpu *vcpu)¶ Inject invalid opcode exeception(UD) to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.
-
void
vcpu_inject_ac
(struct acrn_vcpu *vcpu)¶ Inject alignment check exeception(AC) to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.
-
void
vcpu_inject_ss
(struct acrn_vcpu *vcpu)¶ Inject stack fault exeception(SS) to guest.
- Return
- None
- Pre
- vcpu != NULL
- Parameters
vcpu
: Pointer to vCPU.
ACRN hypervisor uses the vcpu_inject_gp/vcpu_inject_pf functions to queue exception request, and follows SDM vol3 - 6.15, Table 6-5 to generate double fault if the condition is met.
Virtual Interrupt Injection¶
The source of virtual interrupts comes from either DM or assigned devices.
- For SOS assigned devices: as all devices are assigned to SOS directly. Whenever there is a device’s physical interrupt, the corresponding virtual interrupts are injected to SOS via vLAPIC/vIOAPIC. SOS does not use vPIC and does not have emulated devices. See section 3.8.5 Device assignment.
- For UOS assigned devices: only PCI devices could be assigned to UOS. Virtual interrupt injection follows the same way as SOS. A virtual interrupt injection operation is triggered when a device’s physical interrupt occurs.
- For UOS emulated devices: DM (acrn-dm) is responsible for UOS emulated devices’ interrupt lifecycle management. DM knows when an emulated device needs to assert a virtual IOPAIC/PIC Pin or needs to send a virtual MSI vector to Guest. These logic is entirely handled by DM.
Before APICv virtual interrupt delivery, a virtual interrupt can be
injected only if guest interrupt is allowed. There are many cases
that Guest RFLAGS.IF
gets cleared and it would not accept any further
interrupts. HV will check for the available Guest IRQ windows before
injection.
NMI is unmasked interrupt and its injection is always allowed
regardless of the guest IRQ window status. If current IRQ
windows is not present, HV would enable
MSR_IA32_VMX_PROCBASED_CTLS_IRQ_WIN (PROCBASED_CTRL.bit[2])
and
VM Enter directly. The injection will be done on next VM Exit once Guest
issues STI (GuestRFLAG.IF=1)
.
Data structures and interfaces¶
There is no data structure exported to the other components in the hypervisor for virtual interrupts. The APIs listed in the previous sections are meant to be called whenever a virtual interrupt should be injected or acknowledged.