~kris/9p

9hist

c523b5d0a320372e5ce54500032826508571205e — David du Colombier 33 years ago 788c439
Plan 9 from Bell Labs 1992-12-08
6 files changed, 868 insertions(+), 14 deletions(-)

M pc/devrtc.c
M port/proc.c
M power/io.h
A power/scsi.c
A power/scsibuf.c
M power/trap.c
M pc/devrtc.c => pc/devrtc.c +6 -3
@@ 122,10 122,13 @@ rtctime(void)
{
	uchar bcdclock[Nbcd];
	Rtc rtc;
	int i;

	outb(Paddr, Status);
	while(inb(Pdata) & 1)
		;
	for(i = 0; i < 10000; i++){
		outb(Paddr, Status);
		if((inb(Pdata) & 1) == 0)
			break;
	}
	outb(Paddr, Seconds);	bcdclock[0] = inb(Pdata);
	outb(Paddr, Minutes);	bcdclock[1] = inb(Pdata);
	outb(Paddr, Hours);	bcdclock[2] = inb(Pdata);

M port/proc.c => port/proc.c +8 -8
@@ 495,14 495,14 @@ unbreak(Proc *p)
	int b;

	qlock(&broken);
	for(b=0; b < NBROKEN; b++) {
		if(broken.p[b] != p)
			continue;

		broken.n--;
		memmove(&broken.p[b], &broken.p[b+1], sizeof(Proc*)*(NBROKEN-(b+1)));
		ready(p);
	}
	for(b=0; b < broken.n; b++)
		if(broken.p[b] != p) {
			broken.n--;
			memmove(&broken.p[b], &broken.p[b+1],
					sizeof(Proc*)*(NBROKEN-(b+1)));
			ready(p);
			break;
		}
	qunlock(&broken);
}


M power/io.h => power/io.h +9 -0
@@ 67,6 67,15 @@ struct SBCC
#define	WRITEMAP	IO2(ulong, 0xFA0000)
#define LANCEINDEX	0x1E00			/* index of lancemap */

#define SCSI0ADDR	IO2(uchar, 0xF08007)
#define SCSI0DATA	IO2(uchar, 0xF08107)
#define SCSI1ADDR	IO2(uchar, 0xF0C007)
#define SCSI1DATA	IO2(uchar, 0xF0C107)
#define SCSI0CNT	IO2(ulong, 0xF54000)
#define SCSI1CNT	IO2(ulong, 0xF58000)
#define SCSI0FLS	IO2(uchar, 0xF30001)
#define SCSI1FLS	IO2(uchar, 0xF34001)

#define IOID		IO2(uchar, 0xFFFFF0)
#define IO2R1		1	/* IO2 revision level 1 */
#define IO2R2		2	/* IO2 revision level 2 */

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

enum
{
	Rownid		= 0x00,		/* own ID/CDB size register */
	  Eaf		=   0x08,	/* enable advanced features */
	  Fs16MHz	=   0x80,	/* frequency select */
	Rcontrol	= 0x01,		/* control register */
	  Hsp		=   0x01,	/* halt on scsi parity error */
	  Idi		=   0x04,	/* intermediate disconnect interrupt */
	  Edi		=   0x08,	/* ending disconnect interrupt */
	  BurstDma	=   0x20,	/* burst mode dma */
	Rtimeout	= 0x02,		/* timeout period register */
	Rcdb		= 0x03,		/* CDB first byte */
	Rlun		= 0x0F,		/* target LUN register */
	Rcmdphase	= 0x10,		/* command phase register */
	Rsync		= 0x11,		/* synchronous transfer register */
	Rtchi		= 0x12,		/* transfer count hi register */
	Rtcmi		= 0x13,		/* transfer count mi register */
	Rtclo		= 0x14,		/* transfer count lo register */
	Rdestid		= 0x15,		/* destination ID */
	  Dpd		=   0x40,	/* data-phase direction (read) */
	Rsrcid		= 0x16,		/* source ID */
	  Siv		=   0x08,	/* source ID is valid */
	  Er		=   0x80,	/* enable reselection */
	Rstatus		= 0x17,		/* scsi status register */
	Rcmd		= 0x18,		/* command register */
	Rdata		= 0x19,		/* data register */
	Raux		= 0x1F,		/* auxiliary status register */
	  Dbr		=   0x01,	/* data buffer ready */
	  Intrpend	=   0x80,	/* interrupt pending */

	Creset		= 0x00,		/* reset */
	Catn		= 0x02,		/* assert ATN */
	Cnack		= 0x03,		/* negate ACK */
	CselectATN	= 0x06,		/* select with ATN */
	Cselect		= 0x07,		/* select without ATN */
	CtransferATN	= 0x08,		/* select with ATN and transfer */
	Ctransfer	= 0x09,		/* select without ATN and transfer */
	Ctinfo		= 0x20,		/* transfer info */
	Csbt		= 0x80,		/* single-byte transfer */

	Mcomplete	= 0x00,		/* complete */
	Msaveptr	= 0x02,		/* save pointers */
	Mdisconnect	= 0x04,		/* disconnect */
	Mabort		= 0x06,		/* abort */
	Mreject		= 0x07,		/* reject */
	Mnop		= 0x08,		/* nop */
	Mbdr		= 0x0C,		/* bus device reset */
	Midentify	= (0x80|0x40),	/* identify|disconnectok */
};

typedef struct Target Target;
struct Target
{
	QLock;
	Rendez;
	ulong	flags;
	uchar	sync;
	uchar	transfer;
	Target	*active;		/* link on active list */

	int	target;			/* SCSI Unit and logical unit */
	int	lun;
	int	status;

	struct {			/* DMA things */
		ulong	dmaaddr;
		ulong	dmalen;
		int	rflag;

		ulong	*dmacnt;
		uchar	*dmafls;
		ulong	dmamap;
	};

	uchar	cdb[16];
	int	clen;

	uchar	*addr;
	uchar	*data;
	Target	*base;

	Target	*ctlr;
};

enum
{
	NCtlr		= 2,
	NTarget		= 8,
	CtlrID		= 0,

	Fbusy		= 0x0001,
	Fidentified	= 0x0002,
	Fdisconnected	= 0x0004,
	Freselect	= 0x0008,
	Fcomplete	= 0x0010,
	Fsyncxfer	= 0x0100,
	Freadxfer	= 0x0200,
	Fdmaxfer	= 0x0400,
	Fretry		= 0x1000,
	Fidentifying	= 0x2000,
};

static Target target[NCtlr][NTarget];

static	void	dmamap(Target*);

/*
 * Set up and start a data transfer.
 * We don't load the command phase here as
 * we may be continuing a stopped operation.
 */
static void
start(Target *tp)
{
	ulong count;
	Target *dev = tp->ctlr;

	/*
	 * Set up the chip for the transfer.
	 * First the LUN and transfer mode.
	 */
	*dev->addr = Rlun;
	*dev->data = tp->lun;
	*dev->addr = Rsync;
	*dev->data = tp->sync;

	/*
	 * Set up transfer count and targetID+direction.
	 * No need to set the address register here, we're
	 * already pointing to the right one.
	 */
	count = tp->dmalen;
	*dev->data = (count>>16) & 0xFF;
	*dev->data = (count>>8) & 0xFF;
	*dev->data = count & 0xFF;

	if(tp->rflag == 0)
		*dev->data = Dpd|tp->target;
	else
		*dev->data = tp->target;

	/*
	 * Load the command into the chip.
	 */
	*dev->addr = Rownid;
	*dev->data = tp->clen;
	*dev->addr = Rcdb;

print("start %d cdb: ", tp->target);
	for(count = 0; count < tp->clen; count++) {
		*dev->data = tp->cdb[count];
		print("%2.2lux ", tp->cdb[count]);
	}
print("\n");
	dmamap(tp);

	/*
	 * Select and transfer.
	 */
	*dev->addr = Rcmd;
	*dev->data = tp->transfer;
}

/*
 * Arbitrate for the bus and start a data transfer.
 * If this is the first time through for the target,
 * i.e. we haven't identified yet, just set up for
 * selection so we can try a synchronous negotiation.
 */
static void
arbitrate(Target *dev)
{
	Target *tp;

	tp = dev->active;

	*dev->addr = Rcmdphase;
	*dev->data = 0x00;

	if(tp->flags & Fidentified) {
		start(tp);
		return;
	}

	/*
	 * Fretry means that we will try this request again,
	 * but without initialising the command phase, so we will
	 * continue from where we left off.
	 */
	tp->flags |= Fidentifying|Fretry;
	*dev->addr = Rlun;
	*dev->data = tp->lun;
	*dev->addr = Rdestid;
	*dev->data = tp->target;
	*dev->addr = Rcmd;
	*dev->data = CselectATN;
}

static int
done(void *arg)
{
	return (((Target*)arg)->flags & Fbusy) == 0;
}

static int
scsiio(int nbus, int unit, int rw, uchar *cmd, int cbytes, void *data, int dbytes)
{
	ulong s;
	int status;
	Target *ctlr, **l, *f, *tp;

	tp = &target[nbus][unit];
	ctlr = tp->ctlr;

	qlock(tp);
	tp->flags |= Fbusy;
	tp->rflag = rw;
	tp->dmaaddr = (ulong)data;
	tp->dmalen = dbytes;
	tp->status = 0x6002;
	memmove(tp->cdb, cmd, cbytes);
	tp->clen = cbytes;

	s = splhi();
	qlock(ctlr);

	/*
	 * Link the target onto the end of the
	 * ctlr active list and start the request if
	 * the controller is idle.
	 */
	tp->active = 0;
	l = &ctlr->active;
	for(f = *l; f; f = f->active)
		l = &f->active;
	*l = tp;

	if((ctlr->flags & Fbusy) == 0){
		ctlr->flags |= Fbusy;
		arbitrate(ctlr);
	}

	qunlock(ctlr);
	splx(s);

	/*
	 * Wait for the request to complete
	 * and return the status.
	 */
	sleep(tp, done, tp);

	status = tp->status;
	qunlock(tp);
print("status: #%4.4lux\n", status);
	return status;
}

int
scsiexec(Scsi *p, int read)
{
	int rw, s;
	ulong clen, dlen;

	clen = p->cmd.lim - p->cmd.base;
	dlen = p->data.lim - p->data.base;

	rw = 1;
	if(read == ScsiIn)
		rw = 0;

	if(p->target == CtlrID)
		return 0x6002;

	s = scsiio(1, p->target, rw, p->cmd.base, clen, p->data.base, dlen);
	p->status = s;
	return s;
}

/*
 * Target request complete.
 * Only called from interrupt level,
 * i.e. ctlr is 'locked'.
 */
static void
complete(Target *ctlr)
{
	Target *tp;

	tp = ctlr->active;
	tp->flags &= (Fretry|Fidentified|Fbusy);

	/*
	 * Retry (continue) the operation if necessary
	 * (used for initial identify), otherwise unlink from
	 * the ctlr active list.
	 */
	if(tp->flags & Fretry){
		tp->flags &= ~Fretry;
		start(tp);
		return;
	}

	ctlr->active = tp->active;

	/*
	 * All done with this target, wakeup scsiio.
	 * If there are other targets queued, start
	 * one up.
	 */
	tp->flags &= Fidentified;
	*tp->dmafls = 1;

	wakeup(tp);

	if(ctlr->active){
		arbitrate(ctlr);
		return;
	}
	ctlr->flags &= ~Fbusy;
}

/*
 * A target has disconnected.
 * Unlink it from the active list and
 * start another if possible.
 * Only called from interrupt level,
 * i.e. ctlr is 'locked'.
 */
static void
disconnect(Target *ctlr)
{
	Target *tp;

	tp = ctlr->active;
	tp->flags |= Fdisconnected;
	ctlr->active = tp->active;

	if(ctlr->active){
		arbitrate(ctlr);
		return;
	}
	ctlr->flags &= ~Fbusy;
}

/*
 * A target reselected, put it back
 * on the ctlr active list. We mark the ctlr
 * busy, it's the responsibility of the caller
 * to restart the transfer.
 * Only called from interrupt level,
 * i.e. ctlr is 'locked'.
 */
static void
reselect(Target *ctlr, uchar id)
{
	Target *tp;

	tp = &ctlr->base[id];
	tp->active = ctlr->active;
	ctlr->active = tp;
	ctlr->flags |= Fbusy;
	tp->flags &= ~Fdisconnected;
}

static int
identify(Target *tp, uchar status)
{
	int i, n;
	Target *dev;
	static uchar msgout[6] = {
		0x00, 0x01, 0x03, 0x01, 0x3F, 0x0C
	};
	uchar msgin[16];

	dev = tp->ctlr;

	switch(status){

	case 0x11:					/* select complete */
		delay(7);
		*dev->addr = Rcmd;
		*dev->data = Catn;
		break;

	case 0x8E:					/* service required - MSGOUT */
		/*
		 * Send a synchronous data transfer request.
		 * The target may act strangely here and not respond
		 * correctly.
		 */
		tp->sync = 0x00;			/* asynchronous transfer mode */
		tp->flags |= Fidentified;
		*dev->addr = Rtchi;
		*dev->data = 0x00;
		*dev->data = 0x00;
		*dev->data = sizeof(msgout);
		*dev->addr = Rcmd;
		*dev->data = Ctinfo;
		msgout[0] = Midentify|tp->lun;
		*dev->addr = Rdata;
		n = 0;
		while(n < sizeof(msgout)) {
			/*
			 * Wait until the chip says ready.
			 * If an interrupt is indicated, something
			 * went wrong, so get out of here and let
			 * the interrupt happen.
			 */
			if(*dev->addr & Intrpend)
				return 0;
			if(*dev->addr & Dbr){
				*dev->data = msgout[n];
				n++;
			}
		}
		break;

	case 0x1F:					/* transfer complete - MSGIN */
	case 0x4F:					/* unexpected MSGIN */
		/*
		 * Collect the response. The first 3 bytes
		 * of an extended message are:
		 *  0. extended message code	(0x01)
		 *  1. extended message length	(0x03)
		 *  2. sync. tranfer req. code	(0x01)
		 * Only other defined code with length >1 is
		 * 'modify data pointer', which has length 7;
		 * we better not receive anything much longer...
		 * Adjust the number of bytes to collect once we see
		 * how long the response is (byte 2).
		 * If we collect anything other than an extended message
		 * or sync. transfer req. response, toss it
		 * and assume async.
		 */
		*dev->addr = Rtchi;
		*dev->data = 0x00;
		*dev->data = 0x00;
		*dev->data = 5;
		*dev->addr = Rcmd;
		*dev->data = Ctinfo;
		*dev->addr = Rdata;
		for(i = sizeof(msgin)-1, n = 0; n < i; n++){
			while((*dev->addr & Dbr) == 0){
				if(*dev->addr & Intrpend)
					return 0;
			}
			msgin[n] = *dev->data;
			if(n == 1)
				i = msgin[1] + 2;
		}

		if(msgin[2] == 0x01 && (n = msgin[3]) && n != 0xFF){
			if(n < 0x40)
				n = 0x02;
			else if(n < 0x80)
				n = 0x04;
			else
				n = 0;
			n |= msgin[4]<<4;
			tp->sync = n;
		}
		break;

	case 0x4E:
		*dev->addr = Rcmd;
		*dev->data = Csbt|Ctinfo;
		*dev->addr = Rdata;
		while((*dev->addr & Dbr) == 0){
			if(*dev->addr & Intrpend)
				return 0;
		}
		*dev->data = Mnop;
		break;

	case 0x20:					/* transfer info paused */
		delay(7);
		*dev->addr = Rcmd;
		*dev->data = Cnack;
		break;

	case 0x1A:
	case 0x8A:
		/*
		 * Fretry should be set, so we will
		 * pick up from here.
		 */
		*dev->addr = Rcmdphase;
		*dev->data = 0x30;			/* command phase has begun */
		complete(dev);
		break;

	default:
		return 1;
	}
	return 0;
}

static void
saveptr(Target *tp)
{
	ulong count;
	Target *dev;

	*tp->dmafls = 1;

	dev = tp->ctlr;
	/*
	 * The transfer count register contains the
	 * number of bytes NOT transferred.
	 */
	*dev->addr = Rtchi;
	count =  (*dev->data & 0xFF)<<16;
	count += (*dev->data & 0xFF)<<8;
	count += (*dev->data & 0xFF);

	tp->dmaaddr += tp->dmalen - count;
	tp->dmalen = count;

	*dev->addr = Rcmd;
	*dev->data = tp->transfer;
}

int
scsistatus(Target *ctlr)
{
	ulong status;

	*ctlr->addr = Rlun;
	status = *ctlr->data & 0xFF;		/* target status */
	status |= *ctlr->data<<8;		/* command phase */

	return status;
}

void
scsiintr(int ctlrno)
{
	uchar status, x;
	Target *ctlr, *tp;

	ctlr = &target[ctlrno][CtlrID];
	tp = ctlr->active;

	*ctlr->addr = Rstatus;
	status = *ctlr->data;
print("scsiintr: #%2.2lux\n", status);
	if(tp == 0)
		return;

	switch(status){
	case 0x42:				/* timeout */
		tp->flags &= ~Fretry;
		scsistatus(ctlr);		/* Must read lun register */
		complete(ctlr);
		return;

	case 0x16:				/* select-and-transfer complete */
		/*
		 * The target status comes back in the Rlun
		 * register. Returned status is combined
		 * with the final command phase, which
		 * should be 0x60.
		 */
		tp->status = scsistatus(ctlr);	/* target status */
		complete(ctlr);
		return;

	case 0x21:					/* save data pointers */
		saveptr(tp);
		return;

	case 0x4B:					/* unexpected status phase */
		*ctlr->addr = Rcmdphase;
		*ctlr->data = 0x46;			/*  */
		*ctlr->addr = Rtchi;
		*ctlr->data = 0;
		*ctlr->data = 0;
		*ctlr->data = 0;
		*ctlr->addr = Rcmd;
		*ctlr->data = tp->transfer;
		return;

	case 0x85:					/* disconnect */
		if(tp->flags & Fidentifying){
			*ctlr->addr = Rcmdphase;
			*ctlr->data = 0x00;		/* complete retry */
			complete(ctlr);
			return;
		}
		disconnect(ctlr);
		return;

	case 0x81:
		*ctlr->addr = Rsrcid;
		x = *ctlr->data;
		if(x & Siv){				/* source ID valid */
			reselect(ctlr, x & 0x07);
			*ctlr->addr = Rcmdphase;
			*ctlr->data = 0x44;		/* reselected by target */
			start(ctlr->active);
			return;
		}
		break;

	default:
		if((tp->flags & Fidentifying) && identify(tp, status) == 0)
			return;
		break;
	}
	delay(1000);
	print("scsi intr status #%.2ux\n", status);
	print("scsi aux status #%.2ux\n", *ctlr->addr & 0xFF);
	for(*ctlr->addr = 0x00, x = 0x00; x < 0x19; x++){
		if((x & 0x07) == 0x07)
			print("#%.2ux: #%.2ux\n", x, *ctlr->data & 0xFF);
		else
			print("#%.2ux: #%.2ux ", x, *ctlr->data & 0xFF);
	}
	panic("scsiintr\n");
}

void
scsiicltr(int ctlrnr, uchar *data, uchar *addr, ulong *cnt, uchar *fls, ulong map)
{
	int i;
	uchar status;
	Target *ctlr, *tp;

	ctlr = &target[ctlrnr][CtlrID];

	ctlr->data = data;
	ctlr->addr = addr;
	ctlr->base = target[ctlrnr];

	if(*ctlr->addr & Intrpend) {
		*ctlr->addr = Rstatus;
		status = *ctlr->data;
		USED(status);
	}

	/*
	 * Give the chip a soft reset. This will
	 * cause an interrupt. Wait for it and clear it.
	 */
	*ctlr->addr = Rownid;
	*ctlr->data = Fs16MHz|Eaf|CtlrID;
	*ctlr->addr = Rcmd;
	*ctlr->data = Creset;

	while((*ctlr->addr & Intrpend) == 0)
		delay(250);

	*ctlr->addr = Rstatus;
	status = *ctlr->data;
	USED(status);

	/*
	 * Freselect means enable the chip to
	 * respond to reselects.
	 */
	ctlr->flags = /*Freselect|*/Fidentified;

	*ctlr->addr = Rcontrol;
	*ctlr->data = BurstDma|Edi|Idi|Hsp;
	*ctlr->addr = Rtimeout;
	*ctlr->data = 40;				/* 200ms */
	*ctlr->addr = Rsrcid;

	if(ctlr->flags & Freselect)
		*ctlr->data = Er;
	else
		*ctlr->data = 0x00;

	for(i = 0; i < NTarget; i++){
		if(i == CtlrID)
			continue;

		tp = &target[ctlrnr][i];
		tp->lun = 0;
		tp->target = i;
		tp->ctlr = ctlr;
		tp->dmacnt = cnt;
		tp->dmafls = fls;
		tp->dmamap = map;

		if(ctlr->flags & Freselect){
			tp->flags = 0;
			tp->transfer = CtransferATN;
		}
		else {
			tp->flags = Fidentified;
			tp->transfer = Ctransfer;
		}
	}
}

/*
 * Called at boot time.
 * Initialise the hardware.
 * There will be an interrupt in reponse
 * to the reset.
 */
void
resetscsi(void)
{
	uchar m3;
print("scsi init\n");
	/*
	 * Reset both scsi busses
	 */
	m3 = MODEREG->promenet;
	m3 &= ~((1<<6)|(1<<7));
	MODEREG->promenet = m3 | (1<<6) | (1<<7);
	delay(500);
	MODEREG->promenet = m3;
	delay(100);

	scsiicltr(0, SCSI0DATA, SCSI0ADDR, SCSI0CNT, SCSI0FLS, 0x1fc0);
	scsiicltr(1, SCSI1DATA, SCSI1ADDR, SCSI1CNT, SCSI1FLS, 0x1f80);
}

static void
dmamap(Target *tp)
{
	ulong y, len, addr, index, off;

	*tp->dmafls = 1;

	len  = tp->dmalen;
	if(len == 0)
		return;

	addr = tp->dmaaddr;
	index = tp->dmamap;
	for(y = addr+len+BY2PG; addr < y; addr += BY2PG){
		*WRITEMAP = (index<<16)|(addr>>12)&0xFFFF;
		index++;
	}
	off = tp->dmaaddr & (BY2PG-1);
	if(tp->rflag == 0)
		off |= 1<<15;

	*tp->dmacnt = off;
}

/*
 * Known to devscsi.c.
 */
int scsidebugs[8];
int scsiownid = CtlrID;

void
initscsi(void)
{
}

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

/*
 * The following routines
 *	scsibufreset, scsibuf, scsifree and scsialloc
 * provide a pool of buffers used for scsi i/o.
 * This is simplistic at best, and opportunities for
 * deadlock abound.
 */
#define Nbuf	2

struct {
	Lock;
	Scsibuf	*free;
} scsibufalloc;

Scsibuf *
scsialloc(ulong n)
{
	void *x;
	Scsibuf *b;

	if(n > 512*1024)
		panic("scsialloc too big: %d", n);

	b = xalloc(sizeof(Scsibuf));
	x = xalloc(n);

	b->phys = x;
	b->virt = x;

	return b;
}

void
scsibufreset(ulong datasize)
{
	int i;

	for(i = 0; i < Nbuf; i++)
		scsifree(scsialloc(datasize));
}

/*
 * get a scsi io buffer of DATASIZE size
 */
Scsibuf *
scsibuf(void)
{
	Scsibuf *b;

	for(;;) {
		lock(&scsibufalloc);
		b = scsibufalloc.free;
		if(b != 0) {
			scsibufalloc.free = b->next;
			unlock(&scsibufalloc);
			return b;
		}
		unlock(&scsibufalloc);
		resrcwait(0);
	}
	return 0;		/* not reached */
}

void
scsifree(Scsibuf *b)
{
	lock(&scsibufalloc);
	b->next = scsibufalloc.free;
	scsibufalloc.free = b;
	unlock(&scsibufalloc);
}

M power/trap.c => power/trap.c +3 -3
@@ 258,9 258,9 @@ intr(Ureg *ur)
				if(v & (1<<2))
					lanceintr();
				if(v & (1<<1))
					print("SCSI 1 interrupt\n");
					scsiintr(1);
				if(v & (1<<0))
					print("SCSI 0 interrupt\n");
					scsiintr(0);
			}
		}
		/*


@@ 415,7 415,7 @@ notify(Ureg *ur)
	sp = ur->usp - sizeof(Ureg);

	if(sp&0x3 || !okaddr((ulong)u->notify, BY2WD, 0)
	|| !okaddr(sp-ERRLEN-3*BY2WD, sizeof(Ureg)+ERRLEN-3*BY2WD, 1)) {
	|| !okaddr(sp-ERRLEN-3*BY2WD, sizeof(Ureg)+ERRLEN+3*BY2WD, 1)) {
		pprint("suicide: bad address or sp in notify\n");
		qunlock(&u->p->debug);
		pexit("Suicide", 0);