Browse Source
* qemu-kvm/uq/master: pc-bios: update kvmvapic.bin kvmvapic: Use optionrom helpers optionsrom: Reserve space for checksum kvmvapic: Simplify mp/up_set_tpr kvmvapic: Introduce TPR access optimization for Windows guests kvmvapic: Add option ROM target-i386: Add infrastructure for reporting TPR MMIO accesses Allow to use pause_all_vcpus from VCPU context Process pending work while waiting for initial kick-off in TCG mode Remove useless casts from cpu iterators kvm: Set cpu_single_env only once kvm: Synchronize cpu state in kvm_arch_stop_on_emulation_error()stable-1.1
20 changed files with 1468 additions and 30 deletions
@ -0,0 +1,805 @@ |
|||
/*
|
|||
* TPR optimization for 32-bit Windows guests (XP and Server 2003) |
|||
* |
|||
* Copyright (C) 2007-2008 Qumranet Technologies |
|||
* Copyright (C) 2012 Jan Kiszka, Siemens AG |
|||
* |
|||
* This work is licensed under the terms of the GNU GPL version 2, or |
|||
* (at your option) any later version. See the COPYING file in the |
|||
* top-level directory. |
|||
*/ |
|||
#include "sysemu.h" |
|||
#include "cpus.h" |
|||
#include "kvm.h" |
|||
#include "apic_internal.h" |
|||
|
|||
#define APIC_DEFAULT_ADDRESS 0xfee00000 |
|||
|
|||
#define VAPIC_IO_PORT 0x7e |
|||
|
|||
#define VAPIC_CPU_SHIFT 7 |
|||
|
|||
#define ROM_BLOCK_SIZE 512 |
|||
#define ROM_BLOCK_MASK (~(ROM_BLOCK_SIZE - 1)) |
|||
|
|||
typedef enum VAPICMode { |
|||
VAPIC_INACTIVE = 0, |
|||
VAPIC_ACTIVE = 1, |
|||
VAPIC_STANDBY = 2, |
|||
} VAPICMode; |
|||
|
|||
typedef struct VAPICHandlers { |
|||
uint32_t set_tpr; |
|||
uint32_t set_tpr_eax; |
|||
uint32_t get_tpr[8]; |
|||
uint32_t get_tpr_stack; |
|||
} QEMU_PACKED VAPICHandlers; |
|||
|
|||
typedef struct GuestROMState { |
|||
char signature[8]; |
|||
uint32_t vaddr; |
|||
uint32_t fixup_start; |
|||
uint32_t fixup_end; |
|||
uint32_t vapic_vaddr; |
|||
uint32_t vapic_size; |
|||
uint32_t vcpu_shift; |
|||
uint32_t real_tpr_addr; |
|||
VAPICHandlers up; |
|||
VAPICHandlers mp; |
|||
} QEMU_PACKED GuestROMState; |
|||
|
|||
typedef struct VAPICROMState { |
|||
SysBusDevice busdev; |
|||
MemoryRegion io; |
|||
MemoryRegion rom; |
|||
uint32_t state; |
|||
uint32_t rom_state_paddr; |
|||
uint32_t rom_state_vaddr; |
|||
uint32_t vapic_paddr; |
|||
uint32_t real_tpr_addr; |
|||
GuestROMState rom_state; |
|||
size_t rom_size; |
|||
bool rom_mapped_writable; |
|||
} VAPICROMState; |
|||
|
|||
#define TPR_INSTR_ABS_MODRM 0x1 |
|||
#define TPR_INSTR_MATCH_MODRM_REG 0x2 |
|||
|
|||
typedef struct TPRInstruction { |
|||
uint8_t opcode; |
|||
uint8_t modrm_reg; |
|||
unsigned int flags; |
|||
TPRAccess access; |
|||
size_t length; |
|||
off_t addr_offset; |
|||
} TPRInstruction; |
|||
|
|||
/* must be sorted by length, shortest first */ |
|||
static const TPRInstruction tpr_instr[] = { |
|||
{ /* mov abs to eax */ |
|||
.opcode = 0xa1, |
|||
.access = TPR_ACCESS_READ, |
|||
.length = 5, |
|||
.addr_offset = 1, |
|||
}, |
|||
{ /* mov eax to abs */ |
|||
.opcode = 0xa3, |
|||
.access = TPR_ACCESS_WRITE, |
|||
.length = 5, |
|||
.addr_offset = 1, |
|||
}, |
|||
{ /* mov r32 to r/m32 */ |
|||
.opcode = 0x89, |
|||
.flags = TPR_INSTR_ABS_MODRM, |
|||
.access = TPR_ACCESS_WRITE, |
|||
.length = 6, |
|||
.addr_offset = 2, |
|||
}, |
|||
{ /* mov r/m32 to r32 */ |
|||
.opcode = 0x8b, |
|||
.flags = TPR_INSTR_ABS_MODRM, |
|||
.access = TPR_ACCESS_READ, |
|||
.length = 6, |
|||
.addr_offset = 2, |
|||
}, |
|||
{ /* push r/m32 */ |
|||
.opcode = 0xff, |
|||
.modrm_reg = 6, |
|||
.flags = TPR_INSTR_ABS_MODRM | TPR_INSTR_MATCH_MODRM_REG, |
|||
.access = TPR_ACCESS_READ, |
|||
.length = 6, |
|||
.addr_offset = 2, |
|||
}, |
|||
{ /* mov imm32, r/m32 (c7/0) */ |
|||
.opcode = 0xc7, |
|||
.modrm_reg = 0, |
|||
.flags = TPR_INSTR_ABS_MODRM | TPR_INSTR_MATCH_MODRM_REG, |
|||
.access = TPR_ACCESS_WRITE, |
|||
.length = 10, |
|||
.addr_offset = 2, |
|||
}, |
|||
}; |
|||
|
|||
static void read_guest_rom_state(VAPICROMState *s) |
|||
{ |
|||
cpu_physical_memory_rw(s->rom_state_paddr, (void *)&s->rom_state, |
|||
sizeof(GuestROMState), 0); |
|||
} |
|||
|
|||
static void write_guest_rom_state(VAPICROMState *s) |
|||
{ |
|||
cpu_physical_memory_rw(s->rom_state_paddr, (void *)&s->rom_state, |
|||
sizeof(GuestROMState), 1); |
|||
} |
|||
|
|||
static void update_guest_rom_state(VAPICROMState *s) |
|||
{ |
|||
read_guest_rom_state(s); |
|||
|
|||
s->rom_state.real_tpr_addr = cpu_to_le32(s->real_tpr_addr); |
|||
s->rom_state.vcpu_shift = cpu_to_le32(VAPIC_CPU_SHIFT); |
|||
|
|||
write_guest_rom_state(s); |
|||
} |
|||
|
|||
static int find_real_tpr_addr(VAPICROMState *s, CPUState *env) |
|||
{ |
|||
target_phys_addr_t paddr; |
|||
target_ulong addr; |
|||
|
|||
if (s->state == VAPIC_ACTIVE) { |
|||
return 0; |
|||
} |
|||
/*
|
|||
* If there is no prior TPR access instruction we could analyze (which is |
|||
* the case after resume from hibernation), we need to scan the possible |
|||
* virtual address space for the APIC mapping. |
|||
*/ |
|||
for (addr = 0xfffff000; addr >= 0x80000000; addr -= TARGET_PAGE_SIZE) { |
|||
paddr = cpu_get_phys_page_debug(env, addr); |
|||
if (paddr != APIC_DEFAULT_ADDRESS) { |
|||
continue; |
|||
} |
|||
s->real_tpr_addr = addr + 0x80; |
|||
update_guest_rom_state(s); |
|||
return 0; |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
static uint8_t modrm_reg(uint8_t modrm) |
|||
{ |
|||
return (modrm >> 3) & 7; |
|||
} |
|||
|
|||
static bool is_abs_modrm(uint8_t modrm) |
|||
{ |
|||
return (modrm & 0xc7) == 0x05; |
|||
} |
|||
|
|||
static bool opcode_matches(uint8_t *opcode, const TPRInstruction *instr) |
|||
{ |
|||
return opcode[0] == instr->opcode && |
|||
(!(instr->flags & TPR_INSTR_ABS_MODRM) || is_abs_modrm(opcode[1])) && |
|||
(!(instr->flags & TPR_INSTR_MATCH_MODRM_REG) || |
|||
modrm_reg(opcode[1]) == instr->modrm_reg); |
|||
} |
|||
|
|||
static int evaluate_tpr_instruction(VAPICROMState *s, CPUState *env, |
|||
target_ulong *pip, TPRAccess access) |
|||
{ |
|||
const TPRInstruction *instr; |
|||
target_ulong ip = *pip; |
|||
uint8_t opcode[2]; |
|||
uint32_t real_tpr_addr; |
|||
int i; |
|||
|
|||
if ((ip & 0xf0000000ULL) != 0x80000000ULL && |
|||
(ip & 0xf0000000ULL) != 0xe0000000ULL) { |
|||
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 (kvm_enabled() && !kvm_irqchip_in_kernel()) { |
|||
/*
|
|||
* KVM without kernel-based TPR access reporting will pass an IP that |
|||
* points after the accessing instruction. So we need to look backward |
|||
* to find the reason. |
|||
*/ |
|||
for (i = 0; i < ARRAY_SIZE(tpr_instr); i++) { |
|||
instr = &tpr_instr[i]; |
|||
if (instr->access != access) { |
|||
continue; |
|||
} |
|||
if (cpu_memory_rw_debug(env, ip - instr->length, opcode, |
|||
sizeof(opcode), 0) < 0) { |
|||
return -1; |
|||
} |
|||
if (opcode_matches(opcode, instr)) { |
|||
ip -= instr->length; |
|||
goto instruction_ok; |
|||
} |
|||
} |
|||
return -1; |
|||
} else { |
|||
if (cpu_memory_rw_debug(env, ip, opcode, sizeof(opcode), 0) < 0) { |
|||
return -1; |
|||
} |
|||
for (i = 0; i < ARRAY_SIZE(tpr_instr); i++) { |
|||
instr = &tpr_instr[i]; |
|||
if (opcode_matches(opcode, instr)) { |
|||
goto instruction_ok; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
instruction_ok: |
|||
/*
|
|||
* Grab the virtual TPR address from the instruction |
|||
* and update the cached values. |
|||
*/ |
|||
if (cpu_memory_rw_debug(env, ip + instr->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; |
|||
} |
|||
|
|||
static int update_rom_mapping(VAPICROMState *s, CPUState *env, target_ulong ip) |
|||
{ |
|||
target_phys_addr_t paddr; |
|||
uint32_t rom_state_vaddr; |
|||
uint32_t pos, patch, offset; |
|||
|
|||
/* nothing to do if already activated */ |
|||
if (s->state == VAPIC_ACTIVE) { |
|||
return 0; |
|||
} |
|||
|
|||
/* bail out if ROM init code was not executed (missing ROM?) */ |
|||
if (s->state == VAPIC_INACTIVE) { |
|||
return -1; |
|||
} |
|||
|
|||
/* find out virtual address of the ROM */ |
|||
rom_state_vaddr = s->rom_state_paddr + (ip & 0xf0000000); |
|||
paddr = cpu_get_phys_page_debug(env, rom_state_vaddr); |
|||
if (paddr == -1) { |
|||
return -1; |
|||
} |
|||
paddr += rom_state_vaddr & ~TARGET_PAGE_MASK; |
|||
if (paddr != s->rom_state_paddr) { |
|||
return -1; |
|||
} |
|||
read_guest_rom_state(s); |
|||
if (memcmp(s->rom_state.signature, "kvm aPiC", 8) != 0) { |
|||
return -1; |
|||
} |
|||
s->rom_state_vaddr = rom_state_vaddr; |
|||
|
|||
/* fixup addresses in ROM if needed */ |
|||
if (rom_state_vaddr == le32_to_cpu(s->rom_state.vaddr)) { |
|||
return 0; |
|||
} |
|||
for (pos = le32_to_cpu(s->rom_state.fixup_start); |
|||
pos < le32_to_cpu(s->rom_state.fixup_end); |
|||
pos += 4) { |
|||
cpu_physical_memory_rw(paddr + pos - s->rom_state.vaddr, |
|||
(void *)&offset, sizeof(offset), 0); |
|||
offset = le32_to_cpu(offset); |
|||
cpu_physical_memory_rw(paddr + offset, (void *)&patch, |
|||
sizeof(patch), 0); |
|||
patch = le32_to_cpu(patch); |
|||
patch += rom_state_vaddr - le32_to_cpu(s->rom_state.vaddr); |
|||
patch = cpu_to_le32(patch); |
|||
cpu_physical_memory_rw(paddr + offset, (void *)&patch, |
|||
sizeof(patch), 1); |
|||
} |
|||
read_guest_rom_state(s); |
|||
s->vapic_paddr = paddr + le32_to_cpu(s->rom_state.vapic_vaddr) - |
|||
le32_to_cpu(s->rom_state.vaddr); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/*
|
|||
* Tries to read the unique processor number from the Kernel Processor Control |
|||
* Region (KPCR) of 32-bit Windows XP and Server 2003. Returns -1 if the KPCR |
|||
* cannot be accessed or is considered invalid. This also ensures that we are |
|||
* not patching the wrong guest. |
|||
*/ |
|||
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 (cpu_memory_rw_debug(env, env->segs[R_FS].base, |
|||
(void *)&kpcr, sizeof(kpcr), 0) < 0 || |
|||
kpcr.self != env->segs[R_FS].base) { |
|||
return -1; |
|||
} |
|||
return kpcr.number; |
|||
} |
|||
|
|||
static int vapic_enable(VAPICROMState *s, CPUState *env) |
|||
{ |
|||
int cpu_number = get_kpcr_number(env); |
|||
target_phys_addr_t vapic_paddr; |
|||
static const uint8_t enabled = 1; |
|||
|
|||
if (cpu_number < 0) { |
|||
return -1; |
|||
} |
|||
vapic_paddr = s->vapic_paddr + |
|||
(((target_phys_addr_t)cpu_number) << VAPIC_CPU_SHIFT); |
|||
cpu_physical_memory_rw(vapic_paddr + offsetof(VAPICState, enabled), |
|||
(void *)&enabled, sizeof(enabled), 1); |
|||
apic_enable_vapic(env->apic_state, vapic_paddr); |
|||
|
|||
s->state = VAPIC_ACTIVE; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void patch_byte(CPUState *env, target_ulong addr, uint8_t byte) |
|||
{ |
|||
cpu_memory_rw_debug(env, addr, &byte, 1, 1); |
|||
} |
|||
|
|||
static void patch_call(VAPICROMState *s, CPUState *env, target_ulong ip, |
|||
uint32_t target) |
|||
{ |
|||
uint32_t offset; |
|||
|
|||
offset = cpu_to_le32(target - ip - 5); |
|||
patch_byte(env, ip, 0xe8); /* call near */ |
|||
cpu_memory_rw_debug(env, ip + 1, (void *)&offset, sizeof(offset), 1); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
pause_all_vcpus(); |
|||
|
|||
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); |
|||
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; |
|||
default: |
|||
abort(); |
|||
} |
|||
|
|||
resume_all_vcpus(); |
|||
|
|||
paddr = cpu_get_phys_page_debug(env, ip); |
|||
paddr += ip & ~TARGET_PAGE_MASK; |
|||
tb_invalidate_phys_page_range(paddr, paddr + 1, 1); |
|||
} |
|||
|
|||
void vapic_report_tpr_access(DeviceState *dev, void *cpu, target_ulong ip, |
|||
TPRAccess access) |
|||
{ |
|||
VAPICROMState *s = DO_UPCAST(VAPICROMState, busdev.qdev, dev); |
|||
CPUState *env = cpu; |
|||
|
|||
cpu_synchronize_state(env); |
|||
|
|||
if (evaluate_tpr_instruction(s, env, &ip, access) < 0) { |
|||
if (s->state == VAPIC_ACTIVE) { |
|||
vapic_enable(s, env); |
|||
} |
|||
return; |
|||
} |
|||
if (update_rom_mapping(s, env, ip) < 0) { |
|||
return; |
|||
} |
|||
if (vapic_enable(s, env) < 0) { |
|||
return; |
|||
} |
|||
patch_instruction(s, env, ip); |
|||
} |
|||
|
|||
typedef struct VAPICEnableTPRReporting { |
|||
DeviceState *apic; |
|||
bool enable; |
|||
} VAPICEnableTPRReporting; |
|||
|
|||
static void vapic_do_enable_tpr_reporting(void *data) |
|||
{ |
|||
VAPICEnableTPRReporting *info = data; |
|||
|
|||
apic_enable_tpr_access_reporting(info->apic, info->enable); |
|||
} |
|||
|
|||
static void vapic_enable_tpr_reporting(bool enable) |
|||
{ |
|||
VAPICEnableTPRReporting info = { |
|||
.enable = enable, |
|||
}; |
|||
CPUState *env; |
|||
|
|||
for (env = first_cpu; env != NULL; env = env->next_cpu) { |
|||
info.apic = env->apic_state; |
|||
run_on_cpu(env, vapic_do_enable_tpr_reporting, &info); |
|||
} |
|||
} |
|||
|
|||
static void vapic_reset(DeviceState *dev) |
|||
{ |
|||
VAPICROMState *s = DO_UPCAST(VAPICROMState, busdev.qdev, dev); |
|||
|
|||
if (s->state == VAPIC_ACTIVE) { |
|||
s->state = VAPIC_STANDBY; |
|||
} |
|||
vapic_enable_tpr_reporting(false); |
|||
} |
|||
|
|||
/*
|
|||
* Set the IRQ polling hypercalls to the supported variant: |
|||
* - vmcall if using KVM in-kernel irqchip |
|||
* - 32-bit VAPIC port write otherwise |
|||
*/ |
|||
static int patch_hypercalls(VAPICROMState *s) |
|||
{ |
|||
target_phys_addr_t rom_paddr = s->rom_state_paddr & ROM_BLOCK_MASK; |
|||
static const uint8_t vmcall_pattern[] = { /* vmcall */ |
|||
0xb8, 0x1, 0, 0, 0, 0xf, 0x1, 0xc1 |
|||
}; |
|||
static const uint8_t outl_pattern[] = { /* nop; outl %eax,0x7e */ |
|||
0xb8, 0x1, 0, 0, 0, 0x90, 0xe7, 0x7e |
|||
}; |
|||
uint8_t alternates[2]; |
|||
const uint8_t *pattern; |
|||
const uint8_t *patch; |
|||
int patches = 0; |
|||
off_t pos; |
|||
uint8_t *rom; |
|||
|
|||
rom = g_malloc(s->rom_size); |
|||
cpu_physical_memory_rw(rom_paddr, rom, s->rom_size, 0); |
|||
|
|||
for (pos = 0; pos < s->rom_size - sizeof(vmcall_pattern); pos++) { |
|||
if (kvm_irqchip_in_kernel()) { |
|||
pattern = outl_pattern; |
|||
alternates[0] = outl_pattern[7]; |
|||
alternates[1] = outl_pattern[7]; |
|||
patch = &vmcall_pattern[5]; |
|||
} else { |
|||
pattern = vmcall_pattern; |
|||
alternates[0] = vmcall_pattern[7]; |
|||
alternates[1] = 0xd9; /* AMD's VMMCALL */ |
|||
patch = &outl_pattern[5]; |
|||
} |
|||
if (memcmp(rom + pos, pattern, 7) == 0 && |
|||
(rom[pos + 7] == alternates[0] || rom[pos + 7] == alternates[1])) { |
|||
cpu_physical_memory_rw(rom_paddr + pos + 5, (uint8_t *)patch, |
|||
3, 1); |
|||
/*
|
|||
* Don't flush the tb here. Under ordinary conditions, the patched |
|||
* calls are miles away from the current IP. Under malicious |
|||
* conditions, the guest could trick us to crash. |
|||
*/ |
|||
} |
|||
} |
|||
|
|||
g_free(rom); |
|||
|
|||
if (patches != 0 && patches != 2) { |
|||
return -1; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/*
|
|||
* For TCG mode or the time KVM honors read-only memory regions, we need to |
|||
* enable write access to the option ROM so that variables can be updated by |
|||
* the guest. |
|||
*/ |
|||
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; |
|||
s->rom_size = rom_size; |
|||
|
|||
/* We need to round up to avoid creating subpages
|
|||
* from which we cannot run code. */ |
|||
rom_size = TARGET_PAGE_ALIGN(rom_size); |
|||
|
|||
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; |
|||
} |
|||
|
|||
static int vapic_prepare(VAPICROMState *s) |
|||
{ |
|||
vapic_map_rom_writable(s); |
|||
|
|||
if (patch_hypercalls(s) < 0) { |
|||
return -1; |
|||
} |
|||
|
|||
vapic_enable_tpr_reporting(true); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void vapic_write(void *opaque, target_phys_addr_t addr, uint64_t data, |
|||
unsigned int size) |
|||
{ |
|||
CPUState *env = cpu_single_env; |
|||
target_phys_addr_t rom_paddr; |
|||
VAPICROMState *s = opaque; |
|||
|
|||
cpu_synchronize_state(env); |
|||
|
|||
/*
|
|||
* The VAPIC supports two PIO-based hypercalls, both via port 0x7E. |
|||
* o 16-bit write access: |
|||
* Reports the option ROM initialization to the hypervisor. Written |
|||
* value is the offset of the state structure in the ROM. |
|||
* o 8-bit write access: |
|||
* Reactivates the VAPIC after a guest hibernation, i.e. after the |
|||
* option ROM content has been re-initialized by a guest power cycle. |
|||
* o 32-bit write access: |
|||
* Poll for pending IRQs, considering the current VAPIC state. |
|||
*/ |
|||
switch (size) { |
|||
case 2: |
|||
if (s->state == VAPIC_INACTIVE) { |
|||
rom_paddr = (env->segs[R_CS].base + env->eip) & ROM_BLOCK_MASK; |
|||
s->rom_state_paddr = rom_paddr + data; |
|||
|
|||
s->state = VAPIC_STANDBY; |
|||
} |
|||
if (vapic_prepare(s) < 0) { |
|||
s->state = VAPIC_INACTIVE; |
|||
break; |
|||
} |
|||
break; |
|||
case 1: |
|||
if (kvm_enabled()) { |
|||
/*
|
|||
* Disable triggering instruction in ROM by writing a NOP. |
|||
* |
|||
* We cannot do this in TCG mode as the reported IP is not |
|||
* accurate. |
|||
*/ |
|||
pause_all_vcpus(); |
|||
patch_byte(env, env->eip - 2, 0x66); |
|||
patch_byte(env, env->eip - 1, 0x90); |
|||
resume_all_vcpus(); |
|||
} |
|||
|
|||
if (s->state == VAPIC_ACTIVE) { |
|||
break; |
|||
} |
|||
if (update_rom_mapping(s, env, env->eip) < 0) { |
|||
break; |
|||
} |
|||
if (find_real_tpr_addr(s, env) < 0) { |
|||
break; |
|||
} |
|||
vapic_enable(s, env); |
|||
break; |
|||
default: |
|||
case 4: |
|||
if (!kvm_irqchip_in_kernel()) { |
|||
apic_poll_irq(env->apic_state); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
static const MemoryRegionOps vapic_ops = { |
|||
.write = vapic_write, |
|||
.endianness = DEVICE_NATIVE_ENDIAN, |
|||
}; |
|||
|
|||
static int vapic_init(SysBusDevice *dev) |
|||
{ |
|||
VAPICROMState *s = FROM_SYSBUS(VAPICROMState, dev); |
|||
|
|||
memory_region_init_io(&s->io, &vapic_ops, s, "kvmvapic", 2); |
|||
sysbus_add_io(dev, VAPIC_IO_PORT, &s->io); |
|||
sysbus_init_ioports(dev, VAPIC_IO_PORT, 2); |
|||
|
|||
option_rom[nb_option_roms].name = "kvmvapic.bin"; |
|||
option_rom[nb_option_roms].bootindex = -1; |
|||
nb_option_roms++; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void do_vapic_enable(void *data) |
|||
{ |
|||
VAPICROMState *s = data; |
|||
|
|||
vapic_enable(s, first_cpu); |
|||
} |
|||
|
|||
static int vapic_post_load(void *opaque, int version_id) |
|||
{ |
|||
VAPICROMState *s = opaque; |
|||
uint8_t *zero; |
|||
|
|||
/*
|
|||
* The old implementation of qemu-kvm did not provide the state |
|||
* VAPIC_STANDBY. Reconstruct it. |
|||
*/ |
|||
if (s->state == VAPIC_INACTIVE && s->rom_state_paddr != 0) { |
|||
s->state = VAPIC_STANDBY; |
|||
} |
|||
|
|||
if (s->state != VAPIC_INACTIVE) { |
|||
if (vapic_prepare(s) < 0) { |
|||
return -1; |
|||
} |
|||
} |
|||
if (s->state == VAPIC_ACTIVE) { |
|||
if (smp_cpus == 1) { |
|||
run_on_cpu(first_cpu, do_vapic_enable, s); |
|||
} else { |
|||
zero = g_malloc0(s->rom_state.vapic_size); |
|||
cpu_physical_memory_rw(s->vapic_paddr, zero, |
|||
s->rom_state.vapic_size, 1); |
|||
g_free(zero); |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static const VMStateDescription vmstate_handlers = { |
|||
.name = "kvmvapic-handlers", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_UINT32(set_tpr, VAPICHandlers), |
|||
VMSTATE_UINT32(set_tpr_eax, VAPICHandlers), |
|||
VMSTATE_UINT32_ARRAY(get_tpr, VAPICHandlers, 8), |
|||
VMSTATE_UINT32(get_tpr_stack, VAPICHandlers), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static const VMStateDescription vmstate_guest_rom = { |
|||
.name = "kvmvapic-guest-rom", |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_UNUSED(8), /* signature */ |
|||
VMSTATE_UINT32(vaddr, GuestROMState), |
|||
VMSTATE_UINT32(fixup_start, GuestROMState), |
|||
VMSTATE_UINT32(fixup_end, GuestROMState), |
|||
VMSTATE_UINT32(vapic_vaddr, GuestROMState), |
|||
VMSTATE_UINT32(vapic_size, GuestROMState), |
|||
VMSTATE_UINT32(vcpu_shift, GuestROMState), |
|||
VMSTATE_UINT32(real_tpr_addr, GuestROMState), |
|||
VMSTATE_STRUCT(up, GuestROMState, 0, vmstate_handlers, VAPICHandlers), |
|||
VMSTATE_STRUCT(mp, GuestROMState, 0, vmstate_handlers, VAPICHandlers), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static const VMStateDescription vmstate_vapic = { |
|||
.name = "kvm-tpr-opt", /* compatible with qemu-kvm VAPIC */ |
|||
.version_id = 1, |
|||
.minimum_version_id = 1, |
|||
.minimum_version_id_old = 1, |
|||
.post_load = vapic_post_load, |
|||
.fields = (VMStateField[]) { |
|||
VMSTATE_STRUCT(rom_state, VAPICROMState, 0, vmstate_guest_rom, |
|||
GuestROMState), |
|||
VMSTATE_UINT32(state, VAPICROMState), |
|||
VMSTATE_UINT32(real_tpr_addr, VAPICROMState), |
|||
VMSTATE_UINT32(rom_state_vaddr, VAPICROMState), |
|||
VMSTATE_UINT32(vapic_paddr, VAPICROMState), |
|||
VMSTATE_UINT32(rom_state_paddr, VAPICROMState), |
|||
VMSTATE_END_OF_LIST() |
|||
} |
|||
}; |
|||
|
|||
static void vapic_class_init(ObjectClass *klass, void *data) |
|||
{ |
|||
SysBusDeviceClass *sc = SYS_BUS_DEVICE_CLASS(klass); |
|||
DeviceClass *dc = DEVICE_CLASS(klass); |
|||
|
|||
dc->no_user = 1; |
|||
dc->reset = vapic_reset; |
|||
dc->vmsd = &vmstate_vapic; |
|||
sc->init = vapic_init; |
|||
} |
|||
|
|||
static TypeInfo vapic_type = { |
|||
.name = "kvmvapic", |
|||
.parent = TYPE_SYS_BUS_DEVICE, |
|||
.instance_size = sizeof(VAPICROMState), |
|||
.class_init = vapic_class_init, |
|||
}; |
|||
|
|||
static void vapic_register(void) |
|||
{ |
|||
type_register_static(&vapic_type); |
|||
} |
|||
|
|||
type_init(vapic_register); |
|||
Binary file not shown.
@ -0,0 +1,335 @@ |
|||
# |
|||
# Local APIC acceleration for Windows XP and related guests |
|||
# |
|||
# Copyright 2011 Red Hat, Inc. and/or its affiliates |
|||
# |
|||
# Author: Avi Kivity <avi@redhat.com> |
|||
# |
|||
# This work is licensed under the terms of the GNU GPL, version 2, or (at your |
|||
# option) any later version. See the COPYING file in the top-level directory. |
|||
# |
|||
|
|||
#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 |
|||
push %cs |
|||
pop %es |
|||
xor %ax, %ax |
|||
mov $vapic_size/2, %cx |
|||
lea vapic, %di |
|||
cld |
|||
rep stosw |
|||
pop %es |
|||
|
|||
# announce presence to the hypervisor |
|||
mov $vapic_base, %ax |
|||
out %ax, $0x7e |
|||
|
|||
lret |
|||
|
|||
.code32 |
|||
vapic_size = 2*4096 |
|||
|
|||
.macro fixup delta=-4 |
|||
777: |
|||
.text 1 |
|||
.long 777b + \delta - vapic_base |
|||
.text 0 |
|||
.endm |
|||
|
|||
.macro reenable_vtpr |
|||
out %al, $0x7e |
|||
.endm |
|||
|
|||
.text 1 |
|||
fixup_start = . |
|||
.text 0 |
|||
|
|||
.align 16 |
|||
|
|||
vapic_base: |
|||
.ascii "kvm aPiC" |
|||
|
|||
/* relocation data */ |
|||
.long vapic_base ; fixup |
|||
.long fixup_start ; fixup |
|||
.long fixup_end ; fixup |
|||
|
|||
.long vapic ; fixup |
|||
.long vapic_size |
|||
vcpu_shift: |
|||
.long 0 |
|||
real_tpr: |
|||
.long 0 |
|||
.long up_set_tpr ; fixup |
|||
.long up_set_tpr_eax ; fixup |
|||
.long up_get_tpr_eax ; fixup |
|||
.long up_get_tpr_ecx ; fixup |
|||
.long up_get_tpr_edx ; fixup |
|||
.long up_get_tpr_ebx ; fixup |
|||
.long 0 /* esp. won't work. */ |
|||
.long up_get_tpr_ebp ; fixup |
|||
.long up_get_tpr_esi ; fixup |
|||
.long up_get_tpr_edi ; fixup |
|||
.long up_get_tpr_stack ; fixup |
|||
.long mp_set_tpr ; fixup |
|||
.long mp_set_tpr_eax ; fixup |
|||
.long mp_get_tpr_eax ; fixup |
|||
.long mp_get_tpr_ecx ; fixup |
|||
.long mp_get_tpr_edx ; fixup |
|||
.long mp_get_tpr_ebx ; fixup |
|||
.long 0 /* esp. won't work. */ |
|||
.long mp_get_tpr_ebp ; fixup |
|||
.long mp_get_tpr_esi ; fixup |
|||
.long mp_get_tpr_edi ; fixup |
|||
.long mp_get_tpr_stack ; fixup |
|||
|
|||
.macro kvm_hypercall |
|||
.byte 0x0f, 0x01, 0xc1 |
|||
.endm |
|||
|
|||
kvm_hypercall_vapic_poll_irq = 1 |
|||
|
|||
pcr_cpu = 0x51 |
|||
|
|||
.align 64 |
|||
|
|||
mp_get_tpr_eax: |
|||
pushf |
|||
cli |
|||
reenable_vtpr |
|||
push %ecx |
|||
|
|||
fs/movzbl pcr_cpu, %eax |
|||
|
|||
mov vcpu_shift, %ecx ; fixup |
|||
shl %cl, %eax |
|||
testb $1, vapic+4(%eax) ; fixup delta=-5 |
|||
jz mp_get_tpr_bad |
|||
movzbl vapic(%eax), %eax ; fixup |
|||
|
|||
mp_get_tpr_out: |
|||
pop %ecx |
|||
popf |
|||
ret |
|||
|
|||
mp_get_tpr_bad: |
|||
mov real_tpr, %eax ; fixup |
|||
mov (%eax), %eax |
|||
jmp mp_get_tpr_out |
|||
|
|||
mp_get_tpr_ebx: |
|||
mov %eax, %ebx |
|||
call mp_get_tpr_eax |
|||
xchg %eax, %ebx |
|||
ret |
|||
|
|||
mp_get_tpr_ecx: |
|||
mov %eax, %ecx |
|||
call mp_get_tpr_eax |
|||
xchg %eax, %ecx |
|||
ret |
|||
|
|||
mp_get_tpr_edx: |
|||
mov %eax, %edx |
|||
call mp_get_tpr_eax |
|||
xchg %eax, %edx |
|||
ret |
|||
|
|||
mp_get_tpr_esi: |
|||
mov %eax, %esi |
|||
call mp_get_tpr_eax |
|||
xchg %eax, %esi |
|||
ret |
|||
|
|||
mp_get_tpr_edi: |
|||
mov %eax, %edi |
|||
call mp_get_tpr_edi |
|||
xchg %eax, %edi |
|||
ret |
|||
|
|||
mp_get_tpr_ebp: |
|||
mov %eax, %ebp |
|||
call mp_get_tpr_eax |
|||
xchg %eax, %ebp |
|||
ret |
|||
|
|||
mp_get_tpr_stack: |
|||
call mp_get_tpr_eax |
|||
xchg %eax, 4(%esp) |
|||
ret |
|||
|
|||
mp_set_tpr_eax: |
|||
push %eax |
|||
call mp_set_tpr |
|||
ret |
|||
|
|||
mp_set_tpr: |
|||
pushf |
|||
push %eax |
|||
push %ecx |
|||
push %edx |
|||
push %ebx |
|||
cli |
|||
reenable_vtpr |
|||
|
|||
mp_set_tpr_failed: |
|||
fs/movzbl pcr_cpu, %edx |
|||
|
|||
mov vcpu_shift, %ecx ; fixup |
|||
shl %cl, %edx |
|||
|
|||
testb $1, vapic+4(%edx) ; fixup delta=-5 |
|||
jz mp_set_tpr_bad |
|||
|
|||
mov vapic(%edx), %eax ; fixup |
|||
|
|||
mov %eax, %ebx |
|||
mov 24(%esp), %bl |
|||
|
|||
/* %ebx = new vapic (%bl = tpr, %bh = isr, %b3 = irr) */ |
|||
|
|||
lock cmpxchg %ebx, vapic(%edx) ; fixup |
|||
jnz mp_set_tpr_failed |
|||
|
|||
/* compute ppr */ |
|||
cmp %bh, %bl |
|||
jae mp_tpr_is_bigger |
|||
mp_isr_is_bigger: |
|||
mov %bh, %bl |
|||
mp_tpr_is_bigger: |
|||
/* %bl = ppr */ |
|||
rol $8, %ebx |
|||
/* now: %bl = irr, %bh = ppr */ |
|||
cmp %bh, %bl |
|||
ja mp_set_tpr_poll_irq |
|||
|
|||
mp_set_tpr_out: |
|||
pop %ebx |
|||
pop %edx |
|||
pop %ecx |
|||
pop %eax |
|||
popf |
|||
ret $4 |
|||
|
|||
mp_set_tpr_poll_irq: |
|||
mov $kvm_hypercall_vapic_poll_irq, %eax |
|||
kvm_hypercall |
|||
jmp mp_set_tpr_out |
|||
|
|||
mp_set_tpr_bad: |
|||
mov 24(%esp), %ecx |
|||
mov real_tpr, %eax ; fixup |
|||
mov %ecx, (%eax) |
|||
jmp mp_set_tpr_out |
|||
|
|||
up_get_tpr_eax: |
|||
reenable_vtpr |
|||
movzbl vapic, %eax ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_ebx: |
|||
reenable_vtpr |
|||
movzbl vapic, %ebx ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_ecx: |
|||
reenable_vtpr |
|||
movzbl vapic, %ecx ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_edx: |
|||
reenable_vtpr |
|||
movzbl vapic, %edx ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_esi: |
|||
reenable_vtpr |
|||
movzbl vapic, %esi ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_edi: |
|||
reenable_vtpr |
|||
movzbl vapic, %edi ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_ebp: |
|||
reenable_vtpr |
|||
movzbl vapic, %ebp ; fixup |
|||
ret |
|||
|
|||
up_get_tpr_stack: |
|||
reenable_vtpr |
|||
movzbl vapic, %eax ; fixup |
|||
xchg %eax, 4(%esp) |
|||
ret |
|||
|
|||
up_set_tpr_eax: |
|||
push %eax |
|||
call up_set_tpr |
|||
ret |
|||
|
|||
up_set_tpr: |
|||
pushf |
|||
push %eax |
|||
push %ebx |
|||
reenable_vtpr |
|||
|
|||
up_set_tpr_failed: |
|||
mov vapic, %eax ; fixup |
|||
|
|||
mov %eax, %ebx |
|||
mov 16(%esp), %bl |
|||
|
|||
/* %ebx = new vapic (%bl = tpr, %bh = isr, %b3 = irr) */ |
|||
|
|||
lock cmpxchg %ebx, vapic ; fixup |
|||
jnz up_set_tpr_failed |
|||
|
|||
/* compute ppr */ |
|||
cmp %bh, %bl |
|||
jae up_tpr_is_bigger |
|||
up_isr_is_bigger: |
|||
mov %bh, %bl |
|||
up_tpr_is_bigger: |
|||
/* %bl = ppr */ |
|||
rol $8, %ebx |
|||
/* now: %bl = irr, %bh = ppr */ |
|||
cmp %bh, %bl |
|||
ja up_set_tpr_poll_irq |
|||
|
|||
up_set_tpr_out: |
|||
pop %ebx |
|||
pop %eax |
|||
popf |
|||
ret $4 |
|||
|
|||
up_set_tpr_poll_irq: |
|||
mov $kvm_hypercall_vapic_poll_irq, %eax |
|||
kvm_hypercall |
|||
jmp up_set_tpr_out |
|||
|
|||
.text 1 |
|||
fixup_end = . |
|||
.text 0 |
|||
|
|||
/* |
|||
* vapic format: |
|||
* per-vcpu records of size 2^vcpu shift. |
|||
* byte 0: tpr (r/w) |
|||
* byte 1: highest in-service interrupt (isr) (r/o); bits 3:0 are zero |
|||
* byte 2: zero (r/o) |
|||
* byte 3: highest pending interrupt (irr) (r/o) |
|||
*/ |
|||
.text 2 |
|||
|
|||
.align 128 |
|||
|
|||
vapic: |
|||
. = . + vapic_size |
|||
|
|||
OPTION_ROM_END |
|||
Loading…
Reference in new issue