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);
}