/*
 * srio_sm_win32.cpp
 *
 *  Created on: 22/giu/2022
 *      Author: chessaa
 */



/*
 * bsp_srio_mini_drv_mingw.cpp
 *
 */

#include "bsp_srio_mini_driver.h"

#include "dfe_emu_if.h"

#include <windows.h>

#include "grifo_timer_if.h"
#include "grifo_fw_blob.h"
#include "grifo_ctrlink_if.h"

typedef grifo_fw::grifo_fw_blob_nofooter_typed_t<grifo_fw::timer_raw_if_t> timer_fw_image_t;

//static HANDLE shmem;
//#include "batch_manager.h"

#include <stdio.h>
//#include "DataRecorder.h"

//#include "ghost_ipc.h"

static volatile dfe_emu_shared_memory_t* img;

static HANDLE shevn;
static HANDLE shevnin;
static HANDLE timerEvent;
static HANDLE eifEvent;

static HANDLE tHandle;
static DWORD tId;

static volatile unsigned int timer_period;

/*class DataRecorderHeader: public DataRecorderRaw
{
public:

	DataRecorderHeader(): DataRecorderRaw(true, "DSPHDRIN", 0) {}

	bool save(const void* data, unsigned int size)
	{
		return saveRaw(data, size, type_struct);
	}
};*/

struct emu_ctrlink_rx_cw_fileds_t
{
    unsigned int rw: 1;    //0=read
    unsigned int synch: 1; //0=asynch
    unsigned int spare: 2; //0=no skip
    unsigned int adr_err: 1;
    unsigned int last_ch: 1;
    unsigned int broadcast: 1;
    unsigned int spare2: 1;

    unsigned int tag: 8;

    unsigned int payload_len: 16;
};

union emu_ctrlink_rx_cw_t
{
    uint32_t raw;
    emu_ctrlink_rx_cw_fileds_t fields;
};

static void clinkExec(volatile uint32_t* dst, const volatile uint32_t* src)
{
	int cursor=0;
	for(int i=0; src[i]==grifo_fw::grifo_fw_block_raw_helper_t::k_som;)
	{
		grifo_fw::ctrlink_tx_header_t* h=(grifo_fw::ctrlink_tx_header_t*)(&src[i+1]);
		dst[cursor++]=grifo_fw::grifo_fw_block_raw_helper_t::k_som;
		dst[cursor++]=h->destination;

		emu_ctrlink_rx_cw_t cw;
		cw.raw=h->cw.raw;
		dst[cursor++]=cw.raw;
        if(h->cw.fields.rw==0 && (h->destination & 0x3FF0000)==0x100000) /* extract status */
        {
        	memset((void*)&dst[cursor], 0, sizeof(grifo_fw::fpga_info_t));
        }
        if (h->cw.fields.rw)
        {
        	i+=4;
        	i+=h->cw.fields.payload_len;
        }
        else
        {
        	cursor+=h->cw.fields.payload_len;
        	i+=4;
        }
        dst[cursor++]=grifo_fw::grifo_fw_block_raw_helper_t::k_eob;
        dst[cursor]=grifo_fw::grifo_fw_block_raw_helper_t::k_eob;
	}
}

static void dcExec(int pingpong)
{
	clinkExec(img->dc_rx.data[pingpong], &img->dc_tx.data[pingpong][8]);
}

static void excExec(int pingpong)
{
	clinkExec(img->exc_rx.data[pingpong], &img->exc_tx.data[pingpong][8]);
}

#include "aesa_emu.h"

static void aesa_write_(int pingpong)
{
	aesa_write(pingpong, (void*)img->aesa_rx.data[pingpong], (void*)img->aesa.data[pingpong]);
}

//static DataRecorderHeader recHeader;

class MyWaitableTimer
{
public:
	HANDLE hTimer;
	LARGE_INTEGER liDueTime;

	volatile bool isActive;
	volatile int dfe_host_sim_timer_mode_;
	volatile int timer_programmed;

	volatile int sata_save;
	FILE* sataFile;

	volatile unsigned int timer_autorepeat;

	MyWaitableTimer()
	{
		hTimer = CreateWaitableTimer(NULL, 0, NULL);
		dfe_host_sim_timer_mode_=1; //-1; //1; //-1;
		sataFile=0;
		timer_autorepeat=1;
	}

    /*void sataSave(unsigned int dst, unsigned int dst_address, const void* data, unsigned int size)
	{
		bool rec_me=(dst==0x96) && ((dst_address==grifo_fw::HEADER_A) || (dst_address==grifo_fw::HEADER_B));
		if (!rec_me)
			return;
		if (!sataFile)
		{
			sataFile=fopen("./dfe_sata_rerecorded.rec", "wb");

			Recorder::configure(malloc(1024*100), 1024*100);
			Recorder::initialize();
			Recorder::sataStart();
		}

		if (sataFile)
		{
			const char* p=reinterpret_cast<const char*>(data);
			recHeader.save(&p[0x20+12], size-(0x20+12));
		}
    }*/

	void timerStart(unsigned int us)
	{
	}

	void setPeriod(unsigned int us_)
	{
		if (isActive)
			return;
		if (us_==0)
			return;
		int us=us_;
		us=-us;
		us*=10; //ns
		liDueTime.QuadPart=us;
		SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0);
		isActive=true;
	}

	void timerProgram(const void* raw)
	{
		if (dfe_host_sim_timer_mode_!=1)
			return;

		const timer_fw_image_t* t=reinterpret_cast<const timer_fw_image_t*>(raw);

        unsigned int prt_n=t->payload.prt_num;
        unsigned int prt_x=prt_n>128 ? 128 : prt_n;
        unsigned int tot=0;
        unsigned int prt_time=0;
        for(unsigned int i=0; i<prt_x; ++i)
        {
            prt_time=t->payload.prt_fifo_odd.fifo[i];
            tot+=prt_time;
        }
        for(unsigned i=prt_x; i<prt_n; ++i)
            tot+=prt_time;

        //timer_period=((tot*1/56.0)/1000.0)+0.5;
        timer_period=((tot*1/56.0))+0.5;
        timer_programmed=true;
        setPeriod(timer_period);
	}

    static void __monitor_invokable_autobatch();
};

static MyWaitableTimer autoMonitorable_tsim; //myWaitableTimer;

static MyWaitableTimer& myWaitableTimer=autoMonitorable_tsim;

void MyWaitableTimer::__monitor_invokable_autobatch()
{
	SetEvent(myWaitableTimer.hTimer);
}

void softrec_sata_forward(const void* p, unsigned int len)
{
	if (len && autoMonitorable_tsim.sataFile)
	{
		fwrite(p, 1, len, autoMonitorable_tsim.sataFile);
		fflush(autoMonitorable_tsim.sataFile);
	}

}

static DWORD WINAPI FwServer(LPVOID )
{
	static unsigned int batch_limbo=1;

	HANDLE evnlist[3]={shevnin, myWaitableTimer.hTimer, 0};

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
	SetThreadPriorityBoost(GetCurrentThread(), 1);

	for(;;)
	{
		//DWORD res=WaitForSingleObject(shevnin, INFINITE);
		DWORD res=WaitForMultipleObjects(2, evnlist, 0, 1000);

		if (res==WAIT_OBJECT_0)
		{
            //if (myWaitableTimer.dfe_host_sim_timer_mode_==-1)
//				BatchManager::fire();
		}
		else
		{
			bool timer_reprogram=false;
			if (res==WAIT_TIMEOUT)
			{
				if (myWaitableTimer.dfe_host_sim_timer_mode_==2)
				{
					myWaitableTimer.dfe_host_sim_timer_mode_=1;
					timer_reprogram=true;
					timer_period=10000;
					myWaitableTimer.timer_programmed=true;
				}
			}
			else
				timer_reprogram=res==(WAIT_OBJECT_0+1);
			if (timer_reprogram) //res==(WAIT_OBJECT_0+1))
			{
				aesa_event(-1);
				myWaitableTimer.isActive=false;
				if (myWaitableTimer.timer_programmed || myWaitableTimer.timer_autorepeat)
				{
					myWaitableTimer.timer_programmed=false;
					myWaitableTimer.setPeriod(timer_period);
					if (batch_limbo>0)
					{
						--batch_limbo;
						continue;
					}
					SetEvent(timerEvent);
					SetEvent(eifEvent);
    //				BatchManager::fire();
				}
			}
		}
	}
	return 0;
}

int bsp_srio_mini_drv_initialize(unsigned int /*id*/)
{
	static HANDLE shmem;
	const char* shname="GRIFO_FW_EMU";

	unsigned int msize=1024*1024;

	//shmem=OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shname);
	//if (shmem==0)
	shmem=CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, msize, shname);
	if (shmem==0)
	{
		img=new dfe_emu_shared_memory_t;
	}
	else
		img=reinterpret_cast<dfe_emu_shared_memory_t*>(MapViewOfFile(shmem, FILE_MAP_ALL_ACCESS, 0, 0, msize));

	shevn=CreateEventA(0, 0, 0, "GRIFO_FW_EMU_EVN_TO_FW");
	shevnin=CreateEventA(0, 0, 0, "GRIFO_FW_EMU_EVN_FROM_FW");
    timerEvent=CreateEventA(0, 0, 0, "GRIFO_FW_EMU_EVN_TIMER_BATCH");
    eifEvent=CreateEventA(0, 0, 0, "GRIFO_FW_EMU_EVN_EIF_BATCH");

	tHandle=CreateThread(0, 0, FwServer, 0, 0, &tId);
	return 0;
}


static void* emu_memory_read_nowait(unsigned int dst, unsigned int dst_address, unsigned int size, void* data)
{
	void* p=0;
	switch(dst_address)
	{
	case grifo_fw::AESA_RX_A:
		//aesa_event(0);
		//aesa_serve_event(0);
		p=(void*)img->aesa_rx.data[0];
		break;
	case grifo_fw::AESA_RX_B:
		//aesa_event(1);
		//aesa_serve_event(1);
		p=(void*)img->aesa_rx.data[1];
		break;

	case grifo_fw::CTRLINK_RCV_RX_A:
		p=(void*)img->dc_rx.data[0];
		//dcExec(0);
		break;
	case grifo_fw::CTRLINK_RCV_RX_B:
		p=(void*)img->dc_rx.data[1];
		//dcExec(1);
		break;
	case grifo_fw::CTRLINK_EXC_RX_A:
		p=(void*)img->exc_rx.data[0];
		//excExec(0);
		break;
	case grifo_fw::CTRLINK_EXC_RX_B:
		p=(void*)img->exc_rx.data[1];
		//excExec(1);
		break;
	}
	if (data)
	{
		memcpy(data, p, size);
	}
	return p;
}

int bsp_srio_mini_drv_memory_read_wait(unsigned int dst, unsigned int dst_address, unsigned int size,void* data)
{
	void* p=emu_memory_read_nowait(dst, dst_address, size, data);
	return p ? 0 : 1;
}

int bsp_srio_mini_drv_memory_read_nowait_callback(unsigned int dst, unsigned int dst_address, unsigned int size, bsp_srio_mini_drv_read_isr_t callback,void* cookie)
{
	void* p=emu_memory_read_nowait(dst, dst_address, size, 0);
#if 0
	void* p=0;
	switch(dst_address)
	{
	case grifo_fw::AESA_RX_A:
		//aesa_event(0);
		//aesa_serve_event(0);
		p=(void*)img->aesa_rx.data[0];
		break;
	case grifo_fw::AESA_RX_B:
		//aesa_event(1);
		//aesa_serve_event(1);
		p=(void*)img->aesa_rx.data[1];
		break;

	case grifo_fw::CTRLINK_RCV_RX_A:
		p=(void*)img->dc_rx.data[0];
		//dcExec(0);
		break;
	case grifo_fw::CTRLINK_RCV_RX_B:
		p=(void*)img->dc_rx.data[1];
		//dcExec(1);
		break;
	case grifo_fw::CTRLINK_EXC_RX_A:
		p=(void*)img->exc_rx.data[0];
		//excExec(0);
		break;
	case grifo_fw::CTRLINK_EXC_RX_B:
		p=(void*)img->exc_rx.data[1];
		//excExec(1);
		break;
	}
#endif
	if (p && callback)
	{
		callback(cookie, p, size, dst);
		return 0;
	}
	return 1;
}

int bsp_srio_mini_drv_enable(void)
{
	return 1;
}

int bsp_srio_mini_drv_start(unsigned int /*id*/)
{
	return 1;
}

int bsp_srio_mini_drv_set_doorbell_isr(bsp_srio_mini_drv_doorbell_isr_t /*h*/, void* /*cookie*/)
{
	return 0;
}

int bsp_srio_mini_drv_map_memory(void* /*physical_address*/, uintptr_t /*logical_address*/, unsigned int /*size_bytes*/)
{
	return 1;
}


int bsp_srio_mini_drv_memory_write_nowait(unsigned int dst, unsigned int dst_address, const void* source_address, unsigned int size, unsigned int doorbell, bool /*with_response*/)
{
	if (!img)
		return 0;
	unsigned int stdID=dst & 0x7F;

	if (stdID==0x16)
	{
		void* pm=0;
		void* dsp_hdr=0;
		int dsp_event=0;

		if (dst_address==grifo_fw::TIMER_A)
			pm=(void*)img->timer.data[0];
		if (dst_address==grifo_fw::TIMER_B)
			pm=(void*)img->timer.data[1];
		if (dst_address==grifo_fw::HEADER_A)
		{
			pm=(void*)img->header.data[0];
			dsp_hdr=pm;
			dsp_event=1;
		}
		if (dst_address==grifo_fw::HEADER_B)
		{
			pm=(void*)img->header.data[1];
			dsp_hdr=pm;
			dsp_event=2;
		}
		if (dst_address==grifo_fw::AESA_TX_A)
		{
			//aesa_write_(0);
			pm=(void*)img->aesa.data[0];
		}
		if (dst_address==grifo_fw::AESA_TX_B)
		{
			//aesa_write_(1);
			pm=(void*)img->aesa.data[1];
		}
		if (dst_address==grifo_fw::CTRLINK_EXC_TX_A)
		{
			pm=(void*)img->exc_tx.data[0];
		}
		if (dst_address==grifo_fw::CTRLINK_EXC_TX_B)
		{
			pm=(void*)img->exc_tx.data[1];
		}

		if (dst_address==grifo_fw::CTRLINK_RCV_TX_A)
		{
			pm=(void*)img->dc_tx.data[0];
		}
		if (dst_address==grifo_fw::CTRLINK_RCV_TX_B)
		{
			pm=(void*)img->dc_tx.data[1];
		}

		if (pm)
		{
			memcpy(pm, source_address, size);
            //autoMonitorable_tsim.sataSave(dst, dst_address, source_address, size);
		}

		if (dst_address==grifo_fw::AESA_TX_A)
		{
			aesa_write_(0);
		}
		if (dst_address==grifo_fw::AESA_TX_B)
		{
			aesa_write_(1);
		}

		if (doorbell!=bsp_srio_mini_drv_no_doorbell)
		{
			bsp_srio_mini_drv_send_doorbell(dst, doorbell);
			//img->event_code|=(1<<doorbell);
			//SetEvent(shevn);
		}
#if 0
		if (dsp_hdr)
		{
			dsp_event=0;
			const char* src=reinterpret_cast<const char*>(source_address);
			//src=&src[0x20];
			//unsigned int dsp_size=size-0x20;
			GhostDspIpc::writeHeader(dsp_event, src, size);
			GhostDspIpc::signalReadyInput();
		}
#endif
	}
	if (stdID==51) //DSP
	{
		if (dst_address==0x80FFFF00) //Timetag synch
		{
            //BatchManager::broadcastExternalSynchFrom(source_address);
		}
	}
	return 0;
}

int bsp_srio_mini_drv_send_doorbell(unsigned int dst, unsigned int doorbell)
{
	if (img && ((dst & 0x7F)==0x16))
	{
		if (doorbell!=bsp_srio_mini_drv_no_doorbell)
		{
			unsigned long db_code=bsp_srio_mini_drv_no_doorbell;
			switch(doorbell)
			{
			case grifo_fw::TIMER_A_EVENT:
				myWaitableTimer.timerProgram((void*)img->timer.data[0]);
				db_code=doorbell;
				break;
			case grifo_fw::TIMER_B_EVENT:
				myWaitableTimer.timerProgram((void*)img->timer.data[1]);
				db_code=doorbell;
				break;
			case grifo_fw::DISPATCHER_A_EVENT:
			case grifo_fw::DISPATCHER_B_EVENT:
				db_code=doorbell-16;
				break;
			case grifo_fw::AESA_TX_A_EVENT:
				aesa_tx_event(0);
				break;
			case grifo_fw::AESA_TX_B_EVENT:
				aesa_tx_event(1);
				break;
			case grifo_fw::AESA_RX_A_EVENT:
				aesa_rx_event(0);
				break;
			case grifo_fw::AESA_RX_B_EVENT:
				aesa_rx_event(1);
				break;

			case grifo_fw::CTRLINK_RCV_TX_A_EVENT:
				dcExec(0);
				break;
			case grifo_fw::CTRLINK_RCV_TX_B_EVENT:
				dcExec(1);
				break;
			case grifo_fw::CTRLINK_EXC_TX_A_EVENT:
				excExec(0);
				break;
			case grifo_fw::CTRLINK_EXC_TX_B_EVENT:
				excExec(1);
				break;

			default:
				break;
			}
			if (db_code!=bsp_srio_mini_drv_no_doorbell)
			{
				img->event_code|=(1<<db_code);
				if (myWaitableTimer.dfe_host_sim_timer_mode_<0)
					SetEvent(shevn);
			}
		}
	}
	return 0;
}

int bsp_srio_mini_drv_link_status(int hop,int port,int dstId)
{
	(void)(hop);
	(void)(port);
	(void)(dstId);
	return 0;
}

int bsp_srio_mini_drv_read_release(void* /*msg*/)
{
	return 1;
}

//typedef int (*bsp_srio_mini_drv_read_isr_t)( void* cookie,void* data, unsigned int len_bytes, unsigned int src);

int bsp_srio_mini_drv_set_read_isr(bsp_srio_mini_drv_read_isr_t /*h*/, void* /*cookie*/)
{
	return 1;
}

int bsp_srio_mini_drv_memory_read_nowait(unsigned int dst, unsigned int dst_address, void* source_address, unsigned int size, unsigned int /*doorbell*/)
{
	void* p=emu_memory_read_nowait(dst, dst_address, size, source_address);
	return p ? 0 : 1;
}

int bsp_srio_mini_drv_send_message_nowait(unsigned int /*dst*/, unsigned int /*mailbox*/, unsigned int /*letter*/, const void* /*data*/, unsigned int /*size_bytes*/)
{
	return 0;
}

int bsp_srio_mini_drv_flush_queue()
{
	return 1;
}

int bsp_srio_mini_drv_memory_write_wait(unsigned int /*dst*/, unsigned int /*dst_address*/, const void* /*source_address*/, unsigned int /*size*/, unsigned int /*doorbell*/, unsigned int /*timeout_ms*/, bool /*with_response*/)
{
	return 0;
}

int bsp_srio_mini_drv_lane_width(int /* hop*/,int /* port*/,int /* dstId*/)
{
	return 4;
}

int bsp_srio_mini_drv_set_message_isr(bsp_srio_mini_drv_message_isr_t h, void* cookie)
{
	(void)h;
	(void)cookie;
	return 0;
}

int bsp_srio_mini_drv_message_release(void* msg)
{
	(void)msg;
	return 0;
}

int bsp_srio_mini_drv_add_route(uint8_t destination_id,uint8_t destination_port,uint32_t hop_count)
{
	return 0;
}

unsigned int bsp_srio_mini_drv_get_id()
{
	return 0x56;
}


