| /* |
| * KQEMU support |
| * |
| * Copyright (c) 2005 Fabrice Bellard |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include "config.h" |
| #ifdef _WIN32 |
| #include <windows.h> |
| #else |
| #include <sys/types.h> |
| #include <sys/mman.h> |
| #endif |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <inttypes.h> |
| |
| #include "cpu.h" |
| #include "exec-all.h" |
| |
| #ifdef USE_KQEMU |
| |
| #define DEBUG |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include "kqemu/kqemu.h" |
| |
| #define KQEMU_DEVICE "/dev/kqemu" |
| |
| int kqemu_allowed = 1; |
| int kqemu_fd = -1; |
| unsigned long *pages_to_flush; |
| unsigned int nb_pages_to_flush; |
| extern uint32_t **l1_phys_map; |
| |
| #define cpuid(index, eax, ebx, ecx, edx) \ |
| asm volatile ("cpuid" \ |
| : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) \ |
| : "0" (index)) |
| |
| static int is_cpuid_supported(void) |
| { |
| int v0, v1; |
| asm volatile ("pushf\n" |
| "popl %0\n" |
| "movl %0, %1\n" |
| "xorl $0x00200000, %0\n" |
| "pushl %0\n" |
| "popf\n" |
| "pushf\n" |
| "popl %0\n" |
| : "=a" (v0), "=d" (v1) |
| : |
| : "cc"); |
| return (v0 != v1); |
| } |
| |
| static void kqemu_update_cpuid(CPUState *env) |
| { |
| int critical_features_mask, features; |
| uint32_t eax, ebx, ecx, edx; |
| |
| /* the following features are kept identical on the host and |
| target cpus because they are important for user code. Strictly |
| speaking, only SSE really matters because the OS must support |
| it if the user code uses it. */ |
| critical_features_mask = |
| CPUID_CMOV | CPUID_CX8 | |
| CPUID_FXSR | CPUID_MMX | CPUID_SSE | |
| CPUID_SSE2; |
| if (!is_cpuid_supported()) { |
| features = 0; |
| } else { |
| cpuid(1, eax, ebx, ecx, edx); |
| features = edx; |
| } |
| env->cpuid_features = (env->cpuid_features & ~critical_features_mask) | |
| (features & critical_features_mask); |
| /* XXX: we could update more of the target CPUID state so that the |
| non accelerated code sees exactly the same CPU features as the |
| accelerated code */ |
| } |
| |
| int kqemu_init(CPUState *env) |
| { |
| struct kqemu_init init; |
| int ret, version; |
| |
| if (!kqemu_allowed) |
| return -1; |
| |
| kqemu_fd = open(KQEMU_DEVICE, O_RDWR); |
| if (kqemu_fd < 0) { |
| fprintf(stderr, "Could not open '%s' - QEMU acceleration layer not activated\n", KQEMU_DEVICE); |
| return -1; |
| } |
| version = 0; |
| ioctl(kqemu_fd, KQEMU_GET_VERSION, &version); |
| if (version != KQEMU_VERSION) { |
| fprintf(stderr, "Version mismatch between kqemu module and qemu (%08x %08x) - disabling kqemu use\n", |
| version, KQEMU_VERSION); |
| goto fail; |
| } |
| |
| pages_to_flush = qemu_vmalloc(KQEMU_MAX_PAGES_TO_FLUSH * |
| sizeof(unsigned long)); |
| if (!pages_to_flush) |
| goto fail; |
| |
| init.ram_base = phys_ram_base; |
| init.ram_size = phys_ram_size; |
| init.ram_dirty = phys_ram_dirty; |
| init.phys_to_ram_map = l1_phys_map; |
| init.pages_to_flush = pages_to_flush; |
| ret = ioctl(kqemu_fd, KQEMU_INIT, &init); |
| if (ret < 0) { |
| fprintf(stderr, "Error %d while initializing QEMU acceleration layer - disabling it for now\n", ret); |
| fail: |
| close(kqemu_fd); |
| kqemu_fd = -1; |
| return -1; |
| } |
| kqemu_update_cpuid(env); |
| env->kqemu_enabled = 1; |
| nb_pages_to_flush = 0; |
| return 0; |
| } |
| |
| void kqemu_flush_page(CPUState *env, target_ulong addr) |
| { |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu_flush_page: addr=" TARGET_FMT_lx "\n", addr); |
| } |
| #endif |
| if (nb_pages_to_flush >= KQEMU_MAX_PAGES_TO_FLUSH) |
| nb_pages_to_flush = KQEMU_FLUSH_ALL; |
| else |
| pages_to_flush[nb_pages_to_flush++] = addr; |
| } |
| |
| void kqemu_flush(CPUState *env, int global) |
| { |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu_flush:\n"); |
| } |
| #endif |
| nb_pages_to_flush = KQEMU_FLUSH_ALL; |
| } |
| |
| struct fpstate { |
| uint16_t fpuc; |
| uint16_t dummy1; |
| uint16_t fpus; |
| uint16_t dummy2; |
| uint16_t fptag; |
| uint16_t dummy3; |
| |
| uint32_t fpip; |
| uint32_t fpcs; |
| uint32_t fpoo; |
| uint32_t fpos; |
| uint8_t fpregs1[8 * 10]; |
| }; |
| |
| struct fpxstate { |
| uint16_t fpuc; |
| uint16_t fpus; |
| uint16_t fptag; |
| uint16_t fop; |
| uint32_t fpuip; |
| uint16_t cs_sel; |
| uint16_t dummy0; |
| uint32_t fpudp; |
| uint16_t ds_sel; |
| uint16_t dummy1; |
| uint32_t mxcsr; |
| uint32_t mxcsr_mask; |
| uint8_t fpregs1[8 * 16]; |
| uint8_t xmm_regs[8 * 16]; |
| uint8_t dummy2[224]; |
| }; |
| |
| static struct fpxstate fpx1 __attribute__((aligned(16))); |
| |
| static void restore_native_fp_frstor(CPUState *env) |
| { |
| int fptag, i, j; |
| struct fpstate fp1, *fp = &fp1; |
| |
| fp->fpuc = env->fpuc; |
| fp->fpus = (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11; |
| fptag = 0; |
| for (i=7; i>=0; i--) { |
| fptag <<= 2; |
| if (env->fptags[i]) { |
| fptag |= 3; |
| } else { |
| /* the FPU automatically computes it */ |
| } |
| } |
| fp->fptag = fptag; |
| j = env->fpstt; |
| for(i = 0;i < 8; i++) { |
| memcpy(&fp->fpregs1[i * 10], &env->fpregs[j].d, 10); |
| j = (j + 1) & 7; |
| } |
| asm volatile ("frstor %0" : "=m" (*fp)); |
| } |
| |
| static void save_native_fp_fsave(CPUState *env) |
| { |
| int fptag, i, j; |
| uint16_t fpuc; |
| struct fpstate fp1, *fp = &fp1; |
| |
| asm volatile ("fsave %0" : : "m" (*fp)); |
| env->fpuc = fp->fpuc; |
| env->fpstt = (fp->fpus >> 11) & 7; |
| env->fpus = fp->fpus & ~0x3800; |
| fptag = fp->fptag; |
| for(i = 0;i < 8; i++) { |
| env->fptags[i] = ((fptag & 3) == 3); |
| fptag >>= 2; |
| } |
| j = env->fpstt; |
| for(i = 0;i < 8; i++) { |
| memcpy(&env->fpregs[j].d, &fp->fpregs1[i * 10], 10); |
| j = (j + 1) & 7; |
| } |
| /* we must restore the default rounding state */ |
| fpuc = 0x037f | (env->fpuc & (3 << 10)); |
| asm volatile("fldcw %0" : : "m" (fpuc)); |
| } |
| |
| static void restore_native_fp_fxrstor(CPUState *env) |
| { |
| struct fpxstate *fp = &fpx1; |
| int i, j, fptag; |
| |
| fp->fpuc = env->fpuc; |
| fp->fpus = (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11; |
| fptag = 0; |
| for(i = 0; i < 8; i++) |
| fptag |= (env->fptags[i] << i); |
| fp->fptag = fptag ^ 0xff; |
| |
| j = env->fpstt; |
| for(i = 0;i < 8; i++) { |
| memcpy(&fp->fpregs1[i * 16], &env->fpregs[j].d, 10); |
| j = (j + 1) & 7; |
| } |
| if (env->cpuid_features & CPUID_SSE) { |
| fp->mxcsr = env->mxcsr; |
| /* XXX: check if DAZ is not available */ |
| fp->mxcsr_mask = 0xffff; |
| memcpy(fp->xmm_regs, env->xmm_regs, 8 * 16); |
| } |
| asm volatile ("fxrstor %0" : "=m" (*fp)); |
| } |
| |
| static void save_native_fp_fxsave(CPUState *env) |
| { |
| struct fpxstate *fp = &fpx1; |
| int fptag, i, j; |
| uint16_t fpuc; |
| |
| asm volatile ("fxsave %0" : : "m" (*fp)); |
| env->fpuc = fp->fpuc; |
| env->fpstt = (fp->fpus >> 11) & 7; |
| env->fpus = fp->fpus & ~0x3800; |
| fptag = fp->fptag ^ 0xff; |
| for(i = 0;i < 8; i++) { |
| env->fptags[i] = (fptag >> i) & 1; |
| } |
| j = env->fpstt; |
| for(i = 0;i < 8; i++) { |
| memcpy(&env->fpregs[j].d, &fp->fpregs1[i * 16], 10); |
| j = (j + 1) & 7; |
| } |
| if (env->cpuid_features & CPUID_SSE) { |
| env->mxcsr = fp->mxcsr; |
| memcpy(env->xmm_regs, fp->xmm_regs, 8 * 16); |
| } |
| |
| /* we must restore the default rounding state */ |
| asm volatile ("fninit"); |
| fpuc = 0x037f | (env->fpuc & (3 << 10)); |
| asm volatile("fldcw %0" : : "m" (fpuc)); |
| } |
| |
| int kqemu_cpu_exec(CPUState *env) |
| { |
| struct kqemu_cpu_state kcpu_state, *kenv = &kcpu_state; |
| int ret; |
| |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu: cpu_exec: enter\n"); |
| cpu_dump_state(env, logfile, fprintf, 0); |
| } |
| #endif |
| memcpy(kenv->regs, env->regs, sizeof(kenv->regs)); |
| kenv->eip = env->eip; |
| kenv->eflags = env->eflags; |
| memcpy(&kenv->segs, &env->segs, sizeof(env->segs)); |
| memcpy(&kenv->ldt, &env->ldt, sizeof(env->ldt)); |
| memcpy(&kenv->tr, &env->tr, sizeof(env->tr)); |
| memcpy(&kenv->gdt, &env->gdt, sizeof(env->gdt)); |
| memcpy(&kenv->idt, &env->idt, sizeof(env->idt)); |
| kenv->cr0 = env->cr[0]; |
| kenv->cr2 = env->cr[2]; |
| kenv->cr3 = env->cr[3]; |
| kenv->cr4 = env->cr[4]; |
| kenv->a20_mask = env->a20_mask; |
| if (env->dr[7] & 0xff) { |
| kenv->dr7 = env->dr[7]; |
| kenv->dr0 = env->dr[0]; |
| kenv->dr1 = env->dr[1]; |
| kenv->dr2 = env->dr[2]; |
| kenv->dr3 = env->dr[3]; |
| } else { |
| kenv->dr7 = 0; |
| } |
| kenv->dr6 = env->dr[6]; |
| kenv->cpl = 3; |
| kenv->nb_pages_to_flush = nb_pages_to_flush; |
| nb_pages_to_flush = 0; |
| |
| if (!(kenv->cr0 & CR0_TS_MASK)) { |
| if (env->cpuid_features & CPUID_FXSR) |
| restore_native_fp_fxrstor(env); |
| else |
| restore_native_fp_frstor(env); |
| } |
| |
| ret = ioctl(kqemu_fd, KQEMU_EXEC, kenv); |
| |
| if (!(kenv->cr0 & CR0_TS_MASK)) { |
| if (env->cpuid_features & CPUID_FXSR) |
| save_native_fp_fxsave(env); |
| else |
| save_native_fp_fsave(env); |
| } |
| |
| memcpy(env->regs, kenv->regs, sizeof(env->regs)); |
| env->eip = kenv->eip; |
| env->eflags = kenv->eflags; |
| memcpy(env->segs, kenv->segs, sizeof(env->segs)); |
| #if 0 |
| /* no need to restore that */ |
| memcpy(env->ldt, kenv->ldt, sizeof(env->ldt)); |
| memcpy(env->tr, kenv->tr, sizeof(env->tr)); |
| memcpy(env->gdt, kenv->gdt, sizeof(env->gdt)); |
| memcpy(env->idt, kenv->idt, sizeof(env->idt)); |
| env->cr[0] = kenv->cr0; |
| env->cr[3] = kenv->cr3; |
| env->cr[4] = kenv->cr4; |
| env->a20_mask = kenv->a20_mask; |
| #endif |
| env->cr[2] = kenv->cr2; |
| env->dr[6] = kenv->dr6; |
| |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu: kqemu_cpu_exec: ret=0x%x\n", ret); |
| } |
| #endif |
| if ((ret & 0xff00) == KQEMU_RET_INT) { |
| env->exception_index = ret & 0xff; |
| env->error_code = 0; |
| env->exception_is_int = 1; |
| env->exception_next_eip = kenv->next_eip; |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu: interrupt v=%02x:\n", |
| env->exception_index); |
| cpu_dump_state(env, logfile, fprintf, 0); |
| } |
| #endif |
| return 1; |
| } else if ((ret & 0xff00) == KQEMU_RET_EXCEPTION) { |
| env->exception_index = ret & 0xff; |
| env->error_code = kenv->error_code; |
| env->exception_is_int = 0; |
| env->exception_next_eip = 0; |
| #ifdef DEBUG |
| if (loglevel & CPU_LOG_INT) { |
| fprintf(logfile, "kqemu: exception v=%02x e=%04x:\n", |
| env->exception_index, env->error_code); |
| cpu_dump_state(env, logfile, fprintf, 0); |
| } |
| #endif |
| return 1; |
| } else if (ret == KQEMU_RET_INTR) { |
| return 0; |
| } else if (ret == KQEMU_RET_SOFTMMU) { |
| return 2; |
| } else { |
| cpu_dump_state(env, stderr, fprintf, 0); |
| fprintf(stderr, "Unsupported return value: 0x%x\n", ret); |
| exit(1); |
| } |
| return 0; |
| } |
| |
| #endif |