V10/sys/io/kdi.c
/*
* stream driver for DK via KMC-11/KDI board
* delicately intertwined with kmc.c
*/
#include "sys/param.h"
#include "sys/stream.h"
#include "sys/ttyio.h"
#include "sys/dkio.h"
#include "sys/conf.h"
#include "sys/dkstat.h"
#include "sys/dkmod.h"
#include "sys/ubaddr.h"
#include "sys/kmc.h"
#include "sys/kdi.h"
#include "sys/buf.h"
#include "sys/dkwindow.h"
#define KDIPRI 28
/*
* flags
*/
#define KMBUSY 0x01
#define KMSTOP 0x02
#define KMINC 0x04
#define KMBIG 0x3c /* rcvd lots last time, use big rcv buffer */
#define KMXCL 0x40 /* exclusive use */
#define KMXBIG 0x80 /* tell KMC to use large messages */
#define BIGSET(c) ((((c)<<1)|KMINC)&KMBIG | ((c)&~KMBIG))
#define BIGCLR(c) (((c)<<1)&KMBIG | ((c)&~KMBIG))
/*
* device structure
*/
struct device {
char sts;
char x1;
union {
struct {
u_short lo;
u_char hi;
} a;
struct {
u_char x2;
u_char x3;
u_char ch;
u_char ct;
u_char sh;
u_char st;
} q;
} u;
};
/*
* KMC commands
*/
#define KC_INIT 1
#define KC_SEND 2
#define KC_RCVB 3
#define KC_CLOSE 4
#define KC_XINIT 5
#define KC_CMD 6
#define KC_FLAG 7
#define KC_SOI 8
/*
* subcommands of KC_CMD or KC_SEND
*/
#define OFLUSH 02 /* flush output */
#define OSPND 04 /* suspend output */
#define ORSME 010 /* resume output */
#define OBOTM 0200 /* send BOTM trailer, not BOT */
/*
* KMC reports
*/
#define KS_SEND 024
#define KS_RDB 025
#define KS_EOI 026
#define KS_CNTL 027
#define KS_ERR 030
/*
* KC_RCV modes
*/
#define CBLOCK 0040 /* return on block boundary */
#define CTIME 0100 /* return when time expires */
/*
* KS_RDB mode
*/
#define SFULL 0001 /* buffer full */
#define SCNTL 0002 /* cntl char recv */
#define SABORT 0010 /* rcv aborted */
#define SBLOCK 0040 /* block boundary */
#define STIME 0100 /* time limit expired */
/*
* URP control characters
*/
#define D_DELAY 0100
#define D_BREAK 0110
/*
* KMC errors
*/
#define E_NOQB 4 /* internal buffer runout */
#define E_DUP 5 /* duplicate send (shouldn't but does happen) */
#define E_UMETA 7 /* unknown control character */
/*
* tracing
*/
#define DEBUG
#ifdef DEBUG
struct kin ktrbuf[256];
struct kin *ktrp = ktrbuf;
#define TRACE(x) *ktrp++ = x; if (ktrp >= &ktrbuf[256]) ktrp = ktrbuf;
#else
#define TRACE(x) ;
#endif
int kdiclose(), kdiput(), kdiisrv();
long kdiopen();
struct qinit kdirinit = { noput, kdiisrv, kdiopen, kdiclose, 0, 0 };
struct qinit kdiwinit = { kdiput, NULL, kdiopen, kdiclose, 512, 128 };
struct streamtab kdiinfo = { &kdirinit, &kdiwinit };
struct cdevsw kdicdev = cstrinit(&kdiinfo);
struct kmaddr kmcubaddr();
extern struct kdikmc kdikmc[];
extern struct kmcdk k[];
extern int kdicnt;
extern struct kmc kmc[];
extern struct ubaddr kmcaddr[];
extern int kmccnt;
struct dkstat dkstat;
#define KDICHAN 96 /* channels per kdi (== per kmc) */
#define KMC(d) ((d)/KDICHAN)
#define CHAN(d) ((d)%KDICHAN)
/*
* open channel
*/
long
kdiopen(q, dev)
register struct queue *q;
dev_t dev;
{
register struct kdi *kp;
register struct block *bp;
register struct kdikmc *xp;
register int kno, chan;
kno = KMC(minor(dev));
chan = CHAN(minor(dev));
if (kno >= kdicnt || kno >= kmccnt || chan >= KDICHAN)
return (0);
xp = &kdikmc[kno];
if (xp->bad)
return (0);
if (xp->init == 0)
if (kdiinit(dev) == 0)
return (0);
kp = &xp->kdi[chan];
if (xp->state[chan] != DKCLOSED) {
if (kp->ostate&KMXCL) /* exclusive use until reset */
return(0);
if (xp->state[chan] != DKOPEN)
return(0); /* closing channels cannot reopen */
return(1);
}
kp->dkrq = q;
q->ptr = (caddr_t)kp;
WR(q)->flag |= QDELIM|QBIGB;
q->flag |= QDELIM;
WR(q)->ptr = (caddr_t)kp;
xp->state[chan] = DKOPEN;
kp->ostate = KMXCL;
kcmd(kno, KC_INIT, kp->chan, 0, 0, 0, (uaddr_t)NULL);
if ((bp = allocb(64)) == NULL) {
xp->state[chan] = 0;
return(0);
}
kp->lastctl = 0;
kp->ibp = bp;
kp->imap = ubmblk(xp->ubno, bp, 0);
kcmd(kno, KC_RCVB, chan, bp->lim-bp->wptr, 50,
CBLOCK|CTIME, ubadwptr(xp->ubno, bp, kp->imap));
return(1);
}
kdiclose(q)
register struct queue *q;
{
register struct kdi *kp = (struct kdi *)q->ptr;
register s, i;
register struct kdikmc *xp;
xp = &kdikmc[kp->kno];
if (xp->modp == NULL || xp->modp->listnrq == NULL
|| xp->state[kp->chan]==DKRCLOSE)
xp->state[kp->chan] = DKCLOSED;
else if (xp->state[kp->chan] == DKOPEN) {
xp->state[kp->chan] = DKLCLOSE;
for (i=0; i<30 && (WR(q)->count || kp->obp); i++)
tsleep((caddr_t)kp, KDIPRI, 1);
}
kp->dkrq = NULL;
if (xp->modp && xp->modp->listnrq)
putctl2(RD(xp->modp->listnrq), M_PRICTL, DKMCLOSE, kp->chan);
s = spl6();
if (kp->obp) {
freeb(kp->obp);
kp->obp = NULL;
}
splx(s);
kp->ostate &= ~KMXCL;
kcmd(kp->kno, KC_CLOSE, kp->chan, 0, 0, 0, (uaddr_t)NULL);
}
kdiput(q, bp)
register struct queue *q;
register struct block *bp;
{
register struct kdi *kdp = (struct kdi *)q->ptr;
register char *sp;
register s;
register struct kdikmc *xp;
struct block *xbp;
int t;
xp = &kdikmc[kdp->kno];
switch(bp->type) {
case M_CTL:
case M_DATA:
case M_DELAY:
case M_BREAK:
if (xp->state[kdp->chan] < DKLCLOSE) {
freeb(bp);
return;
}
putq(q, bp);
if ((kdp->ostate&KMBUSY)==0)
kmstart(kdp);
return;
case M_PRICTL:
switch (*bp->rptr) {
case DKMCLOSE:
s = bp->rptr[1];
if (s < KDICHAN) {
flushq(q, 1);
if (xp->state[s] == DKOPEN) {
xp->state[s] = DKRCLOSE;
putctl(xp->kdi[s].dkrq->next, M_HANGUP);
} else if (xp->state[s] == DKLCLOSE)
xp->state[s] = DKCLOSED;
kcmd(kdp->kno, KC_CLOSE, s, 0, 0, 0, (uaddr_t)NULL);
}
break;
case DKMXINIT:
/* retrieve channel number */
bp->rptr++;
s = *bp->rptr++;
/* forget it if this isn't a valid channel */
if (s >= KDICHAN || xp->state[s] != DKOPEN)
break;
/* next two bytes (if there) are the traffic type */
if(bp->wptr-bp->rptr>=2){
t = (*bp->rptr++<<8) & 0xff00;
t |= *bp->rptr++ & 0xff;
/* check window sizes */
if (W_VALID(t)) {
/* kmc has only two possible window sizes */
t = W_VALUE(W_ORIG(t));
if(t<1024)
xp->kdi[s].ostate &= ~KMXBIG;
else
xp->kdi[s].ostate |= KMXBIG;
}
}
/* restart urp on the channel, with the correct window */
kdixinit(&xp->kdi[s]);
break;
default:
break;
}
break; /* and freeb */
case M_IOCTL:
sp = stiodata(bp);
bp->type = M_IOCACK;
switch (stiocom(bp)) {
case TIOCSDEV:
if (((struct ttydevb *)sp)->ispeed == 0)
putctl2(q, M_PRICTL, DKMCLOSE, kdp->chan);
case KIOCISURP:
bp->wptr = bp->rptr;
break;
case DIOCSCTL:
bp->wptr = bp->rptr;
if (*sp == 0)
break;
if ((xbp = allocb(1)) == NULL)
bp->type = M_IOCNAK;
else {
xbp->type = M_CTL;
*xbp->wptr++ = *sp;
putq(q, xbp);
if ((kdp->ostate&KMBUSY) == 0)
kmstart(kdp);
}
break;
case DIOCRCTL:
*sp = kdp->lastctl;
kdp->lastctl = 0;
bp->wptr = (unsigned char *)sp + 1;
break;
case DIOCNXCL:
kdp->ostate &= ~KMXCL;
bp->wptr = bp->rptr;
break;
case TIOCGDEV:
((struct ttydevb *)sp)->ispeed =
((struct ttydevb *)sp)->ospeed = B9600;
break;
case KIOCINIT:
kdixinit(kdp);
bp->rptr = bp->wptr;
break;
case KIOCSHUT:
if (kdp->chan > 2) { /* hackish security attempt */
bp->type = M_IOCNAK;
break;
}
kdireset(kdp->kno);
bp->rptr = bp->wptr;
break;
case DIOCSTREAM:
RD(q)->flag &= ~QDELIM;
bp->rptr = bp->wptr;
break;
case DIOCRECORD:
RD(q)->flag |= QDELIM;
bp->rptr = bp->wptr;
break;
case DIOCXWIN: {
unsigned int wins;
/* KMC does big (756) or small (84) windows currently */
wins = (sp[0] + (sp[1]<<8))*sp[4];
kdp->ostate &= ~KMXBIG;
if (wins>=756)
kdp->ostate |= KMXBIG;
/* will take effect next XINIT */
bp->rptr = bp->wptr;
break;
}
default:
bp->wptr = bp->rptr;
bp->type = M_IOCNAK;
break;
}
qreply(q, bp);
return;
case M_STOP:
s = OSPND;
kdp->ostate |= KMSTOP;
goto dontcmd;
case M_FLUSH:
flushq(q, 0);
s = spl6();
if (kdp->obp) {
freeb(kdp->obp); /* this cannot be right -- too early */
kdp->obp = 0;
}
kdp->ostate &= ~(KMSTOP|KMBUSY);
splx(s);
s = OFLUSH|ORSME;
goto docmd;
case M_START:
case M_HANGUP:
s = ORSME;
kdp->ostate &= ~KMSTOP;
goto dontcmd;
docmd:
kcmd(kdp->kno, KC_CMD, kdp->chan, 0, s, 0, (uaddr_t)NULL);
dontcmd:
kmstart(kdp);
break;
default: /* not handled; just toss */
break;
}
freeb(bp);
}
/*
* restart URP on some channel:
* send XINIT to KMC;
* discard any pending transmit block
*
* should pending block really be discarded, or requeued?
* former seems to match real cases
*/
kdixinit(kp)
register struct kdi *kp;
{
register int s;
s = spl6();
kcmd(kp->kno, KC_XINIT, kp->chan, 0, 0, (kp->ostate&KMXBIG)!=0, (uaddr_t)NULL);
if (kp->obp) {
freeb(kp->obp);
kp->obp = NULL;
}
if (kp->omap) {
ubmfree(kdikmc[kp->kno].ubno, kp->omap);
kp->omap = 0;
}
kp->ostate &=~ (KMSTOP|KMBUSY);
splx(s);
}
kdiisrv(q)
register struct queue *q;
{
register struct kdi *kp = (struct kdi *)q->ptr;
register int ubno;
register struct block *bp;
if ((q->next->flag&QFULL)==0 && kp->ibp==NULL) {
bp = allocb(kp->ostate&KMBIG? 1024:64);
if (bp == NULL)
panic("kdi: can't alloc");
kp->ibp = bp;
ubno = kdikmc[kp->kno].ubno;
kp->imap = ubmblk(ubno, bp, 0);
kcmd(kp->kno, KC_RCVB, kp->chan, bp->lim - bp->wptr,
50, CBLOCK|CTIME, ubadwptr(ubno, bp, kp->imap));
}
}
kmstart(kdp)
register struct kdi *kdp;
{
register s = spl5();
register struct block *bp;
register struct queue *qp;
register c;
register struct kdikmc *xp;
if (kdp->ostate&(KMBUSY|KMSTOP)) {
splx(s);
return;
}
if ((qp = kdp->dkrq)==NULL || (qp = WR(qp))==NULL) {
splx(s);
return;
}
xp = &kdikmc[kdp->kno];
if (bp = getq(qp)) switch (bp->type) {
case M_DATA:
c = OBOTM;
if (bp->class&S_DELIM)
c = 0;
kdp->omap = ubmblk(xp->ubno, bp, 0);
kcmd(kdp->kno, KC_SEND, kdp->chan, bp->wptr-bp->rptr,
0, c, ubadrptr(xp->ubno, bp, kdp->omap));
kdp->ostate |= KMBUSY;
dkstat.output += bp->wptr - bp->rptr;
kdp->obp = bp;
break;
case M_DELAY:
c = D_DELAY;
while (*bp->rptr>0) {
c++;
*bp->rptr >>= 1;
}
kdp->omap = ubmblk(xp->ubno, bp, 0);
kcmd(kdp->kno, KC_SEND, kdp->chan, 1, c, 0, ubadrptr(xp->ubno, bp, kdp->omap));
kdp->ostate |= KMBUSY;
kdp->obp = bp;
break;
case M_CTL:
kdp->omap = ubmblk(xp->ubno, bp, 0);
kcmd(kdp->kno, KC_SEND, kdp->chan, 0, *bp->rptr, 0, ubadrptr(xp->ubno, bp, kdp->omap));
kdp->ostate |= KMBUSY;
kdp->obp = bp;
break;
case M_BREAK:
kdp->omap = ubmblk(xp->ubno, bp, 0);
kcmd(kdp->kno, KC_SEND, kdp->chan, 0, D_BREAK, 0, ubadrptr(xp->ubno, bp, kdp->omap));
kdp->ostate |= KMBUSY;
kdp->obp = bp;
break;
default:
printf("mesg %o in kdi\n", bp->type);
freeb(bp);
break;
}
splx(s);
}
int kcmdovern[10];
long kcmdovert[10];
kcmd(kno, type, chan, len, ctl, mode, addr)
uaddr_t addr;
{
register i;
register struct device *dp = kmc[kno].k_addr;
register struct kin *kp;
register s;
static struct kmaddr nulladr = {0, 0};
static serno;
register long loop;
/*
* if the kmc is dead, don't ask it to do things
*/
if(dp->sts ==3)
return;
if (dp->u.q.x3)
dp->u.q.x3 = 0;
busywait:
/*
* wait at low priority till there's a free command. No
* sense slowing all processes down.
*/
if(((dp->u.q.ch+1)%NKMB)==dp->u.q.ct) {
loop = 0;
while (((dp->u.q.ch+1)%NKMB)==dp->u.q.ct) {
if (loop == 1000)
printf("waiting for kdi%d\n", kno);
if(loop++>1000000) {
printf("kmc%d died, restart\n", kno);
kdireset(kno);
return;
}
}
kcmdovern[kno]++;
if(kcmdovert[kno]<loop)
kcmdovert[kno] = loop;
}
s = spl5();
/*
* someone else might have gotten in at low priority. check
* again.
*/
if(((dp->u.q.ch+1)%NKMB)==dp->u.q.ct) {
splx(s);
goto busywait;
}
i = dp->u.q.ch;
if (i >= NKMB)
panic("kcmd");
kp = &k[kno].cmd[i];
kp->type = type;
kp->serno = ++serno;
kp->chan = chan;
kp->len = len;
kp->ctl = ctl;
kp->mode = mode;
kp->addr.hi = addr>>16;
kp->addr.lo = addr;
i++;
if (i >= NKMB)
i = 0;
dp->u.q.ch = i;
TRACE(*kp);
splx(s);
}
u_char kdiseqno[10];
/*
* Interrupt routine-- unload status buffer:
* release write blocks, collect input
*/
kdiintr(kno)
{
register struct device *dp = kmc[kno].k_addr;
register struct kin *sp;
register struct kdi *kp;
register struct block *bp;
register c;
register struct kdikmc *xp;
xp = &kdikmc[kno];
if (dp->u.q.x3)
dp->u.q.x3 = 0;
for ( ; (c = dp->u.q.st) != dp->u.q.sh; dp->u.q.st = c) {
if (c >= NKMB) {
printf("kdi stat buf is %d\n", c);
panic("kdiintr");
}
sp = &k[kno].stat[c];
TRACE(*sp);
c++;
if (c >= NKMB)
c = 0;
if (sp->chan >= KDICHAN) {
printf("kdi%d stat chan is 0%o\n", kno, sp->chan);
printf("type: %o len: 0%o mode: 0%o\n");
continue;
}
if(sp->serno!=kdiseqno[kno]){
printf("kdi%d stat chan is 0%o\n", kno, sp->chan);
printf("seqno is %d, should be %d\n",sp->serno,
kdiseqno[kno]);
}
kdiseqno[kno]=(sp->serno+1)%256;
kp = &xp->kdi[sp->chan];
switch (sp->type) {
case KS_SEND:
if (kp->obp) {
freeb(kp->obp);
kp->obp = NULL;
}
kp->ostate &= ~KMBUSY;
if (kp->omap) {
ubmfree(xp->ubno, kp->omap);
kp->omap = 0;
}
if (kp->dkrq && WR(kp->dkrq)->first)
kmstart(kp);
break;
case KS_RDB:
if (kp->imap) {
ubmfree(xp->ubno, kp->imap);
kp->imap = 0;
}
bp = kp->ibp;
kp->ibp = NULL;
if (kp->dkrq==NULL || xp->state[kp->chan]!=DKOPEN) {
if (bp)
freeb(bp);
break;
}
if (sp->mode & SABORT) {
printf("kdi%d rcv abort chan %d mode %o bp %x\n",
kno, kp->chan, sp->mode, bp);
if (bp)
kp->ibp = bp;
kp->rsize = 0;
kp->ostate &= ~KMBIG;
break;
}
if (bp == NULL) {
printf("kdi%d: no ibp\n", kno);
break;
}
/* special hacks */
if (sp->mode&020
|| sp->mode==0100 && bp->wptr+sp->len==bp->lim) {
/*
* Something no one understands. Print out
* some info so that we might figure out
* why this freeb is in the code.
*/
printf("kdi%d rcv chan %d mode %o bp %x len %d\n",
kno, kp->chan, sp->mode, bp, sp->len);
freeb(bp);
} else {
bp->wptr = bp->lim - sp->len;
kp->rsize += bp->wptr - bp->rptr;
dkstat.input += bp->wptr - bp->rptr;
if(sp->mode&(SBLOCK|STIME)) {
bp->class |= S_DELIM;
if (kp->rsize >= 512)
kp->ostate = BIGSET(kp->ostate);
else
kp->ostate = BIGCLR(kp->ostate);
kp->rsize = 0;
}
(*kp->dkrq->next->qinfo->putp)
(kp->dkrq->next, bp);
kp->ibp = NULL;
if (sp->mode&SCNTL) {
switch (sp->ctl) {
case D_BREAK:
putctl(kp->dkrq->next, M_BREAK);
break;
default:
kp->lastctl = sp->ctl;
putctl1(kp->dkrq->next, M_CTL, sp->ctl);
break;
}
}
}
if ((kp->dkrq->next->flag&QFULL) == 0) {
bp = allocb(kp->ostate&KMBIG? 1024:64);
if (bp == NULL)
panic("kdi: can't alloc");
kp->ibp = bp;
kp->imap = ubmblk(xp->ubno, bp, 0);
kcmd(kno, KC_RCVB, kp->chan, bp->lim - bp->wptr,
50, CBLOCK|CTIME, ubadwptr(xp->ubno, bp, kp->imap));
}
break;
case KS_EOI:
printf("kdi%d chan %d rcv EOI %x\n", kno, sp->chan, sp->len);
break;
case KS_CNTL:
/*
printf("kdi%d chan %d rcv ctl %x\n", kno, sp->chan, sp->len);
*/
break;
case KS_ERR:
printf("kdi%d chan %d error %d\n", kno, sp->chan, sp->len);
if (sp->len==E_NOQB || sp->len==E_UMETA
|| sp->len==E_DUP) {
printf("ignored\n");
break;
}
kdireset(kno);
break;
}
}
}
/*
* init things, including data structures
* -- assume KDICHAN struct kdis and KDICHAN chars for state
* will fit in a buffer. when BSIZE is 4096, it's true.
*/
int kdireload();
kdiinit(dev)
int dev;
{
register struct device *kp;
register i;
register struct kdikmc *xp;
register int kno;
struct kmaddr ka;
extern time_t time;
kno = KMC(minor(dev));
/* close all open channels? */
if ((kp = kmc[kno].k_addr) == NULL)
return (0);
xp = &kdikmc[kno];
if (xp->badtime+30 > time) /* UGH */
return(0);
kp->sts = 0; /* initialize KMC */
if (xp->buf == NULL) {
if ((xp->buf = geteblk()) == NULL)
return (0);
clrbuf(xp->buf);
xp->kdi = (struct kdi *)xp->buf->b_un.b_addr;
xp->state = (char *)&xp->kdi[KDICHAN];
for (i = 0; i < KDICHAN; i++) {
xp->kdi[i].chan = i;
xp->kdi[i].kno = kno;
}
}
if ((xp->modp = dkmodall(dev, kno*KDICHAN, (kno+1)*KDICHAN)) == 0)
return (0);
xp->modp->dkstate = xp->state;
xp->ubno = kmcaddr[kno].ubno;
if (xp->umap == 0) {
if ((xp->umap = ubmalloc(xp->ubno, sizeof(struct kmcdk), 0)) == 0) {
printf("kdi%d: no map\n", kno);
return (0);
}
}
xp->ubad = ubmaddr(xp->ubno, (caddr_t)&k[kno], sizeof(struct kmcdk), xp->umap);
k[kno].kinit.cmdaddr = kmcubaddr((caddr_t)k[kno].cmd, (caddr_t)&k[kno], xp->ubad);
k[kno].kinit.stataddr = kmcubaddr((caddr_t)k[kno].stat, (caddr_t)&k[kno], xp->ubad);
k[kno].kinit.bufaddr = kmcubaddr((caddr_t)k[kno].kmcbuf, (caddr_t)&k[kno], xp->ubad);
ka = kmcubaddr((caddr_t)&k[kno].kinit, (caddr_t)&k[kno], xp->ubad);
kp->u.a.lo = ka.lo;
kp->u.a.hi = ka.hi;
xp->badtime = time;
kp->sts = 1; /* start handshake */
for (i = 0; i<100000; i++) {
if (kp->sts == 2) {
kmc[kno].k_rint = kdiintr;
kmc[kno].k_xint = kdiintr;
kmc[kno].k_reset = kdireload;
xp->init = 1;
return(1); /* KMC is running OK */
}
if (kp->sts == 3) {
printf("kdi%d: entered state 3\n", kno);
return(0);
}
}
printf("kdi%d: kmc not ready\n", kno);
return(0);
}
struct kmaddr
kmcubaddr(memaddr, membase, ubbase)
caddr_t memaddr, membase;
uaddr_t ubbase;
{
register struct kmaddr r;
register long a;
a = (long)memaddr - (long)membase + (long)ubbase;
r.hi = a >> 16;
r.lo = (u_short)a;
return(r);
}
/*
* kmc reload
*/
kdireload(kno)
{
register struct kdikmc *xp;
xp = &kdikmc[kno];
xp->init = 0;
xp->modp = 0;
xp->bad = 0;
}
/*
* zap all open channels
*/
kdireset(kno)
{
register struct kdi *kp;
register struct kdikmc *xp;
register struct device *dp;
xp = &kdikmc[kno];
if (xp->modp==0)
return;
if ((dp = kmc[kno].k_addr) != NULL)
dp->sts = 3; /* disable kdi kmc code */
for (kp = &xp->kdi[KDICHAN-1]; kp >= xp->kdi; --kp) {
register state = xp->state[kp->chan];
if ((state==DKOPEN || state==DKLCLOSE) && kp->dkrq) {
flushq(WR(kp->dkrq), 1);
xp->state[kp->chan] = state==DKOPEN? DKRCLOSE: DKCLOSED;
putctl(kp->dkrq->next, M_HANGUP);
}
if (kp->imap)
ubmfree(xp->ubno, kp->imap);
if (kp->omap)
ubmfree(xp->ubno, kp->omap);
kp->imap = kp->omap = 0;
}
xp->bad = 1;
}