|
|
1.1 root 1: //
2: // nono
1.1.1.2 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.10! root 7: //
! 8: // PIO/PPI (i8255)
! 9: //
! 10:
! 11: // IODevice
! 12: // |
! 13: // | +-------------+
! 14: // +--| i8255Device | (8255 としての共通部分)
! 15: // +-------------+
! 16: // |
! 17: // | +-----------+
! 18: // +--| PPIDevice | (X68030)
! 19: // | +-----------+
! 20: // |
! 21: // | +------------+
! 22: // +--| PIO0Device | (LUNA)
! 23: // | +------------+
! 24: // |
! 25: // | +------------+
! 26: // +--| PIO1Device | (LUNA)
! 27: // +------------+
! 28:
1.1 root 29: #include "pio.h"
1.1.1.9 root 30: #include "bitops.h"
1.1.1.2 root 31: #include "config.h"
1.1 root 32: #include "lcd.h"
1.1.1.3 root 33: #include "mainapp.h"
1.1.1.10! root 34: #include "mpu.h"
! 35: #include "power.h"
1.1 root 36: #include "scheduler.h"
37:
1.1.1.10! root 38: // グローバル参照用
! 39: PPIDevice *gPPI;
! 40: PIO0Device *gPIO0;
! 41: PIO1Device *gPIO1;
1.1 root 42:
43: //
44: // i8255
45: //
46:
47: // コンストラクタ
1.1.1.8 root 48: i8255Device::i8255Device(const std::string& objname_)
49: : inherited(objname_)
1.1 root 50: {
51: }
52:
53: // デストラクタ
54: i8255Device::~i8255Device()
55: {
56: }
57:
1.1.1.4 root 58: // リセット
59: void
1.1.1.10! root 60: i8255Device::ResetHard(bool poweron)
1.1.1.4 root 61: {
62: // clears the control register
63: // and place ports A, B, and C in input mode.
64: // The input latches in ports A, B, and C are not cleared.
65:
66: ctrl = 0b10011011;
67: }
68:
1.1 root 69: // コントロールポートへの書き込み。
70: void
71: i8255Device::WriteCtrl(uint32 data)
72: {
73: if ((data & 0x80)) {
74: // 最上位が立っていればモード設定
75: ctrl = data;
76: putlog(1,
77: "モード設定 groupA=%d groupB=%d portA=%s portB=%s portC=%s/%s",
78: GetGroupAMode(), GetGroupBMode(),
79: PortADir() == DIR_IN ? "IN" : "OUT",
80: PortBDir() == DIR_IN ? "IN" : "OUT",
81: PortCHDir() == DIR_IN ? "IN" : "OUT",
82: PortCLDir() == DIR_IN ? "IN" : "OUT");
83:
84: // モード設定すると PC はゼロクリアされる
85: for (int pc = 0; pc < 8; pc++) {
86: SetPC(pc, 0);
87: }
88: } else {
89: // portC 出力
90: //
91: // 0 x x x P P P V
92: // | | | +-- 書き込むビットデータ
93: // +--+--+----- 書き込むビット位置
94:
95: int pc = (data >> 1) & 7;
96: int val = data & 1;
97: SetPC(pc, val);
98: }
99: }
100:
101: //
102: // X68k PPI
103: //
104:
105: // コンストラクタ
106: PPIDevice::PPIDevice()
1.1.1.8 root 107: : inherited("PPI")
1.1 root 108: {
109: devaddr = baseaddr;
110: devlen = 0x2000;
111: }
112:
113: // デストラクタ
114: PPIDevice::~PPIDevice()
115: {
1.1.1.10! root 116: gPPI = NULL;
1.1 root 117: }
118:
119: uint64
1.1.1.6 root 120: PPIDevice::Read(uint32 offset)
1.1 root 121: {
1.1.1.4 root 122: gMPU->AddCycle(19); // InsideOut p.135
123:
1.1.1.6 root 124: switch (offset) {
1.1 root 125: case 0:
126: case 1:
127: return 0xff;
128: case 2:
1.1.1.6 root 129: putlog(0, "$e9a005 PortC 未実装読み込み");
1.1.1.5 root 130: return 0xff;
1.1 root 131: case 3:
1.1.1.10! root 132: VMPANIC("PPI $e9a007 未サポート読み込み");
1.1 root 133: default:
134: __unreachable();
135: }
136: }
137:
138: uint64
1.1.1.6 root 139: PPIDevice::Write(uint32 offset, uint32 data)
1.1 root 140: {
1.1.1.4 root 141: gMPU->AddCycle(19); // InsideOut p.135
142:
1.1.1.6 root 143: switch (offset) {
1.1 root 144: case 0:
145: case 1:
146: break;
147: case 2:
1.1.1.6 root 148: putlog(0, "$e9a005 未実装書き込み $%02X (無視)", data);
1.1 root 149: break;
150: case 3:
151: if (data != 0x92) {
1.1.1.10! root 152: VMPANIC("PPI $e9a007 未実装書き込み $%02X", data);
1.1 root 153: } else {
1.1.1.6 root 154: putlog(0, "$e9a007 未実装書き込み $%02x (無視)", data);
1.1 root 155: }
156: break;
157: default:
158: __unreachable();
159: }
160: return 0;
161: }
162:
163: uint64
1.1.1.6 root 164: PPIDevice::Peek(uint32 offset)
1.1 root 165: {
166: // XXX
167: return 0xff;
168: }
169:
1.1.1.10! root 170: // PortC 書き込み
! 171: void
! 172: PPIDevice::SetPC(int pc, int data)
! 173: {
! 174: VMPANIC("not impl");
! 175: }
! 176:
1.1 root 177: //
178: // LUNA PIO0
179: //
180: // 仕様は 0x49000000 から 4バイトだが、
181: // 0x48000000 から 0x4c000000 の手前まで 4バイト単位でミラーが見える。
182: //
183: // LUNA の DIP-SW は #1, #2 にそれぞれ8個、計16個ある。
1.1.1.9 root 184: // DIP-SW #1, #2 が設定ファイルの luna-dipsw{1,2} に対応する。
185: // luna-dipsw{1,2} はいずれも "0"/"1" を8つ並べた文字列であり、
1.1 root 186: // 先頭が本体表記でいう 1番、末尾側が本体表記でいう 8番に対応する。
1.1.1.9 root 187: // 値は "0" が down、"1" が up を表す。
188: //
189: // PIO0Device 内ではこの設定を読み込んで bool dipsw1[8], dipsw2[8] の配列で
190: // 値を保持している。dipsw1[0] が本体表記1番、dipsw1[7] が本体表記 8番に対応
191: // する。値は false が down("0")、true が up("1") に対応する。
192: //
193: // PIO から読み出せる値のビットの並び順が機種によって異なる。
194: // LUNA-1 では 1番が LSB、8番が MSB の並び順、
195: // LUNA88K では 1番が MSB、8番が LSB の並び順。
196: // 値は、並び順に関わらずどちらも
197: // %0 が false = down = "0"、%1 が true = up = "1" である。
198: //
199: // #1 #2 #3 #4 #5 #6 #7 #8
200: // +-------------------------+
201: // | [] [] [] [] [] [] [] | 本体外観
202: // | [] |
203: // +-------------------------+
204: //
205: // luna-dipsw1 = "01111111" ← 設定ファイル
206: // |||||||| ↓ このクラスのメンバ変数
207: // |||||||+- dipsw1[7] = true;
208: // ||||||+-- dipsw1[6] = true;
209: // |||||+--- dipsw1[5] = true;
210: // ||||+---- dipsw1[4] = true;
211: // |||+----- dipsw1[3] = true;
212: // ||+------ dipsw1[2] = true;
213: // |+------- dipsw1[1] = true;
214: // +-------- dipsw1[0] = false;
215: //
216: // b7 b6 b5 b4 b3 b2 b1 b0
217: // +----+----+----+----+----+----+----+----+
218: // LUNA-1 | #8 | #7 | #6 | #5 | #4 | #3 | #2 | #1 |
219: // PortA | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | %1=UP, %0=DOWN
220: // +----+----+----+----+----+----+----+----+
221: //
222: // b7 b6 b5 b4 b3 b2 b1 b0
223: // +----+----+----+----+----+----+----+----+
224: // LUNA88K | #1 | #2 | #3 | #4 | #5 | #6 | #7 | #8 |
225: // PortA | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | %1=UP, %0=DOWN
226: // +----+----+----+----+----+----+----+----+
1.1 root 227: //
1.1.1.3 root 228: // LUNA-1:
1.1 root 229: // #1-1 up なら UNIX をロードして移行。down なら ROM モニタで起動。
230: // #1-2 up ならディスプレイコンソール。down ならシリアルコンソール。
231: // #1-3 up - color display, down - force to have monochrome display
232: // #1-4 up - no write verification,
233: // down - verification on every harddisk write operation
234: // #1-5 up - OS is UniOS-U (SystemV/COFF),
235: // down - OS is UniOS (4.3BSD/a.out OMAGIC)
236: // #1-6 down - force monochrome display?
237: // #1-7 up - boot from local device, down - boot from network
238: // #1-8 up - normal boot, down - start diagnostics と書いてあるが
239: // down にすると画面真っ暗で詳細不明。
240: //
241: // #2-x すべて up にすると /boot が自動的にカーネルを起動。一つでも down が
242: // あれば /boot がプロンプトで停止。これは ROM ではなく NetBSD のブート
243: // ローダの話。
244: // どこか由来のソースコードらしいがたぶんビット演算を間違えている。
1.1.1.3 root 245: //
246: // LUNA88K:
247: // #1-1 up なら ROM モニタで停止、down ならマルチユーザブート。
248: // OpenBSD カーネルも up なら UserKernelConfig で止まる模様?。
249: // #1-2 up なら外付けシリアルコンソール、down なら内蔵ビットマップ。
1.1.1.9 root 250: // #1-3..#1-7 は不明?
251: // #1-8 down なら自己診断後電源オフするようだ。
1.1.1.3 root 252: // #2-x はユーザ(?)
1.1 root 253:
254: // コンストラクタ
255: PIO0Device::PIO0Device()
1.1.1.8 root 256: : inherited("PIO0")
1.1 root 257: {
258: devaddr = 0x49000000;
259: devlen = 4;
1.1.1.2 root 260:
1.1.1.3 root 261: // 機種固有パラメータ
262:
263: // LUNA-1 と LUNA88K で使いやすいように初期値を変えておく。
264: switch (gMainApp.GetVMType()) {
265: case VMTYPE_LUNA1:
266: gConfig->SetDefault("luna-dipsw1", "11110111");
1.1.1.9 root 267: msb_first = false;
1.1.1.3 root 268: break;
269: case VMTYPE_LUNA88K:
1.1.1.4 root 270: gConfig->SetDefault("luna-dipsw1", "11111111");
1.1.1.9 root 271: msb_first = true;
1.1.1.3 root 272: break;
273: default:
274: __unreachable();
275: }
276: // DIP-SW#2 はユーザ定義(?)なので(今の所?)どちらも共通。
277: gConfig->SetDefault("luna-dipsw2", "11111111");
278:
1.1.1.10! root 279: monitor.func = ToMonitorCallback(&PIO0Device::MonitorUpdate);
1.1.1.8 root 280: monitor.SetSize(22, 11);
281: monitor.Regist(ID_MONITOR_PIO0);
1.1 root 282: }
283:
284: // デストラクタ
285: PIO0Device::~PIO0Device()
286: {
1.1.1.10! root 287: gPIO0 = NULL;
1.1 root 288: }
289:
290: bool
291: PIO0Device::Init()
292: {
293: // XXX 本当はラッチのタイミングが違う気もするけど、とりあえずね
294:
1.1.1.2 root 295: const ConfigItem& item1 = gConfig->Find("luna-dipsw1");
1.1 root 296: const std::string& str1 = item1.AsString();
297: if (str1.size() != 8) {
298: item1.Err("must be 8 digits");
299: return false;
300: }
301:
1.1.1.2 root 302: const ConfigItem& item2 = gConfig->Find("luna-dipsw2");
1.1 root 303: const std::string& str2 = item2.AsString();
304: if (str2.size() != 8) {
305: item2.Err("must be 8 digits");
306: return false;
307: }
308: for (int i = 0; i < 8; i++) {
309: dipsw1[i] = (str1[i] != '0');
310: dipsw2[i] = (str2[i] != '0');
311: }
312: return true;
313: }
314:
1.1.1.10! root 315: // XXX ResetHard 未調査
1.1.1.7 root 316:
1.1 root 317: uint64
1.1.1.6 root 318: PIO0Device::Read(uint32 offset)
1.1 root 319: {
320: uint32 data;
321:
1.1.1.6 root 322: switch (offset) {
1.1 root 323: case 0:
324: // PortA
325: putlog(1, "DIP-SW 1 読み込み");
326: return GetPA();
327:
328: case 1:
329: // PortB
330: putlog(1, "DIP-SW 2 読み込み");
331: return GetPB();
332:
333: case 2:
334: // PortC
335: data = GetPC();
1.1.1.6 root 336: putlog(1, "PC 読み込み -> $%02x", data);
1.1 root 337: return data;
338:
339: case 3:
340: data = ReadCtrl();
341: putlog(1, "コントロール読み込み -> $%02x", data);
342: return data;
343:
344: default:
345: __unreachable();
346: }
347: }
348:
349: uint64
1.1.1.6 root 350: PIO0Device::Write(uint32 offset, uint32 data)
1.1 root 351: {
1.1.1.6 root 352: switch (offset) {
1.1 root 353: case 0: // PIO0 PortA: DIPSW1 (入力のみ)
354: case 1: // PIO0 PortB: DIPSW2 (入力のみ)
355: return 0;
356:
357: case 2: // PIO0 PortC: ホスト割り込み制御
358: for (int i = 0; i < 8; i++) {
359: SetPC(i, data & 1);
360: data >>= 1;
361: }
362: return 0;
363:
364: case 3: // コントロール
365: WriteCtrl(data);
366: return 0;
367:
368: default:
369: __unreachable();
370: }
371: }
372:
373: uint64
1.1.1.6 root 374: PIO0Device::Peek(uint32 offset)
1.1 root 375: {
1.1.1.6 root 376: switch (offset) {
1.1 root 377: case 0:
378: return GetPA();
379:
380: case 1:
381: return GetPB();
382:
383: case 2:
384: return GetPC();
385:
386: case 3:
387: return ReadCtrl();
388:
389: default:
390: __unreachable();
391: }
392: }
393:
1.1.1.3 root 394: void
1.1.1.8 root 395: PIO0Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1.1.2 root 396: {
397: int y;
398:
1.1.1.8 root 399: screen.Clear();
1.1.1.2 root 400:
401: y = 0;
1.1.1.8 root 402: screen.Print(0, y++, "PortA(DIPSW1):%c%c%c%c%c%c%c%c",
1.1.1.2 root 403: (dipsw1[0] ? '1' : '0'),
404: (dipsw1[1] ? '1' : '0'),
405: (dipsw1[2] ? '1' : '0'),
406: (dipsw1[3] ? '1' : '0'),
407: (dipsw1[4] ? '1' : '0'),
408: (dipsw1[5] ? '1' : '0'),
409: (dipsw1[6] ? '1' : '0'),
410: (dipsw1[7] ? '1' : '0'));
1.1.1.8 root 411: screen.Print(0, y++, "PortB(DIPSW2):%c%c%c%c%c%c%c%c",
1.1.1.2 root 412: (dipsw2[0] ? '1' : '0'),
413: (dipsw2[1] ? '1' : '0'),
414: (dipsw2[2] ? '1' : '0'),
415: (dipsw2[3] ? '1' : '0'),
416: (dipsw2[4] ? '1' : '0'),
417: (dipsw2[5] ? '1' : '0'),
418: (dipsw2[6] ? '1' : '0'),
419: (dipsw2[7] ? '1' : '0'));
1.1.1.8 root 420: screen.Puts(0, y++, "PortC");
421: screen.Puts(1, y++, TA::OnOff(xp_reset), "b7: XP Reset");
422: screen.Puts(1, y++, TA::OnOff(parity), "b6: Parity");
1.1.1.9 root 423: screen.Puts(1, y++, TA::Disable, "b5: ---");
1.1.1.8 root 424: screen.Puts(1, y++, TA::OnOff(int5en), "b4: Int5 Enable");
425: screen.Puts(1, y++, TA::OnOff(int5rq), "b3: Int5 Request");
426: screen.Puts(1, y++, TA::OnOff(int1en), "b2: Int1 Enable");
1.1.1.9 root 427: screen.Puts(1, y++, TA::Disable, "b1: ---");
1.1.1.8 root 428: screen.Puts(1, y++, TA::OnOff(int1rq), "b0: Intq Request");
1.1.1.2 root 429: }
430:
1.1 root 431: // PortA (DIP-SW 1) 読み込み
432: uint32
433: PIO0Device::GetPA() const
434: {
435: uint32 data = 0;
436: for (int i = 0; i < 8; i++) {
437: data |= ((int)dipsw1[i]) << i;
438: }
1.1.1.9 root 439: if (msb_first) {
440: data = bitrev(data);
441: }
1.1 root 442: return data & 0xff;
443: }
444:
445: // PortB (DIP-SW 2) 読み込み
446: uint32
447: PIO0Device::GetPB() const
448: {
449: uint32 data = 0;
450: for (int i = 0; i < 8; i++) {
451: data |= ((int)dipsw2[i]) << i;
452: }
1.1.1.9 root 453: if (msb_first) {
454: data = bitrev(data);
455: }
1.1 root 456: return data & 0xff;
457: }
458:
459: // PortC 読み込み
460: uint32
461: PIO0Device::GetPC() const
462: {
463: uint32 data;
464:
465: data = 0x00 // XXX 空きビットは何が読めるか?
466: | (int1rq ? 0x01 : 0)
467: | (int1en ? 0x04 : 0)
468: | (int5rq ? 0x08 : 0)
469: | (int5en ? 0x10 : 0)
470: | (parity ? 0x40 : 0)
471: | (xp_reset?0x80 : 0);
472: return data;
473: }
474:
475: // PortC 書き込み
476: void
477: PIO0Device::SetPC(int pc, int val)
478: {
479: switch (pc) {
480: case 0:
481: if ((val ^ int1rq)) {
482: int1rq = val;
483: putlog(0, "INT1割り込み要求 %d (未実装)", int1rq);
484: }
485: return;
486: case 1:
487: // Not Connected.
488: return;
489: case 2:
490: if ((val ^ int1en)) {
491: int1en = val;
492: putlog(0, "INT1割り込み許可 %d (未実装)", int1en);
493: }
494: return;
495: case 3:
496: if ((val ^ int5rq)) {
497: int5rq = val;
498: putlog(0, "INT5割り込み要求 %d (未実装)", int5rq);
499: }
500: return;
501: case 4:
502: if ((val ^ int5en)) {
503: int5en = val;
504: putlog(0, "INT5割り込み許可 %d (未実装)", int5en);
505: }
506: return;
507: case 5:
508: // Not Connected.
509: return;
510: case 6:
511: // パリティ有効/無効
512: // 使わないので無視するが読み返しのために覚えておく
513: parity = val;
514: return;
515: case 7:
516: // XP リセット
517: if ((val ^ xp_reset)) {
518: xp_reset = val;
519: putlog(0, "XPリセット %d (未実装)", xp_reset);
520: }
521: return;
522: }
1.1.1.10! root 523: VMPANIC("invalid pc=%d", pc);
1.1 root 524: }
525:
526:
527: //
528: // LUNA PIO1
529: //
530: // 仕様は 0x4d000000 から 4バイトだが、
531: // 0x4c000000 から 0x50000000 の手前まで 4バイト単位でミラーが見える。
532:
533: // コンストラクタ
534: PIO1Device::PIO1Device()
1.1.1.8 root 535: : inherited("PIO1")
1.1 root 536: {
537: devaddr = 0x4d000000;
538: devlen = 4;
539:
1.1.1.10! root 540: poffevent.func = ToEventCallback(&PIO1Device::PowerOffCallback);
! 541: poffevent.time = 1_msec;
! 542: poffevent.Regist("PIO1 Power Off");
1.1 root 543: }
544:
545: // デストラクタ
546: PIO1Device::~PIO1Device()
547: {
1.1.1.10! root 548: gPIO1 = NULL;
1.1 root 549: }
550:
1.1.1.4 root 551: // リセット
552: void
1.1.1.10! root 553: PIO1Device::ResetHard(bool poweron)
1.1.1.4 root 554: {
1.1.1.10! root 555: inherited::ResetHard(poweron);
1.1.1.4 root 556:
1.1.1.10! root 557: gScheduler->StopEvent(poffevent);
1.1.1.4 root 558: }
559:
1.1 root 560: uint64
1.1.1.6 root 561: PIO1Device::Read(uint32 offset)
1.1 root 562: {
563: uint32 data;
564:
1.1.1.6 root 565: switch (offset) {
1.1 root 566: case 0:
567: // PIO1 PortA: LCD データ
568: return gLCD->Read();
569:
570: case 1:
571: // PIO1 PortB: 無効
572: return 0xff;
573:
574: case 2:
575: // PortC
576: data = gLCD->Get();
577: data |= xp_intreq ? 0 : 0x02;
578: // 残りのビットは %1 だろうか?
579: data |= 0x0d;
580: putlog(1, "PC 読み込み -> $%02x", data);
581: return data;
582:
583: case 3:
584: data = ReadCtrl();
585: putlog(1, "コントロール読み込み -> $%02x", data);
586: return data;
587:
588: default:
589: __unreachable();
590: }
591: }
592:
593: uint64
1.1.1.6 root 594: PIO1Device::Write(uint32 offset, uint32 data)
1.1 root 595: {
1.1.1.6 root 596: switch (offset) {
1.1 root 597: case 0: // PIO1 PortA: LCD データ
598: gLCD->Write(data);
599: return 0;
600:
601: case 1: // PIO1 PortB: ローカル割り込み
602: break;
603:
604: case 2: // PIO1 PortC: LCD/パワーオフ制御
605: for (int i = 0; i < 8; i++) {
606: SetPC(i, data & 1);
607: data >>= 1;
608: }
609: return 0;
610:
611: case 3: // コントロール
612: WriteCtrl(data);
613: return 0;
614:
615: default:
616: __unreachable();
617: }
1.1.1.10! root 618: VMPANIC("$%08X <- $%02X 未サポート書き込み", gMPU->GetPaddr(), data);
1.1 root 619: return 0;
620: }
621:
622: uint64
1.1.1.6 root 623: PIO1Device::Peek(uint32 offset)
1.1 root 624: {
625: uint32 data;
626:
1.1.1.6 root 627: switch (offset) {
1.1 root 628: case 0: // PIO1 PortA LCD データ
629: return gLCD->Peek();
630:
631: case 1: // PIO1 PortB 無効
632: return 0xff;
633:
634: case 2: // PIO1 PortC
635: data = gLCD->Get();
636: data |= xp_intreq ? 0 : 0x02;
637: // 残りのビットは %1 だろうか?
638: data |= 0x0d;
639: return data;
640:
641: case 3:
642: return ReadCtrl();
643:
644: default:
645: __unreachable();
646: }
647: }
648:
649: void
650: PIO1Device::SetPC(int pc, int val)
651: {
652: switch (pc) {
653: case 0:
654: case 1:
655: case 2:
656: case 3:
657: // PortC の下半分は入力ポートとして使われることを想定して
658: // いるので、ここへの書き込みは起きないだろうし、無視する。
659: return;
660:
661: case 4:
662: power = val;
663: if (power) {
664: // 電源オフ取り消し
665: putlog(1, "電源 OFF 取消");
1.1.1.10! root 666: gScheduler->StopEvent(poffevent);
1.1 root 667: } else {
668: // 1msec 後に電源オフ
669: putlog(1, "電源 OFF 指示");
1.1.1.10! root 670: gScheduler->RestartEvent(poffevent);
1.1 root 671: }
672: return;
673: case 5:
674: gLCD->SetRW(val);
675: return;
676: case 6:
677: gLCD->SetRS(val);
678: return;
679: case 7:
680: gLCD->SetE(val);
681: return;
682:
683: default:
684: break;
685: }
1.1.1.10! root 686: VMPANIC("PC%d 未実装", pc);
1.1 root 687: }
688:
1.1.1.10! root 689: // 電源オフイベントのコールバック
1.1 root 690: void
1.1.1.4 root 691: PIO1Device::PowerOffCallback(Event& ev)
1.1 root 692: {
693: putlog(0, "電源 OFF");
1.1.1.10! root 694: gPower->MakePowerOffExit();
1.1 root 695: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.