/*******************************************************
 *
 * COPYRIGHT (C) FUJITSU,LTD. 2001 ALL RIGHTS RESERVED.
 * 
 * Created by T.INDOH <indou.takao@jp.fujitsu.com>
 *
 *******************************************************/

#include "sial_api.h"
void sial_getmem(ull kp, void *p, int n);

#define __KERNEL__
#define _LINUX_EFI_H	/*dummy for IA64*/
#include <lcrash.h>
#undef __KERNEL__

#define __KERNEL__
#include <linux/wait.h>
#undef __KERNEL__

#define CMD_LKSTDUMP
#include <linux/lkst_private.h>

void lkst_write_buffer(struct lkst_event_record* pevt_rec, int nrec);
static uint32_t* tmpbuffer_ptr;
static int tmpbuffer_size;

uint64_t cpu_mhz;
uint32_t cpu_freq_l;
uint32_t cpu_freq_h;
uint64_t base_counter;
uint32_t base_counter_l;
uint32_t base_counter_h;
struct timespec base_time;
int buffer_id;
int cmos_flag;
FILE* fp_output;
static struct lkst_header *lkst=NULL;
static struct lkst_header_percpu *lkst_cpu=NULL;

VALUE_S* isDigitStr(VALUE_S *vp)
{
	char *errp;
	int x,result;
	char *s=sial_getptr(vp,char);
	x=strtol(s,&errp,0);
	result=( *errp=='\0' );
	return sial_makebtype(result);
}
VALUE_S* lkst_getoffset_lkst_h_tvsec()
{
	int offset = offsetof( struct lkst_header, xtime.tv_sec );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_h_tvusec()
{
	int offset = offsetof( struct lkst_header, xtime.tv_usec );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_h_tsc()
{
	int offset = offsetof( struct lkst_header, tsc );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_h_cpu_freq()
{
	int offset = offsetof( struct lkst_header, cpu_freq );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_h_cmos_flag()
{
	int offset = offsetof( struct lkst_header, cmos_time_flag );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_cpu_h()
{
	int offset = (char*)(&lkst_cpu[1]) - (char*)(&lkst_cpu[0]);
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_cpu_h_bufferp(VALUE_S* vi)
{
	int offset;
	int i=sial_getval(vi);
	offset = offsetof( struct lkst_header_percpu, buffer_table[i].ptr );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_lkst_h_sbuf_body()
{
	int offset;
	offset = offsetof( struct lkst_header, static_buffer.body );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_evbuf_id()
{
	int offset = offsetof( struct lkst_event_buffer, id );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_evbuf_size()
{
	int offset = offsetof( struct lkst_event_buffer, size );
	return sial_makebtype(offset);
}

VALUE_S* lkst_getoffset_evbuf_body()
{
//	int offset = offsetof( struct lkst_event_buffer, body );
	int offset = (int)LKST_BUFFER_BODY(NULL);
	return sial_makebtype(offset);
}

VALUE_S* lkst_getvalue_buffer_id_max()
{
	return sial_makebtype(LKST_BUFFER_ID_MAX);
}

VALUE_S* lkst_getvalue_buffer_id_void()
{
	return sial_makebtype(LKST_BUFFER_ID_VOID);
}

VALUE_S* lkst_getvalue_cpu_max()
{
	return sial_makebtype(LKST_CPU_MAX);
}

VALUE_S* lkst_getvalue_sbuf_size()
{
	return sial_makebtype(LKST_STATIC_BUFFER_COUNT);
}

VALUE_S* lkst_getvalue_evrec_size()
{
	return sial_makebtype(sizeof(struct lkst_event_record));
}

VALUE_S* lkst_setdata_lkst_h_tvsec(VALUE_S* vdata)
{
	base_time.tv_sec = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_tvusec(VALUE_S* vdata)
{
	base_time.tv_nsec = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_tsc_l(VALUE_S* vdata)
{
	base_counter_l = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_tsc_h(VALUE_S* vdata)
{
	base_counter_h = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_cpu_freq_l(VALUE_S* vdata)
{
	cpu_freq_l = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_cpu_freq_h(VALUE_S* vdata)
{
	cpu_freq_h = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_lkst_h_cmos_flag(VALUE_S* vdata)
{
	cmos_flag = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_setdata_buffer_id(VALUE_S* vdata)
{
	buffer_id = sial_getval(vdata);
	return sial_makebtype(0);
}

VALUE_S* lkst_alloc_buffer(VALUE_S* vsize)
{
	tmpbuffer_size = sial_getval(vsize);
	if(tmpbuffer_ptr != NULL) free(tmpbuffer_ptr);
	tmpbuffer_ptr = malloc(tmpbuffer_size);
	return sial_makebtype(0);
}

VALUE_S* lkst_copy_buffer_pointer(VALUE_S* vptr, VALUE_S* vlen)
{
	int *ptr;
	int  len;

	ptr = (int*)(long)sial_getval(vptr);
	len = (int)sial_getval(vlen);

	if((tmpbuffer_size <= len/sizeof(int))||(tmpbuffer_ptr == NULL))
		return sial_makebtype(1);
	sial_getmem( (ull)(long)(ptr), &(tmpbuffer_ptr[0]), len );


	return sial_makebtype(0);
}

VALUE_S* lkst_copy_buffer(VALUE_S* vidx, VALUE_S* vdata)
{
	int index = sial_getval(vidx);
	int data = sial_getval(vdata);
	if((tmpbuffer_size <= index)||(tmpbuffer_ptr == NULL))
		return sial_makebtype(1);
	tmpbuffer_ptr[index] = data;
	return sial_makebtype(0);
}

VALUE_S* lkst_write_buffer_file()
{
	lkst_write_buffer((struct lkst_event_record*)tmpbuffer_ptr,
	             tmpbuffer_size/sizeof(struct lkst_event_record));
	return sial_makebtype(0);
}

VALUE_S* lkst_release_buffer()
{
	if(tmpbuffer_ptr != NULL) free(tmpbuffer_ptr);
	tmpbuffer_ptr = NULL;
	tmpbuffer_size = 0;
	return sial_makebtype(0);
}

VALUE_S* lkst_check_and_init(VALUE_S* lkst_addr)
{
	char   magic_num[5];

	lkst = (struct lkst_header*)(long)sial_getval(lkst_addr);
	sial_getmem( (ull)(long)(lkst->eyecatcher), &(magic_num[0]), sizeof(lkst->eyecatcher) );
	if( strncmp( magic_num, "LKST", 4) != 0 ) { /* check the magic number 'L','K','S','T' */
		return sial_makebtype(1);
	}
	return sial_makebtype(0);
}

VALUE_S* lkst_check_and_init_cpu(VALUE_S* lkst_cpu_addr)
{
	char   magic_num[5];

	lkst_cpu = (struct lkst_header_percpu*)(long)sial_getval(lkst_cpu_addr);
	sial_getmem( (ull)(long)(lkst_cpu->eyecatcher), &(magic_num[0]), sizeof(lkst_cpu->eyecatcher) );
	if( strncmp( magic_num, "LKCP", 4) != 0 ) { /* check the magic number 'L','K','S','T' */
		return sial_makebtype(1);
	}
	return sial_makebtype(0);
}

VALUE_S* lkst_open_write_file(VALUE_S* vfname)
{
	char* fname = (char*)(long)sial_getval(vfname);
	if (0 == strncmp("-", fname, 1)) {
		fp_output = stdout;
	}
	else if((fp_output = fopen(fname, "w")) == NULL) {
		return sial_makebtype(1);
	}

	/* ** update for version 1.2                        ** */
	/* ** added header and info-record in lksedump file ** */
	{
	#include <linux/stddef.h>
	#include <linux/lkst_buffer.h>
		log_header_t logh = {
			log_magic: LOGFILE_MAGIC,
			log_version: 0
		};
		struct posix_log_entry posix_entry = {
			log_size: 2*sizeof(int) + sizeof(char)*LKST_ARCH_NAME_LEN,
			log_format:PXLOG_BINARY
		};
		int endian_big;
		int buf_ver;
		char arch[LKST_ARCH_NAME_LEN];

		sial_getmem( (ull)(long)(&(lkst->buf_ver)), &(buf_ver), sizeof(lkst->buf_ver) );
		sial_getmem( (ull)(long)(&(lkst->endian_big)), &(endian_big), sizeof(lkst->endian_big) );
		sial_getmem( (ull)(long)( lkst->arch ), &(arch), sizeof(lkst->arch) );

		fwrite( &logh,         sizeof(log_header_t),            1, fp_output );
		fwrite( &posix_entry,  sizeof(struct posix_log_entry),  1, fp_output );
		fwrite( &(endian_big), sizeof(int),                     1, fp_output );
		fwrite( &(buf_ver),    sizeof(int),                     1, fp_output );
		fwrite(  arch,         sizeof(char)*LKST_ARCH_NAME_LEN, 1, fp_output );
	}
	/* ** end ********************************************** */

	return sial_makebtype(0);
}

VALUE_S* lkst_close_write_file()
{
	fclose(fp_output);
	return sial_makebtype(0);
}


BT_SPEC_TABLE = {
	{ "int isDigitStr(string vp)",			  (void*)isDigitStr},
	{ "int lkst_getoffset_lkst_h_tvsec()",            (void*)lkst_getoffset_lkst_h_tvsec},
	{ "int lkst_getoffset_lkst_h_tvusec()",           (void*)lkst_getoffset_lkst_h_tvusec},
	{ "int lkst_getoffset_lkst_h_tsc()",              (void*)lkst_getoffset_lkst_h_tsc},
	{ "int lkst_getoffset_lkst_h_cpu_freq()",         (void*)lkst_getoffset_lkst_h_cpu_freq},
	{ "int lkst_getoffset_lkst_h_cmos_flag()",        (void*)lkst_getoffset_lkst_h_cmos_flag},
	{ "int lkst_getoffset_lkst_cpu_h()",		  (void*)lkst_getoffset_lkst_cpu_h},
	{ "int lkst_getoffset_lkst_cpu_h_bufferp(int i)", (void*)lkst_getoffset_lkst_cpu_h_bufferp},
	{ "int lkst_getoffset_lkst_h_sbuf_body()",        (void*)lkst_getoffset_lkst_h_sbuf_body},
	{ "int lkst_getoffset_evbuf_id()",                (void*)lkst_getoffset_evbuf_id},
	{ "int lkst_getoffset_evbuf_size()",              (void*)lkst_getoffset_evbuf_size},
	{ "int lkst_getoffset_evbuf_body()",              (void*)lkst_getoffset_evbuf_body},
	{ "int lkst_getvalue_sbuf_size()",                (void*)lkst_getvalue_sbuf_size},
	{ "int lkst_getvalue_cpu_max()",                  (void*)lkst_getvalue_cpu_max},
	{ "int lkst_getvalue_buffer_id_max()",            (void*)lkst_getvalue_buffer_id_max},
	{ "int lkst_getvalue_evrec_size()",               (void*)lkst_getvalue_evrec_size},
	{ "int lkst_getvalue_buffer_id_void()",           (void*)lkst_getvalue_buffer_id_void},
	{ "int lkst_setdata_lkst_h_tvsec(int data)",      (void*)lkst_setdata_lkst_h_tvsec},
	{ "int lkst_setdata_lkst_h_tvusec(int data)",     (void*)lkst_setdata_lkst_h_tvusec},
	{ "int lkst_setdata_lkst_h_tsc_l(int data)",      (void*)lkst_setdata_lkst_h_tsc_l},
	{ "int lkst_setdata_lkst_h_tsc_h(int data)",      (void*)lkst_setdata_lkst_h_tsc_h},
	{ "int lkst_setdata_lkst_h_cpu_freq_l(int data)", (void*)lkst_setdata_lkst_h_cpu_freq_l},
	{ "int lkst_setdata_lkst_h_cpu_freq_h(int data)", (void*)lkst_setdata_lkst_h_cpu_freq_h},
	{ "int lkst_setdata_lkst_h_cmos_flag(int data)",  (void*)lkst_setdata_lkst_h_cmos_flag},
	{ "int lkst_setdata_buffer_id(int data)",         (void*)lkst_setdata_buffer_id},
	{ "int lkst_open_write_file(string vfname)",      (void*)lkst_open_write_file},
	{ "int lkst_close_write_file()",                  (void*)lkst_close_write_file},
	{ "int lkst_alloc_buffer(int size)", 		  (void*)lkst_alloc_buffer},
	{ "int lkst_copy_buffer_pointer(int ptr, int len)", (void*)lkst_copy_buffer_pointer},
	{ "int lkst_copy_buffer(int idx, int data)",	  (void*)lkst_copy_buffer},
	{ "int lkst_write_buffer_file()",		  (void*)lkst_write_buffer_file},
	{ "int lkst_release_buffer()",			  (void*)lkst_release_buffer},
	{ "int lkst_check_and_init(int lkst_addr)",	  (void*)lkst_check_and_init},
	{ "int lkst_check_and_init_cpu(int lkst_cpu_addr)", (void*)lkst_check_and_init_cpu},
	{ 0, 0}
};

BT_INIDSO_FUNC()
{
	sial_msg("lkstdumplib being initialized\n");
	return 1;
}

BT_ENDDSO_FUNC()
{
	sial_msg("lkstdumplib being shutdown\n");

	if(tmpbuffer_ptr != NULL) free(tmpbuffer_ptr);
	tmpbuffer_ptr = NULL;
	tmpbuffer_size = 0;
}



void lkst_write_buffer(struct lkst_event_record* pevt_rec, int nrec)
{
	int j;
	struct lkst_log_record* plog_rec;
	struct timespec diff_time;
	uint64_t log_counter, diff_counter;

	cpu_mhz = (((uint64_t)cpu_freq_h << 32) + (uint64_t)cpu_freq_l)*1000;
	base_counter = ((uint64_t)base_counter_h << 32) + (uint64_t)base_counter_l;
	if(cpu_mhz == 0) {
		printf("cpu_mhz is 0\n");
		return;
	}

	plog_rec = malloc(nrec*sizeof(struct lkst_log_record));

	for (j=0; j<nrec; j++) {
		plog_rec[j].posix.log_magic        = 0x76758384;
		plog_rec[j].posix.log_recid        = pevt_rec[j].log_recid;
		plog_rec[j].posix.log_size         = sizeof(lkst_arg_t) * 4;
		plog_rec[j].posix.log_format       = PXLOG_BINARY;
		plog_rec[j].posix.log_event_type   = pevt_rec[j].log_event_type;
		plog_rec[j].posix.log_facility     = LOG_KERN;
		plog_rec[j].posix.log_severity     = LOG_DEBUG;
		plog_rec[j].posix.log_uid          = -1;
		plog_rec[j].posix.log_gid          = -1;
		plog_rec[j].posix.log_pid          = pevt_rec[j].log_pid;
		plog_rec[j].posix.log_pgrp         = -1;
		plog_rec[j].posix.log_flags        = 0;
		plog_rec[j].posix.log_thread       = -1;
		plog_rec[j].posix.log_processor    = pevt_rec[j].processor;
		plog_rec[j].log_arg1 = pevt_rec[j].log_arg1;
		plog_rec[j].log_arg2 = pevt_rec[j].log_arg2;
		plog_rec[j].log_arg3 = pevt_rec[j].log_arg3;
		plog_rec[j].log_arg4 = pevt_rec[j].log_arg4;

		/* convert mc to time */
		log_counter = pevt_rec[j].log_time; /* pevt_rec[j].log_time has cpu counter */
		if(base_counter > log_counter) {
			diff_counter = base_counter - log_counter;
			diff_time.tv_sec  = (unsigned long)(diff_counter/cpu_mhz);
			diff_time.tv_nsec = (unsigned long)((diff_counter%cpu_mhz)*1000000000/cpu_mhz);

			if(base_time.tv_nsec > diff_time.tv_nsec){
				plog_rec[j].posix.log_time.tv_sec  = base_time.tv_sec-diff_time.tv_sec;
				plog_rec[j].posix.log_time.tv_nsec = base_time.tv_nsec-diff_time.tv_nsec;
			}
			else {
				plog_rec[j].posix.log_time.tv_sec  = base_time.tv_sec-diff_time.tv_sec-1;
				plog_rec[j].posix.log_time.tv_nsec = base_time.tv_nsec+1000000000-diff_time.tv_nsec;
			}

		}
		else {
			diff_counter = log_counter - base_counter;
			diff_time.tv_sec  = (unsigned long)(diff_counter/cpu_mhz);
			diff_time.tv_nsec = (unsigned long)((diff_counter%cpu_mhz)*1000000000/cpu_mhz);

			if(base_time.tv_nsec + diff_time.tv_nsec >= 1000000000){
				plog_rec[j].posix.log_time.tv_sec  = base_time.tv_sec+diff_time.tv_sec+1;
				plog_rec[j].posix.log_time.tv_nsec = base_time.tv_nsec+diff_time.tv_nsec-1000000000;
			}
			else {
				plog_rec[j].posix.log_time.tv_sec  = base_time.tv_sec+diff_time.tv_sec;
				plog_rec[j].posix.log_time.tv_nsec = base_time.tv_nsec+diff_time.tv_nsec;
			}
		}

		/* convert localtime to UTC */
		if(cmos_flag) {
			struct timezone tz;
			gettimeofday(NULL, &tz);
			plog_rec[j].posix.log_time.tv_sec += tz.tz_minuteswest*60;
		}
	}
	fwrite(plog_rec, sizeof(struct lkst_log_record), nrec, fp_output);
	free(plog_rec);
}

