|
|
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: * @(#)autoconf.c 7.15 (Berkeley) 5/26/88
7: */
8:
9: /*
10: * Setup the system to run on the current machine.
11: *
12: * Configure() is called at boot time and initializes the uba and mba
13: * device tables and the memory controller monitoring. Available
14: * devices are determined (from possibilities mentioned in ioconf.c),
15: * and the drivers are initialized.
16: */
17:
18: #include "mba.h"
19: #include "uba.h"
20: #include "kra.h" /* XXX wrong file */
21:
22: #include "param.h"
23: #include "systm.h"
24: #include "map.h"
25: #include "buf.h"
26: #include "dkstat.h"
27: #include "vm.h"
28: #include "malloc.h"
29: #include "conf.h"
30: #include "dmap.h"
31: #include "reboot.h"
32:
33: #include "pte.h"
34: #include "cpu.h"
35: #include "mem.h"
36: #include "mtpr.h"
37: #include "nexus.h"
38: #include "scb.h"
39: #include "ioa.h"
40: #include "../vaxbi/bireg.h"
41: #include "../vaxmba/mbareg.h"
42: #include "../vaxmba/mbavar.h"
43: #include "../vaxuba/ubareg.h"
44: #include "../vaxuba/ubavar.h"
45:
46: /*
47: * The following several variables are related to
48: * the configuration process, and are used in initializing
49: * the machine.
50: */
51: int cold; /* if 1, still working on cold-start */
52: int dkn; /* number of iostat dk numbers assigned so far */
53: int cpuspeed = 1; /* relative cpu speed */
54:
55: /*
56: * Addresses of the (locore) routines which bootstrap us from
57: * hardware traps to C code. Filled into the system control block
58: * as necessary.
59: *
60: * RIDICULOUS! CONFIG SHOULD GENERATE AN ioconf.h FOR US, with
61: * mba glue also in `glue.s'. (Unibus adapter glue is special, though.)
62: */
63: #if NMBA > 0
64: int (*mbaintv[4])() = { Xmba0int, Xmba1int, Xmba2int, Xmba3int };
65: #if NMBA > 4
66: Need to expand the table for more than 4 massbus adaptors
67: #endif
68: #endif
69: #if defined(VAX780) || defined(VAX8600)
70: int (*ubaintv[])() =
71: {
72: Xua0int, Xua1int, Xua2int, Xua3int,
73: #if NUBA > 4
74: Xua4int, Xua5int, Xua6int, Xua7int,
75: #endif
76: #if NUBA > 8
77: Need to expand the table for more than 8 unibus adaptors
78: #endif
79: };
80: #endif
81: #if NKDB > 0
82: /* kdb50 driver does not appear in udminit[] (not without csr!) */
83: int Xkdbintr0(); /* generated by autoconf */
84: int (*kdbintv[])() = { Xkdbintr0 };
85: #if NKDB > 1
86: Need to expand the table for more than 1 KDB adapter
87: #endif
88: #endif
89:
90: /*
91: * This allocates the space for the per-uba information,
92: * such as buffered data path usage.
93: */
94: struct uba_hd uba_hd[NUBA];
95:
96: /*
97: * Determine mass storage and memory configuration for a machine.
98: * Get cpu type, and then switch out to machine specific procedures
99: * which will probe adaptors to see what is out there.
100: */
101: configure()
102: {
103: union cpusid cpusid;
104: register struct percpu *ocp;
105: register struct pte *ip;
106:
107: cpusid.cpusid = mfpr(SID);
108: switch (cpusid.cpuany.cp_type) {
109: #if VAX8600
110: case VAX_8600:
111: printf("VAX 8600, serial# %d(%d), hardware ECO level %d(%d)\n",
112: cpusid.cpu780.cp_sno, cpusid.cpu780.cp_plant,
113: cpusid.cpu780.cp_eco >> 4, cpusid.cpu780.cp_eco);
114: break;
115: #endif
116: #if VAX8200
117: case VAX_8200:
118: printf("\
119: VAX 82%c0, hardware rev %d, ucode patch rev %d, sec patch %d, ucode rev %d\n",
120: cpusid.cpu8200.cp_5 ? '5' : '0',
121: cpusid.cpu8200.cp_hrev, cpusid.cpu8200.cp_patch,
122: cpusid.cpu8200.cp_secp, cpusid.cpu8200.cp_urev);
123: mastercpu = mfpr(BINID);
124: break;
125: #endif
126: #if VAX780
127: case VAX_780:
128: printf("\
129: VAX 11/78%c, serial# %d(%d), hardware ECO level %d(%d)\n",
130: cpusid.cpu780.cp_5 ? '5' : '0',
131: cpusid.cpu780.cp_sno, cpusid.cpu780.cp_plant,
132: cpusid.cpu780.cp_eco >> 4, cpusid.cpu780.cp_eco);
133: break;
134: #endif
135: #if VAX750
136: case VAX_750:
137: printf("VAX 11/750, hardware rev %d, ucode rev %d\n",
138: cpusid.cpu750.cp_hrev, cpusid.cpu750.cp_urev);
139: break;
140: #endif
141: #if VAX730
142: case VAX_730:
143: printf("VAX 11/730, ucode rev %d\n", cpusid.cpu730.cp_urev);
144: break;
145: #endif
146: #if VAX630
147: case VAX_630:
148: printf("MicroVAX-II\n");
149: break;
150: #endif
151: }
152: for (ocp = percpu; ocp->pc_cputype; ocp++)
153: if (ocp->pc_cputype == cpusid.cpuany.cp_type) {
154: cpuspeed = ocp->pc_cpuspeed;
155: cpuops = ocp->pc_ops;
156: if (cpuops->cpu_init != NULL)
157: (*cpuops->cpu_init)();
158: probeio(ocp);
159: /*
160: * Write protect the scb and UNIBUS interrupt vectors.
161: * It is strange that this code is here, but this is
162: * as soon as we are done mucking with it, and the
163: * write-enable was done in assembly language
164: * to which we will never return.
165: */
166: for (ip = kvtopte(scb); ip < kvtopte(eUNIvec); ip++) {
167: *(int *)ip &= ~PG_PROT;
168: *(int *)ip |= PG_KR;
169: }
170: mtpr(TBIA, 0);
171: #if GENERIC
172: if ((boothowto & RB_ASKNAME) == 0)
173: setroot();
174: setconf();
175: #else
176: setroot();
177: #endif
178: /*
179: * Configure swap area and related system
180: * parameter based on device(s) used.
181: */
182: swapconf();
183: cold = 0;
184: memenable();
185: return;
186: }
187: printf("cpu type %d not configured\n", cpusid.cpuany.cp_type);
188: asm("halt");
189: }
190:
191: #if VAX8600 || VAX780 || VAX750 || VAX730
192: int nexnum; /* current nexus number */
193: int nsbi; /* current sbi number */
194: #endif
195: #if VAX8200
196: int numkdb; /* current ``kdb'' number */
197: int bi_nodes; /* XXX remembers found bi nodes */
198: #endif
199:
200: /*
201: * Probe the main IO bus(es).
202: * The percpu structure gives us a handle on the addresses and/or types.
203: */
204: probeio(pcpu)
205: register struct percpu *pcpu;
206: {
207: register struct iobus *iob;
208: int ioanum;
209:
210: ioanum = 0;
211: for (iob = pcpu->pc_io; ioanum < pcpu->pc_nioa; ioanum++, iob++) {
212:
213: switch (iob->io_type) {
214:
215: #if VAX630
216: case IO_QBUS:
217: probeqbus((struct qbus *)iob->io_details);
218: break;
219: #endif
220:
221: #if VAX780 || VAX750 || VAX730
222: case IO_SBI780:
223: case IO_CMI750:
224: case IO_XXX730:
225: probenexi((struct nexusconnect *)iob->io_details);
226: break;
227: #endif
228:
229: #if VAX8600
230: case IO_ABUS:
231: probe_Abus(ioanum, iob);
232: break;
233: #endif
234:
235: #if VAX8200
236: case IO_BI:
237: probe_bi((struct bibus *)iob->io_details);
238: break;
239: #endif
240:
241: default:
242: if (iob->io_addr) {
243: printf(
244: "IO adaptor %d, type %d, at address 0x%x is unsupported\n",
245: ioanum, iob->io_type, iob->io_addr);
246: } else
247: printf("IO adaptor %d, type %d, is unsupported\n",
248: ioanum, iob->io_type);
249: break;
250: }
251: }
252: }
253:
254: #if VAX8600
255: probe_Abus(ioanum, iob)
256: register struct iobus *iob;
257: {
258: register struct ioa *ioap;
259: union ioacsr ioacsr;
260: int type;
261: struct sbia_regs *sbiaregs;
262: #ifdef notyet
263: int sbi1fail(), sbi1alert(), sbi1fault(), sbi1err();
264: #endif
265:
266: ioap = &ioa[ioanum];
267: ioaccess(iob->io_addr, Ioamap[ioanum], iob->io_size);
268: if (badaddr((caddr_t)ioap, 4))
269: return;
270: ioacsr.ioa_csr = ioap->ioacsr.ioa_csr;
271: type = ioacsr.ioa_type & IOA_TYPMSK;
272:
273: switch (type) {
274:
275: case IOA_SBIA:
276: printf("SBIA%d at IO adaptor %d address 0x%x\n",
277: nsbi, ioanum, iob->io_addr);
278: #ifdef notyet
279: /* I AM NOT SURE THESE ARE IN THE SAME PLACES */
280: if (nscb == 1) {
281: scb[1].scb_sbifail = scbentry(sbi1fail, SCB_ISTACK);
282: /* maybe not sbifail, maybe scb1.scb_cmrd */
283: /* but how can I find out without a broken SBIA1? */
284: scb[1].scb_sbialert = scbentry(sbi1alert, SCB_ISTACK);
285: scb[1].scb_sbifault = scbentry(sbi1fault, SCB_ISTACK);
286: scb[1].scb_sbierr = scbentry(sbi1err, SCB_ISTACK);
287: }
288: #endif
289: probenexi((struct nexusconnect *)iob->io_details);
290: nsbi++;
291: sbiaregs = (struct sbia_regs *)ioap;
292: sbiaregs->sbi_errsum = -1;
293: sbiaregs->sbi_error = 0x1000;
294: sbiaregs->sbi_fltsts = 0xc0000;
295: break;
296:
297: default:
298: printf("IOA%d at address 0x%x is unsupported (type = 0x%x)\n",
299: ioanum, iob->io_addr, ioacsr.ioa_type);
300: break;
301: }
302: }
303: #endif
304:
305: #if VAX8600 || VAX780 || VAX750 || VAX730
306: /*
307: * Probe nexus space, finding the interconnects
308: * and setting up and probing mba's and uba's for devices.
309: */
310: probenexi(pnc)
311: register struct nexusconnect *pnc;
312: {
313: register struct nexus *nxv;
314: struct nexus *nxp = pnc->psb_nexbase;
315: union nexcsr nexcsr;
316: int i;
317:
318: ioaccess((caddr_t)nxp, Nexmap[nsbi * NNEXSBI],
319: pnc->psb_nnexus * sizeof(struct nexus));
320: nxv = &nexus[nsbi * NNEXSBI];
321: for (nexnum = 0; nexnum < pnc->psb_nnexus; nexnum++, nxp++, nxv++) {
322: if (badaddr((caddr_t)nxv, 4))
323: continue;
324: if (pnc->psb_nextype && pnc->psb_nextype[nexnum] != NEX_ANY)
325: nexcsr.nex_csr = pnc->psb_nextype[nexnum];
326: else
327: nexcsr = nxv->nexcsr;
328: if (nexcsr.nex_csr&NEX_APD)
329: continue;
330: switch (nexcsr.nex_type) {
331:
332: case NEX_MBA:
333: printf("mba%d at tr%d\n", nummba, nexnum);
334: if (nummba >= NMBA) {
335: printf("%d mba's", ++nummba);
336: goto unconfig;
337: }
338: #if NMBA > 0
339: mbafind(nxv, nxp);
340: nummba++;
341: #endif
342: break;
343:
344: case NEX_UBA0:
345: case NEX_UBA1:
346: case NEX_UBA2:
347: case NEX_UBA3:
348: printf("uba%d at tr%d\n", numuba, nexnum);
349: if (numuba >= NUBA) {
350: printf("%d uba's", ++numuba);
351: goto unconfig;
352: }
353: #if NUBA > 0
354: #if VAX750
355: if (numuba >= 2 && cpu == VAX_750) {
356: printf("More than 2 UBA's");
357: goto unsupp;
358: }
359: #endif
360: #if defined(VAX780) || defined(VAX8600)
361: if (cpu == VAX_780 || cpu == VAX_8600)
362: setscbnex(ubaintv[numuba]);
363: #endif
364: i = nexcsr.nex_type - NEX_UBA0;
365: probeuba((struct uba_regs *)nxv, (struct uba_regs *)nxp,
366: pnc->psb_umaddr[i]);
367: #endif /* NUBA */
368: break;
369:
370: case NEX_DR32:
371: /* there can be more than one... are there other codes??? */
372: printf("dr32");
373: goto unsupp;
374:
375: case NEX_MEM4:
376: case NEX_MEM4I:
377: case NEX_MEM16:
378: case NEX_MEM16I:
379: printf("mcr%d at tr%d\n", nmcr, nexnum);
380: if (nmcr >= MAXNMCR) {
381: printf("%d mcr's", ++nmcr);
382: goto unconfig;
383: }
384: switch (cpu) {
385: #if VAX780
386: case VAX_780:
387: /* only ka780 code looks at type */
388: mcrtype[nmcr] = M780C;
389: break;
390: #endif
391: default:
392: break;
393: }
394: mcraddr[nmcr++] = (caddr_t)nxv;
395: break;
396:
397: #if VAX780
398: case NEX_MEM64I:
399: case NEX_MEM64L:
400: case NEX_MEM64LI:
401: case NEX_MEM256I:
402: case NEX_MEM256L:
403: case NEX_MEM256LI:
404: printf("mcr%d (el) at tr%d\n", nmcr, nexnum);
405: if (nmcr >= MAXNMCR) {
406: printf("%d mcr's", ++nmcr);
407: goto unconfig;
408: }
409: mcrtype[nmcr] = M780EL;
410: mcraddr[nmcr++] = (caddr_t)nxv;
411: if (nexcsr.nex_type != NEX_MEM64I &&
412: nexcsr.nex_type != NEX_MEM256I)
413: break;
414: /* fall into ... */
415:
416: case NEX_MEM64U:
417: case NEX_MEM64UI:
418: case NEX_MEM256U:
419: case NEX_MEM256UI:
420: printf("mcr%d (eu) at tr%d\n", nmcr, nexnum);
421: if (nmcr >= MAXNMCR) {
422: printf("%d mcr's", ++nmcr);
423: goto unconfig;
424: }
425: mcrtype[nmcr] = M780EU;
426: mcraddr[nmcr++] = (caddr_t)nxv;
427: break;
428: #endif
429:
430: case NEX_MPM0:
431: case NEX_MPM1:
432: case NEX_MPM2:
433: case NEX_MPM3:
434: printf("mpm");
435: goto unsupp;
436:
437: case NEX_CI:
438: printf("ci");
439: goto unsupp;
440:
441: default:
442: printf("nexus type %x", nexcsr.nex_type);
443: unsupp:
444: printf(" unsupported (at tr %d)\n", nexnum);
445: continue;
446: unconfig:
447: printf(" not configured\n");
448: continue;
449: }
450: }
451: if (nummba > NMBA)
452: nummba = NMBA;
453: if (numuba > NUBA)
454: numuba = NUBA;
455: if (nmcr > MAXNMCR)
456: nmcr = MAXNMCR;
457: }
458:
459: setscbnex(fn)
460: int (*fn)();
461: {
462: register struct scb *scbp = &scb[nsbi];
463:
464: scbp->scb_ipl14[nexnum] = scbp->scb_ipl15[nexnum] =
465: scbp->scb_ipl16[nexnum] = scbp->scb_ipl17[nexnum] =
466: scbentry(fn, SCB_ISTACK);
467: }
468: #endif
469:
470: #include "bi.h"
471: #if NBI > 0
472: /*
473: * Probe BI node space.
474: *
475: * THIS DEPENDS ON BI SPACE == NEXUS SPACE
476: * THIS WILL NOT WORK FOR MULTIPLE BIs
477: */
478: probe_bi(p)
479: register struct bibus *p;
480: {
481: register struct bi_node *biv, *bip;
482: register int node;
483: short dtype;
484:
485: /* must ignore BI errors while configuring */
486: bip = p->pbi_base;
487: ioaccess((caddr_t)bip, Nexmap[0], sizeof(*bip) * NNODEBI);/* XXX */
488: printf("vaxbi0 at address 0x%x\n", bip);
489: biv = (struct bi_node *) &nexus[0]; /* XXX */
490: for (node = 0; node < NNODEBI; node++, bip++, biv++) {
491: if (badaddr((caddr_t)biv, 4))
492: continue;
493: bi_nodes |= 1 << node; /* XXX */
494: dtype = biv->biic.bi_dtype;
495: /* clear bus errors */
496: biv->biic.bi_ber = ~(BIBER_MBZ|BIBER_NMR|BIBER_UPEN);
497: switch (dtype) {
498:
499: case BIDT_KA820: {
500: /* is this right?? */
501: int cp5 = biv->biic.bi_revs & 0x8000 ? '5' : '0';
502:
503: if (node != mastercpu) {
504: printf("slave ka82%c cpu", cp5);
505: goto unsupp;
506: }
507: printf("ka82%c cpu at node %x\n", cp5, node);
508: biv->biic.bi_intrdes = 1 << mastercpu;
509: biv->biic.bi_csr |= BICSR_SEIE | BICSR_HEIE;
510: break;
511: }
512:
513: case BIDT_DWBUA:
514: if (numuba >= NUBA || /*XXX*/numuba > 2) {
515: printf("%d uba's", ++numuba);
516: goto unconfig;
517: }
518: #if NUBA > 0
519: printf("uba%d at node %x\n", numuba, node);
520:
521: /*
522: * Run a self test reset to drop any `old' errors,
523: * so that they cannot cause a BI bus error.
524: */
525: (void) bi_selftest(&biv->biic);
526:
527: /*
528: * Enable interrupts. DWBUAs must have
529: * high priority.
530: */
531: biv->biic.bi_intrdes = 1 << mastercpu;
532: biv->biic.bi_csr = (biv->biic.bi_csr&~BICSR_ARB_MASK) |
533: BICSR_ARB_HIGH;
534: probeuba((struct uba_regs *)biv, (struct uba_regs *)bip,
535: (caddr_t)UMEM8200(node));
536: #endif /* NUBA */
537: break;
538:
539: case BIDT_MS820:
540: printf("mcr%d at node %x\n", nmcr, node);
541: if (nmcr >= MAXNMCR) {
542: printf("%d mcr's", ++nmcr);
543: goto unconfig;
544: }
545: mcraddr[nmcr++] = (caddr_t)biv;
546: biv->biic.bi_intrdes = 1 << mastercpu;
547: biv->biic.bi_csr |= BICSR_SEIE | BICSR_HEIE;
548: break;
549:
550: case BIDT_KDB50:
551: if (numkdb >= NKDB) {
552: printf("%d kdb's", ++numkdb);
553: goto unconfig;
554: }
555: #if NKDB > 0
556: printf("kdb%d at node %x\n", numkdb, node);
557: kdbconfig(numkdb, (struct biiregs *)biv,
558: (struct biiregs *)bip,
559: (int)&scb[0].scb_ipl15[node] - (int)&scb[0]);
560: scb[0].scb_ipl15[node] =
561: scbentry(kdbintv[numkdb], SCB_ISTACK);
562: kdbfind(numkdb);
563: #endif
564: numkdb++;
565: break;
566:
567: case BIDT_DEBNA:
568: case BIDT_DEBNK:
569: printf("debna/debnk ethernet");
570: goto unsupp;
571:
572: default:
573: printf("node type 0x%x ", dtype);
574: unsupp:
575: printf(" unsupported (at node %x)\n", node);
576: break;
577: unconfig:
578: printf(" not configured (at node %x)\n", node);
579: continue;
580: }
581: #ifdef DO_EINTRCSR
582: biv->biic.bi_eintrcsr = BIEIC_IPL17 |
583: (int)&scb[0].scb_bierr - (int)&scb[0];
584: /* but bi reset will need to restore this */
585: #endif
586: }
587: if (numuba > NUBA)
588: numuba = NUBA;
589: if (numkdb > NKDB)
590: numkdb = NKDB;
591: if (nmcr > MAXNMCR)
592: nmcr = MAXNMCR;
593: }
594:
595: #if NKDB > 0
596: /*
597: * Find drives attached to a particular KDB50.
598: */
599: kdbfind(kdbnum)
600: int kdbnum;
601: {
602: extern struct uba_driver kdbdriver;
603: register struct uba_device *ui;
604: register struct uba_driver *udp = &kdbdriver;
605: int t;
606:
607: for (ui = ubdinit; ui->ui_driver; ui++) {
608: /* ui->ui_ubanum is trash */
609: if (ui->ui_driver != udp || ui->ui_alive ||
610: ui->ui_ctlr != kdbnum && ui->ui_ctlr != '?')
611: continue;
612: t = ui->ui_ctlr;
613: ui->ui_ctlr = kdbnum;
614: if ((*udp->ud_slave)(ui) == 0) {
615: ui->ui_ctlr = t;
616: continue;
617: }
618: ui->ui_alive = 1;
619: ui->ui_ubanum = -1;
620:
621: /* make these invalid so we can see if someone uses them */
622: /* might as well make each one different too */
623: ui->ui_hd = (struct uba_hd *)0xc0000010;
624: ui->ui_addr = (caddr_t)0xc0000014;
625: ui->ui_physaddr = (caddr_t)0xc0000018;
626: ui->ui_mi = (struct uba_ctlr *)0xc000001c;
627:
628: if (ui->ui_dk && dkn < DK_NDRIVE)
629: ui->ui_dk = dkn++;
630: else
631: ui->ui_dk = -1;
632: /* ui_type comes from driver */
633: udp->ud_dinfo[ui->ui_unit] = ui;
634: printf("%s%d at %s%d slave %d\n",
635: udp->ud_dname, ui->ui_unit,
636: udp->ud_mname, ui->ui_ctlr, ui->ui_slave);
637: (*udp->ud_attach)(ui);
638: }
639: }
640: #endif /* NKDB > 0 */
641: #endif /* NBI > 0 */
642:
643: #if NMBA > 0
644: struct mba_device *mbaconfig();
645: /*
646: * Find devices attached to a particular mba
647: * and look for each device found in the massbus
648: * initialization tables.
649: */
650: mbafind(nxv, nxp)
651: struct nexus *nxv, *nxp;
652: {
653: register struct mba_regs *mdp;
654: register struct mba_drv *mbd;
655: register struct mba_device *mi;
656: register struct mba_slave *ms;
657: int dn, dt, sn;
658: struct mba_device fnd;
659:
660: mdp = (struct mba_regs *)nxv;
661: mba_hd[nummba].mh_mba = mdp;
662: mba_hd[nummba].mh_physmba = (struct mba_regs *)nxp;
663: setscbnex(mbaintv[nummba]);
664: mdp->mba_cr = MBCR_INIT;
665: mdp->mba_cr = MBCR_IE;
666: fnd.mi_mba = mdp;
667: fnd.mi_mbanum = nummba;
668: for (mbd = mdp->mba_drv, dn = 0; mbd < &mdp->mba_drv[8]; mbd++, dn++) {
669: if ((mbd->mbd_ds&MBDS_DPR) == 0)
670: continue;
671: mdp->mba_sr |= MBSR_NED; /* si kludge */
672: dt = mbd->mbd_dt & 0xffff;
673: if (dt == 0)
674: continue;
675: if (mdp->mba_sr&MBSR_NED)
676: continue; /* si kludge */
677: if (dt == MBDT_MOH)
678: continue;
679: fnd.mi_drive = dn;
680: #define qeq(a, b) ( a == b || a == '?' )
681: if ((mi = mbaconfig(&fnd, dt)) && (dt & MBDT_TAP))
682: for (sn = 0; sn < 8; sn++) {
683: mbd->mbd_tc = sn;
684: for (ms = mbsinit; ms->ms_driver; ms++)
685: if (ms->ms_driver == mi->mi_driver &&
686: ms->ms_alive == 0 &&
687: qeq(ms->ms_ctlr, mi->mi_unit) &&
688: qeq(ms->ms_slave, sn) &&
689: (*ms->ms_driver->md_slave)(mi, ms, sn)) {
690: printf("%s%d at %s%d slave %d\n"
691: , ms->ms_driver->md_sname
692: , ms->ms_unit
693: , mi->mi_driver->md_dname
694: , mi->mi_unit
695: , sn
696: );
697: ms->ms_alive = 1;
698: ms->ms_ctlr = mi->mi_unit;
699: ms->ms_slave = sn;
700: break;
701: }
702: }
703: }
704: }
705:
706: /*
707: * Have found a massbus device;
708: * see if it is in the configuration table.
709: * If so, fill in its data.
710: */
711: struct mba_device *
712: mbaconfig(ni, type)
713: register struct mba_device *ni;
714: register int type;
715: {
716: register struct mba_device *mi;
717: register short *tp;
718: register struct mba_hd *mh;
719:
720: for (mi = mbdinit; mi->mi_driver; mi++) {
721: if (mi->mi_alive)
722: continue;
723: tp = mi->mi_driver->md_type;
724: for (mi->mi_type = 0; *tp; tp++, mi->mi_type++)
725: if (*tp == (type&MBDT_TYPE))
726: goto found;
727: continue;
728: found:
729: #define match(fld) (ni->fld == mi->fld || mi->fld == '?')
730: if (!match(mi_drive) || !match(mi_mbanum))
731: continue;
732: printf("%s%d at mba%d drive %d",
733: mi->mi_driver->md_dname, mi->mi_unit,
734: ni->mi_mbanum, ni->mi_drive);
735: mi->mi_alive = 1;
736: mh = &mba_hd[ni->mi_mbanum];
737: mi->mi_hd = mh;
738: mh->mh_mbip[ni->mi_drive] = mi;
739: mh->mh_ndrive++;
740: mi->mi_mba = ni->mi_mba;
741: mi->mi_drv = &mi->mi_mba->mba_drv[ni->mi_drive];
742: mi->mi_mbanum = ni->mi_mbanum;
743: mi->mi_drive = ni->mi_drive;
744: /*
745: * If drive has never been seen before,
746: * give it a dkn for statistics.
747: */
748: if (mi->mi_driver->md_info[mi->mi_unit] == 0) {
749: mi->mi_driver->md_info[mi->mi_unit] = mi;
750: if (mi->mi_dk && dkn < DK_NDRIVE)
751: mi->mi_dk = dkn++;
752: else
753: mi->mi_dk = -1;
754: }
755: (*mi->mi_driver->md_attach)(mi);
756: printf("\n");
757: return (mi);
758: }
759: return (0);
760: }
761: #endif
762:
763: /*
764: * Fixctlrmask fixes the masks of the driver ctlr routines
765: * which otherwise save r10 and r11 where the interrupt and br
766: * level are passed through.
767: */
768: fixctlrmask()
769: {
770: register struct uba_ctlr *um;
771: register struct uba_device *ui;
772: register struct uba_driver *ud;
773: #define phys(a,b) ((b)(((int)(a))&0x7fffffff))
774:
775: for (um = ubminit; ud = phys(um->um_driver, struct uba_driver *); um++)
776: *phys(ud->ud_probe, short *) &= ~0xc00;
777: for (ui = ubdinit; ud = phys(ui->ui_driver, struct uba_driver *); ui++)
778: *phys(ud->ud_probe, short *) &= ~0xc00;
779: }
780:
781: #ifdef QBA
782: /*
783: * Configure a Q-bus.
784: */
785: probeqbus(qb)
786: struct qbus *qb;
787: {
788: register struct uba_hd *uhp = &uba_hd[numuba];
789:
790: ioaccess((caddr_t)qb->qb_map, Nexmap[0],
791: qb->qb_memsize * sizeof (struct pte));
792: uhp->uh_type = qb->qb_type;
793: uhp->uh_uba = (struct uba_regs *)0xc0000000; /* no uba adaptor regs */
794: uhp->uh_mr = (struct pte *)&nexus[0];
795: /*
796: * The map registers start right at 20088000 on the
797: * ka630, so we have to subtract out the 2k offset to make the
798: * pointers work..
799: */
800: uhp->uh_physuba = (struct uba_regs *)(((u_long)qb->qb_map)-0x800);
801:
802: uhp->uh_memsize = qb->qb_memsize;
803: ioaccess(qb->qb_maddr, UMEMmap[numuba], uhp->uh_memsize * NBPG);
804: uhp->uh_mem = umem[numuba];
805:
806: /*
807: * The I/O page is mapped to the 8K of the umem address space
808: * immediately after the memory section that is mapped.
809: */
810: ioaccess(qb->qb_iopage, UMEMmap[numuba] + uhp->uh_memsize,
811: UBAIOPAGES * NBPG);
812: uhp->uh_iopage = umem[numuba] + (uhp->uh_memsize * NBPG);
813:
814: unifind(uhp, qb->qb_iopage);
815: }
816: #endif
817:
818: #if NUBA > 0
819: probeuba(vubp, pubp, pumem)
820: struct uba_regs *vubp, *pubp;
821: caddr_t pumem;
822: {
823: register struct uba_hd *uhp = &uba_hd[numuba];
824:
825: /*
826: * Save virtual and physical addresses of adaptor.
827: */
828: switch (cpu) {
829: #ifdef DW780
830: case VAX_8600:
831: case VAX_780:
832: uhp->uh_type = DW780;
833: break;
834: #endif
835: #ifdef DW750
836: case VAX_750:
837: uhp->uh_type = DW750;
838: break;
839: #endif
840: #ifdef DW730
841: case VAX_730:
842: uhp->uh_type = DW730;
843: break;
844: #endif
845: #ifdef DWBUA
846: case VAX_8200:
847: uhp->uh_type = DWBUA;
848: break;
849: #endif
850: default:
851: panic("unknown UBA type");
852: /*NOTREACHED*/
853: }
854: uhp->uh_uba = vubp;
855: uhp->uh_physuba = pubp;
856: uhp->uh_mr = vubp->uba_map;
857: uhp->uh_memsize = UBAPAGES;
858:
859: ioaccess(pumem, UMEMmap[numuba], (UBAPAGES + UBAIOPAGES) * NBPG);
860: uhp->uh_mem = umem[numuba];
861: uhp->uh_iopage = umem[numuba] + (uhp->uh_memsize * NBPG);
862:
863: unifind(uhp, pumem + (uhp->uh_memsize * NBPG));
864: }
865:
866: /*
867: * Find devices on a UNIBUS.
868: * Uses per-driver routine to set <br,cvec> into <r11,r10>,
869: * and then fills in the tables, with help from a per-driver
870: * slave initialization routine.
871: */
872: unifind(uhp0, pumem)
873: struct uba_hd *uhp0;
874: caddr_t pumem;
875: {
876: #ifndef lint
877: register int br, cvec; /* MUST BE r11, r10 */
878: #else
879: /*
880: * Lint doesn't realize that these
881: * can be initialized asynchronously
882: * when devices interrupt.
883: */
884: register int br = 0, cvec = 0;
885: #endif
886: register struct uba_device *ui;
887: register struct uba_ctlr *um;
888: register struct uba_hd *uhp = uhp0;
889: u_short *reg, *ap, addr;
890: struct uba_driver *udp;
891: int i, (**ivec)();
892: caddr_t ualloc;
893: extern quad catcher[128];
894: #if DW780 || DWBUA
895: struct uba_regs *vubp = uhp->uh_uba;
896: #endif
897:
898: /*
899: * Initialize the UNIBUS, by freeing the map
900: * registers and the buffered data path registers
901: */
902: uhp->uh_map = (struct map *)
903: malloc((u_long)(UAMSIZ * sizeof (struct map)), M_DEVBUF,
904: M_NOWAIT);
905: if (uhp->uh_map == 0)
906: panic("no mem for unibus map");
907: bzero((caddr_t)uhp->uh_map, (unsigned)(UAMSIZ * sizeof (struct map)));
908: ubainitmaps(uhp);
909:
910: /*
911: * Initialize space for the UNIBUS interrupt vectors.
912: * On the 8600, can't use first slot in UNIvec
913: * (the vectors for the second SBI overlap it);
914: * move each set of vectors forward.
915: */
916: #if VAX8600
917: if (cpu == VAX_8600)
918: uhp->uh_vec = UNIvec[numuba + 1];
919: else
920: #endif
921: uhp->uh_vec = UNIvec[numuba];
922: for (i = 0; i < 128; i++)
923: uhp->uh_vec[i] = scbentry(&catcher[i], SCB_ISTACK);
924: /*
925: * Set last free interrupt vector for devices with
926: * programmable interrupt vectors. Use is to decrement
927: * this number and use result as interrupt vector.
928: */
929: uhp->uh_lastiv = 0x200;
930:
931: #ifdef DWBUA
932: if (uhp->uh_type == DWBUA)
933: BUA(vubp)->bua_offset = (int)uhp->uh_vec - (int)&scb[0];
934: #endif
935:
936: #ifdef DW780
937: if (uhp->uh_type == DW780) {
938: vubp->uba_sr = vubp->uba_sr;
939: vubp->uba_cr = UBACR_IFS|UBACR_BRIE;
940: }
941: #endif
942: /*
943: * First configure devices that have unibus memory,
944: * allowing them to allocate the correct map registers.
945: */
946: ubameminit(numuba);
947: /*
948: * Grab some memory to record the umem address space we allocate,
949: * so we can be sure not to place two devices at the same address.
950: *
951: * We could use just 1/8 of this (we only want a 1 bit flag) but
952: * we are going to give it back anyway, and that would make the
953: * code here bigger (which we can't give back), so ...
954: *
955: * One day, someone will make a unibus with something other than
956: * an 8K i/o address space, & screw this totally.
957: */
958: ualloc = (caddr_t)malloc((u_long)(8 * 1024), M_TEMP, M_NOWAIT);
959: if (ualloc == (caddr_t)0)
960: panic("no mem for unifind");
961: bzero(ualloc, 8*1024);
962:
963: /*
964: * Map the first page of UNIBUS i/o
965: * space to the first page of memory
966: * for devices which will need to dma
967: * output to produce an interrupt.
968: */
969: *(int *)(&uhp->uh_mr[0]) = UBAMR_MRV;
970:
971: #define ubaddr(uhp, off) (u_short *)((int)(uhp)->uh_iopage + ubdevreg(off))
972: /*
973: * Check each unibus mass storage controller.
974: * For each one which is potentially on this uba,
975: * see if it is really there, and if it is record it and
976: * then go looking for slaves.
977: */
978: for (um = ubminit; udp = um->um_driver; um++) {
979: if (um->um_ubanum != numuba && um->um_ubanum != '?' ||
980: um->um_alive)
981: continue;
982: addr = (u_short)um->um_addr;
983: /*
984: * use the particular address specified first,
985: * or if it is given as "0", of there is no device
986: * at that address, try all the standard addresses
987: * in the driver til we find it
988: */
989: for (ap = udp->ud_addr; addr || (addr = *ap++); addr = 0) {
990:
991: if (ualloc[ubdevreg(addr)])
992: continue;
993: reg = ubaddr(uhp, addr);
994: if (badaddr((caddr_t)reg, 2))
995: continue;
996: #ifdef DW780
997: if (uhp->uh_type == DW780 && vubp->uba_sr) {
998: vubp->uba_sr = vubp->uba_sr;
999: continue;
1000: }
1001: #endif
1002: cvec = 0x200;
1003: i = (*udp->ud_probe)(reg, um->um_ctlr, um);
1004: #ifdef DW780
1005: if (uhp->uh_type == DW780 && vubp->uba_sr) {
1006: vubp->uba_sr = vubp->uba_sr;
1007: continue;
1008: }
1009: #endif
1010: if (i == 0)
1011: continue;
1012: printf("%s%d at uba%d csr %o ",
1013: udp->ud_mname, um->um_ctlr, numuba, addr);
1014: if (cvec == 0) {
1015: printf("zero vector\n");
1016: continue;
1017: }
1018: if (cvec == 0x200) {
1019: printf("didn't interrupt\n");
1020: continue;
1021: }
1022: printf("vec %o, ipl %x\n", cvec, br);
1023: csralloc(ualloc, addr, i);
1024: um->um_alive = 1;
1025: um->um_ubanum = numuba;
1026: um->um_hd = uhp;
1027: um->um_addr = (caddr_t)reg;
1028: udp->ud_minfo[um->um_ctlr] = um;
1029: for (cvec /= 4, ivec = um->um_intr; *ivec; cvec++, ivec++)
1030: uhp->uh_vec[cvec] = scbentry(*ivec, SCB_ISTACK);
1031: for (ui = ubdinit; ui->ui_driver; ui++) {
1032: int t;
1033:
1034: if (ui->ui_driver != udp || ui->ui_alive ||
1035: ui->ui_ctlr != um->um_ctlr && ui->ui_ctlr != '?' ||
1036: ui->ui_ubanum != numuba && ui->ui_ubanum != '?')
1037: continue;
1038: t = ui->ui_ctlr;
1039: ui->ui_ctlr = um->um_ctlr;
1040: if ((*udp->ud_slave)(ui, reg) == 0)
1041: ui->ui_ctlr = t;
1042: else {
1043: ui->ui_alive = 1;
1044: ui->ui_ubanum = numuba;
1045: ui->ui_hd = uhp;
1046: ui->ui_addr = (caddr_t)reg;
1047: ui->ui_physaddr = pumem + ubdevreg(addr);
1048: if (ui->ui_dk && dkn < DK_NDRIVE)
1049: ui->ui_dk = dkn++;
1050: else
1051: ui->ui_dk = -1;
1052: ui->ui_mi = um;
1053: /* ui_type comes from driver */
1054: udp->ud_dinfo[ui->ui_unit] = ui;
1055: printf("%s%d at %s%d slave %d",
1056: udp->ud_dname, ui->ui_unit,
1057: udp->ud_mname, um->um_ctlr, ui->ui_slave);
1058: (*udp->ud_attach)(ui);
1059: printf("\n");
1060: }
1061: }
1062: break;
1063: }
1064: }
1065: /*
1066: * Now look for non-mass storage peripherals.
1067: */
1068: for (ui = ubdinit; udp = ui->ui_driver; ui++) {
1069: if (ui->ui_ubanum != numuba && ui->ui_ubanum != '?' ||
1070: ui->ui_alive || ui->ui_slave != -1)
1071: continue;
1072: addr = (u_short)ui->ui_addr;
1073:
1074: for (ap = udp->ud_addr; addr || (addr = *ap++); addr = 0) {
1075:
1076: if (ualloc[ubdevreg(addr)])
1077: continue;
1078: reg = ubaddr(uhp, addr);
1079: if (badaddr((caddr_t)reg, 2))
1080: continue;
1081: #ifdef DW780
1082: if (uhp->uh_type == DW780 && vubp->uba_sr) {
1083: vubp->uba_sr = vubp->uba_sr;
1084: continue;
1085: }
1086: #endif
1087: cvec = 0x200;
1088: i = (*udp->ud_probe)(reg, ui);
1089: #ifdef DW780
1090: if (uhp->uh_type == DW780 && vubp->uba_sr) {
1091: vubp->uba_sr = vubp->uba_sr;
1092: continue;
1093: }
1094: #endif
1095: if (i == 0)
1096: continue;
1097: printf("%s%d at uba%d csr %o ",
1098: ui->ui_driver->ud_dname, ui->ui_unit, numuba, addr);
1099: if (cvec == 0) {
1100: printf("zero vector\n");
1101: continue;
1102: }
1103: if (cvec == 0x200) {
1104: printf("didn't interrupt\n");
1105: continue;
1106: }
1107: printf("vec %o, ipl %x\n", cvec, br);
1108: csralloc(ualloc, addr, i);
1109: ui->ui_hd = uhp;
1110: for (cvec /= 4, ivec = ui->ui_intr; *ivec; cvec++, ivec++)
1111: uhp->uh_vec[cvec] = scbentry(*ivec, SCB_ISTACK);
1112: ui->ui_alive = 1;
1113: ui->ui_ubanum = numuba;
1114: ui->ui_addr = (caddr_t)reg;
1115: ui->ui_physaddr = pumem + ubdevreg(addr);
1116: ui->ui_dk = -1;
1117: /* ui_type comes from driver */
1118: udp->ud_dinfo[ui->ui_unit] = ui;
1119: (*udp->ud_attach)(ui);
1120: break;
1121: }
1122: }
1123:
1124: #ifdef DW780
1125: if (uhp->uh_type == DW780)
1126: uhp->uh_uba->uba_cr = UBACR_IFS | UBACR_BRIE |
1127: UBACR_USEFIE | UBACR_SUEFIE |
1128: (uhp->uh_uba->uba_cr & 0x7c000000);
1129: #endif
1130: numuba++;
1131:
1132: #ifdef AUTO_DEBUG
1133: printf("Unibus allocation map");
1134: for (i = 0; i < 8*1024; ) {
1135: register n, m;
1136:
1137: if ((i % 128) == 0) {
1138: printf("\n%6o:", i);
1139: for (n = 0; n < 128; n++)
1140: if (ualloc[i+n])
1141: break;
1142: if (n == 128) {
1143: i += 128;
1144: continue;
1145: }
1146: }
1147:
1148: for (n = m = 0; n < 16; n++) {
1149: m <<= 1;
1150: m |= ualloc[i++];
1151: }
1152:
1153: printf(" %4x", m);
1154: }
1155: printf("\n");
1156: #endif
1157:
1158: free(ualloc, M_TEMP);
1159: }
1160: #endif /* NUBA */
1161:
1162: /*
1163: * Mark addresses starting at "addr" and continuing
1164: * "size" bytes as allocated in the map "ualloc".
1165: * Warn if the new allocation overlaps a previous allocation.
1166: */
1167: static
1168: csralloc(ualloc, addr, size)
1169: caddr_t ualloc;
1170: u_short addr;
1171: register int size;
1172: {
1173: register caddr_t p;
1174: int warned = 0;
1175:
1176: p = &ualloc[ubdevreg(addr+size)];
1177: while (--size >= 0) {
1178: if (*--p && !warned) {
1179: printf(
1180: "WARNING: device registers overlap those for a previous device!\n");
1181: warned = 1;
1182: }
1183: *p = 1;
1184: }
1185: }
1186:
1187: /*
1188: * Make an IO register area accessible at physical address physa
1189: * by mapping kernel ptes starting at pte.
1190: */
1191: ioaccess(physa, pte, size)
1192: caddr_t physa;
1193: register struct pte *pte;
1194: int size;
1195: {
1196: register int i = btoc(size);
1197: register unsigned v = btop(physa);
1198:
1199: do
1200: *(int *)pte++ = PG_V|PG_KW|v++;
1201: while (--i > 0);
1202: mtpr(TBIA, 0);
1203: }
1204:
1205: /*
1206: * Configure swap space and related parameters.
1207: */
1208: swapconf()
1209: {
1210: register struct swdevt *swp;
1211: register int nblks;
1212:
1213: for (swp = swdevt; swp->sw_dev; swp++)
1214: if (bdevsw[major(swp->sw_dev)].d_psize) {
1215: nblks =
1216: (*bdevsw[major(swp->sw_dev)].d_psize)(swp->sw_dev);
1217: if (nblks != -1 &&
1218: (swp->sw_nblks == 0 || swp->sw_nblks > nblks))
1219: swp->sw_nblks = nblks;
1220: }
1221: if (dumplo == 0 && bdevsw[major(dumpdev)].d_psize)
1222: dumplo = (*bdevsw[major(dumpdev)].d_psize)(dumpdev) - physmem;
1223: if (dumplo < 0)
1224: dumplo = 0;
1225: }
1226:
1227: #define DOSWAP /* Change swdevt, argdev, and dumpdev too */
1228: u_long bootdev; /* should be dev_t, but not until 32 bits */
1229:
1230: static char devname[][2] = {
1231: 'h','p', /* 0 = hp */
1232: 0,0, /* 1 = ht */
1233: 'u','p', /* 2 = up */
1234: 'r','k', /* 3 = hk */
1235: 0,0, /* 4 = sw */
1236: 0,0, /* 5 = tm */
1237: 0,0, /* 6 = ts */
1238: 0,0, /* 7 = mt */
1239: 0,0, /* 8 = tu */
1240: 'r','a', /* 9 = ra */
1241: 0,0, /* 10 = ut */
1242: 'r','b', /* 11 = rb */
1243: 0,0, /* 12 = uu */
1244: 0,0, /* 13 = rx */
1245: 'r','l', /* 14 = rl */
1246: 0,0, /* 15 = tmscp */
1247: 'k','r', /* 16 = ra on kdb50 */
1248: };
1249:
1250: #define PARTITIONMASK 0x7
1251: #define PARTITIONSHIFT 3
1252:
1253: /*
1254: * Attempt to find the device from which we were booted.
1255: * If we can do so, and not instructed not to do so,
1256: * change rootdev to correspond to the load device.
1257: */
1258: setroot()
1259: {
1260: int majdev, mindev, unit, part, controller, adaptor;
1261: dev_t temp, orootdev;
1262: struct swdevt *swp;
1263:
1264: if (boothowto & RB_DFLTROOT ||
1265: (bootdev & B_MAGICMASK) != (u_long)B_DEVMAGIC)
1266: return;
1267: majdev = B_TYPE(bootdev);
1268: if (majdev >= sizeof(devname) / sizeof(devname[0]))
1269: return;
1270: adaptor = B_ADAPTOR(bootdev);
1271: controller = B_CONTROLLER(bootdev);
1272: part = B_PARTITION(bootdev);
1273: unit = B_UNIT(bootdev);
1274: if (majdev == 0) { /* MBA device */
1275: #if NMBA > 0
1276: register struct mba_device *mbap;
1277: int mask;
1278:
1279: /*
1280: * The MBA number used at boot time is not necessarily the same as the
1281: * MBA number used by the kernel. In order to change the rootdev we need to
1282: * convert the boot MBA number to the kernel MBA number. The address space
1283: * for an MBA used by the boot code is 0x20010000 + 0x2000 * MBA_number
1284: * on the 78? and 86?0, 0xf28000 + 0x2000 * MBA_number on the 750.
1285: * Therefore we can search the mba_hd table for the MBA that has the physical
1286: * address corresponding to the boot MBA number.
1287: */
1288: #define PHYSADRSHFT 13
1289: #define PHYSMBAMASK780 0x7
1290: #define PHYSMBAMASK750 0x3
1291:
1292: switch (cpu) {
1293:
1294: case VAX_780:
1295: case VAX_8600:
1296: default:
1297: mask = PHYSMBAMASK780;
1298: break;
1299:
1300: case VAX_750:
1301: mask = PHYSMBAMASK750;
1302: break;
1303: }
1304: for (mbap = mbdinit; mbap->mi_driver; mbap++)
1305: if (mbap->mi_alive && mbap->mi_drive == unit &&
1306: (((long)mbap->mi_hd->mh_physmba >> PHYSADRSHFT)
1307: & mask) == adaptor)
1308: break;
1309: if (mbap->mi_driver == 0)
1310: return;
1311: mindev = mbap->mi_unit;
1312: #else
1313: return;
1314: #endif
1315: } else {
1316: register struct uba_device *ubap;
1317:
1318: for (ubap = ubdinit; ubap->ui_driver; ubap++)
1319: if (ubap->ui_alive && ubap->ui_slave == unit &&
1320: ubap->ui_ctlr == controller &&
1321: ubap->ui_ubanum == adaptor &&
1322: ubap->ui_driver->ud_dname[0] == devname[majdev][0] &&
1323: ubap->ui_driver->ud_dname[1] == devname[majdev][1])
1324: break;
1325:
1326: if (ubap->ui_driver == 0)
1327: return;
1328: mindev = ubap->ui_unit;
1329: }
1330: mindev = (mindev << PARTITIONSHIFT) + part;
1331: orootdev = rootdev;
1332: rootdev = makedev(majdev, mindev);
1333: /*
1334: * If the original rootdev is the same as the one
1335: * just calculated, don't need to adjust the swap configuration.
1336: */
1337: if (rootdev == orootdev)
1338: return;
1339:
1340: printf("Changing root device to %c%c%d%c\n",
1341: devname[majdev][0], devname[majdev][1],
1342: mindev >> PARTITIONSHIFT, part + 'a');
1343:
1344: #ifdef DOSWAP
1345: mindev &= ~PARTITIONMASK;
1346: for (swp = swdevt; swp->sw_dev; swp++) {
1347: if (majdev == major(swp->sw_dev) &&
1348: mindev == (minor(swp->sw_dev) & ~PARTITIONMASK)) {
1349: temp = swdevt[0].sw_dev;
1350: swdevt[0].sw_dev = swp->sw_dev;
1351: swp->sw_dev = temp;
1352: break;
1353: }
1354: }
1355: if (swp->sw_dev == 0)
1356: return;
1357:
1358: /*
1359: * If argdev and dumpdev were the same as the old primary swap
1360: * device, move them to the new primary swap device.
1361: */
1362: if (temp == dumpdev)
1363: dumpdev = swdevt[0].sw_dev;
1364: if (temp == argdev)
1365: argdev = swdevt[0].sw_dev;
1366: #endif
1367: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.