604 lines
13 KiB
C
604 lines
13 KiB
C
|
/*
|
||
|
* 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 */
|