TheNetNode-CB/os/go32/scc.c

891 lines
24 KiB
C
Executable file

/************************************************************************/
/* */
/* ***** ***** */
/* ***** ***** */
/* ***** ***** */
/* ***** ***** */
/* *************** *************** */
/* ***************** ***************** */
/* *************** *************** */
/* ***** ***** TheNetNode */
/* ***** ***** Portable */
/* ***** ***** Network */
/* ***** ***** Software */
/* */
/* File os/go32/scc.c (maintained by: ???) */
/* */
/* This file is part of "TheNetNode" - Software Package */
/* */
/* Copyright (C) 1998 - 2008 NORD><LINK e.V. Braunschweig */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the NORD><LINK ALAS (Allgemeine Lizenz fuer */
/* Amateurfunk Software) as published by Hans Georg Giese (DF2AU) */
/* on 13/Oct/1992; either version 1, or (at your option) any later */
/* version. */
/* */
/* This program is distributed WITHOUT ANY WARRANTY only for further */
/* development and learning purposes. See the ALAS (Allgemeine Lizenz */
/* fuer Amateurfunk Software). */
/* */
/* You should have received a copy of the NORD><LINK ALAS (Allgemeine */
/* Lizenz fuer Amateurfunk Software) along with this program; if not, */
/* write to NORD><LINK e.V., Hinter dem Berge 5, D-38108 Braunschweig */
/* */
/* Dieses Programm ist PUBLIC DOMAIN, mit den Einschraenkungen durch */
/* die ALAS (Allgemeine Lizenz fuer Amateurfunk Software), entweder */
/* Version 1, veroeffentlicht von Hans Georg Giese (DF2AU), */
/* am 13.Oct.1992, oder (wenn gewuenscht) jede spaetere Version. */
/* */
/* Dieses Programm wird unter Haftungsausschluss vertrieben, aus- */
/* schliesslich fuer Weiterentwicklungs- und Lehrzwecke. Naeheres */
/* koennen Sie der ALAS (Allgemeine Lizenz fuer Amateurfunk Software) */
/* entnehmen. */
/* */
/* Sollte dieser Software keine ALAS (Allgemeine Lizenz fuer Amateur- */
/* funk Software) beigelegen haben, wenden Sie sich bitte an */
/* NORD><LINK e.V., Hinter dem Berge 5, D-38108 Braunschweig */
/* */
/************************************************************************/
#if defined(__GO32__)
#define SCC_RXSIZE 8192 /* bei DPMI kann es mehr sein */
#else
#define SCC_RXSIZE 1024 /* Groesse eines Buffers */
#endif
#define SCC_TXSIZE 512
#define SCCCHANS 8
#define PCLK 4915200L
/*** Z8530 ******************************************************************/
/* Write Registers */
#define RES_EX_INT 0x10 /* WR0 */
#define RES_TX_IP 0x28
#define RES_ERR 0x30
#define RES_TX_CRC 0x80
#define RES_EOM 0xC0
#define EX_IE 0x01 /* WR1 */
#define TX_IE 0x02
#define RX_IE 0x10
#define RX_DIS 0xC8 /* WR3 */
#define RX_EN 0xC9
#define SDLC 0x20 /* WR4 */
#define TX_DIS 0x61 /* WR5 */
#define TX_EN 0x69
#define RTS 0x02
#define SDLC_FLAG 0x7E /* WR7 */
#define MINT_EN 0x0A /* WR9 */
#define HW_RES 0xC0
#define NRZ 0x80 /* WR10 */
#define NRZI 0xA0
#define ABUNDER 0x04
#define TRxC_RTxC 0x20 /* WR11 */
#define DPLL_RTxC 0x64
#define DPLL_BRG 0x74
#define TRxOUT_BRG 0x02
#define TRxOUT_DPLL 0x03
#define DPLL_EN 0x23 /* WR14 */
#define DPLL_DIS 0x60
#define DPLL_SRCBRG 0x80
#define DPLL_NRZI 0xE0
#define BRG_EN 0x63
#define ZC_IE 0x02 /* WR15 */
#define DCD_IE 0x08
#define SYNC_IE 0x10
#define CTS_IE 0x20
/* Read Registers */
#define DCD 0x08 /* RR0 */
#define HUNT 0x10
#define CTS 0x20
#define TX_EOM 0x40
#define ABORT 0x80
#define RESIDUE 0x0E /* RR1 */
#define RES8 0x06
#define RX_OVR 0x20
#define CRC_ERR 0x40
#define SDLC_EOF 0x80
#define MIN_LEN 15
/*** Macros *****************************************************************/
#define LOBYTE(val) ((val) & 0xFF)
#define HIBYTE(val) ((val) >> 8)
#define set(var, bit) var |= (1 << bit)
#define res(var, bit) var &= ~(1 << bit)
#define SCCDELAY inp (0xE4) /* non-existing port (2us) */
#ifndef outp
#define outp(p,b) outportb(p,b)
#endif
#ifndef inp
#define inp(p) inportb(p)
#endif
#define WR0(val) outp ((SCCDELAY, scc->ctrl), val)
#define WR8(val) WR (8, val)
#define WR(reg, val) outp ((SCCDELAY, outp (scc->ctrl, reg), \
SCCDELAY, scc->ctrl), val)
#define RR0 inp ((SCCDELAY, scc->ctrl))
#define RR8 inp ((SCCDELAY, scc->data))
#define RR(reg) inp ((SCCDELAY, outp (scc->ctrl, reg), \
SCCDELAY, scc->ctrl))
#define not0(par) (par ? par : 1)
enum {TX_IDLE, TX_DWAIT, TX_DELAY, TX_ACTIVE, TX_FLUSH, TX_TAIL};
typedef struct __scc_struct
{
int l2port;
unsigned chan;
unsigned data, ctrl;
unsigned baud;
int flags;
int dcd;
int rx_err;
unsigned rx_len;
int tx_state;
unsigned tx_timer;
char *tx_buf;
char *tx_ptr;
int tx_len;
MBHEAD *rxfhd;
UBYTE rr0;
unsigned errors;
unsigned tailtime;
unsigned slottime;
unsigned persistance;
unsigned txdelay;
unsigned overruns;
unsigned underruns;
unsigned crcerrors;
unsigned lenerrors;
unsigned buferrors;
} __scc_struct;
typedef struct __scc_board {
char *name;
int chans;
unsigned base;
unsigned irq;
int data[SCCCHANS];
int ctrl;
} __scc_board;
static __scc_struct sccchans[SCCCHANS];
static int scc_major = 0;
static unsigned sccbase;
static unsigned sccirq;
static unsigned sccboard;
static int sccoldmask;
static unsigned sccminors;
static UWORD *sccbuf, *sccin, *sccout;
static __scc_board sccboardtab[] =
{
{"DSCC", 8, 0x300, 7, {1, 0, 3, 2, 5, 4, 7, 6}, 16}, /* DSCC */
{"OSCC", 4, 0x150, 3, {3, 1, 7, 5, 0, 0, 0, 0}, -1}, /* OSCC */
{"USCC", 4, 0x300, 7, {0, 1, 2, 3, 0, 0, 0, 0}, 4}, /* USCC */
{NULL , 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0}
};
static void scc_int(int);
static void int_tx(__scc_struct *);
static void int_rx(__scc_struct *);
static void int_ex(__scc_struct *);
static void int_sp(__scc_struct *);
static void scc_clk (__scc_struct *, int);
static int scc_init(void);
static void scc_exit(void);
static void scc_timer(UWORD);
/* Wenn der Sender keine Daten mehr ausstehen hat, wird ein weiteres
* Frame in den Sendebuffer kopiert. */
static void scc_put_frame(__scc_struct *scc)
{
MBHEAD *fbp;
int port;
LHEAD *l2flp;
if (scc->tx_len == 0) { /* erst senden wenn alles raus ist */
if ((port = scc->l2port) == -1) return;
if (kick[port]) {
/* ein weiteres Frame aus der Sendeliste holen */
l2flp = &txl2fl[port];
ulink((LEHEAD *)(fbp = (MBHEAD *) l2flp->head));
kick[port] = ((LHEAD *)l2flp->head != l2flp);
/* Frame aus dem Buffer in den SCC-Buffer kopieren. Hier wird
der Interrupt nicht gesperrt, da tx_len zuletzt und atomic
gesetzt wird. */
scc->tx_len = cpymbflat(scc->tx_ptr = scc->tx_buf, fbp);
relink((LEHEAD *)fbp, /* als gesendet betrachten und in */
(LEHEAD *)stfl.tail); /* die gesendet Liste umhaengen */
/* Wenn der sender noch nicht an ist, starten wir die Kanal-
Arbitrierung, ansonsten stoppen wir ein eventuelles TXTAIL
und gehen wieder zum senden ueber. */
disable();
switch (scc->tx_state) {
case TX_IDLE:
scc->tx_timer = scc->flags & MODE_d ? 1 : not0 (scc->slottime);
scc->tx_state = TX_DWAIT;
break;
case TX_TAIL:
scc->tx_timer = 1;
scc->tx_state = TX_DELAY;
}
enable();
}
}
}
/* Der SCC-Empfangs-Ringbuffer wird geleert. Dort werden von allen Kanaelen
* Empfangsdaten gesammelt. Das Format entspricht dem alten l1put().
*/
static void scc_get_frame(void)
{
int port, len;
MBHEAD **rxfbpp;
unsigned action;
__scc_struct *scc;
LOOP {
disable();
if (sccout == sccin) {
enable();
break;
}
action = *sccout++; /* Zeichen aus dem Ringbuffer lesen */
if (sccout >= sccbuf+SCC_RXSIZE)
sccout = sccbuf;
enable();
scc = sccchans + ((action>>8) & 0x7F);
if ((port = scc->l2port) == -1)
continue;
rxfbpp = &scc->rxfhd; /* Adresse RX-Framezeiger */
if (!(action & 0x8000)) /* Zeichen oder Befehl ? */
{ /* Zeichen : */
if (!*rxfbpp) /* RX-Frame aktiv ? */
{
*rxfbpp = (MBHEAD *)allocb(ALLOC_MBHEAD); /* nein - neues anlegen */
(*rxfbpp)->l2port = port; /* fuer port */
}
putchr(action & 0xFF,*rxfbpp); /* Zeichen in Frame */
}
else /* Befehl : */
if (*rxfbpp) /* nur wenn Frame aktiv */
{ /* Befehl ausfuehren */
if (action & 0x0001) /* Frame in Muelleimer */
relink((LEHEAD *)*rxfbpp, (LEHEAD *)trfl.tail);
else { /* Frame in RX-Liste */
len = (action>>1)&3;
if ((*rxfbpp)->mbpc > len) /* FCS eleminieren */
(*rxfbpp)->mbpc -= len; /* das erste Byte kommt */
/* noch vor HUNT */
relink((LEHEAD *)*rxfbpp, (LEHEAD *)rxfl.tail);
}
*rxfbpp = NULL; /* kein RX-Frame aktiv */
}
}
}
static void scc(void)
{
__scc_struct *scc;
if (scc_major == 0)
return;
scc_get_frame();
for (scc = sccchans; scc < sccchans+sccminors; scc++) {
scc_put_frame(scc);
}
}
static WORD scc_dcd(PORTINFO *port)
{
__scc_struct *scc;
int state = 0;
scc = &sccchans[port->minor];
if (scc->tx_state)
state |= PTTFLAG;
if (scc->dcd)
state |= DCDFLAG;
return(state);
}
static void scc_ctl(int req, int port)
{
__scc_struct *scc;
int minor;
PORTINFO *p;
p = portpar+port;
minor = p->minor;
scc = &sccchans[minor];
switch (req) {
case L1CCMD :
case L1CRES :
if (p->speed > 384)
p->speed = 384;
if (p->speed < 3)
p->speed = 3;
scc->baud = (unsigned) (p->speed * 100);
scc->slottime = p->slottime;
scc->persistance = p->persistance;
scc->txdelay = p->txdelay;
scc->tailtime = p->speed == 3 ? 4 : TAILTIME;
scc->flags = p->l1mode;
WR (4, SDLC);
WR (10, scc->flags & MODE_z ? NRZ | ABUNDER : NRZI | ABUNDER);
WR (7, SDLC_FLAG);
WR (3, RX_DIS);
WR (5, TX_DIS);
scc_clk (scc, scc->tx_state != TX_IDLE);
WR (3, RX_EN);
WR (5, scc->flags & MODE_t ? TX_EN : TX_DIS);
WR (15, SYNC_IE | DCD_IE);
WR0 (RES_EX_INT);
WR0 (RES_EX_INT);
WR (1, EX_IE | TX_IE | RX_IE);
scc->rr0 = RR0;
/* nochmal zur Sicherheit den CHIP-interrupt-master-enable an */
WR (2, (scc->chan>>1) << 4);
WR (9, MINT_EN);
break;
}
default_l1ctl(req, port); /* Flags loeschen */
};
static int scc_istome(int major, char *devname)
{
char name[21], *cp;
int minor = 0;
strncpy(name, devname, 20); /* Minor bestimmen und abschneiden */
name[20] = 0;
for (cp = name; isalpha(*cp); cp++); /* Zahl suchen */
if (isdigit(*cp)) minor = atoi(cp);
*cp = 0; /* kann nicht schaden */
if (minor < 0 || minor >= sccminors)
return(NO_MINOR); /* falscher Geraetekanal (minor) */
return(minor);
}
static int scc_attach(int port, int minor, BOOLEAN check_only)
{
__scc_struct *scc;
scc = &sccchans[minor];
if (scc_major) {
if (scc->l2port == -1) {
if (!check_only) {
scc->l2port = port;
portpar[port].minor = minor;
scc_ctl(L1CRES, port);
}
return(1);
}
if (scc->l2port == port)
return(1);
}
return(0); /* versuchte Doppeleintragung */
}
static int scc_detach(int port)
{
__scc_struct *scc = &sccchans[portpar[port].minor];
WR (1, 0);
if (scc->rxfhd) {
dealmb(scc->rxfhd);
scc->rxfhd = NULL;
}
scc->l2port = -1;
return(1);
}
static void scc_info(int what, int port, MBHEAD *mbp)
{
int minor, cnt;
__scc_struct *scc;
minor = portpar[port].minor;
switch (what) {
case HW_INF_IDENT :
case HW_INF_INFO :
putprintf(mbp, "%s%u", sccboardtab[sccboard].name, minor);
break;
case HW_INF_STAT :
for (minor = cnt = 0, scc = sccchans; minor < sccminors; minor++, scc++)
if (scc->l2port != -1) {
if (cnt++ == 0)
putstr("\rSCC-Statistics:\r\r", mbp);
putprintf(mbp, " SCC%u RxOvr: %5u TxUnd: %5u RxCRC: %5u RxBuf: %5u\r",
minor, scc->overruns, scc->underruns, scc->crcerrors, scc->buferrors);
}
break;
case HW_INF_CLEAR :
scc = &sccchans[minor];
scc->overruns =
scc->underruns =
scc->crcerrors =
scc->lenerrors =
scc->buferrors = 0;
/* durchfallen */
default :
default_l1info(what, port, mbp);
}
}
/* SCC-Modes:
t -> Sendetakt extern (DF9IC)
e -> Sendetakt BRG (32-fach)
-> Sendetakt DPLL (32-fach)
r -> Empfangstakt extern (DF9IC)
-> Empfangstakt DPLL
*/
static void sccput(UWORD action)
{
*sccin++ = action;
if (sccin >= sccbuf+SCC_RXSIZE)
sccin = sccbuf;
}
#define rxput(port, val) sccput (((port) << 8) | (val))
#define rxeof(port, fcs) sccput (((port) << 8) | 0x8000 | (fcs<<1))
#define rxdiscard(port) sccput (((port) << 8) | 0x8001)
/*** SCC Clock **************************************************************/
static void scc_clk (__scc_struct *scc, int tx_on)
{
UWORD tconst;
if (scc->flags & MODE_t) /* externer Takt */
{
WR (11, TRxC_RTxC);
WR (14, DPLL_DIS);
}
else
{
tconst = PCLK / 64L / scc->baud;
if (tx_on)
tconst *= 32;
tconst -= 2;
WR (12, LOBYTE (tconst));
WR (13, HIBYTE (tconst));
WR (11, tx_on ? DPLL_BRG | TRxOUT_BRG
: (scc->flags & MODE_e ? DPLL_RTxC | TRxOUT_BRG
: DPLL_RTxC | TRxOUT_DPLL));
WR (14, DPLL_SRCBRG);
if ((scc->flags & MODE_z) == 0) WR (14, DPLL_NRZI);
WR (14, DPLL_EN);
}
}
/*** SCC TX *****************************************************************/
static void scc_tx (__scc_struct *scc, int tx_on)
{
/* wenn MODE_t nicht gesetzt ist, muessen wir bei TX umschalten */
if ((scc->flags & MODE_t) == 0)
{
WR (3, RX_DIS);
WR (5, TX_DIS);
scc_clk (scc, tx_on);
if (tx_on)
WR (5, TX_EN | RTS);
else
WR (3, RX_EN);
}
else
WR (5, tx_on ? TX_EN | RTS : TX_EN);
}
/*** Timer ******************************************************************/
static void scc_timer (UWORD ticks)
{
__scc_struct *scc;
if (scc_major == 0)
return;
for (scc = sccchans; scc < sccchans+sccminors; scc++)
if (scc->l2port != -1)
{
disable ();
if (scc->tx_timer)
{
if (scc->tx_timer <= ticks)
{
scc->tx_timer = 0;
switch (scc->tx_state)
{
case TX_DWAIT:
if ( (scc->flags & MODE_d)
|| ( (!scc->dcd)
&& ((rand()&0xff) <= scc->persistance)
)
)
{
scc_tx(scc, TRUE);
scc->tx_timer = not0(scc->txdelay);
scc->tx_state = TX_DELAY;
}
else
scc->tx_timer = not0(scc->slottime);
break;
case TX_DELAY:
WR0 (RES_TX_CRC);
WR8 (*scc->tx_ptr++);
WR0 (RES_EOM);
scc->tx_state = TX_ACTIVE;
break;
case TX_TAIL:
scc_tx(scc, FALSE);
scc->tx_state = TX_IDLE;
}
}
else
scc->tx_timer -= ticks;
}
enable ();
}
}
/*** SCC Interrupt **********************************************************/
static void (*handler[]) (__scc_struct *) = {int_tx, int_ex, int_rx, int_sp};
//#define DEBUG(x) _farpokeb(_dos_ds,0xB8000, x);
static void scc_int(int foo)
{
__scc_struct *scc;
UBYTE rr2;
//DEBUG(ch++);
for (scc = sccchans; scc < sccchans+sccminors; )
if (RR (3))
{
scc++;
rr2 = RR (2);
handler[(rr2 >> 1) & 3] (&sccchans[(rr2 >> 3) ^ 1]);
scc = sccchans;
}
else
scc += 2;
}
/*** TX Handler *************************************************************/
static void int_tx(__scc_struct *scc)
{
switch (scc->tx_state)
{
case TX_ACTIVE:
if (RR0 & TX_EOM)
{
scc->underruns++;
scc->errors++;
scc->tx_ptr = scc->tx_buf; /* txrewind */
scc->tx_state = TX_FLUSH;
}
else
if (scc->tx_ptr >= scc->tx_buf + scc->tx_len) /* Frameende? */
{
WR (10, scc->flags & MODE_z ? NRZ : NRZI);
WR0 (RES_TX_IP);
scc->tx_state = TX_FLUSH;
scc->tx_len = 0; /* Frame gesendet */
} else
WR8 (*scc->tx_ptr++);
break;
case TX_FLUSH:
WR (10, scc->flags & MODE_z ? NRZ | ABUNDER : NRZI | ABUNDER);
if (scc->tx_ptr < scc->tx_buf + scc->tx_len) /* noch Daten? */
{
WR0 (RES_TX_CRC);
WR8 (*scc->tx_ptr++);
WR0 (RES_EOM);
scc->tx_state = TX_ACTIVE;
}
else
{
WR0 (RES_TX_IP);
scc->tx_timer = scc->tailtime; /* + clkstep */
scc->tx_state = TX_TAIL;
}
}
}
/*** Extern Status Change ***************************************************/
static void int_ex (__scc_struct *scc)
{
static UBYTE rr0, delta;
delta = (rr0 = RR0) ^ scc->rr0;
if (scc->l2port != -1)
{
if (scc->rx_len && rr0 & (ABORT | HUNT))
{
rxdiscard (scc->chan);
scc->rx_len = 0;
scc->rx_err = FALSE;
}
if (delta & (scc->flags & MODE_c ? HUNT : DCD))
{
scc->dcd = (scc->flags & MODE_c)
? (!(rr0 & HUNT))
: (rr0 & DCD);
}
}
scc->rr0 = rr0;
WR0 (RES_EX_INT);
}
/*** RX Handler *************************************************************/
static void int_rx (__scc_struct *scc)
{
UBYTE rr8;
rr8 = RR8;
if (!scc->rx_err)
{
/* scc->rx_err = */ rxput (scc->chan, rr8);
scc->rx_len++;
}
}
/*** Special RX Condition ***************************************************/
static void int_sp (__scc_struct *scc)
{
UBYTE rr1;
rr1 = RR (1);
RR8;
if (rr1 & RX_OVR)
{
scc->overruns++;
scc->errors++;
scc->rx_err = TRUE;
}
if (rr1 & SDLC_EOF)
{
if ( !scc->rx_err
&& scc->rx_len >= MIN_LEN+1
&& (rr1 & (CRC_ERR | RESIDUE)) == RES8)
{
rxeof (scc->chan, 1);
}
else
if (scc->rx_len) {
rxdiscard (scc->chan);
/*if (scc->rx_len > 2) {
if (scc->rx_len < MIN_LEN+1) scc->lenerrors++;
if ((rr1 & (CRC_ERR | RESIDUE)) != RES8) scc->crcerrors++;
}*/
}
scc->rx_len = 0;
scc->rx_err = FALSE;
}
WR0 (RES_ERR);
}
/*** SCC Hardware Reset *****************************************************/
static void scc_hw_reset (void)
{
__scc_struct *scc;
UWORD delay;
for (scc = sccchans; scc < &sccchans[sccminors]; scc += 2)
{
RR0;
WR (9, HW_RES);
for (delay = 100; delay--;) SCCDELAY;
}
}
/*** SCC Detect *************************************************************/
static int scc_detect(int max)
{
__scc_struct *scc;
for (scc = sccchans; scc < sccchans+max; scc++)
{
RR0;
WR (13, 0x55); if (RR (13) != 0x55) break;
WR (13, 0xAA); if (RR (13) != 0xAA) break;
}
return((int) (scc - sccchans));
}
/* Boardtyp auswaehlen, Defaultparameter setzen */
static int scc_init(void)
{
char *str;
__scc_board *board;
__scc_struct *scc;
int chan;
for (sccboard = 0, board = sccboardtab; board->name; board++, sccboard++) {
if ((str = getenv(board->name)) != NULL) {
sccbase = board->base;
sccirq = board->irq;
sscanf(str, "%x,%u", &sccbase, &sccirq);
if (sccbase && sccirq) {
if ((sccbuf = malloc(SCC_RXSIZE*sizeof(UWORD))) == NULL)
return(0);
sccin = sccout = sccbuf;
for (chan = 0, scc = sccchans; chan < SCCCHANS; chan++, scc++) {
scc->l2port = -1;
scc->rxfhd = NULL;
scc->chan = chan;
scc->data = sccbase + board->data[chan];
scc->ctrl = scc->data + board->ctrl;
if ((scc->tx_buf = malloc(SCC_TXSIZE)) == NULL) {
while (--scc >= sccchans)
free(scc->tx_buf);
free(sccbuf);
return(0);
}
scc->tx_len = 0;
}
scc_hw_reset();
disable(); /* Interrupt aus */
allocirq(sccirq, 0, scc_int, 0); /* neuen Vektor setzen */
sccoldmask = getmask(sccirq);
maskon(sccirq);
enable();
xprintf("--- /dev/SCC (0x%03X,IRQ%u), ", sccbase, sccirq);
sccminors = scc_detect(board->chans);
if ((sccminors & 1) == 0) {
if (sccminors) {
printf("card is %s, %u channels\n", board->name, sccminors);
return(1);
} else xprintf("%s not detected\n", board->name);
} else xprintf("channel number is odd\n");
}
}
}
return(0); /* keine Karte gefunden/konfiguriert */
}
static void scc_exit(void)
{
if (scc_major) {
if (sccoldmask) maskoff(sccirq);
freeirq(sccirq);
scc_hw_reset();
}
}
static int register_scc(void)
{
MAJOR *m;
if (scc_init()) {
m = register_major();
m->name = "USCC/DSCC/OSCC";
m->istome = scc_istome;
m->exit = scc_exit;
m->handle = scc;
m->ctl = scc_ctl;
m->dcd = scc_dcd;
m->attach = scc_attach;
m->detach = scc_detach;
m->info = scc_info;
m->timer = scc_timer;
return(scc_major = num_major);
}
return(0);
}
/* End of os/go32/scc.c */