/*
 * iMX utp decode program
 *
 * Copyright 2010-2013 Freescale Semiconductor, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syscall.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <linux/reboot.h>
#include <sys/reboot.h>
#include <sys/sysmacros.h>

/* mxc SoC will enable watchdog at USB recovery mode
 * so, the user must service watchdog
 */
#include <linux/watchdog.h>

#define UTP_TARGET_FILE	"/tmp/file.utp"

#define UTP_FLAG_COMMAND	0x00000001
#define UTP_FLAG_DATA		0x00000002
#define UTP_FLAG_STATUS		0x00000004    //indicate an error happens
#define UTP_FLAG_REPORT_BUSY	0x10000000


#pragma pack(1)

#define PACKAGE "uuc"
#define VERSION "0.4"

char *utp_firmware_version = "2.6.31";
char *utp_sn = "000000000000";
char *utp_chipid = "370000A5";
/* for utp ioctl */
#define UTP_IOCTL_BASE  'U'
#define UTP_GET_CPU_ID  _IOR(UTP_IOCTL_BASE, 0, int)

#define NEED_TO_GET_CHILD_PID 1
/*
 * this structure should be in sync with the same in
 * $KERNEL/drivers/usb/gadget/fsl_updater.c
 */
struct utp_message {
	uint32_t flags;
	size_t 	 size;
	union {
		struct {
			uint64_t payload;
			char command[1];
		};
		struct {
			size_t bufsize;
			uint8_t data[1];
		};
		uint32_t status;
	};
};
#pragma pack()

static int utp_file = -1;

static inline char *utp_answer_type(struct utp_message *u)
{
	if (!u)
		return "UNKNOWN";
	if (u->flags & UTP_FLAG_STATUS)
		return "Non-success";
	if (u->flags & UTP_FLAG_DATA)
		return "Data";
	if (u->flags & UTP_FLAG_REPORT_BUSY)
		return "Busy";
	if (u->flags & UTP_FLAG_COMMAND)
		return "Command ?!";
	return "Success";
}

/*
 * utp_mk_devnode
 *
 * Parse the /sys entry to find major and minor for device
 * If/when found, create device node with type 'type'
 *
 * Example: utp_mk_devnode("block", "sda", "/dev/scsi-disk-0", S_IFBLK)
 */
static int utp_mk_devnode(char *class, char *name, char *node, int type)
{
	char sys[256];
	char devnode[20]; /* major:minor */
	int major, minor;
	int len, f, rc = -EINVAL;

	if (access(node, F_OK) == 0) {
		printf("UTP: file/device node %s already exists\n", node);
		return 0;
	}

	snprintf(sys, sizeof(sys), "/sys/%s/%s/dev", class, name);
	f = open(sys, O_RDONLY);
	if (f >= 0) {
		memset(devnode, 0, sizeof(devnode));
		len = read(f, devnode, sizeof(devnode));
		if (len >= 0) {
			sscanf(devnode, "%d:%d", &major, &minor);
		printf("%s: creating node '%s' with %d+%d\n", __func__, node, major, minor);
			unlink(node);
			rc = mknod(node, type | 0666, makedev(major, minor));
		}
		close(f);
	}
	return rc;
}

/*
 * utp_run
 *
 * Start the subshell and execute the command passed
 */
static int utp_run(char *command, ... )
{
	char cmd[1024];
	va_list vptr;

	va_start(vptr, command);
	vsnprintf(cmd, sizeof(cmd), command, vptr);
	va_end(vptr);

	printf("UTP: executing \"%s\"\n", cmd);
	return system(cmd);
}

/*
 * utp_send_busy
 *
 * report the busy state to the kernel state machine
 */
static void utp_send_busy(int u)
{
	struct utp_message w;

	w.flags = UTP_FLAG_REPORT_BUSY;
	w.size = sizeof(w);
	write(u, &w, w.size);
}

/*
 * utp_partition_mmc
 *
 * chat with fdisk to create bootable partition of type 0x53 and extended one
 */
static int utp_partition_mmc(char *disk)
{
	char fc[50];
	int i;
	char shell_cmd[256];
	int fdisk;
	FILE *fdisk_f;

	sprintf(shell_cmd, "fdisk %s", disk);
	fdisk_f = popen(shell_cmd, "w");
	if (fdisk_f < 0)
		return errno;

	fdisk = fileno(fdisk_f);
	for (i = 4; i >= 1 ; i--) {
		sprintf(fc, "d\n%d\n", i);
		write(fdisk, fc, strlen(fc));
	}

	sprintf(fc, "n\np\n1\n1\n+16M\n");
	write(fdisk, fc, strlen(fc));

	sprintf(fc, "n\np\n2\n\n\n");
	write(fdisk, fc, strlen(fc));

	sprintf(fc, "t\n1\n0x%X\n\n", 0x53);
	write(fdisk, fc, strlen(fc));

	write(fdisk, "w\nq\n", 2);

	pclose(fdisk_f);

	return 0;
}

/*
 * utp_do_selftest
 *
 * perform some diagnostics
 *
 * TBW
 */
static int utp_do_selftest(void)
{
	return 0;
}
/*
 * Put the command which needs to send busy first
 * And the host will send poll for getting its return value
 * later, we call these kinds of commands as Asynchronous Commands.
 */
static int utp_can_busy(char *command)
{
	char *async[] ={
		"$ ", "frf", "pollpipe", NULL,
	};
	char **ptr;

	ptr = async;
	while (*ptr) {
		if (strncmp(command, *ptr, strlen(*ptr)) == 0)
			return 1;
		ptr++;
	}
	return 0;
}
#ifdef NEED_TO_GET_CHILD_PID
/* for pipe */
#define READ 0
#define WRITE 1
static pid_t child_pid = -1;
static int utp_flush(void)
{
	int pstat;
	int ret = 0;
	pid_t pid;
	if (utp_file >= 0) {
		fflush(NULL);
		ret = close(utp_file);
		do{
			pid = waitpid(child_pid, &pstat, 0); /* wait for child finished */
		}while (pid == -1 && errno == EINTR);
		printf("UTP: closing the file\n");
	}
	utp_file = -1;
	return ret;
}
pid_t popen2(const char *command, int *infp, int *outfp)
{
	int p_stdin[2], p_stdout[2];
	pid_t pid;

	if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
		return -1;

	pid = fork();

	if (pid < 0)
		return pid;
	else if (pid == 0){
		close(p_stdin[WRITE]);
		if (infp == NULL)
			close(p_stdin[READ]);
		else
			dup2(p_stdin[READ], READ);
		close(p_stdout[READ]);
		if (outfp == NULL)
			close(p_stdout[WRITE]);
		else
			dup2(p_stdout[WRITE], WRITE);

		execl("/bin/sh", "sh", "-c", command, NULL);
		perror("execl");
		exit(1);
	}

	if (infp == NULL)
		close(p_stdin[WRITE]);
	else
		*infp = p_stdin[WRITE];

	if (outfp == NULL)
		close(p_stdout[READ]);
	else
		*outfp = p_stdout[READ];

	close(p_stdin[READ]);
	close(p_stdout[WRITE]);
	return pid;
}
int utp_pipe(char *command, ... )
{
	int infp;
	char shell_cmd[1024];
	va_list vptr;
	va_start(vptr, command);
	vsnprintf(shell_cmd, sizeof(shell_cmd), command, vptr);
	va_end(vptr);

	child_pid = popen2(shell_cmd, &infp, NULL);
	if (child_pid < 0){
		printf("the fork is failed \n");
		return -1;
	}
	utp_file = infp;
	printf("pid is %d, UTP: executing \"%s\"\n",child_pid, shell_cmd);
	return 0;
}

/*
 * Check the process is dead
 */
#define NAME_MAX 30
int is_child_dead(void)
{
	FILE *fh;
	char path[NAME_MAX + 1];
	sprintf(path, "/proc/%u/status", (unsigned int)child_pid);
	if ((fh = fopen(path, "r"))){
		char buf[1024];
		while (fgets(buf, sizeof(buf) -1, fh)){
			if (!strncmp(buf, "State:", 6))
			{
				char *p = buf + 6;
				while (*p == '\t'){
					p++;
					continue;
				}
				if (*p == 'Z'){
					printf("Process status polling: %s is in zombie.\n",path);
					fclose(fh);
					return 1;
				}
				break;
			}
		}
	}
	else{
		printf("Process polling: can't open %s, maybe the process %u has been killed already\n",path,child_pid);
		return 1;
	}

	fclose(fh);
	return 0;
}

int utp_poll_pipe()
{
	int ret = 0, cnt = 0xFFFF;
	while(ret == 0 && cnt > 0){
		ret = is_child_dead();
		usleep(10000);
		cnt--;
	}

	if(ret == 0)
		return 1;//failure
	else
		return 0;//Success
}


#else
static int utp_flush(void)
{
	int ret;
	if (utp_file_f) {
		printf("UTP: waiting for pipe to close\n");
		ret = pclose(utp_file_f);
	}
	else if (utp_file >= 0) {
		printf("UTP: closing the file\n");
		ret = close(utp_file);
	}
	utp_file_f = NULL;
	utp_file = -1;
	printf("UTP: files were flushed.\n");
	return ret;
}
static int utp_pipe(char *command, ... )
{
	int r;
	char shell_cmd[1024];
	va_list vptr;

	va_start(vptr, command);
	vsnprintf(shell_cmd, sizeof(shell_cmd), command, vptr);
	va_end(vptr);

	utp_file_f = popen(shell_cmd, "w");
	utp_file = fileno(utp_file_f);

	return utp_file_f ? 0 : errno;
}
#endif
/*
 * utp_handle_command
 *
 * handle the command from MSC driver
 * command can be:
 * 	?
 *	!<type>
 *	$ <shell_command>
 *	wfs/wff <X>		write firmware to SD/flash
 *	wrs/wrf <X>		write rootfs to SD/flash
 *	frs/frf <X>		format partition for root on SD/flash
 *	erase <X>		erase partition on flash
 *	read			not implemented yet
 *	write			not implemented yet
 */
static struct utp_message *utp_handle_command(int u, char *cmd, unsigned long long payload)
{
	struct utp_message *w = NULL;
	char devnode[50];	/* enough to fit /dev/mmcblk0p99    */
	char sysnode[50];	/*   -"-    -"-  mmcblk0/mmcblk0p99 */
	uint32_t flags, status;
	char *data = NULL;
	int f;
	size_t size;

	printf("UTP: received command '%s'\n", cmd);

	/* defaults */
	status = 0;
	flags = 0;
	size = 0;

	/* these are asynchronous commands and need to send busy */
	if (utp_can_busy(cmd)){
		utp_send_busy(u);
	}

	if (strcmp(cmd, "?") == 0) {
		/* query */
		flags = UTP_FLAG_DATA;
		data = malloc(256);
		sprintf(data,
			"<DEVICE>\n"
			" <FW>%s</FW>\n"
			" <DCE>%s</DCE>\n"
			" <SN>%s</SN>"
			" <CID>%s</CID>"
			" <VID>%04X</VID>"
			" <PID>%04X</PID>"
			"</DEVICE>\n", utp_firmware_version, VERSION, utp_sn, utp_chipid, 0x66F, 0x37FF);
		size = (strlen(data) + 1 ) * sizeof(data[0]);
	}

	else if (cmd[0] == '!') {
		/* reboot the system, and the ACK has already sent out */
		if (cmd[1] == '3') {
			sync();
			kill(-1, SIGTERM);
			sleep(1);
			kill(-1, SIGKILL);

			reboot(LINUX_REBOOT_CMD_RESTART);
			return NULL;
		}
	}

	else if (strncmp(cmd, "$ ", 2) == 0) {
		status = utp_run(cmd + 2);
		if (status)
			flags = UTP_FLAG_STATUS;
	}
	else if ((strcmp(cmd,"wff") == 0) || (strcmp(cmd, "wfs") == 0)) {
		/* Write firmware - to flash or to SD, no matter */
		utp_file = open(UTP_TARGET_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0666);
	}

	else if (strcmp(cmd, "fff") == 0) {
		/* perform actual flashing of the firmware to the NAND */
		utp_flush();
		if (utp_mk_devnode("class/mtd", "mtd1", "/dev/mtd1", S_IFCHR) >= 0 &&
  		    utp_mk_devnode("class/mtd", "mtd0", "/dev/mtd0", S_IFCHR) >= 0) {
			utp_run("kobs-ng -v -d %s", UTP_TARGET_FILE);
		}
	}
	else if (strcmp(cmd, "ffs") == 0) {
		/* perform actual flashing of the firmware to the SD */
		utp_flush();

		/* partition the card */
		if (!status  && utp_mk_devnode("block", "mmcblk0", "/dev/mmc", S_IFBLK) >= 0) {
			status = utp_partition_mmc("/dev/mmc");
			/* waiting for partition table to settle */
			sleep(5);
		}

		/* write data to the first partition */
		if (!status && utp_mk_devnode("block", "mmcblk0/mmcblk0p1", "/dev/mmc0p1", S_IFBLK) >= 0) {
			utp_run("dd if=/dev/zero of=/dev/mmc0p1 bs=512 count=4");
			utp_run("dd if=%s of=/dev/mmc0p1 ibs=512 seek=4 conv=sync,notrunc", UTP_TARGET_FILE);
		}

		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "mknod", 5) == 0) {
		int devtype = S_IFCHR;
		char *class, *item, *type, *node;

		class = strtok(cmd + 6, " \t,;");
		printf("class = '%s'\n", class);
		item = strtok(NULL, " \t,;");
		printf("item = '%s'\n", item);
		node = strtok(NULL, " \t,;");
		printf("node = %s\n", node);
		type = strtok(NULL, " \t,;");
		printf("type = %s\n", type);
		if (!node && item)
			sprintf(devnode, "/dev/%s", item);
		else
			strcpy(devnode, node);
		if (type && (strcmp(type, "block") == 0 || strcmp(type, "blk") == 0))
			devtype = S_IFBLK;
		printf("UTP: running utp_mk_devnode(%s,%s,%s,0x%x)\n",
				class, item, devnode, devtype);
		status = utp_mk_devnode(class, item, devnode, devtype);
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "wrf", 3) == 0) {
		/* Write rootfs to flash */
		printf("UTP: writing rootfs to flash, mtd #%c, size %lld\n",
				cmd[3], payload);

		/* ensure that device node is here  */
		snprintf(devnode, sizeof(devnode), "/dev/mtd%c", cmd[3]);
		utp_mk_devnode("class/mtd", devnode + 5, devnode, S_IFCHR);

		/* then start ubiformat and redirect its input */
		status = utp_pipe("ubiformat %s -f - -S %lld", devnode, payload);
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "pipe", 4) == 0) {
		status = utp_pipe(cmd + 5);
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "pollpipe", 8) == 0) {
		printf("UTP: poll pipe.\n");
		status = utp_poll_pipe();
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "wrs", 3) == 0) {
		/* Write rootfs to the SD */
		printf("UTP: writing rootfs to SD card, mmc partition #%c, size %lld\n",
				cmd[3], payload);

		/* ensure that device node is here  */
		snprintf(devnode, sizeof(devnode), "/dev/mmcblk0p%c", cmd[3]);
		snprintf(sysnode, sizeof(sysnode), "mmcblk0/mmcblk0p%d", cmd[3]);
		utp_mk_devnode("block", sysnode, devnode, S_IFBLK);

		if (payload % 1024)
			printf("UTP: WARNING! payload %% 1024 != 0, the rest will be skipped");

		status = utp_pipe("dd of=%s bs=1K", devnode);
		if (status)
			flags = UTP_FLAG_STATUS;
	}


	else if (strcmp(cmd, "frf") == 0 || strcmp(cmd, "frs") == 0) {
		/* perform actual flashing of the rootfs to the NAND/SD */
		status = utp_flush();
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "untar.", 6) == 0) {
		status = utp_pipe("tar %cxv -C %s", cmd[6], cmd + 8);
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else if (strncmp(cmd, "read", 4) == 0) {
		f = open(cmd + 5, O_RDONLY);
		if (f < 0) {
			flags = UTP_FLAG_STATUS;
			status = errno;
		} else {
			size = lseek(f, 0, SEEK_END);	/* get the file size */
			lseek(f, 0, SEEK_SET);

			data = malloc(size);
			if (!data) {
				flags = UTP_FLAG_STATUS;
				status = -ENOMEM;
			} else {
				read(f, data, size);
				flags = UTP_FLAG_DATA;
			}
		}
	}

	else if (strcmp(cmd, "send") == 0) {
		utp_file = open(UTP_TARGET_FILE, O_TRUNC | O_CREAT | O_WRONLY, 0666);
	}

	else if (strncmp(cmd, "save", 4) == 0) {
		close(utp_file);
		rename(UTP_TARGET_FILE, cmd + 5);
	}


	else if (strcmp(cmd, "selftest") == 0) {
		status = utp_do_selftest();
		if (status)
			flags = UTP_FLAG_STATUS;
	}

	else {
		printf("UTP: Unknown command received, ignored\n");
		flags = UTP_FLAG_STATUS;
		status = -EINVAL;
	}

	w = malloc(size + sizeof(*w));
	if (!w) {
		printf("UTP: Could not allocate %zu+%zu bytes!\n", size, sizeof(*w));
		return NULL;
	}

	memset(w, 0, sizeof(*w) + size);
	w->flags = flags;
	w->size = size + sizeof(*w);
	if (flags & UTP_FLAG_DATA) {
		w->bufsize = size;
		memcpy(w->data, data, size);
	}
	if (flags & UTP_FLAG_STATUS)
		w->status = status;
	if (data)
		free(data);
	return w;
}

void feed_watchdog(void *arg)
{
	int res;
	int *fd = arg;
	while(1) {
		res = ioctl(*fd, WDIOC_KEEPALIVE);
		if (res)
			printf("ioctl WDIOC_KEEPALIVE error L%d, %s\n", __LINE__, strerror(errno));
		printf("%s\n", __func__);
		sleep(60);
	}
}

int main(int argc, char **argv)
{
	int u = -1, wdt_fd = -1, r, need_watchdog = 0;
	int watchdog_timeout = 127;  /* sec */
	int cpu_id = 50;
	struct utp_message *uc, *answer;
	pthread_t a_thread;
	char * utp_devnode="/dev/utp";
	if (argc > 1)
		utp_devnode = argv[1];

	printf("%s %s [built %s %s]\n", PACKAGE, VERSION, __DATE__, __TIME__);
	/* set stdout unbuffered, what is the usage??? */
//	setvbuf(stdout, NULL, _IONBF, 0);
	uc = malloc(sizeof(*uc) + 0x10000);

	mkdir("/tmp", 0777);

	setenv("FILE", UTP_TARGET_FILE, !0);

	printf("UTP: Waiting for %s to appear\n", utp_devnode);

	while (utp_mk_devnode("class/misc", "utp", utp_devnode, S_IFCHR) < 0) {
		putchar('.');
		sleep(1);
	}
	u = open(utp_devnode, O_RDWR);
	r = ioctl(u, UTP_GET_CPU_ID, &cpu_id);
	if (r)
		printf("cpu id get error:L%d, %s\n", __LINE__, strerror(errno));
	else{
		switch (cpu_id) {
			case 23:
			case 25:
			case 28:
			case 50:
				need_watchdog = 0;
				break;
			case 35:
			case 51:
			case 53:
				need_watchdog = 1;
				break;
			default:
				need_watchdog = 0;
		}
		printf("cpu_id is %d\n", cpu_id);
		if (need_watchdog){
			if (utp_mk_devnode("class/misc", "watchdog", "/dev/watchdog", S_IFCHR)){
				printf("The watchdog is not configured, needed by mx35/mx51/mx53 \n");
				printf("%d, %s\n", __LINE__, strerror(errno));
			} else{
				wdt_fd = open("/dev/watchdog", O_RDWR);
				/* set the MAX timeout */
				r = ioctl(wdt_fd, WDIOC_SETTIMEOUT, &watchdog_timeout);
				if (r)
					printf("%d, %s\n", __LINE__, strerror(errno));
				r = pthread_create(&a_thread, NULL, (void *)feed_watchdog, (void *)(&wdt_fd));
				if (r != 0) {
					perror("Thread creation failed");
					exit(EXIT_FAILURE);
				}
			}
		}
	}

	for(;;) {
		r = read(u, uc, sizeof(*uc) + 0x10000);
		if (uc->flags & UTP_FLAG_COMMAND) {
			answer = utp_handle_command(u, uc->command, uc->payload);
			if (answer) {
				printf("UTP: sending %s to kernel for command %s.\n", utp_answer_type(answer), uc->command);
				write(u, answer, answer->size);
				free(answer);
			}
		}else if (uc->flags & UTP_FLAG_DATA) {
			write(utp_file, uc->data, uc->bufsize);
		}else {
			printf("UTP: Unknown flag %x\n", uc->flags);
		}
	}

	/* should never be here */
	return 0;
}

