|
|
1.1 root 1: /*
2: * Copyright (c) 1982, 1986 Regents of the University of California.
3: * All rights reserved. The Berkeley software License Agreement
4: * specifies the terms and conditions for redistribution.
5: *
6: * @(#)ps.c 7.1 (Berkeley) 6/5/86
7: */
8:
9: /*
10: * Evans and Sutherland Picture System 2 driver -- Bill Reeves.
11: */
12:
13: /*
14: * Still to be done:
15: * WAIT_HIT
16: */
17:
18: #include "ps.h"
19: #if NPS > 0
20:
21: #define EXTERNAL_SYNC
22:
23: #include "../machine/pte.h"
24:
25: #include "param.h"
26: #include "systm.h"
27: #include "ioctl.h"
28: #include "map.h"
29: #include "buf.h"
30: #include "conf.h"
31: #include "dir.h"
32: #include "user.h"
33: #include "uio.h"
34:
35: #include "ubareg.h"
36: #include "ubavar.h"
37: #include "psreg.h"
38:
39: int psprobe(), psattach(), psextsync();
40: int psclockintr(), pssystemintr(), psdeviceintr(), psdmaintr();
41: struct uba_device *psdinfo[NPS];
42: u_short psstd[] = { 0 };
43: struct uba_driver psdriver =
44: { psprobe, 0, psattach, 0, psstd, "ps", psdinfo };
45:
46: #define PSUNIT(dev) (minor(dev))
47:
48: #define MAXAUTOREFRESH 4
49: #define MAXAUTOMAP 4
50: #define MAXDBSIZE (0177777/2)
51:
52: #define PSPRI (PZERO+1)
53:
54: #define PSWAIT(psaddr) { \
55: register short i = 20000, j; \
56: while (i-- != 0 && ((j = psaddr->ps_iostat) & DIOREADY) == 0) \
57: ;\
58: }
59:
60: struct psrefresh {
61: enum {
62: SINGLE_STEP_RF,
63: AUTO_RF,
64: TIME_RF
65: } state;
66: enum {
67: RUNNING_RF,
68: SYNCING_RF,
69: WAITING_MAP,
70: STOPPED_RF
71: } mode;
72: u_short sraddrs[MAXAUTOREFRESH];
73: short nsraddrs;
74: short srcntr;
75: char waiting;
76: char stop;
77: int icnt;
78: int timecnt;
79: };
80:
81: struct psdbuffer {
82: enum {
83: ON_DB,
84: OFF_DB
85: } state;
86: u_short dbaddrs[2];
87: u_short dbsize;
88: short rbuffer;
89: };
90:
91: struct psmap {
92: enum {
93: SINGLE_STEP_MAP,
94: AUTO_MAP
95: } state;
96: enum {
97: RUNNING_MAP,
98: WAITING_RF,
99: WAITING_START,
100: STOPPED_MAP
101: } mode;
102: u_short maddrs[MAXAUTOMAP];
103: short nmaddrs;
104: short mcntr;
105: short outputstart;
106: char waiting;
107: char stop;
108: int icnt;
109: };
110:
111: /*
112: * PS2 software state.
113: */
114: struct ps {
115: char ps_open; /* device is open */
116: uid_t ps_uid; /* uid of device owner */
117: struct psrefresh ps_refresh; /* refresh state */
118: struct psdbuffer ps_dbuffer; /* double buffering state */
119: struct psmap ps_map; /* segment map state */
120: int ps_clockticks; /* clock ints between refresh */
121: int ps_clockmiss; /* clock ints w/o refresh */
122: int ps_clockcnt; /* count of clock interrupts */
123: int ps_hitcnt; /* count of hit interrupts */
124: int ps_strayintr; /* count of stray interrupts */
125: int ps_icnt; /* count of system interrupts */
126: /* BEGIN GROT */
127: int ps_lastrequest;
128: int ps_lastrequest2;
129: int ps_lastfunnyrequest;
130: int ps_funnycnt;
131: /* END GROT */
132: } ps[NPS];
133:
134: psprobe(reg)
135: caddr_t reg;
136: {
137: register int br, cvec;
138: register struct psdevice *psaddr = (struct psdevice *)reg;
139:
140: #ifdef lint
141: br = 0; cvec = br; br = cvec;
142: psclockintr((dev_t)0); pssystemintr((dev_t)0);
143: psdeviceintr((dev_t)0); psdmaintr((dev_t)0);
144: psextsync(0, 0);
145: #endif
146: psaddr->ps_iostat = PSRESET;
147: DELAY(200);
148: psaddr->ps_addr = RTCIE;
149: PSWAIT(psaddr); psaddr->ps_data = 01;
150: psaddr->ps_iostat = PSIE;
151: psaddr->ps_addr = RTCSR;
152: PSWAIT(psaddr); psaddr->ps_data = SYNC|RUN;
153: DELAY(200000);
154: psaddr->ps_addr = RTCREQ;
155: PSWAIT(psaddr); psaddr->ps_data = 01;
156: psaddr->ps_iostat = 0;
157: psaddr->ps_iostat = PSRESET;
158: return (sizeof (struct psdevice));
159: }
160:
161: /*ARGSUSED*/
162: psattach(ui)
163: struct uba_device *ui;
164: {
165:
166: }
167:
168: psopen(dev)
169: dev_t dev;
170: {
171: register struct ps *psp;
172: register struct uba_device *ui;
173: register int unit = PSUNIT(dev);
174:
175: if (unit >= NPS || (psp = &ps[minor(dev)])->ps_open ||
176: (ui = psdinfo[unit]) == 0 || ui->ui_alive == 0)
177: return (ENXIO);
178: psp->ps_open = 1;
179: psp->ps_uid = u.u_uid;
180: psp->ps_strayintr = 0;
181: psp->ps_refresh.state = SINGLE_STEP_RF;
182: psp->ps_refresh.mode = STOPPED_RF;
183: psp->ps_refresh.waiting = 0;
184: psp->ps_refresh.stop = 0;
185: psp->ps_dbuffer.state = OFF_DB;
186: psp->ps_map.state = SINGLE_STEP_MAP;
187: psp->ps_map.mode = STOPPED_MAP;
188: psp->ps_map.waiting = 0;
189: psp->ps_map.stop = 0;
190: psp->ps_clockticks = 0;
191: psp->ps_clockmiss = 0;
192: psp->ps_refresh.icnt = psp->ps_map.icnt = psp->ps_clockcnt = 0;
193: psp->ps_hitcnt = 0;
194: psp->ps_icnt = 0;
195: maptouser(ui->ui_addr);
196: return (0);
197: }
198:
199: psclose(dev)
200: dev_t dev;
201: {
202: register struct psdevice *psaddr =
203: (struct psdevice *)psdinfo[PSUNIT(dev)]->ui_addr;
204:
205: ps[PSUNIT(dev)].ps_open = 0;
206: psaddr->ps_iostat = 0; /* clear IENABLE */
207: PSWAIT(psaddr); psaddr->ps_addr = RFSR; /* set in auto refresh mode */
208: PSWAIT(psaddr); psaddr->ps_data = AUTOREF;
209: unmaptouser((caddr_t)psaddr);
210: }
211:
212: /*ARGSUSED*/
213: psread(dev, uio)
214: dev_t dev;
215: struct uio *uio;
216: {
217: }
218:
219: /*ARGSUSED*/
220: pswrite(dev, uio)
221: dev_t dev;
222: struct uio *uio;
223: {
224: }
225:
226: /*ARGSUSED*/
227: psioctl(dev, cmd, data, flag)
228: register caddr_t data;
229: {
230: register struct uba_device *ui = psdinfo[PSUNIT(dev)];
231: register struct ps *psp = &ps[PSUNIT(dev)];
232: int *waddr = *(int **)data;
233: int n, arg, i;
234:
235: switch (cmd) {
236:
237: case PSIOGETADDR:
238: *(caddr_t *)data = ui->ui_addr;
239: break;
240:
241: case PSIOAUTOREFRESH:
242: n = fuword((caddr_t)waddr++);
243: if (n == -1)
244: return (EFAULT);
245: if (n < 0 || n > MAXAUTOREFRESH)
246: return (EINVAL);
247: for (i = 0; i < n; i++) {
248: if ((arg = fuword((caddr_t)waddr++)) == -1)
249: return (EFAULT);
250: psp->ps_refresh.sraddrs[i] = arg;
251: }
252: psp->ps_refresh.state = AUTO_RF;
253: psp->ps_refresh.nsraddrs = n;
254: psp->ps_refresh.srcntr = 0;
255: psp->ps_refresh.mode = WAITING_MAP;
256: break;
257:
258: case PSIOAUTOMAP:
259: n = fuword((caddr_t)waddr++);
260: if (n == -1)
261: return (EFAULT);
262: if (n < 0 || n > MAXAUTOMAP)
263: return (EINVAL);
264: for (i = 0; i < n; i++) {
265: if ((arg = fuword((caddr_t)waddr++)) == -1)
266: return (EFAULT);
267: psp->ps_map.maddrs[i] = arg;
268: }
269: if ((arg = fuword((caddr_t)waddr++)) == -1)
270: return (EFAULT);
271: psp->ps_map.outputstart = arg;
272: psp->ps_map.state = AUTO_MAP;
273: psp->ps_map.nmaddrs = n;
274: psp->ps_map.mcntr = 0;
275: psp->ps_map.mode = WAITING_START;
276: break;
277:
278: case PSIOSINGLEREFRESH:
279: psp->ps_refresh.state = SINGLE_STEP_RF;
280: break;
281:
282: case PSIOSINGLEMAP:
283: psp->ps_map.state = SINGLE_STEP_MAP;
284: break;
285:
286: case PSIODOUBLEBUFFER:
287: if ((arg = fuword((caddr_t)waddr++)) == -1)
288: return (EFAULT);
289: psp->ps_dbuffer.dbaddrs[0] = arg;
290: if ((arg = fuword((caddr_t)waddr++)) == -1)
291: return (EFAULT);
292: if (arg <= 0 || arg > MAXDBSIZE)
293: return (EINVAL);
294: psp->ps_dbuffer.dbsize = arg;
295: psp->ps_dbuffer.dbaddrs[1] = psp->ps_dbuffer.dbaddrs[0]+arg;
296: psp->ps_dbuffer.state = ON_DB;
297: psp->ps_dbuffer.rbuffer = 0;
298: break;
299:
300: case PSIOSINGLEBUFFER:
301: psp->ps_dbuffer.state = OFF_DB;
302: break;
303:
304: case PSIOTIMEREFRESH:
305: if (psp->ps_refresh.state != SINGLE_STEP_RF)
306: return (EINVAL);
307: if ((arg = fuword((caddr_t)waddr++)) == -1)
308: return (EFAULT);
309: psp->ps_refresh.state = TIME_RF;
310: psp->ps_refresh.timecnt = arg;
311: break;
312:
313: case PSIOWAITREFRESH:
314: if (psp->ps_refresh.mode != RUNNING_RF) /* not running */
315: return (0); /* dont wait */
316: /* fall into ... */
317:
318: case PSIOSTOPREFRESH:
319: if (cmd == PSIOSTOPREFRESH) {
320: if (psp->ps_refresh.mode == STOPPED_RF &&
321: psp->ps_refresh.state != TIME_RF)
322: return (0);
323: psp->ps_refresh.stop = 1;
324: }
325: (void) spl5();
326: psp->ps_refresh.waiting = 1;
327: while (psp->ps_refresh.waiting)
328: sleep(&psp->ps_refresh.waiting, PSPRI);
329: (void) spl0();
330: if (cmd == PSIOSTOPREFRESH)
331: psp->ps_refresh.mode = STOPPED_RF;
332: if (psp->ps_refresh.state == TIME_RF)
333: psp->ps_refresh.state = SINGLE_STEP_RF;
334: break;
335:
336: case PSIOWAITMAP:
337: if (psp->ps_map.mode != RUNNING_MAP) /* not running */
338: return (0); /* dont wait */
339: /* fall into ... */
340:
341: case PSIOSTOPMAP:
342: if (cmd == PSIOSTOPMAP)
343: psp->ps_map.stop = 1;
344: (void) spl5();
345: psp->ps_map.waiting = 1;
346: while (psp->ps_map.waiting)
347: sleep(&psp->ps_map.waiting, PSPRI);
348: (void) spl0();
349: break;
350:
351: default:
352: return (ENOTTY);
353: break;
354: }
355: return (0);
356: }
357:
358: #define SAVEPSADDR(psaddr, savepsaddr) { \
359: register short i, xx1; \
360: xx1 = splclock(); \
361: i = psaddr->ps_addr; \
362: while ((psaddr->ps_iostat & DIOREADY) == 0) \
363: ; \
364: savepsaddr = psaddr->ps_data; \
365: splx(xx1); \
366: }
367: #define RESTORPSADDR(psaddr, savepsaddr) { \
368: register short xx2; \
369: xx2 = splclock(); \
370: while ((psaddr->ps_iostat & DIOREADY) == 0) \
371: ;\
372: psaddr->ps_addr = savepsaddr; \
373: splx(xx2); \
374: }
375:
376: psclockintr(dev)
377: dev_t dev;
378: {
379: register struct psdevice *psaddr =
380: (struct psdevice *)psdinfo[PSUNIT(dev)]->ui_addr;
381: register struct ps *psp = &ps[PSUNIT(dev)];
382: int savepsaddr;
383:
384: if (!psp->ps_open)
385: return;
386: psp->ps_clockcnt++;
387: SAVEPSADDR(psaddr, savepsaddr);
388: #ifndef EXTERNAL_SYNC
389: if (psp->ps_refresh.state == AUTO_RF) {
390: if (psp->ps_refresh.mode == SYNCING_RF &&
391: psp->ps_refresh.state != TIME_RF) {
392: (void) psrfnext(psp, psaddr);
393: } else {
394: psp->ps_clockticks++;
395: psp->ps_clockmiss++;
396: }
397: }
398: #endif
399: PSWAIT(psaddr); psaddr->ps_addr = RTCREQ;
400: PSWAIT(psaddr); psaddr->ps_data = 01; /* clear the request bits */
401: RESTORPSADDR(psaddr, savepsaddr);
402: }
403:
404: /*ARGSUSED*/
405: pssystemintr(dev)
406: dev_t dev;
407: {
408: register struct psdevice *psaddr =
409: (struct psdevice *)psdinfo[PSUNIT(dev)]->ui_addr;
410: register struct ps *psp = &ps[PSUNIT(dev)];
411: short request, tmp;
412: register int savepsaddr, x;
413:
414: if (!psp->ps_open)
415: return;
416: psp->ps_icnt++;
417: SAVEPSADDR(psaddr, savepsaddr);
418: PSWAIT(psaddr); psaddr->ps_addr = SYSREQ;
419: PSWAIT(psaddr); request = psaddr->ps_data;
420: request = request&0377;
421: psp->ps_lastrequest2 = psp->ps_lastrequest;
422: psp->ps_lastrequest = request;
423: if (request &~ (HALT_REQ|RFSTOP_REQ|HIT_REQ)) {
424: psp->ps_lastfunnyrequest = request;
425: psp->ps_funnycnt++;
426: }
427: PSWAIT(psaddr); psaddr->ps_addr = SYSREQ;
428: tmp = request&(~(HALT_REQ|MOSTOP_REQ)); /* acknowledge */
429: PSWAIT(psaddr); psaddr->ps_data = tmp;
430:
431: if (request & (MOSTOP_REQ|HALT_REQ)) { /* Map stopped */
432: psp->ps_map.icnt++;
433: psmapstop(psaddr, psp, request);/* kill it dead */
434: if (psp->ps_map.waiting) {
435: psp->ps_map.waiting = 0;
436: wakeup(&psp->ps_map.waiting);
437: if (psp->ps_map.stop) {
438: psp->ps_map.stop = 0;
439: goto tryrf;
440: }
441: }
442: if (psp->ps_map.state == AUTO_MAP && !psmapnext(psp, psaddr)) {
443: psp->ps_map.mcntr = 0;
444: /* prepare for next round */
445: pssetmapbounds(psp, psaddr);
446: if (psp->ps_refresh.state == AUTO_RF) {
447: if (psp->ps_refresh.mode == WAITING_MAP){
448: if (psp->ps_dbuffer.state == ON_DB)
449: /* fill other db */
450: psdbswitch(psp, psaddr);
451: else
452: psp->ps_map.mode = WAITING_RF;
453: #ifdef EXTERNAL_SYNC
454: x = splclock();
455: #endif
456: (void) psrfnext(psp, psaddr);
457: #ifdef EXTERNAL_SYNC
458: splx(x);
459: #endif
460: } else
461: psp->ps_map.mode = WAITING_RF;
462: } else { /* no auto refresh */
463: if (psp->ps_dbuffer.state == ON_DB)
464: /* fill other db */
465: psdbswitch(psp, psaddr);
466: else
467: (void) psmapnext(psp, psaddr);
468: }
469: }
470: }
471: tryrf:
472: if (request & RFSTOP_REQ) { /* Refresh stopped */
473: psp->ps_refresh.icnt++;
474: if (psp->ps_refresh.state == TIME_RF)
475: if (--psp->ps_refresh.timecnt > 0)
476: goto tryhit;
477: psrfstop(psaddr, psp);
478: if (psp->ps_refresh.waiting) {
479: psp->ps_refresh.waiting = 0;
480: wakeup(&psp->ps_refresh.waiting);
481: if (psp->ps_refresh.stop) {
482: psp->ps_refresh.stop = 0;
483: goto tryhit;
484: }
485: }
486: if (psp->ps_refresh.state == AUTO_RF)
487: if (!psrfnext(psp, psaddr)) { /* at end of refresh cycle */
488: if (psp->ps_map.state == AUTO_MAP &&
489: psp->ps_map.mode == WAITING_RF) {
490: if (psp->ps_dbuffer.state == ON_DB)
491: psdbswitch(psp, psaddr);
492: else
493: (void) psmapnext(psp, psaddr);
494: }
495: psp->ps_refresh.srcntr = 0;
496: #ifdef EXTERNAL_SYNC
497: x = splclock();
498: #endif
499: psp->ps_refresh.mode = SYNCING_RF;
500: if (psp->ps_clockticks)
501: (void) psrfnext(psp, psaddr);
502: psp->ps_clockticks = 0;
503: #ifdef EXTERNAL_SYNC
504: splx(x);
505: #endif
506: }
507: }
508: tryhit:
509: if (request & HIT_REQ) /* Hit request */
510: psp->ps_hitcnt++;
511: if (request == 0)
512: psp->ps_strayintr++;
513: RESTORPSADDR(psaddr, savepsaddr);
514: }
515:
516: psrfnext(psp, psaddr)
517: register struct ps *psp;
518: register struct psdevice *psaddr;
519: {
520: u_short start, last;
521:
522: if (psp->ps_refresh.srcntr < psp->ps_refresh.nsraddrs) {
523: psrfstart(psp->ps_refresh.sraddrs[psp->ps_refresh.srcntr++],
524: 0, psp, psaddr);
525: return (1);
526: }
527: if (psp->ps_refresh.srcntr == psp->ps_refresh.nsraddrs &&
528: psp->ps_dbuffer.state == ON_DB) {
529: start = psp->ps_dbuffer.dbaddrs[psp->ps_dbuffer.rbuffer];
530: last = start+psp->ps_dbuffer.dbsize;
531: psrfstart(start, last, psp, psaddr);
532: psp->ps_refresh.srcntr++; /* flag for after dbuffer */
533: return (1);
534: }
535: return (0);
536: }
537:
538: psrfstart(dfaddr, last, psp, psaddr)
539: u_short dfaddr, last;
540: register struct ps *psp;
541: register struct psdevice *psaddr;
542: {
543: short dummy;
544:
545: PSWAIT(psaddr); psaddr->ps_addr = RFASA;
546: PSWAIT(psaddr); psaddr->ps_data = dfaddr;
547: PSWAIT(psaddr);
548: if (last != 0)
549: psaddr->ps_data = last;
550: else
551: dummy = psaddr->ps_data;/* just access to get to status reg */
552: PSWAIT(psaddr); psaddr->ps_data = RFSTART; /* may want | here */
553: psp->ps_refresh.mode = RUNNING_RF;
554: }
555:
556: /*ARGSUSED*/
557: psrfstop(psaddr, psp)
558: register struct psdevice *psaddr;
559: register struct ps *psp;
560: {
561:
562: PSWAIT(psaddr); psaddr->ps_addr = RFSR;
563: PSWAIT(psaddr); psaddr->ps_data = 0;
564: }
565:
566: psdbswitch(psp, psaddr)
567: register struct ps *psp;
568: register struct psdevice *psaddr;
569: {
570:
571: psp->ps_dbuffer.rbuffer = !psp->ps_dbuffer.rbuffer;
572: pssetmapbounds(psp, psaddr);
573: (void) psmapnext(psp, psaddr);
574: }
575:
576: psmapnext(psp, psaddr)
577: register struct ps *psp;
578: register struct psdevice *psaddr;
579: {
580:
581: if (psp->ps_map.mcntr < psp->ps_map.nmaddrs) {
582: psmapstart(psp->ps_map.maddrs[psp->ps_map.mcntr++],
583: psp, psaddr);
584: return (1);
585: }
586: return (0);
587: }
588:
589: pssetmapbounds(psp, psaddr)
590: register struct ps *psp;
591: register struct psdevice *psaddr;
592: {
593: u_short start, last;
594:
595: PSWAIT(psaddr); psaddr->ps_addr = MAOL;
596: PSWAIT(psaddr);
597: if (psp->ps_dbuffer.state == ON_DB) {
598: start = psp->ps_dbuffer.dbaddrs[!psp->ps_dbuffer.rbuffer];
599: last = start+psp->ps_dbuffer.dbsize-2; /* 2 for halt cmd */
600: psaddr->ps_data = last;
601: PSWAIT(psaddr); psaddr->ps_data = start;
602: } else {
603: start = psaddr->ps_data; /* dummy: don't update limit */
604: PSWAIT(psaddr); psaddr->ps_data = psp->ps_map.outputstart;
605: }
606: }
607:
608: psmapstart(dfaddr, psp, psaddr)
609: u_short dfaddr;
610: register struct ps *psp;
611: register struct psdevice *psaddr;
612: {
613:
614: PSWAIT(psaddr); psaddr->ps_addr = MAIA;
615: PSWAIT(psaddr); psaddr->ps_data = dfaddr;
616: PSWAIT(psaddr); psaddr->ps_data = MAO|MAI; /* may want more here */
617: psp->ps_map.mode = RUNNING_MAP;
618: }
619:
620: int pskillcnt = 1;
621:
622: psmapstop(psaddr, psp, request)
623: register struct psdevice *psaddr;
624: register struct ps *psp;
625: short request;
626: {
627: register int i;
628:
629: request &= HALT_REQ|MOSTOP_REQ; /* overkill?? */
630: for (i = 0; i < pskillcnt; i++) {
631: PSWAIT(psaddr); psaddr->ps_addr = MASR;
632: PSWAIT(psaddr); psaddr->ps_data = IOUT; /* zero MAI & MAO */
633: PSWAIT(psaddr); psaddr->ps_addr = MAIA;
634: PSWAIT(psaddr); psaddr->ps_data = 0; /* 0 input addr reg */
635: PSWAIT(psaddr); psaddr->ps_addr = MAOA;
636: PSWAIT(psaddr); psaddr->ps_data = 0; /* 0 output addr reg */
637: PSWAIT(psaddr); psaddr->ps_addr = SYSREQ;
638: PSWAIT(psaddr); psaddr->ps_data = request;
639: }
640: psp->ps_map.mode = STOPPED_MAP;
641: }
642:
643: /*ARGSUSED*/
644: psdeviceintr(dev)
645: dev_t dev;
646: {
647:
648: printf("ps device intr\n");
649: }
650:
651: /*ARGSUSED*/
652: psdmaintr(dev)
653: dev_t dev;
654: {
655:
656: printf("ps dma intr\n");
657: }
658:
659: /*ARGSUSED*/
660: psreset(uban)
661: int uban;
662: {
663:
664: }
665:
666: /*ARGSUSED*/
667: psextsync(PC, PS)
668: {
669: register int n;
670: register struct psdevice *psaddr;
671: register struct ps *psp;
672: register int savepsaddr;
673:
674: #ifdef EXTERNAL_SYNC
675: for (psp = ps, n = 0; n < NPS; psp++, n++) {
676: if (!psp->ps_open)
677: continue;
678: if (psp->ps_refresh.mode == SYNCING_RF &&
679: psp->ps_refresh.state != TIME_RF) {
680: psaddr = (struct psdevice *)psdinfo[n]->ui_addr;
681: SAVEPSADDR(psaddr, savepsaddr);
682: (void) psrfnext(psp, psaddr);
683: RESTORPSADDR(psaddr, savepsaddr);
684: } else {
685: psp->ps_clockticks++;
686: psp->ps_clockmiss++;
687: }
688: }
689: #endif
690: }
691: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.