|
|
1.1 root 1: /*
2: * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
3: *
4: * @APPLE_LICENSE_HEADER_START@
5: *
6: * The contents of this file constitute Original Code as defined in and
7: * are subject to the Apple Public Source License Version 1.1 (the
8: * "License"). You may not use this file except in compliance with the
9: * License. Please obtain a copy of the License at
10: * http://www.apple.com/publicsource and read it before using this file.
11: *
12: * This Original Code and all software distributed under the License are
13: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17: * License for the specific language governing rights and limitations
18: * under the License.
19: *
20: * @APPLE_LICENSE_HEADER_END@
21: */
22: /*
23: * Copyright (c) 1998-1999 Apple Computer
24: *
25: * Hardware independent (relatively) code for the Sun GEM Ethernet Controller
26: *
27: * HISTORY
28: *
29: * dd-mmm-yy
30: * Created.
31: *
32: */
33:
34: //void call_kdp(void);
35:
36: #include "UniNEnetPrivate.h"
37:
38: #define super IOEthernetController
39:
40: OSDefineMetaClassAndStructors( UniNEnet, IOEthernetController )
41:
42: /*-------------------------------------------------------------------------
43: *
44: *
45: *
46: *-------------------------------------------------------------------------*/
47: bool UniNEnet::init(OSDictionary * properties = 0)
48: {
49: /*
50: * Initialize my ivars.
51: */
52: networkInterface = 0;
53: transmitQueue = 0;
54: debugQueue = 0;
55: debugger = 0;
56: interruptSource = 0;
57: txDebuggerPkt = 0;
58: phyId = 0xff;
59: phyStatusPrev = 0;
60: ready = false;
61: debugClient = false;
62: debugTxPoll = false;
63: netifClient = false;
64: isPromiscuous = false;
65: multicastEnabled = false;
66:
67: if (!super::init(properties))
68: return false;
69:
70: /*
71: * Clear the mbuf rings.
72: */
73: for (int i = 0; i < TX_RING_LENGTH; i++)
74: {
75: txElementPtrs[i] = 0;
76: }
77:
78: for (int i = 0; i < RX_RING_LENGTH; i++)
79: {
80: rxMbuf[i] = 0;
81:
82: }
83:
84: return true;
85: }
86:
87: /*-------------------------------------------------------------------------
88: *
89: *
90: *
91: *-------------------------------------------------------------------------*/
92:
93: bool UniNEnet::start(IOService * provider)
94: {
95: volatile UInt32 clockReg;
96: OSString *matchEntry;
97:
98: nub = OSDynamicCast(IOPCIDevice, provider);
99:
100: if (!nub || !super::start(provider))
101: {
102: return false;
103: }
104:
105: transmitQueue = OSDynamicCast(IOOQLockFIFOQueue, getOutputQueue());
106: if (!transmitQueue)
107: {
108: IOLog("Ethernet(UniN): Output queue initialization failed\n");
109: return false;
110: }
111: transmitQueue->retain();
112:
113: /*
114: * Allocate debug queue. This stores packets retired from the TX ring
115: * by the polling routine. We cannot call freePacket() or m_free() within
116: * the debugger context.
117: *
118: * The capacity of the queue is set at maximum to prevent the queue from
119: * calling m_free() due to over-capacity. But we don't expect the size
120: * of the queue to grow too large.
121: */
122: debugQueue = IOPacketQueue::withCapacity((UInt) -1);
123: if (!debugQueue)
124: {
125: return false;
126: }
127:
128: /*
129: * Allocate a IOMBufDBDMAMemoryCursor instance. Currently, the maximum
130: * number of segments is set to 1. The maximum length for each segment
131: * is set to the maximum ethernet frame size (plus padding).
132: */
133: mbufCursor = IOMBufBigMemoryCursor::withSpecification(NETWORK_BUFSIZE, 1);
134: if (!mbufCursor)
135: {
136: IOLog("Ethernet(UniN): IOMBufDBDMAMemoryCursor allocation failure\n");
137: return false;
138: }
139:
140: matchEntry = OSDynamicCast( OSString, getProperty( gIONameMatchedKey ) );
141: if ( matchEntry == 0 )
142: {
143: IOLog("Ethernet(UniN): Cannot obtain matching property.\n");
144: return false;
145: }
146:
147: if ( matchEntry->isEqualTo( "gmac" ) == true )
148: {
149: platformNub = OSDynamicCast( Core99PE, getPlatform() );
150:
151: if ( platformNub == 0 )
152: {
153: IOLog("Ethernet(UniN): Cannot access platform registers.\n");
154: return false;
155: }
156:
157: clockReg = platformNub->readUniNReg( 0x20 );
158: clockReg |= 0x02;
159: platformNub->writeUniNReg( 0x20, clockReg );
160: }
161:
162: /*
163: * BUS MASTER, MEM I/O Space, MEM WR & INV
164: */
165: nub->configWrite32( 0x04, 0x16 );
166:
167: /*
168: * set Latency to Max , cache 32
169: */
170: nub->configWrite32( 0x0C, ((2 + (kGEMBurstSize * (0+1)))<< 8) | (CACHE_LINE_SIZE >> 2) );
171:
172: ioMapEnet = nub->mapDeviceMemoryWithRegister( 0x10 );
173: if ( ioMapEnet == NULL )
174: {
175: return false;
176: }
177: ioBaseEnet = (volatile IOPPCAddress)ioMapEnet->getVirtualAddress();
178:
179: phyId = (UInt8) -1;
180:
181: /*
182: * Get a reference to the IOWorkLoop in our superclass.
183: */
184: IOWorkLoop * myWorkLoop = getWorkLoop();
185:
186: /*
187: * Allocate three IOInterruptEventSources.
188: */
189: interruptSource = IOInterruptEventSource::interruptEventSource(
190: (OSObject *) this,
191: (IOInterruptEventAction) &UniNEnet::interruptOccurred,
192: (IOService *) provider,
193: (int) 0 );
194:
195: if ( interruptSource == NULL )
196: {
197: IOLog("Ethernet(UniN): Couldn't allocate Interrupt event source\n");
198: return false;
199: }
200:
201: if ( myWorkLoop->addEventSource( interruptSource ) != kIOReturnSuccess )
202: {
203: IOLog("Ethernet(UniN): Couldn't add Interrupt event source\n");
204: return false;
205: }
206:
207:
208: timerSource = IOTimerEventSource::timerEventSource
209: (this, (IOTimerEventSource::Action) &UniNEnet::timeoutOccurred);
210: if ( timerSource == NULL )
211: {
212: IOLog("Ethernet(UniN): Couldn't allocate timer event source\n");
213: return false;
214: }
215:
216: if ( myWorkLoop->addEventSource( timerSource ) != kIOReturnSuccess )
217: {
218: IOLog("Ethernet(UniN): Couldn't add timer event source\n");
219: return false;
220: }
221:
222: MGETHDR(txDebuggerPkt, M_DONTWAIT, MT_DATA);
223:
224: if (!txDebuggerPkt)
225: {
226: IOLog("Ethernet(UniN): Couldn't allocate KDB buffer\n");
227: return false;
228: }
229:
230: /*
231: * Perform a hardware reset.
232: */
233: if ( resetAndEnable(false) == false )
234: {
235: IOLog("Ethernet(UniN): resetAndEnable() failed\n");
236: return false;
237: }
238:
239: /*
240: * Cache my MAC address.
241: */
242: if ( getHardwareAddress(&myAddress) != kIOReturnSuccess )
243: {
244: IOLog("Ethernet(UniN): getHardwareAddress() failed\n");
245: return false;
246: }
247:
248: /*
249: * Allocate memory for ring buffers.
250: */
251: if ( allocateMemory() == false)
252: {
253: IOLog("Ethernet(UniN): allocateMemory() failed\n");
254: return false;
255: }
256:
257: /*
258: * Attach a kernel debugger client.
259: */
260: attachDebuggerClient(&debugger);
261:
262: /*
263: * Attach an IOEthernetInterface client.
264: */
265: if ( attachNetworkInterface((IONetworkInterface **) &networkInterface) == false )
266: {
267: IOLog("Ethernet(UniN): attachNetworkInterface() failed\n");
268: return false;
269: }
270: return true;
271: }
272:
273: /*-------------------------------------------------------------------------
274: *
275: *
276: *
277: *-------------------------------------------------------------------------*/
278: #if 1
279: bool UniNEnet::configureNetworkInterface(IONetworkInterface * netif)
280: {
281: if ( super::configureNetworkInterface( netif ) == true )
282: {
283: return netif->setExtraFlags(IFEF_DVR_REENTRY_OK);
284: }
285: return false;
286: }
287: #endif
288:
289: /*-------------------------------------------------------------------------
290: *
291: *
292: *
293: *-------------------------------------------------------------------------*/
294:
295: void UniNEnet::free()
296: {
297: UInt i;
298: TxQueueElement *txElement;
299:
300: resetAndEnable(false);
301:
302: if (debugger)
303: debugger->release();
304:
305: if (getWorkLoop())
306: {
307: getWorkLoop()->disableAllEventSources();
308: }
309:
310: if (timerSource)
311: {
312: timerSource->release();
313: timerSource = 0;
314: }
315:
316: if (interruptSource)
317: {
318: interruptSource->release();
319: }
320:
321: if (txDebuggerPkt)
322: {
323: freePacket(txDebuggerPkt);
324: }
325:
326: if (transmitQueue)
327: {
328: transmitQueue->release();
329: }
330:
331: if (debugQueue)
332: {
333: debugQueue->release();
334: }
335:
336: if (networkInterface)
337: {
338: networkInterface->release();
339: }
340:
341: if (mbufCursor)
342: {
343: mbufCursor->release();
344: }
345:
346: for (i = 0; i < rxMaxCommand; i++)
347: {
348: if (rxMbuf[i])
349: {
350: freePacket(rxMbuf[i]);
351: }
352: }
353:
354: for (i = 0; i < txMaxCommand; i++)
355: {
356: txElement = txElementPtrs[i];
357: txElementPtrs[i] = 0;
358:
359: if (txElement != 0)
360: {
361: if ( --txElement->count == 0 )
362: {
363: freePacket(txElement->mbuf);
364: IOFree( txElement, sizeof(TxQueueElement) );
365: }
366: }
367: }
368:
369: if ( ioMapEnet )
370: {
371: ioMapEnet->release();
372: }
373:
374: if ( dmaCommands != 0 )
375: {
376: IOFreeContiguous( (void *)dmaCommands, dmaCommandsSize );
377: }
378:
379: super::free();
380: }
381:
382: /*-------------------------------------------------------------------------
383: *
384: *
385: *
386: *-------------------------------------------------------------------------*/
387:
388: void UniNEnet::interruptOccurred(IOInterruptEventSource *src,
389: int /*count*/)
390: {
391: UInt32 interruptStatus;
392: bool doFlushQueue;
393: bool doService;
394:
395: do
396: {
397: reserveDebuggerLock();
398:
399: interruptStatus = ReadUniNRegister( ioBaseEnet, kGEMInterruptStatus )
400: & (kGEMInterruptStatus_TxInt | kGEMInterruptStatus_RxDone);
401:
402: doService = false;
403:
404: if ( interruptStatus & kGEMInterruptStatus_TxInt )
405: {
406: txWDInterrupts++;
407: KERNEL_DEBUG(DBG_GEM_TXIRQ | DBG_FUNC_START, 0, 0, 0, 0, 0 );
408: doService = transmitInterruptOccurred();
409: KERNEL_DEBUG(DBG_GEM_TXIRQ | DBG_FUNC_END, 0, 0, 0, 0, 0 );
410: }
411:
412: doFlushQueue = false;
413:
414: if ( interruptStatus & kGEMInterruptStatus_RxDone )
415: {
416: KERNEL_DEBUG(DBG_GEM_RXIRQ | DBG_FUNC_START, 0, 0, 0, 0, 0 );
417: doFlushQueue = receiveInterruptOccurred();
418: KERNEL_DEBUG(DBG_GEM_RXIRQ | DBG_FUNC_END, 0, 0, 0, 0, 0 );
419: }
420:
421: releaseDebuggerLock();
422:
423: /*
424: * Submit all received packets queued up by _receiveInterruptOccurred()
425: * to the network stack. The up call is performed without holding the
426: * debugger lock.
427: */
428: if (doFlushQueue)
429: {
430: networkInterface->flushInputQueue();
431: }
432:
433: /*
434: * Make sure the output queue is not stalled.
435: */
436: if (doService && netifClient)
437: {
438: transmitQueue->service();
439: }
440: }
441: while ( interruptStatus );
442:
443: interruptSource->enable();
444:
445: }
446:
447:
448: /*-------------------------------------------------------------------------
449: *
450: *
451: *
452: *-------------------------------------------------------------------------*/
453:
454: UInt32 UniNEnet::outputPacket(struct mbuf * pkt)
455: {
456: UInt32 ret = kIOOQReturnSuccess;
457:
458: KERNEL_DEBUG(DBG_GEM_TXQUEUE | DBG_FUNC_NONE, (int) pkt, (int) pkt->m_pkthdr.len, 0, 0, 0 );
459:
460: /*
461: * Hold the debugger lock so the debugger can't interrupt us
462: */
463: reserveDebuggerLock();
464:
465: if ( transmitPacket(pkt) == false )
466: {
467: ret = kIOOQReturnStall;
468: }
469:
470: releaseDebuggerLock();
471:
472: return ret;
473: }
474:
475: /*-------------------------------------------------------------------------
476: *
477: *
478: *
479: *-------------------------------------------------------------------------*/
480:
481: bool UniNEnet::resetAndEnable(bool enable)
482: {
483: bool ret = true;
484:
485: reserveDebuggerLock();
486:
487: ready = false;
488:
489: if (timerSource)
490: {
491: timerSource->cancelTimeout();
492: }
493:
494: disableAdapterInterrupts();
495: if (getWorkLoop())
496: {
497: getWorkLoop()->disableAllInterrupts();
498: }
499:
500: if ( resetChip() == false )
501: {
502: ret = false;
503: goto resetAndEnable_exit;
504: }
505:
506: while (enable)
507: {
508: if (!initRxRing() || !initTxRing())
509: {
510: ret = false;
511: break;
512: }
513:
514: if ( phyId != 0xff )
515: {
516: miiInitializePHY(phyId);
517: }
518:
519: if (initChip() == false)
520: {
521: ret = false;
522: break;
523: }
524:
525: // startChip();
526:
527: timerSource->setTimeoutMS(WATCHDOG_TIMER_MS);
528:
529: if (getWorkLoop())
530: {
531: getWorkLoop()->enableAllInterrupts();
532: }
533: enableAdapterInterrupts();
534:
535: ready = true;
536:
537: // sendDummyPacket();
538:
539: break;
540: }
541:
542: resetAndEnable_exit: ;
543:
544: releaseDebuggerLock();
545:
546: return ret;
547: }
548:
549: /*-------------------------------------------------------------------------
550: * Called by IOEthernetInterface client to enable the controller.
551: * This method is always called while running on the default workloop
552: * thread.
553: *-------------------------------------------------------------------------*/
554:
555: IOReturn UniNEnet::enable(IONetworkInterface * netif)
556: {
557: IONetworkParameter * param;
558:
559: /*
560: * If an interface client has previously enabled us,
561: * and we know there can only be one interface client
562: * for this driver, then simply return true.
563: */
564: if (netifClient)
565: {
566: IOLog("EtherNet(UniN): already enabled\n");
567: return kIOReturnSuccess;
568: }
569:
570: /*
571: * Grab a pointer to the statistics structure in the interface.
572: */
573: param = netif->getParameter(kIONetworkStatsKey);
574: if (!param || !(netStats = (IONetworkStats *) param->getBuffer()))
575: {
576: IOLog("EtherNet(UniN): invalid network statistics\n");
577: return kIOReturnError;
578: }
579:
580: if ((ready == false) && !resetAndEnable(true))
581: return kIOReturnIOError;
582:
583: /*
584: * Record the interface as an active client.
585: */
586: netifClient = true;
587:
588: /*
589: * Start our IOOutputQueue object.
590: */
591: transmitQueue->setCapacity(TRANSMIT_QUEUE_SIZE);
592: transmitQueue->start();
593:
594: return kIOReturnSuccess;
595: }
596:
597: /*-------------------------------------------------------------------------
598: * Called by IOEthernetInterface client to disable the controller.
599: * This method is always called while running on the default workloop
600: * thread.
601: *-------------------------------------------------------------------------*/
602:
603: IOReturn UniNEnet::disable(IONetworkInterface * /*netif*/)
604: {
605: /*
606: * If we have no active clients, then disable the controller.
607: */
608: if (debugClient == false)
609: {
610: resetAndEnable(false);
611: }
612:
613: /*
614: * Disable our IOOutputQueue object. This will prevent the
615: * outputPacket() method from being called.
616: */
617: transmitQueue->stop();
618:
619: /*
620: * Flush all packets currently in the output queue.
621: */
622: transmitQueue->setCapacity(0);
623: transmitQueue->flush();
624:
625: netifClient = false;
626:
627: return kIOReturnSuccess;
628: }
629:
630: /*-------------------------------------------------------------------------
631: * This method is called by our debugger client to bring up the controller
632: * just before the controller is registered as the debugger device. The
633: * debugger client is attached in response to the attachDebuggerClient()
634: * call.
635: *
636: * This method is always called while running on the default workloop
637: * thread.
638: *-------------------------------------------------------------------------*/
639:
640: IOReturn UniNEnet::handleDebuggerOpen(IOKernelDebugger * /*debugger*/)
641: {
642: /*
643: * Enable hardware and make it ready to support the debugger client.
644: */
645: if ((ready == false) && !resetAndEnable(true))
646: {
647: return kIOReturnIOError;
648: }
649:
650: /*
651: * Record the debugger as an active client of ours.
652: */
653: debugClient = true;
654:
655: /*
656: * Returning true will allow the kdp registration to continue.
657: * If we return false, then we will not be registered as the
658: * debugger device, and the attachDebuggerClient() call will
659: * return NULL.
660: */
661: return kIOReturnSuccess;
662: }
663:
664: /*-------------------------------------------------------------------------
665: * This method is called by our debugger client to stop the controller.
666: * The debugger will call this method when we issue a detachDebuggerClient().
667: *
668: * This method is always called while running on the default workloop
669: * thread.
670: *-------------------------------------------------------------------------*/
671:
672: IOReturn UniNEnet::handleDebuggerClose(IOKernelDebugger * /*debugger*/)
673: {
674: debugClient = false;
675:
676: /*
677: * If we have no active clients, then disable the controller.
678: */
679: if (netifClient == false)
680: {
681: resetAndEnable(false);
682: }
683:
684: return kIOReturnSuccess;
685: }
686:
687:
688: /*-------------------------------------------------------------------------
689: *
690: *
691: *
692: *-------------------------------------------------------------------------*/
693:
694: void UniNEnet::timeoutOccurred(IOTimerEventSource * /*timer*/)
695: {
696: bool doService = false;
697: UInt32 txRingIndex;
698:
699: if ( ready == false )
700: {
701: IOLog("EtherNet(UniN): Spurious timeout event!!\n");
702: return;
703: }
704:
705: reserveDebuggerLock();
706:
707: monitorLinkStatus();
708:
709: /*
710: * If there are pending entries on the Tx ring
711: */
712: if ( txCommandHead != txCommandTail )
713: {
714: /*
715: * If the hardware tx pointer did not move since the last
716: * check, increment the txWDCount.
717: */
718: txRingIndex = ReadUniNRegister( ioBaseEnet, kGEMTxCompletion );
719: if ( txRingIndex == txRingIndexLast )
720: {
721: txWDCount++;
722: }
723: else
724: {
725: txWDCount = 0;
726: txRingIndexLast = txRingIndex;
727: }
728:
729: if ( txWDCount > 2 )
730: {
731: /*
732: * We only take interrupts every 64 tx completions, so we may be here just
733: * to do normal clean-up of tx packets. We check if the hardware tx pointer
734: * points to the next available tx slot. This indicates that we transmitted all
735: * packets that were scheduled vs rather than the hardware tx being stalled.
736: */
737: if ( txRingIndex != txCommandTail )
738: {
739: UInt32 interruptStatus, compReg, kickReg;
740:
741: interruptStatus = ReadUniNRegister( ioBaseEnet, kGEMInterruptStatus );
742: compReg = ReadUniNRegister( ioBaseEnet, kGEMTxCompletion );
743: kickReg = ReadUniNRegister( ioBaseEnet, kGEMTxKick );
744:
745: //IOLog( "Tx Int Timeout - Comp = %04x Kick = %04x Int = %08x\n\r", (int)compReg, (int)kickReg, (int)interruptStatus );
746: }
747:
748: // dumpRegisters();
749:
750: transmitInterruptOccurred();
751:
752: doService = true;
753:
754: txRingIndexLast = txRingIndex;
755: txWDCount = 0;
756: }
757: }
758: else
759: {
760: txWDCount = 0;
761: }
762:
763: /*
764: * Clean-up after the debugger if the debugger was active.
765: */
766: if (debugTxPoll)
767: {
768: debugQueue->flush();
769: debugTxPoll = false;
770: releaseDebuggerLock();
771: doService = true;
772: }
773: else
774: {
775: releaseDebuggerLock();
776: }
777:
778: /*
779: * Make sure the queue is not stalled.
780: */
781: if (doService && netifClient)
782: {
783: transmitQueue->service();
784: }
785:
786: /*
787: * Restart the watchdog timer
788: */
789: timerSource->setTimeoutMS(WATCHDOG_TIMER_MS);
790:
791: }
792:
793: /*-------------------------------------------------------------------------
794: *
795: *
796: *
797: *-------------------------------------------------------------------------*/
798:
799: const char * UniNEnet::getVendorString() const
800: {
801: return ("Apple");
802: }
803:
804: const char * UniNEnet::getModelString() const
805: {
806: return ("gmac+");
807: }
808:
809: const char * UniNEnet::getRevisionString() const
810: {
811: return ("");
812: }
813:
814:
815: /*-------------------------------------------------------------------------
816: *
817: *
818: *
819: *-------------------------------------------------------------------------*/
820:
821: IOReturn UniNEnet::setPromiscuousMode(IOEnetPromiscuousMode mode)
822: {
823: UInt16 addressFilterReg;
824:
825:
826: reserveDebuggerLock();
827:
828: addressFilterReg = ReadUniNRegister(ioBaseEnet, kGEMMacRxMacConfig);
829:
830: if (mode == kIOEnetPromiscuousModeOff)
831: {
832: addressFilterReg &= ~(kGEMMacRxMacConfig_ReceiveAll);
833: isPromiscuous = false;
834:
835: }
836: else
837: {
838: addressFilterReg |= kGEMMacRxMacConfig_ReceiveAll;
839: isPromiscuous = true;
840:
841: }
842: WriteUniNRegister(ioBaseEnet, kGEMMacRxMacConfig, addressFilterReg);
843:
844: releaseDebuggerLock();
845:
846: return kIOReturnSuccess;
847: }
848:
849: /*-------------------------------------------------------------------------
850: *
851: *
852: *
853: *-------------------------------------------------------------------------*/
854:
855: IOReturn UniNEnet::setMulticastMode(IOEnetMulticastMode mode)
856: {
857: multicastEnabled = (mode == kIOEnetMulticastModeOff) ? false : true;
858:
859: return kIOReturnSuccess;
860: }
861:
862: /*-------------------------------------------------------------------------
863: *
864: *
865: *
866: *-------------------------------------------------------------------------*/
867:
868: IOReturn UniNEnet::setMulticastList(enet_addr_t *addrs, UInt count)
869: {
870: reserveDebuggerLock();
871:
872: resetHashTableMask();
873: for (UInt i = 0; i < count; i++)
874: {
875: addToHashTableMask(addrs->ea_byte);
876: addrs++;
877: }
878: updateHashTableMask();
879:
880: releaseDebuggerLock();
881: return kIOReturnSuccess;
882: }
883:
884: /*-------------------------------------------------------------------------
885: *
886: *
887: *
888: *-------------------------------------------------------------------------*/
889:
890: IOOutputQueue * UniNEnet::allocateOutputQueue()
891: {
892: return IOOQLockFIFOQueue::withTarget( this, TRANSMIT_QUEUE_SIZE );
893: }
894:
895:
896:
897:
898:
899:
900:
901:
902:
903:
904:
905:
906:
907:
908:
909:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.