|
|
1.1 root 1: /* if_il.c 6.2 83/09/24 */
2:
3: #include "il.h"
4:
5: /*
6: * Interlan Ethernet Communications Controller interface
7: */
8: #include "../machine/pte.h"
9:
10: #include "../h/param.h"
11: #include "../h/systm.h"
12: #include "../h/mbuf.h"
13: #include "../h/buf.h"
14: #include "../h/protosw.h"
15: #include "../h/socket.h"
16: #include "../h/vmmac.h"
17: #include "../h/ioctl.h"
18: #include "../h/errno.h"
19:
20: #include "../net/if.h"
21: #include "../net/netisr.h"
22: #include "../net/route.h"
23: #include "../netinet/in.h"
24: #include "../netinet/in_systm.h"
25: #include "../netinet/ip.h"
26: #include "../netinet/ip_var.h"
27: #include "../netinet/if_ether.h"
28: #include "../netpup/pup.h"
29:
30: #include "../vax/cpu.h"
31: #include "../vax/mtpr.h"
32: #include "../vaxif/if_il.h"
33: #include "../vaxif/if_ilreg.h"
34: #include "../vaxif/if_uba.h"
35: #include "../vaxuba/ubareg.h"
36: #include "../vaxuba/ubavar.h"
37:
38: int ilprobe(), ilattach(), ilrint(), ilcint();
39: struct uba_device *ilinfo[NIL];
40: u_short ilstd[] = { 0 };
41: struct uba_driver ildriver =
42: { ilprobe, 0, ilattach, 0, ilstd, "il", ilinfo };
43: #define ILUNIT(x) minor(x)
44: int ilinit(),iloutput(),ilioctl(),ilreset(),ilwatch();
45:
46: /*
47: * Ethernet software status per interface.
48: *
49: * Each interface is referenced by a network interface structure,
50: * is_if, which the routing code uses to locate the interface.
51: * This structure contains the output queue for the interface, its address, ...
52: * We also have, for each interface, a UBA interface structure, which
53: * contains information about the UNIBUS resources held by the interface:
54: * map registers, buffered data paths, etc. Information is cached in this
55: * structure for use by the if_uba.c routines in running the interface
56: * efficiently.
57: */
58: struct il_softc {
59: struct arpcom is_ac; /* Ethernet common part */
60: #define is_if is_ac.ac_if /* network-visible interface */
61: #define is_addr is_ac.ac_enaddr /* hardware Ethernet address */
62: struct ifuba is_ifuba; /* UNIBUS resources */
63: int is_flags;
64: #define ILF_OACTIVE 0x1 /* output is active */
65: #define ILF_RCVPENDING 0x2 /* start rcv in ilcint */
66: #define ILF_STATPENDING 0x4 /* stat cmd pending */
67: short is_lastcmd; /* can't read csr, so must save it */
68: short is_scaninterval; /* interval of stat collection */
69: #define ILWATCHINTERVAL 60 /* once every 60 seconds */
70: struct il_stats is_stats; /* holds on-board statistics */
71: struct il_stats is_sum; /* summation over time */
72: int is_ubaddr; /* mapping registers of is_stats */
73: } il_softc[NIL];
74:
75: ilprobe(reg)
76: caddr_t reg;
77: {
78: register int br, cvec; /* r11, r10 value-result */
79: register struct ildevice *addr = (struct ildevice *)reg;
80: register i;
81:
82: #ifdef lint
83: br = 0; cvec = br; br = cvec;
84: i = 0; ilrint(i); ilcint(i); ilwatch(i);
85: #endif
86:
87: addr->il_csr = ILC_OFFLINE|IL_CIE;
88: DELAY(100000);
89: i = addr->il_csr; /* clear CDONE */
90: if (cvec > 0 && cvec != 0x200)
91: cvec -= 4;
92: return (1);
93: }
94:
95: /*
96: * Interface exists: make available by filling in network interface
97: * record. System will initialize the interface when it is ready
98: * to accept packets. A STATUS command is done to get the ethernet
99: * address and other interesting data.
100: */
101: ilattach(ui)
102: struct uba_device *ui;
103: {
104: register struct il_softc *is = &il_softc[ui->ui_unit];
105: register struct ifnet *ifp = &is->is_if;
106: register struct ildevice *addr = (struct ildevice *)ui->ui_addr;
107: struct sockaddr_in *sin;
108:
109: ifp->if_unit = ui->ui_unit;
110: ifp->if_name = "il";
111: ifp->if_mtu = ETHERMTU;
112:
113: /*
114: * Reset the board and map the statistics
115: * buffer onto the Unibus.
116: */
117: addr->il_csr = ILC_RESET;
118: while ((addr->il_csr&IL_CDONE) == 0)
119: ;
120: if (addr->il_csr&IL_STATUS)
121: printf("il%d: reset failed, csr=%b\n", ui->ui_unit,
122: addr->il_csr, IL_BITS);
123:
124: is->is_ubaddr = uballoc(ui->ui_ubanum, (caddr_t)&is->is_stats,
125: sizeof (struct il_stats), 0);
126: addr->il_bar = is->is_ubaddr & 0xffff;
127: addr->il_bcr = sizeof (struct il_stats);
128: addr->il_csr = ((is->is_ubaddr >> 2) & IL_EUA)|ILC_STAT;
129: while ((addr->il_csr&IL_CDONE) == 0)
130: ;
131: if (addr->il_csr&IL_STATUS)
132: printf("il%d: status failed, csr=%b\n", ui->ui_unit,
133: addr->il_csr, IL_BITS);
134: ubarelse(ui->ui_ubanum, &is->is_ubaddr);
135: #ifdef notdef
136: printf("il%d: addr=%x:%x:%x:%x:%x:%x module=%s firmware=%s\n",
137: ui->ui_unit,
138: is->is_stats.ils_addr[0]&0xff, is->is_stats.ils_addr[1]&0xff,
139: is->is_stats.ils_addr[2]&0xff, is->is_stats.ils_addr[3]&0xff,
140: is->is_stats.ils_addr[4]&0xff, is->is_stats.ils_addr[5]&0xff,
141: is->is_stats.ils_module, is->is_stats.ils_firmware);
142: #endif
143: bcopy((caddr_t)is->is_stats.ils_addr, (caddr_t)is->is_addr,
144: sizeof (is->is_addr));
145: sin = (struct sockaddr_in *)&ifp->if_addr;
146: sin->sin_family = AF_INET;
147: sin->sin_addr = arpmyaddr((struct arpcom *)0);
148: ifp->if_init = ilinit;
149: ifp->if_output = iloutput;
150: ifp->if_ioctl = ilioctl;
151: ifp->if_reset = ilreset;
152: is->is_ifuba.ifu_flags = UBA_CANTWAIT;
153: #ifdef notdef
154: is->is_ifuba.ifu_flags |= UBA_NEEDBDP;
155: #endif
156: if_attach(ifp);
157: }
158:
159: /*
160: * Reset of interface after UNIBUS reset.
161: * If interface is on specified uba, reset its state.
162: */
163: ilreset(unit, uban)
164: int unit, uban;
165: {
166: register struct uba_device *ui;
167:
168: if (unit >= NIL || (ui = ilinfo[unit]) == 0 || ui->ui_alive == 0 ||
169: ui->ui_ubanum != uban)
170: return;
171: printf(" il%d", unit);
172: ilinit(unit);
173: }
174:
175: /*
176: * Initialization of interface; clear recorded pending
177: * operations, and reinitialize UNIBUS usage.
178: */
179: ilinit(unit)
180: int unit;
181: {
182: register struct il_softc *is = &il_softc[unit];
183: register struct uba_device *ui = ilinfo[unit];
184: register struct ildevice *addr;
185: register struct ifnet *ifp = &is->is_if;
186: register struct sockaddr_in *sin;
187: int s;
188:
189: sin = (struct sockaddr_in *)&ifp->if_addr;
190: if (sin->sin_addr.s_addr == 0) /* address still unknown */
191: return;
192:
193: if (ifp->if_flags & IFF_RUNNING)
194: goto justarp;
195: if (if_ubainit(&is->is_ifuba, ui->ui_ubanum,
196: sizeof (struct il_rheader), (int)btoc(ETHERMTU)) == 0) {
197: printf("il%d: can't initialize\n", unit);
198: is->is_if.if_flags &= ~IFF_UP;
199: return;
200: }
201: is->is_ubaddr = uballoc(ui->ui_ubanum, (caddr_t)&is->is_stats,
202: sizeof (struct il_stats), 0);
203: ifp->if_watchdog = ilwatch;
204: is->is_scaninterval = ILWATCHINTERVAL;
205: ifp->if_timer = is->is_scaninterval;
206: addr = (struct ildevice *)ui->ui_addr;
207:
208: /*
209: * Turn off source address insertion (it's faster this way),
210: * and set board online. Former doesn't work if board is
211: * already online (happens on ubareset), so we put it offline
212: * first.
213: */
214: s = splimp();
215: addr->il_csr = ILC_OFFLINE;
216: while ((addr->il_csr & IL_CDONE) == 0)
217: ;
218: addr->il_csr = ILC_CISA;
219: while ((addr->il_csr & IL_CDONE) == 0)
220: ;
221: /*
222: * Set board online.
223: * Hang receive buffer and start any pending
224: * writes by faking a transmit complete.
225: * Receive bcr is not a muliple of 4 so buffer
226: * chaining can't happen.
227: */
228: addr->il_csr = ILC_ONLINE;
229: while ((addr->il_csr & IL_CDONE) == 0)
230: ;
231: addr->il_bar = is->is_ifuba.ifu_r.ifrw_info & 0xffff;
232: addr->il_bcr = sizeof(struct il_rheader) + ETHERMTU + 6;
233: addr->il_csr =
234: ((is->is_ifuba.ifu_r.ifrw_info >> 2) & IL_EUA)|ILC_RCV|IL_RIE;
235: while ((addr->il_csr & IL_CDONE) == 0)
236: ;
237: is->is_flags = ILF_OACTIVE;
238: is->is_if.if_flags |= IFF_UP|IFF_RUNNING;
239: is->is_lastcmd = 0;
240: ilcint(unit);
241: splx(s);
242: justarp:
243: if_rtinit(&is->is_if, RTF_UP);
244: arpattach(&is->is_ac);
245: arpwhohas(&is->is_ac, &sin->sin_addr);
246: }
247:
248: /*
249: * Start output on interface.
250: * Get another datagram to send off of the interface queue,
251: * and map it to the interface before starting the output.
252: */
253: ilstart(dev)
254: dev_t dev;
255: {
256: int unit = ILUNIT(dev), len;
257: struct uba_device *ui = ilinfo[unit];
258: register struct il_softc *is = &il_softc[unit];
259: register struct ildevice *addr;
260: struct mbuf *m;
261: short csr;
262:
263: IF_DEQUEUE(&is->is_if.if_snd, m);
264: addr = (struct ildevice *)ui->ui_addr;
265: if (m == 0) {
266: if ((is->is_flags & ILF_STATPENDING) == 0)
267: return;
268: addr->il_bar = is->is_ubaddr & 0xffff;
269: addr->il_bcr = sizeof (struct il_stats);
270: csr = ((is->is_ubaddr >> 2) & IL_EUA)|ILC_STAT|IL_RIE|IL_CIE;
271: is->is_flags &= ~ILF_STATPENDING;
272: goto startcmd;
273: }
274: len = if_wubaput(&is->is_ifuba, m);
275: /*
276: * Ensure minimum packet length.
277: * This makes the safe assumtion that there are no virtual holes
278: * after the data.
279: * For security, it might be wise to zero out the added bytes,
280: * but we're mainly interested in speed at the moment.
281: */
282: if (len - sizeof(struct ether_header) < ETHERMIN)
283: len = ETHERMIN + sizeof(struct ether_header);
284: if (is->is_ifuba.ifu_flags & UBA_NEEDBDP)
285: UBAPURGE(is->is_ifuba.ifu_uba, is->is_ifuba.ifu_w.ifrw_bdp);
286: addr->il_bar = is->is_ifuba.ifu_w.ifrw_info & 0xffff;
287: addr->il_bcr = len;
288: csr =
289: ((is->is_ifuba.ifu_w.ifrw_info >> 2) & IL_EUA)|ILC_XMIT|IL_CIE|IL_RIE;
290:
291: startcmd:
292: is->is_lastcmd = csr & IL_CMD;
293: addr->il_csr = csr;
294: is->is_flags |= ILF_OACTIVE;
295: }
296:
297: /*
298: * Command done interrupt.
299: */
300: ilcint(unit)
301: int unit;
302: {
303: register struct il_softc *is = &il_softc[unit];
304: struct uba_device *ui = ilinfo[unit];
305: register struct ildevice *addr = (struct ildevice *)ui->ui_addr;
306: short csr;
307:
308: if ((is->is_flags & ILF_OACTIVE) == 0) {
309: printf("il%d: stray xmit interrupt, csr=%b\n", unit,
310: addr->il_csr, IL_BITS);
311: return;
312: }
313:
314: csr = addr->il_csr;
315: /*
316: * Hang receive buffer if it couldn't
317: * be done earlier (in ilrint).
318: */
319: if (is->is_flags & ILF_RCVPENDING) {
320: addr->il_bar = is->is_ifuba.ifu_r.ifrw_info & 0xffff;
321: addr->il_bcr = sizeof(struct il_rheader) + ETHERMTU + 6;
322: addr->il_csr =
323: ((is->is_ifuba.ifu_r.ifrw_info >> 2) & IL_EUA)|ILC_RCV|IL_RIE;
324: while ((addr->il_csr & IL_CDONE) == 0)
325: ;
326: is->is_flags &= ~ILF_RCVPENDING;
327: }
328: is->is_flags &= ~ILF_OACTIVE;
329: csr &= IL_STATUS;
330: switch (is->is_lastcmd) {
331:
332: case ILC_XMIT:
333: is->is_if.if_opackets++;
334: if (csr > ILERR_RETRIES)
335: is->is_if.if_oerrors++;
336: break;
337:
338: case ILC_STAT:
339: if (csr == ILERR_SUCCESS)
340: iltotal(is);
341: break;
342: }
343: if (is->is_ifuba.ifu_xtofree) {
344: m_freem(is->is_ifuba.ifu_xtofree);
345: is->is_ifuba.ifu_xtofree = 0;
346: }
347: ilstart(unit);
348: }
349:
350: /*
351: * Ethernet interface receiver interrupt.
352: * If input error just drop packet.
353: * Otherwise purge input buffered data path and examine
354: * packet to determine type. If can't determine length
355: * from type, then have to drop packet. Othewise decapsulate
356: * packet based on type and pass to type specific higher-level
357: * input routine.
358: */
359: ilrint(unit)
360: int unit;
361: {
362: register struct il_softc *is = &il_softc[unit];
363: struct ildevice *addr = (struct ildevice *)ilinfo[unit]->ui_addr;
364: register struct il_rheader *il;
365: struct mbuf *m;
366: int len, off, resid;
367: register struct ifqueue *inq;
368:
369: is->is_if.if_ipackets++;
370: if (is->is_ifuba.ifu_flags & UBA_NEEDBDP)
371: UBAPURGE(is->is_ifuba.ifu_uba, is->is_ifuba.ifu_r.ifrw_bdp);
372: il = (struct il_rheader *)(is->is_ifuba.ifu_r.ifrw_addr);
373: len = il->ilr_length - sizeof(struct il_rheader);
374: if ((il->ilr_status&(ILFSTAT_A|ILFSTAT_C)) || len < 46 ||
375: len > ETHERMTU) {
376: is->is_if.if_ierrors++;
377: #ifdef notdef
378: if (is->is_if.if_ierrors % 100 == 0)
379: printf("il%d: += 100 input errors\n", unit);
380: #endif
381: goto setup;
382: }
383:
384: /*
385: * Deal with trailer protocol: if type is PUP trailer
386: * get true type from first 16-bit word past data.
387: * Remember that type was trailer by setting off.
388: */
389: il->ilr_type = ntohs((u_short)il->ilr_type);
390: #define ildataaddr(il, off, type) ((type)(((caddr_t)((il)+1)+(off))))
391: if (il->ilr_type >= ETHERPUP_TRAIL &&
392: il->ilr_type < ETHERPUP_TRAIL+ETHERPUP_NTRAILER) {
393: off = (il->ilr_type - ETHERPUP_TRAIL) * 512;
394: if (off >= ETHERMTU)
395: goto setup; /* sanity */
396: il->ilr_type = ntohs(*ildataaddr(il, off, u_short *));
397: resid = ntohs(*(ildataaddr(il, off+2, u_short *)));
398: if (off + resid > len)
399: goto setup; /* sanity */
400: len = off + resid;
401: } else
402: off = 0;
403: if (len == 0)
404: goto setup;
405:
406: /*
407: * Pull packet off interface. Off is nonzero if packet
408: * has trailing header; ilget will then force this header
409: * information to be at the front, but we still have to drop
410: * the type and length which are at the front of any trailer data.
411: */
412: m = if_rubaget(&is->is_ifuba, len, off);
413: if (m == 0)
414: goto setup;
415: if (off) {
416: m->m_off += 2 * sizeof (u_short);
417: m->m_len -= 2 * sizeof (u_short);
418: }
419: switch (il->ilr_type) {
420:
421: #ifdef INET
422: case ETHERPUP_IPTYPE:
423: schednetisr(NETISR_IP);
424: inq = &ipintrq;
425: break;
426:
427: case ETHERPUP_ARPTYPE:
428: arpinput(&is->is_ac, m);
429: goto setup;
430: #endif
431: default:
432: m_freem(m);
433: goto setup;
434: }
435:
436: if (IF_QFULL(inq)) {
437: IF_DROP(inq);
438: m_freem(m);
439: goto setup;
440: }
441: IF_ENQUEUE(inq, m);
442:
443: setup:
444: /*
445: * Reset for next packet if possible.
446: * If waiting for transmit command completion, set flag
447: * and wait until command completes.
448: */
449: if (is->is_flags & ILF_OACTIVE) {
450: is->is_flags |= ILF_RCVPENDING;
451: return;
452: }
453: addr->il_bar = is->is_ifuba.ifu_r.ifrw_info & 0xffff;
454: addr->il_bcr = sizeof(struct il_rheader) + ETHERMTU + 6;
455: addr->il_csr =
456: ((is->is_ifuba.ifu_r.ifrw_info >> 2) & IL_EUA)|ILC_RCV|IL_RIE;
457: while ((addr->il_csr & IL_CDONE) == 0)
458: ;
459: }
460:
461: /*
462: * Ethernet output routine.
463: * Encapsulate a packet of type family for the local net.
464: * Use trailer local net encapsulation if enough data in first
465: * packet leaves a multiple of 512 bytes of data in remainder.
466: */
467: iloutput(ifp, m0, dst)
468: struct ifnet *ifp;
469: struct mbuf *m0;
470: struct sockaddr *dst;
471: {
472: int type, s, error;
473: u_char edst[6];
474: struct in_addr idst;
475: register struct il_softc *is = &il_softc[ifp->if_unit];
476: register struct mbuf *m = m0;
477: register struct ether_header *il;
478: register int off;
479:
480: switch (dst->sa_family) {
481:
482: #ifdef INET
483: case AF_INET:
484: idst = ((struct sockaddr_in *)dst)->sin_addr;
485: if (!arpresolve(&is->is_ac, m, &idst, edst))
486: return (0); /* if not yet resolved */
487: off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len;
488: /* need per host negotiation */
489: if ((ifp->if_flags & IFF_NOTRAILERS) == 0)
490: if (off > 0 && (off & 0x1ff) == 0 &&
491: m->m_off >= MMINOFF + 2 * sizeof (u_short)) {
492: type = ETHERPUP_TRAIL + (off>>9);
493: m->m_off -= 2 * sizeof (u_short);
494: m->m_len += 2 * sizeof (u_short);
495: *mtod(m, u_short *) = htons((u_short)ETHERPUP_IPTYPE);
496: *(mtod(m, u_short *) + 1) = htons((u_short)m->m_len);
497: goto gottrailertype;
498: }
499: type = ETHERPUP_IPTYPE;
500: off = 0;
501: goto gottype;
502: #endif
503:
504: case AF_UNSPEC:
505: il = (struct ether_header *)dst->sa_data;
506: bcopy((caddr_t)il->ether_dhost, (caddr_t)edst, sizeof (edst));
507: type = il->ether_type;
508: goto gottype;
509:
510: default:
511: printf("il%d: can't handle af%d\n", ifp->if_unit,
512: dst->sa_family);
513: error = EAFNOSUPPORT;
514: goto bad;
515: }
516:
517: gottrailertype:
518: /*
519: * Packet to be sent as trailer: move first packet
520: * (control information) to end of chain.
521: */
522: while (m->m_next)
523: m = m->m_next;
524: m->m_next = m0;
525: m = m0->m_next;
526: m0->m_next = 0;
527: m0 = m;
528:
529: gottype:
530: /*
531: * Add local net header. If no space in first mbuf,
532: * allocate another.
533: */
534: if (m->m_off > MMAXOFF ||
535: MMINOFF + sizeof (struct ether_header) > m->m_off) {
536: m = m_get(M_DONTWAIT, MT_HEADER);
537: if (m == 0) {
538: error = ENOBUFS;
539: goto bad;
540: }
541: m->m_next = m0;
542: m->m_off = MMINOFF;
543: m->m_len = sizeof (struct ether_header);
544: } else {
545: m->m_off -= sizeof (struct ether_header);
546: m->m_len += sizeof (struct ether_header);
547: }
548: il = mtod(m, struct ether_header *);
549: il->ether_type = htons((u_short)type);
550: bcopy((caddr_t)edst, (caddr_t)il->ether_dhost, sizeof (edst));
551: bcopy((caddr_t)is->is_addr, (caddr_t)il->ether_shost, 6);
552:
553: /*
554: * Queue message on interface, and start output if interface
555: * not yet active.
556: */
557: s = splimp();
558: if (IF_QFULL(&ifp->if_snd)) {
559: IF_DROP(&ifp->if_snd);
560: splx(s);
561: m_freem(m);
562: return (ENOBUFS);
563: }
564: IF_ENQUEUE(&ifp->if_snd, m);
565: if ((is->is_flags & ILF_OACTIVE) == 0)
566: ilstart(ifp->if_unit);
567: splx(s);
568: return (0);
569:
570: bad:
571: m_freem(m0);
572: return (error);
573: }
574:
575: /*
576: * Watchdog routine, request statistics from board.
577: */
578: ilwatch(unit)
579: int unit;
580: {
581: register struct il_softc *is = &il_softc[unit];
582: register struct ifnet *ifp = &is->is_if;
583: int s;
584:
585: if (is->is_flags & ILF_STATPENDING) {
586: ifp->if_timer = is->is_scaninterval;
587: return;
588: }
589: s = splimp();
590: is->is_flags |= ILF_STATPENDING;
591: if ((is->is_flags & ILF_OACTIVE) == 0)
592: ilstart(ifp->if_unit);
593: splx(s);
594: ifp->if_timer = is->is_scaninterval;
595: }
596:
597: /*
598: * Total up the on-board statistics.
599: */
600: iltotal(is)
601: register struct il_softc *is;
602: {
603: register u_short *interval, *sum, *end;
604:
605: interval = &is->is_stats.ils_frames;
606: sum = &is->is_sum.ils_frames;
607: end = is->is_sum.ils_fill2;
608: while (sum < end)
609: *sum++ += *interval++;
610: is->is_if.if_collisions = is->is_sum.ils_collis;
611: }
612:
613: /*
614: * Process an ioctl request.
615: */
616: ilioctl(ifp, cmd, data)
617: register struct ifnet *ifp;
618: int cmd;
619: caddr_t data;
620: {
621: register struct ifreq *ifr = (struct ifreq *)data;
622: int s = splimp(), error = 0;
623:
624: switch (cmd) {
625:
626: case SIOCSIFADDR:
627: if (ifp->if_flags & IFF_RUNNING)
628: if_rtinit(ifp, -1); /* delete previous route */
629: ilsetaddr(ifp, (struct sockaddr_in *)&ifr->ifr_addr);
630: ilinit(ifp->if_unit);
631: break;
632:
633: default:
634: error = EINVAL;
635: }
636: splx(s);
637: return (error);
638: }
639:
640: ilsetaddr(ifp, sin)
641: register struct ifnet *ifp;
642: register struct sockaddr_in *sin;
643: {
644:
645: ifp->if_addr = *(struct sockaddr *)sin;
646: ifp->if_net = in_netof(sin->sin_addr);
647: ifp->if_host[0] = in_lnaof(sin->sin_addr);
648: sin = (struct sockaddr_in *)&ifp->if_broadaddr;
649: sin->sin_family = AF_INET;
650: sin->sin_addr = if_makeaddr(ifp->if_net, INADDR_ANY);
651: ifp->if_flags |= IFF_BROADCAST;
652: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.