diff options
author | James Morris <jmorris@namei.org> | 2009-05-08 17:56:47 +1000 |
---|---|---|
committer | James Morris <jmorris@namei.org> | 2009-05-08 17:56:47 +1000 |
commit | d254117099d711f215e62427f55dfb8ebd5ad011 (patch) | |
tree | 0848ff8dd74314fec14a86497f8d288c86ba7c65 /drivers/lguest/x86/core.c | |
parent | integrity: remove __setup auditing msgs (diff) | |
parent | NOMMU: Don't check vm_region::vm_start is page aligned in add_nommu_region() (diff) | |
download | linux-dev-d254117099d711f215e62427f55dfb8ebd5ad011.tar.xz linux-dev-d254117099d711f215e62427f55dfb8ebd5ad011.zip |
Merge branch 'master' into next
Diffstat (limited to 'drivers/lguest/x86/core.c')
-rw-r--r-- | drivers/lguest/x86/core.c | 71 |
1 files changed, 68 insertions, 3 deletions
diff --git a/drivers/lguest/x86/core.c b/drivers/lguest/x86/core.c index bf7942327bda..1a83910f674f 100644 --- a/drivers/lguest/x86/core.c +++ b/drivers/lguest/x86/core.c @@ -290,6 +290,62 @@ static int emulate_insn(struct lg_cpu *cpu) return 1; } +/* Our hypercalls mechanism used to be based on direct software interrupts. + * After Anthony's "Refactor hypercall infrastructure" kvm patch, we decided to + * change over to using kvm hypercalls. + * + * KVM_HYPERCALL is actually a "vmcall" instruction, which generates an invalid + * opcode fault (fault 6) on non-VT cpus, so the easiest solution seemed to be + * an *emulation approach*: if the fault was really produced by an hypercall + * (is_hypercall() does exactly this check), we can just call the corresponding + * hypercall host implementation function. + * + * But these invalid opcode faults are notably slower than software interrupts. + * So we implemented the *patching (or rewriting) approach*: every time we hit + * the KVM_HYPERCALL opcode in Guest code, we patch it to the old "int 0x1f" + * opcode, so next time the Guest calls this hypercall it will use the + * faster trap mechanism. + * + * Matias even benchmarked it to convince you: this shows the average cycle + * cost of a hypercall. For each alternative solution mentioned above we've + * made 5 runs of the benchmark: + * + * 1) direct software interrupt: 2915, 2789, 2764, 2721, 2898 + * 2) emulation technique: 3410, 3681, 3466, 3392, 3780 + * 3) patching (rewrite) technique: 2977, 2975, 2891, 2637, 2884 + * + * One two-line function is worth a 20% hypercall speed boost! + */ +static void rewrite_hypercall(struct lg_cpu *cpu) +{ + /* This are the opcodes we use to patch the Guest. The opcode for "int + * $0x1f" is "0xcd 0x1f" but vmcall instruction is 3 bytes long, so we + * complete the sequence with a NOP (0x90). */ + u8 insn[3] = {0xcd, 0x1f, 0x90}; + + __lgwrite(cpu, guest_pa(cpu, cpu->regs->eip), insn, sizeof(insn)); + /* The above write might have caused a copy of that page to be made + * (if it was read-only). We need to make sure the Guest has + * up-to-date pagetables. As this doesn't happen often, we can just + * drop them all. */ + guest_pagetable_clear_all(cpu); +} + +static bool is_hypercall(struct lg_cpu *cpu) +{ + u8 insn[3]; + + /* This must be the Guest kernel trying to do something. + * The bottom two bits of the CS segment register are the privilege + * level. */ + if ((cpu->regs->cs & 3) != GUEST_PL) + return false; + + /* Is it a vmcall? */ + __lgread(cpu, insn, guest_pa(cpu, cpu->regs->eip), sizeof(insn)); + return insn[0] == 0x0f && insn[1] == 0x01 && insn[2] == 0xc1; +} + /*H:050 Once we've re-enabled interrupts, we look at why the Guest exited. */ void lguest_arch_handle_trap(struct lg_cpu *cpu) { @@ -337,7 +393,7 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) break; case 32 ... 255: /* These values mean a real interrupt occurred, in which case - * the Host handler has already been run. We just do a + * the Host handler has already been run. We just do a * friendly check if another process should now be run, then * return to run the Guest again */ cond_resched(); @@ -347,6 +403,15 @@ void lguest_arch_handle_trap(struct lg_cpu *cpu) * up the pointer now to indicate a hypercall is pending. */ cpu->hcall = (struct hcall_args *)cpu->regs; return; + case 6: + /* kvm hypercalls trigger an invalid opcode fault (6). + * We need to check if ring == GUEST_PL and + * faulting instruction == vmcall. */ + if (is_hypercall(cpu)) { + rewrite_hypercall(cpu); + return; + } + break; } /* We didn't handle the trap, so it needs to go to the Guest. */ @@ -503,8 +568,8 @@ void __exit lguest_arch_host_fini(void) int lguest_arch_do_hcall(struct lg_cpu *cpu, struct hcall_args *args) { switch (args->arg0) { - case LHCALL_LOAD_GDT: - load_guest_gdt(cpu, args->arg1, args->arg2); + case LHCALL_LOAD_GDT_ENTRY: + load_guest_gdt_entry(cpu, args->arg1, args->arg2, args->arg3); break; case LHCALL_LOAD_IDT_ENTRY: load_guest_idt_entry(cpu, args->arg1, args->arg2, args->arg3); |