|
|
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.11 root 7: //
1.1.1.13 root 8: // LUNA-88K の ROM エミュレーション
1.1.1.8 root 9: //
1.1.1.11 root 10:
1.1.1.8 root 11: // IODevice
1.1.1.15 root 12: // |
13: // +- ROMDevice (LoadROM()、ウェイト、マスク等を持つ)
14: // | +- PROMDevice (LUNA* の PROM)
15: // | +- IPLROM1Device (X680x0 の IPLROM 後半)
16: // | +- IPLROM2Device (X680x0 の IPLROM 前半)
17: // | +- CGROMDevice (X680x0 の CGROM)
18: // | |
19: // | +- ROMEmuDevice
20: // | +- LunaPROMEmuDevice (LUNA PROM エミュレーションの共通部分)
21: // | | +- Luna1PROMEmuDevice (LUNA-I の PROM エミュレーション)
22: // | | |
23: // | | | +----------------------+
24: // | | +-| Luna88kPROMEmuDevice | (LUNA-88K の PROM エミュレーション)
25: // | | +----------------------+
26: // | |
27: // | +- NewsROMEmuDevice (NEWS の ROM エミュレーション)
28: // | +- ROM30EmuDevice (X68030 の ROM30 エミュレーション)
29: // | +- Virt68kROMEmuDevice (virt-m68k の IPLROM 相当の何か)
30: // |
31: // +- PROM0Device (LUNA* のブートページ切り替え用プロキシ)
32: // +- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ)
1.1.1.2 root 33:
34: #include "romemu_luna88k.h"
1.1.1.5 root 35: #include "iodevstream.h"
1.1 root 36: #include "mainapp.h"
1.1.1.13 root 37: #include "mainram.h"
1.1 root 38: #include "memorystream.h"
1.1.1.7 root 39: #include "mk48t02.h"
1.1.1.8 root 40: #include "mpu88xx0.h"
1.1.1.14 root 41: #include "sysclk.h"
1.1.1.15 root 42: #include "sysctlr.h"
1.1 root 43:
1.1.1.13 root 44: static bool luna88k_fline_callback(MPU88xx0Device *cpu, void *arg);
1.1.1.8 root 45:
46: // m88k の命令コードを少し読みやすく書きたい。
47: // 必要なものは適宜足すこと。
48: enum class OPr {
49: r0, r1, r2, r3, r4, r5, r6, r7,
50: r8, r9, r10, r11, r12, r13, r14, r15,
51: r16, r17, r18, r19, r20, r21, r22, r23,
52: r24, r25, r26, r27, r28, r29, r30, r31,
53: };
54: #define r0 OPr::r0
55: #define r1 OPr::r1
56: #define r2 OPr::r2
57: #define r3 OPr::r3
58: #define r4 OPr::r4
59: #define r5 OPr::r5
60: #define r6 OPr::r6
61: #define r7 OPr::r7
62: #define r8 OPr::r8
63: #define r9 OPr::r9
64: #define r10 OPr::r10
1.1.1.14 root 65: #define r11 OPr::r11
1.1.1.8 root 66: enum OPm5 {
67: eq0 = 0x02,
1.1.1.13 root 68: ge0 = 0x03,
1.1.1.14 root 69: lt0 = 0x0c,
1.1.1.8 root 70: ne0 = 0x0d,
71: };
72: enum OPcr {
1.1.1.17! root 73: psr = 0x01,
! 74: ssbr = 0x03,
1.1.1.14 root 75: snip = 0x05,
76: sfip = 0x06,
1.1.1.17! root 77: sr0 = 0x11,
! 78: sr1 = 0x12,
! 79: sr2 = 0x13,
! 80: sr3 = 0x14,
1.1.1.14 root 81: };
82: enum CMPbits {
83: CMP_EQ = 2,
84: CMP_NE = 3,
85: CMP_LT = 6,
1.1.1.8 root 86: };
87:
1.1.1.11 root 88: #define USC __unused static constexpr
89:
90: USC uint32 OP3(uint32 base, uint32 rd, uint32 rs1, uint32 rs2) {
1.1.1.8 root 91: return (base) | (rd << 21) | (rs1 << 16) | rs2;
92: }
1.1.1.11 root 93: USC uint32 OP4(uint32 base, uint32 rd, uint32 rs1,
1.1.1.8 root 94: uint32 w, uint32 o) {
95: return (base) | (rd << 21) | (rs1 << 16) | (w << 5) | o;
96: }
97:
1.1.1.11 root 98: USC uint32 OP_addu(OPr rd, OPr rs1, uint32 imm) {
1.1.1.8 root 99: return OP3(0x60000000, (uint32)rd, (uint32)rs1, imm);
100: }
1.1.1.11 root 101: USC uint32 OP_addu(OPr rd, OPr rs1, OPr rs2) {
102: return OP3(0xf4006000, (uint32)rd, (uint32)rs1, (uint32)rs2);
1.1.1.8 root 103: }
1.1.1.14 root 104: USC uint32 OP_and(OPr rd, OPr rs1, OPr rs2) {
105: return OP3(0xf4004000, (uint32)rd, (uint32)rs1, (uint32)rs2);
106: }
107: USC uint32 OP_bb1(uint32 b, OPr rs1, uint32 d16) {
108: return OP3(0xd8000000, b, (uint32)rs1, d16 & 0xffff);
109: }
1.1.1.11 root 110: USC uint32 OP_bcnd(OPm5 m5, OPr rs1, uint32 d16) {
111: return OP3(0xe8000000, (uint32)m5, (uint32)rs1, d16 & 0xffff);
1.1.1.8 root 112: }
1.1.1.14 root 113: USC uint32 OP_br(uint32 d26) {
114: return (0xc0000000 | (d26 & 0x03ffffff));
115: }
1.1.1.11 root 116: USC uint32 OP_bsr(uint32 d26) {
117: return (0xc8000000 | (d26 & 0x03ffffff));
118: }
119: USC uint32 OP_clr(OPr rd, OPr rs1, uint32 w, uint32 o) {
1.1.1.8 root 120: return OP4(0xf0008000, (uint32)rd, (uint32)rs1, w, o);
121: }
1.1.1.14 root 122: USC uint32 OP_cmp(OPr rd, OPr rs1, OPr rs2) {
123: return OP3(0xf4007c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
124: }
125: USC uint32 OP_extu(OPr rd, OPr rs1, uint32 w, uint32 o) {
126: return OP4(0xf0009800, (uint32)rd, (uint32)rs1, w, o);
127: }
1.1.1.11 root 128: USC uint32 OP_jmp(OPr rs2) {
1.1.1.8 root 129: return OP3(0xf400c000, 0, 0, (uint32)rs2);
130: }
1.1.1.11 root 131: USC uint32 OP_ld(OPr rd, OPr rs1, uint32 imm) {
1.1.1.8 root 132: return OP3(0x14000000, (uint32)rd, (uint32)rs1, imm);
133: }
1.1.1.11 root 134: USC uint32 OP_ldcr(OPr rd, uint32 crs) {
1.1.1.8 root 135: return OP4(0x80004000, (uint32)rd, 0, crs, 0);
136: }
1.1.1.17! root 137: USC uint32 OP_mask(OPr rd, OPr rs1, uint32 imm) {
! 138: return OP3(0x48000000, (uint32)rd, (uint32)rs1, imm);
! 139: }
1.1.1.11 root 140: USC uint32 __unused OP_or(OPr rd, OPr rs1, OPr rs2) {
1.1.1.8 root 141: return OP3(0xf4005800, (uint32)rd, (uint32)rs1, (uint32)rs2);
142: }
1.1.1.11 root 143: USC uint32 OP_or(OPr rd, OPr rs1, uint32 imm) {
1.1.1.8 root 144: return OP3(0x58000000, (uint32)rd, (uint32)rs1, imm);
145: }
1.1.1.14 root 146: USC uint32 OP_or_c(OPr rd, OPr rs1, OPr rs2) {
147: return OP3(0xf4005c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
148: }
1.1.1.11 root 149: USC uint32 OP_or_u(OPr rd, OPr rs1, uint32 imm) {
1.1.1.8 root 150: return OP3(0x5c000000, (uint32)rd, (uint32)rs1, imm);
151: }
1.1.1.11 root 152: USC uint32 OP_rte() {
1.1.1.8 root 153: return 0xf400fc00;
154: }
1.1.1.14 root 155: USC uint32 OP_st(OPr rd, OPr rs1, uint32 imm) {
156: return OP3(0x24000000, (uint32)rd, (uint32)rs1, imm);
157: }
1.1.1.11 root 158: USC uint32 OP_st(OPr rd, OPr rs1, OPr rs2) {
1.1.1.8 root 159: return OP3(0xf4002400, (uint32)rd, (uint32)rs1, (uint32)rs2);
160: }
1.1.1.14 root 161: USC uint32 OP_st_b(OPr rd, OPr rs1, OPr rs2) {
162: return OP3(0xf4002c00, (uint32)rd, (uint32)rs1, (uint32)rs2);
163: }
1.1.1.11 root 164: USC uint32 OP_stcr(OPr rs1, uint32 crd) {
1.1.1.8 root 165: return OP4(0x80008000, 0, (uint32)rs1, crd, (uint32)rs1);
166: }
1.1.1.17! root 167: USC uint32 OP_xcr(OPr rd, OPr rs1, uint32 cr) {
! 168: return OP4(0x8000c000, (uint32)rd, (uint32)rs1, cr, (uint32)rs1);
! 169: }
! 170: USC uint32 OP_xmem(OPr rd, OPr rs1, uint32 imm) {
! 171: return OP3(0x04000000, (uint32)rd, (uint32)rs1, imm);
! 172: }
1.1.1.11 root 173: USC uint32 OP_doscall(OPr rd, OPr rs1, uint32 imm) {
1.1.1.8 root 174: return OP3(0xfc000400, (uint32)rd, (uint32)rs1, imm);
175: }
1.1.1.14 root 176: #define SxIP_VALID (0x0002)
1.1.1.8 root 177: #define DOS_GETCHAR (1)
178: #define DOS_PUTCHAR (2)
179:
180:
1.1 root 181: // コンストラクタ
182: Luna88kPROMEmuDevice::Luna88kPROMEmuDevice()
183: {
1.1.1.14 root 184: machine_name = "LUNA-88K";
185:
1.1.1.5 root 186: // プロンプトを初期化。
1.1.1.7 root 187: prompt = default_prompt;
1.1 root 188: }
189:
190: // デストラクタ
191: Luna88kPROMEmuDevice::~Luna88kPROMEmuDevice()
192: {
193: }
194:
1.1.1.13 root 195: // 初期化
1.1 root 196: bool
197: Luna88kPROMEmuDevice::Init()
198: {
1.1.1.5 root 199: if (inherited::Init() == false) {
200: return false;
201: }
202:
1.1.1.16 root 203: // ROM の特定アドレスを読み出す人がいるかも知れないので、
204: // サイズは実 ROM と同じにしておく。LUNA-I からの類推。
205: if (AllocROM(256 * 1024, 0) == false) {
206: return false;
207: }
208:
1.1.1.14 root 209: SysClkDevice *sysclk = GetSysClkDevice();
1.1.1.13 root 210:
1.1.1.8 root 211: // DOS call エミュレーションのコールバックを登録
1.1.1.14 root 212: auto mpu88xx0 = GetMPU88xx0Device(mpu);
1.1.1.13 root 213: mpu88xx0->SetFLineCallback(luna88k_fline_callback, this);
1.1.1.8 root 214:
1.1.1.15 root 215: MemoryStreamBE roms(imagebuf.get());
1.1 root 216:
1.1.1.17! root 217: // m88k では ROMIO_BASE は上位ワードだけで表現できれば楽。
! 218: static_assert((ROMIO_BASE & 0xffff) == 0, "");
! 219:
! 220: // ROM のメモリマップ:
! 221: // $41000000.L*3 リセットベクタから ROM _START へ飛ぶコード。
! 222: // $4100000c ROM _START
! 223: // $41000200 割り込みハンドラ
! 224: // $41000270 sysclk
! 225: // $41000300 ROM GETC
! 226: // $41000400 ROM PUTC
! 227: // $41000500 データアクセス例外ハンドラ
! 228:
1.1.1.6 root 229: // リセット時のブートページには ROM (これ) が見えている。
230: // ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の
231: // 0番地付近のベクタも作り直す。そのため、ここのベクタ1 以降 ($41000008〜)
232: // をベクタとして参照することはないので、ベクタ0 の命令列がそのまま使い潰
233: // してよい。というか2ワードでは 0番地から ROM まで飛べないので使わないと
234: // 面倒くさい。一体どういう設計してんだ…。
235:
1.1.1.8 root 236: // ここからややアセンブラっぽいインデント
237:
238: // _START:
239: // ここは 0番地。ここから _ROM_START へジャンプ
1.1.1.17! root 240: roms.Write4(OP_or_u (r1, r0, 0x4100));
! 241: roms.Write4(OP_or (r1, r1, 0x000c));
! 242: roms.Write4(OP_jmp (r1));
1.1.1.8 root 243:
244: // _ROM_START: (ここは ROM)
1.1.1.17! root 245: // r1: work (最後は戻りアドレスとして使う)
! 246: // r2: work (最後はジャンプ先として使う)
! 247: // r3: work
! 248: // r5: ROMIO_BASE
! 249:
1.1.1.8 root 250: // RAM に切り替え
1.1.1.17! root 251: roms.Write4(OP_st (r0, r1, r0));
! 252:
! 253: // 割り込み許可 (INIT の前に必要)。
! 254: // なお PROM が次段に処理を移す時は割り込み許可のまま進んでいるので
! 255: // ここも割り込みを許可したそのまま進む。
! 256: roms.Write4(OP_ldcr (r1, psr));
! 257: roms.Write4(OP_clr (r1, r1, 2, 0)); // clear IND,SFRZ
! 258: roms.Write4(OP_stcr (r1, psr));
! 259:
! 260: // 先に ROMIO_INIT だけ実行。
! 261: roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16));
! 262: roms.Write4(OP_ld (r0, r5, (ROMIO_INIT - ROMIO_BASE)));
! 263:
! 264: // _rom_main:
! 265: uint32 rom_main = roms.GetOffset();
! 266: // _rom_main 以降で r5 を(再)初期化する必要がある。
! 267: roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16));
! 268: roms.Write4(OP_st (r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
1.1.1.8 root 269:
1.1.1.17! root 270: roms.Write4(OP_ld (r0, r5, (ROMIO_LOAD - ROMIO_BASE)));
1.1.1.15 root 271: roms.Write4(OP_br (5)); // _entry
1.1.1.8 root 272: // _prompt:
1.1.1.15 root 273: uint32 prompt = roms.GetOffset();
1.1.1.17! root 274: roms.Write4(OP_or_u (r1, r0, 0x4100));
! 275: roms.Write4(OP_or (r1, r1, roms.GetOffset() + 12));
! 276: roms.Write4(OP_st (r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
! 277: // STOP 相当の無限ループ。
! 278: // 抜ける時は割り込みハンドラから (QUITSTOP) に rte で戻ってくる。
! 279: roms.Write4(OP_br (0));
1.1.1.11 root 280:
1.1.1.14 root 281: // _entry:
1.1.1.17! root 282: // 起動可能なら ENTRY が -1 以外を返す
! 283: roms.Write4(OP_ld (r2, r5, (ROMIO_ENTRY - ROMIO_BASE)));
! 284: roms.Write4(OP_or_c (r1, r0, r0)); // r1 <- $ffffffff
! 285: roms.Write4(OP_cmp (r1, r2, r1));
! 286: // 起動不可なら _prompt へ戻る
! 287: roms.Write4(OP_bb1 (CMP_EQ, r1, (prompt - roms.GetOffset()) / 4));
! 288:
1.1.1.8 root 289: // _go:
290: // 起動先があれば後始末して
1.1.1.17! root 291: roms.Write4(OP_ld (r0, r5, (ROMIO_CLOSE - ROMIO_BASE)));
! 292: // 使ったレジスタを一応クリア
! 293: roms.Write4(OP_or (r3, r0, r0));
! 294: roms.Write4(OP_or (r5, r0, r0));
1.1.1.14 root 295: // 戻ってきたときのために r1 を用意して
1.1.1.15 root 296: roms.Write4(OP_or_u (r1, r0, 0x4100));
297: roms.Write4(OP_or (r1, r1, roms.GetOffset() + 8));
1.1.1.8 root 298: // ジャンプ
1.1.1.15 root 299: roms.Write4(OP_jmp (r2));
1.1.1.14 root 300: // 戻ってきたら
1.1.1.17! root 301: roms.Write4(OP_br ((rom_main - roms.GetOffset()) / 4));
1.1.1.5 root 302:
1.1.1.14 root 303: // 割り込みハンドラ。
304: // _intr:
1.1.1.15 root 305: roms.SetOffset(0x200);
1.1.1.17! root 306: // r1: work
! 307: // r2: work
! 308: // r5: ROMIO_BASE
! 309:
! 310: // レジスタを退避。r1 は sr2 に退避済み。
! 311: roms.Write4(OP_st (r2, r0, 0x2000));
! 312: roms.Write4(OP_st (r5, r0, 0x2004));
! 313: roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16));
! 314:
! 315: roms.Write4(OP_or_u (r1, r0, 0x6500));
! 316: roms.Write4(OP_ld (r1, r1, 0));
! 317: roms.Write4(OP_extu (r1, r1, 3, 29));
! 318: roms.Write4(OP_or (r2, r0, 6));
! 319: roms.Write4(OP_cmp (r2, r1, r2));
! 320: // 割り込みは sysclk, sio のみ許可しているので必ずどちらか。
! 321: roms.Write4(OP_bb1 (CMP_EQ, r2, (0x270 - roms.GetOffset()) / 4));
1.1.1.14 root 322: // key_intr:
1.1.1.17! root 323: roms.Write4(OP_ld (r0, r5, (ROMIO_KEYIN - ROMIO_BASE)));
! 324:
1.1.1.14 root 325: // _intr_ret:
1.1.1.17! root 326: // QUITSTOP があれば rte の戻り先をそっちに書き換える。
1.1.1.15 root 327: uint32 intr_ret = roms.GetOffset();
1.1.1.17! root 328: roms.Write4(OP_ld (r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
! 329: roms.Write4(OP_bcnd (eq0, r1, 6)); // beq @f
! 330: roms.Write4(OP_st (r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
! 331: roms.Write4(OP_or (r1, r1, SxIP_VALID));
! 332: roms.Write4(OP_stcr (r1, snip));
! 333: roms.Write4(OP_addu (r1, r1, 0x0004));
! 334: roms.Write4(OP_stcr (r1, sfip));
1.1.1.14 root 335: // @@:
1.1.1.17! root 336: // レジスタを復帰
! 337: roms.Write4(OP_xcr (r1, r0, sr2));
! 338: roms.Write4(OP_or (r2, r0, r0));
! 339: roms.Write4(OP_or (r5, r0, r0));
! 340: roms.Write4(OP_xmem (r2, r0, 0x2000));
! 341: roms.Write4(OP_xmem (r5, r0, 0x2004));
! 342: roms.Write4(OP_stcr (r0, ssbr));
1.1.1.15 root 343: roms.Write4(OP_rte ());
1.1.1.8 root 344:
1.1.1.17! root 345: // _sysclk_intr:
! 346: assertmsg(roms.GetOffset() < 0x270, "offset=$%x", roms.GetOffset());
! 347: roms.SetOffset(0x270);
1.1.1.14 root 348: // ACK
1.1.1.17! root 349: roms.Write4(OP_or_u (r1, r0, 0x6300));
! 350: roms.Write4(OP_st_b (r0, r1, r0));
1.1.1.14 root 351: // inc
1.1.1.17! root 352: roms.Write4(OP_ld (r1, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
! 353: roms.Write4(OP_addu (r1, r1, 1));
! 354: roms.Write4(OP_st (r1, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
1.1.1.15 root 355: roms.Write4(OP_br ((intr_ret - roms.GetOffset()) / 4));
1.1.1.14 root 356:
1.1.1.8 root 357: // ROM GETC エミュレータ
1.1.1.17! root 358: assertmsg(roms.GetOffset() < 0x300, "offset=$%x", roms.GetOffset());
1.1.1.15 root 359: roms.SetOffset(0x300);
360: roms.Write4(OP_doscall(r2, r0, DOS_GETCHAR));
361: roms.Write4(OP_jmp (r1));
1.1.1.8 root 362:
363: // ROM PUTC エミュレータ
1.1.1.13 root 364: // 表示する文字は r2 に置かれている。
1.1.1.15 root 365: roms.SetOffset(0x400);
366: roms.Write4(OP_doscall(r0, r2, DOS_PUTCHAR));
367: roms.Write4(OP_jmp (r1));
1.1.1.8 root 368:
1.1.1.17! root 369: // データアクセス例外ハンドラ。
! 370: // 例外を起こした命令の次の命令 (現状 snip) に戻る。
! 371: roms.SetOffset(0x500);
! 372: // PROM-1.20 互換動作にするため sr2 に退避した r1 はそのままにする。
! 373: // boot のオレオレ machine read コマンドがこの動作を利用している。
! 374: roms.Write4(OP_ldcr (r1, sr2));
! 375: roms.Write4(OP_stcr (r0, ssbr));
! 376: roms.Write4(OP_rte ());
! 377:
1.1.1.14 root 378: // サブルーチン
1.1.1.15 root 379: roms.SetOffset(0x900);
1.1.1.17! root 380: roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16));
! 381: roms.Write4(OP_ld (r0, r5, (ROMIO_AP1 - ROMIO_BASE)));
1.1.1.14 root 382: //@@:
1.1.1.17! root 383: uint32 subr1 = roms.GetOffset();
! 384: roms.Write4(OP_or_u (r2, r0, 0x4100));
! 385: roms.Write4(OP_or (r2, r2, roms.GetOffset() + 12));
! 386: roms.Write4(OP_st (r2, r5, (ROMIO_QUITSTOP - ROMIO_BASE)));
1.1.1.15 root 387: roms.Write4(OP_br (0)); // STOP 相当
388:
1.1.1.17! root 389: roms.Write4(OP_ld (r2, r5, (ROMIO_CLKCNT - ROMIO_BASE)));
! 390: roms.Write4(OP_mask (r2, r2, 0x01ff)); // r2 &= 0x1ff;
1.1.1.15 root 391: uint freq = sysclk->GetFreq();
1.1.1.17! root 392: roms.Write4(OP_or (r3, r0, freq * 3));// r5 = freq*3;
! 393: roms.Write4(OP_cmp (r3, r2, r3));
! 394: roms.Write4(OP_bb1 (CMP_LT, r3, (subr1 - roms.GetOffset()) / 4));
! 395: // blt @b
! 396: roms.Write4(OP_ld (r0, r5, (ROMIO_AP2 - ROMIO_BASE)));
1.1.1.15 root 397: roms.Write4(OP_jmp (r1));
1.1.1.8 root 398:
399: // 変なインデントここまで
1.1 root 400:
401: return true;
402: }
403:
1.1.1.14 root 404: // ROM 起動時の初期化 (機種依存部分) を行う。
405: void
406: Luna88kPROMEmuDevice::ROM_InitMD()
1.1 root 407: {
1.1.1.15 root 408: IODeviceStream rams(mainram);
1.1.1.13 root 409:
1.1.1.14 root 410: // 割り込みマスクで Sysclk, SIO だけ許可
1.1.1.15 root 411: // XXX とりあえず CPU#0 のみ
412: auto *sysctlr = dynamic_cast<SysCtlrDevice*>(GetInterruptDevice());
413: sysctlr->WriteIntMask(0, 0xc0000000);
1.1 root 414:
1.1.1.17! root 415: // RAM のメモリマップ:
! 416: // $00000000 .. $00000fff 例外ベクタ
! 417: // $00001000 .. $000010ff トランポリン用 (PROM もここを使っている)
! 418: // $00001100 .. ? PROM 用が使う領域があるようだ
! 419: // $00002000 .. レジスタ保存用として勝手に使うことにする
! 420:
1.1.1.16 root 421: rams.SetAddr(0);
1.1.1.17! root 422: // ベクタテーブルを rte で埋めておく。
1.1.1.5 root 423: for (int i = 0; i < 512 * 2; i++) {
1.1.1.15 root 424: rams.Write4(OP_rte());
1.1.1.5 root 425: }
1.1.1.17! root 426: // 例外ベクタの2ワードとトランポリン先の4ワードで PROM まで飛ぶ
! 427: // コードを出力する。
! 428: static const struct {
! 429: uint32 vec;
! 430: uint32 toaddr;
! 431: } vectors [] = {
! 432: { 1, 0x41000200 }, // 割り込み
! 433: { 3, 0x41000500 }, // データアクセス例外
! 434: };
! 435: uint32 midaddr = 0x1000;
! 436: for (int i = 0; i < countof(vectors); i++) {
! 437: uint32 vec = vectors[i].vec;
! 438: uint32 toaddr = vectors[i].toaddr;
! 439: rams.SetAddr(vec * 8);
! 440: // ベクタは定型文で書いておく。
! 441: rams.Write4(OP_or (r0, r0, r0));
! 442: rams.Write4(OP_br ((midaddr - rams.GetAddr()) / 4));
! 443: // 経由地で r1 を sr2 に退避してから、toaddr にジャンプ。
! 444: rams.SetAddr(midaddr);
! 445: rams.Write4(OP_stcr (r1, sr2));
! 446: rams.Write4(OP_or_u (r1, r0, toaddr >> 16));
! 447: rams.Write4(OP_or (r1, r1, toaddr & 0xffff));
! 448: rams.Write4(OP_jmp (r1));
! 449: midaddr = rams.GetAddr();
! 450: }
1.1.1.2 root 451:
1.1.1.5 root 452: // ROM の機能テーブル
1.1.1.8 root 453: // 0x00001100 ?
454: // 0x00001104 ?
455: // 0x00001108 ?
456: // 0x0000110c GETC() のアドレス
457: // 0x00001110 PUTC(c) のアドレス
1.1.1.5 root 458: // 0x00001114 ビットマッププレーンの有効状態が入っている。
459: // bit0: plane0 有効
460: // bit1..7: plane1..7 有効
1.1.1.16 root 461: rams.SetAddr(0x110c);
1.1.1.15 root 462: rams.Write4(0x41000300);
463: rams.Write4(0x41000400);
464: rams.Write4(0x00000001);
1.1.1.5 root 465:
1.1.1.15 root 466: uint32 ap = (nvram->PeekPort(0x7fd) << 8) | nvram->PeekPort(0x7fe);
1.1.1.14 root 467: if (__predict_false(0||(ap & 0x3f1f) == 0x0104)) {
468: auto *mpu88xx0 = GetMPU88xx0Device(mpu);
469: mpu88xx0->reg.r[1] = mpu88xx0->reg.nip;
470: mpu88xx0->DoBranch(0x41000900, false);
471: }
472: }
1.1.1.5 root 473:
1.1.1.17! root 474: void
! 475: Luna88kPROMEmuDevice::ROM_Close()
! 476: {
! 477: inherited::ROM_Close();
! 478:
! 479: // 割り込みをすべてマスクに戻す。
! 480: // XXX とりあえず CPU#0 のみ
! 481: auto *sysctlr = dynamic_cast<SysCtlrDevice*>(GetInterruptDevice());
! 482: sysctlr->WriteIntMask(0, 0x00000000);
! 483: }
! 484:
1.1.1.14 root 485: // ファイルをロードする。
486: // -X が指定されていればそのホストファイル。
487: // そうでなければゲストファイル。この場合引数 fname があれば
488: // NVRAM で指定されたデバイスから読み込む際のファイル名とする。
489: // ロードできればエントリポイントを返す。
490: // ロードできなければ errmsg にメッセージをセットし -1 を返す。
491: uint32
492: Luna88kPROMEmuDevice::LoadFile(const std::string& fname)
493: {
494: uint32 entry;
1.1.1.5 root 495:
1.1.1.10 root 496: if (gMainApp.exec_file) {
1.1.1.14 root 497: // -X が指定されてたらホストプログラムを読み込む。
498: entry = LoadHostFile();
1.1.1.10 root 499: } else {
1.1.1.14 root 500: // NVRAM で指定されるゲストプログラムを読み込む。
501: // XXX とりあえず boot を起動するだけ
502: bootinfo_t bootinfo;
503: GetNVRAM(bootinfo);
504: // 引数で指定されたらファイル名だけ上書き
505: if (fname.empty() == false) {
506: bootinfo.dkfile = fname;
1.1.1.5 root 507: }
1.1.1.14 root 508: entry = LoadGuestFile(bootinfo);
1.1.1.5 root 509: }
510:
511: return entry;
512: }
513:
1.1.1.7 root 514: void
1.1.1.13 root 515: Luna88kPROMEmuDevice::ProcessChar(char asciicode)
1.1.1.7 root 516: {
517: if (prompt_idx == 0) {
518: // 行入力モード (画面更新もしてある)
1.1.1.13 root 519: inherited::ProcessChar(asciicode);
1.1.1.7 root 520: } else {
521: // 0 以外ならノンブロッキングモード
522:
523: // 入力されたキーを表示
524: if (' ' <= asciicode && asciicode < 0x7f) {
1.1.1.11 root 525: Putc(asciicode);
1.1.1.7 root 526: }
527:
528: switch (prompt_idx) {
529: case PROMPT_NVRAM_UPDATE:
530: CommandNvramUpdate2(asciicode);
531: break;
532:
533: case PROMPT_NVRAM_NEW:
534: CommandNvramNew2(asciicode);
535: break;
536:
537: case PROMPT_NVRAM_INIT:
538: CommandNvramInit2(asciicode);
539: break;
540: }
541:
542: // プロンプトを戻す
543: prompt = default_prompt;
544: prompt_idx = 0;
1.1.1.11 root 545: prompt_y = cursor_y;
546: ClearPrompt();
1.1.1.7 root 547: }
548: }
549:
1.1.1.5 root 550: // 入力コマンド処理 (入力行が揃ったところで呼ばれる)。
551: //
552: // b 現在のパラメータで起動
553: // b ... パラメータを指定して起動
554: // nvram 全パラメータを表示
555: // nvram [<name>] name のみ表示?
556: // nvram <name> <val> 値を設定
557: // nvram - <name> 削除
558: // nvram-- NVRAM 初期化
559:
560: void
561: Luna88kPROMEmuDevice::Command()
1.1.1.2 root 562: {
1.1.1.5 root 563: std::vector<char> buf(inputbuf.length() + 1);
564: std::vector<std::string> arg;
1.1.1.2 root 565:
1.1.1.5 root 566: // 1ワードごとに分解
1.1.1.15 root 567: strlcpy(buf.data(), inputbuf.c_str(), buf.size());
1.1.1.5 root 568: char *p = &buf[0];
1.1.1.6 root 569: for (char *ap; (ap = strsep(&p, " ")) != NULL; ) {
570: if (*ap != '\0') {
571: arg.push_back(std::string(ap));
1.1.1.5 root 572: }
573: }
574: if (loglevel >= 1) {
575: for (int i = 0; i < arg.size(); i++) {
1.1.1.15 root 576: putmsgn("arg[%u]=|%s|", i, arg[i].c_str());
1.1.1.5 root 577: }
578: }
1.1.1.2 root 579:
1.1.1.5 root 580: // 空行
581: if (arg.size() == 0) {
582: return;
583: }
584:
585: // 1ワード目がコマンド
586: if (arg[0] == "h") {
1.1.1.7 root 587: CommandH(arg);
1.1.1.5 root 588: return;
589: }
590: if (arg[0] == "b") {
591: CommandB(arg);
592: return;
593: }
594: if (arg[0] == "nvram") {
595: CommandNvram(arg);
596: return;
1.1.1.2 root 597: }
1.1.1.5 root 598:
1.1.1.11 root 599: Print("** Unknown command: %s\n", arg[0].c_str());
1.1.1.2 root 600: }
601:
1.1.1.7 root 602: // "h" コマンド
603: // 引数なしなら、全体のヘルプメッセージ。
604: // 引数ありなら、個別のヘルプメッセージ。
605: void
606: Luna88kPROMEmuDevice::CommandH(const std::vector<std::string>& arg)
607: {
608: if (arg.size() < 2) {
609: // ヘルプメッセージ
610: mainhelp:
1.1.1.11 root 611: Print(
1.1.1.7 root 612: " *** Help Message ***\n"
613: " b : load and execute program\n"
614: " h : show this message\n"
615: " nvram : show/set nvram parameters\n"
616: );
617: return;
618: }
619:
620: const std::string& key = arg[1];
621: if (key == "nvram") {
1.1.1.11 root 622: Print(
1.1.1.7 root 623: " *** Help Message ***\n"
624: " nvram [<symbol>] : show\n"
625: " nvram <symbol> <value> : set\n"
626: " nvram - <symbol> : delete\n"
627: " nvram -- : initialize\n"
628: );
629: return;
630: } else {
631: goto mainhelp;
632: }
633: }
634:
1.1.1.5 root 635: // "b" コマンド
1.1.1.14 root 636: // 引数なしなら NVRAM の情報 (もしくは -X オプション) に従ってプログラムを
637: // ロードして実行。引数ありのほうは未実装。
1.1.1.5 root 638: void
639: Luna88kPROMEmuDevice::CommandB(const std::vector<std::string>& arg)
1.1 root 640: {
1.1.1.14 root 641: entrypoint = LoadFile("");
642: if (entrypoint == -1) {
1.1.1.11 root 643: Print("** %s\n", errmsg.c_str());
1.1.1.14 root 644: } else {
645: execute = true;
646: Print("%s. Entry point = $%08x\n",
647: (gMainApp.exec_file ? "Host program loaded" : "Loaded"),
648: entrypoint);
1.1 root 649: }
1.1.1.5 root 650: }
1.1 root 651:
1.1.1.7 root 652: // "nvram" コマンド
653: // nvram .. 全表示
654: // nvram <key> .. 表示
655: // nvram <key> <val> .. 追加/更新
656: // nvram - <key> .. 削除
657: // nvram -- .. 初期化
1.1.1.5 root 658: void
659: Luna88kPROMEmuDevice::CommandNvram(const std::vector<std::string>& arg)
1.1.1.2 root 660: {
1.1.1.7 root 661: switch (arg.size()) {
662: case 1:
663: // 引数なし
664: CommandNvramShowAll();
665: break;
666: case 2:
667: // 引数1つ
668: if (arg[1] == "--") {
669: CommandNvramInit();
670: } else {
671: CommandNvramShow(arg);
672: }
673: break;
674: case 3:
675: // 引数2つ
676: if (arg[1] == "-") {
677: CommandNvramDelete(arg);
678: } else {
679: CommandNvramUpdate(arg);
680: }
681: break;
682: default:
1.1.1.11 root 683: Print("** Syntax error\n");
1.1.1.7 root 684: }
685: }
686:
687: // NVRAM は 32バイトを1エントリとする。
688: // [0] はヘッダに相当し、フォーマットは別記。
689: // [1] 以降が key (16バイト)、val(16バイト) の並び。こんな感じ。
690: //
691: // 00000020 62 6f 6f 74 5f 64 65 76 69 63 65 00 00 00 00 00 |boot_device.....|
692: // 00000030 73 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |sd..............|
693: //
694: // key, val とも '\0' で終端もしくは16文字で終端のようだ。
695: // val に17文字を設定しようとすると怒られる。
696: // key が "" ならこのエントリは空きで val は無視、
697: // 空きエントリは無視して次へ (FAT のディレクトリエントリみたいな感じ)。
698: // val が "" は有効。
699: //
700: // オフセット 0x540 から始まるエントリが最後で、これ以上追加しようとすると
701: // "nvram set error: no area" (PROM 1.20) と言われる。
1.1.1.13 root 702: // LUNA-88K にはないが LUNA-I では 0x560 にぽつんと ENADDR があったので、
1.1.1.7 root 703: // その辺りとの互換性だろうか。
704:
705: // NVRAM 全項目表示
706: void
707: Luna88kPROMEmuDevice::CommandNvramShowAll()
708: {
709: putmsg(1, "nvram ShowAll");
710: for (uint32 offset = 0x20; offset < 0x560; offset += 0x20) {
711: std::string key = nvram->PeekString(offset);
712: if (key.empty()) {
713: continue;
714: }
715: std::string val = nvram->PeekString(offset + 16);
1.1.1.11 root 716: Print("%-15s : %s\n", key.c_str(), val.c_str());
1.1.1.7 root 717: }
718: }
719:
720: // NVRAM 指定項目を表示
721: void
722: Luna88kPROMEmuDevice::CommandNvramShow(const std::vector<std::string>& arg)
723: {
724: assert(arg.size() >= 2);
725: const std::string& key = arg[1];
726:
727: putmsg(1, "nvram Show |%s|", key.c_str());
728: uint32 offset = FindNVRAM(key);
729: if (offset > 0) {
730: std::string val = nvram->PeekString(offset + 16);
1.1.1.11 root 731: Print("%-15s : %s\n", key.c_str(), val.c_str());
1.1.1.7 root 732: } else {
1.1.1.11 root 733: Print("** Not found\n");
1.1.1.7 root 734: }
735: }
736:
737: // NVRAM 項目を追加/更新
738: void
739: Luna88kPROMEmuDevice::CommandNvramUpdate(const std::vector<std::string>& arg)
740: {
741: // キーがあれば更新、なければ追加。
742: // いずれも事前にプロンプトで確認する。
743:
744: assert(arg.size() >= 3);
745: nvram_key = arg[1];
746: nvram_val = arg[2];
747: putmsg(1, "nvram Update |%s|%s|", nvram_key.c_str(), nvram_val.c_str());
748:
749: if (nvram_key.size() > 16) {
1.1.1.11 root 750: Print("** Key too long\n");
1.1.1.7 root 751: return;
752: }
753: if (nvram_val.size() > 16) {
1.1.1.11 root 754: Print("** Val too long\n");
1.1.1.7 root 755: return;
756: }
757:
758: nvram_offset = FindNVRAM(nvram_key);
759: if (nvram_offset > 0) {
760: // キーがすでにあれば更新。
761: // Update2() に申し送りするのは nvram_offset と nvram_val のみでいい。
762:
763: std::string oldval = nvram->PeekString(nvram_offset + 16);
764: prompt = string_format("Update %s : \"%s\" -> \"%s\" (Y/[N]):",
765: nvram_key.c_str(), oldval.c_str(), nvram_val.c_str());
766: prompt_idx = PROMPT_NVRAM_UPDATE;
767: } else {
768: // キーがなければ追加
769: // New2() に申し送りするのは nvram_offset, nvram_key, nvram_val の3つ。
770:
771: // 先に空き領域をチェックしている
772: nvram_offset = FindNVRAM("");
773: if (nvram_offset == 0) {
1.1.1.11 root 774: Print("** No area available\n");
1.1.1.7 root 775: return;
776: }
777:
778: prompt = string_format("Set new %s : \"%s\" (Y/[N]):",
779: nvram_key.c_str(), nvram_val.c_str());
780: prompt_idx = PROMPT_NVRAM_NEW;
781: }
782:
783: // 入力待ちの状態で戻る
784: // (prompt_idx != 0 なのでキー入力後に指定の関数へ飛んで続きを実行する)
785: }
786:
787: // NVRAM 項目を更新 (キー入力後)
788: void
789: Luna88kPROMEmuDevice::CommandNvramUpdate2(char asciicode)
790: {
791: assert(prompt_idx == PROMPT_NVRAM_UPDATE);
792: assert(nvram_offset > 0);
793: assert(nvram_val.size() <= 16);
794:
1.1.1.11 root 795: Putc('\n');
1.1.1.7 root 796:
797: if (asciicode != 'y' && asciicode != 'Y') {
1.1.1.11 root 798: Print("** Not updated\n");
1.1.1.7 root 799: return;
800: }
801:
802: if (nvram->WriteString(nvram_offset + 16, nvram_val) == false) {
1.1.1.11 root 803: Print("** Write error\n");
1.1.1.7 root 804: return;
805: }
806: WriteNVRAMCsum();
807:
1.1.1.11 root 808: Print("Updated\n");
1.1.1.7 root 809: }
810:
811: // NVRAM 項目を追加 (キー入力後)
812: void
813: Luna88kPROMEmuDevice::CommandNvramNew2(char asciicode)
814: {
815: assert(prompt_idx == PROMPT_NVRAM_NEW);
816: assert(nvram_offset > 0);
817: assert(!nvram_key.empty());
818: assert(nvram_key.size() <= 16);
819: assert(nvram_val.size() <= 16);
820:
1.1.1.11 root 821: Putc('\n');
1.1.1.7 root 822:
823: if (asciicode != 'y' && asciicode != 'Y') {
1.1.1.11 root 824: Print("** Not added\n");
1.1.1.7 root 825: return;
826: }
827:
828: if (nvram->WriteString(nvram_offset, nvram_key) == false
829: || nvram->WriteString(nvram_offset + 16, nvram_val) == false) {
1.1.1.11 root 830: Print("** Write error\n");
1.1.1.7 root 831: return;
832: }
833: WriteNVRAMCsum();
834:
1.1.1.11 root 835: Print("Added\n");
1.1.1.2 root 836: }
837:
1.1.1.7 root 838: // NVRAM 項目削除
839: //
840: // 削除する時は確認がないらしい… (PROM 1.20)
841: // 成功しても何も言わないらしい… (PROM 1.20)
842: // 追加削除に比べてなんか雑じゃない?
843: void
844: Luna88kPROMEmuDevice::CommandNvramDelete(const std::vector<std::string>& arg)
845: {
846: assert(arg.size() >= 3);
847: assert(arg[1] == "-");
848: const std::string& key = arg[2];
849: putmsg(1, "nvram Delete |%s|", key.c_str());
850:
851: if (key.size() > 16) {
1.1.1.11 root 852: Print("** key too long\n");
1.1.1.7 root 853: return;
854: }
855:
856: uint32 offset = FindNVRAM(key);
857: if (offset == 0) {
1.1.1.11 root 858: Print("** Not found\n");
1.1.1.7 root 859: return;
860: }
861:
862: // キーを消せば削除になる (値のほうは消してないようだ)
863: nvram->WriteString(offset, "");
864: WriteNVRAMCsum();
865: }
866:
867: // NVRAM から key を探してそのエントリのオフセットを返す。
868: // key == "" なら空きエントリを探す。
869: // 見付からなければ 0 を返す (オフセット 0 はパラメータには使わないため)
870: uint32
871: Luna88kPROMEmuDevice::FindNVRAM(const std::string& key) const
1.1.1.2 root 872: {
1.1.1.7 root 873: for (uint32 offset = 0x20; offset < 0x560; offset += 0x20) {
874: std::string k = nvram->PeekString(offset);
875: if (k == key) {
876: return offset;
877: }
878: // XXX 同じキーが複数あったらどうなるか
879: }
1.1.1.2 root 880: return 0;
881: }
882:
1.1.1.7 root 883: // NVRAM 初期化
1.1.1.5 root 884: void
1.1.1.7 root 885: Luna88kPROMEmuDevice::CommandNvramInit()
1.1.1.2 root 886: {
1.1.1.7 root 887: putmsg(1, "nvram Init");
888:
889: prompt = "Initialize (Y/[N]):";
890: prompt_idx = PROMPT_NVRAM_INIT;
891:
892: // 入力待ちの状態で戻る
1.1.1.2 root 893: }
894:
1.1.1.7 root 895: // NVRAM 初期化 (キー入力後)
896: void
897: Luna88kPROMEmuDevice::CommandNvramInit2(char asciicode)
1.1.1.2 root 898: {
1.1.1.7 root 899: assert(prompt_idx == PROMPT_NVRAM_INIT);
900:
1.1.1.11 root 901: Putc('\n');
1.1.1.7 root 902:
903: if (asciicode != 'y' && asciicode != 'Y') {
1.1.1.11 root 904: Print("** Not initialized\n");
1.1.1.7 root 905: return;
906: }
907:
908: InitNVRAM();
1.1.1.11 root 909: Print("** Initialized\n");
1.1 root 910: }
911:
1.1.1.5 root 912: // NVRAM を初期化する
913: bool
914: Luna88kPROMEmuDevice::InitNVRAM()
1.1 root 915: {
1.1.1.7 root 916: nvram->ClearAll();
917:
918: nvram->WriteString(0x0004, "<nv>");
919: nvram->WriteString(0x0020, "boot_device");
920: nvram->WriteString(0x0030, "sd");
921: nvram->WriteString(0x0040, "boot_spec");
922: nvram->WriteString(0x0050, "0");
923: nvram->WriteString(0x0060, "boot_unit");
924: nvram->WriteString(0x0070, "0");
925: nvram->WriteString(0x0080, "boot_partition");
926: nvram->WriteString(0x0090, "0");
927: nvram->WriteString(0x00a0, "boot_filename");
928: nvram->WriteString(0x00b0, "vmunix");
929: nvram->WriteString(0x00c0, "main_mem_check");
930: nvram->WriteString(0x00d0, "1");
931: nvram->WriteString(0x00e0, "3port_mem_check");
932: nvram->WriteString(0x00f0, "1");
933:
934: // チェックサム再計算
935: WriteNVRAMCsum();
936:
937: return true;
1.1.1.5 root 938: }
1.1 root 939:
1.1.1.5 root 940: // NVRAM のブート設定を bootinfo に取得する。
941: void
942: Luna88kPROMEmuDevice::GetNVRAM(bootinfo_t& bootinfo)
943: {
1.1.1.7 root 944: // dkunit (装置番号) はたぶん boot_unit (0..)
945: // dkpart (パーティション) は boot_partition (0..)
946: // dkfile (ファイル名) は boot_filename
947: // sd 以外のデバイスは未対応
1.1.1.15 root 948: bootinfo.dkunit = nvram->PeekPort(0x0070) - '0';
949: bootinfo.dkpart = nvram->PeekPort(0x0090) - '0';
1.1.1.7 root 950: bootinfo.dkfile = nvram->PeekString(0x00b0);
1.1.1.15 root 951: putmsg(1, "%s unit=%u", __func__, bootinfo.dkunit);
1.1.1.7 root 952: putmsg(1, "%s part=%c", __func__, bootinfo.dkpart + 'a');
953: putmsg(1, "%s file=|%s|", __func__, bootinfo.dkfile.c_str());
1.1 root 954: }
1.1.1.8 root 955:
956: // コールバック関数 (C のグローバル関数)
957: bool
1.1.1.13 root 958: luna88k_fline_callback(MPU88xx0Device *cpu, void *arg)
1.1.1.8 root 959: {
1.1.1.15 root 960: auto *dev = reinterpret_cast<Luna88kPROMEmuDevice *>(arg);
1.1.1.8 root 961: return dev->FLineCallback(cpu);
962: }
963:
964: // コールバック関数 (こっちが実体)
965: bool
1.1.1.13 root 966: Luna88kPROMEmuDevice::FLineCallback(MPU88xx0Device *cpu)
1.1.1.8 root 967: {
968: // 111111_DDDDDSSSSS_000001_00nnnnnnnn
969: //
970: // 引数は rS1 から順番に格納。
971: // m68k 的なバイト値やワード値の場合は下位詰めしてあるが、不要な上位
972: // ビットの処置はゲストコードよりホストコードで行うほうが楽なので、
973: // すべてこちらで責任持って処理すること。
974: // 戻り値がある DOS call では戻り値は rD に格納。
975:
1.1.1.13 root 976: uint32 D = (cpu->reg.opX >> 21) & 0x1f;
977: uint32 S1 = (cpu->reg.opX >> 16) & 0x1f;
978: uint32 num = cpu->reg.opX & 0xff;
1.1.1.8 root 979:
980: switch (num) {
981: case 0x00: // _EXIT
982: break;
983:
984: case 0x01: // _GETCHAR
1.1.1.13 root 985: cpu->reg.r[D] = Getc();
1.1.1.8 root 986: break;
987:
988: case 0x02: // _PUTCHAR
1.1.1.13 root 989: Putc(cpu->reg.r[S1++] & 0xff);
1.1.1.8 root 990: break;
991:
992: default:
1.1.1.12 root 993: putlog(0, "DOS call #$%02x (NOT IMPLEMENTED)", num);
1.1.1.13 root 994: cpu->reg.r[D] = 0xffffffff;
1.1.1.8 root 995: break;
996: }
997:
998: // 存在しないコール番号、未実装コールも含めて処理済みとする。
999: return true;
1000: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.