
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <termios.h>
#include <string.h>

#define EMULATE_PC

int verbose = 0;

typedef struct {
	char *name;
	int cmd_from_pc_size;
	unsigned char *cmd_from_pc;
	int answer_from_evb_size;
	unsigned char *answer_from_evb;
} cmd_t;

cmd_t cmds[] = {
	{ "hello0",
	  1, (unsigned char []){ 0x09 },
	  1, (unsigned char []){ 0x31 }, },
	{ "reset",
	  4, (unsigned char []){ 0xaa, 0x02, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe2, 0x00, 0x00 }, },
	{ "version_info0", 
	  6, (unsigned char []){ 0xaa, 0x0a, 0x02, 0x00, 0xfe, 0x55 },
	  4, (unsigned char []){ 0xea, 0x01, 0x03, 0x00 }, },
	{ "version_info1",
	  6, (unsigned char []){ 0xaa, 0x0a, 0x02, 0x00, 0xff, 0x55 },
	  4, (unsigned char []){ 0xea, 0x01, 0x00, 0x00 }, },
	{ "set_bps 115200",
	  9, (unsigned char []){ 0xaa, 0x09, 0x05, 0x00, 0x07, 0x01, 
				 0x00, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe9, 0x02, 0x55 }, },
	{ "hello1",
	  1, (unsigned char []){ 0x57 },
	  2, (unsigned char []){ 0x59, 0x00 }, },
	{ "set_bps_9600",
	  9, (unsigned char []){ 0xaa, 0x09, 0x05, 0x00, 0x03, 0x01, 
				 0x00, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe9, 0x02, 0x55 }, },
	{ "reset_low",
	  4, (unsigned char []){ 0xaa, 0x0e, 0x00, 0x55 },
	  3, (unsigned char []){ 0xee, 0x00, 0x00 }, },
	{ "foobar2",
	  4, (unsigned char []){ 0xaa, 0x00, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe0, 0x00, 0x00, }, },
	{ "enable_12v",
	  5, (unsigned char []){ 0xaa, 0x03, 0x01, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe3, 0x00, 0x00, }, },
	{ "enable_vpp",
	  5, (unsigned char []){ 0xaa, 0x04, 0x01, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe4, 0x00, 0x00, }, },
	{ "enable_mdx",
	  5, (unsigned char []){ 0xaa, 0x05, 0x01, 0x00, 0x55 },
	  3, (unsigned char []){ 0xe5, 0x00, 0x00, }, },
	{ "enable_mdy",
	  8, (unsigned char []){ 0xaa, 0x0b, 0x04, 0xff, 0xb3, 0xdf, 
				 0x01, 0x55 },
	  4, (unsigned char []){ 0xeb, 0x01, 0x00, 0x00, }, },
	{ "reset_high",
	  4, (unsigned char []){ 0xaa, 0x0f, 0x00, 0x55 },
	  3, (unsigned char []){ 0xef, 0x00, 0x00 }, },
	{ "boot_fztat",
	  4, (unsigned char []){ 0xaa, 0x06, 0x00, 0x55 },
	  4, (unsigned char []){ 0xe6, 0x01, 0xb3, 0x00 }, },
	{ "negotiate_inferface",
	  8, (unsigned char []){ 0xaa, 0x0b, 0x04, 0xff, 0x7e, 0x00, 
				 0x00, 0x55 },
	  4, (unsigned char []){ 0xeb, 0x01, 0x00, 0x00, }, },
	{ "download_ukernel",
	  8, (unsigned char []){ 0xaa, 0x07, 0x04, 0x00, 0x00, 0x01, 
				 0x72, 0x55 },
	  3, (unsigned char []){ 0xe7, 0x02, 0x44 }, },
	{ "download_ukernel_ack",
	  0, (unsigned char []){  },
	  2, (unsigned char []){ 0x4b, 0x00 }, },
	{ "download_ukernel_recv",
	  5, (unsigned char []){ 0xaa, 0x07, 0x04, 0x00, 0x00, }, 
	  0, (unsigned char []){  }, },
	{ "pmode",
	  6, (unsigned char []){ 0xaa, 0x0a, 0x02, 0xff, 0xe4, 0x55 },
	  4, (unsigned char []){ 0xea, 0x01, 0x84, 0x00 }, },
	{ "pmode2",
	  8, (unsigned char []){ 0xaa, 0x0b, 0x04, 0xff, 0xe3, 0x55, 
				 0x00, 0x55 },
	  4, (unsigned char []){ 0xeb, 0x01, 0x55, 0x00 }, },
	{ "pmode3",
	  8, (unsigned char []){ 0xaa, 0x0b, 0x04, 0xff, 0xe4, 0x04, 
				 0x00, 0x55 },
	  4, (unsigned char []){ 0xeb, 0x01, 0x80, 0x00 }, },
	{ "pmode4",
	  4, (unsigned char []){ 0xaa, 0x08, 0x00, 0x55 }, 
	  3, (unsigned char []){ 0xe8, 0x00, 0x00 }, },
	{ "get_brr",
	  2, (unsigned char []){ 0x55, 0x04 }, 
	  1, (unsigned char []){ 0x3b }, },
	{ "transp_mode_exit",
	  1, (unsigned char []){ 0x3b }, 
	  1, (unsigned char []){ 0x66 }, },
	{ "set_bps_9600_x",
	  9, (unsigned char []){ 0xaa, 0x09, 0x05, 0x01, 0x03, 0x01, 
				 0x00, 0x00, 0x55 },
	  4, (unsigned char []){ 0xe9, 0x01, 0x56, 0x00 }, },
	{ "download_kernel",
	  2, (unsigned char []){ 0x03, 0xc8 },
	  0, (unsigned char []){ }, },
	{ "get_device_id0",
	  1, (unsigned char []){ 0x09 },
	  7, (unsigned char []){ 0x01, 0x00, 0x80, 0x00, 0x00, 0x02, 0x0a }, },
	{ "get_device_id1",
	  2, (unsigned char []){ 0x0d, 0x0a },
	  0, (unsigned char []){ 0x65, 0x0d }, },
};

#define CMD_HELLO0 0
#define CMD_RESET 1
#define CMD_VERSIONINFO0 2
#define CMD_VERSIONINFO1 3
#define CMD_SETBPS115200 4
#define CMD_HELLO1 5
#define CMD_SETBPS9600 6
#define CMD_RESETLOW 7
#define CMD_FOOBAR2 8
#define CMD_ENABLE12V 9
#define CMD_ENABLEVPP 10
#define CMD_ENABLEMDX 11
#define CMD_ENABLEMDY 12
#define CMD_RESETHIGH 13
#define CMD_BOOTFZTAT 14
#define CMD_NEGIF     15
#define CMD_DLUKERNEL    16
#define CMD_DLUKERNELACK 17
#define CMD_DLUKERNELR   18
#define CMD_PMODE       19
#define CMD_PMODE2      20
#define CMD_PMODE3      21
#define CMD_PMODE4      22
#define CMD_GETBRR      23
#define CMD_TPMODEEXIT  24
#define CMD_SETBPS9600x 25
#define CMD_DLKERNEL    26
#define CMD_GETDEVID0   27
#define CMD_GETDEVID1   28


#define CMDS_NO (sizeof(cmds) / sizeof(cmd_t))
#define CMD_LEN_MAX 0x400

unsigned char pc_output[] = {
	CMD_HELLO0, CMD_VERSIONINFO0, CMD_VERSIONINFO1, 
	CMD_SETBPS115200, CMD_HELLO1, CMD_RESET,
	CMD_RESETLOW, CMD_ENABLE12V,  CMD_ENABLEVPP, 
	CMD_ENABLEMDX, CMD_ENABLEMDY, CMD_RESETHIGH,
	CMD_BOOTFZTAT, CMD_NEGIF, CMD_DLUKERNEL, CMD_DLUKERNELACK,
	CMD_VERSIONINFO0, CMD_VERSIONINFO1,
	CMD_SETBPS9600, CMD_HELLO1, 
#if 1
	CMD_PMODE4, // transparent mode on
#endif
#if 0
	CMD_PMODE, CMD_PMODE2, CMD_PMODE, CMD_PMODE3, 
	CMD_PMODE4, // led goes on here
	CMD_GETBRR, CMD_TPMODEEXIT, CMD_SETBPS9600, CMD_HELLO1, 
	CMD_SETBPS9600x, CMD_PMODE4, CMD_DLKERNEL,
	CMD_GETDEVID0, CMD_GETDEVID1
#endif
};

#define PC_OUTPUT_NO (sizeof(pc_output) / sizeof(unsigned char))

int wrcnt = 0;
int rdcnt = 0;

void verb(unsigned char data, char *mode)
{
	if (verbose > 0) {
		printf("%s:", mode);
	}
	if (verbose > 1) {
		printf("(%d,%d)=", rdcnt, wrcnt);
	}
	if (verbose > 0) {
		printf("0x%02x\n", data);
	}
}

void evb_write(int fd, unsigned char data) 
{
	write(fd, &data, 1);
	verb(data, "write");
	wrcnt++;
}

void evb_writen(int fd, unsigned char *buf, int nobytes) 
{
	int k;

	for (k = 0; k < nobytes; k++) {
		evb_write(fd, buf[k]);
	}
}

void evb_read(int fd, unsigned char *data) 
{
	read(fd, data, 1);
	verb(*data, "read");
	rdcnt++;
}

int cmd_interpret(unsigned char *buf, int nobytes, int fd) 
{
	int k;
	cmd_t *cp;

	for (k = 0; k < CMDS_NO; k++) {
		cp = &cmds[k];

		if (cp->cmd_from_pc_size == nobytes) {
			if (memcmp(cp->cmd_from_pc, buf, nobytes) == 0) {
				printf("answering to \"%s\"\n", cp->name);
				evb_writen(fd, cp->answer_from_evb,
					   cp->answer_from_evb_size);
				return k;
			}
		}
	}

	return -1;
}

int tty_setup(int fd, int speed)
{
    struct termios ios;

    if (!isatty(fd)) {
	    fprintf(stderr, "not a tty\n");
	    return -1;
    }

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

    if (tcgetattr(fd, &ios) != 0) {
	perror("tcgetattr");
	return -1;
    }

//    ios.c_cflag = CREAD | CLOCAL | CS8;

    printf("setting bps to %u\n", speed);

    switch(speed) {
    case 9600: speed = B9600; break;
    case 19200: speed = B19200; break;
    case 38400: speed = B38400; break;
    case 57600: speed = B57600; break;
    case 115200: speed = B115200; break;
    default:
	    fprintf(stderr, "unsupported bps speed\n");
	    return -1;
    }


    if (cfsetospeed(&ios, speed) != 0) {
	    perror("cfsetospeed");
	    return -1;
    }

    if (cfsetispeed(&ios, speed) != 0) {
	    perror("cfsetispeed");
	    return -1;
    }

    if (tcsetattr(fd, TCSADRAIN, &ios) != 0) {
	perror("tcsetattr");
	return -1;
    }

    return 0;
}

char *ukernelfile = "target/boot_from_ufpb.bin"; //"uGen2623.cde";
char *kernelfile = "Genm2623.cde";

void cmd_send(int fd) 
{
	int k, m, n, n2, p, fd2;
	cmd_t *cp;
	unsigned char uc, uc2, space[CMD_LEN_MAX];
	unsigned char *buf;

	for (k = 0; k < PC_OUTPUT_NO; k++) {

		p = pc_output[k];

		cp = &cmds[p];

		printf("sending \"%s\"\n", cp->name);
		buf = cp->cmd_from_pc;
		n = cp->cmd_from_pc_size;

		switch(p) {
		case CMD_DLUKERNEL:
			memcpy(space, buf, n);
			buf = space;

			fd2 = open(ukernelfile, O_RDONLY);
			
			if (fd2 < 0) {
				fprintf(stderr, "unable to open file \"%s\"",
					ukernelfile);
				buf = NULL;
				break;
			}

			n2 = lseek(fd2, 0, SEEK_END);

			if (n2 < 0) {
				fprintf(stderr, "Bad file size\n");
				close(fd2);
				buf = NULL;
				break;
			}
                
			lseek(fd2, 0, SEEK_SET);
   
			space[5] = (n2 >> 8) & 0xff;
			space[6] = n2 & 0xff;
			break;

		case CMD_DLKERNEL:
			memcpy(space, buf, n);
			buf = space;

			fd2 = open(kernelfile, O_RDONLY);
			
			if (fd2 < 0) {
				fprintf(stderr, "unable to open file \"%s\"",
					kernelfile);
				buf = NULL;
				break;
			}

			n2 = lseek(fd2, 0, SEEK_END);

			if (n2 < 0) {
				fprintf(stderr, "Bad file size\n");
				close(fd2);
				buf = NULL;
				break;
			}
                
			lseek(fd2, 0, SEEK_SET);
   
			space[0] = (n2 >> 8) & 0xff;
			space[1] = n2 & 0xff;
			break;
		}

		if (!buf) {
			return;
		}

		evb_writen(fd, buf, n);

		printf("waiting for %u bytes\n", cp->answer_from_evb_size);

		for (m = 0; m < cp->answer_from_evb_size; m++){
			evb_read(fd, &uc);
			if (uc != cp->answer_from_evb[m]) {
				printf("expected 0x%02x, got 0x%02x\n",
				       cp->answer_from_evb[m], uc);
			}
		}

		switch(p) {
		case CMD_SETBPS115200:
			tty_setup(fd, 115200);
			break;

		case CMD_SETBPS9600:
			tty_setup(fd, 9600);
			break;

		case CMD_DLUKERNEL:
			printf("downloading ukernel data:\n");

			for (m = 0; m < n2; m++) {
				if (read(fd2, &uc, 1) != 1) {
					fprintf(stderr, "unable to read.\n");
					close(fd2);
					return;
				}
				evb_write(fd, uc);
				evb_read(fd, &uc2);
				if (uc2 != uc) {
					fprintf(stderr, 
						"bad reply 0x%02x (0x%02x).\n",
						uc2, uc);
					close(fd2);
					return;
				}
			}

			close(fd2);
			printf("downloading ukernel data - done.\n");
			break;

		case CMD_DLKERNEL:
			printf("downloading kernel data:\n");

			for (m = 0; m < n2; m++) {
				if (read(fd2, &uc, 1) != 1) {
					fprintf(stderr, "unable to read.\n");
					close(fd2);
					return;
				}
				evb_write(fd, uc);
			}

			close(fd2);
			printf("downloading kernel data - done.\n");
			break;
		}
	}

	printf("no more commands to send!\n");

	while (1) {
		evb_read(fd, &uc);
	}
}

int main(void)
{
	int fd, k, cnt = 0;
	unsigned char buf[CMD_LEN_MAX];

	fd = open("/dev/ttyS0", O_RDWR);

	if (fd < 0) {
		perror("open");
		return 1;
	}

	if (tty_setup(fd, 9600) != 0) {
		close(fd);
		return 1;
	}

#ifdef EMULATE_PC
	cmd_send(fd);
	return 1;
#endif

	while (read(fd, &buf[cnt], 1) > 0) {

		verb(buf[cnt], "read");

		cnt++;

		k = cmd_interpret(buf, cnt, fd);

		if (k >= 0) {
			cnt = 0;
			switch (k) {

			case CMD_SETBPS115200:
				tty_setup(fd, 115200);
				break;

			case CMD_SETBPS9600: {
				unsigned char uc;
				tty_setup(fd, 9600);
#if 0
				while(1) {
					evb_read(fd, &uc);
					evb_write(fd, 0xef);
					evb_write(fd, 0xcf);
				}
#endif
				break;
			}

			case CMD_DLUKERNELR: {
				int m;
				unsigned char uc, uc2, uc3;
				evb_read(fd, &uc);
				evb_read(fd, &uc2);
				evb_read(fd, &uc3);

				if (uc3 != 0x55) {
					printf("unknown command\n");
					break;
				}

				m = uc2 + (uc << 8);

				evb_write(fd, 0xe7);
				evb_write(fd, 0x02);
				evb_write(fd, 0x44);

				while(m--) {
					evb_read(fd, &uc);
					evb_write(fd, uc);
				}

				evb_write(fd, 0x4b);
				evb_write(fd, 0x00);
				break;
			}

			case CMD_DLKERNEL: {
				int m;
				unsigned char uc, uc2;

				m = 0x3c8; // size of kernel fixme;

				while(m--) {
					evb_read(fd, &uc);
				}
				printf("kernel download complete\n");

				/* wait for a 0x09 command (not hello) */

				printf("waiting for get device id\n");

				evb_read(fd, &uc);

				/* answer */

				evb_write(fd, 0x01);
				evb_write(fd, 0x00);
				evb_write(fd, 0x80);
				evb_write(fd, 0x00);
				evb_write(fd, 0x00);
				evb_write(fd, 0x02);
				evb_write(fd, 0x0a);

				/* get wierd message */

				evb_read(fd, &uc);
				evb_read(fd, &uc2);

				evb_write(fd, 0x65);
				evb_write(fd, uc);
				break;
			}

			}
		}

		if (cnt == CMD_LEN_MAX) {
			printf("unsupported cmd: ");
			for (k = 0; k < CMD_LEN_MAX; k++) {
				printf("0x%02x ", buf[k]);
			}
			printf("\n");
			break;
		}
	}

	close(fd);

	return 0;
}
