Skip to contentSkip to conversation

uq/master: TPR access optimization for Windows guests

PATCH In review Jan Kiszka 3 participants 19 messages Feb 5, 2012 – Feb 9, 2012

Subsystems (2)
lore
Share & tools
lore

Jan Kiszka

author
#0 lore raw

cover uq/master: TPR access optimization for Windows guests

And yet another qemu-kvm piece: Windows 32-bit guests make heavy use of the MMIO-based TPR register. On older Intel CPUs and current AMDs, this causes significant KVM vmexit rates as the register needs to be trapped by the hypervisor.

This series introduces a workaround for this hardware limitation: A paravirtual APIC extension, based on guest extension using an options ROM and a special VAPIC device that patches TPR accesses of Windows to call into the option ROM instead. See patch 3 for details.

The speedup is noteworthy: I measured 15 vs. 45 s XP bootup time (disk cached) with the VAPIC enabled vs. disabled on an Intel CPU with flexpriority=0.

Therefore, the VAPI is enabled by default, even in TCG mode. Here, no speedup can be achieved as the emulation overhead of the TPR register is marginal compared to instruction emulation. On the other hand, enabling the optimization causes not measurable slowdown of Windows guests.

The patches depend on the follow upstream or uq/master patches:
- kvm: Implement kvm_irqchip_in_kernel
http://thread.gmane.org/gmane.comp.emulators.qemu/134399
- KVM: Fix breakages of QOM conversion
http://permalink.gmane.org/gmane.comp.emulators.qemu/134844

It is based on upstream commit cb437e48ab (before the VGA breakage). The series plus dependencies is also available at

git://git.kiszka.org/qemu-kvm.git queues/kvm-tpr

Please review carefully.

Jan Kiszka (6):
target-i386: Add infrastructure for reporting TPR MMIO accesses
kvmvapic: Add option ROM
kvmvapic: Introduce TPR access optimization for Windows guests
kvmvapic: Simplify mp/up_set_tpr
optionsrom: Reserve space for checksum
kvmvapic: Use optionrom helpers

 .gitignore                    |    1 +
 Makefile                      |    2 +-
 Makefile.target               |    3 +-
 cpu-all.h                     |    3 +-
 hw/apic.c                     |  126 +++++++-
 hw/apic.h                     |    2 +
 hw/apic_common.c              |   68 ++++-
 hw/apic_internal.h            |   27 ++
 hw/kvm/apic.c                 |   32 ++
 hw/kvmvapic.c                 |  738 +++++++++++++++++++++++++++++++++++++++++
 pc-bios/optionrom/Makefile    |    2 +-
 pc-bios/optionrom/kvmvapic.S  |  335 +++++++++++++++++++
 pc-bios/optionrom/optionrom.h |    3 +-
 target-i386/cpu.h             |    9 +
 target-i386/helper.c          |   19 +
 target-i386/kvm.c             |   24 ++-
 16 files changed, 1374 insertions(+), 20 deletions(-)
 create mode 100644 hw/kvmvapic.c
 create mode 100644 pc-bios/optionrom/kvmvapic.S
Signature · 1 line
1.7.3.4
author
#1 lore raw

PATCH 1/6 target-i386: Add infrastructure for reporting TPR MMIO accesses

From: Jan Kiszka <[email protected]>

This will allow the APIC core to file a TPR access report. Depending on the accelerator and kernel irqchip mode, it will either be delivered right away or queued for later reporting.

In TCG mode, we can restart the triggering instruction and can therefore forward the event directly. KVM does not allows us to restart, so we postpone the delivery of events recording in the user space APIC until the current instruction is completed.

Note that KVM without in-kernel irqchip will report the address after the instruction that triggered a write access. In contrast, read accesses will return the precise information.

---
 cpu-all.h            |    3 ++-
 hw/apic.h            |    2 ++
 hw/apic_common.c     |    4 ++++
 target-i386/cpu.h    |    9 +++++++++
 target-i386/helper.c |   19 +++++++++++++++++++
 target-i386/kvm.c    |   24 ++++++++++++++++++++++--
 6 files changed, 58 insertions(+), 3 deletions(-)
cpu-all.h +2 -1
index e2c3c49..80e6d42 100644
@@ -375,8 +375,9 @@ DECLARE_TLS(CPUState *,cpu_single_env);
 #define CPU_INTERRUPT_TGT_INT_0   0x0100
 #define CPU_INTERRUPT_TGT_INT_1   0x0400
 #define CPU_INTERRUPT_TGT_INT_2   0x0800
+#define CPU_INTERRUPT_TGT_INT_3   0x2000
 
-/* First unused bit: 0x2000.  */
+/* First unused bit: 0x4000.  */
 
 /* The set of all bits that should be masked when single-stepping.  */
 #define CPU_INTERRUPT_SSTEP_MASK \
hw/apic.h +2 -0
index a62d83b..45598bd 100644
@@ -18,6 +18,8 @@ void cpu_set_apic_tpr(DeviceState *s, uint8_t val);
 uint8_t cpu_get_apic_tpr(DeviceState *s);
 void apic_init_reset(DeviceState *s);
 void apic_sipi(DeviceState *s);
+void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip,
+                                   int access);
 
 /* pc.c */
 int cpu_is_bsp(CPUState *env);
hw/apic_common.c +4 -0
index 8373d79..588531b 100644
@@ -68,6 +68,10 @@ uint8_t cpu_get_apic_tpr(DeviceState *d)
     return s ? s->tpr >> 4 : 0;
 }
 
+void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip, int access)
+{
+}
+
 void apic_report_irq_delivered(int delivered)
 {
     apic_irq_delivered += delivered;
target-i386/cpu.h +9 -0
index 37dde79..92e9c87 100644
@@ -482,6 +482,7 @@
 #define CPU_INTERRUPT_VIRQ      CPU_INTERRUPT_TGT_INT_0
 #define CPU_INTERRUPT_INIT      CPU_INTERRUPT_TGT_INT_1
 #define CPU_INTERRUPT_SIPI      CPU_INTERRUPT_TGT_INT_2
+#define CPU_INTERRUPT_TPR       CPU_INTERRUPT_TGT_INT_3
 
 
 enum {
@@ -772,6 +773,9 @@ typedef struct CPUX86State {
     XMMReg ymmh_regs[CPU_NB_REGS];
 
     uint64_t xcr0;
+
+    target_ulong tpr_access_ip;
+    int tpr_access_type;
 } CPUX86State;
 
 CPUX86State *cpu_x86_init(const char *cpu_model);
@@ -1064,4 +1068,9 @@ void svm_check_intercept(CPUState *env1, uint32_t type);
 
 uint32_t cpu_cc_compute_all(CPUState *env1, int op);
 
+#define TPR_ACCESS_READ     0
+#define TPR_ACCESS_WRITE    1
+
+void cpu_report_tpr_access(CPUState *env, int access);
+
 #endif /* CPU_I386_H */
target-i386/helper.c +19 -0
index 2586aff..eca20cd 100644
@@ -1189,6 +1189,25 @@ void cpu_x86_inject_mce(Monitor *mon, CPUState *cenv, int bank,
         }
     }
 }
+
+void cpu_report_tpr_access(CPUState *env, int access)
+{
+    TranslationBlock *tb;
+
+    if (kvm_enabled()) {
+        cpu_synchronize_state(env);
+
+        env->tpr_access_ip = env->eip;
+        env->tpr_access_type = access;
+
+        cpu_interrupt(env, CPU_INTERRUPT_TPR);
+    } else {
+        tb = tb_find_pc(env->mem_io_pc);
+        cpu_restore_state(tb, env, env->mem_io_pc);
+
+        apic_handle_tpr_access_report(env->apic_state, env->eip, access);
+    }
+}
 #endif /* !CONFIG_USER_ONLY */
 
 static void mce_init(CPUX86State *cenv)
target-i386/kvm.c +22 -2
index 981192d..fa77f9d 100644
@@ -1635,8 +1635,10 @@ void kvm_arch_pre_run(CPUState *env, struct kvm_run *run)
     }
 
     if (!kvm_irqchip_in_kernel()) {
-        /* Force the VCPU out of its inner loop to process the INIT request */
-        if (env->interrupt_request & CPU_INTERRUPT_INIT) {
+        /* Force the VCPU out of its inner loop to process any INIT requests
+         * or pending TPR access reports. */
+        if (env->interrupt_request &
+            (CPU_INTERRUPT_INIT | CPU_INTERRUPT_TPR)) {
             env->exit_request = 1;
         }
 
@@ -1730,6 +1732,11 @@ int kvm_arch_process_async_events(CPUState *env)
         kvm_cpu_synchronize_state(env);
         do_cpu_sipi(env);
     }
+    if (env->interrupt_request & CPU_INTERRUPT_TPR) {
+        env->interrupt_request &= ~CPU_INTERRUPT_TPR;
+        apic_handle_tpr_access_report(env->apic_state, env->tpr_access_ip,
+                                      env->tpr_access_type);
+    }
 
     return env->halted;
 }
@@ -1746,6 +1753,16 @@ static int kvm_handle_halt(CPUState *env)
     return 0;
 }
 
+static int kvm_handle_tpr_access(CPUState *env)
+{
+    struct kvm_run *run = env->kvm_run;
+
+    apic_handle_tpr_access_report(env->apic_state, run->tpr_access.rip,
+                                  run->tpr_access.is_write ? TPR_ACCESS_WRITE
+                                                           : TPR_ACCESS_READ);
+    return 1;
+}
+
 int kvm_arch_insert_sw_breakpoint(CPUState *env, struct kvm_sw_breakpoint *bp)
 {
     static const uint8_t int3 = 0xcc;
@@ -1950,6 +1967,9 @@ int kvm_arch_handle_exit(CPUState *env, struct kvm_run *run)
     case KVM_EXIT_SET_TPR:
         ret = 0;
         break;
+    case KVM_EXIT_TPR_ACCESS:
+        ret = kvm_handle_tpr_access(env);
+        break;
     case KVM_EXIT_FAIL_ENTRY:
         code = run->fail_entry.hardware_entry_failure_reason;
         fprintf(stderr, "KVM: entry failed, hardware error 0x%" PRIx64 "\n",
Signature · 1 line
1.7.3.4
author
#2 lore raw

PATCH 2/6 kvmvapic: Add option ROM

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

This imports and builds the original VAPIC option ROM of qemu-kvm. Its interaction with QEMU is described in the commit that introduces the corresponding device model.

---
 .gitignore                   |    1 +
 Makefile                     |    2 +-
 pc-bios/optionrom/Makefile   |    2 +-
 pc-bios/optionrom/kvmvapic.S |  341 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 344 insertions(+), 2 deletions(-)
 create mode 100644 pc-bios/optionrom/kvmvapic.S
.gitignore +1 -0
index f5aab2c..d3b78c3 100644
@@ -75,6 +75,7 @@ pc-bios/vgabios-pq/status
 pc-bios/optionrom/linuxboot.bin
 pc-bios/optionrom/multiboot.bin
 pc-bios/optionrom/multiboot.raw
+pc-bios/optionrom/kvmvapic.bin
 .stgit-*
 cscope.*
 tags
Makefile +1 -1
index 47acf3d..c2ef135 100644
@@ -255,7 +255,7 @@ pxe-e1000.rom pxe-eepro100.rom pxe-ne2k_pci.rom \
 pxe-pcnet.rom pxe-rtl8139.rom pxe-virtio.rom \
 bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
 mpc8544ds.dtb \
-multiboot.bin linuxboot.bin \
+multiboot.bin linuxboot.bin kvmvapic.bin \
 s390-zipl.rom \
 spapr-rtas.bin slof.bin \
 palcode-clipper
pc-bios/optionrom/Makefile +1 -1
index 2caf7e6..f6b4027 100644
@@ -14,7 +14,7 @@ CFLAGS += -I$(SRC_PATH)
 CFLAGS += $(call cc-option, $(CFLAGS), -fno-stack-protector)
 QEMU_CFLAGS = $(CFLAGS)
 
-build-all: multiboot.bin linuxboot.bin
+build-all: multiboot.bin linuxboot.bin kvmvapic.bin
 
 # suppress auto-removal of intermediate files
 .SECONDARY:
pc-bios/optionrom/kvmvapic.S new file +341 -0

341 lines not on this page · raw message

Signature · 1 line
1.7.3.4
author
#3 lore raw

PATCH 3/6 kvmvapic: Introduce TPR access optimization for Windows guests

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor.

The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration. The VMState format was kept compatible, so was the ABI to the option ROM that implements the guest-side para-virtualized driver service. This enables seamless migration from qemu-kvm to upstream or, one day, between KVM and TCG mode.

The basic concept goes like this:
- VAPIC PV interface consisting of I/O port 0x7e and (for KVM in-kernel irqchip) a vmcall hypercall is registered
- VAPIC option ROM is loaded into guest
- option ROM activates TPR MMIO access reporting via port 0x7e
- TPR accesses are trapped and patched in the guest to call into option ROM instead, VAPIC support is enabled
- option ROM TPR helpers track state in memory and invoke hypercall to poll for pending IRQs if required

---
 Makefile.target    |    3 +-
 hw/apic.c          |  126 ++++++++-
 hw/apic_common.c   |   64 +++++-
 hw/apic_internal.h |   27 ++
 hw/kvm/apic.c      |   32 +++
 hw/kvmvapic.c      |  738 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 976 insertions(+), 14 deletions(-)
 create mode 100644 hw/kvmvapic.c
Makefile.target +2 -1
index 68481a3..ec7eff8 100644
@@ -230,7 +230,8 @@ obj-y += device-hotplug.o
 
 # Hardware support
 obj-i386-y += mc146818rtc.o pc.o
-obj-i386-y += sga.o apic_common.o apic.o ioapic_common.o ioapic.o piix_pci.o
+obj-i386-y += apic_common.o apic.o kvmvapic.o
+obj-i386-y += sga.o ioapic_common.o ioapic.o piix_pci.o
 obj-i386-y += vmport.o
 obj-i386-y += pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
hw/apic.c +115 -11

206 lines not on this page · raw message

hw/apic_common.c +62 -2

123 lines not on this page · raw message

hw/apic_internal.h +27 -0
index 0cab010..95cc7cf 100644
@@ -61,6 +61,9 @@
 #define APIC_SV_DIRECTED_IO             (1<<12)
 #define APIC_SV_ENABLE                  (1<<8)
 
+#define VAPIC_ENABLE_BIT                0
+#define VAPIC_ENABLE_MASK               (1 << VAPIC_ENABLE_BIT)
+
 #define MAX_APICS 255
 
 #define MSI_SPACE_SIZE                  0x100000
@@ -82,7 +85,11 @@ typedef struct APICCommonClass
     void (*init)(APICCommonState *s);
     void (*set_base)(APICCommonState *s, uint64_t val);
     void (*set_tpr)(APICCommonState *s, uint8_t val);
+    uint8_t (*get_tpr)(APICCommonState *s);
+    void (*enable_tpr_reporting)(APICCommonState *s);
+    void (*vapic_base_update)(APICCommonState *s);
     void (*external_nmi)(APICCommonState *s);
+    void (*pre_save)(APICCommonState *s);
     void (*post_load)(APICCommonState *s);
 } APICCommonClass;
 
@@ -114,9 +121,29 @@ struct APICCommonState {
     int64_t timer_expiry;
     int sipi_vector;
     int wait_for_sipi;
+
+    uint32_t vapic_control;
+    DeviceState *vapic;
+    target_phys_addr_t vapic_paddr; /* note: persistence via kvmvapic */
 };
 
+typedef struct VAPICState {
+    uint8_t tpr;
+    uint8_t isr;
+    uint8_t zero;
+    uint8_t irr;
+    uint8_t enabled;
+} QEMU_PACKED VAPICState;
+
+extern bool apic_report_tpr_access;
+
 void apic_report_irq_delivered(int delivered);
 bool apic_next_timer(APICCommonState *s, int64_t current_time);
+void apic_enable_tpr_access_reporting(DeviceState *d);
+void apic_enable_vapic(DeviceState *d, target_phys_addr_t paddr);
+void apic_poll_irq(DeviceState *d);
+
+void vapic_report_tpr_access(DeviceState *dev, void *cpu, target_ulong ip,
+                             int access);
 
 #endif /* !QEMU_APIC_INTERNAL_H */
hw/kvm/apic.c +32 -0
index dfc2ab3..326eb37 100644
@@ -92,6 +92,35 @@ static void kvm_apic_set_tpr(APICCommonState *s, uint8_t val)
     s->tpr = (val & 0x0f) << 4;
 }
 
+static uint8_t kvm_apic_get_tpr(APICCommonState *s)
+{
+    return s->tpr >> 4;
+}
+
+static void kvm_apic_enable_tpr_reporting(APICCommonState *s)
+{
+    struct kvm_tpr_access_ctl ctl = {
+        .enabled = 1
+    };
+
+    kvm_vcpu_ioctl(s->cpu_env, KVM_TPR_ACCESS_REPORTING, &ctl);
+}
+
+static void kvm_apic_vapic_base_update(APICCommonState *s)
+{
+    struct kvm_vapic_addr vapid_addr = {
+        .vapic_addr = s->vapic_paddr,
+    };
+    int ret;
+
+    ret = kvm_vcpu_ioctl(s->cpu_env, KVM_SET_VAPIC_ADDR, &vapid_addr);
+    if (ret < 0) {
+        fprintf(stderr, "KVM: setting VAPIC address failed (%s)\n",
+                strerror(-ret));
+        abort();
+    }
+}
+
 static void do_inject_external_nmi(void *data)
 {
     APICCommonState *s = data;
@@ -129,6 +158,9 @@ static void kvm_apic_class_init(ObjectClass *klass, void *data)
     k->init = kvm_apic_init;
     k->set_base = kvm_apic_set_base;
     k->set_tpr = kvm_apic_set_tpr;
+    k->get_tpr = kvm_apic_get_tpr;
+    k->enable_tpr_reporting = kvm_apic_enable_tpr_reporting;
+    k->vapic_base_update = kvm_apic_vapic_base_update;
     k->external_nmi = kvm_apic_external_nmi;
 }
 
hw/kvmvapic.c new file +738 -0

738 lines not on this page · raw message

Signature · 1 line
1.7.3.4
author
#4 lore raw

[Qemu-devel] subpages with memory region aliases (was: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests)

Avi,

Before I forget:

Quoted 16 lines · Jan Kiszka
hw/kvmvapic.c new file +16 -0
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */

hw/kvmvapic.c line 500

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Removing this alignment triggers an interesting bug in the memory layer. Haven't understood the details yet. Is subpage support supposed to work?

hw/kvmvapic.c line 506

Quoted 6 lines
hw/kvmvapic.c new file +6 -0
+
+    memory_region_init_alias(&s->rom, "kvmvapic-rom", section.mr, rom_paddr,
+                             rom_size);
+    memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);
+    s->rom_mapped_writable = true;
+}

Jan

#5 lore raw

Re: [Qemu-devel] subpages with memory region aliases

Quoted 26 lines · Jan Kiszka

Avi,

Before I forget:

Quoted 21 lines · Jan Kiszka
hw/kvmvapic.c new file +21 -0
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
Quoted 10 lines

hw/kvmvapic.c line 500

Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Removing this alignment triggers an interesting bug in the memory layer. Haven't understood the details yet. Is subpage support supposed to work?

There are plenty of restrictions wrt page size in the memory API. Some will be removed, some are enforced by hardware (for example the low bits of the guest address and host address must match).

Subpage of course works, but it can't be direct mapped.

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#6 lore raw

Re: [Qemu-devel] subpages with memory region aliases

Quoted 38 lines · Avi Kivity
Quoted 26 lines · Jan Kiszka

Avi,

Before I forget:

Quoted 21 lines · Jan Kiszka
hw/kvmvapic.c new file +21 -0
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
Quoted 10 lines

hw/kvmvapic.c line 500

Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Removing this alignment triggers an interesting bug in the memory layer. Haven't understood the details yet. Is subpage support supposed to work?

Quoted 6 lines

There are plenty of restrictions wrt page size in the memory API. Some will be removed, some are enforced by hardware (for example the low bits of the guest address and host address must match).

Subpage of course works, but it can't be direct mapped.

Does this mean you can't use them with RAM backing? Should probably be documented and caught gracefully in that case.

Jan

#7 lore raw

Re: [Qemu-devel] subpages with memory region aliases

Quoted 43 lines · Jan Kiszka
Quoted 42 lines · Avi Kivity
Quoted 26 lines · Jan Kiszka

Avi,

Before I forget:

Quoted 21 lines · Jan Kiszka
hw/kvmvapic.c new file +21 -0
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
Quoted 10 lines

hw/kvmvapic.c line 500

Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Removing this alignment triggers an interesting bug in the memory layer. Haven't understood the details yet. Is subpage support supposed to work?

There are plenty of restrictions wrt page size in the memory API. Some will be removed, some are enforced by hardware (for example the low bits of the guest address and host address must match).

Quoted 5 lines
Quoted 2 lines

Subpage of course works, but it can't be direct mapped.

Does this mean you can't use them with RAM backing? Should probably be documented and caught gracefully in that case.

Since a few monthss ago, you can (56384e8b). But it will be handled as mmio, of course.

Signature · 1 line
error compiling committee.c: too many arguments to function
#8 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

In reply to Jan Kiszka
Quoted 10 lines · Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor.

The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration.

However, it's presented as a rewrite instead of a series of changes, so we can't see what the changes are.

Having said that, the end result is way, way better than the original.

Quoted 40 lines

The
VMState format was kept compatible, so was the ABI to the option ROM that implements the guest-side para-virtualized driver service. This enables seamless migration from qemu-kvm to upstream or, one day, between KVM and TCG mode.

The basic concept goes like this:
- VAPIC PV interface consisting of I/O port 0x7e and (for KVM in-kernel irqchip) a vmcall hypercall is registered
- VAPIC option ROM is loaded into guest
- option ROM activates TPR MMIO access reporting via port 0x7e
- TPR accesses are trapped and patched in the guest to call into option ROM instead, VAPIC support is enabled
- option ROM TPR helpers track state in memory and invoke hypercall to poll for pending IRQs if required

hw/kvmvapic.c new file +23 -0
+
+static int evaluate_tpr_instruction(VAPICROMState *s, CPUState *env,
+                                    target_ulong *pip, int access)
+{
+    target_ulong addr_offset;
+    target_ulong ip = *pip;
+    uint8_t opcode[2];
+    uint32_t real_tpr_addr;
+
+    if ((ip & 0xf0000000) != 0x80000000 && (ip & 0xf0000000) != 0xe0000000) {
+        return -1;
+    }
+
+    /*
+     * Early Windows 2003 SMP initialization contains a
+     *
+     *   mov imm32, r/m32
+     *
+     * instruction that is patched by TPR optimization. The problem is that
+     * RSP, used by the patched instruction, is zero, so the guest gets a
+     * double fault and dies.
+     */
+    if (env->regs[R_ESP] == 0) {

hw/kvmvapic.c line 145

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+        return -1;
+    }
+
+    if (access == TPR_ACCESS_WRITE && kvm_enabled() &&
+        !kvm_irqchip_in_kernel()) {
+        /*
+         * KVM without TPR access reporting calls into the user space APIC on
+         * write with IP pointing after the accessing instruction. So we need
+         * to look backward to find the reason.
+         */

Why do we need to do anything at all?

I'm not sure if the ABI guarantees anything about %rip.

Quoted 48 lines
hw/kvmvapic.c new file +48 -0
+        ip -= 5;
+        if (cpu_memory_rw_debug(env, ip, opcode, 1, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] == 0xa3) {
+            addr_offset = 1;
+            goto instruction_ok;
+        }
+
+        ip--;
+        if (cpu_memory_rw_debug(env, ip, opcode, 2, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] == 0x89 && is_abs_modrm(opcode[1])) {
+            addr_offset = 2;
+            goto instruction_ok;
+        }
+
+        ip -= 4;
+        if (cpu_memory_rw_debug(env, ip, opcode, 2, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] != 0xc7 || modrm_reg(opcode[1]) != 0 ||
+            !is_abs_modrm(opcode[1])) {
+            return -1;
+        }
+        addr_offset = 2;
+    } else {
+        if (cpu_memory_rw_debug(env, ip, opcode, sizeof(opcode), 0) < 0) {
+            return -1;
+        }
+
+        switch (opcode[0]) {
+        case 0xc7: /* mov imm32, r/m32 (c7/0) */
+            if (modrm_reg(opcode[1]) != 0) {
+                return -1;
+            }
+            /* fall through */
+        case 0x89: /* mov r32 to r/m32 */
+        case 0x8b: /* mov r/m32 to r32 */
+            if (!is_abs_modrm(opcode[1])) {
+                return -1;
+            }
+            addr_offset = 2;
+            break;
+        case 0xa1: /* mov abs to eax */
+        case 0xa3: /* mov eax to abs */
+            addr_offset = 1;

hw/kvmvapic.c line 203

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+            break;
+        case 0xff: /* push r/m32 */
+            if (modrm_reg(opcode[1]) != 6 || !is_abs_modrm(opcode[1])) {
+                return -1;
+            }
+            addr_offset = 2;
+            break;
+        default:
+            return -1;
+        }

You could code this as a table of predicate functions and insn sizes. When working backward you use the sizes to offset %rip. This eliminates duplication.

Quoted 33 lines
hw/kvmvapic.c new file +33 -0
+    }
+
+instruction_ok:
+    /*
+     * Grab the virtual TPR address from the instruction
+     * and update the cached values.
+     */
+    if (cpu_memory_rw_debug(env, ip + addr_offset, (void *)&real_tpr_addr,
+                            sizeof(real_tpr_addr), 0) < 0) {
+        return -1;
+    }
+    real_tpr_addr = le32_to_cpu(real_tpr_addr);
+    if ((real_tpr_addr & 0xfff) != 0x80) {
+        return -1;
+    }
+    s->real_tpr_addr = real_tpr_addr;
+    update_guest_rom_state(s);
+
+    *pip = ip;
+    return 0;
+}
+
+
+/*
+ * Tries to read the unique processor number from the Kernel Processor Control
+ * Region (KPCR) of 32-bit Windows. Returns -1 if the KPCR cannot be accessed
+ * or is considered invalid.
+ */
+static int get_kpcr_number(CPUState *env)
+{
+    struct kpcr {
+        uint8_t  fill1[0x1c];
+        uint32_t self;

hw/kvmvapic.c line 302

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+        uint8_t  fill2[0x31];
+        uint8_t  number;
+    } QEMU_PACKED kpcr;
+
+    if (smp_cpus == 1) {
+        return 0;
+    }
+    if (cpu_memory_rw_debug(env, env->segs[R_FS].base,
+                            (void *)&kpcr, sizeof(kpcr), 0) < 0 ||
+        kpcr.self != env->segs[R_FS].base) {

Ah, so it works. We may want to do it for UP as well, as a way of verifying that the guest is compatible with these hacks.

Quoted 35 lines
hw/kvmvapic.c new file +34 -0
+        return -1;
+    }
+    return kpcr.number;
+}
 
+static void patch_instruction(VAPICROMState *s, CPUState *env, target_ulong ip)
+{
+    target_phys_addr_t paddr;
+    VAPICHandlers *handlers;
+    uint8_t opcode[2];
+    uint32_t imm32;
+
+    if (smp_cpus == 1) {
+        handlers = &s->rom_state.up;
+    } else {
+        handlers = &s->rom_state.mp;
+    }
+
+    cpu_memory_rw_debug(env, ip, opcode, sizeof(opcode), 0);
+
+    switch (opcode[0]) {
+    case 0x89: /* mov r32 to r/m32 */
+        patch_byte(env, ip, 0x50 + modrm_reg(opcode[1]));  /* push reg */
+        patch_call(s, env, ip + 1, handlers->set_tpr);
+        break;
+    case 0x8b: /* mov r/m32 to r32 */
+        patch_byte(env, ip, 0x90);
+        patch_call(s, env, ip + 1, handlers->get_tpr[modrm_reg(opcode[1])]);
+        break;
+    case 0xa1: /* mov abs to eax */
+        patch_call(s, env, ip, handlers->get_tpr[0]);
+        break;
+    case 0xa3: /* mov eax to abs */
+        patch_call(s, env, ip, handlers->set_tpr_eax);
+        break;

hw/kvmvapic.c line 382

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+    case 0xc7: /* mov imm32, r/m32 (c7/0) */
+        patch_byte(env, ip, 0x68);  /* push imm32 */
+        cpu_memory_rw_debug(env, ip + 6, (void *)&imm32, sizeof(imm32), 0);
+        cpu_memory_rw_debug(env, ip + 1, (void *)&imm32, sizeof(imm32), 1);
+        patch_call(s, env, ip + 5, handlers->set_tpr);
+        break;
+    case 0xff: /* push r/m32 */
+        patch_byte(env, ip, 0x50); /* push eax */
+        patch_call(s, env, ip + 1, handlers->get_tpr_stack);
+        break;

With a table, we could put the patch sequences there as well.

Quoted 26 lines
hw/kvmvapic.c new file +26 -0
+    default:
+        abort();
+    }
+
+    paddr = cpu_get_phys_page_debug(env, ip);
+    paddr += ip & ~TARGET_PAGE_MASK;
+    tb_invalidate_phys_page_range(paddr, paddr + 1, 1);
+}
+
+
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */

hw/kvmvapic.c line 500

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Ok. Subpages aren't broken (at least, they can't be expected to work here). Without a page aligned region you can't expect kvm to map this area.

Wrt tcg, it should work, but I don't think anyone ever tested tcg out of subpage.

hw/kvmvapic.c line 504

Quoted 4 lines
hw/kvmvapic.c new file +4 -0
+
+    memory_region_init_alias(&s->rom, "kvmvapic-rom", section.mr, rom_paddr,
+                             rom_size);
+    memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);

This is incredibly hacky, so at least the spirit of the original code is preserved.

hw/kvmvapic.c line 507

Quoted 4 lines
hw/kvmvapic.c new file +3 -0
+    s->rom_mapped_writable = true;
+}
+

Impressive refactoring overall; I thought that code was a basket case.

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#9 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 8 lines · Avi Kivity
Quoted 7 lines · Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor.

Quoted 6 lines
Quoted 3 lines

The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration.

However, it's presented as a rewrite instead of a series of changes, so we can't see what the changes are.

Yes, but the original code also depends on interfaces we don't have in upstream.

Quoted 47 lines

Having said that, the end result is way, way better than the original.

Quoted 44 lines

The
VMState format was kept compatible, so was the ABI to the option ROM that implements the guest-side para-virtualized driver service. This enables seamless migration from qemu-kvm to upstream or, one day, between KVM and TCG mode.

The basic concept goes like this:
- VAPIC PV interface consisting of I/O port 0x7e and (for KVM in-kernel irqchip) a vmcall hypercall is registered
- VAPIC option ROM is loaded into guest
- option ROM activates TPR MMIO access reporting via port 0x7e
- TPR accesses are trapped and patched in the guest to call into option ROM instead, VAPIC support is enabled
- option ROM TPR helpers track state in memory and invoke hypercall to poll for pending IRQs if required

hw/kvmvapic.c new file +27 -0
+
+static int evaluate_tpr_instruction(VAPICROMState *s, CPUState *env,
+                                    target_ulong *pip, int access)
+{
+    target_ulong addr_offset;
+    target_ulong ip = *pip;
+    uint8_t opcode[2];
+    uint32_t real_tpr_addr;
+
+    if ((ip & 0xf0000000) != 0x80000000 && (ip & 0xf0000000) != 0xe0000000) {
+        return -1;
+    }
+
+    /*
+     * Early Windows 2003 SMP initialization contains a
+     *
+     *   mov imm32, r/m32
+     *
+     * instruction that is patched by TPR optimization. The problem is that
+     * RSP, used by the patched instruction, is zero, so the guest gets a
+     * double fault and dies.
+     */
+    if (env->regs[R_ESP] == 0) {
+        return -1;
+    }
+
+    if (access == TPR_ACCESS_WRITE && kvm_enabled() &&
Quoted 8 lines

hw/kvmvapic.c line 145

Quoted 6 lines
hw/kvmvapic.c new file +6 -0
+        !kvm_irqchip_in_kernel()) {
+        /*
+         * KVM without TPR access reporting calls into the user space APIC on
+         * write with IP pointing after the accessing instruction. So we need
+         * to look backward to find the reason.
+         */

Why do we need to do anything at all?

We need to patch the causing instruction, so we have to know where it starts. Or what do you mean?

Quoted 2 lines

I'm not sure if the ABI guarantees anything about %rip.

That's indeed a point. It's likely coupled to the emulator's internals and when it calls out to user space for MMIO write. How to deal with it?

Quoted 55 lines
Quoted 55 lines
hw/kvmvapic.c new file +54 -0
+        ip -= 5;
+        if (cpu_memory_rw_debug(env, ip, opcode, 1, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] == 0xa3) {
+            addr_offset = 1;
+            goto instruction_ok;
+        }
+
+        ip--;
+        if (cpu_memory_rw_debug(env, ip, opcode, 2, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] == 0x89 && is_abs_modrm(opcode[1])) {
+            addr_offset = 2;
+            goto instruction_ok;
+        }
+
+        ip -= 4;
+        if (cpu_memory_rw_debug(env, ip, opcode, 2, 0) < 0) {
+            return -1;
+        }
+        if (opcode[0] != 0xc7 || modrm_reg(opcode[1]) != 0 ||
+            !is_abs_modrm(opcode[1])) {
+            return -1;
+        }
+        addr_offset = 2;
+    } else {
+        if (cpu_memory_rw_debug(env, ip, opcode, sizeof(opcode), 0) < 0) {
+            return -1;
+        }
+
+        switch (opcode[0]) {
+        case 0xc7: /* mov imm32, r/m32 (c7/0) */
+            if (modrm_reg(opcode[1]) != 0) {
+                return -1;
+            }
+            /* fall through */
+        case 0x89: /* mov r32 to r/m32 */
+        case 0x8b: /* mov r/m32 to r32 */
+            if (!is_abs_modrm(opcode[1])) {
+                return -1;
+            }
+            addr_offset = 2;
+            break;
+        case 0xa1: /* mov abs to eax */
+        case 0xa3: /* mov eax to abs */
+            addr_offset = 1;
+            break;
+        case 0xff: /* push r/m32 */
+            if (modrm_reg(opcode[1]) != 6 || !is_abs_modrm(opcode[1])) {
+                return -1;
+            }
+            addr_offset = 2;
Quoted 8 lines

hw/kvmvapic.c line 203

Quoted 4 lines
hw/kvmvapic.c new file +4 -0
+            break;
+        default:
+            return -1;
+        }

You could code this as a table of predicate functions and insn sizes. When working backward you use the sizes to offset %rip. This eliminates duplication.

OK, will check what it buys us.

Quoted 39 lines
Quoted 39 lines
hw/kvmvapic.c new file +38 -0
+    }
+
+instruction_ok:
+    /*
+     * Grab the virtual TPR address from the instruction
+     * and update the cached values.
+     */
+    if (cpu_memory_rw_debug(env, ip + addr_offset, (void *)&real_tpr_addr,
+                            sizeof(real_tpr_addr), 0) < 0) {
+        return -1;
+    }
+    real_tpr_addr = le32_to_cpu(real_tpr_addr);
+    if ((real_tpr_addr & 0xfff) != 0x80) {
+        return -1;
+    }
+    s->real_tpr_addr = real_tpr_addr;
+    update_guest_rom_state(s);
+
+    *pip = ip;
+    return 0;
+}
+
+
+/*
+ * Tries to read the unique processor number from the Kernel Processor Control
+ * Region (KPCR) of 32-bit Windows. Returns -1 if the KPCR cannot be accessed
+ * or is considered invalid.
+ */
+static int get_kpcr_number(CPUState *env)
+{
+    struct kpcr {
+        uint8_t  fill1[0x1c];
+        uint32_t self;
+        uint8_t  fill2[0x31];
+        uint8_t  number;
+    } QEMU_PACKED kpcr;
+
+    if (smp_cpus == 1) {
Quoted 8 lines

hw/kvmvapic.c line 302

Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+        return 0;
+    }
+    if (cpu_memory_rw_debug(env, env->segs[R_FS].base,
+                            (void *)&kpcr, sizeof(kpcr), 0) < 0 ||
+        kpcr.self != env->segs[R_FS].base) {

Ah, so it works. We may want to do it for UP as well, as a way of verifying that the guest is compatible with these hacks.

I'm not sure if Windows has this properly set up for the UP HAL. I rather think this was a bug in the original implementation. The ROM uses 0 as CPU index in UP mode unconditionally, so should we in QEMU.

Quoted 40 lines
Quoted 40 lines
hw/kvmvapic.c new file +38 -0
+        return -1;
+    }
+    return kpcr.number;
+}
 
+static void patch_instruction(VAPICROMState *s, CPUState *env, target_ulong ip)
+{
+    target_phys_addr_t paddr;
+    VAPICHandlers *handlers;
+    uint8_t opcode[2];
+    uint32_t imm32;
+
+    if (smp_cpus == 1) {
+        handlers = &s->rom_state.up;
+    } else {
+        handlers = &s->rom_state.mp;
+    }
+
+    cpu_memory_rw_debug(env, ip, opcode, sizeof(opcode), 0);
+
+    switch (opcode[0]) {
+    case 0x89: /* mov r32 to r/m32 */
+        patch_byte(env, ip, 0x50 + modrm_reg(opcode[1]));  /* push reg */
+        patch_call(s, env, ip + 1, handlers->set_tpr);
+        break;
+    case 0x8b: /* mov r/m32 to r32 */
+        patch_byte(env, ip, 0x90);
+        patch_call(s, env, ip + 1, handlers->get_tpr[modrm_reg(opcode[1])]);
+        break;
+    case 0xa1: /* mov abs to eax */
+        patch_call(s, env, ip, handlers->get_tpr[0]);
+        break;
+    case 0xa3: /* mov eax to abs */
+        patch_call(s, env, ip, handlers->set_tpr_eax);
+        break;
+    case 0xc7: /* mov imm32, r/m32 (c7/0) */
+        patch_byte(env, ip, 0x68);  /* push imm32 */
+        cpu_memory_rw_debug(env, ip + 6, (void *)&imm32, sizeof(imm32), 0);
+        cpu_memory_rw_debug(env, ip + 1, (void *)&imm32, sizeof(imm32), 1);
Quoted 8 lines

hw/kvmvapic.c line 382

Quoted 6 lines
hw/kvmvapic.c new file +6 -0
+        patch_call(s, env, ip + 5, handlers->set_tpr);
+        break;
+    case 0xff: /* push r/m32 */
+        patch_byte(env, ip, 0x50); /* push eax */
+        patch_call(s, env, ip + 1, handlers->get_tpr_stack);
+        break;

With a table, we could put the patch sequences there as well.

Yep, starting to become really attractive.

Quoted 41 lines
Quoted 27 lines
hw/kvmvapic.c new file +26 -0
+    default:
+        abort();
+    }
+
+    paddr = cpu_get_phys_page_debug(env, ip);
+    paddr += ip & ~TARGET_PAGE_MASK;
+    tb_invalidate_phys_page_range(paddr, paddr + 1, 1);
+}
+
+
+static void vapic_map_rom_writable(VAPICROMState *s)
+{
+    target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK;
+    MemoryRegionSection section;
+    MemoryRegion *as;
+    size_t rom_size;
+    uint8_t *ram;
+
+    as = sysbus_address_space(&s->busdev);
+
+    if (s->rom_mapped_writable) {
+        memory_region_del_subregion(as, &s->rom);
+        memory_region_destroy(&s->rom);
+    }
+
+    /* grab RAM memory region (region @rom_paddr may still be pc.rom) */

hw/kvmvapic.c line 500

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+    section = memory_region_find(as, 0, 1);
+
+    /* read ROM size from RAM region */
+    ram = memory_region_get_ram_ptr(section.mr);
+    rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE;
+    s->rom_size = rom_size;
+
+    /* FIXME: round up as everything underneath would fall apart otherwise
+     * (subpages are broken) */
+    rom_size = TARGET_PAGE_ALIGN(rom_size);

Ok. Subpages aren't broken (at least, they can't be expected to work here). Without a page aligned region you can't expect kvm to map this area.

Quoted 2 lines

Wrt tcg, it should work, but I don't think anyone ever tested tcg out of subpage.

OK, will rephrase the reason.

Quoted 8 lines

hw/kvmvapic.c line 504

Quoted 5 lines
hw/kvmvapic.c new file +4 -0
+
+    memory_region_init_alias(&s->rom, "kvmvapic-rom", section.mr, rom_paddr,
+                             rom_size);
+    memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);

This is incredibly hacky, so at least the spirit of the original code is preserved.

I know, and it caused some pain to write it (not only to find out how to solve it technically). We would need to pass the RAM memory region down to this freaky device, like we do to the i440fx for PAM purposes. But, well, that is not straightforward right now. Better ideas welcome.

Quoted 8 lines

hw/kvmvapic.c line 507

Quoted 5 lines
hw/kvmvapic.c new file +3 -0
+    s->rom_mapped_writable = true;
+}
+

Impressive refactoring overall; I thought that code was a basket case.

Thanks,
Jan

#10 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 13 lines · Jan Kiszka
Quoted 12 lines · Avi Kivity
Quoted 10 lines · Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor.

The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration.

Quoted 5 lines
Quoted 2 lines

However, it's presented as a rewrite instead of a series of changes, so we can't see what the changes are.

Yes, but the original code also depends on interfaces we don't have in upstream.

The usual rant: patch qemu-kvm until it's suitable for upsteam, then submit the end result for upstream. But you don't deserve this today.

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
hw/kvmvapic.c new file +8 -0
+
+    if (access == TPR_ACCESS_WRITE && kvm_enabled() &&
+        !kvm_irqchip_in_kernel()) {
+        /*
+         * KVM without TPR access reporting calls into the user space APIC on
+         * write with IP pointing after the accessing instruction. So we need
+         * to look backward to find the reason.
+         */
Quoted 4 lines
Quoted 1 line

Why do we need to do anything at all?

We need to patch the causing instruction, so we have to know where it starts. Or what do you mean?

Just don't deal with this at all, no one runs on kernels without kernel irqchip.

Quoted 5 lines
Quoted 2 lines

I'm not sure if the ABI guarantees anything about %rip.

That's indeed a point. It's likely coupled to the emulator's internals and when it calls out to user space for MMIO write. How to deal with it?

One way is to verify that it worked this way at least N versions back, and then retro-doc it. The downside is that it reduces our flexibility in the future, but I think that's a small downside.

Another way is not to do it at all.

Quoted 19 lines
Quoted 10 lines
Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+static int get_kpcr_number(CPUState *env)
+{
+    struct kpcr {
+        uint8_t  fill1[0x1c];
+        uint32_t self;
+        uint8_t  fill2[0x31];
+        uint8_t  number;
+    } QEMU_PACKED kpcr;
+
+    if (smp_cpus == 1) {
Quoted 8 lines

hw/kvmvapic.c line 302

Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+        return 0;
+    }
+    if (cpu_memory_rw_debug(env, env->segs[R_FS].base,
+                            (void *)&kpcr, sizeof(kpcr), 0) < 0 ||
+        kpcr.self != env->segs[R_FS].base) {

Ah, so it works. We may want to do it for UP as well, as a way of verifying that the guest is compatible with these hacks.

Quoted 3 lines

I'm not sure if Windows has this properly set up for the UP HAL. I rather think this was a bug in the original implementation. The ROM uses 0 as CPU index in UP mode unconditionally, so should we in QEMU.

I mean just check kpcr.self.

The reason up and smp are so different is that for a long while smp didn't work at all, and for some time it used even uglier hacks than we have today (like stashing the cpu id in TR.sel), so I didn't want to pollute th up code with the smp ugliness. It's also marginally faster (less locked ops), though I doubt it matters on today's processors.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
hw/kvmvapic.c new file +4 -0
+
+    memory_region_init_alias(&s->rom, "kvmvapic-rom", section.mr, rom_paddr,
+                             rom_size);
+    memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);
Quoted 7 lines
Quoted 2 lines

This is incredibly hacky, so at least the spirit of the original code is preserved.

I know, and it caused some pain to write it (not only to find out how to solve it technically). We would need to pass the RAM memory region down to this freaky device, like we do to the i440fx for PAM purposes. But, well, that is not straightforward right now. Better ideas welcome.

Could we make it a child<> of i440FX, and have i440FX pass it the MemoryRegion directly?

It means we'll need to redo the glue for new chipsets, but it should be just a few lines.

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#11 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 32 lines · Avi Kivity
Quoted 13 lines · Jan Kiszka
Quoted 12 lines · Avi Kivity
Quoted 10 lines · Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor.

The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration.

Quoted 5 lines
Quoted 2 lines

However, it's presented as a rewrite instead of a series of changes, so we can't see what the changes are.

Yes, but the original code also depends on interfaces we don't have in upstream.

The usual rant: patch qemu-kvm until it's suitable for upsteam, then submit the end result for upstream. But you don't deserve this today.

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
hw/kvmvapic.c new file +8 -0
+
+    if (access == TPR_ACCESS_WRITE && kvm_enabled() &&
+        !kvm_irqchip_in_kernel()) {
+        /*
+         * KVM without TPR access reporting calls into the user space APIC on
+         * write with IP pointing after the accessing instruction. So we need
+         * to look backward to find the reason.
+         */
Quoted 7 lines
Quoted 4 lines
Quoted 1 line

Why do we need to do anything at all?

We need to patch the causing instruction, so we have to know where it starts. Or what do you mean?

Just don't deal with this at all, no one runs on kernels without kernel irqchip.

Not true for upstream, and not a design goal of this approach, specifically when considering that it also works with TCG. Would be a pity to lose this generality.

Quoted 10 lines
Quoted 6 lines
Quoted 3 lines

I'm not sure if the ABI guarantees anything about %rip.

That's indeed a point. It's likely coupled to the emulator's internals and when it calls out to user space for MMIO write. How to deal with it?

One way is to verify that it worked this way at least N versions back, and then retro-doc it. The downside is that it reduces our flexibility in the future, but I think that's a small downside.

It need not reduce our flexibility, we just need to signal to user space when our behaviour changes again.

Quoted 22 lines

Another way is not to do it at all.

Quoted 19 lines
Quoted 18 lines
Quoted 5 lines
hw/kvmvapic.c new file +5 -0
+static int get_kpcr_number(CPUState *env)
+{
+    struct kpcr {
+        uint8_t  fill1[0x1c];
+        uint32_t self;

hw/kvmvapic.c line 302

Quoted 10 lines
hw/kvmvapic.c new file +10 -0
+        uint8_t  fill2[0x31];
+        uint8_t  number;
+    } QEMU_PACKED kpcr;
+
+    if (smp_cpus == 1) {
+        return 0;
+    }
+    if (cpu_memory_rw_debug(env, env->segs[R_FS].base,
+                            (void *)&kpcr, sizeof(kpcr), 0) < 0 ||
+        kpcr.self != env->segs[R_FS].base) {

Ah, so it works. We may want to do it for UP as well, as a way of verifying that the guest is compatible with these hacks.

Quoted 5 lines
Quoted 3 lines

I'm not sure if Windows has this properly set up for the UP HAL. I rather think this was a bug in the original implementation. The ROM uses 0 as CPU index in UP mode unconditionally, so should we in QEMU.

I mean just check kpcr.self.

Yes, clear, but that means that Windows must have initialized FS.base to point to the KPCR also in UP mode. Is that really the case? E.g. when ACPI is off?! I wonder if that explains the reported bug of qemu-kvm with -no-acpi and in-kernel irqchip...

Quoted 21 lines

The reason up and smp are so different is that for a long while smp didn't work at all, and for some time it used even uglier hacks than we have today (like stashing the cpu id in TR.sel), so I didn't want to pollute th up code with the smp ugliness. It's also marginally faster (less locked ops), though I doubt it matters on today's processors.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
hw/kvmvapic.c new file +4 -0
+
+    memory_region_init_alias(&s->rom, "kvmvapic-rom", section.mr, rom_paddr,
+                             rom_size);
+    memory_region_add_subregion_overlap(as, rom_paddr, &s->rom, 1000);
Quoted 7 lines
Quoted 2 lines

This is incredibly hacky, so at least the spirit of the original code is preserved.

I know, and it caused some pain to write it (not only to find out how to solve it technically). We would need to pass the RAM memory region down to this freaky device, like we do to the i440fx for PAM purposes. But, well, that is not straightforward right now. Better ideas welcome.

Quoted 5 lines

Could we make it a child<> of i440FX, and have i440FX pass it the MemoryRegion directly?

It means we'll need to redo the glue for new chipsets, but it should be just a few lines.

Hmm... not really nice. It is rather a child of the APIC than of the chipset IMHO. Moving it over would also mean establishing logical link to the APIC from there. It is really an ugly beast...

Jan

#12 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 8 lines · Jan Kiszka
Quoted 6 lines
Quoted 3 lines

We need to patch the causing instruction, so we have to know where it starts. Or what do you mean?

Just don't deal with this at all, no one runs on kernels without kernel irqchip.

Not true for upstream,

It was introduced in 2.6.24? Is anyone running anything earlier?

I hope that qemu-1.1 will default to kvm irqchip. In fact we should start thinking about deprecating apic-less kvm (in the kernel).

Quoted 3 lines

and not a design goal of this approach,
specifically when considering that it also works with TCG. Would be a pity to lose this generality.

It doesn't really speed up tcg, does it?

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
Quoted 4 lines

I'm not sure if the ABI guarantees anything about %rip.

That's indeed a point. It's likely coupled to the emulator's internals and when it calls out to user space for MMIO write. How to deal with it?

Quoted 6 lines
Quoted 3 lines

One way is to verify that it worked this way at least N versions back, and then retro-doc it. The downside is that it reduces our flexibility in the future, but I think that's a small downside.

It need not reduce our flexibility, we just need to signal to user space when our behaviour changes again.

This means that if this code detects that rip is no longer accurate using this signal, it has to disable itself. That's not something we want, I think.

Quoted 10 lines
Quoted 6 lines
Quoted 4 lines

I'm not sure if Windows has this properly set up for the UP HAL. I rather think this was a bug in the original implementation. The ROM uses 0 as CPU index in UP mode unconditionally, so should we in QEMU.

I mean just check kpcr.self.

Yes, clear, but that means that Windows must have initialized FS.base to point to the KPCR also in UP mode. Is that really the case? E.g. when ACPI is off?!

No idea.

Quoted 2 lines

I wonder if that explains the reported bug of qemu-kvm with -no-acpi and in-kernel irqchip...

acpi-less smp? it exists but rarely used. It could explain the problem, yes.

Quoted 9 lines
Quoted 9 lines
Quoted 5 lines

I know, and it caused some pain to write it (not only to find out how to solve it technically). We would need to pass the RAM memory region down to this freaky device, like we do to the i440fx for PAM purposes. But, well, that is not straightforward right now. Better ideas welcome.

Could we make it a child<> of i440FX, and have i440FX pass it the MemoryRegion directly?

Quoted 6 lines
Quoted 2 lines

It means we'll need to redo the glue for new chipsets, but it should be just a few lines.

Hmm... not really nice. It is rather a child of the APIC than of the chipset IMHO. Moving it over would also mean establishing logical link to the APIC from there.

Clearly it's involved with the 440fx as well, as it has the magical ability to turn ROM into RAM.

Quoted 1 line

It is really an ugly beast...

Oh yes.

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#13 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 5 lines · Avi Kivity
Quoted 4 lines · Jan Kiszka
Quoted 4 lines
Quoted 3 lines

We need to patch the causing instruction, so we have to know where it starts. Or what do you mean?

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

Just don't deal with this at all, no one runs on kernels without kernel irqchip.

Not true for upstream,

It was introduced in 2.6.24? Is anyone running anything earlier?

I hope that qemu-1.1 will default to kvm irqchip.

I don't expect the MSI thing cleaned up and upstreamed by then. So the question is if we want to disable MSI by default - I guess not...

Quoted 2 lines

In fact we should
start thinking about deprecating apic-less kvm (in the kernel).

Not a good idea as it allows to stress user space code in KVM mode that is not going to be deprecated (as it is used without KVM as well).

Quoted 6 lines
Quoted 4 lines

and not a design goal of this approach,
specifically when considering that it also works with TCG. Would be a pity to lose this generality.

It doesn't really speed up tcg, does it?

Nope, but it removes yet another blocking point for TCG<->KVM migration.

Quoted 13 lines
Quoted 13 lines
Quoted 12 lines
Quoted 8 lines
Quoted 5 lines

I'm not sure if the ABI guarantees anything about %rip.

That's indeed a point. It's likely coupled to the emulator's internals and when it calls out to user space for MMIO write. How to deal with it?

One way is to verify that it worked this way at least N versions back, and then retro-doc it. The downside is that it reduces our flexibility in the future, but I think that's a small downside.

Quoted 6 lines
Quoted 2 lines

It need not reduce our flexibility, we just need to signal to user space when our behaviour changes again.

This means that if this code detects that rip is no longer accurate using this signal, it has to disable itself. That's not something we want, I think.

...or it could check in the other direction, i.e. forward. I think there are only two reasonable options for the emulator. We could encode them in a KVM_CAP return code.

Quoted 12 lines
Quoted 11 lines
Quoted 7 lines
Quoted 5 lines

I'm not sure if Windows has this properly set up for the UP HAL. I rather think this was a bug in the original implementation. The ROM uses 0 as CPU index in UP mode unconditionally, so should we in QEMU.

I mean just check kpcr.self.

Yes, clear, but that means that Windows must have initialized FS.base to point to the KPCR also in UP mode. Is that really the case? E.g. when ACPI is off?!

Quoted 7 lines

No idea.

Quoted 2 lines

I wonder if that explains the reported bug of qemu-kvm with -no-acpi and in-kernel irqchip...

acpi-less smp? it exists but rarely used. It could explain the problem, yes.

Testing right now...

Quoted 13 lines
Quoted 13 lines
Quoted 12 lines
Quoted 6 lines

I know, and it caused some pain to write it (not only to find out how to solve it technically). We would need to pass the RAM memory region down to this freaky device, like we do to the i440fx for PAM purposes. But, well, that is not straightforward right now. Better ideas welcome.

Could we make it a child<> of i440FX, and have i440FX pass it the MemoryRegion directly?

It means we'll need to redo the glue for new chipsets, but it should be just a few lines.

Quoted 6 lines
Quoted 3 lines

Hmm... not really nice. It is rather a child of the APIC than of the chipset IMHO. Moving it over would also mean establishing logical link to the APIC from there.

Clearly it's involved with the 440fx as well, as it has the magical ability to turn ROM into RAM.

I think all our ROM is in shadow RAM, only protected by the PAM settings. We could also reconfigure PAM in the guest, but the granularity does not match, and it would not be compatible with older VAPIC ROMs.

We could establish a side channel to the chipset and linking the kvmvapic to it at board level. That link could point to any chipset supporting the interface.

Jan

maintainer
#14 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

In reply to Jan Kiszka
Quoted 6 lines · Jan Kiszka
Quoted 1 line

I mean just check kpcr.self.

Yes, clear, but that means that Windows must have initialized FS.base to point to the KPCR also in UP mode. Is that really the case? E.g. when ACPI is off?! I wonder if that explains the reported bug of qemu-kvm with -no-acpi and in-kernel irqchip...

Yes, it does. It's used by some fast-path kernel APIs, and indeed the canonical way to find the KPCR base from ring 0 is to look at FS:[1Ch].

Similarly in userspace you can find the thread information block at FS:[sizeof(void*)*6], and FS:[1Ch] is something else. But your code cannot be reached from userspace, so that's always fine.

Paolo

author
#15 lore raw

Re: [PATCH 3/6] kvmvapic: Introduce TPR access optimization for Windows guests

Quoted 10 lines · Paolo Bonzini
Quoted 6 lines · Jan Kiszka
Quoted 1 line

I mean just check kpcr.self.

Yes, clear, but that means that Windows must have initialized FS.base to point to the KPCR also in UP mode. Is that really the case? E.g. when ACPI is off?! I wonder if that explains the reported bug of qemu-kvm with -no-acpi and in-kernel irqchip...

Yes, it does. It's used by some fast-path kernel APIs, and indeed the canonical way to find the KPCR base from ring 0 is to look at FS:[1Ch].

Yep, can confirm this so far. I briefly tested a non-ACPI installation, and it both works without problems on qemu-kvm and shows that pattern at FS:[1C].

I guess I will simply remove the smp_cpus == 1 special case.

Jan

PS: Please strip of the newsgroups from CC when picking up a thread via news.gmane.org. Some clients may not be configured to reply to them (like mine - intentionally :) ).

author
#16 lore raw

PATCH 4/6 kvmvapic: Simplify mp/up_set_tpr

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

The CH registers is only written, never read. So we can remove these operations and, in case of up_set_tpr, also the ECX push/pop.

---
 pc-bios/optionrom/kvmvapic.S |    6 +-----
 1 files changed, 1 insertions(+), 5 deletions(-)
pc-bios/optionrom/kvmvapic.S +1 -5
index e1d8f18..856c1e5 100644
@@ -202,7 +202,6 @@ mp_isr_is_bigger:
 	mov %bh, %bl
 mp_tpr_is_bigger:
 	/* %bl = ppr */
-	mov %bl, %ch   /* ch = ppr */
 	rol $8, %ebx
 	/* now: %bl = irr, %bh = ppr */
 	cmp %bh, %bl
@@ -276,7 +275,6 @@ up_set_tpr_eax:
 up_set_tpr:
 	pushf
 	push %eax
-	push %ecx
 	push %ebx
 	reenable_vtpr
 
@@ -284,7 +282,7 @@ up_set_tpr_failed:
 	mov vapic, %eax	; fixup
 
 	mov %eax, %ebx
-	mov 20(%esp), %bl
+	mov 16(%esp), %bl
 
 	/* %ebx = new vapic (%bl = tpr, %bh = isr, %b3 = irr) */
 
@@ -298,7 +296,6 @@ up_isr_is_bigger:
 	mov %bh, %bl
 up_tpr_is_bigger:
 	/* %bl = ppr */
-	mov %bl, %ch   /* ch = ppr */
 	rol $8, %ebx
 	/* now: %bl = irr, %bh = ppr */
 	cmp %bh, %bl
@@ -306,7 +303,6 @@ up_tpr_is_bigger:
 
 up_set_tpr_out:
 	pop %ebx
-	pop %ecx
 	pop %eax
 	popf
 	ret $4
Signature · 1 line
1.7.3.4
author
#17 lore raw

PATCH 5/6 optionsrom: Reserve space for checksum

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

Always add a byte before the final 512-bytes alignment to reserve the space for the ROM checksum.

---
 pc-bios/optionrom/optionrom.h |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
pc-bios/optionrom/optionrom.h +2 -1
index aa783de..3daf7da 100644
@@ -124,7 +124,8 @@
 	movw		%ax, %ds;
 
 #define OPTION_ROM_END					\
-    .align 512, 0;					\
+	.byte		0;				\
+	.align		512, 0;				\
     _end:
 
 #define BOOT_ROM_END					\
Signature · 1 line
1.7.3.4
author
#18 lore raw

PATCH 6/6 kvmvapic: Use optionrom helpers

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

Use OPTION_ROM_START/END from the common header file, add comment to init code.

---
 pc-bios/optionrom/kvmvapic.S |   18 ++++++++----------
 1 files changed, 8 insertions(+), 10 deletions(-)
pc-bios/optionrom/kvmvapic.S +8 -10
index 856c1e5..aa17a40 100644
@@ -9,12 +9,10 @@
 # option) any later version.  See the COPYING file in the top-level directory.
 #
 
-	.text 0
-	.code16
-.global _start
-_start:
-	.short 0xaa55
-	.byte (_end - _start) / 512
+#include "optionrom.h"
+
+OPTION_ROM_START
+
 	# clear vapic area: firmware load using rep insb may cause
 	# stale tpr/isr/irr data to corrupt the vapic area.
 	push %es
@@ -26,8 +24,11 @@ _start:
 	cld
 	rep stosw
 	pop %es
+
+	# announce presence to the hypervisor
 	mov $vapic_base, %ax
 	out %ax, $0x7e
+
 	lret
 
 	.code32
@@ -331,7 +332,4 @@ up_set_tpr_poll_irq:
 vapic:
 . = . + vapic_size
 
-.byte 0  # reserve space for signature
-.align 512, 0
-
-_end:
+OPTION_ROM_END
Signature · 1 line
1.7.3.4