V10/sys/io/uda.c
/*
* DSA port driver for uda50
*
* this just passes packets around;
* it makes calls to the routines in ra.c
* to get the packets filled in
*/
#include "sys/param.h"
#include "sys/buf.h"
#include "sys/ubaddr.h"
#include "sys/map.h"
#include "sys/uba.h"
#include "sys/uda.h"
#include "sys/mscp.h"
/*
* ONEPATH tries to use only one data path per controller;
* a nice idea, but a disaster on the DW780 --
* the UDA50 will cheerfully run transfers in parallel from
* two drives on the same data path, which confuses the
* DW780 and mixes up the data
*/
/* #define ONEPATH 1 /* don't */
#define hiword(x) (((long)(x)>>16)&0177777)
#define loword(x) ((long)(x)&0177777)
extern struct uba uba[];
extern struct ubaddr udaddr[];
extern struct ud ud[];
extern int udcnt;
struct ctab {
int (*c_seql)();
int (*c_dg)();
int c_ctype;
} udctab[MSMAXID];
struct device {
short udip;
short udsa;
};
/*
* bits in udsa
*/
#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 */
/*
* uda communication area
* ring sizes are chosen so that,
* with 4K byte buffers,
* one udcomm + 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 udcomm {
short ud__r0; /* reserved (ugh) */
char ud__r1;
char ud_bdp; /* path to purge */
short ud_cmdint; /* flag for command interrupt */
short ud_rspint; /* flag for response interrupt */
long ud_rsp[NRSP]; /* response pointer ring */
long ud_cmd[NCMD]; /* command pointer ring */
};
/*
* bits in ring pointers
*/
#define DPOWN 0x80000000 /* port owns descriptor */
#define DIE 0x40000000 /* ring transition intr enab */
#define CSIZE 60 /* max size of command packet */
#define RSIZE 60 /* max size of response packet */
#define HDRSIZE 4 /* size of the header */
struct udcmd {
short uc_len; /* length of message */
char uc_tc; /* type, credits */
char uc_cid; /* connection id */
char uc_data[CSIZE];
};
struct udrsp {
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 udmptoi(up, mp) ((struct udcmd *)((char *)(mp) - HDRSIZE) - (up)->ud_cpkt)
#define TIMEOUT 15 /* time between checks */
int udinit(), udsend(), udmap(), udunmap();
struct mscmd *udgpkt();
struct msportsw udport = {
udinit, udgpkt, udmap, udsend, udunmap
};
/*
* init the port
* returns nonzero if probably ok
* allowed to sleep
*/
#define UDCOFF (((sizeof(struct udcomm)/sizeof(struct udcmd))+1)*sizeof(struct udcmd))
udinit(dev, type, force, cid, seql, dg)
unsigned int dev;
unsigned int cid;
int force;
int (*seql)(), (*dg)();
{
register struct ud *up;
register int ubno;
struct buf *geteblk();
extern udtimer();
if (dev >= udcnt)
return (0);
if (cid >= MSMAXID)
return (0);
udctab[cid].c_seql = seql;
udctab[cid].c_dg = dg;
udctab[cid].c_ctype = type;
up = &ud[dev];
ubno = udaddr[dev].ubno;
if (up->ud_flags & UISTART && force == 0)
return (1);
if ((up->ud_addr = (struct device *)ubaddr(&udaddr[dev])) == 0)
return (0);
if (ubbadaddr(ubno, &up->ud_addr->udsa, sizeof(short))) {
printf("ud%d not present\n", dev);
return (0);
}
udrundown(dev);
if ((up->ud_flags & UINIT) == 0) {
up->ud_cbuf = geteblk();
clrbuf(up->ud_cbuf);
up->ud_rbuf = geteblk();
clrbuf(up->ud_rbuf);
up->ud_cbm = ubmbuf(ubno, up->ud_cbuf, 0);
up->ud_rbm = ubmbuf(ubno, up->ud_cbuf, 0);
#if ONEPATH
up->ud_bdpno = ubmapath(ubno);
#endif
up->ud_comm = (struct udcomm *)up->ud_cbuf->b_un.b_addr;
up->ud_cpkt = (struct udcmd *)(up->ud_cbuf->b_un.b_addr + UDCOFF);
up->ud_rpkt = (struct udrsp *)up->ud_rbuf->b_un.b_addr;
up->ud_flags |= UINIT;
timeout(udtimer, (caddr_t)dev, TIMEOUT * HZ);
}
return (udreset(dev));
}
/*
* reset device
* initially or after error or power fail
* -- empirically, INITSTALL must be 200000 or so
* to work with TD Systems SCSI adapter on 8550
*/
#define INITSTALL 200000 /* >>1200us */
udreset(dev)
int dev;
{
register struct ud *up;
register struct device *rp;
register uaddr_t pa;
register int i;
up = &ud[dev];
rp = up->ud_addr;
up->ud_flags &=~ UIDONE;
up->ud_flags |= UISTART;
rp->udip = 0; /* hard reset */
pa = ubadbuf(udaddr[dev].ubno, up->ud_cbuf, up->ud_cbm);
up->ud_pcomm = pa + (4 * sizeof(short)); /* -> ud_rsp */
up->ud_pcpkt = pa + UDCOFF;
up->ud_cnext = 0;
up->ud_rnext = 0;
up->ud_credits = 0;
for (i = 0; (rp->udsa & STEP1) == 0 && i < INITSTALL; i++)
;
if ((rp->udsa & STEP1) == 0) {
up->ud_flags &=~ UISTART;
printf("ud%d won't init\n", dev);
return (0);
}
rp->udsa = ERR | IE | (NCP2<<11) | (NRP2<<8) | (udaddr[dev].vec>>2);
return (1);
}
/*
* finish up init, step by step
* called from interrupt code
*/
udinintr(dev)
int dev;
{
register struct ud *up;
register struct device *rp;
register int i;
register uaddr_t pa;
up = &ud[dev];
rp = up->ud_addr;
if (up->ud_flags & UIDONE) {
printf("ud%d: unexpected init: sa %o\n", dev, rp->udsa);
return;
}
switch (rp->udsa & STEPS) {
case STEP1:
udreset(dev);
return;
case STEP2:
/* need to set PI for 780 type unibus. */
rp->udsa = loword(up->ud_pcomm)|((uba[udaddr[dev].ubno].flags&UBQBUS)?0:PI);
return;
case STEP3:
rp->udsa = hiword(up->ud_pcomm);
return;
case STEP4:
rp->udsa = GO;
for (i = 0; i < NCMD; i++) {
up->ud_comm->ud_cmd[i] = 0; /* unnecessary */
up->ud_cpkt[i].uc_len = CSIZE;
up->ud_cbusy[i] = FREE;
up->ud_back[i] = NOBACK;
}
pa = ubadbuf(udaddr[dev].ubno, up->ud_rbuf, up->ud_rbm);
pa += 2 * sizeof(short);
for (i = 0; i < NRSP; i++, pa += sizeof(struct udrsp)) {
up->ud_comm->ud_rsp[i] = pa | DIE | DPOWN;
up->ud_rpkt[i].ur_len = RSIZE;
}
up->ud_flags |= UIDONE | UFIRST;
wakeup((caddr_t)up);
return;
default:
printf("ud%d init bad: sa %o\n", dev, rp->udsa);
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)
*/
udrundown(dev)
int dev;
{
static struct msend me;
register int i;
me.m_sts = STRST; /* magic */
for (i = 0; i < MSMAXID; i++)
if (udctab[i].c_seql)
(*udctab[i].c_seql)(dev, udctab[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 *
udgpkt(dev)
int dev;
{
register struct ud *up;
register int i;
int s;
up = &ud[dev];
if (up->ud_addr->udsa & STEP1) /* was reset somehow */
udreset(dev);
s = spl6();
while ((up->ud_flags & UIDONE) == 0)
sleep((caddr_t)up, PRIINI);
while (up->ud_credits < 2 && (up->ud_flags & UFIRST) == 0) {
if (udpkscan(dev, 1))
continue;
up->ud_flags |= UCWAIT;
sleep((caddr_t)&up->ud_credits, PRICRED);
}
if ((up->ud_flags & UFIRST) == 0)
up->ud_credits--;
for (;;) {
for (i = 0; i < NCMD; i++)
if (up->ud_cbusy[i] == FREE)
break;
if (i < NCMD)
break;
if (udpkscan(dev, 1) == 0 && udcmdscan(dev) == 0) { /* kludge for hung controller */
up->ud_flags |= UPWAIT;
sleep((caddr_t)up->ud_cbusy, PRIPKT);
}
}
up->ud_cbusy[i] = NABBED;
splx(s);
return ((struct mscmd *)up->ud_cpkt[i].uc_data);
}
/*
* map a transfer into unibus space
* and record in the buffer descriptor of a packet
* may sleep
*/
udmap(dev, mp, bp)
int dev;
struct mscmd *mp;
register struct buf *bp;
{
register struct ud *up;
register int i;
up = &ud[dev];
i = udmptoi(up, mp);
#if ONEPATH
if (up->ud_cmap[i] == 0)
up->ud_cmap[i] = ubmbuf(udaddr[dev].ubno, bp, USLP);
up->ud_cmap[i] = ubinspath(up->ud_bdpno, up->ud_cmap[i]);
#else
if (up->ud_cmap[i] == 0)
up->ud_cmap[i] = ubmbuf(udaddr[dev].ubno, bp, UBDP|USLP);
#endif
up->ud_cbusy[i] |= MAPPED;
*(long *)&mp->m_buff = ubadbuf(udaddr[dev].ubno, bp, up->ud_cmap[i]);
#if ONEPATH
*(long *)&mp->m_buff |= up->ud_bdpno << 24;
#else
*(long *)&mp->m_buff |= ubmpath(up->ud_cmap[i]) << 24;
#endif
}
/*
* free a mapped packet
* flush the bdp;
* change the path in the map to the DDP before freeing (ugh)
* so we won't free our reserved BDP too
* it is OK to free an already freed packet, mostly to help reset code
*/
udunmap(dev, mp)
int dev;
struct mscmd *mp;
{
register struct ud *up;
register int i;
up = &ud[dev];
i = udmptoi(up, mp);
if (up->ud_cmap[i]) {
#if ONEPATH
ubmflush(udaddr[dev].ubno, ubmpath(up->ud_cmap[i]));
ubmfree(udaddr[dev].ubno, ubinspath(0, up->ud_cmap[i]));
#else
ubmfree(udaddr[dev].ubno, up->ud_cmap[i]);
#endif
up->ud_cmap[i] = 0;
}
up->ud_cbusy[i] = FREE;
if (up->ud_flags & UPWAIT) {
up->ud_flags &=~ UPWAIT;
wakeup((caddr_t)up->ud_cbusy);
}
}
/*
* send a packet
* may not sleep
* call at spl5
*/
udsend(dev, cid, mp)
int dev;
int cid;
struct mscmd *mp;
{
register struct ud *up;
register int i;
register int j;
register struct device *rp;
up = &ud[dev];
up->ud_flags &=~ UFIRST;
i = udmptoi(up, mp);
up->ud_cpkt[i].uc_cid = cid;
j = up->ud_cnext++;
if (up->ud_cnext >= NCMD)
up->ud_cnext = 0;
if (up->ud_comm->ud_cmd[j] & DPOWN)
panic("udsend");
if (up->ud_back[j] >= 0) { /* ran for a while with no free */
udcmdscan(dev);
if (up->ud_back[j] >= 0)
panic("udsend");
}
rp = up->ud_addr;
if (rp->udsa & ERR) { /* and now we lose a packet */
printf("ud%d: hard error %o\n", dev, rp->udsa & 0177777);
udreset(dev);
return;
}
up->ud_back[j] = i;
up->ud_comm->ud_cmd[j] = DPOWN | DIE |
up->ud_pcpkt + (i * sizeof(struct udcmd)) + (2 * sizeof(short));
up->ud_cbusy[i] |= SENT;
up->ud_cbusy[i] &=~ NABBED;
i = rp->udip; /* initiate polling */
}
/*
* interrupt routine
*/
long ud_spur;
long ud_npr;
#define INIRETRY 5 /* TQK50 botch -- needs time to settle */
ud0int(dev)
int dev;
{
register struct ud *up;
register struct device *rp;
register int i;
up = &ud[dev];
if (dev >= udcnt || (up->ud_flags & UINIT) == 0) {
printf("ud%d: stray intr\n", dev);
return;
}
rp = up->ud_addr;
if ((up->ud_flags & UIDONE) == 0) {
for (i = 0; i < INIRETRY; i++)
if (rp->udsa & (STEPS|ERR))
break;
if ((rp->udsa & (STEPS|ERR)) == 0) {
printf("ud%d: init lost; sa 0%o\n", dev, rp->udsa);
return;
}
}
if (rp->udsa & ERR) {
printf("ud%d: hard error %o\n", dev, rp->udsa & 0177777);
udreset(dev);
return;
}
if (rp->udsa & STEPS) {
udinintr(dev);
return;
}
if (up->ud_comm->ud_bdp == 0
&& up->ud_comm->ud_cmdint == 0
&& up->ud_comm->ud_rspint == 0)
ud_spur++;
if (up->ud_comm->ud_bdp) {
ubmflush(udaddr[dev].ubno, up->ud_comm->ud_bdp);
up->ud_comm->ud_bdp = 0;
rp->udsa = 0; /* ack purge */
ud_npr++;
}
while (up->ud_comm->ud_cmdint) {
up->ud_comm->ud_cmdint = 0;
udcmdscan(dev);
}
while (up->ud_comm->ud_rspint) {
up->ud_comm->ud_rspint = 0;
if (udpkscan(dev, 0))
up->ud_flags &=~ UTIMER;
}
}
/*
* free packets which are completely sent
* (and which don't have associated map)
*/
udcmdscan(dev)
int dev;
{
register struct ud *up;
register int i, j;
register int freed;
register struct udcomm *udc;
up = &ud[dev];
udc = up->ud_comm;
freed = 0;
for (j = 0; j < NCMD; j++)
if (up->ud_back[j] >= 0
&& (udc->ud_cmd[j] & DPOWN) == 0) {
i = up->ud_back[j];
if ((up->ud_cbusy[i] & (SENT|MAPPED)) == SENT) {
up->ud_cbusy[i] = FREE;
freed++;
}
up->ud_back[j] = NOBACK;
}
if (freed && up->ud_flags & UPWAIT)
wakeup((caddr_t)up->ud_cbusy);
return (freed);
}
/*
* check for responses from the controller
* and deal with them
* return a count for debugging purposes
* called at spl6 (because of udtimer)
*/
int
udpkscan(dev, doall)
int dev;
int doall;
{
register struct ud *up;
register int i;
int nf;
register struct udrsp *pk;
register struct ctab *cp;
register struct udcomm *udc; /* speed */
up = &ud[dev];
udc = up->ud_comm;
nf = 0;
for (i = up->ud_rnext; ; i < NRSP-1 ? i++ : (i=0)) {
if (udc->ud_rsp[i] & DPOWN) {
up->ud_rnext = i;
/* don't stop if doall? */
break;
}
nf++;
pk = &up->ud_rpkt[i];
up->ud_credits += pk->ur_tc & MTNC;
if (up->ud_flags & UCWAIT) {
wakeup((caddr_t)&up->ud_credits);
up->ud_flags &=~ UCWAIT;
}
if (pk->ur_cid > MSMAXID)
printf("ud%d msg id %d\n", dev, pk->ur_cid);
else {
cp = &udctab[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;
udc->ud_rsp[i] |= DPOWN | DIE;
}
return (nf);
}
/*
* timeout routine
* return any waiting packets
* -- callout routines don't necessarily run at high priority.
* hence the spl6, so udpkscan won't be reentered
*/
int ud_kicked;
udtimer(i)
int i;
{
register struct ud *up;
register int s;
up = &ud[i];
if ((up->ud_flags & UINIT) == 0)
return;
if (up->ud_flags & UIDONE) {
if ((up->ud_flags & UTIMER) == 0)
up->ud_flags |= UTIMER;
else {
s = spl6();
if (udpkscan(i, 1) && up->ud_flags & UPWAIT) {
wakeup((caddr_t)up->ud_cbusy);
ud_kicked++;
}
splx(s);
up->ud_flags &=~ UTIMER;
}
}
timeout(udtimer, (caddr_t)i, TIMEOUT * HZ);
}