vm io: reset on an outb to cf9
authorRonald G. Minnich <rminnich@gmail.com>
Wed, 6 Sep 2017 17:43:06 +0000 (10:43 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 7 Sep 2017 14:23:13 +0000 (10:23 -0400)
On an outb of (1 << 2) to cf9, exit the vm, since that is the reset register.

While there are some bits in the control register that affect
the type of reset, they're not important in a VM since they relate
to hardware that doesn't exist. We can just reset if bit 2 is a 1.

Why cf9? Why do IO at all? There are good arguments that this could be
a vmcall, but what's nice about cf9 is that it's standard, and has
been forever. Further, you can cause this reset to happen easily from
user mode. And that's very nice.

This is tested and working for any value with bit 2 clear (no reset)
and any value with bit 2 set (reset). Where was it all my life?

The following simple handy dandy tool is very useful on linux.

int main(int argc, char *argv[])
{
int i = 6;

if (argc > 1)
i = strtoull(argv[1], 0, 0);
iopl(3);
outb(i, 0xcf9);
exit(0)
}

// on linux:
// cc -static -o cf9 cf9.c

Change-Id: I727102a1187859d8c800fcce46b34fb3524f2b0e
Signed-off-by: Ronald G. Minnich <rminnich@gmail.com>
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/vmm/io.c

index a640c27..bec6d8c 100644 (file)
@@ -123,12 +123,14 @@ int io(struct guest_thread *vm_thread)
        uint16_t *ip16;
        uintptr_t ip;
        uint32_t edx;
+       uint32_t eax;
        struct vm_trapframe *vm_tf = &(vm_thread->uthread.u_ctx.tf.vm_tf);
 
        /* Get the RIP of the io access. */
        if (rippa(vm_thread, (uint64_t *)&ip))
                return VM_PAGE_FAULT;
        edx = vm_tf->tf_rdx;
+       eax = vm_tf->tf_rax;
        ip8 = (void *)ip;
        ip16 = (void *)ip;
        //printf("io: ip16 %p\n", *ip16, edx);
@@ -176,6 +178,21 @@ int io(struct guest_thread *vm_thread)
                        //printf("ignoring write to cfc ");
                        return 0;
                }
+               if (edx == 0xcf9) {
+                       // on real hardware, an outb to 0xcf9 with bit 2 set is
+                       // about as hard a reset as you can get. It yanks the
+                       // reset on everything, including all the cores.  It
+                       // usually happens after the kernel has done lots of work
+                       // to carefully quiesce the machine but, once it happens,
+                       // game is over. Hence, an exit(0) is most appropriate,
+                       // since it's not an error.
+                       if (eax & (1 << 2)) {
+                               printf("outb to PCI reset port with bit 2 set: time to die\n");
+                               exit(0);
+                       }
+                       return 0;
+               }
+
                /* Another case where we print a message but it's not an error. */
                printf("out al, dx: unhandled IO address dx @%p is 0x%x\n", ip8, edx);
                return 0;