/*
 * /src/NTP/ntp-4/libparse/parsestreams.c,v 4.7 1999/11/28 09:13:53 kardel RELEASE_19991128_A
 *  
 * parsestreams.c,v 4.7 1999/11/28 09:13:53 kardel RELEASE_19991128_A
 *
 * STREAMS module for reference clocks
 * (SunOS4.x)
 *
 * Copyright (c) 1989-1998 by 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.
 *
 */

#define KERNEL			/* MUST */
#define VDDRV			/* SHOULD */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifndef lint
static char rcsid[] = "parsestreams.c,v 4.7 1999/11/28 09:13:53 kardel RELEASE_19991128_A";
#endif

#ifndef KERNEL
#include "Bletch: MUST COMPILE WITH KERNEL DEFINE"
#endif

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
#include <sundev/mbvar.h>
#include <sun/autoconf.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/dir.h>
#include <sys/signal.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/ttold.h>
#include <sys/user.h>
#include <sys/tty.h>

#ifdef VDDRV
#include <sun/vddrv.h>
#endif

#include "ntp_stdlib.h"
#include "ntp_fp.h"
/*
 * just make checking compilers more silent
 */
extern int printf      P((const char *, ...));
extern int putctl1     P((queue_t *, int, int));
extern int canput      P((queue_t *));
extern void putbq      P((queue_t *, mblk_t *));
extern void freeb      P((mblk_t *));
extern void qreply     P((queue_t *, mblk_t *));
extern void freemsg    P((mblk_t *));
extern void panic      P((const char *, ...));
extern void usec_delay P((int));

#include "parse.h"
#include "sys/parsestreams.h"

/*
 * use microtime instead of uniqtime if advised to
 */
#ifdef MICROTIME
#define uniqtime microtime
#endif

#ifdef VDDRV
static unsigned int parsebusy = 0;

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

extern struct streamtab parseinfo;


#ifdef PPS_SYNC
static char mnam[] = "PARSEPPS     ";	/* name this baby - keep room for revision number */
#else
static char mnam[] = "PARSE        ";	/* name this baby - keep room for revision number */
#endif
struct vdldrv parsesync_vd = 
{
	VDMAGIC_PSEUDO,		/* nothing like a real driver - a STREAMS module */
	mnam,
};

/*
 * strings support usually not in kernel
 */
static int
Strlen(
	register const char *s
	)
{
	register int c;

	c = 0;
	if (s)
	{
		while (*s++)
		{
			c++;
		}
	}
	return c;
}

static void
Strncpy(
	register char *t,
	register char *s,
	register int   c
	)
{
	if (s && t)
	{
		while ((c-- > 0) && (*t++ = *s++))
		    ;
	}
}

static int
Strcmp(
	register const char *s,
	register const char *t
	)
{
	register int c = 0;

	if (!s || !t || (s == t))
	{
		return 0;
	}

	while (!(c = *s++ - *t++) && *s && *t)
	    /* empty loop */;
  
	return c;
}

static int
Strncmp(
	register char *s,
	register char *t,
	register int n
	)
{
	register int c = 0;

	if (!s || !t || (s == t))
	{
		return 0;
	}

	while (n-- && !(c = *s++ - *t++) && *s && *t)
	    /* empty loop */;
  
	return c;
}
 
void
ntp_memset(
	char *a,
	int x,
	int c
	)
{
	while (c-- > 0)
	    *a++ = x;
}

/*
 * driver init routine
 * since no mechanism gets us into and out of the fmodsw, we have to
 * do it ourselves
 */
/*ARGSUSED*/
int
xxxinit(
	unsigned int fc,
	struct vddrv *vdp,
	addr_t vdin,
	struct vdstat *vds
	)
{
	extern struct fmodsw fmodsw[];
	extern int fmodcnt;
  
	struct fmodsw *fm    = fmodsw;
	struct fmodsw *fmend = &fmodsw[fmodcnt];
	struct fmodsw *ifm   = (struct fmodsw *)0;
	char *mname          = parseinfo.st_rdinit->qi_minfo->mi_idname;
  
	switch (fc)
	{
	    case VDLOAD:
		vdp->vdd_vdtab = (struct vdlinkage *)&parsesync_vd;
		/*
		 * now, jog along fmodsw scanning for an empty slot
		 * and deposit our name there
		 */
		while (fm <= fmend)
		{
	  if (!Strncmp(fm->f_name, mname, FMNAMESZ))
			{
				printf("vddrinit[%s]: STREAMS module already loaded.\n", mname);
				return(EBUSY);
			}
			else
			    if ((ifm == (struct fmodsw *)0) && 
				(fm->f_name[0] == '\0') &&
				(fm->f_str == (struct streamtab *)0))
			    {
				    /*
				     * got one - so move in
				     */
				    ifm = fm;
				    break;
			    }
			fm++;
		}

		if (ifm == (struct fmodsw *)0)
		{
			printf("vddrinit[%s]: no slot free for STREAMS module\n", mname);
			return (ENOSPC);
		}
		else
		{
			static char revision[] = "4.7";
			char *s, *S, *t;
	  
			s = rcsid;		/* NOOP - keep compilers happy */

			Strncpy(ifm->f_name, mname, FMNAMESZ);
			ifm->f_name[FMNAMESZ] = '\0';
			ifm->f_str = &parseinfo;
			/*
			 * 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 = parsesync_vd.Drv_name; 
			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 (0);
		} 
		break;
      
	    case VDUNLOAD:
		if (parsebusy > 0)
		{
			printf("vddrinit[%s]: STREAMS module has still %d instances active.\n", mname, parsebusy);
			return (EBUSY);
		}
		else
		{
			while (fm <= fmend)
			{
				if (!Strncmp(fm->f_name, mname, FMNAMESZ))
				{
					/*
					 * got it - kill entry
					 */
					fm->f_name[0] = '\0';
					fm->f_str = (struct streamtab *)0;
					fm++;
		  
					break;
				}
				fm++;
			}
			if (fm > fmend)
			{
				printf("vddrinit[%s]: cannot find entry for STREAMS module\n", mname);
				return (ENXIO);
			}
			else
			    return (0);
		}
      

	    case VDSTAT:
		return (0);

	    default:
		return (EIO);
      
	}
	return EIO;
}

#endif

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

static int parseopen  P((queue_t *, dev_t, int, int));
static int parseclose P((queue_t *, int));
static int parsewput  P((queue_t *, mblk_t *));
static int parserput  P((queue_t *, mblk_t *));
static int parsersvc  P((queue_t *));

static char mn[] = "parse";

static struct module_info driverinfo =
{
	0,				/* module ID number */
	mn,			/* module name */
	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 */
};

struct streamtab parseinfo =	/* stream info element for dpr driver */
{
	&rinit,			/* read queue */
	&winit,			/* write queue */
	NULL,				/* read mux */
	NULL,				/* write mux */
	NULL				/* module auto push */
};

/*--------------- 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 */

int parsedebug = ~0;

extern void uniqtime P((struct timeval *));

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

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

static int init_linemon P((queue_t *));
static void close_linemon P((queue_t *, queue_t *));

#define M_PARSE		0x0001
#define M_NOPARSE	0x0002

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

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

		str->so_flags   = SO_READOPT|SO_HIWAT|SO_LOWAT;
		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);
		putnext(q, mp);
		return putctl1(WR(q)->q_next, M_CTL, (mode == M_PARSE) ? MC_SERVICEIMM :
			       MC_SERVICEDEF);
	}
	else
	{
		parseprintf(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
	)
{
	register parsestream_t *parse;
	static int notice = 0;
  
	parseprintf(DD_OPEN,("parse: OPEN\n")); 
  
	if (sflag != MODOPEN)
	{			/* open only for modules */
		parseprintf(DD_OPEN,("parse: OPEN - FAILED - not MODOPEN\n")); 
		return OPENFAIL;
	}

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

#ifdef VDDRV
	parsebusy++;
#endif
  
	q->q_ptr = (caddr_t)kmem_alloc(sizeof(parsestream_t));
	if (q->q_ptr == (caddr_t)0)
	{
		parseprintf(DD_OPEN,("parse: OPEN - FAILED - no memory\n")); 
#ifdef VDDRV
		parsebusy--;
#endif
		return OPENFAIL;
	}
	WR(q)->q_ptr = q->q_ptr;
  
	parse = (parsestream_t *)(void *)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;

	if (!parse_ioinit(&parse->parse_io))
	{
		/*
		 * ok guys - beat it
		 */
		kmem_free((caddr_t)parse, sizeof(parsestream_t));
#ifdef VDDRV
		parsebusy--;
#endif
		return OPENFAIL;
	}

	if (setup_stream(q, M_PARSE))
	{
		(void) init_linemon(q);	/* hook up PPS ISR routines if possible */

		parseprintf(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)
		{
#ifdef VDDRV
			printf("%s: Copyright (C) 1991-1998, Frank Kardel\n", parsesync_vd.Drv_name);
#else
			printf("%s: Copyright (C) 1991-1998, Frank Kardel\n", "parsestreams.c,v 4.7 1999/11/28 09:13:53 kardel RELEASE_19991128_A");
#endif
			notice = 1;
		}

		return MODOPEN;
	}
	else
	{
		kmem_free((caddr_t)parse, sizeof(parsestream_t));

#ifdef VDDRV
		parsebusy--;
#endif
		return OPENFAIL;
	}
}

/*ARGSUSED*/
static int
parseclose(
	queue_t *q,
	int flags
	)
{
	register parsestream_t *parse = (parsestream_t *)(void *)q->q_ptr;
	register unsigned long s;
  
	parseprintf(DD_CLOSE,("parse: CLOSE\n"));
  
	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;

#ifdef VDDRV
	parsebusy--;
#endif
	return 0;
}

/*
 * move unrecognized stuff upward
 */
static int
parsersvc(
	queue_t *q
	)
{
	mblk_t *mp;
  
	while ((mp = getq(q)))
	{
		if (canput(q->q_next) || (mp->b_datap->db_type > QPCTL))
		{
			putnext(q, mp);
			parseprintf(DD_RSVC,("parse: RSVC - putnext\n"));
		}
		else
		{
			putbq(q, mp);
			parseprintf(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,
	register mblk_t *mp
	)
{
	register int ok = 1;
	register mblk_t *datap;
	register struct iocblk *iocp;
	parsestream_t         *parse = (parsestream_t *)(void *)q->q_ptr;
  
	parseprintf(DD_WPUT,("parse: parsewput\n"));
  
	switch (mp->b_datap->db_type)
	{
	    default:
		putnext(q, mp);
		break;
      
	    case M_IOCTL:
		    iocp = (struct iocblk *)(void *)mp->b_rptr;
		switch (iocp->ioc_cmd)
		{
		    default:
			parseprintf(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 *)(void *)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 & (unsigned)~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 *)(void *)mp->b_cont->b_rptr;

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

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

				    case PARSEIOC_SETCS:
					parseprintf(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;
			}
			parseprintf(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(
       register 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 (unsigned)~0;
}

/*
 * convert incoming data
 */
static int
parserput(
	queue_t *q,
	mblk_t *mp
	)
{
	unsigned char type;
  
	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
		 */
		parseprintf(DD_RPUT,("parse: parserput - forward type 0x%x\n", type));
		if (canput(q->q_next) || (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 *)(void *)q->q_ptr;
			    register mblk_t *nmp;
			    register unsigned long ch;
			    timestamp_t ctime;

			    /*
			     * get time on packet delivery
			     */
			    uniqtime(&ctime.tv);

			    if (!(parse->parse_status & PARSE_ENABLE))
			    {
				    parseprintf(DD_RPUT,("parse: parserput - parser disabled - forward type 0x%x\n", type));
				    if (canput(q->q_next) || (mp->b_datap->db_type > QPCTL))
				    {
					    putnext(q, mp);
				    }
				    else
					putq(q, mp);
			    }
			    else
			    {
				    parseprintf(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 (canput(parse->parse_queue->q_next) && (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 (canput(parse->parse_queue->q_next) && (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 *)(void *)q->q_ptr;
			    timestamp_t ctime;
			    register mblk_t *nmp;
			    register int status = cd_invert ^ (type == M_UNHANGUP);

			    uniqtime(&ctime.tv);
	
			    parseprintf(DD_RPUT,("parse: parserput - M_%sHANGUP\n", (type == M_HANGUP) ? "" : "UN"));

			    if ((parse->parse_status & PARSE_ENABLE) &&
				parse_iopps(&parse->parse_io, (int)(status ? SYNC_ONE : SYNC_ZERO), &ctime))
			    {
				    nmp = (mblk_t *)NULL;
				    if (canput(parse->parse_queue->q_next) && (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 (canput(q->q_next) || (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  P((queue_t *, queue_t *));	/* handle line monitor for "zs" driver */
static void close_zs_linemon P((queue_t *, queue_t *));

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

static int
init_linemon(
	register 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;

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

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

static void
close_linemon(
	register queue_t *q,
	register 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;
		}
		parseprintf(DD_INSTALL, ("close_linemon: cannot find driver close routine for \"%s\"\n", dname));
#endif
	}
	parseprintf(DD_INSTALL, ("close_linemon: cannot find driver name\n"));
}

#ifdef sun

#include <sundev/zsreg.h>
#include <sundev/zscom.h>
#include <sundev/zsvar.h>

static unsigned long cdmask  = ZSRR0_CD;

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

struct zsops   *emergencyzs;
extern void zsopinit   P((struct zscom *, struct zsops *));
static int  zs_xsisr   P((struct zscom *));	/* zs external status interupt handler */

static int
init_zs_linemon(
	register queue_t *q,
	register queue_t *my_q
	)
{
	register struct zscom *zs;
	register struct savedzsops *szs;
	register parsestream_t  *parsestream = (parsestream_t *)(void *)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 zsaline *)(void *)q->q_ptr)->za_common;
	if (!zs)
	{
		/*
		 * well - not found on startup - just say no (shouldn't happen though)
		 */
		return 0;
	}
	else
	{
		unsigned long s;
      
		/*
		 * 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 *)(void *)kmem_alloc(sizeof(struct savedzsops));

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

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

			s = splhigh();

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

			szs->zsops            = *zs->zs_ops;
			szs->zsops.zsop_xsint = zs_xsisr; /* place our bastard */
			szs->oldzsops         = zs->zs_ops;
			emergencyzs           = zs->zs_ops;
	  
			zsopinit(zs, &szs->zsops); /* hook it up */
	  
			(void) splx(s);

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

			return 1;
		}
	}
}

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

	zs = ((struct zsaline *)(void *)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;
      
		zsopinit(zs, szs->oldzsops); /* reset to previous handler functions */

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

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

#ifdef PPS_SYNC
extern void hardpps P((struct timeval *, long));
#ifdef PPS_NEW
extern struct timeval timestamp;
#else
extern struct timeval pps_time;
#endif
#endif

/*
 * take external status interrupt (only CD interests us)
 */
static int
zs_xsisr(
	 struct zscom *zs
	)
{
	register struct zsaline *za = (struct zsaline *)(void *)zs->zs_priv;
	register struct zscc_device *zsaddr = zs->zs_addr;
	register queue_t *q;
	register unsigned char zsstatus;
	register int loopcheck;
	register char *dname;
#ifdef PPS_SYNC
	register unsigned int s;
	register long usec;
#endif

	/*
	 * pick up current state
	 */
	zsstatus = zsaddr->zscc_control;

	if ((za->za_rr0 ^ zsstatus) & (cdmask))
	{
		timestamp_t cdevent;
		register int status;
      
		za->za_rr0 = (za->za_rr0 & ~(cdmask)) | (zsstatus & (cdmask));

#ifdef PPS_SYNC
		s = splclock();
#ifdef PPS_NEW
		usec = timestamp.tv_usec;
#else
		usec = pps_time.tv_usec;
#endif
#endif
		/*
		 * time stamp
		 */
		uniqtime(&cdevent.tv);
      
#ifdef PPS_SYNC
		(void)splx(s);
#endif

		/*
		 * logical state
		 */
		status = cd_invert ? (zsstatus & cdmask) == 0 : (zsstatus & cdmask) != 0;

#ifdef PPS_SYNC
		if (status)
		{
			usec = cdevent.tv.tv_usec - usec;
			if (usec < 0)
			    usec += 1000000;

			hardpps(&cdevent.tv, usec);
		}
#endif

		q = za->za_ttycommon.t_readq;

		/*
		 * 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 *)(void *)q->q_ptr)->parse_status & PARSE_ENABLE) &&
					    parse_iopps(&((parsestream_t *)(void *)q->q_ptr)->parse_io, (int)(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 *)(void *)q->q_ptr)->parse_io);
					}
		  
					if (status)
					{
						((parsestream_t *)(void *)q->q_ptr)->parse_ppsclockev.tv = cdevent.tv;
						++(((parsestream_t *)(void *)q->q_ptr)->parse_ppsclockev.serial);
					}

					parseprintf(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 has been handled
		 */
		ZSDELAY(2);

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

	if (zsstatus & cdmask)	/* fake CARRIER status */
		za->za_flags |= ZAS_CARR_ON;
	else
		za->za_flags &= ~ZAS_CARR_ON;
	
	/*
	 * 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 avoided 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 int (*zsisr) P((struct zscom *));
		  
				/*
				 * back home - phew (hopping along stream queues might
				 * prove dangerous to your health)
				 */
				if ((zsisr = ((struct savedzsops *)((parsestream_t *)(void *)q->q_ptr)->parse_data)->oldzsops->zsop_xsint))
					return zsisr(zs);
				else
				    panic("zs_xsisr: unable to locate original ISR");
		  
				parseprintf(DD_ISR, ("zs_xsisr: non CD event was processed for \"%s\"\n", dname));
				/*
				 * now back to our program ...
				 */
				return 0;
			}
		}

		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");
	return 0;
}
#endif				/* sun */

/*
 * History:
 *
 * parsestreams.c,v
 * Revision 4.7  1999/11/28 09:13:53  kardel
 * RECON_4_0_98F
 *
 * Revision 4.6  1998/12/20 23:45:31  kardel
 * fix types and warnings
 *
 * Revision 4.5  1998/11/15 21:23:38  kardel
 * ntp_memset() replicated in Sun kernel files
 *
 * Revision 4.4  1998/06/13 12:15:59  kardel
 * superfluous variable removed
 *
 * Revision 4.3  1998/06/12 15:23:08  kardel
 * fix prototypes
 * adjust for ansi2knr
 *
 * Revision 4.2  1998/05/24 18:16:22  kardel
 * moved copy of shadow status to the beginning
 *
 * Revision 4.1  1998/05/24 09:38:47  kardel
 * streams initiated iopps calls (M_xHANGUP) are now consistent with the
 * respective calls from zs_xsisr()
 * simulation of CARRIER status to avoid unecessary M_xHANGUP messages
 *
 * Revision 4.0  1998/04/10 19:45:38  kardel
 * Start 4.0 release version numbering
 *
 * from V3 3.37 log info deleted 1998/04/11 kardel
 */
