428 lines
12 KiB
C
428 lines
12 KiB
C
|
/*
|
||
|
* 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
|