/*
 * kdd-xen.c -- xen-specific functions for the kdd debugging stub
 *
 * Tim Deegan <Tim.Deegan@citrix.com>
 * 
 * Copyright (c) 2007-2010, Citrix Systems Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/mman.h>

#include <xenctrl.h>
#include <xen/xen.h>
#include <xen/hvm/save.h>

#include "kdd.h"

#define MAPSIZE 4093 /* Prime */

#define PAGE_SHIFT 12
#define PAGE_SIZE (1U << PAGE_SHIFT)

struct kdd_guest {
    struct xentoollog_logger xc_log; /* Must be first for xc log callbacks */
    xc_interface *xc_handle;
    uint32_t domid;
    char id[80];
    FILE *log;
    int verbosity;
    void *hvm_buf;
    uint32_t hvm_sz;
    uint32_t pfns[MAPSIZE];
    void * maps[MAPSIZE];
};


/* Flush any mappings we have of the guest memory: it's not polite
 * top hold on to them while the guest is running */
static void flush_maps(kdd_guest *g)
{
    int i;
    for (i = 0; i < MAPSIZE; i++) {
        if (g->maps[i] != NULL)
            munmap(g->maps[i], PAGE_SIZE);
        g->maps[i] = NULL;
    }
}


/* Halt the guest so we can debug it */
void kdd_halt(kdd_guest *g)
{
    uint32_t sz;
    void *buf;

    xc_domain_pause(g->xc_handle, g->domid);

    /* How much space do we need for the HVM state? */
    sz = xc_domain_hvm_getcontext(g->xc_handle, g->domid, 0, 0);
    if (sz == (uint32_t) -1) {
        KDD_LOG(g, "Can't get HVM state size for domid %"PRIu32": %s\n",
                g->domid, strerror(errno));
        return;
    }
    buf = realloc(g->hvm_buf, sz);
    if (!buf) {
        KDD_LOG(g, "Couldn't allocate %"PRIu32" for HVM buffer\n", sz);
        return;
    }
    g->hvm_buf = buf;
    g->hvm_sz = sz;
    memset(buf, 0, sz);

    /* Get the HVM state */
    sz = xc_domain_hvm_getcontext(g->xc_handle, g->domid, buf, sz);
    if (sz == (uint32_t) -1) {
        KDD_LOG(g, "Can't get HVM state for domid %"PRIu32": %s\n",
                g->domid, strerror(errno));
    }
}

/* Check whether the guest has stopped.  Returns 'interesting' vcpu or -1 */
/* TODO: open DEBUG_VIRQ if it's free and wait for events rather than
 * always polling the guest state */
int kdd_poll_guest(kdd_guest *g)
{
    /* TODO: finish plumbing through polling for breakpoints */
    return 0;
}

/* Update the HVM state */
static void hvm_writeback(kdd_guest *g)
{
    if (g->hvm_buf && xc_domain_hvm_setcontext(g->xc_handle, g->domid, 
                                               g->hvm_buf, g->hvm_sz))
        KDD_LOG(g, "Can't set HVM state for domid %"PRIu32": %s\n",
                g->domid, strerror(errno));
}

/* Start the guest running again */
void kdd_run(kdd_guest *g)
{
    flush_maps(g);
    hvm_writeback(g);
    xc_domain_unpause(g->xc_handle, g->domid);
}

/* How many CPUs are there in this guest? */
int kdd_count_cpus(kdd_guest *g)
{
    struct hvm_save_descriptor *desc;
    int maxcpu = 0;

    if (!g->hvm_buf)
        return 0;

    /* Scan the CPU save records. */
    for (desc = g->hvm_buf;
         (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz;
         desc = ((void *)desc) + (sizeof *desc) + desc->length) {
        if (desc->typecode == HVM_SAVE_CODE(CPU)) {
            if (maxcpu < desc->instance) 
                maxcpu = desc->instance;
        }
    }
    return maxcpu + 1;
}

/* Helper fn: get CPU state from cached HVM state */
static struct hvm_hw_cpu *get_cpu(kdd_guest *g, int cpuid)
{
    struct hvm_save_descriptor *desc;
    struct hvm_hw_cpu *cpu;

    if (!g->hvm_buf)
        return NULL;

    /* Find the right CPU */
    for (desc = g->hvm_buf;
         (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz;
         desc = ((void *)desc) + (sizeof *desc) + desc->length) {
        if (desc->typecode == HVM_SAVE_CODE(CPU) && desc->instance == cpuid) {
            cpu = ((void *)desc) + (sizeof *desc);
            if ((void *) cpu + sizeof (*cpu) <= g->hvm_buf + g->hvm_sz)
                return cpu;
        }
    }
    
    KDD_LOG(g, "Dom %"PRIu32" has no CPU %i\n", g->domid, cpuid);
    return NULL;
}

/* Helper fn: get APIC state from cached HVM state */
static struct hvm_hw_lapic_regs *get_lapic(kdd_guest *g, int cpuid)
{
    struct hvm_save_descriptor *desc;
    struct hvm_hw_lapic_regs *regs;

    if (!g->hvm_buf)
        return NULL;

    /* Find the right CPU's LAPIC */
    for (desc = g->hvm_buf;
         (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz;
         desc = ((void *)desc) + (sizeof *desc) + desc->length) {
        if (desc->typecode == HVM_SAVE_CODE(LAPIC_REGS) && desc->instance == cpuid) {
            regs = ((void *)desc) + (sizeof *desc);
            if ((void *) regs + sizeof (*regs) <= g->hvm_buf + g->hvm_sz)
                return regs;
        }
    }
    
    KDD_LOG(g, "Dom %"PRIu32" has no LAPIC %i\n", g->domid, cpuid);
    return NULL;
}


/* Accessors for guest user registers */
static void kdd_get_regs_x86_32(struct hvm_hw_cpu *cpu, kdd_regs_x86_32 *r)
{
    r->gs     = cpu->gs_sel;
    r->fs     = cpu->fs_sel;
    r->es     = cpu->es_sel;
    r->ds     = cpu->ds_sel;
    r->edi    = cpu->rdi;
    r->esi    = cpu->rsi;
    r->ebx    = cpu->rbx;
    r->edx    = cpu->rdx;
    r->ecx    = cpu->rcx;
    r->eax    = cpu->rax;
    r->ebp    = cpu->rbp;
    r->eip    = cpu->rip;
    r->cs     = cpu->cs_sel;
    r->eflags = cpu->rflags;
    r->esp    = cpu->rsp;
    r->ss     = cpu->ss_sel;
    memcpy(r->fp, cpu->fpu_regs, 112); // 108 save area + 4 of ???
}

static void kdd_set_regs_x86_32(struct hvm_hw_cpu *cpu, kdd_regs_x86_32 *r)
{
    cpu->gs_sel = r->gs;
    cpu->fs_sel = r->fs;
    cpu->es_sel = r->es;
    cpu->ds_sel = r->ds;
    cpu->rdi    = r->edi;
    cpu->rsi    = r->esi;
    cpu->rbx    = r->ebx;
    cpu->rdx    = r->edx;
    cpu->rcx    = r->ecx;
    cpu->rax    = r->eax;
    cpu->rbp    = r->ebp;
    cpu->rip    = r->eip;
    cpu->cs_sel = r->cs;
    cpu->rflags = r->eflags;
    cpu->rsp    = r->esp;
    cpu->ss_sel = r->ss;
    memcpy(cpu->fpu_regs, r->fp, 112); // 108 save area + 4 of ???
}

static void kdd_get_regs_x86_64(struct hvm_hw_cpu *cpu, kdd_regs_x86_64 *r)
{
    // XXX debug pattern
    uint16_t i;
    for (i = 0 ; i < (sizeof *r / 2) ; i++)
        ((uint16_t *)r)[i] = i;

    r->cs     = cpu->cs_sel;
    r->ds     = cpu->ds_sel;
    r->es     = cpu->es_sel;
    r->fs     = cpu->fs_sel;
    r->gs     = cpu->gs_sel;
    r->ss     = cpu->ss_sel;
    r->rflags = cpu->rflags;
    r->dr0    = cpu->dr0;
    r->dr1    = cpu->dr1;
    r->dr2    = cpu->dr2;
    r->dr3    = cpu->dr3;
    r->dr6    = cpu->dr6;
    r->dr7    = cpu->dr7;
    r->rax    = cpu->rax;
    r->rcx    = cpu->rcx;
    r->rdx    = cpu->rdx;
    r->rbx    = cpu->rbx;
    r->rsp    = cpu->rsp;
    r->rbp    = cpu->rbp;
    r->rsi    = cpu->rsi;
    r->rdi    = cpu->rdi;
    r->r8     = cpu->r8;
    r->r9     = cpu->r9;
    r->r10    = cpu->r10;
    r->r11    = cpu->r11;
    r->r12    = cpu->r12;
    r->r13    = cpu->r13;
    r->r14    = cpu->r14;
    r->r15    = cpu->r15;
    r->rip    = cpu->rip;
    memcpy(r->fp, cpu->fpu_regs, 112); // Definitely not right
}

static void kdd_set_regs_x86_64(struct hvm_hw_cpu *cpu, kdd_regs_x86_64 *r)
{
    cpu->cs_sel = r->cs;
    cpu->ds_sel = r->ds;
    cpu->es_sel = r->es;
    cpu->fs_sel = r->fs;
    cpu->gs_sel = r->gs;
    cpu->ss_sel = r->ss;
    cpu->rflags = r->rflags;
    cpu->dr0    = r->dr0;
    cpu->dr1    = r->dr1;
    cpu->dr2    = r->dr2;
    cpu->dr3    = r->dr3;
    cpu->dr6    = r->dr6;
    cpu->dr7    = r->dr7;
    cpu->rax    = r->rax;
    cpu->rcx    = r->rcx;
    cpu->rdx    = r->rdx;
    cpu->rbx    = r->rbx;
    cpu->rsp    = r->rsp;
    cpu->rbp    = r->rbp;
    cpu->rsi    = r->rsi;
    cpu->rdi    = r->rdi;
    cpu->r8     = r->r8;
    cpu->r9     = r->r9;
    cpu->r10    = r->r10;
    cpu->r11    = r->r11;
    cpu->r12    = r->r12;
    cpu->r13    = r->r13;
    cpu->r14    = r->r14;
    cpu->r15    = r->r15;
    cpu->rip    = r->rip;
    memcpy(r->fp, cpu->fpu_regs, 112); // Definitely not right
}


int kdd_get_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64)
{
    struct hvm_hw_cpu *cpu; 
    
    cpu = get_cpu(g, cpuid);
    if (!cpu) 
        return -1;

    memset(r, 0, sizeof(*r));
    
    if (w64)
        kdd_get_regs_x86_64(cpu, &r->r64);
    else
        kdd_get_regs_x86_32(cpu, &r->r32);

    return 0;
}

int kdd_set_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64)
{
    struct hvm_hw_cpu *cpu; 
    
    cpu = get_cpu(g, cpuid);
    if (!cpu) 
        return -1;
    
    if (w64)
        kdd_set_regs_x86_64(cpu, &r->r64);
    else
        kdd_set_regs_x86_32(cpu, &r->r32);

    hvm_writeback(g);
    return 0;
}


/* Accessors for guest control registers */
static void kdd_get_ctrl_x86_32(struct hvm_hw_cpu *cpu, kdd_ctrl_x86_32 *c)
{    
    c->cr0 = cpu->cr0;
    c->cr2 = cpu->cr2;
    c->cr3 = cpu->cr3;
    c->cr4 = cpu->cr4;
    c->dr0 = cpu->dr0;
    c->dr1 = cpu->dr1;
    c->dr2 = cpu->dr2;
    c->dr3 = cpu->dr3;
    c->dr6 = cpu->dr6;
    c->dr7 = cpu->dr7;
    c->gdt_base = cpu->gdtr_base;
    c->gdt_limit = cpu->gdtr_limit;
    c->idt_base = cpu->idtr_base;
    c->idt_limit = cpu->idtr_limit;
    c->tss_sel = cpu->tr_sel;
    c->ldt_sel = cpu->ldtr_sel;
}

static void kdd_get_ctrl_x86_64(struct hvm_hw_cpu *cpu, 
                                struct hvm_hw_lapic_regs *lapic,
                                kdd_ctrl_x86_64 *c)
{    
    c->cr0 = cpu->cr0;
    c->cr2 = cpu->cr2;
    c->cr3 = cpu->cr3;
    c->cr4 = cpu->cr4;
    c->dr0 = cpu->dr0;
    c->dr1 = cpu->dr1;
    c->dr2 = cpu->dr2;
    c->dr3 = cpu->dr3;
    c->dr6 = cpu->dr6;
    c->dr7 = cpu->dr7;
    c->gdt_base = cpu->gdtr_base;
    c->gdt_limit = cpu->gdtr_limit;
    c->idt_base = cpu->idtr_base;
    c->idt_limit = cpu->idtr_limit;
    c->tss_sel = cpu->tr_sel;
    c->ldt_sel = cpu->ldtr_sel;
    c->cr8 = lapic->data[0x80] >> 4; /* Top half of the low byte of the TPR */
}


int kdd_get_ctrl(kdd_guest *g, int cpuid, kdd_ctrl *ctrl, int w64)
{
    struct hvm_hw_cpu *cpu; 
    struct hvm_hw_lapic_regs *lapic;

    cpu = get_cpu(g, cpuid);
    if (!cpu)
        return -1;

    if (w64) {
        lapic = get_lapic(g, cpuid);
        if (!lapic)
            return -1;
        kdd_get_ctrl_x86_64(cpu, lapic, &ctrl->c64);
    } else {
        kdd_get_ctrl_x86_32(cpu, &ctrl->c32);
    }

    return 0;
}

int kdd_wrmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t value)
{
    struct hvm_hw_cpu *cpu;

    cpu = get_cpu(g, cpuid);
    if (!cpu)
        return -1;
    
    switch (msr) {
    case 0x00000174: cpu->sysenter_cs = value; break;
    case 0x00000175: cpu->sysenter_esp = value; break;
    case 0x00000176: cpu->sysenter_eip = value; break;
    case 0xc0000080: cpu->msr_efer = value; break;
    case 0xc0000081: cpu->msr_star = value; break;
    case 0xc0000082: cpu->msr_lstar = value; break;
    case 0xc0000083: cpu->msr_cstar = value; break;
    case 0xc0000084: cpu->msr_syscall_mask = value; break;
    case 0xc0000100: cpu->fs_base = value; break;
    case 0xc0000101: cpu->gs_base = value; break;
    case 0xc0000102: cpu->shadow_gs = value; break;
    default:
        return -1;
    }

    hvm_writeback(g);
    return 0;   
}

int kdd_rdmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t *value)
{
    struct hvm_hw_cpu *cpu;

    cpu = get_cpu(g, cpuid);
    if (!cpu)
        return -1;
    
    switch (msr) {
    case 0x00000174: *value = cpu->sysenter_cs; break;
    case 0x00000175: *value = cpu->sysenter_esp; break;
    case 0x00000176: *value = cpu->sysenter_eip; break;
    case 0xc0000080: *value = cpu->msr_efer; break;
    case 0xc0000081: *value = cpu->msr_star; break;
    case 0xc0000082: *value = cpu->msr_lstar; break;
    case 0xc0000083: *value = cpu->msr_cstar; break;
    case 0xc0000084: *value = cpu->msr_syscall_mask; break;
    case 0xc0000100: *value = cpu->fs_base; break;
    case 0xc0000101: *value = cpu->gs_base; break;
    case 0xc0000102: *value = cpu->shadow_gs; break;
    default:
        return -1;
    }

    return 0;   
}


/* Accessor for guest physical memory */
static uint32_t kdd_access_physical_page(kdd_guest *g, uint64_t addr, 
                                         uint32_t len, uint8_t *buf, int write)
{
    uint32_t map_pfn, map_offset;
    uint8_t *map;

    map_pfn = (addr >> PAGE_SHIFT);
    map_offset = addr & (PAGE_SIZE - 1);

    /* Evict any mapping of the wrong frame from our slot */ 
    if (g->pfns[map_pfn % MAPSIZE] != map_pfn
        && g->maps[map_pfn % MAPSIZE] != NULL) {
        munmap(g->maps[map_pfn % MAPSIZE], PAGE_SIZE);
        g->maps[map_pfn % MAPSIZE] = NULL;
    }
    g->pfns[map_pfn % MAPSIZE] = map_pfn;

    /* Now map the frame if it's not already there */
    if (g->maps[map_pfn % MAPSIZE] != NULL)
        map = g->maps[map_pfn % MAPSIZE];
    else {
        map = xc_map_foreign_range(g->xc_handle,
                                   g->domid,
                                   PAGE_SIZE,
                                   PROT_READ|PROT_WRITE,
                                   map_pfn);

        KDD_DEBUG(g, "map: %u, 0x%16.16"PRIx32": %p +0x%"PRIx32"\n",
                  write ? PROT_READ|PROT_WRITE : PROT_READ,
                  map_pfn, map, map_offset);

        if (!map) 
            return 0;
        g->maps[map_pfn % MAPSIZE] = map;
    }

    if (write) 
        memcpy(map + map_offset, buf, len);
    else
        memcpy(buf, map + map_offset, len);

    return len;
}

uint32_t kdd_access_physical(kdd_guest *g, uint64_t addr, 
                             uint32_t len, uint8_t *buf, int write)
{
    uint32_t chunk, rv, done = 0;
    while (len > 0) {
        chunk = PAGE_SIZE - (addr & (PAGE_SIZE - 1));
        if (chunk > len) 
            chunk = len;
        rv = kdd_access_physical_page(g, addr, chunk, buf, write);
        done += rv;
        if (rv != chunk)
            return done;
        addr += chunk;
        buf += chunk;
        len -= chunk;
    }
    return done;
}


/* Plumb libxc log messages into our own logging */
static void kdd_xc_log(struct xentoollog_logger *logger,
                       xentoollog_level level,
                       int errnoval /* or -1 */,
                       const char *context /* eg "xc", "xl", may be 0 */,
                       const char *format /* without level, context, \n */,
                       va_list al)
{
    kdd_guest *g = (kdd_guest *) logger;
    /* Suppress most libxc levels unless we're logging at debug level */
    if (g->verbosity < 1 || (level < XTL_WARN && g->verbosity < 3))
        return;
    fprintf(g->log, "libxc[%s:%i:%i]: ", context ? : "?", level, errnoval);
    vfprintf(g->log, format, al);
    fprintf(g->log, "\n");
    (void) fflush(g->log);
}


/* Set up guest-specific state */
kdd_guest *kdd_guest_init(char *arg, FILE *log, int verbosity)
{
    kdd_guest *g = NULL;
    xc_interface *xch = NULL;
    uint32_t domid;
    xc_dominfo_t info;

    g = calloc(1, sizeof (kdd_guest));
    if (!g) 
        goto err;
    g->log = log;
    g->verbosity = verbosity;
    g->xc_log.vmessage = kdd_xc_log;

    xch = xc_interface_open(&g->xc_log, NULL, 0);
    if (!xch)
        goto err;
    g->xc_handle = xch;

    domid = strtoul(arg, NULL, 0);
    if (domid == 0)
        goto err;
    g->domid = domid;

    /* Check that the domain exists and is HVM */
    if (xc_domain_getinfo(xch, domid, 1, &info) != 1 || !info.hvm)
        goto err;

    snprintf(g->id, (sizeof g->id) - 1, 
             "a xen guest with domain id %i", g->domid);

    return g;

 err:
    free(g);
    if (xch)
        xc_interface_close(xch);
    return NULL;
}

/* Say what kind of guest this is */
char *kdd_guest_identify(kdd_guest *g)
{
    return g->id;
}

/* Tear down guest-specific state */
void kdd_guest_teardown(kdd_guest *g)
{
    flush_maps(g);
    xc_interface_close(g->xc_handle);
    free(g->hvm_buf);
    free(g);
}