/*
 *****************************************************************
 *                                                               *
 *    Copyright (c) Essential Communications Corp, 1997          *
 *                                                               *
 *   All Rights Reserved.  Unpublished rights  reserved  under   *
 *   the copyright laws of the United States.                    *
 *                                                               *
 *   RESTRICTED RIGHTS LEGEND   Use, duplication, or disclosure  *
 *   by the U.S. Government is subject to restrictions  as  set  *
 *   forth in Subparagraph (c)(1)(ii)  of  DFARS  252.227-7013,  *
 *   or  in  FAR 52.227-19, as applicable.                       *
 *                                                               *
 *****************************************************************
 *
 * 	sink.c
 *
 * $Source: /usr/users/releng/repos/src/utils/sink.c,v $
 * $Revision: 1.28 $ $Locker:  $
 * $Date: 1999/01/05 23:25:35 $ $Author: droux $ $State: Exp $
 */

#ifndef __lint
static char rcsid[] = "$Header: /usr/users/releng/repos/src/utils/sink.c,v 1.28 1999/01/05 23:25:35 droux Exp $";
#endif /* __lint */

/*
 * sink 
 *
 * Test HIPPI interface input performance.
 *
 */
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#if defined(__sgi) 
#include <getopt.h>
#include <sys/mman.h>
#endif /* defined(__sgi) */
#ifdef sun
#include <sys/param.h>
#endif /* sun */

#include "essioctl.h"

char *dev[] = {NIC0, NIC1, NIC2, NIC3, NIC4, NIC5, NIC6, NIC7, NIC8,
		NIC9, NIC10, NIC11, NIC12, NIC13, NIC14, NIC15};
int devno, nics;

#if __osf__ || __hpux || __NetBSD__ || _AIX || defined(sun)
#define DEV_NAME "hip"
#endif
#if __sgi
#define DEV_NAME "ess"
#endif


#if defined(__hpux) || defined(__osf__) || defined(__NetBSD__) || _AIX
static void *memalign(int, int);
#endif /* defined(__hpux) || defined(__osf__) || defined(__NetBSD__) || _AIX */
#if defined(__sgi)
#if defined(ESS_IRIX64)
#define MEM_ALIGN 16384
#else
#define MEM_ALIGN 4096
#endif /* defined(ESS_IRIX64) */
#endif /* defined(__sgi) */
#if defined(__hpux) || _AIX
#define MEM_ALIGN 4096
#endif /* defined(__hpux) */
#if defined(__osf__) || defined(__NetBSD__)
#if defined(__alpha)
#define MEM_ALIGN 8192
#else /* defined(__alpha) */
#define MEM_ALIGN 4096
#endif /* defined(__alpha) */
#endif /* defined(__osf__) || defined(__NetBSD__) */
#ifdef sun
#define MEM_ALIGN PAGESIZE
#endif /* sun */


#if defined(__sgi)
static char *usage = "%s [-2ks] [-D <unit>] [-l <size>] \n"
		     "[-n <packets>] [-u <ulp>] [-c <check_level>]\n";
#else /* defined(__sgi) */
static char *usage = "%s [-2s] [-D <unit>] [-l <size>] \n"
		     "[-n <packets>] [-u <ulp>] [-c <check_level>]\n";
#endif /* defined(__sgi) */

extern int errno;

/* Buffer allocation length (except mapkern) */
int aloc_len = 2 * 1024 * 1024;

int
main(int argc, char *argv[])
{
    int	c, n, fd, i, j, verbose = 0;
    int	len=4096+8, ulen = 0, pkt_start = 1;
    int doublethread = 0, retv, check = 0, check_err = 0;
    int ulp = 0x82, errs = 0;
    int is_ph;
#if defined(__sgi)
    int mapkern = 0;
    int spfd;
#endif /* defined(__sgi) */
    int shared = 0;
    int	child;
    u_int utmp;
#if 0
    u_int d2len;
#endif
    u_int	*buf, *buf1,*buf2, *bufc;
    u_int	fp1, fp2, pkt_offset;
    u_long	pkt_len, pkt_last = 0, tmp_len;
    u_int	fp_len = 0, fp_last = 0, data_len, packets = 0;
    u_long	data_off;
    u_char      *cbuf, curChar;
    char	*device_name = DEV_NAME"0";
    unsigned long count = 0xffffffff; /*  more or less forever */
    
    while ((c = getopt(argc, argv, "v2ksD:l:u:n:c:")) != EOF)
	switch (c) {
	  case 'v': verbose++;					break;
	  case 'D': device_name = optarg;			break;
	  case '2': doublethread++;				break;
	  case 'l': ulen = (int)strtoul(optarg, (char **)0, 0);	break;
	  case 'n': count = atoi(optarg);			break;
	  case 'c': check = (int)strtol(optarg, (char **)0, 0);	break;
	  case 'u': ulp = (int)strtoul(optarg, (char **) 0, 0);	break;
	  case 's': shared++;					break;
#if defined(__sgi)
	  case 'k': mapkern++;                                  break;
#endif /* defind(__sgi) */
	    
	  default:
	    errs++;
	}

    /* When ulen is not specified, sink will adjust read to packet
     * length (assuming a stream of constant size packets) */
    if (ulen)
	len = ulen;
    /* For now, buf must be long enough to hold the 8 byte FP header */
    if (len < 8/* FP */) {
	(void) fprintf(stderr,"Bad length argument: %d\n", len);
	errs++;
    }

    if (errs) {
	(void) fprintf(stderr, usage, argv[0]);
	exit(1);
    }

    if (doublethread && check) {
	(void) fprintf(stderr, 
		       "Can't specify both -2 and -c at the same time\n");
	exit(1);
    }
  
    /* Convert the unit to the devno */
    if (strncmp(device_name, DEV_NAME, 3) == 0 &&
	device_name[3] >= '0' && device_name[3] <= '9') {
	    devno = atoi(&device_name[3]);
    } else {
	fprintf(stderr, "%s is invalid\n", device_name);
	exit(1);
    }
  
    /* find out how many cards are in the system */
    if ((fd = open(dev[0], O_RDWR)) < 0) {
	perror("open failed");
	exit(1);
    }
    if ((ioctl(fd, HIPIOC_GET_NICS, &nics)) < 0) {
	perror(" HIPIOC_GET_NICS ioctl failed");
	exit(1);
    }
    if ((close(fd)) < 0) {
	perror("close failed");
	exit(1);
    }
    /* Make sure we are in range */
    if (devno >= nics) {
	fprintf(stderr, "%s is invalid, there are %d NICs\n", 
		device_name, nics);
	exit(1);
    }

#if defined(__sgi)
    if (mapkern) {
	spfd = open(device_name, O_RDWR);
	if (spfd < 0) {
	    perror("couldn't open special hippi device");
	    exit(1);
	}
	buf1 = mmap((void *)0, len, PROT_READ | PROT_WRITE,
		    MAP_SHARED, spfd, getpagesize());
	if ((void *)buf1 == (void *)-1L) {
	    perror("couldn't map first buffer");
	    exit(1);
	} 
	if (doublethread) {
	    buf2 = mmap((void *)0, len, PROT_READ | PROT_WRITE,
			MAP_SHARED, spfd, 2*getpagesize());
	    if ((void *)buf2 == (void *)-1L) {
		perror("couldn't map second buffer");
		exit(1);
	    }
	}
    } else {
#endif /* defined(__sgi) */

	/* Bufs aligned on a page boundary */
	buf1 = memalign(MEM_ALIGN, aloc_len);
	buf2 = memalign(MEM_ALIGN, aloc_len);

#if defined(__sgi)
	if (mpin(buf1, len) < 0)
	    (void) printf("couldn't pin buf1\n");
	if (mpin(buf2, len) < 0)
	    (void) printf("couldn't pin buf2\n");
    }
#endif /* defined(__sgi) */

    (void) printf("%s : %s receiving into %d byte buffers\n",
		  doublethread ? "two processes":"one process",
		  device_name, len);

    /*
     * For double thread execution fork() before opening the HIPPI
     * device.
     */
    if (doublethread)
	child = (fork() == 0);
    else
	child = 0;


    /*
     * Open a suitable HIPPI device. Since this is for read only use
     * O_RDONLY
     */
    HIP_APP_OPEN(dev[devno], O_RDONLY, fd);
    if (fd < 0) {
	fprintf(stderr, "%s ", (child)?"Child":"Parent");
	perror("couldn't open hippi device");
	exit(1);
    }

    /* Get the operating mode so that we can do the proper processing */
    if (ioctl(fd, HIPIOC_GET_DEVICE_STATE, &is_ph) < 0) {
	perror("couldn't get device state");
	exit(1);
    }
    is_ph &= HIP_PH;

    /* There is no FP header for ph mode so turn off pkt_start */
    if (is_ph)
	fp_len = 0xffffffff;

    /* Bind the ULP. The device must be bound to a ULP before packets
     * can be received. A minus value means shared bind. */
    if (is_ph)
	ulp = HIPPI_ULP_PH;
    if (shared || doublethread)
	ulp = 0 - ulp;
    if (ioctl(fd, HIPIOC_BIND_ULP, ulp) < 0) {
	perror("couldn't bind to ULP");
	exit(1);
    }

    buf =  child ? buf2 : buf1;
    bufc = child ? buf2 : buf1;
#if defined(__sgi)
    /* hack in the buffer "number" for kernel mapped buffers */
    if (mapkern)
	buf = child ? (u_int *)2L : (u_int *)1L;
#endif /* defined(__sgi) */
    
    n=0;
    for (;;) {
	/*
	 * Read packet data. We adjust the read len to try to match read
	 * length to the packet length.
	 */
	retv = (int)read(fd, buf, (size_t)len);
	if (retv < 0) {
	    (void) printf("(%s) : read return error - pkt %d\n",
			  child ? "Child" : "Parent", n);
	    perror("trouble reading");
	    if (errno == EIO) {
		errno = 0;
		ioctl(fd, HIPIOCR_ERRS, &retv);
		(void) printf("HIPPI error no 0x%x (errno %d)", retv, errno);
		if (retv & HIP_DSTERR_PARITY)
		    (void) printf(" parity");
		if (retv & HIP_DSTERR_LLRC)
		    (void) printf(" LLRC");
		if (retv & HIP_DSTERR_FRAME)
		    (void) printf(" frame");
		if (retv & HIP_DSTERR_SYNC)
		    (void) printf(" sync");
		if (retv & HIP_DSTERR_ILBURST)
		    (void) printf(" illegal burst");
		if (retv & HIP_DSTERR_SDIC)
		    (void) printf(" SDIC lost");
		if (retv & HIP_DSTERR_SHBST)
		    (void) printf(" Short burst");
		if (retv & HIP_DSTERR_RDY)
		    (void) printf(" Lost link ready");
		if (retv & HIP_DSTERR_RXTO)
		    (void) printf(" Rx Idle");
		if (retv & HIP_DSTERR_PKTLEN)
		    (void) printf(" packet length error");
		(void) printf("\n");
		fflush(stdout);
	    }
	    close(fd);
	    exit(1);
	}

	if (pkt_start) {
	    pkt_len = 0L;
	    check_err = 0;
	    if (is_ph == 0) {
#if defined(__alpha) || defined(__i386)
		fp1 = htonl(buf[0]);
		fp2 = htonl(buf[1]);
#else	/* defined(__alpha) || defined(__i386) */
		fp1 = buf[0];
		fp2 = buf[1];
#endif	/* defined(__alpha) || defined(__i386) */
	        fp_len = 8 + (fp1 & 0x000007f8) /*D1*/ + fp2 /*D2*/;
		/* Unspecified packet length */
		if (fp2 == 0xffffffff)
		    fp_len = 0xffffffff;
	    }
	}

	/* Accumulate packet length */
	pkt_len += retv;

	/* Perform data verification */
	if (check) {
	    if (pkt_start) {
		data_off = 0L;
		if (is_ph) {
		    cbuf = (u_char *)(&bufc[0]);
		    data_len = retv;
		} else {
		    /* packet hader (4) + d1 area */
		    cbuf = (u_char *)(&bufc[4 + ((fp1 & 0x7f8)>>2)]);
		    data_len = retv - 16 - (fp1 & 0x7f8);
		}
		curChar = *cbuf;
	    } else {
		cbuf = (u_char *)(&bufc[0]);
		data_len = retv;
	    }
	    /* The send buffer from blast is loaded with
	     * characters in increasing order. We take the 
	     * first character in the packet as the root. The 
	     * test is done on a character stream to avoid
	     * big endian and little endian problems that can 
	     * occur in multiple byte objects. */
	    for (i = 0; i < data_len && !check_err; i++) {
		if (curChar++ != *cbuf++) {
		    if (check == 1)
		        check_err = 1;
		    (void) printf(
		   "packet %d, char number %ld was %x expected %x\n",
				  packets, data_off, *(cbuf-1), curChar-1);
		    (void) printf("10 chars before and after are  0x");
		    for (j=-11; j<-1; j++)
			(void) printf("%2.2x", *(cbuf+j));
		    (void) printf(" %2.2x ", *(cbuf-1));
		    for (j=0; j<10; j++)
			(void) printf("%2.2x", *(cbuf+j));
		    (void) printf("\n");
		}
		data_off++;
	    }
	}

	/* When reading a long packet after a short packet, the len will
	 * be at the short size. Let len grow over successive reads */
	if (len < aloc_len && pkt_len > len)
	    len = pkt_len;
	    if (len > aloc_len)
		len = aloc_len;

	/* Is the packet complete */
	pkt_start = 0;
	if (fp_len == 0xffffffff) {
	    /* We are in an unspecified length  packet so see if we are 
	     * at the end */
	    if (ioctl(fd, HIPIOCR_PKT_OFFSET, &pkt_offset) < 0) {
		perror("couldn't get packet offset");
		exit(1);
	    }
	    if (pkt_offset == 0) {
		pkt_start = 1;
		count--;
		packets++;
	    }
	} else {
	    if (check) {
		/* Packet length must equal fp length */
	        if (ioctl(fd, HIPIOCR_PKT_OFFSET, &pkt_offset) < 0) {
		    perror("couldn't get packet offset");
		    exit(1);
	        }
	        if (pkt_offset == 0 && pkt_len != (u_long)fp_len) {
		    (void) printf(
		    "packet %d FP size was %u, didn't match received size %u\n",
				packets, fp_len, pkt_len);
		}
		if (pkt_offset == 0)
		    pkt_len = fp_len;
	    }
	    /* Packet length was specified */
	    if (pkt_len == fp_len) {
		pkt_start = 1;	/* We have read the whole packet */
		count--;
		packets++;
	    }
	}

	/* Adjust read length (len) to read a whole packet (where possible) */
	if (ulen == 0
#if __sgi
			&& mapkern == 0
#endif	/* __sgi */
			&& pkt_start) {
	    /* This is a different size packet */
	    if (pkt_last != pkt_len) {
		if (verbose)
		    printf(" New packet length %lu, old packet length %lu\n", 
			    pkt_len, pkt_last);
	        /* Current packet smaller than previous packet */
		tmp_len = pkt_len;
		if (tmp_len < len) {
		    len = tmp_len;
		}
		/* Current packet larger than previous packet. In this
		 * case we have part of the packet so reset pkt_start */
		if (tmp_len > aloc_len)
		    tmp_len = aloc_len;
		if (tmp_len > len) {
		    len = tmp_len;
		}
		if (len < 2048)
		    len = 2048;
	    }
	    pkt_last = pkt_len;
	}

	if (pkt_len > len)
	    len = pkt_len;
	    if (len > aloc_len)
		len = aloc_len;

	n++;
#if PRINT
	if (!child) {
	    if ((n&8191) == 0)
	        (void) printf("%d packets received by parent\n", n);
	    else if ((n&1023) == 0)
	        (void) printf("%d packets received by parent\n", n);
	} else if ((n&1023) == 64)
	    (void) printf("%d packets received by child\n", n);
#endif
	if (!count)
	    break;
    }

    /* Let the child finish */
    if (doublethread && !child)
	sleep(1);

#if defined(__sgi)
    if (mapkern) {
	if (munmap(buf1, len) < 0) {
	    perror("couldn't unmap first buffer");
	    exit(2);
	}
	if (doublethread) 
	    if (munmap(buf2, len) < 0) {
		perror("couldn't unmap second buffer");
		exit(2);
	    }
    }
#endif /* defined(__sgi) */

    exit(check_err);
    return(0);
}


#if defined(__hpux) || defined(__osf__) || (__NetBSD__) || _AIX
/* memalign function goes to no trouble to save a pointer to the start
 * of the malloc(ed) area so doing a free following a memalign could 
 * likely cause an error */

static void *
memalign(int align, int size)
{
    char *tmp_addr;
    int i;

    tmp_addr = malloc((unsigned)(size + align));
    i = align - (long)tmp_addr & (align-1);
    return((void *)&tmp_addr[i]);
}
#endif /* __hpux || __osf__ || __NetBSD__ || _AIX */
