diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /emu/Nt/deveia.c | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'emu/Nt/deveia.c')
| -rw-r--r-- | emu/Nt/deveia.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/emu/Nt/deveia.c b/emu/Nt/deveia.c new file mode 100644 index 00000000..4294b8fe --- /dev/null +++ b/emu/Nt/deveia.c @@ -0,0 +1,672 @@ +/* + * Windows serial driver + * + * to do: + * scan the registry for serial ports? + */ + +#define Unknown win_Unknown +#include <windows.h> +#undef Unknown +#undef Sleep +#include "dat.h" +#include "fns.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <lm.h> +#include <direct.h> + +// local fcts +static void openport(int); +static void wrctl(int, char*); +static long rdstat(int, void*, long, ulong ); + +enum +{ + Devchar = 't', + + Ndataqid = 1, + Nctlqid, + Nstatqid, + Nqid = 3, /* number of QIDs */ + + Maxctl = 128, + + // in/out buffer sizes for comm port (NT requires an even number) + // set it to x* the max styx message rounded up to the + // nearest 4 byte value + CommBufSize = ((((8192+128)*2)+3) & ~3) +}; + +/* + * Macros to manage QIDs + */ +#define NETTYPE(x) ((x)&0x0F) +#define NETID(x) ((x)>>4) +#define NETQID(i,t) (((i)<<4)|(t)) + +static Dirtab *eiadir; +static int ndir; + +typedef struct Eia Eia; +struct Eia { + Ref r; + HANDLE comfh; //handle to open port + int restore; //flag to restore prev. states + DCB dcb; //win32 device control block used for restore + int id; //index to host port name in sysdev +}; + +// the same timeouts are used for all ports +// currently there is no Inferno interface to +// change the timeouts. +static COMMTIMEOUTS timeouts; + +// std win32 serial port names are COM1..COM4 +// however there can be more and they can be +// named anything. we should be more flexible +// pehaps adding a ctl command to allow you to +// access any win32 comm port +static char* sysdev[] = { + "COM1:", + "COM2:", + "COM3:", + "COM4:", + "COM5:", + "COM6:", + "COM7:", + "COM8:", + NULL +}; + +static Eia *eia; + +typedef struct OptTable OptTable; +struct OptTable { + char *str; + DWORD flag; +}; + +#define BAD ((DWORD)-1) + +// valid bit-per-byte sizes +static OptTable size[] = { + {"5", 5}, + {"6", 6}, + {"7", 7}, + {"8", 8}, + {NULL, BAD} +}; + +// valid stop bits +static OptTable stopbits[] = { + {"1", ONESTOPBIT}, + {"1.5", ONE5STOPBITS}, + {"2", TWOSTOPBITS}, + {NULL, BAD} +}; + +// valid parity settings +static OptTable parity[] = { + {"o", ODDPARITY}, + {"e", EVENPARITY}, + {"s", SPACEPARITY}, + {"m", MARKPARITY}, + {"n", NOPARITY}, + {NULL, NOPARITY} +}; + + +static char * +ftos(OptTable *tbl, DWORD flag) +{ + while(tbl->str && tbl->flag != flag) + tbl++; + if(tbl->str == 0) + return "unknown"; + return tbl->str; +} + +static DWORD +stof(OptTable *tbl, char *str) +{ + while(tbl->str && strcmp(tbl->str, str) != 0) + tbl++; + return tbl->flag; +} + +static void +eiainit(void) +{ + int i,x; + byte ports; //bitmask of active host ports + int nports; //number of active host ports + int max; //number of highest port + Dirtab *dp; + + // setup the timeouts; choose carefully + timeouts.ReadIntervalTimeout = 2; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 200; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 400; + + // check to see which ports exist by trying to open them + // keep results in a bitmask + ports = nports = max = 0; + for(i=0; (sysdev[i] != NULL) && (i<8); i++) { + HANDLE comfh = CreateFile(sysdev[i], 0, 0, NULL, /* no security attrs */ + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if(comfh != INVALID_HANDLE_VALUE) { + ports |= 1<<i; + CloseHandle(comfh); + nports++; + max = i; + } + } + + if(nports == 0) + return; //no ports + + // allocate directory table and eia structure + // for each active port. + ndir = Nqid*nports+1; + dp = eiadir = calloc(ndir, sizeof(Dirtab)); + if(dp == 0) + panic("eiainit"); + eia = calloc(nports, sizeof(Eia)); + if(eia == 0) { + free(dp); + panic("eiainit"); + } + + // fill in the directory table and initialize + // the eia structure. skip inactive ports. + sprint(dp->name, "."); + dp->qid.path = 0; + dp->qid.type = QTDIR; + dp->perm = DMDIR|0555; + dp++; + x = 0; // index in eia[] + for(i = 0; i <= max; i++) { + if( (ports & (1<<i)) == 0) + continue; //port 'i' is not active + sprint(dp->name, "eia%d", i); + dp->qid.path = NETQID(x, Ndataqid); + dp->perm = 0660; + dp++; + sprint(dp->name, "eia%dctl", i); + dp->qid.path = NETQID(x, Nctlqid); + dp->perm = 0660; + dp++; + sprint(dp->name, "eia%dstatus", i); + dp->qid.path = NETQID(x, Nstatqid); + dp->perm = 0660; + dp++; + // init the eia structure + eia[x].restore = 0; + eia[x].id = i; + x++; + } +} + +static Chan* +eiaattach(char *spec) +{ + if(eiadir == nil) + error(Enodev); + + return devattach(Devchar, spec); +} + +static Walkqid* +eiawalk(Chan *c, Chan *nc, char **name, int nname) +{ + return devwalk(c, nc, name, nname, eiadir, ndir, devgen); +} + +static int +eiastat(Chan *c, uchar *db, int n) +{ + return devstat(c, db, n, eiadir, ndir, devgen); +} + +static Chan* +eiaopen(Chan *c, int mode) +{ + int port = NETID(c->qid.path); + + c = devopen(c, mode, eiadir, ndir, devgen); + + switch(NETTYPE(c->qid.path)) { + case Nctlqid: + case Ndataqid: + case Nstatqid: + if(incref(&eia[port].r) != 1) + break; + if(waserror()) { + decref(&eia[port].r); + nexterror(); + } + openport(port); + poperror(); + break; + } + return c; +} + +static void +eiaclose(Chan *c) +{ + int port = NETID(c->qid.path); + + if((c->flag & COPEN) == 0) + return; + + switch(NETTYPE(c->qid.path)) { + case Nctlqid: + case Ndataqid: + case Nstatqid: + if(decref(&eia[port].r) == 0) { + osenter(); + CloseHandle(eia[port].comfh); + osleave(); + } + break; + } + +} + +static long +eiaread(Chan *c, void *buf, long n, vlong offset) +{ + DWORD cnt; + int port = NETID(c->qid.path); + BOOL good; + + if(c->qid.type & QTDIR) + return devdirread(c, buf, n, eiadir, ndir, devgen); + + switch(NETTYPE(c->qid.path)) { + case Ndataqid: + cnt = 0; + // if ReadFile timeouts and cnt==0 then just re-read + // this will give osleave() a chance to detect an + // interruption (i.e. killprog) + while(cnt==0) { + osenter(); + good = ReadFile(eia[port].comfh, buf, n, &cnt, NULL); + SleepEx(0,FALSE); //allow another thread access to port + osleave(); + if(!good) + oserror(); + } + return cnt; + case Nctlqid: + return readnum(offset, buf, n, eia[port].id, NUMSIZE); + case Nstatqid: + return rdstat(port, buf, n, offset); + } + + return 0; +} + +static long +eiawrite(Chan *c, void *buf, long n, vlong offset) +{ + DWORD cnt; + char cmd[Maxctl]; + int port = NETID(c->qid.path); + BOOL good; + uchar *data; + + if(c->qid.type & QTDIR) + error(Eperm); + + switch(NETTYPE(c->qid.path)) { + case Ndataqid: + cnt = 0; + data = (uchar*)buf; + // if WriteFile times out (i.e. return true; cnt<n) then + // allow osleave() to check for an interrupt otherwise try + // to send the unsent data. + while(n>0) { + osenter(); + good = WriteFile(eia[port].comfh, data, n, &cnt, NULL); + osleave(); + if(!good) + oserror(); + data += cnt; + n -= cnt; + } + return (data-(uchar*)buf); + case Nctlqid: + if(n >= sizeof(cmd)) + n = sizeof(cmd)-1; + memmove(cmd, buf, n); + cmd[n] = 0; + wrctl(port, cmd); + return n; + } + return 0; +} + +static int +eiawstat(Chan *c, uchar *dp, int n) +{ + Dir d; + int i; + + if(!iseve()) + error(Eperm); + if(c->qid.type & QTDIR) + error(Eperm); + if(NETTYPE(c->qid.path) == Nstatqid) + error(Eperm); + + n = convM2D(dp, n, &d, nil); + i = Nqid*NETID(c->qid.path)+NETTYPE(c->qid.path)-Ndataqid; + if(d.mode != ~0UL) + eiadir[i+1].perm = d.mode&0666; + return n; +} + +Dev eiadevtab = { + Devchar, + "eia", + + eiainit, + eiaattach, + eiawalk, + eiastat, + eiaopen, + devcreate, + eiaclose, + eiaread, + devbread, + eiawrite, + devbwrite, + devremove, + eiawstat +}; + + +// +// local functions +// + +/* + * open the indicated comm port and then set + * the default settings for the port. + */ +static void +openport(int port) +{ + Eia* p = &eia[port]; + + // open the port + p->comfh = CreateFile(sysdev[p->id], + GENERIC_READ|GENERIC_WRITE, //open underlying port for rd/wr + 0, //comm port can't be shared + NULL, //no security attrs + OPEN_EXISTING, //a must for comm port + FILE_ATTRIBUTE_NORMAL, //nonoverlapped io + NULL); //another must for comm port + + if(p->comfh == INVALID_HANDLE_VALUE) + oserror(); + if(waserror()){ + CloseHandle(p->comfh); + p->comfh = INVALID_HANDLE_VALUE; + nexterror(); + } + + // setup in/out buffers (NT requires an even number) + if(!SetupComm(p->comfh, CommBufSize, CommBufSize)) + oserror(); + + // either use existing settings or set defaults + if(!p->restore) { + // set default settings + if(!GetCommState(p->comfh, &p->dcb)) + oserror(); + p->dcb.BaudRate = 9600; + p->dcb.ByteSize = 8; + p->dcb.fParity = 0; + p->dcb.Parity = NOPARITY; + p->dcb.StopBits = ONESTOPBIT; + p->dcb.fInX = 0; //default to xoff + p->dcb.fOutX = 0; + p->dcb.fAbortOnError = 1; //read/write abort on err + } + + // set state and timeouts + if(!SetCommState(p->comfh, &p->dcb) || + !SetCommTimeouts(p->comfh, &timeouts)) + oserror(); + poperror(); +} + +/* + * Obtain status information on the com port. + */ +static long +rdstat(int port, void *buf, long n, ulong offset) +{ + HANDLE comfh = eia[port].comfh; + char str[Maxctl]; + char *s; + DCB dcb; + DWORD modemstatus; + DWORD porterr; + COMSTAT portstat; + int frame, overrun, i; + + // valid line control ids + static enum { + L_CTS, L_DSR, L_RING, L_DCD, L_DTR, L_RTS, L_MAX + }; + int status[L_MAX]; + + // line control strings (should match above id's) + static char* lines[] = { + "cts", "dsr", "ring", "dcd", "dtr", "rts", NULL + }; + + + // get any error conditions; also clears error flag + // and enables io + if(!ClearCommError(comfh, &porterr, &portstat)) + oserror(); + + // get comm port state + if(!GetCommState(comfh, &dcb)) + oserror(); + + // get modem line information + if(!GetCommModemStatus(comfh, &modemstatus)) + oserror(); + + // now set our local flags + status[L_CTS] = MS_CTS_ON & modemstatus; + status[L_DSR] = MS_DSR_ON & modemstatus; + status[L_RING] = MS_RING_ON & modemstatus; + status[L_DCD] = MS_RLSD_ON & modemstatus; + status[L_DTR] = FALSE; //?? cand this work: dcb.fDtrControl; + status[L_RTS] = FALSE; //?? dcb.fRtsControl; + frame = porterr & CE_FRAME; + overrun = porterr & CE_OVERRUN; + + /* TO DO: mimic native eia driver's first line */ + + s = seprint(str, str+sizeof(str), "opens %d ferr %d oerr %d baud %d", + eia[port].r.ref-1, + frame, + overrun, + dcb.BaudRate); + + // add line settings + for(i=0; i < L_MAX; i++) + if(status[i]) + s = seprint(s, str+sizeof(str), " %s", lines[i]); + seprint(s, str+sizeof(str), "\n"); + return readstr(offset, buf, n, str); +} + +// +// write on ctl file. modify the settings for +// the underlying port. +// +static void +wrctl(int port, char *cmd) +{ + DCB dcb; + int nf, n, i; + char *f[16]; + HANDLE comfh = eia[port].comfh; + DWORD flag, opt; + BOOL rslt; + int chg; + + // get the current settings for the port + if(!GetCommState(comfh, &dcb)) + oserror(); + + chg = 0; + nf = tokenize(cmd, f, nelem(f)); + for(i = 0; i < nf; i++){ + if(strcmp(f[i], "break") == 0){ + if(!SetCommBreak(comfh)) + oserror(); + SleepEx((DWORD)300, FALSE); + if(!ClearCommBreak(comfh)) + oserror(); + continue; + } + + n = atoi(f[i]+1); + switch(*f[i]) { + case 'B': + case 'b': // set the baud rate + if(n < 110) + error(Ebadarg); + dcb.BaudRate = n; + chg = 1; + break; + case 'C': + case 'c': + /* dcd */ + break; + case 'D': + case 'd': // set DTR + opt = n ? SETDTR : CLRDTR; + if(!EscapeCommFunction(comfh, opt)) + oserror(); + break; + case 'E': + case 'e': + /* dsr */ + break; + case 'F': + case 'f': // flush any untransmitted data + if(!PurgeComm(comfh, PURGE_TXCLEAR)) + oserror(); + break; + case 'H': + case 'h': + /* hangup */ + /* TO DO: close handle */ + break; + case 'I': + case 'i': + /* fifo: nothing to do */ + break; + case 'K': + case 'k': + /* send a break */ + if(!SetCommBreak(comfh)) + oserror(); + SleepEx((DWORD)300, FALSE); + if(!ClearCommBreak(comfh)) + oserror(); + break; + case 'L': + case 'l': // set bits per byte + flag = stof(size, f[0]+1); + if(flag == BAD) + error(Ebadarg); + dcb.ByteSize = (BYTE)flag; + chg = 1; + break; + case 'M': + case 'm': // set CTS (modem control) + dcb.fOutxCtsFlow = (n!=0); + chg = 1; + break; + case 'N': + case 'n': + /* don't block on output */ + break; + case 'P': + case 'p': // set parity -- even or odd + flag = stof(parity, f[0]+1); + if(flag==BAD) + error(Ebadarg); + dcb.Parity = (BYTE)flag; + chg = 1; + break; + case 'Q': + case 'q': + /* set i/o queue limits */ + break; + case 'R': + case 'r': // set RTS + opt = n ? SETRTS : CLRRTS; + if(!EscapeCommFunction(comfh, opt)) + oserror(); + break; + case 'S': + case 's': // set stop bits -- valid: 1 or 2 (win32 allows 1.5??) + flag = stof(stopbits, f[0]+1); + if(flag==BAD) + error(Ebadarg); + dcb.StopBits = flag; + chg = 1; + break; + case 'T': + case 't': + break; + case 'W': + case 'w': + /* set uart timer */ + break; + case 'X': + case 'x': // xon/xoff + opt = n ? SETXON : SETXOFF; + if(!EscapeCommFunction(comfh, opt)) + oserror(); + break; + default: + /* ignore */ + break; + } + } + + if(!chg) + return; + // make the changes on the underlying port, but flush + // outgoing chars down the port before + osenter(); + rslt = FlushFileBuffers(comfh); + if(rslt) + rslt = SetCommState(comfh, &dcb); + osleave(); + if(!rslt) + oserror(); + eia[port].restore = 1; + eia[port].dcb = dcb; +} |
