/*
 * /src/NTP/REPOSITORY/v4/libparse/parsesolaris.c,v 3.27 1997/02/16 01:14:01 kardel Exp
 *  
 * parsesolaris.c,v 3.27 1997/02/16 01:14:01 kardel Exp
 *
 * STREAMS module for reference clocks
 *
 * Copyright (C) 1993, 1994, 1995, 1996, 1997 by Frank Kardel
 * derived work from parsestreams.c ((c) 1991-1993, Frank Kardel) and
 * dcf77sync.c((c) Frank Kardel)
 * Friedrich-Alexander Universität Erlangen-Nürnberg, Germany
 *                                    
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#ifndef lint
static char rcsid[] = "parsesolaris.c,v 3.27 1997/02/16 01:14:01 kardel Exp";
#endif

/*
 * needed to cope with Solaris 2.3 header file chaos
 */
#include <sys/types.h>
/*
 * the Solaris 2.2 include list
 */
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/termios.h>
#include <sys/stream.h>
#include <sys/strtty.h>
#include <sys/stropts.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "ntp_fp.h"
#include "parse.h"
#include <sys/parsestreams.h>

/*--------------- loadable driver section -----------------------------*/

static struct streamtab parseinfo;

static struct fmodsw fmod_templ =
{
  "parse",			/* module name */
  &parseinfo,			/* module information */
  D_NEW|D_MP|D_MTQPAIR,		/* exclusive for q pair */
  /* lock ptr */
};

extern struct mod_ops mod_strmodops;

static struct modlstrmod modlstrmod = 
{
  &mod_strmodops,		/* a STREAMS module */
  "PARSE      - NTP reference",	/* name this baby - keep room for revision number */
  &fmod_templ
};

static struct modlinkage modlinkage =
{
  MODREV_1,
  {
    &modlstrmod,
    NULL
  }
};

/*
 * module management routines
 */
/*ARGSUSED*/
int _init(void)
{
  static char revision[] = "3.27";
  char *s, *S, *t;
  
  /*
   * copy RCS revision into Drv_name
   *
   * are we forcing RCS here to do things it was not built for ?
   */
  s = revision;
  if (*s == '$')
    {
      /*
       * skip "$Revision: "
       * if present. - not necessary on a -kv co (cvs export)
       */
      while (*s && (*s != ' '))
	{
	  s++;
	}
      if (*s == ' ') s++;
    }
	  
  t = modlstrmod.strmod_linkinfo; 
  while (*t && (*t != ' '))
    {
      t++;
    }
  if (*t == ' ') t++;
	  
  S = s;
  while (*S && (((*S >= '0') && (*S <= '9')) || (*S == '.')))
    {
      S++;
    }
	  
  if (*s && *t && (S > s))
    {
      if (strlen(t) >= (S - s))
	{
	  (void) strncpy(t, s, S - s);
	}
    }
  return (mod_install(&modlinkage));
}

/*ARGSUSED*/
int _info(struct modinfo *modinfop)
{
  return (mod_info(&modlinkage, modinfop));
}

/*ARGSUSED*/
int _fini(void)
{
  if (mod_remove(&modlinkage) != DDI_SUCCESS)
    {
      return EBUSY;
    }
  else
    return DDI_SUCCESS;
}

/*--------------- stream module definition ----------------------------*/

static int parseopen();
static int parseclose();
static int parsewput();
static int parserput();
static int parsersvc();

static struct module_info driverinfo =
{
  0,				/* module ID number */
  fmod_templ.f_name,		/* module name - why repeated here ? compat ?*/
  0,				/* minimum accepted packet size */
  INFPSZ,			/* maximum accepted packet size */
  1,				/* high water mark - flow control */
  0				/* low water mark - flow control */
};

static struct qinit rinit =	/* read queue definition */
{
  parserput,			/* put procedure */
  parsersvc,			/* service procedure */
  parseopen,			/* open procedure */
  parseclose,			/* close procedure */
  NULL,				/* admin procedure - NOT USED FOR NOW */
  &driverinfo,			/* information structure */
  NULL				/* statistics */
};

static struct qinit winit =	/* write queue definition */
{
  parsewput,			/* put procedure */
  NULL,				/* service procedure */
  NULL,				/* open procedure */
  NULL,				/* close procedure */
  NULL,				/* admin procedure - NOT USED FOR NOW */
  &driverinfo,			/* information structure */
  NULL				/* statistics */
};

static struct streamtab parseinfo =	/* stream info element for parse driver */
{
  &rinit,			/* read queue */
  &winit,			/* write queue */
  NULL,				/* read mux */
  NULL				/* write mux */
};

/*--------------- driver data structures ----------------------------*/

/*
 * we usually have an inverted signal - but you
 * can change this to suit your needs
 */
int cd_invert = 1;		/* invert status of CD line - PPS support via CD input */

#ifdef PARSEDEBUG
int parsedebug = ~0;
#else
int parsedebug = 0;
#endif

/*--------------- module implementation -----------------------------*/

#define TIMEVAL_USADD(_X_, _US_) do {\
				    (_X_)->tv_usec += (_US_);\
			            if ((_X_)->tv_usec >= 1000000)\
				      {\
					 (_X_)->tv_sec++;\
					 (_X_)->tv_usec -= 1000000;\
				      }\
				 } while (0)

static int init_linemon();
static void close_linemon();

#define M_PARSE		0x0001
#define M_NOPARSE	0x0002

static void
pprintf(int lev, char *form, ...)
{
  va_list ap;

  va_start(ap, form);

  if (lev & parsedebug)
    vcmn_err(CE_CONT, form, ap);

  va_end(ap);
}

static int
setup_stream(queue_t *q, int mode)
{
  register mblk_t *mp;

  pprintf(DD_OPEN,"parse: SETUP_STREAM - setting up stream for q=%x\n", q);

  mp = allocb(sizeof(struct stroptions), BPRI_MED);
  if (mp)
    {
      struct stroptions *str = (struct stroptions *)mp->b_wptr;

      str->so_flags   = SO_READOPT|SO_HIWAT|SO_LOWAT|SO_ISNTTY;
      str->so_readopt = (mode == M_PARSE) ? RMSGD : RNORM;
      str->so_hiwat   = (mode == M_PARSE) ? sizeof(parsetime_t) : 256;
      str->so_lowat   = 0;
      mp->b_datap->db_type = M_SETOPTS;
      mp->b_wptr     += sizeof(struct stroptions);
      if (!q)
	panic("NULL q - strange");
      putnext(q, mp);
      return putctl1(WR(q)->q_next, M_CTL, (mode == M_PARSE) ? MC_SERVICEIMM :
		     MC_SERVICEDEF);
    }
  else
    {
      pprintf(DD_OPEN, "parse: setup_stream - FAILED - no MEMORY for allocb\n"); 
      return 0;
    }
}

/*ARGSUSED*/
static int parseopen(queue_t *q, dev_t *dev, int flag, int sflag, cred_t *credp)
{
  register parsestream_t *parse;
  static int notice = 0;
  
  pprintf(DD_OPEN, "parse: OPEN - q=%x\n", q); 
  
  if (sflag != MODOPEN)
    {			/* open only for modules */
      pprintf(DD_OPEN, "parse: OPEN - FAILED - not MODOPEN\n"); 
      return EIO;
    }

  if (q->q_ptr != (caddr_t)NULL)
    {
      pprintf(DD_OPEN, "parse: OPEN - FAILED - EXCLUSIVE ONLY\n"); 
      return EBUSY;
    }

  q->q_ptr = (caddr_t)kmem_alloc(sizeof(parsestream_t), KM_SLEEP);
  if (q->q_ptr == (caddr_t)0)
    {
      return ENOMEM;
    }

  pprintf(DD_OPEN, "parse: OPEN - parse area q=%x, q->q_ptr=%x\n", q, q->q_ptr); 
  WR(q)->q_ptr = q->q_ptr;
  pprintf(DD_OPEN, "parse: OPEN - WQ parse area q=%x, q->q_ptr=%x\n", WR(q), WR(q)->q_ptr); 
  
  parse = (parsestream_t *) q->q_ptr;
  bzero((caddr_t)parse, sizeof(*parse));
  parse->parse_queue     = q;
  parse->parse_status    = PARSE_ENABLE;
  parse->parse_ppsclockev.tv.tv_sec  = 0;
  parse->parse_ppsclockev.tv.tv_usec = 0;
  parse->parse_ppsclockev.serial     = 0;

  qprocson(q);

  pprintf(DD_OPEN, "parse: OPEN - initializing io subsystem q=%x\n", q); 

  if (!parse_ioinit(&parse->parse_io))
    {
      /*
       * ok guys - beat it
       */
      qprocsoff(q);

      kmem_free((caddr_t)parse, sizeof(parsestream_t));

      return EIO;
    }

  pprintf(DD_OPEN, "parse: OPEN - initializing stream q=%x\n", q); 

  if (setup_stream(q, M_PARSE))
    {
      (void) init_linemon(q);	/* hook up PPS ISR routines if possible */
      pprintf(DD_OPEN, "parse: OPEN - SUCCEEDED\n"); 

      /*
       * I know that you know the delete key, but you didn't write this
       * code, did you ? - So, keep the message in here.
       */
      if (!notice)
	{
	  cmn_err(CE_CONT, "?%s: Copyright (c) 1993-1996, Frank Kardel\n", modlstrmod.strmod_linkinfo);
	  notice = 1;
	}

      return 0;
    }
  else
    {
      qprocsoff(q);

      kmem_free((caddr_t)parse, sizeof(parsestream_t));

      return EIO;
    }
}

/*ARGSUSED*/
static int parseclose(queue_t *q, int flags)
{
  register parsestream_t *parse = (parsestream_t *)q->q_ptr;
  register unsigned long s;
  
  pprintf(DD_CLOSE, "parse: CLOSE\n");
  
  qprocsoff(q);

  s = splhigh();
  
  if (parse->parse_dqueue)
    close_linemon(parse->parse_dqueue, q);
  parse->parse_dqueue = (queue_t *)0;

  (void) splx(s);
      
  parse_ioend(&parse->parse_io);

  kmem_free((caddr_t)parse, sizeof(parsestream_t));

  q->q_ptr = (caddr_t)NULL;
  WR(q)->q_ptr = (caddr_t)NULL;

  return 0;
}

/*
 * move unrecognized stuff upward
 */
static int parsersvc(queue_t *q)
{
  mblk_t *mp;
  
  while ((mp = getq(q)))
    {
      if (canputnext(q) || (mp->b_datap->db_type > QPCTL))
	{
	  putnext(q, mp);
          pprintf(DD_RSVC, "parse: RSVC - putnext\n");
	}
      else
	{
	  putbq(q, mp);
          pprintf(DD_RSVC, "parse: RSVC - flow control wait\n");
	  break;
	}
    }
  return 0;
}

/*
 * do ioctls and
 * send stuff down - dont care about
 * flow control
 */
static int parsewput(queue_t *q, mblk_t *mp)
{
  register int ok = 1;
  register mblk_t *datap;
  register struct iocblk *iocp;
  parsestream_t         *parse = (parsestream_t *)q->q_ptr;
  
  pprintf(DD_WPUT, "parse: parsewput\n");
  
  switch (mp->b_datap->db_type)
    {
    default:
      putnext(q, mp);
      break;
      
    case M_IOCTL:
      iocp = (struct iocblk *)mp->b_rptr;
      switch (iocp->ioc_cmd)
	{
	default:
	  pprintf(DD_WPUT, "parse: parsewput - forward M_IOCTL\n");
	  putnext(q, mp);
	  break;

	case CIOGETEV:
	  /*
	   * taken from Craig Leres ppsclock module (and modified)
	   */
	  datap = allocb(sizeof(struct ppsclockev), BPRI_MED);
	  if (datap == NULL || mp->b_cont)
	    {
	      mp->b_datap->db_type = M_IOCNAK;
	      iocp->ioc_error = (datap == NULL) ? ENOMEM : EINVAL;
	      if (datap != NULL)
		freeb(datap);
	      qreply(q, mp);
	      break;
	    }

	  mp->b_cont = datap;
	  *(struct ppsclockev *)datap->b_wptr = parse->parse_ppsclockev;
	  datap->b_wptr +=
	    sizeof(struct ppsclockev) / sizeof(*datap->b_wptr);
	  mp->b_datap->db_type = M_IOCACK;
	  iocp->ioc_count = sizeof(struct ppsclockev);
	  qreply(q, mp);
	  break;
	  
	case PARSEIOC_ENABLE:
	case PARSEIOC_DISABLE:
	  {
	    parse->parse_status = (parse->parse_status & ~PARSE_ENABLE) |
	                          (iocp->ioc_cmd == PARSEIOC_ENABLE) ?
				    PARSE_ENABLE : 0;
	    if (!setup_stream(RD(q), (parse->parse_status & PARSE_ENABLE) ?
			      M_PARSE : M_NOPARSE))
	      {
		mp->b_datap->db_type = M_IOCNAK;
	      }
	    else
	      {
		mp->b_datap->db_type = M_IOCACK;
	      }
	    qreply(q, mp);
	    break;
	  }	    

	case PARSEIOC_TIMECODE:
	case PARSEIOC_SETFMT:
	case PARSEIOC_GETFMT:
	case PARSEIOC_SETCS:
          if (iocp->ioc_count == sizeof(parsectl_t))
	    {
	      parsectl_t *dct = (parsectl_t *)mp->b_cont->b_rptr;

	      switch (iocp->ioc_cmd)
		{
		case PARSEIOC_TIMECODE:
		  pprintf(DD_WPUT, "parse: parsewput - PARSEIOC_TIMECODE\n");
		  ok = parse_timecode(dct, &parse->parse_io);
		  break;
		  
		case PARSEIOC_SETFMT:
		  pprintf(DD_WPUT, "parse: parsewput - PARSEIOC_SETFMT\n");
		  ok = parse_setfmt(dct, &parse->parse_io);
		  break;

		case PARSEIOC_GETFMT:
		  pprintf(DD_WPUT, "parse: parsewput - PARSEIOC_GETFMT\n");
		  ok = parse_getfmt(dct, &parse->parse_io);
		  break;

		case PARSEIOC_SETCS:
		  pprintf(DD_WPUT, "parse: parsewput - PARSEIOC_SETCS\n");
		  ok = parse_setcs(dct, &parse->parse_io);
		  break;
		}
	      mp->b_datap->db_type = ok ? M_IOCACK : M_IOCNAK;
	    }
	  else
	    {
	      mp->b_datap->db_type = M_IOCNAK;
	    }
	  pprintf(DD_WPUT, "parse: parsewput qreply - %s\n", (mp->b_datap->db_type == M_IOCNAK) ? "M_IOCNAK" : "M_IOCACK");
	  qreply(q, mp);
	  break;
	}
    }
  return 0;
}

/*
 * read characters from streams buffers
 */
static unsigned long rdchar(mblk_t **mp)
{
  while (*mp != (mblk_t *)NULL)
    {
      if ((*mp)->b_wptr - (*mp)->b_rptr)
	{
	  return (unsigned long)(*(unsigned char *)((*mp)->b_rptr++));
	}
      else
	{
	  register mblk_t *mmp = *mp;
	  
	  *mp = (*mp)->b_cont;
	  freeb(mmp);
	}
    }
  return ~0;
}

/*
 * convert incoming data
 */
static int parserput(queue_t *q, mblk_t *imp)
{
  register unsigned char type;
  mblk_t *mp = imp;
  
  switch (type = mp->b_datap->db_type)
    {
     default:
      /*
       * anything we don't know will be put on queue
       * the service routine will move it to the next one
       */
      pprintf(DD_RPUT, "parse: parserput - forward type 0x%x\n", type);

      if (canputnext(q) || (mp->b_datap->db_type > QPCTL))
	{
	  putnext(q, mp);
	}
      else
	putq(q, mp);
      break;
      
     case M_BREAK:
     case M_DATA:
      {
	register parsestream_t * parse = (parsestream_t *)q->q_ptr;
	register mblk_t *nmp;
	register unsigned long ch;
	timestamp_t ctime;
        timespec_t hrestime;

	/*
	 * get time on packet delivery
	 */
	gethrestime(&hrestime);
	ctime.tv.tv_sec  = hrestime.tv_sec;
	ctime.tv.tv_usec = hrestime.tv_nsec / 1000;

	if (!(parse->parse_status & PARSE_ENABLE))
	  {
	    pprintf(DD_RPUT, "parse: parserput - parser disabled - forward type 0x%x\n", type);
	    if (canputnext(q) || (mp->b_datap->db_type > QPCTL))
	      {
		putnext(q, mp);
	      }
	    else
	      putq(q, mp);
	  }
	else
	  {
	    pprintf(DD_RPUT, "parse: parserput - M_%s\n", (type == M_DATA) ? "DATA" : "BREAK");

	    if (type == M_DATA)
	      {
		/*
		 * parse packet looking for start an end characters
		 */
		while (mp != (mblk_t *)NULL)
		  {
		    ch = rdchar(&mp);
		    if (ch != ~0 && parse_ioread(&parse->parse_io, (unsigned int)ch, &ctime))
		      {
			/*
			 * up up and away (hopefully ...)
			 * don't press it if resources are tight or nobody wants it
			 */
			nmp = (mblk_t *)NULL;
			if (canputnext(parse->parse_queue) && (nmp = allocb(sizeof(parsetime_t), BPRI_MED)))
			  {
			    bcopy((caddr_t)&parse->parse_io.parse_dtime, (caddr_t)nmp->b_rptr, sizeof(parsetime_t));
			    nmp->b_wptr += sizeof(parsetime_t);
			    putnext(parse->parse_queue, nmp);
			  }
			else
			  if (nmp) freemsg(nmp);
			parse_iodone(&parse->parse_io);
		      }
		  }	
	      }
	    else
	      {
		if (parse_ioread(&parse->parse_io, (unsigned int)0, &ctime))
		  {
		    /*
		     * up up and away (hopefully ...)
		     * don't press it if resources are tight or nobody wants it
		     */
		    nmp = (mblk_t *)NULL;
		    if (canputnext(parse->parse_queue) && (nmp = allocb(sizeof(parsetime_t), BPRI_MED)))
		      {
			bcopy((caddr_t)&parse->parse_io.parse_dtime, (caddr_t)nmp->b_rptr, sizeof(parsetime_t));
			nmp->b_wptr += sizeof(parsetime_t);
			putnext(parse->parse_queue, nmp);
		      }
		    else
		      if (nmp) freemsg(nmp);
		    parse_iodone(&parse->parse_io);
		  }
		freemsg(mp);
	      }
	    break;
	  }
      }

      /*
       * CD PPS support for non direct ISR hack
       */
    case M_HANGUP:
    case M_UNHANGUP:
      {
	register parsestream_t * parse = (parsestream_t *)q->q_ptr;
	timestamp_t ctime;
        timespec_t hrestime;
	register mblk_t *nmp;
	register int status = cd_invert ^ (type == M_HANGUP);

	gethrestime(&hrestime);
	ctime.tv.tv_sec  = hrestime.tv_sec;
	ctime.tv.tv_usec = hrestime.tv_nsec / 1000;
	
	pprintf(DD_RPUT, "parse: parserput - M_%sHANGUP\n", (type == M_HANGUP) ? "" : "UN");

	if ((parse->parse_status & PARSE_ENABLE) &&
	    parse_iopps(&parse->parse_io, status ? SYNC_ONE : SYNC_ZERO, &ctime))
	  {
	    nmp = (mblk_t *)NULL;
	    if (canputnext(parse->parse_queue) && (nmp = allocb(sizeof(parsetime_t), BPRI_MED)))
	      {
		bcopy((caddr_t)&parse->parse_io.parse_dtime, (caddr_t)nmp->b_rptr, sizeof(parsetime_t));
		nmp->b_wptr += sizeof(parsetime_t);
		putnext(parse->parse_queue, nmp);
	      }
	    else
	      if (nmp) freemsg(nmp);
	    parse_iodone(&parse->parse_io);
	    freemsg(mp);
	  }
	else
	  if (canputnext(q) || (mp->b_datap->db_type > QPCTL))
	    {
	      putnext(q, mp);
	    }
	  else
	    putq(q, mp);
	
	if (status)
	  {
	    parse->parse_ppsclockev.tv = ctime.tv;
	    ++(parse->parse_ppsclockev.serial);
	  }
      }
    }
  return 0;
}

static int  init_zs_linemon();	/* handle line monitor for "zs" driver */
static void close_zs_linemon();
static void zs_xsisr();		/* zs external status interupt handler */

/*-------------------- CD isr status monitor ---------------*/

static int init_linemon(queue_t *q)
{
  register queue_t *dq;
  
  dq = WR(q);
  /*
   * we ARE doing very bad things down here (basically stealing ISR
   * hooks)
   *
   * so we chase down the STREAMS stack searching for the driver
   * and if this is a known driver we insert our ISR routine for
   * status changes in to the ExternalStatus handling hook
   */
  while (dq->q_next)
    {
      dq = dq->q_next;		/* skip down to driver */
    }

  /*
   * find appropriate driver dependent routine
   */
  if (dq->q_qinfo && dq->q_qinfo->qi_minfo)
    {
      register char *dname = dq->q_qinfo->qi_minfo->mi_idname;

      pprintf(DD_INSTALL, "init_linemon: driver is \"%s\"\n", dname);

#ifdef sun
      if (dname && !strcmp(dname, "zs"))
	{
	  return init_zs_linemon(dq, q);
	}
      else
#endif
	{
	  pprintf(DD_INSTALL, "init_linemon: driver \"%s\" not suitable for CD monitoring\n", dname);
	  return 0;
	}
    }
  pprintf(DD_INSTALL, "init_linemon: cannot find driver\n");
  return 0;
}

static void close_linemon(queue_t *q, queue_t *my_q)
{
  /*
   * find appropriate driver dependent routine
   */
  if (q->q_qinfo && q->q_qinfo->qi_minfo)
    {
      register char *dname = q->q_qinfo->qi_minfo->mi_idname;

#ifdef sun
      if (dname && !strcmp(dname, "zs"))
	{
	  close_zs_linemon(q, my_q);
	  return;
	}
  pprintf(DD_INSTALL, "close_linemon: cannot find driver close routine for \"%s\"\n", dname);
#endif
    }
  pprintf(DD_INSTALL, "close_linemon: cannot find driver name\n");
}

#ifdef sun
#include <sys/tty.h>
#include <sys/zsdev.h>
#include <sys/ser_async.h>
#include <sys/ser_zscc.h>

/*
 * there should be some docs telling how to get to
 * sz:zs_usec_delay and zs:initzsops()
 */
#define zs_usec_delay 5

struct savedzsops
{
  struct zsops  zsops;
  struct zsops *oldzsops;
};

static struct zsops   *emergencyzs;

static int init_zs_linemon(queue_t *q, queue_t *my_q)
{
  register struct zscom *zs;
  register struct savedzsops *szs;
  register parsestream_t  *parsestream = (parsestream_t *)my_q->q_ptr;
  /*
   * we expect the zsaline pointer in the q_data pointer
   * from there on we insert our on EXTERNAL/STATUS ISR routine
   * into the interrupt path, before the standard handler
   */
  zs = ((struct asyncline *)q->q_ptr)->za_common;
  if (!zs)
    {
      /*
       * well - not found on startup - just say no (shouldn't happen though)
       */
      return 0;
    }
  else
    {
      /*
       * we do a direct replacement, in case others fiddle also
       * if somebody else grabs our hook and we disconnect
       * we are in DEEP trouble - panic is likely to be next, sorry
       */
      szs = (struct savedzsops *) kmem_alloc(sizeof(struct savedzsops), KM_SLEEP);

      if (szs == (struct savedzsops *)0)
	{
          pprintf(DD_INSTALL, "init_zs_linemon: CD monitor NOT installed - no memory\n");

	  return 0;
	}
      else
	{
	  parsestream->parse_data   = (void *)szs;

	  mutex_enter(zs->zs_excl);

	  parsestream->parse_dqueue = q; /* remember driver */

	  szs->zsops            = *zs->zs_ops;
	  szs->zsops.zsop_xsint = (void (*)())zs_xsisr; /* place our bastard */
	  szs->oldzsops         = zs->zs_ops;
	  emergencyzs           = zs->zs_ops;
	  
	  zs->zs_ops = &szs->zsops; /* hook it up */
	  /*
	   * XXX: this is usually done via zsopinit() 
	   * - have yet to find a way to call that routine
	   */
	  zs->zs_xsint          = (void (*)())zs_xsisr;
	  
	  mutex_exit(zs->zs_excl);

          pprintf(DD_INSTALL, "init_zs_linemon: CD monitor installed\n");

          return 1;
	}
    }
}

/*
 * unregister our ISR routine - must call under splhigh() (or 
 * whatever block ZS status interrupts)
 */
static void close_zs_linemon(queue_t *q, queue_t *my_q)
{
  register struct zscom *zs;
  register parsestream_t  *parsestream = (parsestream_t *)my_q->q_ptr;

  zs = ((struct asyncline *)q->q_ptr)->za_common;
  if (!zs)
    {
      /*
       * well - not found on startup - just say no (shouldn't happen though)
       */
      return;
    }
  else
    {
      register struct savedzsops *szs = (struct savedzsops *)parsestream->parse_data;

      mutex_enter(zs->zs_excl);

      zs->zs_ops = szs->oldzsops; /* reset to previous handler functions */
      /*
       * XXX: revert xsint (usually done via zsopinit() - have still to find
       * a way to call that bugger
       */
      zs->zs_xsint = zs->zs_ops->zsop_xsint;

      mutex_exit(zs->zs_excl);

      kmem_free((caddr_t)szs, sizeof (struct savedzsops));
      
      pprintf(DD_INSTALL, "close_zs_linemon: CD monitor deleted\n");
      return;
    }
}

#define ZSRR0_IGNORE	(ZSRR0_CD|ZSRR0_SYNC|ZSRR0_CTS)

#define MAXDEPTH 50		/* maximum allowed stream crawl */

/*
 * take external status interrupt (only CD interests us)
 */
static void zs_xsisr(struct zscom *zs)
{
  register struct asyncline *za = (struct asyncline *)zs->zs_priv;
  register queue_t *q;
  register unsigned char zsstatus;
  register int loopcheck;
  register unsigned char cdstate;
  register char *dname = "-UNKNOWN-";
  timespec_t hrestime;

  /*
   * pick up current state
   */
  zsstatus = SCC_READ0();

  if (za->za_rr0 ^ (cdstate = zsstatus & ZSRR0_CD))
    {
      timestamp_t cdevent;
      register int status;
      
      /*
       * time stamp
       */
      gethrestime(&hrestime);
      cdevent.tv.tv_sec  = hrestime.tv_sec;
      cdevent.tv.tv_usec = hrestime.tv_nsec / 1000;

      q = za->za_ttycommon.t_readq;

      /*
       * logical state
       */
      status = cd_invert ? cdstate == 0 : cdstate != 0;

      /*
       * ok - now the hard part - find ourself
       */
      loopcheck = MAXDEPTH;
      
      while (q)
	{
	  if (q->q_qinfo && q->q_qinfo->qi_minfo)
	    {
	      dname = q->q_qinfo->qi_minfo->mi_idname;

	      if (!strcmp(dname, parseinfo.st_rdinit->qi_minfo->mi_idname))
		{
		  /*
		   * back home - phew (hopping along stream queues might
		   * prove dangerous to your health)
		   */

		  if ((((parsestream_t *)q->q_ptr)->parse_status & PARSE_ENABLE) &&
		      parse_iopps(&((parsestream_t *)q->q_ptr)->parse_io, status ? SYNC_ONE : SYNC_ZERO, &cdevent))
		    {
		      /*
		       * XXX - currently we do not pass up the message, as
		       * we should.
		       * for a correct behaviour wee need to block out
		       * processing until parse_iodone has been posted via
		       * a softcall-ed routine which does the message pass-up
		       * right now PPS information relies on input being
		       * received
		       */
		      parse_iodone(&((parsestream_t *)q->q_ptr)->parse_io);
		    }
		  
		  if (status)
		    {
		      ((parsestream_t *)q->q_ptr)->parse_ppsclockev.tv = cdevent.tv;
		      ++(((parsestream_t *)q->q_ptr)->parse_ppsclockev.serial);
		    }

		  pprintf(DD_ISR, "zs_xsisr: CD event %s has been posted for \"%s\"\n", status ? "ONE" : "ZERO", dname);
		  break;
		}
	    }

	  q = q->q_next;

	  if (!loopcheck--)
	    {
	      panic("zs_xsisr: STREAMS Queue corrupted - CD event");
	    }
	}

      /*
       * only pretend that CD and ignored transistion (SYNC,CTS)
       * have been handled
       */
      za->za_rr0 = (za->za_rr0 & ~ZSRR0_IGNORE) | (zsstatus & ZSRR0_IGNORE);

      if (((za->za_rr0 ^ zsstatus) & ~ZSRR0_IGNORE) == 0)
	{
	  /*
	   * all done - kill status indication and return
	   */
	  SCC_WRITE0(ZSWR0_RESET_STATUS); /* might kill other conditions here */
	  return;
	}
    }      

   pprintf(DD_ISR, "zs_xsisr: non CD event 0x%x for \"%s\"\n", 
			(za->za_rr0 ^ zsstatus) & ~ZSRR0_CD, dname);
  /*
   * we are now gathered here to process some unusual external status
   * interrupts.
   * any CD events have also been handled and shouldn't be processed
   * by the original routine (unless we have a VERY busy port pin)
   * some initializations are done here, which could have been done before for
   * both code paths but have been avioded for minimum path length to
   * the uniq_time routine
   */
  dname = (char *) 0;
  q = za->za_ttycommon.t_readq;

  loopcheck = MAXDEPTH;
      
  /*
   * the real thing for everything else ...
   */
  while (q)
    {
      if (q->q_qinfo && q->q_qinfo->qi_minfo)
	{
	  dname = q->q_qinfo->qi_minfo->mi_idname;
	  if (!strcmp(dname, parseinfo.st_rdinit->qi_minfo->mi_idname))
	    {
	      register void (*zsisr)();
		  
	      /*
	       * back home - phew (hopping along stream queues might
	       * prove dangerous to your health)
	       */
	      if ((zsisr = ((struct savedzsops *)((parsestream_t *)q->q_ptr)->parse_data)->oldzsops->zsop_xsint))
		zsisr(zs);
	      else
		panic("zs_xsisr: unable to locate original ISR");
		  
	      pprintf(DD_ISR, "zs_xsisr: non CD event was processed for \"%s\"\n", dname);
	      /*
	       * now back to our program ...
	       */
	      return;
	    }
	}

      q = q->q_next;

      if (!loopcheck--)
	{
	  panic("zs_xsisr: STREAMS Queue corrupted - non CD event");
	}
    }

  /*
   * last resort - shouldn't even come here as it indicates
   * corrupted TTY structures
   */
  printf("zs_zsisr: looking for \"%s\" - found \"%s\" - taking EMERGENCY path\n", parseinfo.st_rdinit->qi_minfo->mi_idname, dname ? dname : "-NIL-");
      
  if (emergencyzs && emergencyzs->zsop_xsint)
    emergencyzs->zsop_xsint(zs);
  else
    panic("zs_xsisr: no emergency ISR handler");
}
#endif				/* sun */

/*
 * History:
 *
 * parsesolaris.c,v
 * Revision 3.27  1997/02/16 01:14:01  kardel
 * cleanup for gcc - gcc and Solaris 2.5.1 still crash at modload -
 * linkage format?
 *
 * Revision 3.26  1996/12/07 15:46:40  kardel
 * avoid CTTY on open
 *
 * Revision 3.25  1996/12/01 16:04:21  kardel
 * freeze for 5.86.12.2 PARSE-Patch
 *
 * Revision 3.24  1996/12/01 13:00:04  kardel
 * first working auto-configure version
 *
 * Revision 3.23  1996/11/24 23:16:33  kardel
 * checkpoint - partial autoconfigure update for parse modules
 *
 * Revision 3.22  1996/11/16 18:28:32  kardel
 * cleaned up version of parsesolaris.c
 * currently untested
 * remaining issues to be solved:
 *   how to de-install a zs interrupt
 *   safely not using splhigh()
 *
 * Revision 3.21  1996/10/05 13:30:23  kardel
 * general update
 *
 * Revision 3.20  1995/08/06  19:15:20  kardel
 * parsesolaris seems to be stable now
 *
 * Revision 3.19  1995/08/05  07:17:37  kardel
 * removed sign extension trap
 *
 * Revision 3.18  1995/06/18  12:10:51  kardel
 * removed dispersion calulation from parse subsystem
 *
 * Revision 3.17  1994/07/09  21:31:38  kardel
 * use gethrtime instead of uniqtime(which calls mutex_enter/exit->fatal in ISRs
 *
 * Revision 3.16  1994/05/30  09:57:40  kardel
 * kmem_alloc checking
 *
 * Revision 3.15  1994/02/15  22:20:51  kardel
 * rcsid fixed
 *
 * Revision 3.14  1994/02/15  22:06:04  kardel
 * added qprocsx & flags for MT capability
 *
 * Revision 3.13  1994/02/13  19:16:47  kardel
 * updated verbose Copyright message
 *
 * Revision 3.12  1994/02/02  17:45:35  kardel
 * rcs ids fixed
 *
 * Revision 3.9  1994/01/25  19:05:26  kardel
 * 94/01/23 reconcilation
 *
 * Revision 3.8  1994/01/23  17:22:04  kardel
 * 1994 reconcilation
 *
 * Revision 3.7  1993/12/15  18:24:41  kardel
 * Now also ignoring state changes on ZSRR0_{SYNC,CTS} to avoid zs driver bugs (Solaris 2.3)
 *
 * Revision 3.6  1993/12/15  12:48:53  kardel
 * fixed message loss on M_*HANHUP messages
 *
 * Revision 3.5  1993/12/14  21:05:12  kardel
 * PPS working now for SunOS 5.x zs external status hook
 *
 * Revision 3.4  1993/11/13  11:13:17  kardel
 * Solaris 2.3 additional includes
 *
 * Revision 3.3  1993/11/11  11:20:33  kardel
 * declaration fixes
 *
 * Revision 3.2  1993/11/05  15:40:25  kardel
 * shut up nice feature detection
 *
 * Revision 3.1  1993/11/01  20:00:29  kardel
 * parse Solaris support (initial version)
 *
 */
