/*
 * CHU STREAMS module for SunOS
 *
 * Version 2.6
 *
 * Copyright 1991-1994, Nick Sayer
 *
 * Special thanks to Greg Onufer for his debug assists.
 * Special thanks to Matthias Urlichs for the 4.1.x loadable driver support
 *   code.
 * Special wet-noodle whippings to Sun for not properly documenting
 *   ANYTHING that makes this stuff at all possible.
 *
 * Should be PUSHed directly on top of a serial I/O channel.
 * Provides complete chucode structures to user space.
 *
 * COMPILATION:
 *
 *
 * To make a SunOS 4.1.x compatable loadable module (from the ntp kernel
 * directory):
 *
 * % cc -c -I../include -DLOADABLE tty_chu_STREAMS.c
 *
 * The resulting .o file is the loadable module. Modload it
 * thusly:
 *
 * % modload tty_chu_STREAMS.o -entry _chuinit
 *
 * When none of the instances are pushed in a STREAM, you can
 * modunload the driver in the usual manner if you wish.
 *
 * As an alternative to loading it dynamically you can compile it
 * directly into the kernel by hacking str_conf.c. See the README
 * file for more details on doing it the old fashioned way.
 *
 *
 * To make a Solaris 2.x compatable module (from the ntp kernel
 * directory):
 *
 * % {gcc,cc} -c -I../include -DSOLARIS2 tty_chu_STREAMS.c
 * % ld -r -o /usr/kernel/strmod/chu tty_chu_STREAMS.o
 * % chmod 755 /usr/kernel/strmod/chu
 *
 * The OS will load it for you automagically when it is first pushed.
 *
 * If you get syntax errors from <sys/timer.h> (really references
 * to types that weren't typedef'd in gcc's version of types.h),
 * add -D_SYS_TIMER_H to blot out the miscreants.
 *
 * Under Solaris 2.2 and previous, do not attempt to modunload the
 * module unless you're SURE it's not in use. I haven't tried it, but
 * I've been told it won't do the right thing. Under Solaris 2.3 (and
 * presumably future revs) an attempt to unload the module when it's in
 * use will properly refuse with a "busy" message.
 *
 *
 * HISTORY:
 *
 * v2.6 - Mutexed the per-instance chucode just to be safe.
 * v2.5 - Fixed show-stopper bug in Solaris 2.x - qprocson().
 * v2.4 - Added dynamic allocation support for Solaris 2.x.
 * v2.3 - Added support for Solaris 2.x.
 * v2.2 - Added SERVICE IMMEDIATE hack.
 * v2.1 - Added 'sixth byte' heuristics.
 * v2.0 - first version with an actual version number.
 *        Added support for new CHU 'second 31' data format.
 *        Deleted PEDANTIC and ANAL_RETENTIVE.
 *
 */

#ifdef SOLARIS2
# ifndef NCHU
#  define NCHU 1
# endif
# define _KERNEL
#elif defined(LOADABLE)
# ifndef NCHU
#  define NCHU 3
#  define KERNEL
# endif
#else
# include "chu.h"
#endif

#if NCHU > 0

/*
 * Number of microseconds we allow between
 * character arrivals.  The speed is 300 baud
 * so this should be somewhat more than 30 msec
 */
#define	CHUMAXUSEC	(60*1000)	/* 60 msec */

#include <sys/types.h>
#include <sys/stream.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <sys/user.h>
#include <syslog.h>
#include <sys/tty.h>

#include <sys/chudefs.h>

#ifdef SOLARIS2

#include <sys/ksynch.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/conf.h>
#include <sys/strtty.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#endif

#ifdef LOADABLE

#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sundev/mbvar.h>
#include <sun/autoconf.h>
#include <sun/vddrv.h>

#endif


static struct module_info rminfo = { 0, "chu", 0, INFPSZ, 0, 0 };
static struct module_info wminfo = { 0, "chu", 0, INFPSZ, 0, 0 };
static int chuopen(), churput(), chuwput(), chuclose();

static struct qinit rinit = { churput, NULL, chuopen, chuclose, NULL,
	&rminfo, NULL };

static struct qinit winit = { chuwput, NULL, NULL, NULL, NULL,
	&wminfo, NULL };

struct streamtab chuinfo = { &rinit, &winit, NULL, NULL };

/*
 * Here's our private data type and structs
 */
struct priv_data 
{
#ifdef SOLARIS2
  kmutex_t chucode_mutex;
#else
  char in_use;
#endif
  struct chucode chu_struct;
};

#ifndef SOLARIS2
struct priv_data our_priv_data[NCHU];
#endif

#ifdef SOLARIS2

static struct fmodsw fsw =
{
  "chu",
  &chuinfo,
  D_NEW | D_MP
};

extern struct mod_ops mod_strmodops;

static struct modlstrmod modlstrmod =
{
  &mod_strmodops,
  "CHU timecode decoder v2.6",
  &fsw
};

static struct modlinkage modlinkage =
{
  MODREV_1,
  (void*) &modlstrmod,
  NULL
};

int _init()
{
  return mod_install(&modlinkage);
}

int _info(foo)
struct modinfo *foo;
{
  return mod_info(&modlinkage,foo);
}

int _fini()
{
  return mod_remove(&modlinkage);
}

#endif /* SOLARIS2 */

#ifdef LOADABLE

# ifdef sun

static struct vdldrv vd =
{
    VDMAGIC_PSEUDO,
    "chu",
    NULL, NULL, NULL, 0, 0, NULL, NULL, 0, 0,
};

static struct fmodsw *chu_fmod;

/*ARGSUSED*/
chuinit (fc, vdp, vdi, vds)
    unsigned int fc;
    struct vddrv *vdp;
    addr_t vdi;
    struct vdstat *vds;
{
    switch (fc) {
    case VDLOAD:
        {
            int dev, i;

            /* Find free entry in fmodsw */
            for (dev = 0; dev < fmodcnt; dev++) {
                if (fmodsw[dev].f_str == NULL)
                    break;
            }
            if (dev == fmodcnt)
                return (ENODEV);
            chu_fmod = &fmodsw[dev];

	    /* If you think a kernel would have strcpy() you're mistaken. */
            for (i = 0; i <= FMNAMESZ; i++)
                chu_fmod->f_name[i] = wminfo.mi_idname[i];

            chu_fmod->f_str = &chuinfo;
        }
        vdp->vdd_vdtab = (struct vdlinkage *) & vd;

	{
	    int i;

	    for (i=0; i<NCHU; i++)
	        our_priv_data[i].in_use=0;
	}

        return 0;
    case VDUNLOAD:
        {
            int dev;

            for (dev = 0; dev < NCHU; dev++)
                if (our_priv_data[dev].in_use) {
                    /* One of the modules is still open */
                    return (EBUSY);
                }
        }
        chu_fmod->f_name[0] = '\0';
        chu_fmod->f_str = NULL;
        return 0;
    case VDSTAT:
        return 0;
    default:
        return EIO;
    }
}

# endif /* sun */

#endif /* LOADABLE */

#if !defined(LOADABLE) && !defined(SOLARIS2)

char chu_first_open=1;

#endif

/*ARGSUSED*/
static int chuopen(q, dev, flag, sflag)
queue_t *q;
dev_t dev;
int flag;
int sflag;
{
  int i;

#if !defined(LOADABLE) && !defined(SOLARIS2)
  if (chu_first_open)
  {
    chu_first_open=0;

    for(i=0;i<NCHU;i++)
      our_priv_data[i].in_use=0;
  }
#endif

#ifdef SOLARIS2
  /* According to the docs, calling with KM_SLEEP can never
     fail */

  q->q_ptr = kmem_alloc( sizeof(struct priv_data), KM_SLEEP );
  ((struct priv_data *) q->q_ptr)->chu_struct.ncodechars = 0;

  mutex_init(&((struct priv_data *) q->q_ptr)->chucode_mutex,"Chucode Mutex",MUTEX_DRIVER,NULL);
  qprocson(q);

  if (!putnextctl1(WR(q), M_CTL, MC_SERVICEIMM))
  {
    qprocsoff(q);
    mutex_destroy(&((struct priv_data *)q->q_ptr)->chucode_mutex);
    kmem_free(q->q_ptr, sizeof(struct chucode) );
    return (EFAULT);
  }

  return 0;

#else
  for(i=0;i<NCHU;i++)
    if (!our_priv_data[i].in_use)
    {
      ((struct priv_data *) (q->q_ptr))=&(our_priv_data[i]);
      our_priv_data[i].in_use++;
      our_priv_data[i].chu_struct.ncodechars = 0;
      if (!putctl1(WR(q)->q_next, M_CTL, MC_SERVICEIMM))
      {
        our_priv_data[i].in_use=0;
        u.u_error = EFAULT;
	return (OPENFAIL);
      }
      return 0;
    }

  u.u_error = EBUSY;
  return (OPENFAIL);
#endif

}

/*ARGSUSED*/
static int chuclose(q, flag)
queue_t *q;
int flag;
{
#ifdef SOLARIS2
  qprocsoff(q);
  mutex_destroy(&((struct priv_data *)q->q_ptr)->chucode_mutex);
  kmem_free(q->q_ptr, sizeof(struct chucode) );
#else
  ((struct priv_data *) (q->q_ptr))->in_use=0;
#endif
  return (0);
}

/*
 * Now the crux of the biscuit.
 *
 * We will be passed data from the man downstairs. If it's not a data
 * packet, it must be important, so pass it along unmunged. If, however,
 * it is a data packet, we're gonna do special stuff to it. We're going
 * to pass each character we get to the old line discipline code we
 * include below for just such an occasion. When the old ldisc code
 * gets a full chucode struct, we'll hand it back upstairs.
 *
 * chuinput takes a single character and q (as quickly as possible).
 * passback takes a pointer to a chucode struct and q and sends it upstream.
 */

void chuinput();
void passback();

static int churput(q, mp)
queue_t *q;
mblk_t *mp;
{
  mblk_t *bp;

  switch(mp->b_datap->db_type)
  {
    case M_DATA:
      for(bp=mp; bp!=NULL; bp=bp->b_cont)
      {
	while(bp->b_rptr < bp->b_wptr)
	  chuinput( ((u_char)*(bp->b_rptr++)) , q );
      }
      freemsg(mp);
    break;
    default:
      putnext(q,mp);
    break;
  }

}

/*
 * Writing to a chu device doesn't make sense, but we'll pass them
 * through in case they're important.
 */

static int chuwput(q, mp)
queue_t *q;
mblk_t *mp;
{
  putnext(q,mp);
}

/*
 * Take a pointer to a filled chucode struct and a queue and
 * send the chucode stuff upstream
 */

void passback(outdata,q)
struct chucode *outdata;
queue_t *q;
{
  mblk_t *mp;
  int j;

  mp=(mblk_t*) allocb(sizeof(struct chucode),BPRI_LO);

  if (mp==NULL)
  {
#ifdef SOLARIS2
    cmn_err(CE_WARN,"chu module couldn't allocate message block");
#else
    log(LOG_ERR,"chu: cannot allocate message");
#endif
    return;
  }

  for(j=0;j<sizeof(struct chucode); j++)
    *mp->b_wptr++ = *( ((char*)outdata) + j );

  putnext(q,mp);
}

/*
 * This routine was copied nearly verbatim from the old line discipline.
 */
void chuinput(c,q)
register u_char c;
queue_t *q;
{
  register struct chucode *chuc;
  register int i;
  long sec, usec;
  struct timeval tv;

  /*
   * Quick, Batman, get a timestamp! We need to do this
   * right away. The time between the end of the stop bit
   * and this point is critical, and should be as nearly
   * constant and as short as possible. (Un)fortunately,
   * the Sun's clock granularity is so big this isn't a
   * major problem.
   *
   * uniqtime() is totally undocumented, but there you are.
   */
  uniqtime(&tv);

#ifdef SOLARIS2
  mutex_enter(&((struct priv_data *)q->q_ptr)->chucode_mutex);
#endif

  /*
   * Now, locate the chu struct once so we don't have to do it
   * over and over.
   */
  chuc=&(((struct priv_data *) (q->q_ptr))->chu_struct);

	/*
	 * Compute the difference in this character's time stamp
	 * and the last.  If it exceeds the margin, blow away all
	 * the characters currently in the buffer.
	 */
  i = (int)chuc->ncodechars;
  if (i > 0)
  {
    sec = tv.tv_sec - chuc->codetimes[i-1].tv_sec;
    usec = tv.tv_usec - chuc->codetimes[i-1].tv_usec;
    if (usec < 0)
    {
      sec -= 1;
      usec += 1000000;
    }
    if (sec != 0 || usec > CHUMAXUSEC)
    {
      i = 0;
      chuc->ncodechars = 0;
    }
  }

  /*
   * Store the character.
   */
  chuc->codechars[i] = (u_char)c;
  chuc->codetimes[i] = tv;

  /*
   * Now we perform the 'sixth byte' heuristics.
   *
   * This is a long story.
   *
   * We used to be able to count on the first byte of the code
   * having a '6' in the LSD. This prevented most code framing
   * errors (garbage before the first byte wouldn't typically
   * have a 6 in the LSD). That's no longer the case.
   *
   * We can get around this, however, by noting that the 6th byte
   * must be either equal to or one's complement of the first.
   * If we get a sixth byte that ISN'T like that, then it may
   * well be that the first byte is garbage. The right thing
   * to do is to left-shift the whole buffer one count and
   * continue to wait for the sixth byte.
   */
  if (i == NCHUCHARS/2)
  {
    register u_char temp_byte;

    temp_byte=chuc->codechars[i] ^ chuc->codechars[0];

    if ( (temp_byte) && (temp_byte!=0xff) )
    {
      register int t;
      /*
       * No match. Left-shift the buffer and try again
       */
      for(t=0;t<=NCHUCHARS/2;t++)
      {
	chuc->codechars[t]=chuc->codechars[t+1];
	chuc->codetimes[t]=chuc->codetimes[t+1];
      }

      i--; /* This is because of the ++i immediately following */
    }
  }

  /*
   * We done yet?
   */
  if (++i < NCHUCHARS)
  {
    /*
     * We're not done. Not much to do here. Save the count and wait
     * for another character.
     */
    chuc->ncodechars = (u_char)i;
  }
  else
  {
    /*
     * We are done. Mark this buffer full and pass it along.
     */
    chuc->ncodechars = NCHUCHARS;

    /*
     * Now we have a choice. Either the front half and back half
     * have to match, or be one's complement of each other.
     *
     * So let's try the first byte and see
     */

    if(chuc->codechars[0] == chuc->codechars[NCHUCHARS/2])
    {
      chuc->chutype = CHU_TIME;
      for( i=0; i<(NCHUCHARS/2); i++)
        if (chuc->codechars[i] != chuc->codechars[i+(NCHUCHARS/2)])
        {
          chuc->ncodechars = 0;
#ifdef SOLARIS2
          mutex_exit(&((struct priv_data *)q->q_ptr)->chucode_mutex);
#endif
          return;
        }
    }
    else
    {
      chuc->chutype = CHU_YEAR;
      for( i=0; i<(NCHUCHARS/2); i++)
        if (((chuc->codechars[i] ^ chuc->codechars[i+(NCHUCHARS/2)]) & 0xff)
	  != 0xff )
        {
          chuc->ncodechars = 0;
#ifdef SOLARIS2
          mutex_exit(&((struct priv_data *)q->q_ptr)->chucode_mutex);
#endif
          return;
        }
    }

    passback(chuc,q); /* We're done! */
    chuc->ncodechars = 0; /* Start all over again! */
  }
#ifdef SOLARIS2
  mutex_exit(&((struct priv_data *)q->q_ptr)->chucode_mutex);
#endif
}

#endif /* NCHU > 0 */
