/**
 * \file kernel/init.c
 *  License details are found in the file LICENSE.
 * \brief
 *  main function and initializer
 * \author Taku Shimosawa  <shimosawa@is.s.u-tokyo.ac.jp> \par
 *      Copyright (C) 2011 - 2012  Taku Shimosawa
 * \author Balazs Gerofi  <bgerofi@riken.jp> \par
 *      Copyright (C) 2012  RIKEN AICS
 * \author Tomoki Shirasawa  <tomoki.shirasawa.kk@hitachi-solutions.com> \par
 *      Copyright (C) 2012 - 2013 Hitachi, Ltd.
 * \author Balazs Gerofi  <bgerofi@is.s.u-tokyo.ac.jp> \par
 *      Copyright (C) 2013  The University of Tokyo
 */
/*
 * HISTORY:
 *  2013/09/02 shirasawa add terminate thread
 *  2013/06/02 balazs resolved merge conflicts with futex code
 *  2013/05/20 simin exchange the dcfa stuff init/exit order in mcexec
 */
#include <types.h>
#include <kmsg.h>
#include <kmalloc.h>
#include <ihk/cpu.h>
#include <ihk/mm.h>
#include <ihk/debug.h>
#include <ihk/dma.h>
#include <ihk/perfctr.h>
#include <process.h>
#include <init.h>
#include <cls.h>
#include <syscall.h>
#include <sysfs.h>
#include <ihk/monitor.h>

//#define IOCTL_FUNC_EXTENSION
#ifdef IOCTL_FUNC_EXTENSION
#include <ioctl.h>
#endif

//#define DEBUG_PRINT_INIT

#ifdef DEBUG_PRINT_INIT
#define dkprintf(...) do { kprintf(__VA_ARGS__); } while (0)
#define ekprintf(...) do { kprintf(__VA_ARGS__); } while (0)
#else
#define dkprintf(...) do { } while (0)
#define ekprintf(...) do { kprintf(__VA_ARGS__); } while (0)
#endif

#define DUMP_LEVEL_USER_UNUSED_EXCLUDE 24

extern unsigned long ihk_mc_get_ns_per_tsc(void);
extern long syscall(int, ihk_mc_user_context_t *);

struct ihk_os_monitor *monitor;
struct rusage_global *rusage;

static void handler_init(void)
{
	ihk_mc_set_syscall_handler(syscall);
}

unsigned long data[1024] __attribute__((aligned(64)));

#ifdef USE_DMA
static void dma_test(void)
{
	struct ihk_dma_request req;
	unsigned long fin = 0;
	int i, j;

	for (j = 0; j < 2; ++j) {
		fin = 0;

		memset(data, 0, 1024 * sizeof(unsigned long));

		for (i = 0; i < 8; i++) {
			data[i] = i;
		}

		kprintf("DMA Test Started.\n");
		memset(&req, 0, sizeof(req));
		req.src_os = IHK_THIS_OS;
		req.src_phys = virt_to_phys(data);
		req.dest_os = IHK_THIS_OS;
		req.dest_phys = virt_to_phys(&data[256]);
		req.size = 64;
		req.notify = (void *)virt_to_phys(&fin);
		req.notify_os = IHK_THIS_OS;
		req.priv = (void *)0x2984;

		kprintf("VtoP : %p, %lx\n", data, virt_to_phys(data));
		kprintf("notify : %p, %lx (%lx)\n", &fin, virt_to_phys(&fin),
				sizeof(req));

		if (ihk_mc_dma_request(0, &req) != 0) {
			kprintf("Failed to request DMA!\n");
		}
		kprintf("DMA Test Wait.\n");
		while (!fin) {
			barrier();
		}
		kprintf("DMA Test End.\n");

		for (i = 0; i < 8; i++) {
			if (data[i] != data[256 + i]) {
				kprintf("DMA result is inconsistent!\n");
				panic("");
			}
		}
	}
}
#endif

extern char *ihk_get_kargs(void);

char *find_command_line(char *name)
{
	char *cmdline = ihk_get_kargs();

	if (!cmdline) {
		return NULL;
	}
	return strstr(cmdline, name);
}

extern int safe_kernel_map;

static void parse_kargs(void)
{
	char *ptr;
	char *key_dump_level = "dump_level=";
	unsigned int dump_level = DUMP_LEVEL_USER_UNUSED_EXCLUDE;

	kprintf("KCommand Line: %s\n", ihk_get_kargs());

	/* parse dump_level option */
	ptr = find_command_line(key_dump_level);
	if (ptr) {
		ptr += strlen(key_dump_level);
		dump_level = 0;
		while (('0' <= *ptr) && (*ptr <= '9')) {
			dump_level *= 10;
			dump_level += *ptr++ - '0';
		}
	}
	ihk_mc_set_dump_level(dump_level);

	ptr = find_command_line("safe_kernel_map");
	if (ptr) {
		safe_kernel_map = 1;
	}

	/* idle_halt option */
	ptr = find_command_line("idle_halt");
	if (ptr) {
		idle_halt = 1;
	}

	/* allow_oversubscribe option */
	ptr = find_command_line("allow_oversubscribe");
	if (ptr) {
		allow_oversubscribe = 1;
	}
}

extern void ihk_mc_get_boot_time(unsigned long *tv_sec, unsigned long *tv_nsec);
extern unsigned long ihk_mc_get_ns_per_tsc(void);

static void time_init(void)
{
	unsigned long tv_sec, tv_nsec;
	unsigned long ns_per_kclock;
	unsigned long tsc;

	ihk_mc_get_boot_time(&tv_sec, &tv_nsec);
	ns_per_kclock = ihk_mc_get_ns_per_tsc();

	tod_data.origin.tv_sec = tv_sec;
	tod_data.origin.tv_nsec = tv_nsec;

	if (ns_per_kclock) {
		tod_data.clocks_per_sec = (1000L * NS_PER_SEC) / ns_per_kclock;

		tsc = rdtsc();
		tod_data.origin.tv_sec -= tsc / tod_data.clocks_per_sec;
		tod_data.origin.tv_nsec -= NS_PER_SEC * (tsc % tod_data.clocks_per_sec)
			/ tod_data.clocks_per_sec;
		if (tod_data.origin.tv_nsec < 0) {
			--tod_data.origin.tv_sec;
			tod_data.origin.tv_nsec += NS_PER_SEC;
		}
	}

	if (!ns_per_kclock) {
		gettime_local_support = 0;
	}

	if (gettime_local_support) {
		tod_data.do_local = 1;
	}
	return;
}

#ifdef POSTK_DEBUG_TEMP_FIX_73 /* NULL access for *monitor fix */
void monitor_init(void)
#else /* POSTK_DEBUG_TEMP_FIX_73 */
static void monitor_init()
#endif /* POSTK_DEBUG_TEMP_FIX_73 */
{
	int z;
	unsigned long phys;
#ifdef POSTK_DEBUG_TEMP_FIX_73 /* NULL access for *monitor fix */
	const struct ihk_mc_cpu_info *cpu_info = ihk_mc_get_cpu_info();

	if (!cpu_info) {
		panic("PANIC: in monitor_init() ihk_mc_cpu_info is NULL.");
		return;
	}

	z = sizeof(struct ihk_os_monitor) +
	    sizeof(struct ihk_os_cpu_monitor) * (cpu_info->ncpus - 1);
	z = (z + PAGE_SIZE -1) >> PAGE_SHIFT;
	monitor = ihk_mc_alloc_pages(z, IHK_MC_AP_CRITICAL);
	memset(monitor, 0, z * PAGE_SIZE);
	monitor->num_processors = (cpu_info->ncpus - 1);
	phys = virt_to_phys(monitor);
	ihk_set_monitor(phys, sizeof(struct ihk_os_monitor) +
	                    sizeof(struct ihk_os_cpu_monitor) * (cpu_info->ncpus - 1));
	return;
#else /* POSTK_DEBUG_TEMP_FIX_73 */

	z = sizeof(struct ihk_os_monitor) +
	    sizeof(struct ihk_os_cpu_monitor) * num_processors;
	z = (z + PAGE_SIZE -1) >> PAGE_SHIFT;
	monitor = ihk_mc_alloc_pages(z, IHK_MC_AP_CRITICAL);
	memset(monitor, 0, z * PAGE_SIZE);
	monitor->num_processors = num_processors;
	phys = virt_to_phys(monitor);
	ihk_set_monitor(phys, sizeof(struct ihk_os_monitor) +
	                    sizeof(struct ihk_os_cpu_monitor) * num_processors);
#endif /* POSTK_DEBUG_TEMP_FIX_73 */
}

int nmi_mode;

static void nmi_init()
{
	unsigned long phys;

	phys = virt_to_phys(&nmi_mode);
	ihk_set_nmi_mode_addr(phys);
}

static void rest_init(void)
{
	handler_init();

#ifdef USE_DMA
	ihk_mc_dma_init();
	dma_test();
#endif

	ap_init();
#ifndef POSTK_DEBUG_TEMP_FIX_73 /* NULL access for *monitor fix */
	monitor_init();
#endif /* !POSTK_DEBUG_TEMP_FIX_73 */
	cpu_local_var_init();
	nmi_init();
	time_init();
	kmalloc_init();

	ihk_ikc_master_init();

	proc_init();

	sched_init();
}

static void setup_remote_snooping_samples(void)
{
	static long lvalue = 0xf123456789abcde0;
	static char *svalue = "string(remote)";
	int error;
	struct sysfs_bitmap_param param;

	error = sysfs_createf(SYSFS_SNOOPING_OPS_d32, &lvalue, 0444, "/sys/test/remote/d32");
	if (error) {
		panic("setup_remote_snooping_samples: d32");
	}

	error = sysfs_createf(SYSFS_SNOOPING_OPS_d64, &lvalue, 0444, "/sys/test/remote/d64");
	if (error) {
		panic("setup_remote_snooping_samples: d64");
	}

	error = sysfs_createf(SYSFS_SNOOPING_OPS_u32, &lvalue, 0444, "/sys/test/remote/u32");
	if (error) {
		panic("setup_remote_snooping_samples: u32");
	}

	error = sysfs_createf(SYSFS_SNOOPING_OPS_u64, &lvalue, 0444, "/sys/test/remote/u64");
	if (error) {
		panic("setup_remote_snooping_samples: u64");
	}

	error = sysfs_createf(SYSFS_SNOOPING_OPS_s, svalue, 0444, "/sys/test/remote/s");
	if (error) {
		panic("setup_remote_snooping_samples: s");
	}

	param.nbits = 40;
	param.ptr = &lvalue;

	error = sysfs_createf(SYSFS_SNOOPING_OPS_pbl, &param, 0444, "/sys/test/remote/pbl");
	if (error) {
		panic("setup_remote_snooping_samples: pbl");
	}

	param.nbits = 40;
	param.ptr = &lvalue;

	error = sysfs_createf(SYSFS_SNOOPING_OPS_pb, &param, 0444, "/sys/test/remote/pb");
	if (error) {
		panic("setup_remote_snooping_samples: pb");
	}

	error = sysfs_createf(SYSFS_SNOOPING_OPS_u32K, &lvalue, 0444, "/sys/test/remote/u32K");
	if (error) {
		panic("setup_remote_snooping_samples: u32K");
	}

	return;
} /* setup_remote_snooping_samples() */

static void populate_sysfs(void)
{
	cpu_sysfs_setup();
	numa_sysfs_setup();
	//setup_remote_snooping_samples();
} /* populate_sysfs() */

int host_ikc_inited = 0;
extern int num_processors;

static void post_init(void)
{
	cpu_enable_interrupt();

	while (!host_ikc_inited) {
		barrier();
		cpu_pause();
	}

	if (find_command_line("hidos")) {
		int ikc_cpu = ihk_mc_get_ikc_cpu(ihk_mc_get_processor_id());
		if(ikc_cpu < 0) {
			ekprintf("%s,ihk_mc_get_ikc_cpu failed\n", __FUNCTION__);
		}
		init_host_ikc2mckernel();
		init_host_ikc2linux(ikc_cpu);
	}

	arch_setup_vdso();
	arch_start_pvclock();
	ap_start();

	sysfs_init();
	populate_sysfs();
}
#ifdef DCFA_RUN
extern void user_main();
#endif

#ifdef DCFA_KMOD
extern int mc_cmd_client_init(void);
extern void ibmic_cmd_init(void);
#endif

int main(void)
{
	arch_init();

	/*
	 * In attached-mic,
	 * bootparam is not mapped until arch_init() is finished.
	 * In builtin-mic and builtin-x86,
	 * virtual address of bootparam is changed in arch_init().
	 */
	parse_kargs();

	mem_init();

	rest_init();

	arch_ready();

	post_init();

	futex_init();

	done_init();
	kputs("IHK/McKernel booted.\n");

#ifdef DCFA_KMOD
	mc_cmd_client_init();
#ifdef CMD_DCFA
	ibmic_cmd_init();
#endif
#endif

#ifdef DCFA_RUN
	kputs("DCFA begin\n");

	user_main();

	kputs("DCFA end\n");
#else
	schedule();
#endif

	return 0;
}
