(*
 * Copyright (C) 2006-2007 XenSource Ltd.
 * Copyright (C) 2008      Citrix Ltd.
 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
 *
 * This program 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; version 2.1 only. with the special
 * exception on linking described in file LICENSE.
 *
 * This program 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.
 *)

(** *)
type domid = int

(* ** xenctrl.h ** *)

type vcpuinfo =
{
	online: bool;
	blocked: bool;
	running: bool;
	cputime: int64;
	cpumap: int32;
}

type domaininfo =
{
	domid             : domid;
	dying             : bool;
	shutdown          : bool;
	paused            : bool;
	blocked           : bool;
	running           : bool;
	hvm_guest         : bool;
	shutdown_code     : int;
	total_memory_pages: nativeint;
	max_memory_pages  : nativeint;
	shared_info_frame : int64;
	cpu_time          : int64;
	nr_online_vcpus   : int;
	max_vcpu_id       : int;
	ssidref           : int32;
	handle            : int array;
}

type sched_control =
{
	weight : int;
	cap    : int;
}

type physinfo_cap_flag =
	| CAP_HVM
	| CAP_DirectIO

type physinfo =
{
	threads_per_core : int;
	cores_per_socket : int;
	nr_cpus          : int;
	max_node_id      : int;
	cpu_khz          : int;
	total_pages      : nativeint;
	free_pages       : nativeint;
	scrub_pages      : nativeint;
	(* XXX hw_cap *)
	capabilities     : physinfo_cap_flag list;
	max_nr_cpus      : int;
}

type version =
{
	major : int;
	minor : int;
	extra : string;
}


type compile_info =
{
	compiler : string;
	compile_by : string;
	compile_domain : string;
	compile_date : string;
}

type shutdown_reason = Poweroff | Reboot | Suspend | Crash | Halt

type domain_create_flag = CDF_HVM | CDF_HAP

exception Error of string

type handle

(* this is only use by coredumping *)
external sizeof_core_header: unit -> int
       = "stub_sizeof_core_header"
external sizeof_vcpu_guest_context: unit -> int
       = "stub_sizeof_vcpu_guest_context"
external sizeof_xen_pfn: unit -> int = "stub_sizeof_xen_pfn"
(* end of use *)

external interface_open: unit -> handle = "stub_xc_interface_open"
external interface_close: handle -> unit = "stub_xc_interface_close"

external is_fake: unit -> bool = "stub_xc_interface_is_fake"

let with_intf f =
	let xc = interface_open () in
	let r = try f xc with exn -> interface_close xc; raise exn in
	interface_close xc;
	r

external _domain_create: handle -> int32 -> domain_create_flag list -> int array -> domid
       = "stub_xc_domain_create"

let int_array_of_uuid_string s =
	try
		Scanf.sscanf s
			"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
			(fun a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 ->
				[| a0; a1; a2; a3; a4; a5; a6; a7;
				   a8; a9; a10; a11; a12; a13; a14; a15 |])
	with _ -> invalid_arg ("Xc.int_array_of_uuid_string: " ^ s)

let domain_create handle n flags uuid =
	_domain_create handle n flags (int_array_of_uuid_string uuid)

external _domain_sethandle: handle -> domid -> int array -> unit
                          = "stub_xc_domain_sethandle"

let domain_sethandle handle n uuid =
	_domain_sethandle handle n (int_array_of_uuid_string uuid)

external domain_max_vcpus: handle -> domid -> int -> unit
       = "stub_xc_domain_max_vcpus"

external domain_pause: handle -> domid -> unit = "stub_xc_domain_pause"
external domain_unpause: handle -> domid -> unit = "stub_xc_domain_unpause"
external domain_resume_fast: handle -> domid -> unit = "stub_xc_domain_resume_fast"
external domain_destroy: handle -> domid -> unit = "stub_xc_domain_destroy"

external domain_shutdown: handle -> domid -> shutdown_reason -> unit
       = "stub_xc_domain_shutdown"

external _domain_getinfolist: handle -> domid -> int -> domaininfo list
       = "stub_xc_domain_getinfolist"

let domain_getinfolist handle first_domain =
	let nb = 2 in
	let last_domid l = (List.hd l).domid + 1 in
	let rec __getlist from =
		let l = _domain_getinfolist handle from nb in
		(if List.length l = nb then __getlist (last_domid l) else []) @ l
		in
	List.rev (__getlist first_domain)

external domain_getinfo: handle -> domid -> domaininfo= "stub_xc_domain_getinfo"

external domain_get_vcpuinfo: handle -> int -> int -> vcpuinfo
       = "stub_xc_vcpu_getinfo"

external domain_ioport_permission: handle -> domid -> int -> int -> bool -> unit
       = "stub_xc_domain_ioport_permission"
external domain_iomem_permission: handle -> domid -> nativeint -> nativeint -> bool -> unit
       = "stub_xc_domain_iomem_permission"
external domain_irq_permission: handle -> domid -> int -> bool -> unit
       = "stub_xc_domain_irq_permission"

external vcpu_affinity_set: handle -> domid -> int -> bool array -> unit
       = "stub_xc_vcpu_setaffinity"
external vcpu_affinity_get: handle -> domid -> int -> bool array
       = "stub_xc_vcpu_getaffinity"

external vcpu_context_get: handle -> domid -> int -> string
       = "stub_xc_vcpu_context_get"

external sched_id: handle -> int = "stub_xc_sched_id"

external sched_credit_domain_set: handle -> domid -> sched_control -> unit
       = "stub_sched_credit_domain_set"
external sched_credit_domain_get: handle -> domid -> sched_control
       = "stub_sched_credit_domain_get"

external shadow_allocation_set: handle -> domid -> int -> unit
       = "stub_shadow_allocation_set"
external shadow_allocation_get: handle -> domid -> int
       = "stub_shadow_allocation_get"

external evtchn_alloc_unbound: handle -> domid -> domid -> int
       = "stub_xc_evtchn_alloc_unbound"
external evtchn_reset: handle -> domid -> unit = "stub_xc_evtchn_reset"

external readconsolering: handle -> string = "stub_xc_readconsolering"

external send_debug_keys: handle -> string -> unit = "stub_xc_send_debug_keys"
external physinfo: handle -> physinfo = "stub_xc_physinfo"
external pcpu_info: handle -> int -> int64 array = "stub_xc_pcpu_info"

external domain_setmaxmem: handle -> domid -> int64 -> unit
       = "stub_xc_domain_setmaxmem"
external domain_set_memmap_limit: handle -> domid -> int64 -> unit
       = "stub_xc_domain_set_memmap_limit"
external domain_memory_increase_reservation: handle -> domid -> int64 -> unit
       = "stub_xc_domain_memory_increase_reservation"

external domain_set_machine_address_size: handle -> domid -> int -> unit
       = "stub_xc_domain_set_machine_address_size"
external domain_get_machine_address_size: handle -> domid -> int
       = "stub_xc_domain_get_machine_address_size"

external domain_cpuid_set: handle -> domid -> (int64 * (int64 option))
                        -> string option array
                        -> string option array
       = "stub_xc_domain_cpuid_set"
external domain_cpuid_apply_policy: handle -> domid -> unit
       = "stub_xc_domain_cpuid_apply_policy"
external cpuid_check: handle -> (int64 * (int64 option)) -> string option array -> (bool * string option array)
       = "stub_xc_cpuid_check"

external map_foreign_range: handle -> domid -> int
                         -> nativeint -> Xenmmap.mmap_interface
       = "stub_map_foreign_range"

external domain_get_pfn_list: handle -> domid -> nativeint -> nativeint array
       = "stub_xc_domain_get_pfn_list"

external domain_assign_device: handle -> domid -> (int * int * int * int) -> unit
       = "stub_xc_domain_assign_device"
external domain_deassign_device: handle -> domid -> (int * int * int * int) -> unit
       = "stub_xc_domain_deassign_device"
external domain_test_assign_device: handle -> domid -> (int * int * int * int) -> bool
       = "stub_xc_domain_test_assign_device"

external version: handle -> version = "stub_xc_version_version"
external version_compile_info: handle -> compile_info
       = "stub_xc_version_compile_info"
external version_changeset: handle -> string = "stub_xc_version_changeset"
external version_capabilities: handle -> string =
  "stub_xc_version_capabilities"

external watchdog : handle -> int -> int32 -> int
  = "stub_xc_watchdog"

(* core dump structure *)
type core_magic = Magic_hvm | Magic_pv

type core_header = {
	xch_magic: core_magic;
	xch_nr_vcpus: int;
	xch_nr_pages: nativeint;
	xch_index_offset: int64;
	xch_ctxt_offset: int64;
	xch_pages_offset: int64;
}

external marshall_core_header: core_header -> string = "stub_marshall_core_header"

(* coredump *)
let coredump xch domid fd =
	let dump s =
		let wd = Unix.write fd s 0 (String.length s) in
		if wd <> String.length s then
			failwith "error while writing";
		in

	let info = domain_getinfo xch domid in

	let nrpages = info.total_memory_pages in
	let ctxt = Array.make info.max_vcpu_id None in
	let nr_vcpus = ref 0 in
	for i = 0 to info.max_vcpu_id - 1
	do
		ctxt.(i) <- try
			let v = vcpu_context_get xch domid i in
			incr nr_vcpus;
			Some v
			with _ -> None
	done;

	(* FIXME page offset if not rounded to sup *)
	let page_offset =
		Int64.add
			(Int64.of_int (sizeof_core_header () +
			 (sizeof_vcpu_guest_context () * !nr_vcpus)))
			(Int64.of_nativeint (
				Nativeint.mul
					(Nativeint.of_int (sizeof_xen_pfn ()))
					nrpages)
				)
		in

	let header = {
		xch_magic = if info.hvm_guest then Magic_hvm else Magic_pv;
		xch_nr_vcpus = !nr_vcpus;
		xch_nr_pages = nrpages;
		xch_ctxt_offset = Int64.of_int (sizeof_core_header ());
		xch_index_offset = Int64.of_int (sizeof_core_header ()
					+ sizeof_vcpu_guest_context ());
		xch_pages_offset = page_offset;
	} in

	dump (marshall_core_header header);
	for i = 0 to info.max_vcpu_id - 1
	do
		match ctxt.(i) with
		| None -> ()
		| Some ctxt_i -> dump ctxt_i
	done;
	let pfns = domain_get_pfn_list xch domid nrpages in
	if Array.length pfns <> Nativeint.to_int nrpages then
		failwith "could not get the page frame list";

	let page_size = Xenmmap.getpagesize () in
	for i = 0 to Nativeint.to_int nrpages - 1
	do
		let page = map_foreign_range xch domid page_size pfns.(i) in
		let data = Xenmmap.read page 0 page_size in
		Xenmmap.unmap page;
		dump data
	done

(* ** Misc ** *)

(**
   Convert the given number of pages to an amount in KiB, rounded up.
 *)
external pages_to_kib : int64 -> int64 = "stub_pages_to_kib"
let pages_to_mib pages = Int64.div (pages_to_kib pages) 1024L

let _ = Callback.register_exception "xc.error" (Error "register_callback")
