/* * Windows serial driver * * to do: * scan the registry for serial ports? */ #define Unknown win_Unknown #include #undef Unknown #undef Sleep #include "dat.h" #include "fns.h" #include "error.h" #include #include #include #include #include #include // 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<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<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; cnt0) { 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; }