/* A library of routines that will talk to a design on the TM.
 * The TM design must be wrapped with a communications layer like the
 * one that portmux_gen creates.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>

static char Rcsid[] = "$Header: /jayar/i/tm4/tmu/src/tmports/RCS/tmports.c,v 1.14 2013/04/25 19:03:59 drg Exp drg $";

#include "tmports.h"

#define NUM_TM_CHIPS	1

struct tm_packet	packet;
int tm_sock;
static void trychip();

struct portinfo {
	char *name;
	char iox;
	int nbits;
	int needs_ack;
	int chip;
	int port;
	} portinfo[TM_MAX_PORTS];

int nports;

/* This should come from getservbyname, but it is too hard to make that work
 * from a unprivileged program. -- drg
 */
#define TM_PORT_HOUSEKEEPING	3462
#define TM_PORT_DATA		9100


static int l_socket_read(int fd, char *buf, int nbytes);
static int l_socket_write(int fd, char *buf, int nbytes);


int tm_init(char *tmhost) {

	/* Contact the tmumon process, and get the list of ports */

	int sock, i, nchips;
	struct sockaddr_in serveraddr;
	struct hostent *hp, *gethostbyname();
	FILE *server;
	char buf[1024], dir[1024];
	char *cp;
	struct stat statbuf;

	if(tm_sock > 0) {
		close(tm_sock);
		for(i = 0; i<nports; i++) {
			free(portinfo[nports].name);
			}
		nports = 0;
		}

	tm_sock = socket(AF_INET, SOCK_STREAM, 0);
	if(tm_sock < 0) {
		perror("tm_init: opening stream socket");
		return(-1);
		}

	if((tmhost == (char *) NULL) || (tmhost[0] == '\0')) {
		if((tmhost = getenv("TM_SERVER")) == (char *) NULL)
			tmhost = DEFAULT_SERVER;
		}

	serveraddr.sin_family = AF_INET;
	hp = gethostbyname(tmhost);
	if(hp == (struct hostent *) NULL) {
		fprintf(stderr, "tm_init: can't find address for '%s'\n",
				tmhost);
		return(-1);
		}
	bcopy(hp->h_addr, &serveraddr.sin_addr.s_addr,
			sizeof(serveraddr.sin_addr.s_addr));
	serveraddr.sin_port = htons(TM_PORT_DATA);

	if(connect(tm_sock, (struct sockaddr *) &serveraddr,
				sizeof(serveraddr))) {
		perror("tm_init: can't connect to tmumon");
		return(-1);
		}

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0) {
		perror("tm_init: opening stream socket 2");
		return(-1);
		}
	serveraddr.sin_port = htons(TM_PORT_HOUSEKEEPING);

	if(connect(sock, (struct sockaddr *) &serveraddr,
				sizeof(serveraddr))) {
		perror("tm_init: can't connect to tmumon 2");
		return(-1);
		}

	server = fdopen(sock, "r+");
	setbuf(server, NULL);

	nchips = NUM_TM_CHIPS;

	fprintf(server, "getportlistdir\n");
	fflush(server);
	cp = fgets(dir, sizeof(dir), server);

	if(!cp || feof(server) || ferror(server)) {
		fclose(server);
		close(tm_sock);
		tm_sock = 0;
		return(-1);
		}
	fclose(server);

	dir[strlen(dir) - 1] = '\0';
	if(stat(dir, &statbuf) == -1) {
		/* The directory doesn't exist, which probably means we're
		 * trying to run this on a non-EECG machine.  Fall back to
		 * searching the current directory for the ports files.
		 */
		strcpy(dir, ".");
		}

	for(i=0; i<nchips; i++) {
		sprintf(buf, "%s/%s%d.ports", dir, "fpga", i);
		trychip(buf, i);
		}
	return(0);
	}



static void trychip(char *filename, int chipno) {

	/* Attempt to open the given file and read information about the
	 * ports on the FPGA design.
	 */

	FILE *portfile;
	char buf[1024], name[1024], mode[1024], handshake_from_circuit[1024], handshake_from_sun[1024]; 
	char *cp;
	int nbits, nfields, needs_ack;
	int portno;

	if((portfile = fopen(filename, "r")) == (FILE *) NULL)
		return;

	portno = 0;
	while(1) {
		cp = fgets(buf, sizeof(buf), portfile);
		if(!cp || feof(portfile) || ferror(portfile))
			break;
		if((buf[0] == '#') || (buf[0] == '\n'))
			continue;
		nfields = sscanf(buf, "%s %s %d %s %s", name, mode, &nbits,
                                      handshake_from_circuit, handshake_from_sun);
                if(nfields == 3) {
			needs_ack = 0;
			}
		else if(nfields == 5) {
			needs_ack = 1;
			}
		else {
			fprintf(stderr, "tm_init: file %s: don't understand line '%s'", filename, buf);
			continue;
			}

		portinfo[nports].name = strdup(name);
		if(isupper(mode[0]))
			mode[0] = tolower(mode[0]);
		portinfo[nports].iox = mode[0];
		portinfo[nports].nbits = nbits;
		portinfo[nports].needs_ack = needs_ack;
		portinfo[nports].chip = chipno;
		portinfo[nports].port = portno++;
		nports++;
		}
	fclose(portfile);
	}



int tm_open(char *portname, char *mode) {

	/* Open the given port for reading or writing */

	int portno;

	if(tm_sock <= 0)
		return(-1);
	
	for(portno = 0; portno < nports; portno++) {
		if(!strcmp(portname, portinfo[portno].name)) {
			switch(portinfo[portno].iox) {

			case 'i':
				if(mode[0] != 'w')
					return(-1);
				break;

			case 'o':
				if(mode[0] != 'r')
					return(-1);
				break;
				}
			return(portno);
			}
		}

	return(-1);
	}



int tm_get_port_width(char *portname) {

	/* How many bits wide is the given port ? */

	int portno;

	if(tm_sock <= 0)
		return(-1);
	
	for(portno = 0; portno < nports; portno++) {
		if(!strcmp(portname, portinfo[portno].name)) {
			return(portinfo[portno].nbits);
			}
		}

	return(-1);
	}



int tm_write(int port, char *buf, int nbytes) {

	/* Write nbytes of data from buf into the given port */

	int n, retval;
	int portbytes, i;
	char *packet_data_ptr, *itemptr;
	int save_nbytes = nbytes;
	int result_code;

	if(tm_sock <= 0)
		return(-1);
	if((port < 0) || (port >= nports))
		return(-1);

	if(portinfo[port].iox != 'i')
		return(-1);

	/* For widths <= 16, round the width to the next highest byte.
	 * Above that, round the width to the next highest 32 bit word.
	 */

	if(portinfo[port].nbits > 16)
		portbytes = ((portinfo[port].nbits + 31) / 32) * 4;
	else
		portbytes = (portinfo[port].nbits + 7) / 8;

	if((nbytes % portbytes) != 0)
		return(-1);
	
	while(nbytes > 0) {

		if(nbytes > TM_MAX_PACKET_DATA)
			n = (TM_MAX_PACKET_DATA / portbytes) * portbytes;
		else
			n = nbytes;

		packet.h = 0;
		TM_SET_PACKET_WRITE(packet, 1);
		TM_SET_PACKET_NEEDS_ACK(packet, portinfo[port].needs_ack);
		TM_SET_PACKET_CHIP(packet, portinfo[port].chip);
		TM_SET_PACKET_PORT(packet, portinfo[port].port);
		TM_SET_PACKET_PORT_BYTES(packet, (portinfo[port].nbits + 7) / 8);
		TM_SET_PACKET_LENGTH(packet, n);
#ifdef notdef
printf("write nbytes %d n %d h %X\n", nbytes, n, packet.h);
#endif

		/* Convert the data to least significant byte first.  */

		if(ntohl(1) == 1) {
			packet_data_ptr = packet.data;
			for(itemptr = buf; itemptr < &buf[n];
						itemptr += portbytes) {
				for(i=0; i<portbytes; i++) {
					packet_data_ptr[i] = itemptr[portbytes - 1 - i];
					}
				packet_data_ptr += portbytes;
				}
			}
		else {
			bcopy(buf, packet.data, n);
			}

		packet.h = htonl(packet.h);
		retval = l_socket_write(tm_sock, (char *) &packet, n + TM_PACKET_HLEN);
		if(retval != (n + TM_PACKET_HLEN))
			return(-1);
		retval = l_socket_read(tm_sock, (char *) &result_code, sizeof(result_code));
		if(retval != sizeof(result_code))
			return(-1);
		result_code = ntohl(result_code);
		if(result_code == -1)
			return(-1);
		nbytes -= result_code;
		buf += result_code;
		}

	return(save_nbytes - nbytes);
	}



int tm_read(int port, char *buf, int nbytes) {

	/* Read nbytes of data from the given port */

	int n, retval;
	int portbytes, i;
	char *itemptr;
	int save_nbytes = nbytes;

	if(tm_sock <= 0)
		return(-1);
	if((port < 0) || (port >= nports))
		return(-1);

	if(portinfo[port].iox != 'o')
		return(-1);

	/* For widths <= 16, round the width to the next highest byte.
	 * Above that, round the width to the next highest 32 bit word.
	 */

	if(portinfo[port].nbits > 16)
		portbytes = ((portinfo[port].nbits + 31) / 32) * 4;
	else
		portbytes = (portinfo[port].nbits + 7) / 8;

	if((nbytes % portbytes) != 0)
		return(-1);
	
	while(nbytes > 0) {
		if(nbytes > TM_MAX_PACKET_DATA)
			n = (TM_MAX_PACKET_DATA / portbytes) * portbytes;
		else
			n = nbytes;
		packet.h = 0;
		TM_SET_PACKET_WRITE(packet, 0);
		TM_SET_PACKET_NEEDS_ACK(packet, portinfo[port].needs_ack);
		TM_SET_PACKET_CHIP(packet, portinfo[port].chip);
		TM_SET_PACKET_PORT(packet, portinfo[port].port);
		TM_SET_PACKET_PORT_BYTES(packet, (portinfo[port].nbits + 7) / 8);
		TM_SET_PACKET_LENGTH(packet, n);
#ifdef notdef
printf("read nbytes %d n %d h %X\n", nbytes, n, packet.h);
#endif
		packet.h = htonl(packet.h);
		retval = l_socket_write(tm_sock, (char *) &packet, TM_PACKET_HLEN);
		if(retval != TM_PACKET_HLEN)
			return(-1);

		retval = l_socket_read(tm_sock, (char *) &packet, TM_PACKET_HLEN);
		if(retval != TM_PACKET_HLEN)
			return(-1);

		packet.h = ntohl(packet.h);
		if(packet.h == -1)
			return(-1);

		retval = l_socket_read(tm_sock, (char *) packet.data, n);
		if(retval != n)
			return(-1);

		/* Convert the data we just read to host byte order.  */

		if(ntohl(1) == 1) {
			for(itemptr = buf; itemptr < &buf[n];
						itemptr += portbytes) {
				for(i=0; i<portbytes; i++)
					itemptr[i] = packet.data[
						(itemptr - buf) +
						portbytes - 1 - i];
				}
			}
		else
			memcpy(buf, packet.data, n);

		nbytes -= n;
		buf += n;
		}
	return(save_nbytes - nbytes);
	}


int tm_close(int port) {

	/* Close this port */

	if(tm_sock <= 0)
		return(-1);
	if((port < 0) || (port >= nports))
		return(-1);
	return(0);
	}



static int l_socket_read(int fd, char *buf, int nbytes) {

   /* Read nbytes from a socket.  Handle partial reads. */

   int n, retval;
   char *cp;

   cp = buf;
   n = nbytes;

   while(n > 0) {
      retval = read(fd, cp, n);

      if(retval <= 0) {
         return(-1);
      }

      n -= retval;
      cp += retval;
   }

   return(nbytes);
}



static int l_socket_write(int fd, char *buf, int nbytes) {

   /* Write nbytes to a socket.  Handle partial writes. */

   int n, retval;
   char *cp;

   cp = buf;
   n = nbytes;

   while(n > 0) {
      retval = write(fd, cp, n);

      if(retval <= 0) {
         return(-1);
      }

      n -= retval;
      cp += retval;
   }

   return(nbytes);
}
