#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <elf.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include "kexec.h"
#include "crashdump.h"
#include "kexec-syscall.h"

#include "config.h"

#ifdef HAVE_LIBXENCTRL
#include <xenctrl.h>
#endif

struct crash_note_info {
	unsigned long base;
	unsigned long length;
};

static int xen_phys_cpus;
static struct crash_note_info *xen_phys_notes;

/* based on code from xen-detect.c */
static int is_dom0;
#if defined(__i386__) || defined(__x86_64__)
static jmp_buf xen_sigill_jmp;
void xen_sigill_handler(int sig)
{
	longjmp(xen_sigill_jmp, 1);
}

static void xen_cpuid(uint32_t idx, uint32_t *regs, int pv_context)
{
#ifdef __i386__
    /* Use the stack to avoid reg constraint failures with some gcc flags */
    asm volatile (
        "push %%eax; push %%ebx; push %%ecx; push %%edx\n\t"
        "test %1,%1 ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
        "mov %%eax,(%2); mov %%ebx,4(%2)\n\t"
        "mov %%ecx,8(%2); mov %%edx,12(%2)\n\t"
        "pop %%edx; pop %%ecx; pop %%ebx; pop %%eax\n\t"
        : : "a" (idx), "c" (pv_context), "S" (regs) : "memory" );
#else
    asm volatile (
        "test %5,%5 ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
        : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
        : "0" (idx), "1" (pv_context), "2" (0) );
#endif
}

static int check_for_xen(int pv_context)
{
	uint32_t regs[4];
	char signature[13];
	uint32_t base;

	for (base = 0x40000000; base < 0x40010000; base += 0x100)
	{
		xen_cpuid(base, regs, pv_context);

		*(uint32_t *)(signature + 0) = regs[1];
		*(uint32_t *)(signature + 4) = regs[2];
		*(uint32_t *)(signature + 8) = regs[3];
		signature[12] = '\0';

		if (strcmp("XenVMMXenVMM", signature) == 0 && regs[0] >= (base + 2))
			goto found;
	}

	return 0;

found:
	xen_cpuid(base + 1, regs, pv_context);
	return regs[0];
}

static int xen_detect_pv_guest(void)
{
	struct sigaction act, oldact;
	int is_pv = -1;

	if (setjmp(xen_sigill_jmp))
		return is_pv;

	memset(&act, 0, sizeof(act));
	act.sa_handler = xen_sigill_handler;
	sigemptyset (&act.sa_mask);
	if (sigaction(SIGILL, &act, &oldact))
		return is_pv;
	if (check_for_xen(1))
		is_pv = 1;
	sigaction(SIGILL, &oldact, NULL);
	return is_pv;
}
#else
static int xen_detect_pv_guest(void)
{
	return 1;
}
#endif

/*
 * Return 1 if its a PV guest.
 * This includes dom0, which is the only PV guest where kexec/kdump works.
 * HVM guests have to be handled as native hardware.
 */
int xen_present(void)
{
	if (!is_dom0) {
		if (access("/proc/xen", F_OK) == 0)
			is_dom0 = xen_detect_pv_guest();
		else
			is_dom0 = -1;
	}
	return is_dom0 > 0;
}

unsigned long xen_architecture(struct crash_elf_info *elf_info)
{
	unsigned long machine = elf_info->machine;
#ifdef HAVE_LIBXENCTRL
	int rc;
	xen_capabilities_info_t capabilities;
	xc_interface *xc;

	if (!xen_present())
		goto out;

	memset(capabilities, '0', XEN_CAPABILITIES_INFO_LEN);

	xc = xc_interface_open(NULL, NULL, 0);
	if ( !xc ) {
		fprintf(stderr, "failed to open xen control interface.\n");
		goto out;
	}

	rc = xc_version(xc, XENVER_capabilities, &capabilities[0]);
	if ( rc == -1 ) {
		fprintf(stderr, "failed to make Xen version hypercall.\n");
		goto out_close;
	}

	if (strstr(capabilities, "xen-3.0-x86_64"))
		machine = EM_X86_64;
        else if (strstr(capabilities, "xen-3.0-x86_32"))
		machine = EM_386;

 out_close:
	xc_interface_close(xc);

 out:
#endif
	return machine;
}

#ifdef HAVE_LIBXENCTRL
int get_xen_vmcoreinfo(uint64_t *addr, uint64_t *len)
{
	xc_interface *xc;
	int ret = 0;

	xc = xc_interface_open(NULL, NULL, 0);
	if (!xc) {
	        fprintf(stderr, "failed to open xen control interface.\n");
	        return -1;
	}

	ret = xc_kexec_get_range(xc, KEXEC_RANGE_MA_VMCOREINFO, 0, len, addr);

	xc_interface_close(xc);

	if (ret < 0)
	        return -1;
	return 0;
}

int xen_get_nr_phys_cpus(void)
{
	xc_interface *xc;
	int max_cpus;
	int cpu = -1;

	if (xen_phys_cpus)
		return xen_phys_cpus;

	xc = xc_interface_open(NULL, NULL, 0);
	if (!xc) {
		fprintf(stderr, "failed to open xen control interface.\n");
		return -1;
	}

	max_cpus = xc_get_max_cpus(xc);
	if (max_cpus <= 0)
		goto out;

	xen_phys_notes = calloc(max_cpus, sizeof(*xen_phys_notes));
	if (xen_phys_notes == NULL)
		goto out;

	for (cpu = 0; cpu < max_cpus; cpu++) {
		uint64_t size, start;
		int ret;

		ret = xc_kexec_get_range(xc, KEXEC_RANGE_MA_CPU, cpu, &size, &start);
		if (ret < 0)
			break;

		xen_phys_notes[cpu].base = start;
		xen_phys_notes[cpu].length = size;
	}

	xen_phys_cpus = cpu;

out:
	xc_interface_close(xc);
	return cpu;
}
#else
int get_xen_vmcoreinfo(uint64_t *addr, uint64_t *len)
{
	return -1;
}

int xen_get_nr_phys_cpus(void)
{
	return -1;
}
#endif


int xen_get_note(int cpu, uint64_t *addr, uint64_t *len)
{
	struct crash_note_info *note;

	if (xen_phys_cpus <= 0)
		return -1;

	note = xen_phys_notes + cpu;

	*addr = note->base;
	*len = note->length;

	return 0;
}

#ifdef HAVE_LIBXENCTRL
int xen_get_crashkernel_region(uint64_t *start, uint64_t *end)
{
	uint64_t size;
	xc_interface *xc;
	int rc = -1;

	xc = xc_interface_open(NULL, NULL, 0);
	if (!xc) {
		fprintf(stderr, "failed to open xen control interface.\n");
		goto out;
	}

	rc = xc_kexec_get_range(xc, KEXEC_RANGE_MA_CRASH, 0, &size, start);
	if (rc < 0) {
		fprintf(stderr, "failed to get crash region from hypervisor.\n");
		goto out_close;
	}

	*end = *start + size - 1;

out_close:
	xc_interface_close(xc);

out:
	return rc;
}
#else
int xen_get_crashkernel_region(uint64_t *start, uint64_t *end)
{
	return -1;
}
#endif
