/******************************************************************************
版权所有：	
文件名称：	watchdog.c
文件版本：	01.00
创建作者:	   sunxi
创建日期：	2022-05-19
功能说明：	
			watchdog 的使用说明：
			1.本看门狗只侦察本进程中的各个线程的运行情况；
			2.如何使用，请参考watchdog_test();
			3. 在阻塞型的线程中，不能加入watchdog;
			4. 本狗为应用狗;

其它说明：	
修改记录：
*/

/*------------------------------- 头文件 --------------------------------------
*/

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <time.h>
#include <dirent.h>
#include <signal.h>
#include <sys/prctl.h>

#include "../include/bspconfig.h"
#include "rt.h"

#include "watchdog.h"
#include "shm_comm_packet.h"
#include "shm.h"


//#define WD_PATH	"/tmp/wdt_record"
#define WD_PATH	"/app/data/wdt_record" //noted by sunxi: 保存在flash中，重启后，不会掉失


#define WDT_MAX_ITEMS   64

#define wd_info_str ""

//心跳结构体
typedef struct HB_T
{
	uint16_t A;
	uint16_t B;
	uint16_t heartbeat_A; //每秒加1
	uint16_t heartbeat_B; //与heartbeat_A相同
	uint16_t crc;
}HB_T;

//裸核程序空间写完标志结构体
typedef struct SHM_E907_PRO_W_F_T
{
	uint16_t A;
	uint16_t B;
	uint16_t finish_flag; //裸核程序空间写完时，0x55写入finish_flag.
	uint32_t len;//裸核程序的实际长度
	uint16_t crc;
}SHM_E907_PRO_W_F_T;


typedef struct _wdt_item {
	const char *name;
	uint8_t     feed;
	uint32_t runing;//计时器
	uint32_t period;//秒
}wdt_item_t;

struct ap_watchdog_t
{
    int (*add_item)(const char *name, uint32_t *id, uint32_t period);
    int (*remove_item)(uint32_t id);
    int (*feed)(uint32_t id);
    int (*set_period)(uint32_t id, uint32_t seconds);
    uint32_t (*get_period)(uint32_t id);
    int (*start)(void);
    int (*stop)(void);
    int (*is_start)(void);
    int (*record)(const char *name);
    void (*set_debug)(int set);

	char appName[128];
    int _debug;
    wdt_item_t items[WDT_MAX_ITEMS];

    uint8_t runing_flag;

    pthread_mutex_t     mutex;
};

static pthread_t pthread_tid = 0;

int watchdog_feed_flag = 0;//放到心跳线程(与db进行心跳握手)，以确保watchdog_thread线程运行正常
struct ap_watchdog_t ap_watchdog;

extern uint32_t wdt_id_mainloop;

//检查文件夹是否存在
//不存在，创建
int CreateNewFolder(char* path)
{
	DIR *dir;

	if(path == NULL)
	{
		return -1;
	}

	//检查文件夹是否存在
	dir = opendir(path);
	if(NULL == dir)
 	{
 		//不存在，创建
 		mkdir(path,0775);
		
 	}
   	else
	{
		closedir(dir);
	}
	return 0;
}


int watchdog_init(void)
{
	int i;

	CreateNewFolder(WD_PATH);

    memset(ap_watchdog.items, 0, sizeof(ap_watchdog.items));

    for(i=0; i<WDT_MAX_ITEMS; i++)
        ap_watchdog.items[i].feed = 1;

    ap_watchdog.runing_flag = 0;
    ap_watchdog._debug = 0;

	strcpy(ap_watchdog.appName, "dtu_t536");

    pthread_mutex_init(&ap_watchdog.mutex, NULL);

	ap_watchdog.start();

	return 0;
}

int watchdog_exit(void)
{
	ap_watchdog.stop();

	pthread_join(pthread_tid, NULL);
	return 0;
}

//period:周期，单位为秒
static int add_item(const char *name, uint32_t *id, uint32_t period)
{
	int i;
	
    if(name ==  NULL)
        return -1;

    int ret = -1;

    // try to get mutex
    struct timespec tout;
    clock_gettime(CLOCK_REALTIME, &tout);
    tout.tv_sec += 5; //5s default

    if(pthread_mutex_timedlock(&ap_watchdog.mutex, &tout) != 0)
        return -1;

    for(i=0; i<WDT_MAX_ITEMS; i++) 
    {
        if(ap_watchdog.items[i].name == NULL) 
        {
            ap_watchdog.items[i].feed = 1;
			ap_watchdog.items[i].runing = 0;
			ap_watchdog.items[i].period = period;
            *id = i;
            ap_watchdog.items[i].name = name;
			rt_printf("WDT add_item i=%d,name= %s \r\n",i, ap_watchdog.items[i].name);
            ret = 0;
            break;
        }
    }

    pthread_mutex_unlock(&ap_watchdog.mutex);
	
    return ret;
}

static int remove_item(uint32_t id)
{
    if(id > WDT_MAX_ITEMS)
        return -1;

    // try to get mutex
    struct timespec tout;
    clock_gettime(CLOCK_REALTIME, &tout);
    tout.tv_sec += 5; //5s default

    if(pthread_mutex_timedlock(&ap_watchdog.mutex, &tout) != 0)
        return -1;

    ap_watchdog.items[id].name = NULL;
    ap_watchdog.items[id].feed = 1;
	ap_watchdog.items[id].runing = 0;

    pthread_mutex_unlock(&ap_watchdog.mutex);
    return 0;
}

static int feed(uint32_t id)
{
    if(id > WDT_MAX_ITEMS)
        return -1;

    ap_watchdog.items[id].feed = 1;
    return 0;
}

static int set_period(uint32_t id, uint32_t seconds) 
{
    if(id > WDT_MAX_ITEMS)
        return -1;

    ap_watchdog.items[id].period = seconds;
    return 0;

}

static uint32_t get_period(uint32_t id) 
{
    if(id > WDT_MAX_ITEMS)
        return -1;

    return ap_watchdog.items[id].period;
}

//设置心跳
static int write_freq_hb(uint8_t *data)
{
	int ret = 0;
	int cnt = 0;

	if(data == NULL)
		return -1;
		
    while(1)
    {
    	// ret = shm_comm_packet_write(SHM_ADDR_D_HB, data, sizeof(HB_T));
		if(ret > 0)	
		{
			break;
		}

		if(++cnt > 3)
			break;

        usleep(30);
    }
    return ret;
}

static int get_e907_PRO_W_F(void)
{
    SHM_E907_PRO_W_F_T flag;
	int ret = 0;
	int cnt = 0;
		
    while(1)
    {
		ret = shm_comm_packet_read(SHM_ADDR_U_PRO_W_F, sizeof(flag), (uint8_t *)&flag, sizeof(flag));
		if(ret > 0)	
		{
			if(flag.finish_flag == 0x55)
			 	return flag.len;
			else
				return -1;
		}

		if(++cnt > 3)
			break;

        usleep(30);
    }
    return ret;
}


static void *watchdog_thread(void *arg)
{
	int i = 0;
	static HB_T hb;
	int ret = 0;
	static int read_flag = 0;
	struct file * pfile;
	loff_t pos;

	memset(&hb,0,sizeof(hb));

    pthread_detach(pthread_self());

	prctl(PR_SET_NAME, "watchdog_thread");
    while (ap_watchdog.is_start()) {

        watchdog_feed_flag = 1;
        sleep(1);

		//把裸核程序读出来，并写到tmp文件夹中。
		if(read_flag == 0)
		{
			ret = get_e907_PRO_W_F();
			if(ret > 100)//返回裸核程序的实际长度，所以一定会大于100才有意义
			{
				//finish
				read_flag = 1;
				//从shm中读取裸核程序
				// 创建数据文件
				pfile = rt_file_open("/tmp/amp_rv0.bin",O_CREAT|O_RDWR,0);
				if(!IS_ERR(pfile))
				{
					pos = 0;
					rt_file_write(pfile, SHM_BASE_R+SHM_ADDR_U_E907_PRO, ret, &pos);
					rt_file_close(pfile,0);

				}

			}
		}

		hb.heartbeat_A++;
		hb.heartbeat_B = hb.heartbeat_A;
		write_freq_hb((uint8_t *)&hb); //给裸核的心跳

		for(i=0; i<WDT_MAX_ITEMS; i++) 
		{
			if(ap_watchdog.items[i].name != NULL) 
			{
				if(++ap_watchdog.items[i].runing > ap_watchdog.items[i].period)
				{
					if(ap_watchdog.items[i].feed != 1) 
					{
						ap_watchdog.record(ap_watchdog.items[i].name);

						rt_printf("WDT-------%s is timeout!!!\r\n",ap_watchdog.items[i].name);
						system("reboot -f");
                		//exit(EXIT_FAILURE);//TODO. noted by sunxi: 这里需要确认是，是重启应用程序，还是系统reboot!!!
					}
					else
					{
						if(ap_watchdog._debug) 
			            {
			                rt_printf("WDT-------%s is Runing.\r\n",ap_watchdog.items[i].name);
			            }
					}

					
					ap_watchdog.items[i].feed = 0;	
					ap_watchdog.items[i].runing = 0;

				}
			

			}
		}

    }
    return NULL;
}

static int start(void)
{
    int ret;

    if(ap_watchdog.runing_flag == 1)
    {
        return 0;
    }

	ap_watchdog._debug = 0;
    ap_watchdog.runing_flag = 1;

    ret = pthread_create(&pthread_tid, NULL, watchdog_thread, NULL);
    if(ret != 0) {
        rt_printf("can not create thread any more: %d\r\n",errno);
        ap_watchdog.runing_flag = 0;
        return -1;
    }
    return 0;
}

static int stop(void)
{
    ap_watchdog.runing_flag = 0;
    return 0;
}

static int is_start(void) 
{
    return ap_watchdog.runing_flag;
}

static int record(const char *name)
{
    time_t   tt;
	char buf[512];
	FILE * f = NULL;
	char  rec[256];
	char tmpBuf[128];
	int size;

    if(name == NULL)
        return -1;

    tt = time(NULL);

	memset(buf,0,sizeof(buf));
	sprintf(buf, "%s/wdt_record_%s", WD_PATH,ap_watchdog.appName);

    f = fopen(buf, "a");
	if(f == NULL) {
		perror("fopen");
        rt_printf("fopen is error!!!\r\n");
        return -1;
    }

	memset(tmpBuf, 0, sizeof(tmpBuf));
	strftime(tmpBuf, 128, "%Y-%m-%d %H:%M:%S", localtime(&tt));
	memset(rec, 0, sizeof(rec));
    size = snprintf(rec, sizeof(rec), "%s %s fuck you up; \r\n", tmpBuf, name);

    fwrite(rec, size, 1,  f);
    fclose(f);

    return 0;
}

static void set_debug(int set) 
{
    ap_watchdog._debug = set;
}

struct ap_watchdog_t ap_watchdog = 
{
    .add_item = add_item,
    .remove_item = remove_item,
    .feed = feed,
    .set_period = set_period,
    .get_period = get_period,
    .start = start,
    .stop = stop,
    .is_start = is_start,
    .record = record,
    .set_debug = set_debug,
};


/*
period: 看门狗的喂狗周期
因为可能存在某些线程，其运行过程很长，运行的中间，没有合里加sleep等休息函数，
造成占用时间较长。
于是，这里的period，可以尽量设置长一点的时间。

*/
int watchdog_add_item(const char *name, uint32_t *id, uint32_t period)
{

	int ret = 0;

	ret = ap_watchdog.add_item(name, id,period);

	return ret;

}

int watchdog_feed(uint32_t id)
{
	int ret = 0;

    ret = ap_watchdog.feed(id);

	
    return ret;
}

int watchdog_remove_item(uint32_t id)
{
	int ret = 0;

    ret = ap_watchdog.remove_item(id);

	
    return ret;
}


//===================================================================================================================
//test


void *watchdog_test_pthread(void *arg)
{

    pthread_detach(pthread_self());


    uint32_t wdt_id;
    int rc = watchdog_add_item("watchdog_test_pthread", &wdt_id,10);
    if(rc != 0) {
        printf("can not add watchdog_test_pthread task to wdt\r\n");
        return NULL;
    }


    while(1)
    {
    	//把喂狗操作放在首行，确保每个循环都有喂狗
    	watchdog_feed(wdt_id);// 去掉本行喂狗，就可以产生记录
    
        sleep(1);
	
    }

    watchdog_remove_item(wdt_id);

	return NULL;

}

int watchdog_test(void)
{
	int ret;
	watchdog_init();

	pthread_t ConnectCheck_id;
    ret = pthread_create(&ConnectCheck_id, NULL, watchdog_test_pthread, NULL);
    if(ret != 0) {
        printf("can not create watchdog_test_pthread\r\n");
        return -1;
    }
	return 0;
}













void watchdog_feed_mainloop(void)
{
	watchdog_feed(wdt_id_mainloop);

}
void watchdog_feed_protect(void)
{

}
void watchdog_reset_cpu(int i)
{
	printf("reboot code: %d .\n", i);
	system("reboot -f");
}
void watchdog_feed_mainloop_50s(void)
{
	watchdog_feed(wdt_id_mainloop);
}

/*------------------------------- 文件结束 -------------------------------*/

