// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>

#include <xtensa/config/core.h>

#include "rom/rtc.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_api.h"

#include "soc/uart_reg.h"
#include "soc/io_mux_reg.h"
#include "soc/dport_reg.h"
#include "soc/rtc_cntl_reg.h"

#include "gdbstub.h"

/*
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
task switching / interrupt code runs into an unrecoverable error. The default task stack
overflow handler also is in here.
*/

#if !CONFIG_FREERTOS_PANIC_SILENT_REBOOT
//printf may be broken, so we fix our own printing fns...
inline static void panicPutchar(char c) {
	while (((READ_PERI_REG(UART_STATUS_REG(0))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT)>=126) ;
	WRITE_PERI_REG(UART_FIFO_REG(0), c);
}

inline static void panicPutStr(const char *c) {
	int x=0;
	while (c[x]!=0) {
		panicPutchar(c[x]);
		x++;
	}
}

inline static void panicPutHex(int a) {
	int x;
	int c;
	panicPutchar(' ');
	for (x=0; x<8; x++) {
		c=(a>>28)&0xf;
		if (c<10) panicPutchar('0'+c); else panicPutchar('a'+c-10);
		a<<=4;
	}
}

inline static void panicPutDec(int a) {
	int n1, n2;
	n1=a%10;
	n2=a/10;
	panicPutchar(' ');
	if (n2==0) panicPutchar(' '); else panicPutchar(n2+'0');
	panicPutchar(n1+'0');
}
#else
//No printing wanted. Stub out these functions.
inline static void panicPutchar(char c) { }
inline static void panicPutStr(const char *c) { }
inline static void panicPutHex(int a) { }
inline static void panicPutDec(int a) { }
#endif

int xPortGetCoreID();

void  __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) {
	panicPutStr("***ERROR*** A stack overflow in task");
	panicPutStr((char*)pcTaskName);
	panicPutStr("has been detected.\r\n");
}

static const char *edesc[]={
		"IllegalInstruction", "Syscall", "InstructionFetchError", "LoadStoreError",
		"Level1Interrupt", "Alloca", "IntegerDivideByZero", "PCValue",
		"Privileged", "LoadStoreAlignment", "res", "res",
		"InstrPDAddrError", "LoadStorePIFDataError", "InstrPIFAddrError", "LoadStorePIFAddrError",
		"InstTLBMiss", "InstTLBMultiHit", "InstFetchPrivilege", "res",
		"InstrFetchProhibited", "res", "res", "res",
		"LoadStoreTLBMiss", "LoadStoreTLBMultihit", "LoadStorePrivilege", "res",
		"LoadProhibited", "StoreProhibited", "res", "res",
		"Cp0Dis", "Cp1Dis", "Cp2Dis", "Cp3Dis",
		"Cp4Dis", "Cp5Dis", "Cp6Dis", "Cp7Dis"
	};


void commonErrorHandler(XtExcFrame *frame);

//The fact that we've panic'ed probably means the other CPU is now running wild, possibly
//messing up the serial output, so we kill it here.
static void haltOtherCore() {
	if (xPortGetCoreID()==0) {
		//Kill app cpu
		CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_STALL_APPCPU_C1<<RTC_CNTL_SW_STALL_APPCPU_C1_S);
		SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, 0x21<<RTC_CNTL_SW_STALL_APPCPU_C1_S);
		CLEAR_PERI_REG_MASK(RTC_CNTL_SW_CPU_STALL_REG, RTC_CNTL_SW_STALL_APPCPU_C0<<RTC_CNTL_SW_STALL_APPCPU_C0_S);
		SET_PERI_REG_MASK(RTC_CNTL_SW_CPU_STALL_REG, 2<<RTC_CNTL_SW_STALL_APPCPU_C0_S);
	} else {
		//Kill pro cpu
		CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_STALL_PROCPU_C1<<RTC_CNTL_SW_STALL_PROCPU_C1_S);
		SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, 0x21<<RTC_CNTL_SW_STALL_PROCPU_C1_S);
		CLEAR_PERI_REG_MASK(RTC_CNTL_SW_CPU_STALL_REG, RTC_CNTL_SW_STALL_PROCPU_C0<<RTC_CNTL_SW_STALL_PROCPU_C0_S);
		SET_PERI_REG_MASK(RTC_CNTL_SW_CPU_STALL_REG, 2<<RTC_CNTL_SW_STALL_PROCPU_C0_S);
	}
}

//Returns true when a debugger is attached using JTAG.
static int inOCDMode() {
#if CONFIG_FREERTOS_DEBUG_OCDAWARE
	int dcr;
	int reg=0x10200C; //DSRSET register
	asm("rer %0,%1":"=r"(dcr):"r"(reg));
	return (dcr&0x1);
#else
	return 0; //Always return no debugger is attached.
#endif
}

void panicHandler(XtExcFrame *frame) {
	haltOtherCore();
	panicPutStr("Guru Meditation Error: Core ");
	panicPutDec(xPortGetCoreID());
	panicPutStr(" panic'ed.\r\n");

	if (inOCDMode()) {
		asm("break.n 1");
	}
	commonErrorHandler(frame);
}

static void setFirstBreakpoint(uint32_t pc) {
	asm(
		"wsr.ibreaka0 %0\n" \
		"rsr.ibreakenable a3\n" \
		"movi a4,1\n" \
		"or a4, a4, a3\n" \
		"wsr.ibreakenable a4\n" \
		::"r"(pc):"a3","a4");
}

void xt_unhandled_exception(XtExcFrame *frame) {
	int *regs=(int*)frame;
	int x;

	haltOtherCore();
	panicPutStr("Guru Meditation Error of type ");
	x=regs[20];
	if (x<40) panicPutStr(edesc[x]); else panicPutStr("Unknown");
	panicPutStr(" occurred on core ");
	panicPutDec(xPortGetCoreID());
	if (inOCDMode()) {
		panicPutStr(" at pc=");
		panicPutHex(regs[1]);
		panicPutStr(". Setting bp and returning..\r\n");
		//Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger
		//will kick in exactly at the context the error happened.
		setFirstBreakpoint(regs[1]);
		return;
	}
	panicPutStr(". Exception was unhandled.\r\n");
	commonErrorHandler(frame);
}


/*
We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the
serial port and either jump to the gdb stub, halt the CPU or reboot.
*/
void commonErrorHandler(XtExcFrame *frame) {
	int *regs=(int*)frame;
	int x, y;
	const char *sdesc[]={
		"PC      ","PS      ","A0      ","A1      ","A2      ","A3      ","A4      ","A5      ",
		"A6      ","A7      ","A8      ","A9      ","A10     ","A11     ","A12     ","A13     ",
		"A14     ","A15     ","SAR     ","EXCCAUSE","EXCVADDR","LBEG    ","LEND    ","LCOUNT  "};

	panicPutStr("Register dump:\r\n");

	for (x=0; x<24; x+=4) {
		for (y=0; y<4; y++) {
			if (sdesc[x+y][0]!=0) {
				panicPutStr(sdesc[x+y]);
				panicPutStr(": ");
				panicPutHex(regs[x+y+1]);
				panicPutStr("  ");
			}
		}
		panicPutStr("\r\n");
	}
#if CONFIG_FREERTOS_PANIC_GDBSTUB
	panicPutStr("Entering gdb stub now.\r\n");
	gdbstubPanicHandler(frame);
#elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT
	panicPutStr("Rebooting...\r\n");
	for (x=0; x<100; x++) ets_delay_us(1000);
	software_reset();
#else
	panicPutStr("CPU halted.\r\n");
	while(1);
#endif
}


void setBreakpointIfJtag(void *fn) {
	if (!inOCDMode()) return;
	setFirstBreakpoint((uint32_t)fn);
}