
#include "config.h"
#include "config-host.h"

#ifdef USE_KVM
 #define KVM_ALLOWED_DEFAULT 1
#else
 #define KVM_ALLOWED_DEFAULT 0
#endif

int kvm_allowed = KVM_ALLOWED_DEFAULT;
int kvm_irqchip = 1;

#ifdef USE_KVM

#include <string.h>
#include "hw/hw.h"
#include "sysemu.h"

#include "qemu-kvm.h"
#include <libkvm.h>
#include <pthread.h>
#include <sys/utsname.h>

extern void perror(const char *s);

kvm_context_t kvm_context;

extern int smp_cpus;

pthread_mutex_t qemu_mutex = PTHREAD_MUTEX_INITIALIZER;
__thread CPUState *vcpu_env;

static sigset_t io_sigset, io_negsigset;

static int wait_hack;

#define SIG_IPI (SIGRTMIN+4)

struct vcpu_info {
    int sipi_needed;
    int init;
    pthread_t thread;
    int signalled;
    int stop;
    int stopped;
} vcpu_info[4];

static void sig_ipi_handler(int n)
{
}

void kvm_update_interrupt_request(CPUState *env)
{
    if (env && env != vcpu_env) {
	if (vcpu_info[env->cpu_index].signalled)
	    return;
	vcpu_info[env->cpu_index].signalled = 1;
	if (vcpu_info[env->cpu_index].thread)
	    pthread_kill(vcpu_info[env->cpu_index].thread, SIG_IPI);
    }
}

void kvm_update_after_sipi(CPUState *env)
{
    vcpu_info[env->cpu_index].sipi_needed = 1;
    kvm_update_interrupt_request(env);

    /*
     * the qemu bios waits using a busy loop that's much too short for
     * kvm.  add a wait after the first sipi.
     */
    {
	static int first_sipi = 1;

	if (first_sipi) {
	    wait_hack = 1;
	    first_sipi = 0;
	}
    }
}

void kvm_apic_init(CPUState *env)
{
    if (env->cpu_index != 0)
	vcpu_info[env->cpu_index].init = 1;
    kvm_update_interrupt_request(env);
}

#include <signal.h>

static int try_push_interrupts(void *opaque)
{
    return kvm_arch_try_push_interrupts(opaque);
}

static void post_kvm_run(void *opaque, int vcpu)
{

    pthread_mutex_lock(&qemu_mutex);
    kvm_arch_post_kvm_run(opaque, vcpu);
}

static int pre_kvm_run(void *opaque, int vcpu)
{
    CPUState *env = cpu_single_env;

    if (env->cpu_index == 0 && wait_hack) {
	int i;

	wait_hack = 0;

	pthread_mutex_unlock(&qemu_mutex);
	for (i = 0; i < 10; ++i)
	    usleep(1000);
	pthread_mutex_lock(&qemu_mutex);
    }

    kvm_arch_pre_kvm_run(opaque, vcpu);

    if (env->interrupt_request & CPU_INTERRUPT_EXIT)
	return 1;
    pthread_mutex_unlock(&qemu_mutex);
    return 0;
}

void kvm_load_registers(CPUState *env)
{
    if (kvm_allowed)
	kvm_arch_load_regs(env);
}

void kvm_save_registers(CPUState *env)
{
    if (kvm_allowed)
	kvm_arch_save_regs(env);
}

int kvm_cpu_exec(CPUState *env)
{
    int r;

    r = kvm_run(kvm_context, env->cpu_index);
    if (r < 0) {
        printf("kvm_run returned %d\n", r);
        exit(1);
    }

    return 0;
}

extern int vm_running;

static int has_work(CPUState *env)
{
    if (!vm_running)
	return 0;
    if (!(env->hflags & HF_HALTED_MASK))
	return 1;
    return kvm_arch_has_work(env);
}

static int kvm_eat_signal(CPUState *env, int timeout)
{
    struct timespec ts;
    int r, e, ret = 0;
    siginfo_t siginfo;
    struct sigaction sa;

    ts.tv_sec = timeout / 1000;
    ts.tv_nsec = (timeout % 1000) * 1000000;
    r = sigtimedwait(&io_sigset, &siginfo, &ts);
    if (r == -1 && (errno == EAGAIN || errno == EINTR) && !timeout)
	return 0;
    e = errno;
    pthread_mutex_lock(&qemu_mutex);
    cpu_single_env = vcpu_env;
    if (r == -1 && !(errno == EAGAIN || errno == EINTR)) {
	printf("sigtimedwait: %s\n", strerror(e));
	exit(1);
    }
    if (r != -1) {
	sigaction(siginfo.si_signo, NULL, &sa);
	sa.sa_handler(siginfo.si_signo);
	ret = 1;
    }
    pthread_mutex_unlock(&qemu_mutex);

    return ret;
}


static void kvm_eat_signals(CPUState *env, int timeout)
{
    int r = 0;

    while (kvm_eat_signal(env, 0))
	r = 1;
    if (!r && timeout) {
	r = kvm_eat_signal(env, timeout);
	if (r)
	    while (kvm_eat_signal(env, 0))
		;
    }
    /*
     * we call select() even if no signal was received, to account for
     * for which there is no signal handler installed.
     */
    pthread_mutex_lock(&qemu_mutex);
    cpu_single_env = vcpu_env;
    main_loop_wait(0);
    pthread_mutex_unlock(&qemu_mutex);
}

static void kvm_main_loop_wait(CPUState *env, int timeout)
{
    pthread_mutex_unlock(&qemu_mutex);
    if (env->cpu_index == 0)
	kvm_eat_signals(env, timeout);
    else {
	if (!kvm_irqchip_in_kernel(kvm_context) &&
	    (timeout || vcpu_info[env->cpu_index].stopped)) {
	    sigset_t set;
	    int n;

	paused:
	    sigemptyset(&set);
	    sigaddset(&set, SIG_IPI);
	    sigwait(&set, &n);
	} else {
	    struct timespec ts;
	    siginfo_t siginfo;
	    sigset_t set;

	    ts.tv_sec = 0;
	    ts.tv_nsec = 0;
	    sigemptyset(&set);
	    sigaddset(&set, SIG_IPI);
	    sigtimedwait(&set, &siginfo, &ts);
	}
	if (vcpu_info[env->cpu_index].stop) {
	    vcpu_info[env->cpu_index].stop = 0;
	    vcpu_info[env->cpu_index].stopped = 1;
	    pthread_kill(vcpu_info[0].thread, SIG_IPI);
	    goto paused;
	}
    }
    pthread_mutex_lock(&qemu_mutex);
    cpu_single_env = env;
    vcpu_info[env->cpu_index].signalled = 0;
}

static int all_threads_paused(void)
{
    int i;

    for (i = 1; i < smp_cpus; ++i)
	if (vcpu_info[i].stopped)
	    return 0;
    return 1;
}

static void pause_other_threads(void)
{
    int i;

    for (i = 1; i < smp_cpus; ++i) {
	vcpu_info[i].stop = 1;
	pthread_kill(vcpu_info[i].thread, SIG_IPI);
    }
    while (!all_threads_paused())
	kvm_eat_signals(vcpu_env, 0);
}

static void resume_other_threads(void)
{
    int i;

    for (i = 1; i < smp_cpus; ++i) {
	vcpu_info[i].stop = 0;
	vcpu_info[i].stopped = 0;
	pthread_kill(vcpu_info[i].thread, SIG_IPI);
    }
}

static void kvm_vm_state_change_handler(void *context, int running)
{
    if (running)
	resume_other_threads();
    else
	pause_other_threads();
}

static void update_regs_for_sipi(CPUState *env)
{
    kvm_arch_update_regs_for_sipi(env);
    vcpu_info[env->cpu_index].sipi_needed = 0;
    vcpu_info[env->cpu_index].init = 0;
}

static void update_regs_for_init(CPUState *env)
{
    cpu_reset(env);
    kvm_arch_load_regs(env);
}

static void setup_kernel_sigmask(CPUState *env)
{
    sigset_t set;

    sigprocmask(SIG_BLOCK, NULL, &set);
    sigdelset(&set, SIG_IPI);
    if (env->cpu_index == 0)
	sigandset(&set, &set, &io_negsigset);
    
    kvm_set_signal_mask(kvm_context, env->cpu_index, &set);
}

static int kvm_main_loop_cpu(CPUState *env)
{
    struct vcpu_info *info = &vcpu_info[env->cpu_index];

    setup_kernel_sigmask(env);
    pthread_mutex_lock(&qemu_mutex);

    kvm_qemu_init_env(env);
    env->ready_for_interrupt_injection = 1;

    cpu_single_env = env;
    while (1) {
	while (!has_work(env))
	    kvm_main_loop_wait(env, 10);
	if (env->interrupt_request & CPU_INTERRUPT_HARD)
	    env->hflags &= ~HF_HALTED_MASK;
	if (!kvm_irqchip_in_kernel(kvm_context) && info->sipi_needed)
	    update_regs_for_sipi(env);
	if (!kvm_irqchip_in_kernel(kvm_context) && info->init)
	    update_regs_for_init(env);
	if (!(env->hflags & HF_HALTED_MASK) && !info->init)
	    kvm_cpu_exec(env);
	env->interrupt_request &= ~CPU_INTERRUPT_EXIT;
	kvm_main_loop_wait(env, 0);
	if (qemu_shutdown_requested())
	    break;
	else if (qemu_powerdown_requested())
	    qemu_system_powerdown();
	else if (qemu_reset_requested()) {
	    env->interrupt_request = 0;
	    qemu_system_reset();
	    kvm_arch_load_regs(env);
	}
    }
    pthread_mutex_unlock(&qemu_mutex);
    return 0;
}

static void *ap_main_loop(void *_env)
{
    CPUState *env = _env;
    sigset_t signals;

    vcpu_env = env;
    sigfillset(&signals);
    //sigdelset(&signals, SIG_IPI);
    sigprocmask(SIG_BLOCK, &signals, NULL);
    kvm_create_vcpu(kvm_context, env->cpu_index);
    kvm_qemu_init_env(env);
    if (kvm_irqchip_in_kernel(kvm_context))
	env->hflags &= ~HF_HALTED_MASK;
    kvm_main_loop_cpu(env);
    return NULL;
}

static void kvm_add_signal(int signum)
{
    sigaddset(&io_sigset, signum);
    sigdelset(&io_negsigset, signum);
    sigprocmask(SIG_BLOCK,  &io_sigset, NULL);
}

int kvm_init_ap(void)
{
    CPUState *env = first_cpu->next_cpu;
    int i;

    qemu_add_vm_change_state_handler(kvm_vm_state_change_handler, NULL);
    sigemptyset(&io_sigset);
    sigfillset(&io_negsigset);
    kvm_add_signal(SIGIO);
    kvm_add_signal(SIGALRM);
    kvm_add_signal(SIGUSR2);
    if (!kvm_irqchip_in_kernel(kvm_context))
        kvm_add_signal(SIG_IPI);

    vcpu_env = first_cpu;
    signal(SIG_IPI, sig_ipi_handler);
    for (i = 1; i < smp_cpus; ++i) {
	pthread_create(&vcpu_info[i].thread, NULL, ap_main_loop, env);
	env = env->next_cpu;
    }
    return 0;
}

int kvm_main_loop(void)
{
    vcpu_info[0].thread = pthread_self();
    return kvm_main_loop_cpu(first_cpu);
}

static int kvm_debug(void *opaque, int vcpu)
{
    CPUState *env = cpu_single_env;

    env->exception_index = EXCP_DEBUG;
    return 1;
}

static int kvm_inb(void *opaque, uint16_t addr, uint8_t *data)
{
    *data = cpu_inb(0, addr);
    return 0;
}

static int kvm_inw(void *opaque, uint16_t addr, uint16_t *data)
{
    *data = cpu_inw(0, addr);
    return 0;
}

static int kvm_inl(void *opaque, uint16_t addr, uint32_t *data)
{
    *data = cpu_inl(0, addr);
    return 0;
}

#define PM_IO_BASE 0xb000

static int kvm_outb(void *opaque, uint16_t addr, uint8_t data)
{
    if (addr == 0xb2) {
	switch (data) {
	case 0: {
	    cpu_outb(0, 0xb3, 0);
	    break;
	}
	case 0xf0: {
	    unsigned x;

	    /* enable acpi */
	    x = cpu_inw(0, PM_IO_BASE + 4);
	    x &= ~1;
	    cpu_outw(0, PM_IO_BASE + 4, x);
	    break;
	}
	case 0xf1: {
	    unsigned x;

	    /* enable acpi */
	    x = cpu_inw(0, PM_IO_BASE + 4);
	    x |= 1;
	    cpu_outw(0, PM_IO_BASE + 4, x);
	    break;
	}
	default:
	    break;
	}
	return 0;
    }
    cpu_outb(0, addr, data);
    return 0;
}

static int kvm_outw(void *opaque, uint16_t addr, uint16_t data)
{
    cpu_outw(0, addr, data);
    return 0;
}

static int kvm_outl(void *opaque, uint16_t addr, uint32_t data)
{
    cpu_outl(0, addr, data);
    return 0;
}

static int kvm_readb(void *opaque, uint64_t addr, uint8_t *data)
{
    *data = ldub_phys(addr);
    return 0;
}
 
static int kvm_readw(void *opaque, uint64_t addr, uint16_t *data)
{
    *data = lduw_phys(addr);
    return 0;
}

static int kvm_readl(void *opaque, uint64_t addr, uint32_t *data)
{
    /* hack: Red Hat 7.1 generates some wierd accesses. */
    if (addr > 0xa0000 - 4 && addr < 0xa0000) {
	*data = 0;
	return 0;
    }

    *data = ldl_phys(addr);
    return 0;
}

static int kvm_readq(void *opaque, uint64_t addr, uint64_t *data)
{
    *data = ldq_phys(addr);
    return 0;
}

static int kvm_writeb(void *opaque, uint64_t addr, uint8_t data)
{
    stb_phys(addr, data);
    return 0;
}

static int kvm_writew(void *opaque, uint64_t addr, uint16_t data)
{
    stw_phys(addr, data);
    return 0;
}

static int kvm_writel(void *opaque, uint64_t addr, uint32_t data)
{
    stl_phys(addr, data);
    return 0;
}

static int kvm_writeq(void *opaque, uint64_t addr, uint64_t data)
{
    stq_phys(addr, data);
    return 0;
}

static int kvm_io_window(void *opaque)
{
    return 1;
}

 
static int kvm_halt(void *opaque, int vcpu)
{
    return kvm_arch_halt(opaque, vcpu);
}

static int kvm_shutdown(void *opaque, int vcpu)
{
    qemu_system_reset_request();
    return 1;
}
 
static struct kvm_callbacks qemu_kvm_ops = {
    .debug = kvm_debug,
    .inb   = kvm_inb,
    .inw   = kvm_inw,
    .inl   = kvm_inl,
    .outb  = kvm_outb,
    .outw  = kvm_outw,
    .outl  = kvm_outl,
    .readb = kvm_readb,
    .readw = kvm_readw,
    .readl = kvm_readl,
    .readq = kvm_readq,
    .writeb = kvm_writeb,
    .writew = kvm_writew,
    .writel = kvm_writel,
    .writeq = kvm_writeq,
    .halt  = kvm_halt,
    .shutdown = kvm_shutdown,
    .io_window = kvm_io_window,
    .try_push_interrupts = try_push_interrupts,
    .post_kvm_run = post_kvm_run,
    .pre_kvm_run = pre_kvm_run,
};

int kvm_qemu_init()
{
    /* Try to initialize kvm */
    kvm_context = kvm_init(&qemu_kvm_ops, cpu_single_env);
    if (!kvm_context) {
      	return -1;
    }

    return 0;
}

int kvm_qemu_create_context(void)
{
    int r;
    if (!kvm_irqchip) {
        kvm_disable_irqchip_creation(kvm_context);
    }
    if (kvm_create(kvm_context, phys_ram_size, (void**)&phys_ram_base) < 0) {
	kvm_qemu_destroy();
	return -1;
    }
    r = kvm_arch_qemu_create_context();
    if(r <0)
	kvm_qemu_destroy();
    return 0;
}

void kvm_qemu_destroy(void)
{
    kvm_finalize(kvm_context);
}

void kvm_cpu_register_physical_memory(target_phys_addr_t start_addr,
                                      unsigned long size,
                                      unsigned long phys_offset)
{
#ifdef KVM_CAP_USER_MEMORY
    int r = 0;

    r = kvm_check_extension(kvm_context, KVM_CAP_USER_MEMORY);
    if (r) {
        if (!(phys_offset & ~TARGET_PAGE_MASK)) {
                r = kvm_is_allocated_mem(kvm_context, start_addr, size);
            if (r)
                return;
            r = kvm_is_intersecting_mem(kvm_context, start_addr);
            if (r)
                kvm_create_mem_hole(kvm_context, start_addr, size);
            r = kvm_register_userspace_phys_mem(kvm_context, start_addr,
                                                phys_ram_base + phys_offset,
                                                size, 0);
        }
        if (phys_offset & IO_MEM_ROM) {
            phys_offset &= ~IO_MEM_ROM;
            r = kvm_is_intersecting_mem(kvm_context, start_addr);
            if (r)
                kvm_create_mem_hole(kvm_context, start_addr, size);
            r = kvm_register_userspace_phys_mem(kvm_context, start_addr,
                                                phys_ram_base + phys_offset,
                                                size, 0);
        }
        if (r < 0) {
            printf("kvm_cpu_register_physical_memory: failed\n");
            exit(1);
        }
        return;
    }
#endif
    if (phys_offset & IO_MEM_ROM) {
        phys_offset &= ~IO_MEM_ROM;
        memcpy(phys_ram_base + start_addr, phys_ram_base + phys_offset, size);
    }
}

int kvm_qemu_check_extension(int ext)
{
    return kvm_check_extension(kvm_context, ext);
}

int kvm_qemu_init_env(CPUState *cenv)
{
    return kvm_arch_qemu_init_env(cenv);
}

int kvm_update_debugger(CPUState *env)
{
    struct kvm_debug_guest dbg;
    int i;

    dbg.enabled = 0;
    if (env->nb_breakpoints || env->singlestep_enabled) {
	dbg.enabled = 1;
	for (i = 0; i < 4 && i < env->nb_breakpoints; ++i) {
	    dbg.breakpoints[i].enabled = 1;
	    dbg.breakpoints[i].address = env->breakpoints[i];
	}
	dbg.singlestep = env->singlestep_enabled;
    }
    return kvm_guest_debug(kvm_context, env->cpu_index, &dbg);
}


/*
 * dirty pages logging
 */
/* FIXME: use unsigned long pointer instead of unsigned char */
unsigned char *kvm_dirty_bitmap = NULL;
int kvm_physical_memory_set_dirty_tracking(int enable)
{
    int r = 0;

    if (!kvm_allowed)
        return 0;

    if (enable) {
        if (!kvm_dirty_bitmap) {
            unsigned bitmap_size = BITMAP_SIZE(phys_ram_size);
            kvm_dirty_bitmap = qemu_malloc(bitmap_size);
            if (kvm_dirty_bitmap == NULL) {
                perror("Failed to allocate dirty pages bitmap");
                r=-1;
            }
            else {
                r = kvm_dirty_pages_log_enable_all(kvm_context);
            }
        }
    }
    else {
        if (kvm_dirty_bitmap) {
            r = kvm_dirty_pages_log_reset(kvm_context);
            qemu_free(kvm_dirty_bitmap);
            kvm_dirty_bitmap = NULL;
        }
    }
    return r;
}

/* get kvm's dirty pages bitmap and update qemu's */
int kvm_get_dirty_pages_log_range(unsigned long start_addr,
                                  unsigned char *bitmap,
                                  unsigned int offset,
                                  unsigned long mem_size)
{
    unsigned int i, j, n=0;
    unsigned char c;
    unsigned page_number, addr, addr1;
    unsigned int len = ((mem_size/TARGET_PAGE_SIZE) + 7) / 8;

    /* 
     * bitmap-traveling is faster than memory-traveling (for addr...) 
     * especially when most of the memory is not dirty.
     */
    for (i=0; i<len; i++) {
        c = bitmap[i];
        while (c>0) {
            j = ffsl(c) - 1;
            c &= ~(1u<<j);
            page_number = i * 8 + j;
            addr1 = page_number * TARGET_PAGE_SIZE;
            addr  = offset + addr1;
            cpu_physical_memory_set_dirty(addr);
            n++;
        }
    }
    return 0;
}
int kvm_get_dirty_bitmap_cb(unsigned long start, unsigned long len,
                            void *bitmap, void *opaque)
{
    return kvm_get_dirty_pages_log_range(start, bitmap, start, len);
}

/* 
 * get kvm's dirty pages bitmap and update qemu's
 * we only care about physical ram, which resides in slots 0 and 3
 */
int kvm_update_dirty_pages_log(void)
{
    int r = 0;


    r = kvm_get_dirty_pages_range(kvm_context, 0, phys_ram_size,
                                  kvm_dirty_bitmap, NULL,
                                  kvm_get_dirty_bitmap_cb);
    return r;
}

int kvm_get_phys_ram_bitmap_cb(unsigned long start, unsigned long len,
                               void *local_bitmap, void *qemu_bitmap)
{
    unsigned int bsize  = ((len/TARGET_PAGE_SIZE) + 7) / 8;
    unsigned int offset = ((start/TARGET_PAGE_SIZE) + 7) / 8;

    memcpy(qemu_bitmap + offset, local_bitmap, bsize);

    return 0;
}

int kvm_get_phys_ram_page_bitmap(unsigned char *bitmap)
{
    int r=0;
    void *local_bitmap;
    unsigned int bsize = BITMAP_SIZE(phys_ram_size);

    local_bitmap = qemu_malloc(bsize);
    if (!local_bitmap) {
        fprintf(stderr, "could not allocate memory for phys_page bitmap\n");
        return 1;
    }

    r = kvm_get_mem_map_range(kvm_context, 0, phys_ram_size,
                              local_bitmap, bitmap,
                              kvm_get_phys_ram_bitmap_cb);

    qemu_free(local_bitmap);
    return r;
}

#ifdef KVM_CAP_IRQCHIP

int kvm_set_irq(int irq, int level)
{
    return kvm_set_irq_level(kvm_context, irq, level);
}

#endif

#endif
