#include "SoC.h"
#include "CPU.h"
#include "MMU.h"
#include "mem.h"
#include "callout_RAM.h"
#include "RAM.h"
#include "cp15.h"
#include "math64.h"
#include "pxa255_IC.h"
#include "pxa255_TIMR.h"
#include "pxa255_RTC.h"
#include "pxa255_UART.h"
#include "pxa255_PwrClk.h"
#include "pxa255_GPIO.h"
#include "pxa255_DMA.h"
#include "pxa255_DSP.h"
#include "pxa255_LCD.h"
#ifdef EMBEDDED
	#include <avr/io.h>
#endif


#define ERR(s)	do{err_str(s " Halting\r\n"); while(1); }while(0)

static const UInt8 embedded_boot[] =	{
						0x01, 0x00, 0x8F, 0xE2, 0x10, 0xFF, 0x2F, 0xE1, 0x04, 0x27, 0x01, 0x20, 0x00, 0x21, 0x00, 0xF0,
						0x0D, 0xF8, 0x0A, 0x24, 0x24, 0x07, 0x65, 0x1C, 0x05, 0x27, 0x00, 0x22, 0x00, 0xF0, 0x06, 0xF8,
						0x20, 0x60, 0x24, 0x1D, 0x49, 0x1C, 0x80, 0x29, 0xF8, 0xD1, 0x28, 0x47, 0xBC, 0x46, 0xBB, 0xBB,
						0x70, 0x47
					};

#define ROM_BASE	0x00000000UL
#define ROM_SIZE	sizeof(embedded_boot)

#define RAM_BASE	0xA0000000UL
#define RAM_SIZE	0x01000000UL	//16M @ 0xA0000000


static Boolean vMemF(ArmCpu* cpu, void* buf, UInt32 vaddr, UInt8 size, Boolean write, Boolean priviledged, UInt8* fsrP){
	
	SoC* soc = cpu->userData;
	UInt32 pa;
	
	if(size & (size - 1)){	//size is not a power of two
		
		return false;	
	}
	if(vaddr & (size - 1)){	//bad alignment
		
		return false;	
	}

	return mmuTranslate(&soc->mmu, vaddr, priviledged, write, &pa, fsrP) && memAccess(&soc->mem, pa, size, write, buf);
}


static Boolean hyperF(ArmCpu* cpu){		//return true if handled

	SoC* soc = cpu->userData;
	
	switch(cpu->regs[12]){
	
		case 0:{
		
			err_str("Hypercall 0 caught\r\n");
			soc->go = false;
			break;
		}
		
		case 1:{
			
			err_dec(cpu->regs[0]);
			break;
		}
	
		case 2:{
			
			char x[2];
			x[1] = 0;
			x[0] = cpu->regs[0];
			err_str(x);
			break;
		}
		
		case 3:{
			
			cpu->regs[0] = RAM_SIZE;
			break;
		}
		
		case 4:{			//block device access perform [do a read or write]
		
			//IN:
			// R0 = op
			// R1 = sector
			
			return soc->blkF(soc->blkD, cpu->regs[1], soc->blkDevBuf, cpu->regs[0]);
		}
		
		case 5:{			//block device buffer access [read or fill emulator's buffer]
		
			//IN:
			// R0 = word value
			// R1 = word offset (0, 1, 2...)
			// R2 = op (1 = write, 0 = read)
			//OUT:
			// R0 = word value
			
			if(cpu->regs[1] >= BLK_DEV_BLK_SZ / sizeof(UInt32)) return false;	//invalid request
			
			if(cpu->regs[2] == 0){
				
				cpu->regs[0] = soc->blkDevBuf[cpu->regs[1]];
			}
			else if(cpu->regs[2] == 1){
				
				soc->blkDevBuf[cpu->regs[1]] = cpu->regs[0];
			}
			else return false;
		}
	}
	return true;
}

static void setFaultAdrF(ArmCpu* cpu, UInt32 adr, UInt8 faultStatus){
	
	SoC* soc = cpu->userData;
	
	cp15SetFaultStatus(&soc->cp15, adr, faultStatus);
}

static void emulErrF(_UNUSED_ ArmCpu* cpu, const char* str){
	err_str("Emulation error: <<");
	err_str(str);
	err_str(">> halting\r\n");
	while(1);
}

static Boolean pMemReadF(void* userData, UInt32* buf, UInt32 pa){	//for DMA engine and MMU pagetable walks

	ArmMem* mem = userData;

	return memAccess(mem, pa, 4, false, buf);
}

static void dumpCpuState(ArmCpu* cpu, char* label){

	UInt8 i;

	if(label){
		err_str("CPU ");
		err_str(label);
		err_str("\r\n");
	}
	
	for(i = 0; i < 16; i++){
		err_str("R");
		err_dec(i);
		err_str("\t= 0x");
		err_hex(cpuGetRegExternal(cpu, i));
		err_str("\r\n");	
	}
	err_str("CPSR\t= 0x");
	err_hex(cpuGetRegExternal(cpu, ARM_REG_NUM_CPSR));
	err_str("\r\nSPSR\t= 0x");
	err_hex(cpuGetRegExternal(cpu, ARM_REG_NUM_SPSR));
	err_str("\r\n");
}

static UInt16 socUartPrvRead(void* userData){			//these are special funcs since they always get their own userData - the uart :)
	
	SoC* soc = userData;
	UInt16 v;
	int r;
	
	r = soc->rcF();
	if(r == CHAR_CTL_C) v = UART_CHAR_BREAK;
	else if(r == CHAR_NONE) v = UART_CHAR_NONE;
	else if(r >= 0x100) v = UART_CHAR_NONE;		//we canot send this char!!!
	else v = r;
	
	return v;
}

static void socUartPrvWrite(UInt16 chr, void* userData){	//these are special funcs since they always get their own userData - the uart :)
	
	SoC* soc = userData;
	
	if(chr == UART_CHAR_NONE) return;
	soc->wcF(chr);
}

void LinkError_SIZEOF_STRUCT_SOC_wrong();


void socRamModeAlloc(SoC* soc, _UNUSED_ void* ignored){
	
	UInt32* ramB = emu_alloc(RAM_SIZE);
	if(!ramB) ERR("Cannot allocate RAM buffer");
	if(!ramInit(&soc->ram.RAM, &soc->mem, RAM_BASE, RAM_SIZE, ramB)) ERR("Cannot init RAM");
	
	soc->calloutMem = false;	
}

void socRamModeCallout(SoC* soc, void* callout){
	
	if(!coRamInit(&soc->ram.coRAM, &soc->mem, RAM_BASE, RAM_SIZE, callout)) ERR("Cannot init coRAM");
	
	soc->calloutMem = true;	
}

#define ERR_(s)	ERR("error");

void socInit(SoC* soc, SocRamAddF raF, void*raD, readcharF rc, writecharF wc, blockOp blkF, void* blkD){

	Err e;
	
	soc->rcF = rc;
	soc->wcF = wc;
	
	soc->blkF = blkF;
	soc->blkD = blkD;

	soc->go = true;
	
	e = cpuInit(&soc->cpu, ROM_BASE, vMemF, emulErrF, hyperF, &setFaultAdrF);
	if(e){
		err_str("Failed to init CPU: ");
	//	err_dec(e);
	//	err_str(". Halting\r\n");
		while(1);
	}
	soc->cpu.userData = soc;
	
	memInit(&soc->mem);
	mmuInit(&soc->mmu, pMemReadF, &soc->mem);
	
	if(ROM_SIZE > sizeof(soc->romMem)) {
	//	err_str("Failed to init CPU: ");
		err_str("ROM_SIZE to small");
	//	err_str(". Halting\r\n");
		while(1);
	}
	
	raF(soc, raD);
	
	if(!ramInit(&soc->ROM, &soc->mem, ROM_BASE, ROM_SIZE, soc->romMem)) ERR_("Cannot init ROM");
	
	cp15Init(&soc->cp15, &soc->cpu, &soc->mmu);
	
	__mem_copy(soc->romMem, embedded_boot, sizeof(embedded_boot));
	
	if(!pxa255icInit(&soc->ic, &soc->cpu, &soc->mem)) ERR_("Cannot init PXA255's interrupt controller");
	if(!pxa255timrInit(&soc->timr, &soc->mem, &soc->ic)) ERR_("Cannot init PXA255's OS timers");
	if(!pxa255rtcInit(&soc->rtc, &soc->mem, &soc->ic)) ERR_("Cannot init PXA255's RTC");
	if(!pxa255uartInit(&soc->ffuart, &soc->mem, &soc->ic,PXA255_FFUART_BASE, PXA255_I_FFUART)) ERR_("Cannot init PXA255's FFUART");
	if(!pxa255uartInit(&soc->btuart, &soc->mem, &soc->ic,PXA255_BTUART_BASE, PXA255_I_BTUART)) ERR_("Cannot init PXA255's BTUART");
	if(!pxa255uartInit(&soc->stuart, &soc->mem, &soc->ic,PXA255_STUART_BASE, PXA255_I_STUART)) ERR_("Cannot init PXA255's STUART");
	if(!pxa255pwrClkInit(&soc->pwrClk, &soc->cpu, &soc->mem)) ERR_("Cannot init PXA255's Power and Clock manager");
	if(!pxa255gpioInit(&soc->gpio, &soc->mem, &soc->ic)) ERR_("Cannot init PXA255's GPIO controller");
	if(!pxa255dmaInit(&soc->dma, &soc->mem, &soc->ic)) ERR_("Cannot init PXA255's DMA controller");
	if(!pxa255dspInit(&soc->dsp, &soc->cpu)) ERR_("Cannot init PXA255's cp0 DSP");
	if(!pxa255lcdInit(&soc->lcd, &soc->mem, &soc->ic)) ERR_("Cannot init PXA255's LCD controller");

	pxa255uartSetFuncs(&soc->ffuart, socUartPrvRead, socUartPrvWrite, soc);	
}

void gdbCmdWait(SoC* soc, unsigned gdbPort, int* ss);

void socRun(SoC* soc, UInt32 gdbPort){
	
	UInt32 prevRtc = 0;
	UInt32 cyclesCapt = 0;
	
	UInt32 cycles = 0;	//make 64 if you REALLY need it... later
	
	#ifdef GDB_SUPPORT
		int ss = 1;	//for gdb stub single step
	#else
		gdbPort = 0;	//use the param somehow to quiet GCC
	#endif
	
	while(soc->go){
		
		cycles++;

	#ifdef EMBEDDED	
		if(!(PIND & 0x10)){	//btn down
		
			if(!prevRtc){
			
				do{
					prevRtc = gRtc;
				}while(prevRtc != gRtc);
				cyclesCapt = 0;
			}
			else{
			
				UInt32 t;
				
				//we only care to go on if the rtc is now different
				do{
					t = gRtc;
				}while(t != gRtc);
				
				if(t != prevRtc){
				
					if(!cyclesCapt){
						
						//this code assumes we're called often enough that the next rtc vals we see is the NEXT second, not the one after or any other such thing
						cyclesCapt = cycles;
						prevRtc = t;
					}
					else{
					
						err_dec(cycles - cyclesCapt);
						err_str(" Hz\r\n");
					
						cyclesCapt = 0;
						prevRtc = 0;
					}
				}
			}
		}
	#endif		
		
		if(!(cycles & 0x000007UL)) pxa255timrTick(&soc->timr);
		if(!(cycles & 0x0000FFUL)) pxa255uartProcess(&soc->ffuart);
		if(!(cycles & 0x000FFFUL)) pxa255rtcUpdate(&soc->rtc);
		if(!(cycles & 0x01FFFFUL)) pxa255lcdFrame(&soc->lcd);
		
		#ifdef GDB_SUPPORT
			gdbCmdWait(soc, gdbPort, &ss);
		#endif
		
		cpuCycle(&soc->cpu);
	}
}









#ifdef GDB_SUPPORT


	
	#include <sys/socket.h>
	#include <sys/time.h>
	#include <sys/types.h>
	#include <sys/select.h>
	#include <unistd.h>
	#include <errno.h>
	#include <stdlib.h>
	#include <netinet/in.h>
	#include <string.h>
	#include <stdio.h>
	
	
	
	static int socdBkptDel(SoC* soc, UInt32 addr, UInt8 sz){
		
		UInt8 i;
		
		for(i = 0; i < soc->nBkpt; i++){
			
			if(soc->bkpt[i] == addr){
				
				soc->nBkpt--;
				soc->bkpt[i] = soc->bkpt[soc->nBkpt];
				i--;
			}	
		}
		return 1;
	}
	
	
	static int socdBkptAdd(SoC* soc, UInt32 addr, UInt8 sz){	//boolean
		
		socdBkptDel(soc, addr, sz);
		
		if(soc->nBkpt == MAX_BKPT) return 0;
		
		soc->bkpt[soc->nBkpt++] = addr;
		
		return 1;
	}
	
	static int socdWtpDel(SoC* soc, UInt32 addr, UInt8 sz){
		
		UInt8 i;
		
		for(i = 0; i < soc->nWtp; i++){
			
			if(soc->wtpA[i] == addr && soc->wtpS[i] == sz){
				
				soc->nWtp--;
				soc->wtpA[i] = soc->wtpA[soc->nWtp];
				soc->wtpS[i] = soc->wtpS[soc->nWtp];
				i--;
			}	
		}
		return 1;
	}
	
	
	static int socdWtpAdd(SoC* soc, UInt32 addr, UInt8 sz){	//boolean
		
		socdWtpDel(soc, addr, sz);
		
		if(soc->nWtp == MAX_WTP) return 0;
		
		soc->wtpA[soc->nWtp] = addr;
		soc->wtpS[soc->nWtp] = sz;
		soc->nWtp++;
		
		return 1;
	}
	
	
	UInt32 htoi(const char** cP){
		
		UInt32 i = 0;
		const char* in = *cP;
		char c;
		
		while((c = *in) != 0){
			
			if(c >= '0' && c <= '9') i = (i * 16) + (c - '0');
			else if(c >= 'a' && c <= 'f') i = (i * 16) + (c + 10 - 'a');
			else if(c >= 'A' && c <= 'F') i = (i * 16) + (c + 10 - 'A');
			else break;
			in++;
		}
		
		*cP = in;
		
		return i;
	}
	
	static UInt32 swap32(UInt32 x){
		
		return ((x >> 24) & 0xff) | ((x >> 8) & 0xff00) | ((x & 0xff00) << 8) | ((x & 0xff) << 24);	
	}
	
	
	int gdb_memAccess(SoC* soc, UInt32 addr, UInt8* buf, int write){
		
		UInt32 pa = 0;
		UInt8 fsr = 0;
		
		return mmuTranslate(&soc->mmu, addr, true, false, &pa, &fsr) && memAccess(&soc->mem, pa, 1, write | 0x80, buf);
	}
	
	static int addRegToStr(SoC* soc, char* str, int reg){
		
		if(reg == 0x19 || reg < 0x10){
			if(reg == 0x19) reg = ARM_REG_NUM_CPSR;
			sprintf(str + strlen(str), "%08x", swap32(cpuGetRegExternal(&soc->cpu, reg)));
		}
		else if(reg >= 0x10 && reg < 0x18){
			
			strcat(str, "000000000000000000000000");
		}
		else if(reg == 0x18){	//fps
			
			strcat(str, "00000000");
		}
		else return 0;
		
		return 1;
	}
	
	static int interpPacket(SoC* soc,const char* in, char* out, int* ss){	//return 0 if we failed to interp a command, 1 is all ok, -1 to send no reply and run
		
		ArmCpu* cpu = &soc->cpu;
		unsigned char c;
		unsigned addr, len;
		unsigned char* ptr;
		int i;
		int ret = 1;
		
		
		
		if(strcmp(in, "qSupported") == 0){
			
			strcpy(out, "PacketSize=99");	
		}
		else if(strcmp(in, "vCont?") == 0){
			
			out[0] = 0;
		}
		else if(strcmp(in, "s") == 0){		//single step
			
			*ss = 1;
			return -1;
		}
		else if(strcmp(in, "c") == 0 || in[0] == 'C'){		//continue [with signal, which we ignore]
			
			return -1;
		}
		else if(in[0] == 'Z' || in[0] == 'z'){
			
			char op = in[0];
			char type = in[1];
			int (*f)(SoC* soc, UInt32 addr, UInt8 sz) = NULL;
			
			in += 3;
			addr = htoi(&in);
			if(*in++ != ',') goto fail;	//no comma?
			len = htoi(&in);
			if(*in) goto fail;		//string not over?
			
			if(type == '0' || type == '1'){	//bkpt
				
				f = (op == 'Z') ? socdBkptAdd : socdBkptDel;
			}
	/*
			else if(type == '2' || type == '3'){	//wtp
				
				f = (op == 'Z') ? socdWtpAdd : socdWtpDel;
			}
			else goto fail;
	*/
			strcpy(out,f(soc, addr, len) ? "OK" : "e00");
		}
		else if(in[0] == 'H' && (in[1] == 'c' || in[1] == 'g')){
			strcpy(out, "OK");	
		}
		else if(in[0] == 'q'){
			
			if(in[1] == 'C'){
				
				strcpy(out, "");	
			}
			else if(strcmp(in  +1, "Offsets") == 0){
				
				strcpy(out, "Text=0;Data=0;Bss=0");
			}
			else goto fail;
		}
		else if(in[0] == 'p'){	//read register
			
			in++;
			i = htoi(&in);
			if(*in) goto fail;	//string not over?
			
			out[0] = 0;
			if(!addRegToStr(soc, out, i)) goto fail;
		}
		else if(strcmp(in, "g") == 0){	//read all registers
			
			out[0] = 0;
			for(i = 0; i < 0x1a; i++) if(!addRegToStr(soc, out, i)) goto fail;
		}
		else if(in[0] == 'P'){	//write register
			
			in++;
			i = htoi(&in);
			if(*in++ != '=') goto fail;	//string not over?
			if(i == 0x19 || i <16){
				if(i == 0x19) i = ARM_REG_NUM_CPSR;
				addr = htoi(&in);
				sprintf(out, "OK");
				cpuSetReg(cpu, i, addr);
			}
			else strcpy(out,"e00");
		}
		else if(in[0] == 'm'){	//read memory
			
			in++;
			addr = htoi(&in);
			if(*in++ != ',') goto fail;
			len = htoi(&in);
			if(*in) goto fail;
			out[0] = 0;
			while(len--){
				
				if(!gdb_memAccess(soc, addr++, &c, false)) break;
				sprintf(out + strlen(out), "%02x", c);	
			}
		}
		else if(strcmp(in, "?") == 0){
			
			strcpy(out,"S05");	
		}
		else goto fail;
		
	send_pkt:
		return ret;
		
	fail:
		out[0] = 0;
		ret = 0;
		goto send_pkt;
	}
	
	static void sendpacket(int sock, char* packet, int withAck){
		
		unsigned int c;
		int i;
				
		c = 0;
		for(i = 0; i < strlen(packet); i++) c += packet[i];
		memmove(packet + (withAck ? 2 : 1), packet, strlen(packet) + 1);
		if(withAck){
			packet[0] = '+';
			packet[1] = '$';
		}
		else{
			packet[0] = '$';
		}
		sprintf(packet + strlen(packet), "#%02x", c & 0xFF);
		
		//printf("sending packet <<%s>>\n", packet);
		send(sock, packet, strlen(packet), 0);	
	}
	
	void gdbCmdWait(SoC* soc, unsigned gdbPort, int* ss){
		
		ArmCpu* cpu = &soc->cpu;
		static int running = 0;
		static int sock = -1;
		char packet[4096];
		struct timeval tv = {0};
		fd_set set;
		int ret;
		
		if(*ss && running){
			
			strcpy(packet,"S05");
			sendpacket(sock, packet, 0);
			running = 0;	//perform single step
		}
		*ss = 0;
		
		if(running){	//check for breakpoints
			
			UInt8 i;
			
			for(i = 0; i < soc->nBkpt; i++){
				
				if(soc->cpu.regs[15] == soc->bkpt[i]){
					
				//	printf("bkpt hit: pc=0x%08lX bk=0x%08lX i=%d\n", soc->cpu.regs[15], soc->bkpt[i], i);
					strcpy(packet,"S05");
					sendpacket(sock, packet, 0);
					running = 0;	//perform breakpoint hit
					break;
				}
			}
		}
		
		if(gdbPort){
			
			if(sock == -1){	//no socket yet - make one
				
				struct sockaddr_in sa = {AF_INET, htons(gdbPort)};
				socklen_t sl = sizeof(sa);
				
				inet_aton("127.0.0.1", &sa.sin_addr.s_addr);
				
				sock = socket(PF_INET, SOCK_STREAM, 0);
				if(sock == -1){
					err_str("gdb socket creation fails: ");
					err_dec(errno);
					ERR("\n");
				}
				
				ret = bind(sock, (struct sockaddr*)&sa, sizeof(sa));
				if(ret){
					err_str("gdb socket bind fails: ");
					err_dec(errno);
					ERR("\n");
				}
				
				ret = listen(sock, 1);
				if(ret){
					err_str("gdb socket listen fails: ");
					err_dec(errno);
					ERR("\n");
				}
				
				ret = accept(sock, (struct sockaddr*)&sa, &sl);
				if(ret == -1){
					err_str("gdb socket accept fails: ");
					err_dec(errno);
					ERR("\n");
				}
				close(sock);
				sock = ret;
				
				soc->nBkpt = 0;
				soc->nWtp = 0;
			}
		}
		if(gdbPort){
				
			do{
		
				FD_ZERO(&set);
				FD_SET(sock, &set);
				tv.tv_sec = running ? 0 : 0x00f00000UL;
				do{
					ret = select(sock + 1, &set, NULL, NULL, &tv);
				}while(!ret && !running);
				if(ret < 0){
					err_str("select fails: ");
					err_dec(errno);
					ERR("\n");
				}
				if(ret > 0){
					char c;
					char* p;
					int i, len = 0, esc = 0, end = 0;
					
					ret = recv(sock, &c, 1, 0);
					if(ret != 1) ERR("failed to receive byte (1)\n");
					
					if(c == 3){
						strcpy(packet,"S11");
						sendpacket(sock, packet, 0);
						running = 0;	//perform breakpoint hit
					}
					else if(c != '$'){
						//printf("unknown packet header '%c'\n", c);
					}
					else{
						do{
							if(esc){
								c = c ^ 0x20;
								esc = 0;
							}
							else if(c == 0x7d){
								esc = 1;
							}
							
							if(!esc){	//we cannot be here if we're being escaped
								
								packet[len++] = c;
								if(end == 0 && c == '#') end = 2;
								else if(end){
									
									end--;
									if(!end) break;
								}
								
								ret = recv(sock, &c, 1, 0);
								if(ret != 1) ERR("failed to receive byte (2)\n");
							}
						}while(1);
						packet[len] = 0;
						
						memmove(packet, packet + 1, len);
						len -= 4;
						packet[len] = 0;
						ret = interpPacket(soc, p = strdup(packet), packet, ss);
						if(ret == 0) printf("how do i respond to packet <<%s>>\n", p);
						if(ret == -1){	//ack it anyways
							char c = '+';
							send(sock, &c, 1, 0);
							running = 1;
						}
						else sendpacket(sock, packet, 1);
						
						emu_free(p);
					}
				}
			}while(!running);
		}
	}

#endif
