x86: vmm: support dst_shorthand APIC IPIs
There are a couple bits in the ICR that specify things like 'broadcast'
and 'self'. Linux uses these if nr_cpus > 2.
Note that we have to do the check in the kernel too. The kernel gets
first dibs on resolving an vmexit. That took a couple hours to figure
out...
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/arch/x86/vmm/vmm.c b/kern/arch/x86/vmm/vmm.c
index c42fe1d..74082c9 100644
--- a/kern/arch/x86/vmm/vmm.c
+++ b/kern/arch/x86/vmm/vmm.c
@@ -378,11 +378,18 @@
{
uint32_t destination = tf->tf_rdx & 0xffffffff;
uint8_t vector = tf->tf_rax & 0xff;
- uint8_t type = (tf->tf_rax >> 8) & 0x7;
+ uint8_t del_mode = (tf->tf_rax >> 8) & 0x7;
+ uint8_t dst_mode = (tf->tf_rax >> 11) & 0x1;
struct guest_pcore *gpc;
int target_coreid;
+ uint8_t dst_shorthand = (tf->tf_rax >> 18) & 0x3;
- if (type != 0 || destination == 0xffffffff)
+ if (dst_mode || del_mode != 0 /* Fixed */)
+ return false;
+ /* dst_shorthand includes broadcasts, but also includes a self-ipi. the
+ * guest ought to be using the self-ipi register instead of the
+ * shorthand. */
+ if (dst_shorthand || destination == 0xffffffff)
return false;
gpc = lookup_guest_pcore(current, destination);
if (!gpc)
diff --git a/user/vmm/vmxmsr.c b/user/vmm/vmxmsr.c
index 03b8a13..a6e7ee0 100644
--- a/user/vmm/vmxmsr.c
+++ b/user/vmm/vmxmsr.c
@@ -147,34 +147,93 @@
return 0;
}
-static int apic_icr_write(struct guest_thread *vm_thread,
+static int irq_guest_core_verbose(struct virtual_machine *vm,
+ unsigned int gpcoreid, unsigned int vector)
+{
+ if (!vmm_interrupt_guest(vm, gpcoreid, vector))
+ return 0;
+ fprintf(stderr, "Failed to send IRQ %d to cpu %d: %s\n", vector,
+ gpcoreid, errstr());
+ return -1;
+}
+
+/* Note that the kernel has first dibs on handling ICR writes, and it'll try to
+ * send an IPI if there is a single target. emsr_lapic_icr_write() */
+static int apic_icr_write(struct guest_thread *gth,
struct vmm_gpcore_init *gpci)
{
- /* We currently only handle physical destinations.
- * TODO(ganshun): Support logical destinations if needed. */
- struct virtual_machine *vm = gth_to_vm(vm_thread);
- struct vm_trapframe *vm_tf = gth_to_vmtf(vm_thread);
+ struct virtual_machine *vm = gth_to_vm(gth);
+ struct vm_trapframe *vm_tf = gth_to_vmtf(gth);
uint32_t destination = vm_tf->tf_rdx & 0xffffffff;
uint8_t vector = vm_tf->tf_rax & 0xff;
- uint8_t type = (vm_tf->tf_rax >> 8) & 0x7;
+ uint8_t del_mode = (vm_tf->tf_rax >> 8) & 0x7;
+ uint8_t dst_mode = (vm_tf->tf_rax >> 11) & 0x1;
int apic_offset = vm_tf->tf_rcx & 0xff;
+ uint8_t dst_shorthand = (vm_tf->tf_rax >> 18) & 0x3;
- if (destination >= vm->nr_gpcs && destination != 0xffffffff) {
- fprintf(stderr, "UNSUPPORTED DESTINATION 0x%02x!\n",
- destination);
+ bool broadcast = false;
+ bool self = false;
+ int not_ok = 0;
+
+ /* We currently only handle physical destinations. */
+ if (dst_mode) {
+ fprintf(stderr,
+ "x2APIC ICR unsupported logical destination mode\n");
return SHUTDOWN_UNHANDLED_EXIT_REASON;
}
- switch (type) {
- case 0:
- /* Send IPI */
- if (destination == 0xffffffff) {
- /* Broadcast */
- for (int i = 0; i < vm->nr_gpcs; i++)
- vmm_interrupt_guest(vm, i, vector);
+
+ /* You'd think they'd just say "bit 0 is self, bit 1 is broadcast", but
+ * it's inverted... */
+ switch (dst_shorthand) {
+ case 0x0:
+ broadcast = false;
+ self = false;
+ break;
+ case 0x1:
+ broadcast = false;
+ self = true;
+ break;
+ case 0x2:
+ broadcast = true;
+ self = true;
+ break;
+ case 0x3:
+ broadcast = true;
+ self = false;
+ break;
+ }
+
+ /* Let them override the shorthand with the old eight Fs */
+ if (destination == 0xffffffff) {
+ broadcast = true;
+ self = true;
+ }
+
+ if (destination >= vm->nr_gpcs && !(self && broadcast)) {
+ fprintf(stderr, "Bad APIC dest 0x%02x, shorthand 0x%x!\n",
+ destination, dst_shorthand);
+ return SHUTDOWN_UNHANDLED_EXIT_REASON;
+ }
+
+ switch (del_mode) {
+ case 0: /* Fixed */
+ if (broadcast) {
+ for (int i = 0; i < vm->nr_gpcs; i++) {
+ if (i == gth->gpc_id && !self)
+ continue;
+ not_ok |= irq_guest_core_verbose(vm, i, vector);
+ }
} else {
- /* Send individual IPI */
- vmm_interrupt_guest(vm, destination, vector);
+ if (self)
+ not_ok = irq_guest_core_verbose(vm, gth->gpc_id,
+ vector);
+ else
+ not_ok = irq_guest_core_verbose(vm, destination,
+ vector);
}
+ if (not_ok)
+ return SHUTDOWN_UNHANDLED_EXIT_REASON;
+
break;
case 0x5: /* INIT */
case 0x6: /* SIPI */
@@ -182,7 +241,7 @@
* allowed to try to make them for now. */
break;
default:
- fprintf(stderr, "Unsupported IPI type %d!\n", type);
+ fprintf(stderr, "Unsupported IPI del_mode %d!\n", del_mode);
break;
}