#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
/*
* Stargate's Avanstar serial board. There are ISA, EISA, microchannel
* versions. We only handle the ISA one.
*
* At the expense of performance, I've tried to be careful about
* endian-ness to make this convertable to other ISA bus machines.
* However, xchgw() is in assembler and will have to be translated.
*/
#define LENDIAN 1
/* unsigned short little endian representation */
#ifdef LENDIAN
#define LEUS(x) (x)
#else
#define LEUS(x) ( (((x)<<8)&0xff00) | (((x)>>8)&0xff) )
#endif LENDIAN
typedef struct Astar Astar;
typedef struct Astarchan Astarchan;
enum
{
/* ISA control ports */
ISAid= 0, /* Id port and its values */
ISAid0= 0xEC,
ISAid1= 0x13,
ISAid0x= 0x69,
ISAid1x= 0x96,
ISActl1= 1, /* board control */
ISAien= 1<<7, /* interrupt enable */
ISAirq= 7<<4, /* mask for irq code */
ISAnotdl= 1<<1, /* download bit (0 == download) */
ISApr= 1<<0, /* program ready */
ISActl2= 2, /* board control */
ISA186ien= 1<<7, /* I186 irq enable bit state */
ISA186idata= 1<<6, /* I186 irq data bit state */
ISAmen= 1<<4, /* enable memory to respond to ISA cycles */
ISAmbank= 0xf<<0, /* shift for 4 bit memory bank */
ISAmaddr= 3, /* bits 14-19 of the boards mem address */
ISAstat1= 4, /* board status (1 bit per channel) */
ISAstat2= 5, /* board status (1 bit per channel) */
Pageshift= 14, /* footprint of card mem in ISA space */
Pagesize= 1<<Pageshift,
Pagemask= Pagesize-1,
PCIrange= 0x00,
PCIremap= 0x04,
PCIregion= 0x18,
PCImailbox= 0x40,
PCIdoorbell0= 0x60,
PCIdoorbell1= 0x64,
PCIcontrol= 0x68, /* write */
PCIstatus= 0x68, /* read */
PCIcommand= 0x6C,
Maxcard= 8,
Pramsize= 64*1024, /* size of program ram */
};
#define APAGE(x) ((x)>>Pageshift)
#define LOCKPAGE(a, o) if((a)->needpage){ilock(&(a)->pagelock);setpage(a, o);}
#define UNLOCKPAGE(a) if((a)->needpage)iunlock(&(a)->pagelock)
/* IRQ codes */
static int isairqcode[16] =
{
-1, -1, -1, 0<<4,
1<<4, 2<<4, -1, -1,
-1, 3<<4, 4<<4, 5<<4,
6<<4, -1, -1, 7<<4,
};
/* control program global control block */
typedef struct GCB GCB;
struct GCB
{
ushort cmd; /* command word */
ushort status; /* status word */
ushort serv; /* service request, must be accessed via exchange 'X' */
ushort avail; /* available buffer space */
ushort type; /* board type */
ushort cpvers; /* control program version */
ushort ccbn; /* control channel block count */
ushort ccboff; /* control channel block offset */
ushort ccbsz; /* control channel block size */
ushort cmd2; /* command word 2 */
ushort status2; /* status word 2 */
ushort errserv; /* comm error service request 'X' */
ushort inserv; /* input buffer service request 'X' */
ushort outserv; /* output buffer service request 'X' */
ushort modemserv; /* modem change service request 'X' */
ushort cmdserv; /* channel command service request 'X' */
};
enum
{
/* GCB.cmd commands/codes */
Greadycmd= 0,
Gdiagcmd= 1,
Gresetcmd= 2,
/* GCB.status values */
Gready= 0,
Gstopped= 1,
Gramerr= 2,
Gbadcmd= 3,
Gbusy= 4,
/* GCB.type values */
Gx00m= 0x6,
G100e= 0xA,
Gx00i= 0xC,
/* GCB.cmd2 bit */
Gintack= 0x1,
/* GCB.status2 bits */
Ghas232= (1<<0),
Ghas422= (1<<1),
Ghasmodems= (1<<2),
Ghasrj11s= (1<<7),
Ghasring= (1<<8),
Ghasdcd= (1<<9),
Ghasdtr= (1<<10),
Ghasdsr= (1<<11),
Ghascts= (1<<12),
Ghasrts= (1<<13),
};
/* control program channel control block */
typedef struct CCB CCB;
struct CCB
{
ushort baud; /* baud rate */
ushort format; /* data format */
ushort proto; /* line protocol */
ushort insize; /* input buffer size */
ushort outsize; /* output buffer size */
ushort intrigger; /* input buffer trigger rate */
ushort outlow; /* output buffer low water mark */
char xon[2]; /* xon characters */
ushort inhigh; /* input buffer high water mark */
ushort inlow; /* input buffer low water mark */
ushort cmd; /* channel command */
ushort status; /* channel status */
ushort inbase; /* input buffer start addr */
ushort inlim; /* input buffer ending addr */
ushort outbase; /* output buffer start addr */
ushort outlim; /* output buffer ending addr */
ushort inwp; /* input read and write pointers */
ushort inrp;
ushort outwp; /* output read and write pointers */
ushort outrp;
ushort errstat; /* error status */
ushort badp; /* bad character pointer */
ushort mctl; /* modem control */
ushort mstat; /* modem status */
ushort bstat; /* blocking status */
ushort rflag; /* character received flag */
char xoff[2]; /* xoff characters */
ushort status2;
char strip[2]; /* strip/error characters */
};
enum
{
/* special baud rate codes for CCB.baud */
Cb76800= 0xff00,
Cb115200= 0xff01,
/* CCB.format fields */
Clenmask= 3<<0, /* data bits */
C1stop= 0<<2, /* stop bits */
C2stop= 1<<2,
Cnopar= 0<<3, /* parity */
Coddpar= 1<<3,
Cevenpar= 3<<3,
Cmarkpar= 5<<3,
Cspacepar= 7<<3,
Cparmask= 7<<3,
Cnormal= 0<<6, /* normal mode */
Cecho= 1<<6, /* echo mode */
Clloop= 2<<6, /* local loopback */
Crloop= 3<<6, /* remote loopback */
/* CCB.proto fields */
Cobeyxon= 1<<0, /* obey received xoff/xon controls */
Canyxon= 1<<1, /* any rcvd character restarts xmit */
Cgenxon= 1<<2, /* generate xoff/xon controls */
Cobeycts= 1<<3, /* obey hardware flow ctl */
Cgendtr= 1<<4, /* dtr off when uart rcvr full */
C½duplex= 1<<5, /* rts off while xmitting */
Cgenrts= 1<<6, /* generate hardware flow ctl */
Cmctl= 1<<7, /* direct modem control via CCB.mctl */
Cstrip= 1<<12, /* to strip out characters */
Ceia422= 1<<13, /* to select eia 422 lines */
/* CCB.cmd fields */
Cconfall= 1<<0, /* configure channel and UART */
Cconfchan= 1<<1, /* configure just channel */
Cflushin= 1<<2, /* flush input buffer */
Cflushout= 1<<3, /* flush output buffer */
Crcvena= 1<<4, /* enable receiver */
Crcvdis= 1<<5, /* disable receiver */
Cxmtena= 1<<6, /* enable transmitter */
Cxmtdis= 1<<7, /* disable transmitter */
Cmreset= 1<<9, /* reset modem */
/* CCB.errstat fields */
Coverrun= 1<<0,
Cparity= 1<<1,
Cframing= 1<<2,
Cbreak= 1<<3,
/* CCB.mctl fields */
Cdtrctl= 1<<0,
Crtsctl= 1<<1,
Cbreakctl= 1<<4,
/* CCB.mstat fields */
Cctsstat= 1<<0,
Cdsrstat= 1<<1,
Cristat= 1<<2,
Cdcdstat= 1<<3,
/* CCB.bstat fields */
Cbrcvoff= 1<<0,
Cbxmtoff= 1<<1,
Clbxoff= 1<<2, /* transmitter blocked by XOFF */
Clbcts= 1<<3, /* transmitter blocked by CTS */
Crbxoff= 1<<4, /* remote blocked by xoff */
Crbrts= 1<<4, /* remote blocked by rts */
};
/* host per controller info */
struct Astar
{
QLock; /* lock for rendez */
Rendez r; /* waiting for command completion */
ISAConf;
Pcidev* pci;
Lock pagelock; /* lock for setting page */
int page; /* page currently mapped */
int id; /* from plan9.ini */
int nchan; /* number of channels */
Astarchan *c; /* channels */
int ramsize; /* 16k or 256k */
int memsize; /* size of memory currently mapped */
int needpage;
int pagebase; /* pci */
GCB *gcb; /* global board comm area */
uchar *addr; /* base of memory area */
int running;
};
/* host per channel info */
struct Astarchan
{
QLock; /* lock for rendez */
Rendez r; /* waiting for command completion */
Astar *a; /* controller */
CCB *ccb; /* channel control block */
int perm;
int opens;
int baud; /* baud rate */
int framing; /* framing errors */
int overrun; /* overruns */
int dtr; /* non-zero means dtr on */
int rts; /* non-zero means rts on */
Queue *iq;
Queue *oq;
};
Astar *astar[Maxcard];
static int nastar;
enum
{
Qmem= 1,
Qbctl,
Qdata,
Qctl,
Qstat,
};
#define TYPE(x) ((x)&0xff)
#define BOARD(x) (((x)>>16)&0xff)
#define CHAN(x) (((x)>>8)&0xff)
#define QID(b,c,t) (((b)<<16)|((c)<<8)|(t))
static int astarsetup(Astar*);
static void astarintr(Ureg*, void*);
static void astarkick(Astarchan*);
static void astarkickin(Astarchan*);
static void enable(Astarchan*);
static void disable(Astarchan*);
static void astarctl(Astarchan*, char*);
/*
* Only 16k maps into ISA space
*/
static void
setpage(Astar *a, ulong offset)
{
int i;
if(a->pci){
print("#G%d: setpage caller pc %uX\n", a->id, getcallerpc(a));
return;
}
i = APAGE(offset);
if(i == a->page)
return;
outb(a->port+ISActl2, ISAmen|i);
a->page = i;
}
/*
* generate the astar directory entries
*/
static int
astargen(Chan *c, Dirtab *, int , int i, Dir *db)
{
int dev, sofar, ch, t;
extern ulong kerndate;
memset(db, 0, sizeof(Dir));
sofar = 0;
for(dev = 0; dev < nastar; dev++){
if(sofar == i){
sprint(db->name, "astar%dmem", astar[dev]->id);
db->qid.path = QID(dev, 0, Qmem);
db->mode = 0660;
db->length1 = astar[dev]->memsize; /* BOTCH */
db->length2 = astar[dev]->memsize; /* BOTCH */
break;
}
sofar++;
if(sofar == i){
sprint(db->name, "astar%dctl", astar[dev]->id);
db->qid.path = QID(dev, 0, Qbctl);
db->mode = 0660;
break;
}
sofar++;
if(i - sofar < 3*astar[dev]->nchan){
i -= sofar;
ch = i/3;
t = i%3;
switch(t){
case 0:
sprint(db->name, "eia%d%2.2d", astar[dev]->id, ch);
db->mode = astar[dev]->c[ch].perm;
db->qid.path = QID(dev, ch, Qdata);
break;
case 1:
sprint(db->name, "eia%d%2.2dctl", astar[dev]->id, ch);
db->mode = astar[dev]->c[ch].perm;
db->qid.path = QID(dev, ch, Qctl);
break;
case 2:
sprint(db->name, "eia%d%2.2dstat", astar[dev]->id, ch);
db->mode = 0444;
db->qid.path = QID(dev, ch, Qstat);
break;
}
break;
}
sofar += 3*astar[dev]->nchan;
}
if(dev == nastar)
return -1;
db->qid.vers = 0;
db->atime = seconds();
db->mtime = kerndate;
memmove(db->uid, eve, NAMELEN);
memmove(db->gid, eve, NAMELEN);
db->type = devtab[c->type]->dc;
db->dev = c->dev;
if(c->flag&CMSG)
db->mode |= CHMOUNT;
return 1;
}
static void
astarreset(void)
{
int i, x;
Astar *a;
Pcidev *p;
p = nil;
for(i = 0; i < Maxcard; i++){
a = astar[nastar] = xalloc(sizeof(Astar));
if(isaconfig("serial", i, a) == 0){
xfree(a);
astar[nastar] = 0;
continue;
}
a->ramsize = 0;
/* check all possible names */
if(cistrcmp(a->type, "a100i") == 0 || cistrcmp(a->type,"A100I") == 0)
a->ramsize = 16*1024;
else if(cistrcmp(a->type, "a200i") == 0 ||
cistrcmp(a->type,"A200I") == 0 ||
cistrcmp(a->type, "a16i") == 0)
a->ramsize = 256*1024;
else if(cistrcmp(a->type, "AvanstarXp") == 0){
if(p = pcimatch(p, 0x114F, 0x6001)){
a->pci = p;
/*
* It's really 128KB, but split into
* two 64KB chunks.
*/
a->ramsize = 64*1024;
}
}
if(a->ramsize == 0){
xfree(a);
astar[nastar] = 0;
continue;
}
a->id = i;
if(a->pci){
a->irq = p->intl;
a->port = p->mem[1].bar & ~0x03;
a->mem = upamalloc(p->mem[2].bar & ~0x0F, p->mem[2].size, 0);
a->addr = (uchar*)a->mem;
a->gcb = (GCB*)(a->mem+0x10000);
/*
* Toggle the software reset and wait for
* the adapter local init status to indicate done.
*/
outl(a->port+PCIremap, 0xA0000001);
x = inl(a->port+PCIcommand);
outl(a->port+PCIcommand, 0x40000000|x);
microdelay(1);
outl(a->port+PCIcommand, x);
delay(100);
for(x = 0; x < 10000; x++){
if(inl(a->port+PCIcommand) & 0x80000000)
break;
}
if(!(inl(a->port+PCIcommand) & 0x80000000))
print("#G: didn't reset\n", a->id);
/*
* So the memory can be read before any other
* initialisation takes place.
*/
a->memsize = a->ramsize;
}
else{
/* defaults */
if(a->irq == 0)
a->irq = 15;
a->mem = umbmalloc(a->mem, Pagesize, Pagesize);
if(a->mem == 0)
panic("astarreset: %lux", a->mem);
a->mem = PADDR(a->mem);
if(astarsetup(a) < 0){
xfree(a);
astar[nastar] = 0;
continue;
}
print("\tctl1 %ux ctl2 %ux maddr %ux stat1 %ux stat2 %ux\n",
inb(a->port+ISActl1), inb(a->port+ISActl2), inb(a->port+ISAmaddr),
inb(a->port+ISAstat1), inb(a->port+ISAstat2));
}
print("#G%d: %s port 0x%luX addr 0x%luX irq %d\n",
a->id, a->type, a->port, a->addr, a->irq);
nastar++;
}
}
/* isa ports an ax00i can appear at */
static int isaport[] = { 0x200, 0x208, 0x300, 0x308, 0x600, 0x608, 0x700, 0x708, 0 };
static int
astarprobe(int port)
{
uchar c, c1;
if(port < 0)
return 0;
c = inb(port+ISAid);
c1 = inb(port+ISAid);
return (c == ISAid0 && c1 == ISAid1)
|| (c == ISAid1 && c1 == ISAid0)
|| (c == ISAid0x && c1 == ISAid1x)
|| (c == ISAid1x && c1 == ISAid0x);
}
static int
astarsetup(Astar *a)
{
int i, found;
/* see if the card exists */
found = 0;
if(a->port == 0)
for(i = 0; isaport[i]; i++){
a->port = isaport[i];
found = astarprobe(isaport[i]);
if(found){
isaport[i] = -1;
break;
}
}
else
found = astarprobe(a->port);
if(!found){
print("#G%d: not found\n", a->id);
return -1;
}
/* check interrupt level */
if(isairqcode[a->irq] == -1){
print("#G%d: bad irq %d\n", a->id, a->irq);
return -1;
}
/* set ISA memory address */
outb(a->port+ISAmaddr, (a->mem>>12) & 0xfc);
a->gcb = KADDR(a->mem);
a->addr = KADDR(a->mem);
/* disable ISA memory response */
outb(a->port+ISActl2, 0);
a->memsize = 0;
a->page = -1;
/* reset processor */
outb(a->port+ISActl1, 0);
return 0;
}
static Chan*
astarattach(char *spec)
{
return devattach('G', spec);
}
static int
astarwalk(Chan *c, char *name)
{
return devwalk(c, name, 0, 0, astargen);
}
static void
astarstat(Chan *c, char *dp)
{
devstat(c, dp, 0, 0, astargen);
}
static Chan*
astaropen(Chan *c, int omode)
{
Astar *a;
Astarchan *ac;
c = devopen(c, omode, 0, 0, astargen);
a = astar[BOARD(c->qid.path)];
switch(TYPE(c->qid.path)){
case Qmem:
case Qbctl:
if(!iseve())
error(Eperm);
break;
case Qdata:
case Qctl:
ac = a->c + CHAN(c->qid.path);
qlock(ac);
if(waserror()){
qunlock(ac);
ac->opens--;
nexterror();
}
if(ac->opens++ == 0){
enable(ac);
qreopen(ac->iq);
qreopen(ac->oq);
}
qunlock(ac);
poperror();
break;
}
return c;
}
static void
astarclose(Chan *c)
{
Astar *a;
Astarchan *ac;
if((c->flag & COPEN) == 0)
return;
a = astar[BOARD(c->qid.path)];
switch(TYPE(c->qid.path)){
case Qdata:
case Qctl:
ac = a->c + CHAN(c->qid.path);
qlock(ac);
if(waserror()){
qunlock(ac);
nexterror();
}
if(--(ac->opens) == 0){
disable(ac);
qclose(ac->iq);
qclose(ac->oq);
}
qunlock(ac);
poperror();
break;
}
}
/*
* read PCI mapped memory
*/
static long
pcimemread(Astar *a, uchar *to, long n, ulong offset)
{
uchar *from;
int rem;
if(offset+n > a->memsize){
if(offset >= a->memsize)
return 0;
n = a->memsize - offset;
}
from = a->addr+offset;
for(rem = n; rem > 0; rem--)
*to++ = *from++;
return n;
}
/*
* read ISA mapped memory
*/
static long
isamemread(Astar *a, uchar *to, long n, ulong offset)
{
uchar *from, *e, *tp;
int i, rem;
uchar tmp[256];
if(offset+n > a->memsize){
if(offset >= a->memsize)
return 0;
n = a->memsize - offset;
}
for(rem = n; rem > 0; rem -= i){
/* map in right piece of memory */
i = offset&Pagemask;
from = a->addr + i;
i = Pagesize - i;
if(i > rem)
i = rem;
if(i > sizeof(tmp))
i = sizeof(tmp);
/*
* byte at a time so endian doesn't matter,
* go via tmp to avoid pagefaults while ilock'd
*/
tp = tmp;
LOCKPAGE(a, offset);
for(e = tp + i; tp < e;)
*tp++ = *from++;
UNLOCKPAGE(a);
memmove(to, tmp, i);
to += i;
offset += i;
}
return n;
}
/*
* read ISA status
*/
static long
bctlread(Astar *a, void *buf, long n, ulong offset)
{
char s[128];
if(a->pci)
sprint(s, "range %uX remap %uX region %uX mailbox %uX doorbell0 %uX doorbell1 %uX control %uX command %uX",
inl(a->port+PCIrange),
inl(a->port+PCIremap),
inl(a->port+PCIregion),
inl(a->port+PCImailbox),
inl(a->port+PCIdoorbell0),
inl(a->port+PCIdoorbell1),
inl(a->port+PCIcontrol),
inl(a->port+PCIcommand));
else
sprint(s, "id %4.4ux ctl1 %2.2ux ctl2 %2.2ux maddr %2.2ux stat %4.4ux",
(inb(a->port+ISAid)<<8)|inb(a->port+ISAid),
inb(a->port+ISActl1), inb(a->port+ISActl2),
inb(a->port+ISAmaddr),
(inb(a->port+ISAstat2)<<8)|inb(a->port+ISAstat1));
return readstr(offset, buf, n, s);
}
static long
statread(Astarchan *ac, void *buf, long n, ulong offset)
{
char s[128];
int mstat, bstat;
LOCKPAGE(ac->a, 0);
mstat = LEUS(ac->ccb->mstat);
bstat = LEUS(ac->ccb->bstat);
UNLOCKPAGE(ac->a);
/* CCB.mstat fields */
sprint(s, "opens %d ferr %d oerr %d baud %d", ac->opens, ac->framing,
ac->overrun, ac->baud);
if(mstat & Cctsstat)
strcat(s, " cts");
if(mstat & Cdsrstat)
strcat(s, " dsr");
if(mstat & Cristat)
strcat(s, " ring");
if(mstat & Cdcdstat)
strcat(s, " dcd");
if(ac->dtr)
strcat(s, " dtr");
if(ac->rts && (bstat & Crbrts) == 0)
strcat(s, " rts");
strcat(s, "\n");
return readstr(offset, buf, n, s);
}
static long
astarread(Chan *c, void *buf, long n, vlong off)
{
Astar *a;
Astarchan *ac;
ulong offset = off;
if(c->qid.path & CHDIR)
return devdirread(c, buf, n, 0, 0, astargen);
switch(TYPE(c->qid.path)){
case Qstat:
a = astar[BOARD(c->qid.path)];
return statread(a->c + CHAN(c->qid.path), buf, n, offset);
case Qmem:
a = astar[BOARD(c->qid.path)];
if(a->pci)
return pcimemread(a, buf, n, offset);
return isamemread(a, buf, n, offset);
case Qbctl:
return bctlread(astar[BOARD(c->qid.path)], buf, n, offset);
case Qdata:
a = astar[BOARD(c->qid.path)];
ac = a->c + CHAN(c->qid.path);
return qread(ac->iq, buf, n);
}
return 0;
}
/*
* write PCI mapped memory
*/
static long
pcimemwrite(Astar *a, uchar *from, long n, ulong offset)
{
uchar *to;
int rem;
ulong limit;
/*
* Disallow writes above 0xD000 where the i960
* data structures live if writing in the lower bank.
*/
if(a->addr == (uchar*)a->mem)
limit = 0xD000;
else
limit = a->memsize;
if(offset+n > limit){
if(offset >= limit)
return 0;
n = limit - offset;
}
to = a->addr+offset;
for(rem = n; rem > 0; rem--)
*to++ = *from++;
return n;
}
/*
* write ISA mapped memory
*/
static long
isamemwrite(Astar *a, uchar *from, long n, ulong offset)
{
uchar *to, *e, *tp;
int i, rem;
uchar tmp[256];
if(offset+n > a->memsize){
if(offset >= a->memsize)
return 0;
n = a->memsize - offset;
}
for(rem = n; rem > 0; rem -= i){
/* map in right piece of memory */
i = offset&Pagemask;
to = a->addr + i;
i = Pagesize - i;
if(i > rem)
i = rem;
if(i > sizeof(tmp))
i = sizeof(tmp);
/*
* byte at a time so endian doesn't matter,
* go via tmp to avoid pagefaults while ilock'd
*/
memmove(tmp, from, i);
tp = tmp;
LOCKPAGE(a, offset);
for(e = tp + i; tp < e;)
*to++ = *tp++;
UNLOCKPAGE(a);
from += i;
offset += i;
}
return n;
}
/*
* put board into download mode
*/
static void
downloadmode(Astar *a)
{
int c, i;
/* put board in download mode */
if(a->pci){
/*
* Don't let the download write over the
* i960 data structures.
*/
a->memsize = 0xD000;
a->addr = (uchar*)a->mem;
}
else{
a->memsize = Pramsize;
a->needpage = 1;
c = inb(a->port+ISActl1);
outb(a->port+ISActl1, c & ~ISAnotdl);
/* give it up to 5 seconds to reset */
for(i = 0; i < 21; i++){
if(!(inb(a->port+ISActl1) & ISAnotdl))
break;
tsleep(&a->r, return0, 0, 500);
}
if(inb(a->port+ISActl1) & ISAnotdl){
print("#G%d: did not reset\n", a->id);
error(Eio);
}
/* enable ISA access to first 16k */
a->page = -1;
setpage(a, 0);
}
}
/*
* start control program
*/
static void
startcp(Astar *a)
{
int c, n, i, sz;
uchar *x;
CCB *ccb;
Astarchan *ac;
if(a->running)
error(Eio);
/* take board out of download mode and enable IRQ */
if(a->pci){
outl(a->port+PCImailbox, 1);
/* wait for control program to signal life */
delay(100);
for(i = 0; i < 10; i++){
if(inl(a->port+PCImailbox) & 0x80000000)
break;
tsleep(&a->r, return0, 0, 100);
}
if(!(inl(a->port+PCImailbox) & 0x80000000)){
print("#G%d: program not ready\n", a->id);
//error(Eio);
}
a->addr = (uchar*)(a->mem+0x10000);
}
else{
c = inb(a->port+ISActl1);
outb(a->port+ISActl1, c|ISAien|ISAnotdl);
/* wait for control program to signal life */
for(i = 0; i < 21; i++){
if(inb(a->port+ISActl1) & ISApr)
break;
tsleep(&a->r, return0, 0, 500);
}
if((inb(a->port+ISActl1) & ISApr) == 0){
print("#G%d: program not ready\n", a->id);
error(Eio);
}
}
a->memsize = a->ramsize;
if(a->pci || a->memsize <= Pagesize)
a->needpage = 0;
else{
a->page = -1;
setpage(a, 0);
a->needpage = 1;
}
if(waserror()){
UNLOCKPAGE(a);
poperror();
}
LOCKPAGE(a, 0);
i = LEUS(a->gcb->type);
switch(i){
default:
print("#G%d: wrong board type %uX\n", a->id, i);
error(Eio);
case 0x0C:
case 0x12: /* AvanstarXp */
break;
}
/* check assumptions */
n = LEUS(a->gcb->ccbn);
if(n != 8 && n != 16){
print("#G%d: has %d channels?\n", a->id, i);
error(Eio);
}
x = a->addr + LEUS(a->gcb->ccboff);
sz = LEUS(a->gcb->ccbsz);
if(x+n*sz > a->addr+Pagesize){
print("#G%d: ccb's not in 1st page\n", a->id);
error(Eio);
}
for(i = 0; i < n; i++){
ccb = (CCB*)(x + i*sz);
if(APAGE(LEUS(ccb->inbase)) != APAGE(LEUS(ccb->inlim)) ||
APAGE(LEUS(ccb->outbase)) != APAGE(LEUS(ccb->outlim))){
print("#G%d: chan buffer spans pages\n", a->id);
error(Eio);
}
}
UNLOCKPAGE(a);
poperror();
/* setup the channels */
a->running = 1;
a->nchan = i;
a->c = smalloc(a->nchan * sizeof(Astarchan));
for(i = 0; i < a->nchan; i++){
ac = &a->c[i];
ac->a = a;
ac->ccb = (CCB*)x;
ac->baud = 9600; /* a100i default */
ac->perm = 0660;
ac->iq = qopen(4*1024, 0, astarkickin, ac);
ac->oq = qopen(4*1024, 0, astarkick, ac);
x += sz;
}
/* set up interrupt level, enable interrupts */
if(a->pci){
/*
* Which bits in the interrupt control register should be set?
*/
outl(a->port+PCIcontrol, 0x00031F00);
intrenable(VectorPIC + a->irq, astarintr, a, a->pci->tbdf);
}
else{
c = inb(a->port+ISActl1);
c &= ~ISAirq;
c |= ISAien|isairqcode[a->irq];
outb(a->port+ISActl1, c);
intrenable(VectorPIC + a->irq, astarintr, a, BUSUNKNOWN);
}
/* enable control program interrupt generation */
LOCKPAGE(a, 0);
a->gcb->cmd2 = LEUS(Gintack);
UNLOCKPAGE(a);
}
static void
bctlwrite(Astar *a, char *cmsg)
{
if(waserror()){
qunlock(a);
nexterror();
}
qlock(a);
if(a->running)
error(Eio);
if(strncmp(cmsg, "download", 8) == 0){
/* put board in download mode */
downloadmode(a);
} else if(strncmp(cmsg, "run", 3) == 0){
/* start up downloaded program */
startcp(a);
} else if(strncmp(cmsg, "test", 4) == 0){
uchar *p;
#ifdef notdef
p = a->addr;
a->page = -1;
setpage(a, 16*1024);
*p = 'X';
p = a->addr;
print("page0: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
setpage(a, 2*16*1024);
print("page1: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr;
*p = 'Y';
p = a->addr;
print("page1: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
setpage(a, 16*1024);
p = a->addr;
print("page0: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
#else
p = a->addr;
*p = 'A';
print(" 0K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr+(16*1024);
*p = 'B';
print("16K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr;
print(" 0K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
*p = 'C';
print(" 0K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr+(64*1024);
*p = 'D';
print("64K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr;
print(" 0K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
p = a->addr+(16*1024);
print("16K: %8.8uX %2.2uX\n", inl(a->port+PCIremap), *p);
#endif /* notdef */
} else
error(Ebadarg);
qunlock(a);
poperror();
}
/*
* Send a command to a channel
* (must be called with ac qlocked)
*/
static int
chancmddone(void *arg)
{
Astarchan *ac = arg;
int x;
LOCKPAGE(ac->a, 0);
x = ac->ccb->cmd;
UNLOCKPAGE(ac->a);
return !x;
}
static void
chancmd(Astarchan *ac, int cmd)
{
CCB *ccb;
int status;
ccb = ac->ccb;
LOCKPAGE(ac->a, 0);
ccb->cmd = cmd;
UNLOCKPAGE(ac->a);
/* wait outside of lock */
tsleep(&ac->r, chancmddone, ac, 1000);
LOCKPAGE(ac->a, 0);
status = ccb->status;
cmd = ccb->cmd;
UNLOCKPAGE(ac->a);
if(cmd){
print("#G%d: cmd didn't terminate\n", ac->a->id);
error(Eio);
}
if(status){
print("#G%d: cmd status %ux\n", ac->a->id, status);
error(Eio);
}
}
/*
* enable a channel for IO, set standard params.
* (must be called with ac qlocked)
*/
static void
enable(Astarchan *ac)
{
Astar *a = ac->a;
int n;
/* make sure we control RTS, DTR and break */
LOCKPAGE(a, 0);
n = LEUS(ac->ccb->proto) | Cmctl;
ac->ccb->proto = LEUS(n);
ac->ccb->outlow = 64;
UNLOCKPAGE(a);
chancmd(ac, Cconfall);
astarctl(ac, "l8");
astarctl(ac, "p0");
astarctl(ac, "d1");
astarctl(ac, "r1");
chancmd(ac, Crcvena|Cxmtena|Cconfall);
}
/*
* disable a channel for IO
* (must be called with ac qlocked)
*/
static void
disable(Astarchan *ac)
{
int n;
astarctl(ac, "d0");
astarctl(ac, "r0");
LOCKPAGE(ac->a, 0);
n = LEUS(ac->ccb->proto) | Cmctl;
ac->ccb->proto = LEUS(n);
UNLOCKPAGE(ac->a);
chancmd(ac, Crcvdis|Cxmtdis|Cflushin|Cflushout|Cconfall);
}
/*
* change channel parameters
* (must be called with ac qlocked)
*/
static void
astarctl(Astarchan *ac, char *cmd)
{
int i, n;
int command;
CCB *ccb;
Astar *a;
/* let output drain for a while */
for(i = 0; i < 16 && qlen(ac->oq); i++)
tsleep(&ac->r, qlen, ac->oq, 125);
if(strncmp(cmd, "break", 5) == 0)
cmd = "k";
ccb = ac->ccb;
command = 0;
i = atoi(cmd+1);
a = ac->a;
LOCKPAGE(a, 0);
switch(*cmd){
case 'B':
case 'b':
/* set baud rate (high rates are special - only 16 bits) */
switch(i){
case 76800:
ccb->baud = LEUS(Cb76800);
break;
case 115200:
ccb->baud = LEUS(Cb115200);
break;
default:
ccb->baud = LEUS(i);
break;
}
ac->baud = i;
/* set trigger level to about 50 per second */
n = i/500;
i = (LEUS(ccb->inlim) - LEUS(ccb->inbase))/2;
if(n > i)
n = i;
ccb->intrigger = LEUS(n);
command = Cconfall;
break;
case 'D':
case 'd':
n = LEUS(ccb->mctl);
if(i)
n |= Cdtrctl;
else
n &= ~Cdtrctl;
ac->dtr = i;
ccb->mctl = LEUS(n);
break;
case 'f':
case 'F':
qflush(ac->oq);
break;
case 'H':
case 'h':
qhangup(ac->iq, nil);
qhangup(ac->oq, nil);
break;
case 'L':
case 'l':
n = i - 5;
if(n < 0 || n > 3)
error(Ebadarg);
n |= LEUS(ccb->format) & ~Clenmask;
ccb->format = LEUS(n);
command = Cconfall;
break;
case 'm':
case 'M':
/* turn on cts */
n = LEUS(ccb->proto);
if(i){
n |= Cobeycts|Cgenrts;
n &= ~Cmctl;
} else {
n &= ~(Cobeycts|Cgenrts);
n |= Cmctl;
}
ccb->proto = LEUS(n);
command = Cconfall;
break;
case 'n':
case 'N':
qnoblock(ac->oq, i);
break;
case 'P':
case 'p':
switch(*(cmd+1)){
case 'e':
n = Cevenpar;
break;
case 'o':
n = Coddpar;
break;
default:
n = Cnopar;
break;
}
n |= LEUS(ccb->format) & ~Cparmask;
ccb->format = LEUS(n);
command = Cconfall;
break;
case 'K':
case 'k':
if(i <= 0)
i = 250;
n = LEUS(ccb->mctl) | Cbreakctl;
ccb->mctl = LEUS(n);
UNLOCKPAGE(a);
tsleep(&ac->r, return0, 0, i);
LOCKPAGE(a, 0);
n &= ~Cbreakctl;
ccb->mctl = LEUS(n);
break;
case 'R':
case 'r':
n = LEUS(ccb->mctl);
if(i)
n |= Crtsctl;
else
n &= ~Crtsctl;
ccb->mctl = LEUS(n);
ac->rts = i;
break;
case 'Q':
case 'q':
qsetlimit(ac->iq, i);
qsetlimit(ac->oq, i);
break;
case 'X':
case 'x':
n = LEUS(ccb->proto);
if(i)
n |= Cobeyxon;
else
n &= ~Cobeyxon;
ccb->proto = LEUS(n);
command = Cconfall;
break;
}
UNLOCKPAGE(a);
if(command)
chancmd(ac, command);
}
static long
astarwrite(Chan *c, void *buf, long n, vlong off)
{
Astar *a;
Astarchan *ac;
char cmsg[32];
ulong offset = off;
if(c->qid.path & CHDIR)
error(Eperm);
a = astar[BOARD(c->qid.path)];
switch(TYPE(c->qid.path)){
case Qmem:
if(a->pci)
return pcimemwrite(a, buf, n, offset);
return isamemwrite(a, buf, n, offset);
case Qbctl:
if(n > sizeof cmsg)
n = sizeof(cmsg) - 1;
memmove(cmsg, buf, n);
cmsg[n] = 0;
bctlwrite(a, cmsg);
return n;
case Qdata:
ac = a->c + CHAN(c->qid.path);
return qwrite(ac->oq, buf, n);
case Qctl:
ac = a->c + CHAN(c->qid.path);
if(n > sizeof cmsg)
n = sizeof(cmsg) - 1;
memmove(cmsg, buf, n);
cmsg[n] = 0;
if(waserror()){
qunlock(ac);
nexterror();
}
qlock(ac);
astarctl(ac, cmsg);
qunlock(ac);
poperror();
}
return 0;
}
static void
astarwstat(Chan *c, char *dp)
{
Dir d;
Astarchan *ac;
if(!iseve())
error(Eperm);
if(CHDIR & c->qid.path)
error(Eperm);
if(TYPE(c->qid.path) != Qdata && TYPE(c->qid.path) != Qctl)
error(Eperm);
ac = astar[BOARD(c->qid.path)]->c + CHAN(c->qid.path);
convM2D(dp, &d);
d.mode &= 0666;
ac->perm = d.mode;
}
Dev astardevtab = {
'G',
"astar",
astarreset,
devinit,
astarattach,
devclone,
astarwalk,
astarstat,
astaropen,
devcreate,
astarclose,
astarread,
devbread,
astarwrite,
devbwrite,
devremove,
astarwstat,
};
/*
* get output going
*/
static void
astaroutput(Astarchan *ac)
{
Astar *a = ac->a;
CCB *ccb = ac->ccb;
uchar buf[256];
uchar *rp, *wp, *bp, *ep, *p, *e;
int n;
if(a->needpage)
setpage(a, 0);
ep = a->addr;
rp = ep + LEUS(ccb->outrp);
wp = ep + LEUS(ccb->outwp);
bp = ep + LEUS(ccb->outbase);
ep = ep + LEUS(ccb->outlim);
for(;;){
n = rp - wp - 1;
if(n < 0)
n += ep - bp + 1;
if(n == 0)
break;
if(n > sizeof(buf))
n = sizeof(buf);
n = qconsume(ac->oq, buf, n);
if(n <= 0)
break;
if(a->needpage)
setpage(a, bp - a->addr);
e = buf + n;
for(p = buf; p < e;){
*wp++ = *p++;
if(wp > ep)
wp = bp;
}
if(a->needpage)
setpage(a, 0);
ccb->outwp = LEUS(wp - a->addr);
}
}
static void
astarkick(Astarchan *ac)
{
ilock(&ac->a->pagelock);
astaroutput(ac);
iunlock(&ac->a->pagelock);
}
/*
* process input
*/
static void
astarinput(Astarchan *ac)
{
Astar *a = ac->a;
CCB *ccb = ac->ccb;
uchar buf[256];
uchar *rp, *wp, *bp, *ep, *p, *e;
int n;
if(a->needpage)
setpage(a, 0);
ep = a->addr;
rp = ep + LEUS(ccb->inrp);
wp = ep + LEUS(ccb->inwp);
bp = ep + LEUS(ccb->inbase);
ep = ep + LEUS(ccb->inlim);
for(;;){
n = wp - rp;
if(n == 0)
break;
if(n < 0)
n += ep - bp + 1;
if(n > sizeof(buf))
n = sizeof(buf);
if(a->needpage)
setpage(a, bp - a->addr);
e = buf + n;
for(p = buf; p < e;){
*p++ = *rp++;
if(rp > ep)
rp = bp;
}
if(ac->opens == 0 || qproduce(ac->iq, buf, n) < 0)
break; /* flow controlled or not open */
if(a->needpage)
setpage(a, 0);
ccb->inrp = LEUS(rp - a->addr);
}
if(a->needpage)
setpage(a, 0);
}
/*
* get flow controlled input going again
*/
static void
astarkickin(Astarchan *ac)
{
ilock(&ac->a->pagelock);
astarinput(ac);
iunlock(&ac->a->pagelock);
}
/*
* handle an interrupt
*/
static void
astarintr(Ureg *ur, void *arg)
{
Astar *a = arg;
Astarchan *ac;
ulong globvec, vec, invec, outvec, errvec, mvec, cmdvec;
int c, status;
USED(ur);
if(a->running == 0)
panic("#G%d: interrupt but cp not running\n", a->id);
lock(&a->pagelock);
if(a->needpage)
setpage(a, 0);
/* get causes */
globvec = LEUS(xchgw(&a->gcb->serv, 0));
USED(globvec);
invec = LEUS(xchgw(&a->gcb->inserv, 0));
outvec = LEUS(xchgw(&a->gcb->outserv, 0));
errvec = LEUS(xchgw(&a->gcb->errserv, 0));
mvec = LEUS(xchgw(&a->gcb->modemserv, 0));
cmdvec = LEUS(xchgw(&a->gcb->cmdserv, 0));
USED(mvec);
/* reenable interrupts */
if(a->pci){
/*
* Only the PCI doorbell interrupt is expected.
*/
status = inl(a->port+PCIstatus);
if((status & 0x0810E000) != 0x00002000)
print("#G%d: unexpected interrupt %uX\n", a->id, status);
if(status & 0x00002000)
outl(a->port+PCIdoorbell1, 1);
}
a->gcb->cmd2 = LEUS(Gintack);
/* service interrupts */
ac = a->c;
for(vec = invec; vec; vec >>= 1){
if(vec&1)
astarinput(ac);
ac++;
}
ac = a->c;
for(vec = outvec; vec; vec >>= 1){
if(vec&1)
astaroutput(ac);
ac++;
}
ac = a->c;
for(vec = cmdvec; vec; vec >>= 1){
if(vec&1)
wakeup(&ac->r);
ac++;
}
ac = a->c;
for(vec = errvec; vec; vec >>= 1){
c = LEUS(ac->ccb->errstat);
if(c & Cframing)
ac->framing++;
if(c & Coverrun)
ac->overrun++;
ac++;
}
unlock(&a->pagelock);
}