plan 9 kernel history: overview | file list | diff list

1991/0601/port/devscc.c (diff list | history)

port/devscc.c on 1991/0601
1991/0601    
#include	"u.h" 
#include	"lib.h" 
#include	"mem.h" 
#include	"dat.h" 
#include	"fns.h" 
#include	"io.h" 
#include	"errno.h" 
 
#include	"devtab.h" 
 
/* 
 *  Driver for the Z8530. 
 */ 
enum 
{ 
	/* wr 0 */ 
	ResExtPend=	2<<3, 
	ResTxPend=	5<<3, 
	ResErr=		6<<3, 
 
	/* wr 1 */ 
	ExtIntEna=	1<<0, 
	TxIntEna=	1<<1, 
	RxIntDis=	0<<3, 
	RxIntFirstEna=	1<<3, 
	RxIntAllEna=	2<<3, 
 
	/* wr 3 */ 
	RxEna=		1, 
	Rx5bits=	0<<6, 
	Rx7bits=	1<<6, 
	Rx6bits=	2<<6, 
	Rx8bits=	3<<6, 
 
	/* wr 4 */ 
	SyncMode=	0<<2, 
	Rx1stop=	1<<2, 
	Rx1hstop=	2<<2, 
	Rx2stop=	3<<2, 
	X16=		1<<6, 
 
	/* wr 5 */ 
	TxRTS=		1<<1, 
	TxEna=		1<<3, 
	TxBreak=	1<<4, 
	TxDTR=		1<<7, 
	Tx5bits=	0<<5, 
	Tx7bits=	1<<5, 
	Tx6bits=	2<<5, 
	Tx8bits=	3<<5, 
 
	/* wr 9 */ 
	IntEna=		1<<3, 
	ResetB=		1<<6, 
	ResetA=		2<<6, 
	HardReset=	3<<6, 
 
	/* wr 11 */ 
	TRxCOutBR=	2, 
	TxClockBR=	2<<3, 
	RxClockBR=	2<<5, 
	TRxCOI=		1<<2, 
 
	/* wr 14 */ 
	BREna=		1, 
	BRSource=	2, 
 
	/* rr 0 */ 
	RxReady=	1, 
	TxReady=	1<<2, 
	RxDCD=		1<<3, 
	RxCTS=		1<<5, 
	RxBreak=	1<<7, 
 
	/* rr 3 */ 
	ExtPendB=	1,	 
	TxPendB=	1<<1, 
	RxPendB=	1<<2, 
	ExtPendA=	1<<3,	 
	TxPendA=	1<<4, 
	RxPendA=	1<<5, 
}; 
 
typedef struct SCC	SCC; 
struct SCC 
{ 
	QLock; 
	ushort	sticky[16];	/* sticky write register values */ 
	uchar	*ptr;		/* command/pointer register in Z8530 */ 
	uchar	*data;		/* data register in Z8530 */ 
	int	printing;	/* true if printing */ 
 
	/* console interface */ 
	int	nostream;	/* can't use the stream interface */ 
	IOQ	*iq;		/* input character queue */ 
	IOQ	*oq;		/* output character queue */ 
 
	/* stream interface */ 
	Queue	*wq;		/* write queue */ 
	Rendez	r;		/* kproc waiting for input */ 
	Alarm	*a;		/* alarm for waking the kernel process */ 
	int	delay;		/* between character input and waking kproc */ 
 	int	kstarted;	/* kproc started */ 
	uchar	delim[256];	/* characters that act as delimiters */ 
}; 
SCC	scc[2]; 
 
void 
onepointseven(void) 
{ 
	int i; 
	for(i = 0; i < 20; i++) 
		; 
} 
 
/* 
 *  Access registers using the pointer in register 0. 
 *  This is a bit stupid when accessing register 0. 
 */ 
void 
sccwrreg(SCC *sp, int addr, int value) 
{ 
	onepointseven(); 
	*sp->ptr = addr; 
	wbflush(); 
	onepointseven(); 
	*sp->ptr = sp->sticky[addr] | value; 
	wbflush(); 
} 
ushort 
sccrdreg(SCC *sp, int addr) 
{ 
	onepointseven(); 
	*sp->ptr = addr; 
	wbflush(); 
	onepointseven(); 
	return *sp->ptr; 
} 
 
/* 
 *  set the baud rate by calculating and setting the baudrate 
 *  generator constant.  This will work with fairly non-standard 
 *  baud rates. 
 */ 
void 
sccsetbaud(SCC *sp, int rate) 
{ 
	int brconst; 
 
	brconst = (SCCFREQ+16*rate-1)/(2*16*rate) - 2; 
 
	sccwrreg(sp, 12, brconst & 0xff); 
	sccwrreg(sp, 13, (brconst>>8) & 0xff); 
} 
 
/* 
 *  toggle DTR 
 */ 
void 
sccdtr(SCC *sp, int n) 
{ 
	if(n) 
		sp->sticky[5] |= TxDTR; 
	else 
		sp->sticky[5] &=~TxDTR; 
	sccwrreg(sp, 5, 0); 
} 
 
/* 
 *  toggle RTS 
 */ 
void 
sccrts(SCC *sp, int n) 
{ 
	if(n) 
		sp->sticky[5] |= TxRTS; 
	else 
		sp->sticky[5] &=~TxRTS; 
	sccwrreg(sp, 5, 0); 
} 
 
/* 
 *  send break 
 */ 
void 
sccbreak(SCC *sp, int ms) 
{ 
	sp->sticky[1] &=~TxIntEna; 
	sccwrreg(sp, 1, 0); 
	sccwrreg(sp, 5, TxBreak|TxEna); 
	tsleep(&u->p->sleep, return0, 0, ms); 
	sccwrreg(sp, 5, 0); 
	if(sp->oq){ 
		sp->sticky[1] |= TxIntEna; 
		sccwrreg(sp, 1, 0); 
	} 
} 
 
/* 
 *  9600 baud, 1 stop bit, 8 bit chars, no interrupts, 
 *  transmit and receive enabled, interrupts disabled. 
 */ 
void 
sccsetup(void) 
{ 
	SCC *sp; 
	static int already; 
 
	if(already) 
		return; 
	already = 1; 
 
	/* 
	 *  get port addresses 
	 */ 
	scc[0].ptr = &SCCADDR->ptra; 
	scc[0].data = &SCCADDR->dataa; 
	scc[1].ptr = &SCCADDR->ptrb; 
	scc[1].data = &SCCADDR->datab; 
 
	for(sp = scc; sp < &scc[2]; sp++){ 
		memset(sp->sticky, 0, sizeof(sp->sticky)); 
 
		/* 
		 *  turn on baud rate generator and set rate to 9600 baud. 
		 *  use 1 stop bit. 
		 */ 
		sp->sticky[14] = BRSource; 
		sccwrreg(sp, 14, 0); 
		sccsetbaud(sp, 9600); 
		sp->sticky[4] = Rx1stop | X16; 
		sccwrreg(sp, 4, 0); 
		sp->sticky[11] = TxClockBR | RxClockBR | TRxCOutBR | TRxCOI; 
		sccwrreg(sp, 11, 0); 
		sp->sticky[14] = BREna | BRSource; 
		sccwrreg(sp, 14, 0); 
 
		/* 
		 *  enable I/O, 8 bits/character 
		 */ 
		sp->sticky[3] = RxEna | Rx8bits; 
		sccwrreg(sp, 3, 0); 
		sp->sticky[5] = TxEna | Tx8bits; 
		sccwrreg(sp, 5, 0); 
	} 
} 
 
/* 
 *  Queue n characters for output; if queue is full, we lose characters. 
 *  Get the output going if it isn't already. 
 */ 
void 
sccputs(IOQ *cq, char *s, int n) 
{ 
	SCC *sp = cq->ptr; 
	int ch, x; 
 
	x = splhi(); 
	lock(cq); 
	puts(cq, s, n); 
	if(sp->printing == 0){ 
		ch = getc(cq); 
		/*kprint("<start %2.2ux>", ch);*/ 
		if(ch >= 0){ 
			sp->printing = 1; 
			while((*sp->ptr&TxReady)==0) 
				; 
			*sp->data = ch; 
		} 
	} 
	unlock(cq); 
	splx(x); 
} 
 
/* 
 *  an scc interrupt (a damn lot of work for one character) 
 */ 
void 
sccintr0(SCC *sp, uchar x) 
{ 
	int ch; 
	IOQ *cq; 
 
	if(x & ExtPendB){ 
		/*kprint("scc %d: %2.2ux\n", sp-scc, *sp->ptr);*/ 
		sccwrreg(sp, 0, ResExtPend); 
	} 
	if(x & RxPendB){ 
		cq = sp->iq; 
		while(*sp->ptr&RxReady){ 
			onepointseven(); 
			ch = *sp->data; 
			if(cq->putc) 
				(*cq->putc)(cq, ch); 
			else { 
				putc(cq, ch); 
				if(sp->delim[ch]) 
					wakeup(&cq->r); 
			} 
		} 
	} 
	if(x & TxPendB){ 
		cq = sp->oq; 
		lock(cq); 
		ch = getc(cq); 
		/*kprint("<%2.2ux>", ch);*/ 
		onepointseven(); 
		if(ch < 0){ 
			/*kprint("<done>", ch);*/ 
			sccwrreg(sp, 0, ResTxPend); 
			sp->printing = 0; 
			wakeup(&cq->r); 
		}else 
			*sp->data = ch; 
		unlock(cq); 
	} 
} 
void 
sccintr(void) 
{ 
	uchar x; 
 
	x = sccrdreg(&scc[0], 3); 
	sccintr0(&scc[1], x); 
	sccintr0(&scc[0], x>>3); 
} 
 
/* 
 *  turn on a port's interrupts.  set DTR and RTS 
 */ 
void 
sccdevice(SCC *sp) 
{ 
	/* 
	 *  turn on both ports 
	 */ 
	sccsetup(); 
 
	/* 
	 *  set up i/o routines 
	 */ 
	if(sp->oq){ 
		sp->oq->puts = sccputs; 
		sp->oq->ptr = sp; 
		sp->sticky[1] |= TxIntEna | ExtIntEna; 
	} 
	if(sp->iq){ 
		sp->iq->ptr = sp; 
		sp->sticky[1] |= RxIntAllEna | ExtIntEna; 
	} 
 
	/* 
 	 *  turn on interrupts 
	 */ 
	sccwrreg(sp, 1, 0); 
	sp->sticky[9] |= IntEna; 
	sccwrreg(sp, 9, 0); 
 
	/* 
	 *  turn on DTR and RTS 
	 */ 
	sccdtr(sp, 1); 
	sccrts(sp, 1); 
} 
 
/* 
 *  set up an scc port as something other than a stream 
 */ 
void 
sccspecial(int port, IOQ *oq, IOQ *iq, int baud) 
{ 
	SCC *sp = &scc[port]; 
 
	sp->nostream = 1; 
	sp->oq = oq; 
	sp->iq = iq; 
	sccdevice(sp); 
	sccsetbaud(sp, baud); 
} 
 
static void	scctimer(Alarm*); 
static int	sccputc(IOQ *, int); 
static void	sccstopen(Queue*, Stream*); 
static void	sccstclose(Queue*); 
static void	sccoput(Queue*, Block*); 
static void	scckproc(void *); 
Qinfo sccinfo = 
{ 
	nullput, 
	sccoput, 
	sccstopen, 
	sccstclose, 
	"scc" 
}; 
 
/* 
 *  create a helper process per port 
 */ 
static void 
scctimer(Alarm *a) 
{ 
	SCC *sp = a->arg; 
 
	cancel(a); 
	sp->a = 0; 
	wakeup(&sp->iq->r); 
} 
 
static int 
sccputc(IOQ *cq, int ch) 
{ 
	SCC *sp = cq->ptr; int r; 
 
	r = putc(cq, ch); 
 
	/* 
	 *  pass upstream within sp->delay milliseconds 
	 */ 
	if(sp->a==0){ 
		if(sp->delay == 0) 
			wakeup(&cq->r); 
		else 
			sp->a = alarm(sp->delay, scctimer, sp); 
	} 
	return r; 
} 
 
static void 
sccstopen(Queue *q, Stream *s) 
{ 
	SCC *sp; 
	char name[NAMELEN]; 
 
	kprint("sccstopen: q=0x%ux, inuse=%d, type=%d, dev=%d, id=%d\n", 
		q, s->inuse, s->type, s->dev, s->id); 
	sp = &scc[s->id]; 
	qlock(sp); 
	sp->wq = WR(q); 
	WR(q)->ptr = sp; 
	RD(q)->ptr = sp; 
	sp->delay = 64; 
	sp->iq->putc = sccputc; 
	qunlock(sp); 
 
	/* start with all characters as delimiters */ 
	memset(sp->delim, 1, sizeof(sp->delim)); 
	 
	if(sp->kstarted == 0){ 
		sp->kstarted = 1; 
		sprint(name, "scc%d", s->id); 
		kproc(name, scckproc, sp); 
	} 
} 
 
static void 
sccstclose(Queue *q) 
{ 
	SCC *sp = q->ptr; 
 
	qlock(sp); 
	kprint("sccstclose: q=0x%ux, id=%d\n", q, sp-scc); 
	sp->wq = 0; 
	sp->iq->putc = 0; 
	WR(q)->ptr = 0; 
	RD(q)->ptr = 0; 
	qunlock(sp); 
} 
 
static void 
sccoput(Queue *q, Block *bp) 
{ 
	SCC *sp = q->ptr; 
	IOQ *cq; 
	int n, m; 
 
	if(sp == 0){ 
		freeb(bp); 
		return; 
	} 
	cq = sp->oq; 
	if(waserror()){ 
		freeb(bp); 
		nexterror(); 
	} 
	if(bp->type == M_CTL){ 
		while (cangetc(cq))	/* let output drain */ 
			sleep(&cq->r, cangetc, cq); 
		n = strtoul((char *)(bp->rptr+1), 0, 0); 
		switch(*bp->rptr){ 
		case 'B': 
		case 'b': 
			sccsetbaud(sp, n); 
			break; 
		case 'D': 
		case 'd': 
			sccdtr(sp, n); 
			break; 
		case 'K': 
		case 'k': 
			sccbreak(sp, n); 
			break; 
		case 'R': 
		case 'r': 
			sccrts(sp, n); 
			break; 
		case 'W': 
		case 'w': 
			if(n>=0 && n<1000) 
				sp->delay = n; 
			break; 
		} 
	}else while((m = BLEN(bp)) > 0){ 
		while ((n = canputc(cq)) == 0){ 
			kprint(" sccoput: sleeping\n"); 
			sleep(&cq->r, canputc, cq); 
		} 
		if(n > m) 
			n = m; 
		(*cq->puts)(cq, bp->rptr, n); 
		bp->rptr += n; 
	} 
	freeb(bp); 
	poperror(); 
} 
 
/* 
 *  process to send bytes upstream for a port 
 */ 
static void 
scckproc(void *a) 
{ 
	SCC *sp = a; 
	IOQ *cq = sp->iq; 
	Block *bp; 
	int n; 
 
loop: 
	while ((n = cangetc(cq)) == 0) 
		sleep(&cq->r, cangetc, cq); 
	/*kprint(" scckproc: %d\n", n);*/ 
	qlock(sp); 
	if(sp->wq == 0){ 
		cq->out = cq->in; 
	}else{ 
		bp = allocb(n); 
		bp->flags |= S_DELIM; 
		bp->wptr += gets(cq, bp->wptr, n); 
		PUTNEXT(RD(sp->wq), bp); 
	} 
	qunlock(sp); 
	goto loop; 
} 
 
enum{ 
	Qdir=		0, 
	Qtty0=		STREAMQID(0, Sdataqid), 
	Qtty0ctl=	STREAMQID(0, Sctlqid), 
	Qtty1=		STREAMQID(1, Sdataqid), 
	Qtty1ctl=	STREAMQID(1, Sctlqid), 
}; 
 
Dirtab sccdir[]={ 
	"tty0",		{Qtty0},	0,		0666, 
	"tty0ctl",	{Qtty0ctl},	0,		0666, 
	"tty1",		{Qtty1},	0,		0666, 
	"tty1ctl",	{Qtty1ctl},	0,		0666, 
}; 
 
#define	NSCC	(sizeof sccdir/sizeof(Dirtab)) 
 
/* 
 *  allocate the queues if no one else has 
 */ 
void 
sccreset(void) 
{ 
	SCC *sp; 
 
	for(sp = scc; sp < &scc[2]; sp++){ 
		if(sp->nostream) 
			continue; 
		sp->iq = ialloc(sizeof(IOQ), 0); 
		initq(sp->iq); 
		sp->oq = ialloc(sizeof(IOQ), 0); 
		initq(sp->oq); 
		sccdevice(sp); 
	} 
} 
 
void 
sccinit(void) 
{ 
} 
 
Chan* 
sccattach(char *spec) 
{ 
	return devattach('t', spec); 
} 
 
Chan* 
sccclone(Chan *c, Chan *nc) 
{ 
	return devclone(c, nc); 
} 
 
int 
sccwalk(Chan *c, char *name) 
{ 
	return devwalk(c, name, sccdir, NSCC, devgen); 
} 
 
void 
sccstat(Chan *c, char *dp) 
{ 
	switch(c->qid.path){ 
	case Qtty0: 
		streamstat(c, dp, "tty0"); 
		break; 
	case Qtty1: 
		streamstat(c, dp, "tty1"); 
		break; 
	default: 
		devstat(c, dp, sccdir, NSCC, devgen); 
		break; 
	} 
} 
 
Chan* 
sccopen(Chan *c, int omode) 
{ 
	SCC *sp; 
 
	switch(c->qid.path){ 
	case Qtty0: 
	case Qtty0ctl: 
		sp = &scc[0]; 
		break; 
	case Qtty1: 
	case Qtty1ctl: 
		sp = &scc[1]; 
		break; 
	default: 
		sp = 0; 
		break; 
	} 
 
	if(sp && sp->nostream) 
		errors("in use"); 
 
	if((c->qid.path & CHDIR) == 0) 
		streamopen(c, &sccinfo); 
	return devopen(c, omode, sccdir, NSCC, devgen); 
} 
 
void 
scccreate(Chan *c, char *name, int omode, ulong perm) 
{ 
	error(Eperm); 
} 
 
void 
sccclose(Chan *c) 
{ 
	if(c->stream) 
		streamclose(c); 
} 
 
long 
sccread(Chan *c, void *buf, long n, ulong offset) 
{ 
	SCC *sp = &scc[0]; int s; 
 
	switch(c->qid.path&~CHDIR){ 
	case Qdir: 
		return devdirread(c, buf, n, sccdir, NSCC, devgen); 
	case Qtty1ctl: 
		++sp; 
		/* fall through */ 
	case Qtty0ctl: 
		if(offset) 
			return 0; 
		s = splhi(); 
		*(uchar *)buf = *sp->ptr; 
		splx(s); 
		return 1; 
	} 
	return streamread(c, buf, n); 
} 
 
long 
sccwrite(Chan *c, void *va, long n, ulong offset) 
{ 
	return streamwrite(c, va, n, 0); 
} 
 
void 
sccremove(Chan *c) 
{ 
	error(Eperm); 
} 
 
void 
sccwstat(Chan *c, char *dp) 
{ 
	error(Eperm); 
} 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)