/* Copyright 2011, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* To compile the libdaqctl testbench sample code, use the appropriate
 * cross compiler and run the command:
 *
 *   gcc -Wall -O -mcpu=arm9 -DTESTBENCH -o libxuartctl2-tb libxuartctl2.c
 *
 * On uclibc based initrd's, the following additional gcc options are
 * necessary: -Wl,--rpath,/slib -Wl,-dynamic-linker,/slib/ld-uClibc.so.0
 */

#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sched.h>
#include <getopt.h>
#include <termios.h>
#include <ctype.h>
#include <limits.h>

#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__                                                              \
    ({ long int __result;                                                     \
       do __result = (long int) (expression);                                 \
       while (__result == -1L && errno == EINTR);                             \
       __result; }))
#endif

static unsigned char *xuart_sk_mode = NULL;
static unsigned short xtbl_8n2[256];
static unsigned short xtbl_8e2[256];
static unsigned short xtbl_8o2[256];
static unsigned short xtbl_8e1[256];
static unsigned short xtbl_8o1[256];
static unsigned short xtbl_7n2[256];
static unsigned short xtbl_7e2[256];
static unsigned short xtbl_7o2[256];
static unsigned short xtbl_7e1[256];
static unsigned short xtbl_7o1[256];
static struct {
	char *mode;
	unsigned short *xtbl;
	char nbits;
	short rmsk;
} xuart_modes[] = { 
	{"8n1", NULL, 8, 0xff},
	{"raw", NULL, 8, 0xff},
	{"8n2", xtbl_8n2, 9, 0xff},
	{"dmx", xtbl_8n2, 9, 0xff},
	{"8e1", xtbl_8e1, 9, 0xff},
	{"8o1", xtbl_8o1, 9, 0xff},
	{"8e2", xtbl_8e2, 10, 0xff},
	{"8o2", xtbl_8o2, 10, 0xff},
	{"7n1", NULL, 7, 0x7f},
	{"7n2", xtbl_7n2, 8, 0x7f},
	{"7e1", xtbl_7e1, 8, 0x7f},
	{"7o1", xtbl_7o1, 8, 0x7f},
	{"7e2", xtbl_7e2, 9, 0x7f},
	{"7o2", xtbl_7o2, 9, 0x7f},
	{"9n1", NULL, 9, 0x1ff},
	{NULL, NULL, 0, 0},
};


static void fill_xtbl(void) {
	int i;
	static int filled = 0;
	if (filled) return;
	filled = 1;

	for (i = 0; i < 256; i++) {
		int j = i;
		int p = 0;
		while (j) {
			if (j & 0x1) p++;
			j >>= 1;
		}
		
		xtbl_7n2[i] = 0x80 | i;
		xtbl_8n2[i] = 0x100 | i;
		if (p & 0x1) {
			xtbl_8e1[i] = 0x100 | i; 
			xtbl_8o1[i] = i;
			xtbl_8e2[i] = 0x300 | i;
			xtbl_8o2[i] = 0x200 | i;
		} else {
			xtbl_8o1[i] = 0x100 | i; 
			xtbl_8e1[i] = i;
			xtbl_8o2[i] = 0x300 | i;
			xtbl_8e2[i] = 0x200 | i;
		}

		if (i >= 128) {
			xtbl_7o1[i] = xtbl_7o1[i-128];
			xtbl_7e1[i] = xtbl_7e1[i-128];
			xtbl_7o2[i] = xtbl_7o2[i-128];
			xtbl_7e2[i] = xtbl_7e2[i-128];
		} else if (p & 0x1) {
			xtbl_7e1[i] = 0x80 | i;
			xtbl_7o1[i] = i & 0x7f;
			xtbl_7o2[i] = 0x100 | (i & 0x7f);
			xtbl_7e2[i] = 0x180 | i;
		} else {
			xtbl_7o1[i] = 0x80 | i;
			xtbl_7e1[i] = i & 0x7f;
			xtbl_7e2[i] = 0x100 | (i & 0x7f);
			xtbl_7o2[i] = 0x180 | i;
		}
	}

}


/* This function is used to connect to a socket and get a socket fd in 
 * return.  It takes two arguments, hostspec and args.  The hostspec must be
 * in the form of hostname:port, if it is NULL it will default to
 * localhost:7350 (the first XUART port).  The args argument is any additional
 * arguments.  In the case of this library, it is required to use at least
 * --mode=raw since the read/write functions provided by this library utilize
 * the raw mode of the XUARTs.  All other options will default to the xuartctl
 * server defaults.
 *
 * This function will return a socket file descriptor that can be used as-is
 * or with other xuart_*() functions.
 */
int xuart_connect(char *hostspec, char *args) {
	struct sockaddr_in sa;
	struct hostent *he;
	int sk, r, x, tos = IPTOS_LOWDELAY;
	char *p;
	char buf[1024];
	char cmd[1024];
	struct linger lx;

	/* hostspec must be in form hostname:port */
	if (hostspec) strncpy(buf, hostspec, sizeof(buf));
	else strcpy(buf, "127.0.0.1");
	buf[sizeof(buf) - 1] = 0;

	if (args) {
		snprintf(cmd, sizeof(cmd),
		  "xuartctl --server --port=%s %s >/dev/null 2>&1", buf, args);
		r = system(cmd);
		assert (r == 0);
	}
	p = buf;
	while (*p != ':' && *p != 0) p++;
	if (*p == 0) p = "7350";
	else {
		*p = 0;
		p++;
	}

	sa.sin_family = AF_INET;
	he = gethostbyname(buf);
	assert(he != NULL);
	memcpy(&sa.sin_addr, he->h_addr_list[0], he->h_length);
	sa.sin_port = htons(strtoul(p, NULL, 0));
	assert(sa.sin_port != 0);
	sk = socket(PF_INET, SOCK_STREAM, 0);
	assert(sk != -1);
	lx.l_onoff = 1;
	lx.l_linger = 15;
	setsockopt(sk, SOL_SOCKET, SO_LINGER, &lx, sizeof(lx));
	r = connect(sk, (struct sockaddr *)&sa, sizeof(sa));
	setsockopt(sk, IPPROTO_IP, IP_TOS, &tos, 4);
	x = 1;
	setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &x, 4);
	fcntl(sk, F_SETFL, O_NONBLOCK);

	if (r) {
		close(sk);
		return -1;
	} 
	fill_xtbl();
	xuart_sk_mode = realloc(xuart_sk_mode, getdtablesize());
	xuart_sk_mode[sk] = 0;
	return sk;
}


unsigned char xuart_vmin = 1, xuart_vtime; /* Works just like termios VMIN/VTIME */
/*
 * VMIN = 0 and VTIME = 0:
 * This is a completely non-blocking read - the call is satisfied
 * immediately directly from the driver's input queue. If data are
 * available, it's transferred to the caller's buffer up to nbytes and
 * returned. Otherwise zero is immediately returned to indicate "no
 * data". We'll note that this is "polling" of the serial port, and
 * it's almost always a bad idea. If done repeatedly, it can consume
 * enormous amounts of processor time and is highly inefficient. Don't
 * use this mode unless you really, really know what you're doing.
 *
 * VMIN = 0 and VTIME > 0:
 * This is a pure timed read. If data are available in the input queue,
 * it's transferred to the caller's buffer up to a maximum of nbytes,
 * and returned immediately to the caller. Otherwise the driver blocks
 * until data arrives, or when VTIME tenths expire from the start of
 * the call. If the timer expires without data, zero is returned. A
 * single byte is sufficient to satisfy this read call, but if more
 * is available in the input queue, it's returned to the caller. Note
 * that this is an overall timer, not an intercharacter one.
 *
 * VMIN > 0 and VTIME > 0:
 * A read() is satisfied when either VMIN characters have been transferred
 * to the caller's buffer, or when VTIME tenths expire between characters.
 * Since this timer is not started until the first character arrives,
 * this call can block indefinitely if the serial line is idle. This
 * is the most common mode of operation, and we consider VTIME to be
 * an intercharacter timeout, not an overall one. This call should
 * never return zero bytes read.
 *
 * VMIN > 0 and VTIME = 0:
 * This is a counted read that is satisfied only when at least VMIN
 * characters have been transferred to the caller's buffer - there is
 * no timing component involved. This read can be satisfied from the
 * driver's input queue (where the call could return immediately), or
 * by waiting for new data to arrive: in this respect the call could
 * block indefinitely. We believe that it's undefined behavior if
 * nbytes is less then VMIN.
 */
/* xuart_read16() returns 16-bit RX chars for use in modes where there
 * is >8 data bits.  It drops idle/break codes, obeys xuart_vmin/vtime globals,
 * and right shifts the RX opcode byte for proper alignment in the word. 
 * bufsz is in units of bytes.
 *
 * Returns number of bytes placed in buf.
 */
ssize_t xuart_read16(int sk, void *buf, size_t bufsz) {
	ssize_t nread;
	unsigned short x;
	unsigned char *put = buf, *get, *cbuf = buf;
	fd_set fds;
	struct timeval tv;
	unsigned char vmin = xuart_vmin, vtime = xuart_vtime;
	int expired, nbits = xuart_modes[xuart_sk_mode[sk]].nbits;
	int shift, rmsk;

	shift = 2 + (9 - nbits);
	rmsk = xuart_modes[xuart_sk_mode[sk]].rmsk;

	assert(bufsz > 0);
	assert((bufsz & 0x1) == 0);
	if (vmin > bufsz) vmin = bufsz;
	FD_ZERO(&fds);

again:
	expired = 0;
	FD_SET(sk, &fds);
	if (vtime != 0 && ((put - cbuf) || vmin == 0)) {
		tv.tv_sec = vtime / 10;
		tv.tv_usec = (vtime % 10) * 100000;
		if (select(sk + 1, &fds, NULL, NULL, &tv) == 0)
		  expired = 1;
	} else if (vmin != 0) select(sk + 1, &fds, NULL, NULL, NULL);

	nread = TEMP_FAILURE_RETRY(read(sk, put, bufsz));

	if (nread == -1 && errno == EAGAIN) nread = 0;

	if (nread < 0) {
		if (put - cbuf) return put - cbuf;
		else return nread;
	}

	assert((nread & 0x1) == 0); /* Verify we get a even number of bytes */

	for (get = put; nread > 0; nread -= 2) {
		x = *get++;
		x |= *get++ << 8;
		if (x & 0x1800) continue; /* ignore idle or break */
		x = (x >> shift) & rmsk;
		*put++ = x;
		*put++ = x >> 8;
		bufsz -= 2;
	}

	if (vmin && ((put - cbuf) < vmin) && !(vtime && expired)) 
	  goto again;
		 
	return (put - cbuf);
}


/* This function is for writing in modes >8 data bits.  bufsz is in bytes.
 * Returns number of bytes written or -1 on error.
 */
ssize_t xuart_write16(int sk, void *buf, size_t bufsz) {
	unsigned short a, tempbuf[bufsz/2];
	unsigned char *p = buf;
	ssize_t written;
	int i, nbits = xuart_modes[xuart_sk_mode[sk]].nbits;

	assert((bufsz & 0x1) == 0);

	for (i = 0; i < bufsz; i += 2) {
		a = *p++;
		a |= *p++ << 8;
		a &= ~(0xffff << nbits);
		tempbuf[i/2] = a;
	}
	
tryagain:
	written = TEMP_FAILURE_RETRY(write(sk, tempbuf, bufsz));

	if (written == -1 && (errno == EAGAIN)) {
		fd_set fds;
		FD_ZERO(&fds);
		FD_SET(sk, &fds);
		select(sk + 1, NULL, &fds, NULL, NULL);
		goto tryagain;
	}
	return written;
}

/* This function will issue a break for bit_times number of bit times on a 
 * previously opened XUART socket.  
 *
 * XXX: It is recommended to have an idlewrite following a breakwrite
 */
int xuart_breakwrite(int sk, unsigned int bit_times) {
	unsigned short buf = 0;	
	int r, shift_by_6 = 0, rem = 0;

	while (bit_times > 0x3ff<<6) {
		xuart_breakwrite(sk, 0x3ff<<6);
		bit_times -= 0x3ff<<6;
	}

	if (bit_times > 0x3ff) {
		shift_by_6 = 1;
		rem = bit_times & 0x3f;
		bit_times >>= 6;
	}
	
	buf |= bit_times;
	buf |= shift_by_6 << 10;
	buf |= 0x1 << 11;

	r = TEMP_FAILURE_RETRY(write(sk, &buf, 2));
	assert (r == 2);

	if (rem) xuart_breakwrite(sk, rem);

	return 0;
}


/* This function will issue an idle for bit_times number of bit times on a 
 * previously opened XUART socket.  Returns 0 for success. 
 */
int xuart_idlewrite(int sk, unsigned int bit_times) {
	unsigned short buf = 0;	
	int r, shift_by_6 = 0, rem = 0;

	while (bit_times > 0x3ff<<6) {
		xuart_idlewrite(sk, 0x3ff<<6);
		bit_times -= 0x3ff<<6;
	}

	if (bit_times > 0x3ff) {
		shift_by_6 = 1;
		rem = bit_times & 0x3f;
		bit_times >>= 6;
	}
	
	buf |= bit_times;
	buf |= shift_by_6 << 10;
	buf |= 0x2 << 11;

	r = TEMP_FAILURE_RETRY(write(sk, &buf, 2));
	assert (r == 2);

	if (rem) xuart_idlewrite(sk, rem);

	return 0;
}


/* If mode == NULL, previous mode is retained.  If baudrate == 0, previous
 * baudrate is retained.  Returns 0 for success, -1 for invalid mode.
 */
int xuart_config(int sk, char *mode, int baudrate) {
	int baud;
	int i, match;
	unsigned short buf[3], *b = buf;

	if (mode == NULL) goto skip_mode;
	for (i = 0, match = -1; NULL != xuart_modes[i].mode; i++) {
		if (strcasecmp(mode, xuart_modes[i].mode) == 0) {
			match = i;
			break;
		}
	}
	if (match == -1) return -1;

	if (strcasecmp(mode, "dmx") == 0) baudrate = 250000;
	*b++ = 0x990c | (xuart_modes[match].nbits - 7);
	xuart_sk_mode[sk] = match;

skip_mode:
	if (baudrate != 0) {
		baud = 100000000 / (baudrate * 8) - 1;
		*b++ = 0x1f00 | (baud >> 8);
		*b++ = 0x1800 | (baud & 0xff);
	}

	if (b == buf) return 0;

	i = TEMP_FAILURE_RETRY(write(sk, buf, (b - buf) * 2));
	assert (i == (b - buf) * 2);
	return 0;
}


ssize_t xuart_read(int sk, unsigned char *buf, int n) {
	ssize_t r, i;

	r = xuart_read16(sk, buf, n);
	for (i = 0; i < (r/2); i++) buf[i] = buf[i*2];
	return r/2;	
}


/* This function does the right thing with parity modes via the
 * xtbl set up in xuart_config().
 */
ssize_t xuart_write(int sk, unsigned char *buf, int n) {
	int i, r;
	unsigned short sbuf[n];
	unsigned short *xtbl = xuart_modes[xuart_sk_mode[sk]].xtbl;
	
	if (xtbl) for (i = 0; i < n; i++) sbuf[i] = xtbl[buf[i]]; 
	else for (i = 0; i < n; i++) sbuf[i] = buf[i]; 

	r = xuart_write16(sk, sbuf, n*2);
	if (r > 0) r = r / 2;
	return r;
}


#ifdef TESTBENCH
/* Sample client app */
int main(void) {
	int sk;
	unsigned short recvbuf_16bit[4];
	unsigned char recvbuf[4];
	unsigned char txbuf[4] = {0x55,0xaa,0x55,0xaa};
	unsigned short txbuf_16bit[4] = {0x55, 0xaa, 0x155, 0x1aa};

	/* Second argument is for setting defaults, must use raw sockets */
	sk = xuart_connect("127.0.0.1:7350", "--mode=raw");
	assert(sk != -1);

	xuart_config(sk, "8n1", 9600); /* Initialize settings */
	xuart_breakwrite(sk, 11); /* Break for 11 bit times */
	xuart_idlewrite(sk, 11); /* Idle for 11 bit times */
	xuart_write(sk, txbuf, 4); /* Write 4 bytes, just like the write(2) syscall */
	xuart_read(sk, recvbuf, 4); /* Read 4 bytes, just like read(2). */

	xuart_config(sk, "7e1", 115200); /* Change settings of XUART anytime */
	xuart_write(sk, txbuf, 4);
	xuart_read(sk, recvbuf, 4);

	xuart_config(sk, "8n1", 9000); /* Change settings again, any integer baud works */
	xuart_write(sk, txbuf, 4);
	xuart_vmin = 4; xuart_vtime = 0; /* Termios style VMIN/VTIME processing... */
	xuart_read(sk, recvbuf, 4);

	xuart_config(sk, "9n1", 250000); /* Enable 9-bit mode */
	xuart_write16(sk, txbuf_16bit, 8);
	xuart_read16(sk, recvbuf_16bit, 8); 
	return 0;
}
#endif
