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;
 	}