/*--------------
 * ppp-meter.c -- Linux PPP meter v .69lite-beer 
 * Usage: hack and slash.  It's configured to use the second parallel port,
 * at 0x278, and run in random mode.  You can change the stuff at compile
 * time, but I just wanted to get it to work.  Primary sources were the 
 * pppstats program, and the Linux CPU meter.  I just hacked them together,
 * and it seems to work. I hope to have v.69dark-beer out shortly.  I'd like
 * to get this to use the Scroll lock light on the keyboard, like the ethernet
 * heartbeats on some transceivers.  It shouldn't be that hard, but I'm not 
 * quite up to it yet.  :)  My apologies to the original authors.
 * Sean Berry, spberry@iastate.edu: (515) 233 5328
 */

/*-----------------------------------------------------------------------------
 * meter.c -- Linux CPU meter v0.2.  Uses circuit described in led-stat.txt
 * Usage: meter [-m 0-4] [-p io_addr] [-t usecs] [-l max_load] [-n leds] [-h]
 *
 * AUTHORS:
 *  Brian D. Chase (chasebd@nextwork.rose-hulman.edu) -- 23 Jan 1995,
 *  based on original version by Joseph W. Vigneau (joev@wpi.edu) (c)1994.
 * REVISIONS:
 * v0.1  23 Jan 1995 
 *  Removed gross spawning of ps to gather CPU load info. 
 *  Added command line options for lots of things.
 *  Added led_mask table for speed.
 *  Tried to optimize main polling loop reasonably well.
 *  Pretty much a rewrite :-)
 * v0.2  29 Jan 1995
 *  Added new modes: hacked traditional, cylon, inverted cylon, pulse,
 *     inverted pulse, random.
 *  Lost a little efficiency but gained a lot of functionality.
 *  Changed -u option to -t option.
 * NOTES:
 *  This program is covered by GNU's GPL.
 *---------------------------------------------------------------------------*/

#include <ctype.h>
#include <errno.h>
#include <nlist.h>
#include <stdio.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <net/if.h>

#define	VJC	1

#include <sys/types.h>
#include <sys/fcntl.h>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <linux/ppp.h>
#include <netinet/ip.h>

#undef	LITTLE_ENDIAN

#include <netinet/tcp.h>
#include <slhc.h> /* linux base directory passed on -I parameter */

#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include "port.h"

#define STATS		stats
#define COMP		slcomp
#define RCOMP			V(STATS.rcomp)
#define RUNCOMP			V(STATS.runcomp)
#define ROTHERS			V(STATS.rothers)
#define RPACKETS		(RCOMP+RUNCOMP+ROTHERS)
#define RERRORS			V(STATS.rerrors)
#define ROVERRUN		V(STATS.roverrun)
#define RTOSSED			V(STATS.tossed)
#define RERROR			(RERRORS+ROVERRUN+RTOSSED)
#define SCOMP			V(STATS.scomp)
#define SUNCOMP			V(STATS.suncomp)
#define SOTHERS			V(STATS.sothers)
#define SPACKETS		(SCOMP+SUNCOMP+SOTHERS)
#define SERROR			V(STATS.serrors)
#define stats_ibytes		V(STATS.rbytes)
#define stats_ipackets		RPACKETS
#define stats_compressedin	RCOMP
#define stats_uncompressedin	RUNCOMP
#define stats_errorin		RERROR
#define stats_tossed		RTOSSED
#define stats_ip		(RPACKETS-RERROR-ROTHERS)
#define stats_obytes		V(STATS.sbytes)
#define stats_opackets		SPACKETS
#define stats_compressedout	SCOMP
#define stats_uncompressedout	SUNCOMP
#define stats_errorout		SERROR
#define stats_searches		V(COMP.sls_o_searches)
#define stats_misses		V(COMP.sls_o_misses)

#define	MAX_BYTES		2500
#define SPEED_UP		1
#define SLOW_DOWN		0
#define MAX_LEDS		8
#define LPT1_PORT 		0x378
#define LPT2_PORT		0x278
#define LPT_PORT		LPT2_PORT
#define square(A)		((A)*(A))
#define SLEEPS_FOR		1000000

int run_traditional(float ld_avg, float ld_max, int leds_num, 
		    unsigned int port, long usecs);
int run_cylon(float ld_avg, float ld_max, int leds_num, unsigned int port,
	      long usecs, int mode_flag);
int run_pulse(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag);
int run_random(float ld_avg, float ld_max, int leds_num, unsigned int port,
	       long usecs);
int run_random2(float ld_avg, float ld_max, int leds_num, unsigned int port,
		long usecs);


FILE *fp;                         /* file ptr to load average info */
int c;                            /* temp int for character storage */
int run_mode = 5;                 /* meter mode to use */
int leds = 8;              	/* num of LEDs to use */
long us_count = 1000000;           /* num of usecs between pollings */
unsigned int lp_port = 0x278; 	/* parallel port I/O address */
float load_avg = 0.9;             /* load average value */

struct ifreq	ifreq;
int		fd;


char	*kmemf  = "/dev/kmem";
char	*system_loc = "/vmlinuz";

int	kflag;
int	vflag;
unsigned interval = 5;
int	unit;

u_char	signalled;			/* set if alarm goes off "early" */

#define V(offset) ((line % 20)? sc->offset - osc->offset : sc->offset)

typedef struct __struct

{
  struct ppp_stats  stats;
  struct slcompress slcomp;
} PPP_STRUCT;

main(argc, argv)
	int argc;
	char *argv[];
	
{

/* set access permissions on the port */

if ((ioperm(lp_port, 1, 1)) == -1) {

fprintf(stderr, "%s: port 0x%x invalid.\n", argv[0], lp_port);

perror(argv[0]);

exit(1);

}
		--argc; 
		++argv;
		interval = 1;		/* measured in seconds */
		
		system_loc = *argv;
		++argv, --argc;

	

	memset (&ifreq, '\0', sizeof (ifreq));
	sprintf (ifreq.ifr_ifrn.ifrn_name, "ppp%d", unit);

	/* Get an internet socket for doing socket ioctls. */

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket(AF_INET,SOCK_DGRAM,0)");
		exit(1);
	}
	
	intpr();		/*** MAIN OUTPUT LOOP ***/

	exit(0);		/*** DONE **************/

}


/*** END MAIN PPPSTATS LOOP ***/

/*
 * Print a running summary of interface statistics.
 * Repeat display every interval seconds, showing statistics
 * collected over that interval.  Assumes that interval is non-zero.
 * First line printed at top of screen is always cumulative.
 */

intpr()

{

	register int line = 0;
	int oldmask;

#ifdef __STDC__

	void catchalarm(int);

#else

	void catchalarm();

#endif

	PPP_STRUCT *sc, *osc;

	sc  = (PPP_STRUCT *) calloc (1, sizeof(PPP_STRUCT));
	osc = (PPP_STRUCT *) calloc (1, sizeof(PPP_STRUCT));

	ifreq.ifr_ifru.ifru_data = (caddr_t) sc;

	while (1) 
	
	{
	
	    if (ioctl (fd, SIOCDEVPRIVATE, (caddr_t) &ifreq) < 0) 
	    
	    {
		perror ("ioctl(SIOCDEVPRIVATE)");
		exit(1);
	    }

	    (void)signal(SIGALRM, catchalarm);
	    
	    signalled = 0;
	    
	    (void)alarm(interval);

	/*** output area ***/

	if ( line % 20 ) {
	    run_random(stats_ibytes, MAX_BYTES, MAX_LEDS, LPT_PORT, SLEEPS_FOR);
	}
 	    /*	printf("%d\n",stats_ibytes);	*/ 	
	    line++;

	    oldmask = sigblock(sigmask(SIGALRM));

	    if (! signalled) {
		sigpause(0);
	    }

	    sigsetmask(oldmask);

	    signalled = 0;

	    (void)alarm(interval);

	    bcopy((char *)sc, (char *)osc, sizeof(PPP_STRUCT));

	}
}




/*
 * Called if an interval expires before sidewaysintpr has completed a loop.
 * Sets a flag to not wait for the alarm.
 */

void catchalarm(arg)

int arg;

{
	signalled = 1;
}




/*-----------------------------------------------------------------------------
 * run_traditional - orginal flavour LED meter.
 */
int run_traditional(float ld_avg, float ld_max, int leds_num, 
		    unsigned int port, long usecs)
{
   /* lookup table to use for bit mask */ 
   static unsigned char led_mask[] = 
   {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; 
   int num_lights;
   
   /* take load_avg and use it to specify num of lights to turn on */
   if (ld_avg/ld_max > 1.0)
      num_lights = leds_num;
   else
      num_lights = (int) (ld_avg/ld_max * leds_num);

   port_out(port, led_mask[num_lights]);  /* turn on the lights */
   usleep(usecs);                         /* sleep */

   return 0;
}

/*-----------------------------------------------------------------------------
 * run_cylon - Battlestar Galactica Cylon and Knight Rider Kit meter mode.
 *   the lights cycle from low to high and back, kind of like 1D pong.
 */
int run_cylon(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag)
{
#define CY_IDLE_QUANTUM  500000L      /* longest quantum time slice */
#define CY_MIN_QUANTUM   10000        /* shortest quantum time slice */
#define CYD_IDLE_QUANTUM 10000        /* idle speed in inverted pulse */
   static unsigned char cylon_mask[] = /* bit mask table */
   {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
   static int led_index;              /* index into led mask table */
   static int direction;              /* keeps track of LED direction */ 
   float change_rate;                 /* scaling factor applied to load avgs */
   int led_quantum;                   /* time slice alotted to each led */ 
   int i, count;                      /* misc counters */

   /* setup cylon to increase the frequency of led cycling as */
   /* the load average increases */
   if (mode_flag == SPEED_UP) {
      change_rate = -((float) CY_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * ld_avg) + 
	 CY_IDLE_QUANTUM+CY_MIN_QUANTUM;
   }
   /* setup cylon to decrease the frequency of led cycling as */
   /* the load average decreases */
   else if (mode_flag == SLOW_DOWN) {
      change_rate = (float)(usecs - CYD_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * square(ld_avg)) + CYD_IDLE_QUANTUM;
   }
   else return -1;  /* error return (currently unused) */
  
   /* setup the cycles based on the current time quantum */
   /* ensuring that at least one cycle is run */
   count = (usecs/led_quantum > 0 ? usecs/led_quantum : 1);

   /* controlling loop for cylon cycling. (practicing safe port_out() usage) */
   for (i=0; i < count; i++) {
      if (direction == 0 && led_index < leds_num-1) {
	 port_out(port, cylon_mask[led_index]);
	 led_index++;
      }
      else if (direction == 0 && led_index == leds_num-1) {
	 port_out(port, cylon_mask[led_index]);
	 led_index--;
	 direction = 1;
      }
      else if (direction == 1 && led_index > 0) {
	 port_out(port, cylon_mask[led_index]);
	 led_index--;
      }
      else if (direction == 1 && led_index == 0) {
	 port_out(port, cylon_mask[led_index]);
	 led_index++;
	 direction = 0;
      }
      else fprintf(stderr, "Missed a case silly\n");
      
      usleep(led_quantum);
   }
   return 0;
}

/*-----------------------------------------------------------------------------
 * run_pulse - sequence through the LEDs from lowest to highest.  In normal
 *   pulse mode, cycling goes faster as load increases.  In inverted mode,
 *   cycling is fastest when the system is idle.
 */
int run_pulse(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag)
{
#define PU_IDLE_QUANTUM  500000L      /* longest quantum time slice */
#define PU_MIN_QUANTUM   10000        /* shortest quantum time slice */
#define PUD_IDLE_QUANTUM 10000        /* idle speed in inverted pulse */
   static int led_index;              /* index of led to ligh up */ 
   float change_rate;                 /* scaling factor applied to load avgs */
   int led_quantum;                   /* time slice alotted to each led */ 
   int i, count;                      /* misc counters */

   /* setup pulse to increase the frequency of led cycling as */
   /* the load average increases */
   if (mode_flag == SPEED_UP) {
      change_rate = -((float) PU_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * ld_avg) + 
	 PU_IDLE_QUANTUM + PU_MIN_QUANTUM;
   }
   /* setup pulse to decrease the frequency of led cycling as */
   /* the load average decreases */
   else if (mode_flag == SLOW_DOWN) {
      change_rate = (float)(usecs - PUD_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * square(ld_avg)) + PUD_IDLE_QUANTUM;
   }
   else return -1;  /* error return (currently unused) */

   /* setup the cycles based on the current time quantum */
   /* ensuring that at least one cycle is run */
   count = (usecs/led_quantum > 0 ? usecs/led_quantum : 1);

   /* loop through led lighting until:  i x led_quantum == usecs */ 
   for (i=0; i < count; i++) {
      if (led_index < leds_num) {
	 port_out(port, 1 << led_index);
	 led_index++;
      }
      else if (led_index >= leds_num) {
	 led_index = 0;
      }
      else fprintf(stderr, "This shouldn't happen\n");
      
      usleep(led_quantum);
   }
   return 0;
}

/*-----------------------------------------------------------------------------
 * run_random - output random bit patterns on the display with the number
 *   of leds lit up proportional to the load average.
 */
int run_random(float ld_avg, float ld_max, int leds_num, unsigned int port,
	       long usecs)
{
#define QUANTUM_DIV 4                  /* sort of a fudge factor */
   unsigned char mask;                 /* mask to output */  
   int num_bits;                       /* number of bits to set in mask */
   int i, j;                           /* misc counters */
   int flippy;

   /* get the number of bits we'll randomly set */ 
   if (ld_avg/ld_max > 1.0)
      num_bits = MAX_LEDS;
   else
      num_bits = (int) (ld_avg/ld_max * leds_num);
   if (num_bits == 0)
      num_bits = 1;

   /* set a random collection of bits in the mask proportional to the */
   /* load average.  it's not entirely perfect in that if you're setting */
   /* num_bits in the mask, some of the same bits may get set more than */
   /* once causing less than num_bit unique bits.  this isn't a real */
   /* problem... it still looks okay. */   
   /* I fixed this problem, it's now entirely accurate.  spberry@iastate.edu */

   for (j=0; j < QUANTUM_DIV; j++) {
      mask = 0;
      for (i=0; i < num_bits; i++) {
	 while (1)
	 {
	 flippy = (random() % leds_num);
	 if (!(mask & (1 << flippy)))
		 {
		 mask |= 1 << (flippy);
	 	 break;  
		 }
	 }
      }
      port_out(port, mask);
      usleep(usecs/QUANTUM_DIV);
   }
   return 0;
}

/**** END OF FILE ****/

