xref: /inferno-os/emu/Nt/deveia.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
1 /*
2  *  Windows serial driver
3  *
4  * to do:
5  *	scan the registry for serial ports?
6  */
7 
8 #define Unknown win_Unknown
9 #include	<windows.h>
10 #undef Unknown
11 #undef	Sleep
12 #include	"dat.h"
13 #include	"fns.h"
14 #include	"error.h"
15 #include	<sys/types.h>
16 #include	<sys/stat.h>
17 #include	<fcntl.h>
18 #include	<stdio.h>
19 #include	<lm.h>
20 #include	<direct.h>
21 
22 // local fcts
23 static void openport(int);
24 static void wrctl(int, char*);
25 static long rdstat(int, void*, long, ulong );
26 
27 enum
28 {
29 	Devchar = 't',
30 
31 	Ndataqid = 1,
32 	Nctlqid,
33 	Nstatqid,
34 	Nqid = 3,		/* number of QIDs */
35 
36 	Maxctl = 128,
37 
38 	// in/out buffer sizes for comm port (NT requires an even number)
39 	// set it to x* the max styx message rounded up to the
40 	// nearest 4 byte value
41 	CommBufSize = ((((8192+128)*2)+3) & ~3)
42 };
43 
44 /*
45  *  Macros to manage QIDs
46  */
47 #define NETTYPE(x)	((x)&0x0F)
48 #define NETID(x)	((x)>>4)
49 #define NETQID(i,t)	(((i)<<4)|(t))
50 
51 static Dirtab *eiadir;
52 static int ndir;
53 
54 typedef struct Eia Eia;
55 struct Eia {
56 	Ref	r;
57 	HANDLE      comfh;          //handle to open port
58 	int		restore;       //flag to restore prev. states
59 	DCB		dcb;           //win32 device control block used for restore
60 	int		id;            //index to host port name in sysdev
61 };
62 
63 // the same timeouts are used for all ports
64 // currently there is no Inferno interface to
65 // change the timeouts.
66 static COMMTIMEOUTS  timeouts;
67 
68 // std win32 serial port names are COM1..COM4
69 // however there can be more and they can be
70 // named anything. we should be more flexible
71 // pehaps adding a ctl command to allow you to
72 // access any win32 comm port
73 static char* sysdev[] = {
74 	"COM1:",
75 	"COM2:",
76 	"COM3:",
77 	"COM4:",
78 	"COM5:",
79 	"COM6:",
80 	"COM7:",
81 	"COM8:",
82 	NULL
83 };
84 
85 static Eia *eia;
86 
87 typedef struct OptTable OptTable;
88 struct OptTable {
89 	char   *str;
90 	DWORD  flag;
91 };
92 
93 #define BAD ((DWORD)-1)
94 
95 // valid bit-per-byte sizes
96 static OptTable size[] = {
97 	{"5",	5},
98 	{"6",	6},
99 	{"7",	7},
100 	{"8",	8},
101 	{NULL,  BAD}
102 };
103 
104 // valid stop bits
105 static OptTable stopbits[] = {
106 	{"1",    ONESTOPBIT},
107 	{"1.5",  ONE5STOPBITS},
108 	{"2",    TWOSTOPBITS},
109 	{NULL,   BAD}
110 };
111 
112 // valid parity settings
113 static OptTable parity[] = {
114 	{"o",    ODDPARITY},
115 	{"e",    EVENPARITY},
116 	{"s",    SPACEPARITY},
117 	{"m",    MARKPARITY},
118 	{"n",    NOPARITY},
119 	{NULL,   NOPARITY}
120 };
121 
122 
123 static char *
124 ftos(OptTable *tbl, DWORD flag)
125 {
126 	while(tbl->str && tbl->flag != flag)
127 		tbl++;
128 	if(tbl->str == 0)
129 		return "unknown";
130 	return tbl->str;
131 }
132 
133 static DWORD
134 stof(OptTable *tbl, char *str)
135 {
136 	while(tbl->str && strcmp(tbl->str, str) != 0)
137 		tbl++;
138 	return tbl->flag;
139 }
140 
141 static void
142 eiainit(void)
143 {
144 	int     i,x;
145 	byte    ports;   //bitmask of active host ports
146 	int     nports;  //number of active host ports
147 	int     max;     //number of highest port
148 	Dirtab *dp;
149 
150 	// setup the timeouts; choose carefully
151 	timeouts.ReadIntervalTimeout = 2;
152 	timeouts.ReadTotalTimeoutMultiplier = 0;
153 	timeouts.ReadTotalTimeoutConstant = 200;
154 	timeouts.WriteTotalTimeoutMultiplier = 0;
155 	timeouts.WriteTotalTimeoutConstant = 400;
156 
157 	// check to see which ports exist by trying to open them
158 	// keep results in a bitmask
159 	ports = nports = max = 0;
160 	for(i=0; (sysdev[i] != NULL) && (i<8); i++) {
161 		HANDLE comfh = CreateFile(sysdev[i], 0, 0, NULL,	/* no security attrs */
162 			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
163 
164 		if(comfh != INVALID_HANDLE_VALUE) {
165 			ports |= 1<<i;
166 			CloseHandle(comfh);
167 			nports++;
168 			max = i;
169 		}
170 	}
171 
172 	if(nports == 0)
173 		return;  //no ports
174 
175 	// allocate directory table and eia structure
176 	// for each active port.
177 	ndir = Nqid*nports+1;
178 	dp = eiadir = malloc(ndir*sizeof(Dirtab));
179 	if(dp == 0)
180 		panic("eiainit");
181 	eia = malloc(nports*sizeof(Eia));
182 	if(eia == 0) {
183 		free(dp);
184 		panic("eiainit");
185 	}
186 
187 	// fill in the directory table and initialize
188 	// the eia structure.  skip inactive ports.
189 	sprint(dp->name, ".");
190 	dp->qid.path = 0;
191 	dp->qid.type = QTDIR;
192 	dp->perm = DMDIR|0555;
193 	dp++;
194 	x = 0;  // index in eia[]
195 	for(i = 0; i <= max; i++) {
196 		if( (ports & (1<<i)) == 0)
197 			continue;  //port 'i' is not active
198 		sprint(dp->name, "eia%d", i);
199 		dp->qid.path = NETQID(x, Ndataqid);
200 		dp->perm = 0660;
201 		dp++;
202 		sprint(dp->name, "eia%dctl", i);
203 		dp->qid.path = NETQID(x, Nctlqid);
204 		dp->perm = 0660;
205 		dp++;
206 		sprint(dp->name, "eia%dstatus", i);
207 		dp->qid.path = NETQID(x, Nstatqid);
208 		dp->perm = 0660;
209 		dp++;
210 		// init the eia structure
211 		eia[x].restore = 0;
212 		eia[x].id = i;
213 		x++;
214 	}
215 }
216 
217 static Chan*
218 eiaattach(char *spec)
219 {
220 	if(eiadir == nil)
221 		error(Enodev);
222 
223 	return devattach(Devchar, spec);
224 }
225 
226 static Walkqid*
227 eiawalk(Chan *c, Chan *nc, char **name, int nname)
228 {
229 	return devwalk(c, nc, name, nname, eiadir, ndir, devgen);
230 }
231 
232 static int
233 eiastat(Chan *c, uchar *db, int n)
234 {
235 	return devstat(c, db, n, eiadir, ndir, devgen);
236 }
237 
238 static Chan*
239 eiaopen(Chan *c, int mode)
240 {
241 	int port = NETID(c->qid.path);
242 
243 	c = devopen(c, mode, eiadir, ndir, devgen);
244 
245 	switch(NETTYPE(c->qid.path)) {
246 	case Nctlqid:
247 	case Ndataqid:
248 	case Nstatqid:
249 		if(incref(&eia[port].r) != 1)
250 			break;
251 		if(waserror()) {
252 			decref(&eia[port].r);
253 			nexterror();
254 		}
255 		openport(port);
256 		poperror();
257 		break;
258 	}
259 	return c;
260 }
261 
262 static void
263 eiaclose(Chan *c)
264 {
265 	int port = NETID(c->qid.path);
266 
267 	if((c->flag & COPEN) == 0)
268 		return;
269 
270 	switch(NETTYPE(c->qid.path)) {
271 	case Nctlqid:
272 	case Ndataqid:
273 	case Nstatqid:
274 		if(decref(&eia[port].r) == 0) {
275 			osenter();
276 			CloseHandle(eia[port].comfh);
277 			osleave();
278 		}
279 		break;
280 	}
281 
282 }
283 
284 static long
285 eiaread(Chan *c, void *buf, long n, vlong offset)
286 {
287 	DWORD cnt;
288 	int port = NETID(c->qid.path);
289 	BOOL good;
290 
291 	if(c->qid.type & QTDIR)
292 		return devdirread(c, buf, n, eiadir, ndir, devgen);
293 
294 	switch(NETTYPE(c->qid.path)) {
295 	case Ndataqid:
296 		cnt = 0;
297 		// if ReadFile timeouts and cnt==0 then just re-read
298 		// this will give osleave() a chance to detect an
299 		// interruption (i.e. killprog)
300 		while(cnt==0) {
301   			osenter();
302 			good = ReadFile(eia[port].comfh, buf, n, &cnt, NULL);
303 			SleepEx(0,FALSE);  //allow another thread access to port
304 			osleave();
305 			if(!good)
306 				oserror();
307 		}
308 		return cnt;
309 	case Nctlqid:
310 		return readnum(offset, buf, n, eia[port].id, NUMSIZE);
311 	case Nstatqid:
312 		return rdstat(port, buf, n, offset);
313 	}
314 
315 	return 0;
316 }
317 
318 static long
319 eiawrite(Chan *c, void *buf, long n, vlong offset)
320 {
321 	DWORD cnt;
322 	char cmd[Maxctl];
323 	int port = NETID(c->qid.path);
324 	BOOL good;
325 	uchar *data;
326 
327 	if(c->qid.type & QTDIR)
328 		error(Eperm);
329 
330 	switch(NETTYPE(c->qid.path)) {
331 	case Ndataqid:
332 		cnt = 0;
333 		data = (uchar*)buf;
334 		// if WriteFile times out (i.e. return true; cnt<n) then
335 		// allow osleave() to check for an interrupt otherwise try
336 		// to send the unsent data.
337 		while(n>0) {
338 	  		osenter();
339 			good = WriteFile(eia[port].comfh, data, n, &cnt, NULL);
340 			osleave();
341 			if(!good)
342 				oserror();
343 			data += cnt;
344 			n -= cnt;
345 		}
346 		return (data-(uchar*)buf);
347 	case Nctlqid:
348 		if(n >= sizeof(cmd))
349 			n = sizeof(cmd)-1;
350 		memmove(cmd, buf, n);
351 		cmd[n] = 0;
352 		wrctl(port, cmd);
353 		return n;
354 	}
355 	return 0;
356 }
357 
358 static int
359 eiawstat(Chan *c, uchar *dp, int n)
360 {
361 	Dir d;
362 	int i;
363 
364 	if(!iseve())
365 		error(Eperm);
366 	if(c->qid.type & QTDIR)
367 		error(Eperm);
368 	if(NETTYPE(c->qid.path) == Nstatqid)
369 		error(Eperm);
370 
371 	n = convM2D(dp, n, &d, nil);
372 	i = Nqid*NETID(c->qid.path)+NETTYPE(c->qid.path)-Ndataqid;
373 	if(d.mode != ~0UL)
374 		eiadir[i+1].perm = d.mode&0666;
375 	return n;
376 }
377 
378 Dev eiadevtab = {
379         Devchar,
380         "eia",
381 
382         eiainit,
383         eiaattach,
384         eiawalk,
385         eiastat,
386         eiaopen,
387         devcreate,
388         eiaclose,
389         eiaread,
390         devbread,
391         eiawrite,
392         devbwrite,
393         devremove,
394         eiawstat
395 };
396 
397 
398 //
399 // local functions
400 //
401 
402 /*
403  * open the indicated comm port and then set
404  * the default settings for the port.
405  */
406 static void
407 openport(int port)
408 {
409 	Eia* p = &eia[port];
410 
411 	// open the port
412 	p->comfh = CreateFile(sysdev[p->id],
413 		GENERIC_READ|GENERIC_WRITE,     //open underlying port for rd/wr
414 		0,	                            //comm port can't be shared
415 		NULL,	                        //no security attrs
416 		OPEN_EXISTING,                  //a must for comm port
417 		FILE_ATTRIBUTE_NORMAL,          //nonoverlapped io
418 		NULL);                          //another must for comm port
419 
420 	if(p->comfh == INVALID_HANDLE_VALUE)
421 		oserror();
422 	if(waserror()){
423 		CloseHandle(p->comfh);
424 		p->comfh = INVALID_HANDLE_VALUE;
425 		nexterror();
426 	}
427 
428 	// setup in/out buffers (NT requires an even number)
429 	if(!SetupComm(p->comfh, CommBufSize, CommBufSize))
430 		oserror();
431 
432 	// either use existing settings or set defaults
433 	if(!p->restore) {
434 		// set default settings
435 		if(!GetCommState(p->comfh, &p->dcb))
436 			oserror();
437 		p->dcb.BaudRate = 9600;
438 		p->dcb.ByteSize = 8;
439 		p->dcb.fParity = 0;
440 		p->dcb.Parity = NOPARITY;
441 		p->dcb.StopBits = ONESTOPBIT;
442 		p->dcb.fInX = 0;  //default to xoff
443 		p->dcb.fOutX = 0;
444 		p->dcb.fAbortOnError = 1; //read/write abort on err
445 	}
446 
447 	// set state and timeouts
448 	if(!SetCommState(p->comfh, &p->dcb) ||
449 	   !SetCommTimeouts(p->comfh, &timeouts))
450 		oserror();
451 	poperror();
452 }
453 
454 /*
455  * Obtain status information on the com port.
456  */
457 static long
458 rdstat(int port, void *buf, long n, ulong offset)
459 {
460 	HANDLE comfh = eia[port].comfh;
461 	char str[Maxctl];
462 	char *s;
463 	DCB dcb;
464 	DWORD modemstatus;
465 	DWORD porterr;
466 	COMSTAT  portstat;
467 	int frame, overrun, i;
468 
469 	// valid line control ids
470 	static enum {
471 		L_CTS, L_DSR, L_RING, L_DCD, L_DTR, L_RTS, L_MAX
472 	};
473 	int status[L_MAX];
474 
475 	// line control strings (should match above id's)
476 	static char* lines[] = {
477 		"cts", "dsr", "ring", "dcd", "dtr",	"rts", NULL
478 	};
479 
480 
481 	// get any error conditions; also clears error flag
482 	// and enables io
483 	if(!ClearCommError(comfh, &porterr, &portstat))
484 		oserror();
485 
486 	// get comm port state
487 	if(!GetCommState(comfh, &dcb))
488 		oserror();
489 
490 	// get modem line information
491 	if(!GetCommModemStatus(comfh, &modemstatus))
492 		oserror();
493 
494 	// now set our local flags
495 	status[L_CTS] = MS_CTS_ON & modemstatus;
496 	status[L_DSR] = MS_DSR_ON & modemstatus;
497 	status[L_RING] = MS_RING_ON & modemstatus;
498 	status[L_DCD] = MS_RLSD_ON & modemstatus;
499 	status[L_DTR] = FALSE;  //?? cand this work: dcb.fDtrControl;
500 	status[L_RTS] = FALSE;  //??   dcb.fRtsControl;
501 	frame = porterr & CE_FRAME;
502 	overrun = porterr & CE_OVERRUN;
503 
504 	/* TO DO: mimic native eia driver's first line */
505 
506 	s = seprint(str, str+sizeof(str), "opens %d ferr %d oerr %d baud %d",
507 		    eia[port].r.ref-1,
508 			frame,
509 			overrun,
510 		    dcb.BaudRate);
511 
512 	// add line settings
513 	for(i=0; i < L_MAX; i++)
514 		if(status[i])
515 			s = seprint(s, str+sizeof(str), " %s", lines[i]);
516 	seprint(s, str+sizeof(str), "\n");
517 	return readstr(offset, buf, n, str);
518 }
519 
520 //
521 // write on ctl file. modify the settings for
522 // the underlying port.
523 //
524 static void
525 wrctl(int port, char *cmd)
526 {
527 	DCB dcb;
528 	int nf, n,  i;
529 	char *f[16];
530 	HANDLE comfh = eia[port].comfh;
531 	DWORD  flag, opt;
532 	BOOL   rslt;
533 	int chg;
534 
535 	// get the current settings for the port
536 	if(!GetCommState(comfh, &dcb))
537 		oserror();
538 
539 	chg = 0;
540 	nf = tokenize(cmd, f, nelem(f));
541 	for(i = 0; i < nf; i++){
542 		if(strcmp(f[i], "break") == 0){
543 			if(!SetCommBreak(comfh))
544 				oserror();
545 			SleepEx((DWORD)300, FALSE);
546 			if(!ClearCommBreak(comfh))
547 				oserror();
548 			continue;
549 		}
550 
551 		n = atoi(f[i]+1);
552 		switch(*f[i]) {
553 		case 'B':
554 		case 'b':	// set the baud rate
555 			if(n < 110)
556 				error(Ebadarg);
557 			dcb.BaudRate = n;
558 			chg = 1;
559 			break;
560 		case 'C':
561 		case 'c':
562 			/* dcd */
563 			break;
564 		case 'D':
565 		case 'd':  // set DTR
566 			opt = n ? SETDTR : CLRDTR;
567 			if(!EscapeCommFunction(comfh, opt))
568 				oserror();
569 			break;
570 		case 'E':
571 		case 'e':
572 			/* dsr */
573 			break;
574 		case 'F':
575 		case 'f':	// flush any untransmitted data
576 			if(!PurgeComm(comfh, PURGE_TXCLEAR))
577 				oserror();
578 			break;
579 		case 'H':
580 		case 'h':
581 			/* hangup */
582 			/* TO DO: close handle */
583 			break;
584 		case 'I':
585 		case 'i':
586 			/* fifo: nothing to do */
587 			break;
588 		case 'K':
589 		case 'k':
590 			/* send a break */
591 			if(!SetCommBreak(comfh))
592 				oserror();
593 			SleepEx((DWORD)300, FALSE);
594 			if(!ClearCommBreak(comfh))
595 				oserror();
596 			break;
597 		case 'L':
598 		case 'l':	// set bits per byte
599 			flag = stof(size, f[0]+1);
600 			if(flag == BAD)
601 				error(Ebadarg);
602 			dcb.ByteSize = (BYTE)flag;
603 			chg = 1;
604 			break;
605 		case 'M':
606 		case 'm':	// set CTS (modem control)
607 			dcb.fOutxCtsFlow = (n!=0);
608 			chg = 1;
609 			break;
610 		case 'N':
611 		case 'n':
612 			/* don't block on output */
613 			break;
614 		case 'P':
615 		case 'p':	// set parity -- even or odd
616 			flag = stof(parity, f[0]+1);
617 			if(flag==BAD)
618 				error(Ebadarg);
619 			dcb.Parity = (BYTE)flag;
620 			chg = 1;
621 			break;
622 		case 'Q':
623 		case 'q':
624 			/* set i/o queue limits */
625 			break;
626 		case 'R':
627 		case 'r':	// set RTS
628 			opt = n ? SETRTS : CLRRTS;
629 			if(!EscapeCommFunction(comfh, opt))
630 				oserror();
631 			break;
632 		case 'S':
633 		case 's':	// set stop bits -- valid: 1 or 2 (win32 allows 1.5??)
634 			flag = stof(stopbits, f[0]+1);
635 			if(flag==BAD)
636 				error(Ebadarg);
637 			dcb.StopBits = flag;
638 			chg = 1;
639 			break;
640 		case 'T':
641 		case 't':
642 			break;
643 		case 'W':
644 		case 'w':
645 			/* set uart timer */
646 			break;
647 		case 'X':
648 		case 'x':	// xon/xoff
649 			opt = n ? SETXON : SETXOFF;
650 			if(!EscapeCommFunction(comfh, opt))
651 				oserror();
652 			break;
653 		default:
654 			/* ignore */
655 			break;
656 		}
657 	}
658 
659 	if(!chg)
660 		return;
661 	// make the changes on the underlying port, but flush
662 	// outgoing chars down the port before
663 	osenter();
664 	rslt = FlushFileBuffers(comfh);
665 	if(rslt)
666 		rslt = SetCommState(comfh, &dcb);
667 	osleave();
668 	if(!rslt)
669 		oserror();
670 	eia[port].restore = 1;
671 	eia[port].dcb = dcb;
672 }
673