V10/sys/io/bda.c

/*
 * DSA port driver for KDB50 (aka BDA)
 * called by, e.g., ra.c
 */

#include "sys/param.h"
#include "sys/buf.h"
#include "sys/biic.h"
#include "sys/biaddr.h"
#include "sys/map.h"
#include "sys/bda.h"
#include "sys/mscp.h"
#include "sys/pte.h"

#define	hiword(x)	(((long)(x)>>16)&0177777)
#define	loword(x)	((long)(x)&0177777)

extern struct biaddr bdaddr[];
extern struct bd bd[];
extern int bdcnt;
struct ctab {
	int (*c_seql)();
	int (*c_dg)();
	int c_ctype;
} bdctab[MSMAXID];

/*
 * bd_flags
 */

#define	UINIT	01		/* already did trivial init */
#define	UIDONE	02		/* initialization all done */
#define	UPWAIT	04		/* waiting for command packet */
#define	UFIRST	010		/* let first packet go even if no credits */
#define	UTIMER	020		/* timer will kick on next go */
#define	UCWAIT	040		/* waiting for credits */
#define	UPMWAIT	0100		/* waiting for map */

#define	NOBACK	(-1)

/*
 * bd_cbusy
 */

#define	FREE	0
#define	NABBED	01
#define	SENT	02
#define	MAPPED	04

/*
 * bdip is really bigpr0 in struct biic
 */

struct device {
	short	bdxx;	/* unused */
	short	bdip;
	short	bdsar;
	short	bdsaw;
};

#define	bdaregs(p) ((struct device *)&((p)->bigpr0))

/*
 * bits in bdsa (variously read and write half)
 */

#define	ERR	0100000
#define	STEP4	040000
#define	STEP3	020000
#define	STEP2	010000
#define	STEP1	04000
#define	STEPS	(STEP1|STEP2|STEP3|STEP4)

#define	IE	0200		/* step1 interrupt enable */
#define	PI	01		/* step2 purge intr enab */
#define	GO	01		/* step4 ok */

/*
 * bda communication area
 * ring sizes are chosen so that,
 * with 4K byte buffers,
 * one bdcomm + CSIZE-sized command packets will
 * fit in one buffer;
 * RSIZE-sized response packets
 * will fit in another
 *
 * ring sizes must be powers of 2
 * (they are passed as such to the port)
 *
 * a delicacy:
 * command packets should not cross 64Kb boundaries.
 * it is believed that the KDB50 gets it wrong if they do.
 * hence, make CSIZE+4 evenly divide a page,
 * and get it aligned sensibly in bdreset
 */

#define	NCP2	5
#define	NRP2	5
#define	NCMD	(1<<NCP2)
#define	NRSP	(1<<NRP2)

struct bdcomm {
	short	bd__r0;		/* reserved (ugh) */
	char	bd__r1;
	char	bd_bdp;		/* path to purge */
	short	bd_cmdint;	/* flag for command interrupt */
	short	bd_rspint;	/* flag for response interrupt */
	long	bd_rsp[NRSP];	/* response pointer ring */
	long	bd_cmd[NCMD];	/* command pointer ring */
};

/*
 * bits in ring pointers
 */

#define	DPOWN	0x80000000	/* port owns descriptor */
#define	DIE	0x40000000	/* ring transition intr enab */

#define	BDAVIRT	0x80000000	/* in m_buff: mapped transfer */
#define	V	0x80000000	/* valid bit in page map */

#define	CSIZE	60	/* max size of command packet */
#define	RSIZE	60	/* max size of response packet */
#define	HDRSIZE	4	/* size of the header */

struct bdcmd {
	short	uc_len;		/* length of message */
	char	uc_tc;		/* type, credits */
	char	uc_cid;		/* connection id */
	char	uc_data[CSIZE];
};

struct bdrsp {
	short	ur_len;		/* length of message */
	char	ur_tc;		/* type, credits */
	char	ur_cid;		/* connection id */
	char	ur_data[RSIZE];
};

/*
 * message types
 */

#define	MTYPE	0xf0		/* where type lives */
#define	MTSEQL	0x00		/* sequential message */
#define	MTDG	0x10		/* datagram */
#define	MTCR	0x20		/* credit notification */

#define	MTNC	0xf		/* credits in tc */

/*
 * etc
 */

#define	PRIINI	(PZERO-3)
#define	PRIPKT	(PZERO-2)
#define	PRICRED	(PZERO-1)

/*
 * command packet to index
 */
#define	bdmptoi(up, mp)	((struct bdcmd *)((char *)(mp) - HDRSIZE) - (up)->bd_cpkt)

#define	TIMEOUT	10		/* time between checks */

int bdinit(), bdsend(), bdmap(), bdunmap();
struct mscmd *bdgpkt();
struct msportsw bdport = {
	bdinit, bdgpkt, bdmap, bdsend, bdunmap
};

/*
 * init the port
 * called once only
 * returns nonzero if probably ok
 * allowed to sleep
 */

bdinit(dev, type, force, cid, seql, dg)
unsigned int dev;
unsigned int cid;
int force;
int (*seql)(), (*dg)();
{
	register struct bd *up;
	struct buf *geteblk();
	extern bdtimer();

	if (dev >= bdcnt)
		return (0);
	if (cid >= MSMAXID)
		return (0);
	bdctab[cid].c_seql = seql;
	bdctab[cid].c_dg = dg;
	bdctab[cid].c_ctype = type;
	up = &bd[dev];
	if (up->bd_flags & UINIT && force == 0)
		return (1);
	if ((up->bd_addr = (struct biic *)biaddr(&bdaddr[dev])) == 0) {
		printf("bd%d absent\n", dev);
		return (0);
	}
	bdrundown(dev);
	if ((up->bd_flags & UINIT) == 0) {
		up->bd_cbuf = geteblk();
		clrbuf(up->bd_cbuf);
		up->bd_rbuf = geteblk();
		clrbuf(up->bd_rbuf);
		up->bd_mbuf = geteblk();
		clrbuf(up->bd_mbuf);
		up->bd_comm = (struct bdcomm *)up->bd_cbuf->b_un.b_addr;
#define	BDCOFF	(((sizeof(struct bdcomm)/sizeof(struct bdcmd))+1)*sizeof(struct bdcmd))
		up->bd_cpkt = (struct bdcmd *)(up->bd_cbuf->b_un.b_addr + BDCOFF);
		up->bd_rpkt = (struct bdrsp *)up->bd_rbuf->b_un.b_addr;
		up->bd_pmap = (long *)up->bd_mbuf->b_un.b_addr;
		up->bd_flags |= UINIT;
		timeout(bdtimer, (caddr_t)dev, TIMEOUT * HZ);
	}
	bdreset(dev);
	return (1);
}

/*
 * reset device
 * initially or after error or power fail
 *
 * just kick the device here;
 * ideally we would get an interrupt when self-test finishes
 * (step 1 starts) but we don't, so let the timer routine catch it
 */

bdreset(dev)
register int dev;
{
	register struct bd *up;

	up = &bd[dev];
	up->bd_flags &=~ UIDONE;
	up->bd_addr->bicsr |= BINRST;	/* hard reset */
}

/*
 * finish up init, step by step
 * called from interrupt code
 */

bdinintr(dev)
register int dev;
{
	register struct bd *up;
	register struct device *rp;
	register struct biic *bi;

	up = &bd[dev];
	rp = bdaregs(up->bd_addr);
	if (up->bd_flags & UIDONE)
		printf("bd%d: unexpected init: sa %o\n", dev, rp->bdsar);
	switch (rp->bdsar & STEPS) {
	case STEP1:
		up->bd_cnext = 0;
		up->bd_rnext = 0;
		up->bd_credits = 0;
		bi = up->bd_addr;
		biinit(&bdaddr[dev], 0);
		bi->biuir = bdaddr[dev].vec;
		bi->bieir = (bdaddr[dev].vec + sizeof(long))|EIBR5;
		rp->bdsaw = ERR | IE | (NCP2<<11) | (NRP2<<8);
		return;

	case STEP2:
		rp->bdsaw = PI | loword(physadr(up->bd_comm->bd_rsp));
		return;

	case STEP3:
		rp->bdsaw = hiword(physadr(up->bd_comm->bd_rsp));
		return;

	case STEP4:
		rp->bdsaw = GO;
		for (dev = 0; dev < NCMD; dev++) {
			up->bd_comm->bd_cmd[dev] = 0;	/* unnecessary */
			up->bd_cpkt[dev].uc_len = CSIZE;
			up->bd_back[dev] = NOBACK;
		}
		for (dev = 0; dev < NRSP; dev++) {
			up->bd_rpkt[dev].ur_len = RSIZE;
			up->bd_comm->bd_rsp[dev] = physadr(up->bd_rpkt[dev].ur_data) | DIE | DPOWN;
		}
		up->bd_flags |= UIDONE | UFIRST;
		rminit(up->bd_map, BDANMAP, (up->bd_mbuf->b_bcount/sizeof(long))-1, 1);
		wakeup((caddr_t)up);
		return;

	default:
		printf("bd%d init bad: sar %o\n", dev, rp->bdsar);
		return;
	}
}

/*
 * tell the class drivers that the controller was reset
 * so they can clean up
 * called after controller is stopped (so it's safe to unmap things)
 */
bdrundown(dev)
int dev;
{
	static struct msend me;
	register int i;

	me.m_sts = STRST;	/* magic */
	for (i = 0; i < MSMAXID; i++)
		if (bdctab[i].c_seql)
			(*bdctab[i].c_seql)(dev, bdctab[i].c_ctype, &me);
}

/*
 * allocate a packet
 * and sufficient resources to send it
 * eg credits
 * may sleep
 *
 * somewhat silly assumption:
 * class driver will allocate a packet, and send it right away or nearly so
 */

struct mscmd *
bdgpkt(dev)
int dev;
{
	register struct bd *up;
	register int i;
	int s;

	up = &bd[dev];
	s = spl6();
	while ((up->bd_flags & UIDONE) == 0)
		sleep((caddr_t)up, PRIINI);
	while (up->bd_credits < 2 && (up->bd_flags & UFIRST) == 0) {
		if (bdpkscan(dev, 1))
			continue;
		up->bd_flags |= UCWAIT;
		sleep((caddr_t)&up->bd_credits, PRICRED);
	}
	if ((up->bd_flags & UFIRST) == 0)
		up->bd_credits--;
	for (;;) {
		for (i = 0; i < NCMD; i++)
			if (up->bd_cbusy[i] == FREE)
				break;
		if (i < NCMD)
			break;
		if (bdpkscan(dev, 1) == 0 && bdcmdscan(dev) == 0) {	/* kludge for hung controller */
			up->bd_flags |= UPWAIT;
			sleep((caddr_t)up->bd_cbusy, PRIPKT);
		}
	}
	up->bd_cbusy[i] = NABBED;
	splx(s);
	return ((struct mscmd *)up->bd_cpkt[i].uc_data);
}

/*
 * map a transfer if need be
 * and record in the buffer descriptor of a packet
 * may sleep
 *
 * BDA use of m_buff:
 *	first longword is buffer address;
 *		if BDAVIRT clear, physical addr, else virtual
 *	second longword is phys addr of base of page table if virtual
 *
 * subtlety: pretend we mapped it even if we didn't,
 * so it won't be unmapped until someone calls bdunmap
 * else they may be confused when they call it anyway
 */

bdmap(dev, mp, bp)
int dev;
struct mscmd *mp;
register struct buf *bp;
{
	register int i;
	register long *b;
	register struct pte *pt;
	int s;
	int base;
	register int size;
	register struct bd *up;

	up = &bd[dev];
	i = bdmptoi(up, mp);
	up->bd_cbusy[i] |= MAPPED;
	if ((bp->b_flags & (B_PHYS|B_UAREA|B_PAGET|B_DIRTY)) == 0) {
		b = (long *)&mp->m_buff;
		*b++ = (long)bp->b_un.b_addr|BDAVIRT;
		*b++ = physadr(Sysmap);
		*b = 0;
		return;
	}
	size = btoc(bp->b_bcount);
	if ((long)bp->b_un.b_addr & PGOFSET)
		size++;			/* unaligned */
	s = spl6();
	while ((base = rmalloc(up->bd_map, size)) == 0) {
		up->bd_flags |= UPMWAIT;
		sleep((caddr_t)up->bd_map, PRIPKT);
	}
	splx(s);
	up->bd_mbase[i] = base;
	up->bd_msize[i] = size;
	b = &up->bd_pmap[base];
	pt = btopte(bp);
	while (--size >= 0)
		*b++ = pt++->pg_pfnum | V;
	b = (long *)&mp->m_buff;
	*b++ = ctob(base)|((long)bp->b_un.b_addr&PGOFSET)|BDAVIRT;
	*b++ = physadr(up->bd_pmap);
	*b = 0;
}

/*
 * free a mapped packet
 */

bdunmap(dev, mp)
int dev;
struct mscmd *mp;
{
	register struct bd *up;
	register int i;

	up = &bd[dev];
	i = bdmptoi(up, mp);
	if (up->bd_cbusy[i] == FREE)
		return;		/* wasn't mapped, and already freed */
	if (up->bd_msize[i]) {
		rmfree(up->bd_map, up->bd_msize[i], up->bd_mbase[i]);
		up->bd_msize[i] = 0;
		if (up->bd_flags & UPMWAIT) {
			up->bd_flags &=~ UPMWAIT;
			wakeup((caddr_t)up->bd_map);
		}
	}
	up->bd_cbusy[i] = FREE;
	if (up->bd_flags & UPWAIT) {
		up->bd_flags &=~ UPWAIT;
		wakeup((caddr_t)up->bd_cbusy);
	}
}

/*
 * send a packet
 * may not sleep
 * call at spl5
 */

bdsend(dev, cid, mp)
int dev;
int cid;
struct mscmd *mp;
{
	register struct bd *up;
	register int i;
	register int j;

	up = &bd[dev];
	up->bd_flags &=~ UFIRST;
	i = bdmptoi(up, mp);
	up->bd_cpkt[i].uc_cid = cid;
	j = up->bd_cnext++;
	if (up->bd_cnext >= NCMD)
		up->bd_cnext = 0;
	if (up->bd_comm->bd_cmd[j] & DPOWN)
		panic("bdsend");
	if (up->bd_back[j] >= 0) {	/* ran for a while with no free */
		bdcmdscan(dev);
		if (up->bd_back[j] >= 0)
			panic("bdsend");
	}
	up->bd_back[j] = i;
	up->bd_comm->bd_cmd[j] = DPOWN | DIE | physadr(mp);
	up->bd_cbusy[i] |= SENT;
	up->bd_cbusy[i] &=~ NABBED;
	i = bdaregs(up->bd_addr)->bdip;		/* initiate polling */
}

/*
 * interrupt routine
 */

long bd_spur;
int bdspl;

bd0int(dev)
int dev;
{
	register struct bd *up;
	register struct device *rp;

	bdspl = mfpr(0x12);
	up = &bd[dev];
	if (dev >= bdcnt || (up->bd_flags & UINIT) == 0) {
		printf("bd%d: stray intr\n", dev);
		return;
	}
	rp = bdaregs(up->bd_addr);
	if (rp->bdsar & ERR) {
		printf("bd%d: hard error %o\n", dev, rp->bdsar & 0177777);
		bdreset(dev);
		return;
	}
	if (rp->bdsar & STEPS) {
		bdinintr(dev);
		return;
	}
	if (up->bd_comm->bd_cmdint == 0
	&&  up->bd_comm->bd_rspint == 0)
		bd_spur++;
	while (up->bd_comm->bd_cmdint) {
		up->bd_comm->bd_cmdint = 0;
		bdcmdscan(dev);
	}
	while (up->bd_comm->bd_rspint) {
		up->bd_comm->bd_rspint = 0;
		if (bdpkscan(dev, 0))
			up->bd_flags &=~ UTIMER;
	}
}

bd1int(dev)
int dev;
{
	printf("bd%d: error intr\n", dev);
}

/*
 * free packets which are completely sent
 * (and which don't have associated map)
 */

bdcmdscan(dev)
int dev;
{
	register struct bd *up;
	register int i, j;
	register int freed;
	register struct bdcomm *bdc;

	up = &bd[dev];
	bdc = up->bd_comm;
	freed = 0;
	for (j = 0; j < NCMD; j++)
		if (up->bd_back[j] >= 0
		&&  (bdc->bd_cmd[j] & DPOWN) == 0) {
			i = up->bd_back[j];
			if ((up->bd_cbusy[i] & (SENT|MAPPED)) == SENT) {
				up->bd_cbusy[i] = FREE;
				freed++;
			}
			up->bd_back[j] = NOBACK;
		}
	if (freed && up->bd_flags & UPWAIT)
		wakeup((caddr_t)up->bd_cbusy);
	return (freed);
}

/*
 * check for responses from the controller
 * and deal with them
 * return a count for debugging purposes
 */

int
bdpkscan(dev, doall)
int dev;
int doall;
{
	register struct bd *up;
	register int i;
	int nf;
	register struct bdrsp *pk;
	register struct ctab *cp;
	register struct bdcomm *bdc;	/* speed */
	int s;

	up = &bd[dev];
	bdc = up->bd_comm;
	nf = 0;
	s = spl6();
	for (i = up->bd_rnext; ; i < NRSP-1 ? i++ : (i = 0)) {
		if (bdc->bd_rsp[i] & DPOWN) {
			up->bd_rnext = i;
			/* don't stop if doall? */
			break;
		}
		nf++;
		pk = &up->bd_rpkt[i];
		up->bd_credits += pk->ur_tc & MTNC;
		if (up->bd_flags & UCWAIT) {
			wakeup((caddr_t)&up->bd_credits);
			up->bd_flags &=~ UCWAIT;
		}
		if (pk->ur_cid > MSMAXID)
			printf("bd%d msg id %d\n", dev, pk->ur_cid);
		else {
			cp = &bdctab[pk->ur_cid];
			switch (pk->ur_tc & MTYPE) {
			case MTSEQL:
				if (cp->c_seql)
					(*cp->c_seql)(dev, cp->c_ctype, (struct msend *)pk->ur_data);
				break;
		
			case MTDG:
				if (cp->c_dg)
					(*cp->c_dg)(dev, cp->c_ctype, pk->ur_data);
				break;
		
			/* default: ignore */
			}
		}
		pk->ur_len = RSIZE;
		bdc->bd_rsp[i] |= DPOWN | DIE;
	}
	splx(s);
	return (nf);
}

/*
 * timeout routine
 * return any waiting packets
 * -- callout routines don't necessarily run at high priority.
 * hence the spl6, so bdpkscan won't be reentered
 */

int bd_kicked;

bdtimer(i)
int i;
{
	register struct bd *up;
	register int s;

	up = &bd[i];
	if ((up->bd_flags & UINIT) == 0)
		return;
	if ((up->bd_flags & UIDONE) == 0) {
		if ((bdaregs(up->bd_addr)->bdsar & STEPS) == STEP1)
			bdinintr(i);
	}
	else if ((up->bd_flags & UTIMER) == 0)
		up->bd_flags |= UTIMER;
	else {
		s = spl6();
		if (bdpkscan(i, 1) && up->bd_flags & UPWAIT) {
			wakeup((caddr_t)up->bd_cbusy);
			bd_kicked++;
		}
		splx(s);
		up->bd_flags &=~ UTIMER;
	}
	timeout(bdtimer, (caddr_t)i, TIMEOUT * HZ);
}