text: /*-
   * Copyright (c) 2015 xhyve developers
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. 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 NETAPP, INC ``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 NETAPP, INC 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 <stdint.h>
  #include <stdbool.h>
  #include <string.h>
  #include <assert.h>
  #include <errno.h>
  #include <sys/time.h>
  #include <sys/uio.h>
  #include <xhyve/support/misc.h>
  #include <xhyve/support/specialreg.h>
  #include <xhyve/vmm/vmm.h>
  #include <xhyve/vmm/vmm_lapic.h>
  #include <xhyve/vmm/vmm_instruction_emul.h>
  #include <xhyve/vmm/vmm_callout.h>
  #include <xhyve/vmm/vmm_stat.h>
  #include <xhyve/vmm/vmm_api.h>
  #include <xhyve/vmm/io/vatpic.h>
  #include <xhyve/vmm/io/vhpet.h>
  #include <xhyve/vmm/io/vioapic.h>
  #include <xhyve/vmm/io/vrtc.h>
  
  static struct vm *vm;
  static int memflags;
  static uint32_t lowmem_limit;
  static enum vm_mmap_style mmap_style;
  static size_t lowmem;
  static void *lowmem_addr;
  static size_t highmem;
  static void *highmem_addr;
  
  static void
  vcpu_freeze(int vcpu, bool freeze)
  {
  	enum vcpu_state state;
  
  	state = (freeze) ? VCPU_FROZEN : VCPU_IDLE;
  
  	if (vcpu_set_state(vm, vcpu, state, freeze)) {
  		xhyve_abort("vcpu_set_state failed\n");
  	}
  }
  
  static void
  vcpu_freeze_all(bool freeze)
  {
  	enum vcpu_state state;
  	int vcpu;
  
  	state = (freeze) ? VCPU_FROZEN : VCPU_IDLE;
  
  	for (vcpu = 0; vcpu < VM_MAXCPU; vcpu++) {
  		if (vcpu_set_state(vm, vcpu, state, freeze)) {
  			xhyve_abort("vcpu_set_state failed\n");
  		}
  	}
  }
  
  int
  xh_vm_create(void)
  {
  	int error;
  
  	if (vm != NULL) {
  		return (EEXIST);
  	}
  
  	error =	vmm_init();
  
  	if (error != 0) {
  		return (error);
  	}
  
  	memflags = 0;
  	lowmem_limit = (3ull << 30);
  
  	return (vm_create(&vm));
  }
  
  void
  xh_vm_destroy(void)
  {
  	assert(vm != NULL);
  
  	vm_destroy(vm);
  
  	if (vmm_cleanup() == 0) {
  		vm = NULL;
  	}
  }
  
  int
  xh_vcpu_create(int vcpu)
  {
  	assert(vm != NULL);
  	return (vcpu_create(vm, vcpu));
  }
  
  void
  xh_vcpu_destroy(int vcpu)
  {
  	assert(vm != NULL);
  	vcpu_destroy(vm, vcpu);
  }
  
  int
  xh_vm_get_memory_seg(uint64_t gpa, size_t *ret_len)
  {
  	int error;
  
  	struct vm_memory_segment seg;
  
  	error = vm_gpabase2memseg(vm, gpa, &seg);
  
  	if (error == 0) {
  		*ret_len = seg.len;
  	}
  
  	return (error);
  }
  
  static int
  setup_memory_segment(uint64_t gpa, size_t len, void **addr)
  {
  	void *object;
  	uint64_t offset;
  	int error;
  
  	vcpu_freeze_all(true);
  	error = vm_malloc(vm, gpa, len);
  	if (error == 0) {
  		error = vm_get_memobj(vm, gpa, len, &offset, &object);
  		if (error == 0) {
  			*addr = (void *) (((uintptr_t) object) + offset);
  		}
  	}
  	vcpu_freeze_all(false);
  	return (error);
  }
  
  int
  xh_vm_setup_memory(size_t len, enum vm_mmap_style vms)
  {
  	void **addr;
  	int error;
  
  	/* XXX VM_MMAP_SPARSE not implemented yet */
  	assert(vms == VM_MMAP_NONE || vms == VM_MMAP_ALL);
  
  	mmap_style = vms;
  
  	/*
  	 * If 'len' cannot fit entirely in the 'lowmem' segment then
  	 * create another 'highmem' segment above 4GB for the remainder.
  	 */
  
  	lowmem = (len > lowmem_limit) ? lowmem_limit : len;
  	highmem = (len > lowmem_limit) ? (len - lowmem) : 0;
  
  	if (lowmem > 0) {
  		addr = (vms == VM_MMAP_ALL) ? &lowmem_addr : NULL;
  		if ((error = setup_memory_segment(0, lowmem, addr))) {
  			return (error);
  		}
  	}
  
  	if (highmem > 0) {
  		addr = (vms == VM_MMAP_ALL) ? &highmem_addr : NULL;
  		if ((error = setup_memory_segment((4ull << 30), highmem, addr))) {
  			return (error);
  		}
  	}
  
  	return (0);
  }
  
  void *
  xh_vm_map_gpa(uint64_t gpa, size_t len)
  {
  	assert(mmap_style == VM_MMAP_ALL);
  
  	if ((gpa < lowmem) && ((gpa + len) <= lowmem)) {
  		return ((void *) (((uintptr_t) lowmem_addr) + gpa));
  	}
  
  	if (gpa >= (4ull << 30)) {
  		gpa -= (4ull << 30);
  		if ((gpa < highmem) && ((gpa + len) <= highmem)) {
  			return ((void *) (((uintptr_t) highmem_addr) + gpa));
  		}
  	}
  
  	return (NULL);
  }
  
  int
  xh_vm_gla2gpa(int vcpu, struct vm_guest_paging *paging, uint64_t gla,
  	int prot, uint64_t *gpa, int *fault)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_gla2gpa(vm, vcpu, paging, gla, prot, gpa, fault);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  uint32_t
  xh_vm_get_lowmem_limit(void)
  {
  	return (lowmem_limit);
  }
  
  void
  xh_vm_set_lowmem_limit(uint32_t limit)
  {
  	lowmem_limit = limit;
  }
  
  void
  xh_vm_set_memflags(int flags)
  {
  	memflags = flags;
  }
  
  size_t
  xh_vm_get_lowmem_size(void)
  {
  	return (lowmem);
  }
  
  size_t
  xh_vm_get_highmem_size(void)
  {
  	return (highmem);
  }
  
  int
  xh_vm_set_desc(int vcpu, int reg, uint64_t base, uint32_t limit,
  	uint32_t access)
  {
  	struct seg_desc sd;
  	int error;
  
  	sd.base = base;
  	sd.limit = limit;
  	sd.access = access;
  	vcpu_freeze(vcpu, true);
  	error = vm_set_seg_desc(vm, vcpu, reg, &sd);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_get_desc(int vcpu, int reg, uint64_t *base, uint32_t *limit,
  	uint32_t *access)
  {
  	struct seg_desc sd;
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_get_seg_desc(vm, vcpu, reg, &sd);
  	if (error == 0) {
  		*base = sd.base;
  		*limit = sd.limit;
  		*access = sd.access;
  	} 
  	vcpu_freeze(vcpu, false);
  
  	return (error);	
  }
  
  int
  xh_vm_get_seg_desc(int vcpu, int reg, struct seg_desc *seg_desc)
  {
  	int error;
  
  	error = xh_vm_get_desc(vcpu, reg, &seg_desc->base, &seg_desc->limit,
  		&seg_desc->access);
  
  	return (error);
  }
  
  int
  xh_vm_set_register(int vcpu, int reg, uint64_t val)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_set_register(vm, vcpu, reg, val);
  	vcpu_freeze(vcpu, false);
  
  	return (error);	
  }
  
  int
  xh_vm_get_register(int vcpu, int reg, uint64_t *retval)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_get_register(vm, vcpu, reg, retval);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_run(int vcpu, struct vm_exit *ret_vmexit)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_run(vm, vcpu, ret_vmexit);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_suspend(enum vm_suspend_how how)
  {
  	return (vm_suspend(vm, how));
  }
  
  int
  xh_vm_reinit(void)
  {
  	int error;
  
  	vcpu_freeze_all(true);
  	error = vm_reinit(vm);
  	vcpu_freeze_all(false);
  
  	return (error);
  }
  
  int
  xh_vm_apicid2vcpu(int apicid)
  {
  	return (apicid);
  }
  
  int
  xh_vm_inject_exception(int vcpu, int vector, int errcode_valid,
  	uint32_t errcode, int restart_instruction)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_inject_exception(vm, vcpu, vector, errcode_valid, errcode,
  		restart_instruction);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_lapic_irq(int vcpu, int vector)
  {
  	return (lapic_intr_edge(vm, vcpu, vector));
  }
  
  int
  xh_vm_lapic_local_irq(int vcpu, int vector)
  {
  	return (lapic_set_local_intr(vm, vcpu, vector));
  }
  
  int
  xh_vm_lapic_msi(uint64_t addr, uint64_t msg)
  {
  	return (lapic_intr_msi(vm, addr, msg));
  }
  
  int
  xh_vm_ioapic_assert_irq(int irq)
  {
  	return (vioapic_assert_irq(vm, irq));
  }
  
  int
  xh_vm_ioapic_deassert_irq(int irq)
  {
  	return (vioapic_deassert_irq(vm, irq));
  }
  
  int
  xh_vm_ioapic_pulse_irq(int irq)
  {
  	return (vioapic_pulse_irq(vm, irq));
  }
  
  int
  xh_vm_ioapic_pincount(int *pincount)
  {
  	*pincount = vioapic_pincount(vm);
  	return (0);
  }
  
  int
  xh_vm_isa_assert_irq(int atpic_irq, int ioapic_irq)
  {
  	int error;
  
  	error = vatpic_assert_irq(vm, atpic_irq);
  
  	if ((error == 0) && (ioapic_irq != -1)) {
  		error = vioapic_assert_irq(vm, ioapic_irq);
  	}
  
  	return (error);
  }
  
  int
  xh_vm_isa_deassert_irq(int atpic_irq, int ioapic_irq)
  {
  	int error;
  
  	error = vatpic_deassert_irq(vm, atpic_irq);
  	if ((error == 0) && (ioapic_irq != -1)) {
  		error = vioapic_deassert_irq(vm, ioapic_irq);
  	}
  
  	return (error);
  }
  
  int
  xh_vm_isa_pulse_irq(int atpic_irq, int ioapic_irq)
  {
  	int error;
  
  	error = vatpic_pulse_irq(vm, atpic_irq);
  	if ((error == 0) && (ioapic_irq != -1)) {
  		error = vioapic_pulse_irq(vm, ioapic_irq);
  	}
  
  	return (error);
  }
  
  int
  xh_vm_isa_set_irq_trigger(int atpic_irq, enum vm_intr_trigger trigger)
  {
  	return (vatpic_set_irq_trigger(vm, atpic_irq, trigger));
  }
  
  int
  xh_vm_inject_nmi(int vcpu)
  {
  	return (vm_inject_nmi(vm, vcpu));
  }
  
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wpadded"
  static struct {
  	const char *name;
  	int type;
  } capstrmap[] = {
  	{ "hlt_exit", VM_CAP_HALT_EXIT },
  	{ "mtrap_exit", VM_CAP_MTRAP_EXIT },
  	{ "pause_exit", VM_CAP_PAUSE_EXIT },
  	{ NULL, 0 }
  };
  #pragma clang diagnostic pop
  
  int
  xh_vm_capability_name2type(const char *capname)
  {
  	int i;
  
  	for (i = 0; (capstrmap[i].name != NULL) && (capname != NULL); i++) {
  		if (strcmp(capstrmap[i].name, capname) == 0) {
  			return (capstrmap[i].type);
  		}
  	}
  
  	return (-1);
  }
  
  const char *
  xh_vm_capability_type2name(int type)
  {
  	int i;
  
  	for (i = 0; (capstrmap[i].name != NULL); i++) {
  		if (capstrmap[i].type == type) {
  			return (capstrmap[i].name);
  		}
  	}
  
  	return (NULL);
  }
  
  int
  xh_vm_get_capability(int vcpu, enum vm_cap_type cap, int *retval)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_get_capability(vm, vcpu, cap, retval);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_set_capability(int vcpu, enum vm_cap_type cap, int val)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_set_capability(vm, vcpu, cap, val);
  	vcpu_freeze(vcpu, false);
  
  	return (error);	
  }
  
  int
  xh_vm_get_intinfo(int vcpu, uint64_t *i1, uint64_t *i2)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_get_intinfo(vm, vcpu, i1, i2);
  	vcpu_freeze(vcpu, false);
  
  	return (error);	
  }
  
  int
  xh_vm_set_intinfo(int vcpu, uint64_t exit_intinfo)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_exit_intinfo(vm, vcpu, exit_intinfo);
  	vcpu_freeze(vcpu, false);
  
  	return (error);	
  }
  
  uint64_t *
  xh_vm_get_stats(int vcpu, struct timeval *ret_tv, int *ret_entries)
  {
  	static uint64_t statbuf[64];
  	struct timeval tv;
  	int re;
  	int error;
  
  	getmicrotime(&tv);
  	error = vmm_stat_copy(vm, vcpu, &re, ((uint64_t *) &statbuf));
  
  	if (error == 0) {
  		if (ret_entries) {
  			*ret_entries = re;
  		}
  		if (ret_tv) {
  			*ret_tv = tv;
  		}
  		return (((uint64_t *) &statbuf));
  	} else {
  		return (NULL);
  	}
  }
  
  const char *
  xh_vm_get_stat_desc(int index)
  {
  	static char desc[128];
  
  	if (vmm_stat_desc_copy(index, ((char *) &desc), sizeof(desc)) == 0) {
  		return (desc);
  	} else {
  		return (NULL);
  	}
  }
  
  int
  xh_vm_get_x2apic_state(int vcpu, enum x2apic_state *s)
  {
  	return (vm_get_x2apic_state(vm, vcpu, s));
  }
  
  int
  xh_vm_set_x2apic_state(int vcpu, enum x2apic_state s)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_set_x2apic_state(vm, vcpu, s);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_get_hpet_capabilities(uint32_t *capabilities)
  {
  	return (vhpet_getcap(capabilities));
  }
  
  int
  xh_vm_copy_setup(int vcpu, struct vm_guest_paging *pg, uint64_t gla, size_t len,
  	int prot, struct iovec *iov, int iovcnt, int *fault)
  {
  	void *va;
  	uint64_t gpa;
  	size_t n, off;
  	int i, error;
  
  	for (i = 0; i < iovcnt; i++) {
  		iov[i].iov_base = 0;
  		iov[i].iov_len = 0;
  	}
  
  	while (len) {
  		assert(iovcnt > 0);
  
  		error = xh_vm_gla2gpa(vcpu, pg, gla, prot, &gpa, fault);
  		if ((error) || *fault) {
  			return (error);
  		}
  
  		off = gpa & XHYVE_PAGE_MASK;
  		n = min(len, XHYVE_PAGE_SIZE - off);
  
  		va = xh_vm_map_gpa(gpa, n);
  		if (va == NULL) {
  			return (EFAULT);
  		}
  
  		iov->iov_base = va;
  		iov->iov_len = n;
  		iov++;
  		iovcnt--;
  
  		gla += n;
  		len -= n;
  	}
  
  	return (0);
  }
  
  void
  xh_vm_copyin(struct iovec *iov, void *dst, size_t len)
  {
  	const char *src;
  	char *d;
  	size_t n;
  
  	d = dst;
  	while (len) {
  		assert(iov->iov_len);
  		n = min(len, iov->iov_len);
  		src = iov->iov_base;
  		bcopy(src, d, n);
  		iov++;
  		d += n;
  		len -= n;
  	}
  }
  
  void
  xh_vm_copyout(const void *src, struct iovec *iov, size_t len)
  {
  	const char *s;
  	char *dst;
  	size_t n;
  
  	s = src;
  	while (len) {
  		assert(iov->iov_len);
  		n = min(len, iov->iov_len);
  		dst = iov->iov_base;
  		bcopy(s, dst, n);
  		iov++;
  		s += n;
  		len -= n;
  	}
  }
  
  int
  xh_vm_rtc_write(int offset, uint8_t value)
  {
  	return (vrtc_nvram_write(vm, offset, value));
  }
  
  int
  xh_vm_rtc_read(int offset, uint8_t *retval)
  {
  	return (vrtc_nvram_read(vm, offset, retval));
  }
  
  int
  xh_vm_rtc_settime(time_t secs)
  {
  	return (vrtc_set_time(vm, secs));
  }
  
  int
  xh_vm_rtc_gettime(time_t *secs)
  {
  	*secs = vrtc_get_time(vm);
  	return (0);
  }
  
  int
  xh_vcpu_reset(int vcpu)
  {
  	int error;
  
  #define SET_REG(r, v) (error = xh_vm_set_register(vcpu, (r), (v)))
  #define SET_DESC(d, b, l, a) (error = xh_vm_set_desc(vcpu, (d), (b), (l), (a)))
  
  	if (SET_REG(VM_REG_GUEST_RFLAGS, 0x2) ||
  		SET_REG(VM_REG_GUEST_RIP, 0xfff0) ||
  		SET_REG(VM_REG_GUEST_CR0, CR0_NE) ||
  		SET_REG(VM_REG_GUEST_CR3, 0) ||
  		SET_REG(VM_REG_GUEST_CR4, 0) ||
  		SET_REG(VM_REG_GUEST_CS, 0xf000) ||
  		SET_REG(VM_REG_GUEST_SS, 0) ||
  		SET_REG(VM_REG_GUEST_DS, 0) ||
  		SET_REG(VM_REG_GUEST_ES, 0) ||
  		SET_REG(VM_REG_GUEST_FS, 0) ||
  		SET_REG(VM_REG_GUEST_GS, 0) ||
  		SET_REG(VM_REG_GUEST_RAX, 0) ||
  		SET_REG(VM_REG_GUEST_RBX, 0) ||
  		SET_REG(VM_REG_GUEST_RCX, 0) ||
  		SET_REG(VM_REG_GUEST_RDX, 0xf00) ||
  		SET_REG(VM_REG_GUEST_RSI, 0) ||
  		SET_REG(VM_REG_GUEST_RDI, 0) ||
  		SET_REG(VM_REG_GUEST_RBP, 0) ||
  		SET_REG(VM_REG_GUEST_RSP, 0) ||
  		SET_REG(VM_REG_GUEST_TR, 0) ||
  		SET_REG(VM_REG_GUEST_LDTR, 0) ||
  		SET_DESC(VM_REG_GUEST_CS, 0xffff0000, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_SS, 0, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_DS, 0, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_ES, 0, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_FS, 0, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_GS, 0, 0xffff, 0x0093) ||
  		SET_DESC(VM_REG_GUEST_GDTR, 0, 0xffff, 0) ||
  		SET_DESC(VM_REG_GUEST_IDTR, 0, 0xffff, 0) ||
  		SET_DESC(VM_REG_GUEST_TR, 0, 0, 0x0000008b) ||
  		SET_DESC(VM_REG_GUEST_LDTR, 0, 0xffff, 0x00000082))
  	{
  		return (error);
  	}
  
  	return (0);
  }
  
  int
  xh_vm_active_cpus(cpuset_t *cpus)
  {
  	*cpus = vm_active_cpus(vm);
  	return (0);
  }
  
  int
  xh_vm_suspended_cpus(cpuset_t *cpus)
  {
  	*cpus = vm_suspended_cpus(vm);
  	return (0);
  }
  
  int
  xh_vm_activate_cpu(int vcpu)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_activate_cpu(vm, vcpu);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_restart_instruction(int vcpu)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vm_restart_instruction(vm, vcpu);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  
  int
  xh_vm_emulate_instruction(int vcpu, uint64_t gpa, struct vie *vie,
  	struct vm_guest_paging *paging, mem_region_read_t memread,
  	mem_region_write_t memwrite, void *memarg)
  {
  	int error;
  
  	vcpu_freeze(vcpu, true);
  	error = vmm_emulate_instruction(vm, vcpu, gpa, vie, paging, memread,
  		memwrite, memarg);
  	vcpu_freeze(vcpu, false);
  
  	return (error);
  }
  