~kris/9p

9hist

014857cc7cfd364411f471b44f77b62e56810317 — David du Colombier 27 years ago f82c55e
Plan 9 from Bell Labs 1999-09-10
5 files changed, 526 insertions(+), 149 deletions(-)

M ip/devip.c
M ip/ip.h
M ip/udp.c
A pc/vgaclgd546x.c
M port/devsdp.c
M ip/devip.c => ip/devip.c +1 -1
@@ 462,7 462,7 @@ ipwstat(Chan*, char*)
	error(Eperm);
}

static void
void
closeconv(Conv *cv)
{
	Conv *nc;

M ip/ip.h => ip/ip.h +1 -1
@@ 278,7 278,7 @@ Proto*	Fsrcvpcolx(Fs*, uchar);
char*	Fsstdconnect(Conv*, char**, int);
char*	Fsstdannounce(Conv*, char**, int);
char*	Fsstdbind(Conv*, char**, int);

void	closeconv(Conv*);
/* 
 *  logging
 */

M ip/udp.c => ip/udp.c +55 -31
@@ 119,6 119,7 @@ udpclose(Conv *c)
{
	Udpcb *ucb;

	c->state = 0;
	qclose(c->rq);
	qclose(c->wq);
	qclose(c->eq);


@@ 234,7 235,7 @@ udpiput(Proto *udp, uchar *ia, Block *bp)
{
	int len, olen, ottl;
	Udphdr *uh;
	Conv *c, **p, *gc;
	Conv *c, **p, *spec, *gen;
	Udpcb *ucb;
	uchar raddr[IPaddrlen], laddr[IPaddrlen];
	ushort rport, lport;


@@ 273,11 274,9 @@ udpiput(Proto *udp, uchar *ia, Block *bp)
	qlock(udp);

	/*
	 *  Look for a conversation structure for this port.  One
	 *  with headers off wins over one with headers on, i.e.,
	 *  specific wins over generic.
	 *  Look for a conversation structure for this packet
	 */
	gc = c = nil;
	spec = gen = c = nil;
	for(p = udp->conv; *p; p++) {
		c = *p;
		if(c->inuse == 0)


@@ 285,23 284,41 @@ udpiput(Proto *udp, uchar *ia, Block *bp)
		if(c->lport == lport){
			ucb = (Udpcb*)c->ptcl;

			/* with headers turned on, descriminate only on local port */
			if(ucb->headers && gc == nil){
				gc = c;
				continue;
			switch(c->state){
			case Announced:
				/* headers + Announced is special behaviour */
				if(ucb->headers)
					goto found;
				spec = c;
				break;
				
			case Connected:
				/* exact match */
				if(c->rport == rport)
				if(ipcmp(c->raddr, raddr) == 0 || ipisbm(c->raddr))
					goto found;
			}

			/* otherwise discriminate on lport, rport, and raddr */
			if(c->rport == 0 || c->rport == rport)
			if(ipisbm(c->raddr) || ipcmp(c->raddr, IPnoaddr) == 0
			   || ipcmp(c->raddr, raddr) == 0)
				break;
		} else if(c->lport == 0) {
			/* generic listen for all udp ports */
			if(c->state == Announced)
				gen = c;
		}
	}
found:
	if(*p == nil){
		if(gc != nil)
			c = gc;
		else {
		if(spec != nil)
			gen = spec;
		if(gen != nil){
			if(ipforme(f, laddr) != Runi)
				v4tov6(laddr, ia);
			c = Fsnewcall(gen, raddr, rport, laddr, lport);
			if(c == nil){
				freeblist(bp);
				return;
			}
			c->state = Connected;
		} else {
			upriv->ustats.udpNoPorts++;
			qunlock(udp);
			netlog(f, Logudp, "udp: no conv %I!%d -> %I!%d\n", raddr, rport,


@@ 357,21 374,8 @@ udpiput(Proto *udp, uchar *ia, Block *bp)
		hnputs(bp->rp + 2*IPv4addrlen, rport);
		hnputs(bp->rp + 2*IPv4addrlen + 2, lport);
		break;
	default:
		/* connection oriented udp */
		if(ipcmp(c->raddr, IPnoaddr) == 0){
			/* save the src address in the conversation */
		 	ipmove(c->raddr, raddr);
			c->rport = rport;

			/* reply with the same ip address (if not broadcast) */
			if(ipforme(f, laddr) == Runi)
				ipmove(c->laddr, laddr);
			else
				v4tov6(c->laddr, ia);
		}
		break;
	}

	if(bp->next)
		bp = concatblock(bp);



@@ 388,6 392,22 @@ udpiput(Proto *udp, uchar *ia, Block *bp)

}

/* close any incoming calls waiting on this conversation */
void
udpcloseincalls(Conv *c)
{
	Conv *nc;

	qlock(c);

	for(nc = c->incall; nc; nc = c->incall){
		c->incall = nc->next;
		closeconv(nc);
	}

	qunlock(c);
}

char*
udpctl(Conv *c, char **f, int n)
{


@@ 397,9 417,13 @@ udpctl(Conv *c, char **f, int n)
	if(n == 1){
		if(strcmp(f[0], "headers4") == 0){
			ucb->headers = 4;
			/* close any calls that got in twixt announce and headers */
			udpcloseincalls(c);
			return nil;
		} else if(strcmp(f[0], "headers") == 0){
			ucb->headers = 6;
			/* close any calls that got in twixt announce and headers */
			udpcloseincalls(c);
			return nil;
		}
	}

A pc/vgaclgd546x.c => pc/vgaclgd546x.c +290 -0
@@ 0,0 1,290 @@
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"

#define	Image	IMAGE
#include <draw.h>
#include <memdraw.h>
#include <cursor.h>
#include "screen.h"

typedef struct {
	ushort	x;
	ushort	y;
	ushort	preset;
	ushort	enable;
	ushort	addr;
} Cursor546x;

enum {
	PaletteState	= 0xB0,
	CursorMMIO	= 0xE0,
};

static ulong
clgd546xlinear(VGAscr* scr, int* size, int* align)
{
	ulong aperture, oaperture;
	int oapsize, wasupamem;
	Pcidev *p;

	oaperture = scr->aperture;
	oapsize = scr->apsize;
	wasupamem = scr->isupamem;

	aperture = 0;
	if(p = pcimatch(nil, 0x1013, 0)){
		switch(p->did){
		case 0xD0:
		case 0xD4:
		case 0xD6:
			aperture = p->mem[0].bar & ~0x0F;
			*size = p->mem[0].size;
			break;
		default:
			break;
		}
	}

	if(wasupamem){
		if(oaperture == aperture)
			return oaperture;
		upafree(oaperture, oapsize);
	}
	scr->isupamem = 0;

	aperture = upamalloc(aperture, *size, *align);
	if(aperture == 0){
		if(wasupamem && upamalloc(oaperture, oapsize, 0)){
			aperture = oaperture;
			scr->isupamem = 1;
		}
		else
			scr->isupamem = 0;
	}
	else
		scr->isupamem = 1;

	return aperture;
}
static void
clgd546xenable(VGAscr* scr)
{
	Pcidev *p;
	Physseg seg;
	int size, align;
	ulong aperture;

	/*
	 * Only once, can't be disabled for now.
	 * scr->io holds the virtual address of
	 * the MMIO registers.
	 */
	if(scr->io)
		return;
	if(p = pcimatch(nil, 0x1013, 0)){
		switch(p->did){
		case 0xD0:
		case 0xD4:
		case 0xD6:
			break;
		default:
			return;
		}
	}
	else
		return;
	scr->io = upamalloc(p->mem[1].bar & ~0x0F, p->mem[1].size, 0);
	if(scr->io == 0)
		return;

	memset(&seg, 0, sizeof(seg));
	seg.attr = SG_PHYSICAL;
	seg.name = smalloc(NAMELEN);
	snprint(seg.name, NAMELEN, "clgd546xmmio");
	seg.pa = scr->io;
	seg.size = p->mem[1].size;
	addphysseg(&seg);

	scr->io = (ulong)KADDR(scr->io);

	size = p->mem[0].size;
	align = 0;
	aperture = clgd546xlinear(scr, &size, &align);
	if(aperture) {
		scr->aperture = aperture;
		scr->apsize = size;
		memset(&seg, 0, sizeof(seg));
		seg.attr = SG_PHYSICAL;
		seg.name = smalloc(NAMELEN);
		snprint(seg.name, NAMELEN, "clgd546xscreen");
		seg.pa = aperture;
		seg.size = size;
		addphysseg(&seg);
	}
}

static void
clgd546xcurdisable(VGAscr* scr)
{
	Cursor546x *cursor546x;

	if(scr->io == 0)
		return;
	cursor546x = (Cursor546x*)(scr->io+CursorMMIO);
	cursor546x->enable = 0;
}

static void
clgd546xcurload(VGAscr* scr, Cursor* curs)
{
	int c, i, m, y;
	uchar *p;
	Cursor546x *cursor546x;

	if(scr->io == 0)
		return;
	cursor546x = (Cursor546x*)(scr->io+CursorMMIO);

	/*
	 * Disable the cursor then change only the bits
	 * that need it.
	 */
	cursor546x->enable = 0;
	p = (uchar*)(scr->aperture + scr->storage);
	for(y = 0; y < 16; y++){
		c = curs->set[2*y];
		m = 0;
		for(i = 0; i < 8; i++){
			if(c & (1<<(7-i)))
				m |= 1<<i;
		}
		*p++ = m;
		c = curs->set[2*y + 1];
		m = 0;
		for(i = 0; i < 8; i++){
			if(c & (1<<(7-i)))
				m |= 1<<i;
		}
		*p++ = m;
		p += 6;
		c = curs->set[2*y]|curs->clr[2*y];
		m = 0;
		for(i = 0; i < 8; i++){
			if(c & (1<<(7-i)))
				m |= 1<<i;
		}
		*p++ = m;
		c = curs->set[2*y + 1]|curs->clr[2*y + 1];
		m = 0;
		for(i = 0; i < 8; i++){
			if(c & (1<<(7-i)))
				m |= 1<<i;
		}
		*p++ = m;
		p += 6;
	}

	/*
	 * Save the cursor hotpoint and enable the cursor.
	 */
	scr->offset = curs->offset;
	cursor546x->enable = 1;
}

static int
clgd546xcurmove(VGAscr* scr, Point p)
{
	int x, xo, y, yo;
	Cursor546x *cursor546x;

	if(scr->io == 0)
		return 1;
	cursor546x = (Cursor546x*)(scr->io+CursorMMIO);

	if((x = p.x+scr->offset.x) < 0){
		xo = -x;
		x = 0;
	}
	else
		xo = 0;
	if((y = p.y+scr->offset.y) < 0){
		yo = -y;
		y = 0;
	}
	else
		yo = 0;

	cursor546x->preset = (xo<<8)|yo;
	cursor546x->x = x;
	cursor546x->y = y;

	return 0;
}

static void
clgd546xcurenable(VGAscr* scr)
{
	uchar *p;
	Cursor546x *cursor546x;

	clgd546xenable(scr);
	if(scr->io == 0)
		return;
	cursor546x = (Cursor546x*)(scr->io+CursorMMIO);

	/*
	 * Cursor colours.
	 * Can't call setcolor here as cursor is already locked.
	 */
	p = (uchar*)(scr->io+PaletteState);
	*p |= 0x08;
	vgao(PaddrW, 0x00);
	vgao(Pdata, Pwhite);
	vgao(Pdata, Pwhite);
	vgao(Pdata, Pwhite);
	vgao(PaddrW, 0x0F);
	vgao(Pdata, Pblack);
	vgao(Pdata, Pblack);
	vgao(Pdata, Pblack);
	*p &= ~0x08;

	/*
	 * Find a place for the cursor data in display memory.
	 * 2 cursor images might be needed, 1KB each so use the last
	 * 2KB of the framebuffer and initialise them to be
	 * transparent.
	 */
	scr->storage = ((vgaxi(Seqx, 0x14) & 0x07)+1)*1024*1022;
	cursor546x->addr = (scr->storage>>10)<<2;
	memset((uchar*)(scr->aperture + scr->storage), 0, 2*64*16);

	/*
	 * Load, locate and enable the 64x64 cursor.
	 */
	clgd546xcurload(scr, &arrow);
	clgd546xcurmove(scr, ZP);
	cursor546x->enable = 1;
}

VGAdev vgaclgd546xdev = {
	"clgd546x",

	clgd546xenable,
	nil,
	nil,
	clgd546xlinear,
};

VGAcur vgaclgd546xcur = {
	"clgd546xhwgc",

	clgd546xcurenable,
	clgd546xcurdisable,
	clgd546xcurload,
	clgd546xcurmove,
};

M port/devsdp.c => port/devsdp.c +179 -116
@@ 74,7 74,8 @@ enum {
	CDial,
	CAccept,
	COpen,
	CClosing,
	CLocalClose,
	CRemoteClose,
	CClosed,
};



@@ 86,7 87,7 @@ struct Conv {

	int state;
	int dataopen;

	int reader;		// reader proc has been started

	ulong	timeout;
	int		retries;


@@ 95,9 96,11 @@ struct Conv {
	ulong dialid;
	ulong acceptid;

	QLock readlk;		// protects readproc
	Proc *readproc;
	QLock readlk;

	Chan *chan;	// packet channel
	char *channame;

	char owner[NAMELEN];		/* protections */
	int	perm;


@@ 131,8 134,8 @@ enum {
enum {
	ConOpenRequest,
	ConOpenAck,
	ConOpenAckAck,
	ConClose,
	ConCloseAck,
	ConReset,
};



@@ 186,7 189,8 @@ static char *convstatename[] = {
	[CDial] "Dial",
	[CAccept] "Accept",
	[COpen] "Open",
	[CClosing] "Closing",
	[CLocalClose] "LocalClose",
	[CRemoteClose] "RemoteClose",
	[CClosed] "Closed",
};



@@ 203,6 207,7 @@ static Block *readcontrol(Conv *c, int n);
static Block *readdata(Conv *c, int n);
static void convoput(Conv *c, int type, Block *b);
static void convoput2(Conv *c, int op, ulong dialid, ulong acceptid);
static void convreader(void *a);


static void


@@ 328,8 333,12 @@ sdpopen(Chan* ch, int omode)
		if(strcmp(up->user, c->owner) != 0 || (perm & c->perm) != perm)
				error(Eperm);
		c->ref++;
		if(TYPE(ch->qid) == Qdata)
		if(TYPE(ch->qid) == Qdata) {
			if(c->dataopen == 0)
			if(c->readproc != nil)
				postnote(c->readproc, 1, "interrupt", 0);
			c->dataopen++;
		}
		qunlock(c);
		poperror();
		break;


@@ 377,10 386,10 @@ print("close c->ref = %d\n", c->ref);
				break;
			case CAccept:
			case COpen:
				convsetstate(c, CClosing);
				break;
			case CClosing:
				convsetstate(c, CLocalClose);
				break;
			case CLocalClose:
				panic("local close already happened");
			}
		}
		qunlock(c);


@@ 474,20 483,22 @@ print("Qctl write : conv->id = %d\n", c->id);
			error("short write");
		arg0 = cb->f[0];
print("cmd = %s\n", arg0);
		if(strcmp(arg0, "chan") == 0) {
		if(strcmp(arg0, "accept") == 0) {
			if(cb->nf != 2)
				error("usage: chan file");
				error("usage: accect file");
			if(c->chan != nil)
				error("chan already set");
				error("already connected");
			c->chan = namec(cb->f[1], Aopen, ORDWR, 0);
		} else if(strcmp(arg0, "accept") == 0) {
			if(cb->nf != 2)
				error("usage: accect id");
			c->dialid = atoi(cb->f[1]);
			convsetstate(c, CAccept);
			c->channame = malloc(strlen(cb->f[1])+1);
			strcpy(c->channame, cb->f[1]);
		} else if(strcmp(arg0, "dial") == 0) {
			if(cb->nf != 1)
				error("usage: dial");
			if(cb->nf != 2)
				error("usage: accect file");
			if(c->chan != nil)
				error("already connected");
			c->chan = namec(cb->f[1], Aopen, ORDWR, 0);
			c->channame = malloc(strlen(cb->f[1])+1);
			strcpy(c->channame, cb->f[1]);
			convsetstate(c, CDial);
		} else if(strcmp(arg0, "drop") == 0) {
			if(cb->nf != 2)


@@ 584,7 595,7 @@ sdpclone(Sdp *sdp)
			break;
		}
		if(canqlock(c)){
			if(c->state == CClosed)
			if(c->state == CClosed && c->reader == 0)
				break;
			qunlock(c);
		}


@@ 597,7 608,10 @@ sdpclone(Sdp *sdp)

	c->ref++;
	c->state = CInit;

	if(!waserror()) {
		kproc("convreader", convreader, c);
		c->reader = 1;
	}
	strncpy(c->owner, up->user, sizeof(c->owner));
	c->perm = 0660;
	qunlock(c);


@@ 647,11 661,13 @@ print("convtimer: %s\n", convstatename[c->state]);
	case CAccept:
		if(convretry(c))
			convoput2(c, ConOpenAck, c->dialid, c->acceptid);
		else
			convoput2(c, ConReset, c->dialid, c->acceptid);
		break;
	case COpen:
		// check for control packet and keepalive
		break;
	case CClosing:
	case CLocalClose:
		if(convretry(c))
			convoput2(c, ConClose, c->dialid, c->acceptid);
		break;


@@ 730,15 746,18 @@ print("convsetstate %s -> %s\n", convstatename[c->state], convstatename[state]);
		assert(c->state == CDial || c->state == CAccept);
		if(c->state == CDial) {
			convretryinit(c);
			convoput2(c, ConOpenAck, c->dialid, c->acceptid);
			convoput2(c, ConOpenAckAck, c->dialid, c->acceptid);
		}
		// setup initial key and auth method
		break;
	case CClosing:
		assert(c->state == COpen);
	case CLocalClose:
		assert(c->state == CAccept || c->state == COpen);
		convretryinit(c);
		convoput2(c, ConClose, c->dialid, c->acceptid);
		break;
	case CRemoteClose:
		convoput2(c, ConReset, c->dialid, c->acceptid);
		break;
	case CClosed:
		if(c->readproc)
			postnote(c->readproc, 1, "interrupt", 0);


@@ 748,6 767,10 @@ print("convsetstate %s -> %s\n", convstatename[c->state], convstatename[state]);
			cclose(c->chan);
			c->chan = nil;
		}
		if(c->channame) {
			free(c->channame);
			c->channame = nil;
		}
		strcpy(c->owner, "network");
		c->perm = 0660;
		c->dialid = 0;


@@ 778,34 801,6 @@ onewaycleanup(OneWay *ow)
}


static Block *
convreadblock(Conv *c, int n)
{
	Block *b;

	qlock(&c->readlk);
	if(waserror()) {
		c->readproc = nil;
		qunlock(&c->readlk);
		nexterror();
	}
	qlock(c);
	if(c->state == CClosed) {
		qunlock(c);
		poperror();
		qunlock(&c->readlk);
		return 0;
	}
	c->readproc = up;
	qunlock(c);

	b = devtab[c->chan->type]->bread(c->chan, n, 0);
	c->readproc = nil;
	poperror();
	qunlock(&c->readlk);

	return b;
}


// assume we hold lock for c


@@ 853,8 848,6 @@ conviput(Conv *c, Block *b, int control)
	
		c->in.controlseq = cseq;
		b->rp += 4;
		if(control)
			return b;
		c->in.controlpkt = b;
		wakeup(&c->in.controlready);
		return nil;


@@ 895,63 888,96 @@ conviput2(Conv *c, Block *b)
	dialid = nhgetl(con->dialid);
	acceptid = nhgetl(con->acceptid);

print("conviput2: %d %uld %uld\n", con->op, dialid, acceptid);
	switch(c->state) {
	default:
		panic("unknown state: %d", c->state);
	case CInit:
		break;
	case CDial:
		if(dialid != c->dialid)
			goto Reset;
		break;
	case CAccept:
	case COpen:
	case CLocalClose:
	case CRemoteClose:
		if(dialid != c->dialid || acceptid != c->acceptid)
			goto Reset;
		break;
	case CClosed:
		goto Reset;
	}


print("conviput2: %s: %d %uld %uld\n", convstatename[c->state], con->op, dialid, acceptid);
	switch(con->op) {
	case ConOpenRequest:
		switch(c->state) {
		default:
			convoput2(c, ConReset, dialid, acceptid);
			break;
		case CInit:
			c->dialid = dialid;
			convsetstate(c, CAccept);
			break;
			return;
		case CAccept:
		case COpen:
			if(dialid != c->dialid || acceptid != c->acceptid)
				convoput2(c, ConReset, dialid, acceptid);
			break;
			// duplicate ConOpenRequest that we ignore
			return;
		}
		break;
	case ConOpenAck:
		switch(c->state) {
		case CDial:
			if(dialid != c->dialid) {
				convoput2(c, ConReset, dialid, acceptid);
				break;
			}
			c->acceptid = acceptid;
			convsetstate(c, COpen);
			break;
			return;
		case COpen:
			// duplicate that we have to ack
			convoput2(c, ConOpenAckAck, acceptid, dialid);
			return;
		}
		break;
	case ConOpenAckAck:
		switch(c->state) {
		case CAccept:
			if(dialid != c->dialid || acceptid != c->acceptid) {
				convoput2(c, ConReset, dialid, acceptid);
				break;
			}
			convsetstate(c, COpen);
			return;
		case COpen:
			// duplicate that we ignore
			return;
		}
		break;
	case ConClose:
		convoput2(c, ConCloseAck, dialid, acceptid);
		// fall though
	case ConReset:
		convoput2(c, ConReset, dialid, acceptid);
		switch(c->state) {
		case CInit:
		case CDial:
			if(dialid == c->dialid)
				convsetstate(c, CClosed);
			break;
		case CAccept:
		case CLocalClose:
			convsetstate(c, CClosed);
			return;
		case COpen:
		case CClosing:
			if(dialid == c->dialid && acceptid == c->acceptid)
				convsetstate(c, CClosed);
			break;
			convsetstate(c, CRemoteClose);
			return;
		case CRemoteClose:
			return;
		}
	case ConCloseAck:
		if(c->state == CClosing && dialid == c->dialid && acceptid == c->acceptid)
		return;
	case ConReset:
		switch(c->state) {
		case CInit:
		case CDial:
		case CAccept:
		case COpen:
		case CLocalClose:
			convsetstate(c, CClosed);
		break;
			return;
		case CRemoteClose:
			return;
		}
		return;
	}
Reset:
	// invalid connection message - reset to sender
	convoput2(c, ConReset, dialid, acceptid);
}

// assume hold conv lock


@@ 1003,12 1029,45 @@ print("chan = nil\n");
	devtab[c->chan->type]->write(c->chan, &con, sizeof(con), 0);
}

static Block *
convreadblock(Conv *c, int n)
{
	Block *b;
	Chan *ch = nil;

	qlock(&c->readlk);
	if(waserror()) {
		c->readproc = nil;
		if(ch)
			cclose(ch);
		qunlock(&c->readlk);
		nexterror();
	}
	qlock(c);
	if(c->state == CClosed) {
		qunlock(c);
		error("closed");
	}
	c->readproc = up;
	ch = c->chan;
	incref(ch);
	qunlock(c);

	b = devtab[ch->type]->bread(ch, n, 0);
	c->readproc = nil;
	cclose(ch);
	poperror();
	qunlock(&c->readlk);

	return b;
}

static int
readready(void *a)
{
	Conv *c = a;

	return (c->state == CClosed) || c->in.controlpkt != nil || c->dataopen == 0;
	return (c->state == CClosed) || c->in.controlpkt != nil;
}

static Block *


@@ 1016,9 1075,10 @@ readcontrol(Conv *c, int n)
{
	Block *b;

	USED(n);
	for(;;) {
		qlock(c);
		if(c->state == CClosed || c->state == CInit) {
		if(c->state == CInit || c->state == CClosed) {
			qunlock(c);
			return nil;
		}


@@ 1031,34 1091,9 @@ readcontrol(Conv *c, int n)
		}
		qunlock(c);

		// hack - this is to avoid gating onto the
		// read which will in general result in excessive
		// context switches.
		// The assumed behavior is that the client will read
		// from the control channel until the session is authenticated
		// at which point it will open the data channel and
		// start reading on that.  After the data channel is opened,
		// read on the channel are required for packets to
		// be delivered to the control channel

		if(c->dataopen) {
			sleep(&c->in.controlready, readready, c);
		} else {
			b = convreadblock(c, n);
			if(b == nil)
				return nil;
			qlock(c);
			if(waserror()) {
				qunlock(c);
				return nil;
			}
			b = conviput(c, b, 1);
			poperror();
			qunlock(c);
			if(b != nil)
				return b;
		}
		sleep(&c->in.controlready, readready, c);
	}
	return 0;
}

static Block *


@@ 1068,8 1103,6 @@ readdata(Conv *c, int n)

	for(;;) {
		b = convreadblock(c, n);
		if(b == nil)
			return nil;
		qlock(c);
		if(waserror()) {
			qunlock(c);


@@ 1082,3 1115,33 @@ readdata(Conv *c, int n)
			return b;
	}
}

static void
convreader(void *a)
{
	Conv *c = a;
	Block *b;

	qlock(c);
	assert(c->reader == 1);
	while(c->dataopen == 0) {
		qunlock(c);
		b = nil;
		if(!waserror()) {
			b = convreadblock(c, 2000);
			poperror();
		}
		qlock(c);
		if(b == nil) {
			convsetstate(c, CClosed);
			break;
		}
		if(!waserror()) {
			conviput(c, b, 1);
			poperror();
		}
	}
	c->reader = 0;
	qunlock(c);
	pexit("hangup", 1);
}