[PATCH] x86: let userspace inject interrupts into the local APIC

From: Paolo Bonzini
Date: Tue Mar 19 2013 - 11:51:35 EST


There is no way for userspace to inject interrupts into a VCPU's
local APIC, which is important in order to inject INITs coming from
the chipset. KVM_INTERRUPT is currently disabled when the in-kernel
local APIC is used, so we can repurpose it. The shorthand destination
field must contain APIC_DEST_SELF, which has a double effect: first,
the ICR2 register is not used and the 32-bit field of KVM_INTERRUPT is
enough; second, it ensures that the valid range of the irq field is
distinct in the userspace-APIC and kernel-APIC cases.

Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
Documentation/virtual/kvm/api.txt | 13 ++++++++++---
arch/x86/kvm/lapic.c | 20 +++++++++++++++-----
arch/x86/kvm/lapic.h | 1 +
arch/x86/kvm/x86.c | 6 +++---
4 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/Documentation/virtual/kvm/api.txt b/Documentation/virtual/kvm/api.txt
index 4237c27..a69706b 100644
--- a/Documentation/virtual/kvm/api.txt
+++ b/Documentation/virtual/kvm/api.txt
@@ -396,8 +396,8 @@ Type: vcpu ioctl
Parameters: struct kvm_interrupt (in)
Returns: 0 on success, -1 on error

-Queues a hardware interrupt vector to be injected. This is only
-useful if in-kernel local APIC or equivalent is not used.
+Queues a hardware interrupt vector to be injected into either the VCPU
+or into the in-kernel local APIC or equivalent (if in use).

/* for KVM_INTERRUPT */
struct kvm_interrupt {
@@ -407,7 +407,14 @@ struct kvm_interrupt {

X86:

-Note 'irq' is an interrupt vector, not an interrupt pin or line.
+If the in-kernel local APIC is enabled, 'irq' is sent to the local APIC
+as if it were written to the ICR register, except that it is not reflected
+into ICR if the guest reads it. The destination (bits 18 and 19) must be
+set to APIC_DEST_SELF.
+
+If the in-kernel local APIC is disabled, 'irq' is an interrupt vector (not
+an interrupt pin or line) that is injected synchronously into the VCPU.
+

PPC:

diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index a8e9369..efb67f7 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -826,12 +826,9 @@ void kvm_apic_set_eoi_accelerated(struct kvm_vcpu *vcpu, int vector)
}
EXPORT_SYMBOL_GPL(kvm_apic_set_eoi_accelerated);

-static void apic_send_ipi(struct kvm_lapic *apic)
+static void apic_send_ipi(struct kvm_lapic *apic, u32 icr_low, u32 icr_high)
{
- u32 icr_low = kvm_apic_get_reg(apic, APIC_ICR);
- u32 icr_high = kvm_apic_get_reg(apic, APIC_ICR2);
struct kvm_lapic_irq irq;
-
irq.vector = icr_low & APIC_VECTOR_MASK;
irq.delivery_mode = icr_low & APIC_MODE_MASK;
irq.dest_mode = icr_low & APIC_DEST_MASK;
@@ -855,6 +852,16 @@ static void apic_send_ipi(struct kvm_lapic *apic)
kvm_irq_delivery_to_apic(apic->vcpu->kvm, apic, &irq);
}

+int kvm_lapic_ioctl_interrupt(struct kvm_vcpu *vcpu, u32 value)
+{
+ if ((value & APIC_SHORT_MASK) != APIC_DEST_SELF)
+ return -EINVAL;
+
+ apic_send_ipi(vcpu->arch.apic, value, 0);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(kvm_lapic_ioctl_interrupt);
+
static u32 apic_get_tmcct(struct kvm_lapic *apic)
{
ktime_t remaining;
@@ -1155,7 +1162,10 @@ static int apic_reg_write(struct kvm_lapic *apic, u32 reg, u32 val)
case APIC_ICR:
/* No delay here, so we always clear the pending bit */
apic_set_reg(apic, APIC_ICR, val & ~(1 << 12));
- apic_send_ipi(apic);
+ apic_send_ipi(apic,
+ kvm_apic_get_reg(apic, APIC_ICR),
+ kvm_apic_get_reg(apic, APIC_ICR2));
+
break;

case APIC_ICR2:
diff --git a/arch/x86/kvm/lapic.h b/arch/x86/kvm/lapic.h
index 2c721b9..0631b5c 100644
--- a/arch/x86/kvm/lapic.h
+++ b/arch/x86/kvm/lapic.h
@@ -49,6 +49,7 @@ void kvm_lapic_reset(struct kvm_vcpu *vcpu);
u64 kvm_lapic_get_cr8(struct kvm_vcpu *vcpu);
void kvm_lapic_set_tpr(struct kvm_vcpu *vcpu, unsigned long cr8);
void kvm_lapic_set_eoi(struct kvm_vcpu *vcpu);
+int kvm_lapic_ioctl_interrupt(struct kvm_vcpu *vcpu, u32 value);
void kvm_lapic_set_base(struct kvm_vcpu *vcpu, u64 value);
u64 kvm_lapic_get_base(struct kvm_vcpu *vcpu);
void kvm_apic_set_version(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 3e0a8ba..ab4a401 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2703,11 +2703,11 @@ static int kvm_vcpu_ioctl_set_lapic(struct kvm_vcpu *vcpu,
static int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu,
struct kvm_interrupt *irq)
{
- if (irq->irq >= KVM_NR_INTERRUPTS)
- return -EINVAL;
if (irqchip_in_kernel(vcpu->kvm))
- return -ENXIO;
+ return kvm_lapic_ioctl_interrupt(vcpu, irq->irq);

+ if (irq->irq >= KVM_NR_INTERRUPTS)
+ return -EINVAL;
kvm_queue_interrupt(vcpu, irq->irq, false);
kvm_make_request(KVM_REQ_EVENT, vcpu);

--
1.8.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/