|
|
1.1 ! root 1: // ! 2: // nono ! 3: // Copyright (C) 2017 [email protected] ! 4: // ! 5: ! 6: #include "spc.h" ! 7: #include "mpu.h" ! 8: #include "mystring.h" ! 9: #include "scheduler.h" ! 10: #include "scsitarget.h" ! 11: #include "vm.h" ! 12: ! 13: SPCDevice *gSPC; ! 14: ! 15: SPCDevice::SPCDevice(uint32 addr) ! 16: { ! 17: logname = "spc"; ! 18: devname = "SPC"; ! 19: // X68k と LUNA でアドレスが違う。 ! 20: devaddr = addr; ! 21: // devlen は XIOSpace に必要なので LUNA ではダミー ! 22: devlen = 0x2000; ! 23: ! 24: config_name = "spc0"; ! 25: ! 26: monitor.Init(70, 17); ! 27: ! 28: // イベント ! 29: // 名前とコールバック関数は都度セットする。 ! 30: event.dev = this; ! 31: } ! 32: ! 33: SPCDevice::~SPCDevice() ! 34: { ! 35: } ! 36: ! 37: bool ! 38: SPCDevice::Init() ! 39: { ! 40: if (gVM->GetType() == VM::TYPE_LUNA) { ! 41: regbase = 0xe1000000; ! 42: regstride = 4; ! 43: // おそらく 6.144MHz が入っていると思われる ! 44: t_CLF = 163; ! 45: } else { ! 46: regbase = 0xe96021; ! 47: regstride = 2; ! 48: t_CLF = 200; ! 49: } ! 50: ! 51: return true; ! 52: } ! 53: ! 54: // 本体リセットスイッチによるリセット ! 55: void ! 56: SPCDevice::ResetHard() ! 57: { ! 58: // LUNA は本体回路が分からないのでとりあえずこっち。 ! 59: if (gVM->GetType() == VM::TYPE_LUNA) { ! 60: Reset(); ! 61: } ! 62: } ! 63: ! 64: // MPU の RESET 命令によるリセット ! 65: void ! 66: SPCDevice::ResetSoft() ! 67: { ! 68: // X680x0 はこっちだと分かっている。 ! 69: if (gVM->GetType() == VM::TYPE_X68030) { ! 70: Reset(); ! 71: } ! 72: } ! 73: ! 74: // SPC リセット ! 75: void ! 76: SPCDevice::Reset() ! 77: { ! 78: putlog(1, "リセット"); ! 79: ! 80: // 初期値 (p.61) ! 81: spc.sctl = SPC::SCTL_RESET; ! 82: spc.ints = 0x00; ! 83: spc.ssts &= 0x0f; ! 84: spc.serr = 0x00; ! 85: ! 86: // XXX マニュアルには書いていないけど、 ! 87: // SDGC bit5 の転送ごとに割り込みを発生させるビットが ! 88: // OFFになることを NetBSD が期待しているし、 ! 89: // このビットは落ちるのではないだろうか ! 90: spc.sdgc = 0x00; ! 91: } ! 92: ! 93: uint64 ! 94: SPCDevice::Read8(uint32 addr) ! 95: { ! 96: uint32 an = addr - regbase; ! 97: uint8 data; ! 98: ! 99: // regbase を減算するとレジスタは常に +0 のバイトにある。 ! 100: // X68k で +1 (偶数アドレス) はバスエラーになる ! 101: // LUNA で +1,+2,+3 はミラー ! 102: if (gVM->GetType() == VM::TYPE_X68030) { ! 103: if ((an % regstride) != 0) { ! 104: return (uint64)-1; ! 105: } ! 106: } ! 107: an /= regstride; ! 108: ! 109: switch (an) { ! 110: case SPC::BDID: ! 111: data = 1 << myid; ! 112: putlog(3, "BDID -> $%02x", data); ! 113: return data; ! 114: ! 115: case SPC::SCTL: ! 116: data = spc.sctl; ! 117: putlog(3, "SCTL -> $%02x", data); ! 118: return data; ! 119: ! 120: case SPC::SCMD: ! 121: data = GetSCMD(); ! 122: putlog(3, "SCMD -> $%02x", data); ! 123: return data; ! 124: ! 125: case SPC::TMOD: ! 126: // MB89352 には存在しないレジスタ。 ! 127: // 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。 ! 128: return 0xfc; ! 129: ! 130: case SPC::INTS: ! 131: data = spc.ints; ! 132: if (loglevel >= 2) { ! 133: static uint32 prev_ints = -1U; ! 134: if (event.active) { ! 135: // イベント中は値が変わった時だけ表示 ! 136: if (prev_ints != data) { ! 137: putlog(3, "INTS -> $%02x", data); ! 138: prev_ints = data; ! 139: } ! 140: } else { ! 141: putlog(3, "INTS -> $%02x", data); ! 142: prev_ints = -1U; ! 143: } ! 144: } ! 145: return data; ! 146: ! 147: case SPC::PSNS: ! 148: data = GetPSNS(); ! 149: putlog(3, "PSNS -> $%02x", data); ! 150: return data; ! 151: ! 152: case SPC::SSTS: ! 153: // ここで DataIn フェーズを進めてみる。 ! 154: // 本来 DataIn フェーズでの DREG キューへのデータの充填は ! 155: // レジスタアクセスとは無関係に行われるはずだが、そんな機構は ! 156: // 面倒なので、(DREG Full/Empty を調べるために) SSTS をアクセスした ! 157: // ところで、データがあれば DREG キューを埋めるようにしておく。 ! 158: if (phase == SCSI::Phase::DataIn) { ! 159: EnqueueToDREG(); ! 160: } ! 161: ! 162: // ここからが本来の SSTS 読み込み ! 163: data = GetSSTS(); ! 164: if (loglevel >= 2) { ! 165: static uint32 prev_ssts = -1U; ! 166: if (event.active) { ! 167: // イベント中は値が変わった時だけ表示 ! 168: if (prev_ssts != data) { ! 169: putlog(3, "SSTS -> $%02x", data); ! 170: prev_ssts = data; ! 171: } ! 172: } else { ! 173: putlog(3, "SSTS -> $%02x", data); ! 174: prev_ssts = -1U; ! 175: } ! 176: } ! 177: return data; ! 178: ! 179: case SPC::SERR: ! 180: data = spc.serr; ! 181: putlog(3, "SERR -> $%02x", data); ! 182: return data; ! 183: ! 184: case SPC::PCTL: ! 185: data = GetPCTL(); ! 186: putlog(3, "PCTL -> $%02x", data); ! 187: return data; ! 188: ! 189: case SPC::MBC: ! 190: putlog(0, "MBC 未実装読み込み"); ! 191: return 0xff; ! 192: ! 193: case SPC::DREG: ! 194: // XFER_OUT は DREG アクセスでリセットされる ! 195: spc.serr &= ~SPC::SERR_XFER_OUT; ! 196: ! 197: // 受信データがあればここで DREG に充填 ! 198: EnqueueToDREG(); ! 199: if (spc.dreg.Length() > 0) { ! 200: data = spc.dreg.Dequeue(); ! 201: spc.tc--; ! 202: if (loglevel >= 4) { ! 203: putlog("DREG -> $%02x (q=%d datalen=%d TC=%d)", ! 204: data, spc.dreg.Length(), datalen, spc.tc); ! 205: } else { ! 206: putlog(3, "DREG -> $%02x", data); ! 207: } ! 208: if (spc.tc == 0) { ! 209: SetINTS(SPC::INTS_COMPLETE); ! 210: } ! 211: } else { ! 212: // どうなる? ! 213: data = 0xff; ! 214: putlog(3, "DREG -> $%02x (empty)", data); ! 215: } ! 216: return data; ! 217: ! 218: case SPC::TEMP: ! 219: data = spc.temp_in; ! 220: putlog(3, "TEMP -> $%02x", data); ! 221: return data; ! 222: ! 223: case SPC::TCH: ! 224: data = spc.tc >> 16; ! 225: putlog(3, "TCH -> $%02x", data); ! 226: return data; ! 227: ! 228: case SPC::TCM: ! 229: data = (spc.tc >> 8) & 0xff; ! 230: putlog(3, "TCM -> $%02x", data); ! 231: return data; ! 232: ! 233: case SPC::TCL: ! 234: data = spc.tc & 0xff; ! 235: putlog(3, "TCL -> $%02x", data); ! 236: return data; ! 237: ! 238: case SPC::EXBF: ! 239: // MB89352 には存在しないレジスタ ! 240: return 0xff; ! 241: } ! 242: PANIC("not reached"); ! 243: } ! 244: ! 245: uint64 ! 246: SPCDevice::Read16(uint32 addr) ! 247: { ! 248: // XXX どうしたもんか ! 249: if (gVM->GetType() == VM::TYPE_LUNA) { ! 250: // LUNA はミラー ! 251: return (Read8(addr) << 8) | Read8(addr + 1); ! 252: } else { ! 253: // X68k は奇数アドレスのみ ! 254: return 0xff00 | Read8(addr + 1); ! 255: } ! 256: } ! 257: ! 258: uint64 ! 259: SPCDevice::Write8(uint32 addr, uint32 data) ! 260: { ! 261: uint32 an = addr - regbase; ! 262: ! 263: // regbase を減算するとレジスタは常に +0 のバイトにある。 ! 264: // X68k で +1 (偶数アドレス) はバスエラーになる ! 265: // LUNA で +1,+2,+3 はミラー ! 266: if (gVM->GetType() == VM::TYPE_X68030) { ! 267: if ((an % regstride) != 0) { ! 268: return (uint64)-1; ! 269: } ! 270: } ! 271: an /= regstride; ! 272: ! 273: switch (an) { ! 274: case SPC::BDID: ! 275: myid = data; ! 276: putlog(3, "BDID <- $%02x", myid); ! 277: return 0; ! 278: case SPC::SCTL: ! 279: WriteSCTL(data); ! 280: return 0; ! 281: ! 282: case SPC::SCMD: ! 283: WriteSCMD(data); ! 284: return 0; ! 285: ! 286: case SPC::TMOD: ! 287: // MB89352 には存在しないレジスタ ! 288: return 0; ! 289: ! 290: case SPC::INTS: ! 291: WriteINTS(data); ! 292: return 0; ! 293: ! 294: case SPC::SDGC: ! 295: spc.sdgc = data; ! 296: putlog(3, "SDGC <- $%02x", spc.sdgc); ! 297: return 0; ! 298: ! 299: case SPC::PCTL: ! 300: data &= SPC::PCTL_MASK; ! 301: putlog(3, "PCTL <- $%02x", data); ! 302: spc.busfree_intr_enable = (data & SPC::PCTL_BFINT_EN); ! 303: SetMSG((data & SPC::PCTL_MSG)); ! 304: SetCD((data & SPC::PCTL_CD)); ! 305: SetIO((data & SPC::PCTL_IO)); ! 306: return 0; ! 307: ! 308: case SPC::DREG: ! 309: putlog(3, "DREG <- $%02x len=%d", data, datalen); ! 310: // XFER_OUT は DREG アクセスでリセットされる ! 311: spc.serr &= ~SPC::SERR_XFER_OUT; ! 312: ! 313: if (phase == SCSI::Phase::Command) { ! 314: // コマンドフェーズなら DREG 使わず cmds に入れていく。 ! 315: cmds.push_back(data); ! 316: } else if (datalen > 0) { ! 317: // data が指定されていればそっちに書き込む。 ! 318: *dataptr++ = data; ! 319: datalen--; ! 320: } ! 321: spc.tc--; ! 322: if (spc.tc == 0) { ! 323: if (phase == SCSI::Phase::Command) { ! 324: // コマンドフェーズで全部受け取ったらコマンドを実行。 ! 325: if (loglevel >= 3) { ! 326: std::string cmdbuf = "Cmd ="; ! 327: for (uint i = 0; i < cmds.size(); i++) { ! 328: cmdbuf += string_format(" %02x", cmds[i]); ! 329: } ! 330: putlog("%s", cmdbuf.c_str()); ! 331: } ! 332: target[tgtid]->ExecCommand(cmds); ! 333: } else if (phase == SCSI::Phase::DataOut) { ! 334: // データアウトフェーズで全部受け取ったら? ! 335: } ! 336: SetINTS(SPC::INTS_COMPLETE); ! 337: } ! 338: return 0; ! 339: ! 340: case SPC::TEMP: ! 341: spc.temp_out = data; ! 342: putlog(3, "TEMP <- $%02x", spc.temp_out); ! 343: ! 344: // セレクション失敗後 SPC は SEL を下げないので TEMP に $00 を ! 345: // 書き込むことで SEL を下げるらしい ! 346: if (0) { //spc_phase == SCSIphase::Selection) { ! 347: BusFree(); ! 348: } ! 349: // Out 方向ならバスに出す ! 350: if (GetIO() == false) { ! 351: SetDATA(spc.temp_out); ! 352: } ! 353: return 0; ! 354: ! 355: case SPC::TCH: ! 356: spc.tc = (spc.tc & 0x00ffff) | (data << 16); ! 357: putlog(3, "TC(H) = $%06x", spc.tc); ! 358: return 0; ! 359: case SPC::TCM: ! 360: spc.tc = (spc.tc & 0xff00ff) | (data << 8); ! 361: putlog(3, "TC(M) = $%06x", spc.tc); ! 362: return 0; ! 363: case SPC::TCL: ! 364: spc.tc = (spc.tc & 0xffff00) | data; ! 365: putlog(3, "TC(L) = $%06x", spc.tc); ! 366: return 0; ! 367: ! 368: case SPC::EXBF: ! 369: // MB89352 には存在しないレジスタ ! 370: return 0; ! 371: } ! 372: PANIC("not reached"); ! 373: } ! 374: ! 375: uint64 ! 376: SPCDevice::Write16(uint32 addr, uint32 data) ! 377: { ! 378: // XXX どうしたもんか ! 379: if (gVM->GetType() == VM::TYPE_LUNA) { ! 380: // LUNA はミラーでバスエラーは起きない ! 381: Write8(addr, data >> 8); ! 382: Write8(addr + 1, data); ! 383: return 0; ! 384: } else { ! 385: // X68k は奇数アドレスのみ ! 386: return Write8(addr + 1, data); ! 387: } ! 388: } ! 389: ! 390: uint64 ! 391: SPCDevice::Peek8(uint32 addr) ! 392: { ! 393: uint32 an = addr - regbase; ! 394: uint8 data; ! 395: ! 396: // regbase を減算するとレジスタは常に +0 のバイトにある。 ! 397: // X68k で +1 (偶数アドレス) はバスエラーになる ! 398: // LUNA では +1,+2,+3 はミラー ! 399: if (gVM->GetType() == VM::TYPE_X68030) { ! 400: if ((an % regstride) != 0) { ! 401: return (uint64)-1; ! 402: } ! 403: } ! 404: an /= regstride; ! 405: ! 406: switch (an) { ! 407: case SPC::BDID: ! 408: data = 1 << myid; ! 409: break; ! 410: ! 411: case SPC::SCTL: ! 412: data = spc.sctl; ! 413: break; ! 414: ! 415: case SPC::SCMD: ! 416: data = GetSCMD(); ! 417: break; ! 418: ! 419: case SPC::TMOD: ! 420: // MB89352 には存在しないレジスタ。 ! 421: // 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。 ! 422: data = 0xfc; ! 423: break; ! 424: ! 425: case SPC::INTS: ! 426: data = spc.ints; ! 427: break; ! 428: ! 429: case SPC::PSNS: ! 430: data = GetPSNS(); ! 431: break; ! 432: ! 433: case SPC::SSTS: ! 434: data = GetSSTS(); ! 435: break; ! 436: ! 437: case SPC::SERR: ! 438: data = spc.serr; ! 439: break; ! 440: ! 441: case SPC::PCTL: ! 442: data = GetPCTL(); ! 443: break; ! 444: ! 445: case SPC::MBC: ! 446: data = 0xff; ! 447: break; ! 448: ! 449: case SPC::DREG: ! 450: data = spc.dreg.Peek(0); ! 451: break; ! 452: ! 453: case SPC::TEMP: ! 454: data = spc.temp_in; ! 455: break; ! 456: ! 457: case SPC::TCH: ! 458: data = spc.tc >> 16; ! 459: break; ! 460: ! 461: case SPC::TCM: ! 462: data = (spc.tc >> 8) & 0xff; ! 463: break; ! 464: ! 465: case SPC::TCL: ! 466: data = spc.tc & 0xff; ! 467: break; ! 468: ! 469: case SPC::EXBF: ! 470: // MB89352 には存在しないレジスタ ! 471: data = 0xff; ! 472: break; ! 473: ! 474: default: ! 475: // XXX 未調査 ! 476: return (uint64)-1; ! 477: } ! 478: return data; ! 479: } ! 480: ! 481: bool ! 482: SPCDevice::MonitorUpdate() ! 483: { ! 484: int x; ! 485: int y; ! 486: uint32 sctl; ! 487: uint32 scmd; ! 488: uint32 ints; ! 489: uint32 psns; ! 490: uint32 sdgc; ! 491: uint32 ssts; ! 492: uint32 serr; ! 493: uint32 pctl; ! 494: uint32 tc; ! 495: ! 496: monitor.Clear(); ! 497: ! 498: sctl = spc.sctl; ! 499: scmd = GetSCMD(); ! 500: ints = spc.ints; ! 501: psns = GetPSNS(); ! 502: sdgc = spc.sdgc; ! 503: ssts = GetSSTS(); ! 504: serr = spc.serr; ! 505: pctl = GetPCTL(); ! 506: tc = spc.tc; ! 507: ! 508: y = 0; ! 509: monitor.Print(0, y++, "BDID: $%02x (ID=%d)", (1 << myid), myid); ! 510: monitor.Print(0, y++, "SCTL: $%02x", sctl); ! 511: monitor.Print(0, y++, "SCMD: $%02x", scmd); ! 512: monitor.Print(0, y++, "INTS: $%02x", ints); ! 513: monitor.Print(0, y++, "PSNS: $%02x", psns); ! 514: monitor.Print(0, y++, "SDGC: $%02x", sdgc); ! 515: monitor.Print(0, y++, "SSTS: $%02x", ssts); ! 516: monitor.Print(0, y++, "SERR: $%02x", serr); ! 517: monitor.Print(0, y++, "PCTL: $%02x", pctl); ! 518: monitor.Print(0, y++, "MBC : $XX"); ! 519: monitor.Print(0, y, "DREG: "); ! 520: { ! 521: uint i; ! 522: for (i = 0; i < spc.dreg.Length(); i++) { ! 523: monitor.Print(6 + i * 4, y, "$%02x", spc.dreg.Peek(i)); ! 524: } ! 525: for (; i < 8; i++) { ! 526: monitor.Print(6 + i * 4, y, TA::Disable, "---"); ! 527: } ! 528: } ! 529: y++; ! 530: monitor.Print(0, y++, "TEMP: $%02x(IN) $%02x(OUT)", ! 531: spc.temp_in, spc.temp_out); ! 532: monitor.Print(0, y++, "TC : $%06x", tc); ! 533: ! 534: x = 10; ! 535: y = 1; ! 536: ! 537: // SCTL ! 538: static const char * const sctl_bits[] = { ! 539: "RST", "CTRL", "DIAG", "ARB", "PARI", "SEL", "RESL", "INTR" ! 540: }; ! 541: MonitorReg(x, y, sctl, sctl_bits); ! 542: y++; ! 543: ! 544: // SCMD ! 545: static const char * const scmd_bits[] = { ! 546: "", "", "", "RST", "INTC", "PROG", "", "TERM" ! 547: }; ! 548: MonitorReg(x, y, scmd, scmd_bits); ! 549: static const char * const scmd_cmd[] = { ! 550: //12345678901234 ! 551: "(Bus Release)", ! 552: "(Select)", ! 553: "(Reset ATN)", ! 554: "(Set ATN)", ! 555: "(Transfer)", ! 556: "(XferPause)", ! 557: "(ResetACK/REQ)", ! 558: "(Set ACK/REQ)", ! 559: }; ! 560: monitor.Print(x, y, "%s", scmd_cmd[scmd >> 5]); ! 561: y++; ! 562: ! 563: // INTS ! 564: static const char * const ints_bits[] = { ! 565: "Sel", "Resl", "Disc", "Cmpl", "SReq", "TOut", "HErr", "Rst" ! 566: }; ! 567: MonitorReg(x, y, ints, ints_bits); ! 568: y++; ! 569: ! 570: // PSNS ! 571: static const char * const psns_bits[] = { ! 572: "REQ", "ACK", "ATN", "SEL", "BSY", "MSG", "C/D", "I/O" ! 573: }; ! 574: MonitorReg(x, y, psns, psns_bits); ! 575: y++; ! 576: ! 577: // SDGC ! 578: static const char * const sdgc_bits[] = { ! 579: "", "", "XfEn", "", "", "", "", "" ! 580: }; ! 581: MonitorReg(x, y, sdgc, sdgc_bits); ! 582: y++; ! 583: ! 584: // SSTS ! 585: static const char * const ssts_bits[] = { ! 586: "", "", "SBSY", "XInP", "RSTI", "TCZ", "Full", "Empt" ! 587: }; ! 588: MonitorReg(x, y, ssts, ssts_bits); ! 589: static const char * const ssts_conn[] = { ! 590: //12345678 ! 591: "(NotConn)", ! 592: "(As Tgt)", ! 593: "(As Init)", ! 594: "(undef?)", ! 595: }; ! 596: monitor.Print(x, y, "%s", ssts_conn[ssts >> 6]); ! 597: y++; ! 598: ! 599: // SERR ! 600: static const char * const serr_bits[] = { ! 601: "", "", "XOut", "", "PErr", "", "Shrt", "" ! 602: }; ! 603: MonitorReg(x, y, serr, serr_bits); ! 604: y++; ! 605: ! 606: // PCTL ! 607: static const char * const pctl_bits[] = { ! 608: "BIE", "", "", "", "", "MSG", "C/D", "I/O" ! 609: }; ! 610: MonitorReg(x, y, pctl, pctl_bits); ! 611: monitor.Print(x + 15, y, "(%s)", SCSI::GetPhaseName(pctl & 7)); ! 612: y++; ! 613: ! 614: // バス状態 ! 615: x = 52; ! 616: y = 0; ! 617: // ここは bool を返す GetBSY(), GetSEL() ではなく、 ! 618: // 生の BSY, SEL 変数を見ること。 ! 619: uint8 bsy = BSY; ! 620: uint8 sel = SEL; ! 621: monitor.Print(x, y++, "RST :%d", GetRST()); ! 622: monitor.Print(x, y++, "BSY :%d(________)", bsy ? 1 : 0); ! 623: monitor.Print(x, y++, "SEL :%d(________)", sel ? 1 : 0); ! 624: monitor.Print(x, y++, "MSG :%d", GetMSG()); ! 625: monitor.Print(x, y++, "CD :%d", GetCD()); ! 626: monitor.Print(x, y++, "IO :%d", GetIO()); ! 627: monitor.Print(x, y++, "ATN :%d", GetATN()); ! 628: monitor.Print(x, y++, "REQ :%d", GetREQ()); ! 629: monitor.Print(x, y++, "ACK :%d", GetACK()); ! 630: monitor.Print(x, y++, "DATA:$%02x", GetDATA()); ! 631: ! 632: // BSY, SEL の内訳 ! 633: x = 59; ! 634: y = 1; ! 635: for (int i = 0; i < 8; i++) { ! 636: if ((bsy & (1 << i))) { ! 637: monitor.Print(x + i, y, "%d", i); ! 638: } else { ! 639: monitor.Print(x + i, y, "_"); ! 640: } ! 641: } ! 642: y++; ! 643: for (int i = 0; i < 8; i++) { ! 644: if ((sel & (1 << i))) { ! 645: monitor.Print(x + i, y, "%d", i); ! 646: } else { ! 647: monitor.Print(x + i, y, "_"); ! 648: } ! 649: } ! 650: ! 651: // ターゲット ! 652: x = 0; ! 653: y = 14; ! 654: monitor.Print(x, y, "Target"); ! 655: if (tgtid >= 0) { ! 656: monitor.Print(x + 6, y, ": #%d", tgtid); ! 657: } ! 658: y++; ! 659: ! 660: monitor.Print(x, y, "Phase: %s", SCSI::GetPhaseName(phase)); ! 661: y++; ! 662: ! 663: monitor.Print(x, y, "Command:"); ! 664: if (phase < 8 && cmds.size() > 0) { ! 665: const char *cmdname = SCSI::GetCommandName(cmds[0]); ! 666: if (cmdname) { ! 667: monitor.Print(x + 9, y, "%s", cmdname); ! 668: } else { ! 669: monitor.Print(x + 9, y, "$%02x", cmds[0]); ! 670: } ! 671: } ! 672: ! 673: return true; ! 674: } ! 675: ! 676: // レジスタをビットごとに表示する。MonitorUpdate の下請け。 ! 677: void ! 678: SPCDevice::MonitorReg(int x, int y, uint32 reg, const char * const *names) ! 679: { ! 680: for (int i = 0; i < 8; i++) { ! 681: bool b = reg & (1 << (7 - i)); ! 682: uint attr = b ? TA::On : TA::Off; ! 683: monitor.Print(x + i * 5, y, attr, "%s", names[i]); ! 684: } ! 685: } ! 686: ! 687: // SCTL レジスタへの書き込み ! 688: void ! 689: SPCDevice::WriteSCTL(uint32 data) ! 690: { ! 691: // リセットの立ち上がり検出が必要 ! 692: uint32 up = ~spc.sctl & data; ! 693: ! 694: spc.sctl = data; ! 695: putlog(3, "SCTL <- $%02x", spc.sctl); ! 696: if ((up & SPC::SCTL_RESET)) { ! 697: // リセット ! 698: Reset(); ! 699: } else { ! 700: // 通常動作 ! 701: } ! 702: if ((spc.sctl & SPC::SCTL_CTL_RESET)) { ! 703: putlog(2, "Control Reset"); ! 704: } ! 705: if ((spc.sctl & SPC::SCTL_DIAG_MODE)) { ! 706: putlog(0, "SCTL 未実装 Diag Mode"); ! 707: } ! 708: } ! 709: ! 710: // SCMD レジスタ値の取得 ! 711: // Read*() と Peek8() から呼ばれる。 ! 712: uint32 ! 713: SPCDevice::GetSCMD() const ! 714: { ! 715: uint32 data; ! 716: ! 717: data = spc.scmd & 0xef; ! 718: // XXX RST Out もここで読み出せるのかな? ! 719: if (GetRST()) { ! 720: data |= SPC::SCMD_RST_OUT; ! 721: } ! 722: ! 723: return data; ! 724: } ! 725: ! 726: // SCMD レジスタへの書き込み ! 727: void ! 728: SPCDevice::WriteSCMD(uint32 data) ! 729: { ! 730: spc.scmd = data; ! 731: putlog(3, "SCMD <- $%02x", spc.scmd); ! 732: ! 733: // RST Out 操作は SCTL Reset がオフの時だけ有効 ! 734: if ((spc.sctl & SPC::SCTL_RESET) == 0) { ! 735: SetRST(spc.scmd & SPC::SCMD_RST_OUT); ! 736: ! 737: if (GetRST() == true) { ! 738: putlog(2, "SCSIBus バスリセット"); ! 739: ! 740: // 即イベント発生させる? ! 741: SetINTS(SPC::INTS_RESET_COND); ! 742: ! 743: // バスフリーフェーズ ! 744: BusFree(); ! 745: } ! 746: } ! 747: ! 748: switch (spc.scmd >> 5) { ! 749: case 0: ! 750: putlog(2, "SCMD Bus Release コマンド"); ! 751: BusFree(); ! 752: break; ! 753: case 1: ! 754: SelectCommand(); ! 755: break; ! 756: case 2: ! 757: putlog(0, "SCMD Reset ATN コマンド (未実装)"); ! 758: break; ! 759: case 3: ! 760: putlog(2, "SCMD Set ATN コマンド (未実装)"); ! 761: break; ! 762: case 4: ! 763: TransferCommand(); ! 764: break; ! 765: case 5: ! 766: putlog(2, "SCMD Transfer Pause コマンド"); ! 767: break; ! 768: case 6: ! 769: putlog(2, "SCMD Reset ACK/REQ コマンド"); ! 770: // イニシエータなら ACK を、ターゲットなら REQ をリセットする。 ! 771: // ターゲットモードはサポートしないので、操作するのは ACK のみ。 ! 772: spc.ackreq = false; ! 773: SetACK(false); ! 774: break; ! 775: case 7: ! 776: putlog(2, "SCMD Set ACK/REQ コマンド"); ! 777: // イニシエータなら ACK を、ターゲットなら REQ をセットする。 ! 778: // ターゲットモードはサポートしないので、操作するのは ACK のみ。 ! 779: spc.ackreq = true; ! 780: SetACK(true); ! 781: if (1) { ! 782: target[tgtid]->Ack(); ! 783: } ! 784: break; ! 785: } ! 786: } ! 787: ! 788: // INTS レジスタへの書き込み ! 789: void ! 790: SPCDevice::WriteINTS(uint32 data) ! 791: { ! 792: uint32 ack; ! 793: ! 794: // クリアすることになるビットを覚えておく ! 795: ack = spc.ints & data; ! 796: ! 797: // '1' が書き込まれたビットをクリア ! 798: spc.ints &= ~data; ! 799: ! 800: // SPC では INTS でビットを落とす行為は、 ! 801: // ホストが割り込み要因に対して処理を完了したことを ! 802: // SPC に通知する意味が含まれている。 ! 803: ! 804: // DISCONNECTED ビットが立っているときは SPC は SCSI から ! 805: // セレクション、リセレクション要求が来ても応答しない。 ! 806: // DISCONNECTED ビットが下ろされると、応答するようになる。 ! 807: ! 808: // TIMEOUT ビットをクリアした時が ! 809: if ((ack & SPC::INTS_TIMEOUT)) { ! 810: // セレクションフェーズなら ! 811: if (GetBSY() == false && GetSEL() == true) { ! 812: // TC がゼロならバスフリー ! 813: // そうでなければリスタート ! 814: if (spc.tc == 0) { ! 815: BusFree(); ! 816: } else { ! 817: event.time = spc.tc * t_CLF * 2; ! 818: event.name = "SPC SelFailed"; ! 819: event.func = ! 820: (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; ! 821: gScheduler->AddEvent(&event); ! 822: } ! 823: } ! 824: } ! 825: ! 826: putlog(3, "INTS = $%02x", spc.ints); ! 827: } ! 828: ! 829: // INTS 設定 ! 830: // INTS レジスタにビットを立てる要因になる事象が起きた時にコールする。 ! 831: // INTS を書き換えて、必要なら割り込みを上げる。 ! 832: // INTS レジスタへの書き込みではないので注意。 ! 833: void ! 834: SPCDevice::SetINTS(uint32 data) ! 835: { ! 836: // 立ち上がりでだけ割り込みがかかるわけではないが表示のため。 ! 837: uint32 up = ~spc.ints & data; ! 838: ! 839: // 指定された要因を立てる ! 840: spc.ints |= data; ! 841: ! 842: // 割り込み許可なら割り込み。 ! 843: // RESET_COND はノンマスカブル割り込み。 ! 844: if ((spc.sctl & SPC::SCTL_INTR_EN) || (spc.ints & SPC::INTS_RESET_COND)) { ! 845: putlog(3, "割り込み ints=%x up=%x", spc.ints, up); ! 846: if (gVM->GetType() == VM::TYPE_LUNA) { ! 847: gMPU->Interrupt(2); ! 848: } else { ! 849: // とりあえず内蔵だけ ! 850: gMPU->Interrupt(1, 0x6c); ! 851: } ! 852: } ! 853: } ! 854: ! 855: // PSNS レジスタ値の取得。 ! 856: // Read*() と Peek8() から呼ばれる。 ! 857: uint32 ! 858: SPCDevice::GetPSNS() const ! 859: { ! 860: uint32 data; ! 861: ! 862: data = spc.psns & 0xe0; ! 863: if (GetREQ()) { ! 864: data |= SPC::PSNS_REQ; ! 865: } ! 866: if (GetACK()) { ! 867: data |= SPC::PSNS_ACK; ! 868: } ! 869: if (GetATN()) { ! 870: data |= SPC::PSNS_ATN; ! 871: } ! 872: if (GetSEL()) { ! 873: data |= SPC::PSNS_SEL; ! 874: } ! 875: if (GetBSY()) { ! 876: data |= SPC::PSNS_BSY; ! 877: } ! 878: if (GetMSG()) { ! 879: data |= SPC::PSNS_MSG; ! 880: } ! 881: if (GetCD()) { ! 882: data |= SPC::PSNS_CD; ! 883: } ! 884: if (GetIO()) { ! 885: data |= SPC::PSNS_IO; ! 886: } ! 887: ! 888: return data; ! 889: } ! 890: ! 891: // SSTS レジスタ値の取得。 ! 892: // Read*() と Peek8() から呼ばれる。 ! 893: uint32 ! 894: SPCDevice::GetSSTS() const ! 895: { ! 896: uint32 data; ! 897: ! 898: data = spc.ssts & 0xf0; ! 899: if (GetRST()) { // RST 信号 ! 900: data |= SPC::SSTS_RST_IN; ! 901: } ! 902: if (spc.tc == 0) { // TC がゼロか ! 903: data |= SPC::SSTS_TC_ZERO; ! 904: } ! 905: if (spc.dreg.Length() == 8) { ! 906: data |= SPC::SSTS_DREG_FULL; ! 907: } ! 908: if (spc.dreg.Length() == 0) { ! 909: data |= SPC::SSTS_DREG_EMPTY; ! 910: } ! 911: ! 912: return data; ! 913: } ! 914: ! 915: // PCTL レジスタ値の取得。 ! 916: // Read*() と Peek8() から呼ばれる。 ! 917: uint32 ! 918: SPCDevice::GetPCTL() const ! 919: { ! 920: uint32 data; ! 921: ! 922: data = 0; ! 923: if (spc.busfree_intr_enable) { ! 924: data |= SPC::PCTL_BFINT_EN; ! 925: } ! 926: if (GetMSG()) { ! 927: data |= SPC::PCTL_MSG; ! 928: } ! 929: if (GetCD()) { ! 930: data |= SPC::PCTL_CD; ! 931: } ! 932: if (GetIO()) { ! 933: data |= SPC::PCTL_IO; ! 934: } ! 935: ! 936: return data; ! 937: } ! 938: ! 939: // バスフリーフェーズに移行する ! 940: void ! 941: SPCDevice::BusFree() ! 942: { ! 943: // バスの状態を変える ! 944: B_BusFree(myid); ! 945: ! 946: // SCSI 仕様は BSY 下ろしてから最大 1200ns 以内に他の信号も下ろす ! 947: // SPC 仕様は BSY 下ろしてから最大 (6*t_CLF + 75) ns 以内に ! 948: // INTR が上がる。 ! 949: // ここでは適当に 600ns とする。 ! 950: event.time = 600; ! 951: event.name = "SPC BusFree"; ! 952: event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; ! 953: event.code = 0; // SPC からのバスフリー ! 954: gScheduler->AddEvent(&event); ! 955: } ! 956: ! 957: // ターゲットがバスフリーフェーズに移行した ! 958: void ! 959: SPCDevice::BusRelease() ! 960: { ! 961: putlog(3, "Target BusRelease"); ! 962: ! 963: // XXX SPC が SCSI バスからのバスフリーをどのぐらいの時間で ! 964: // 検出するのか調べていない。後で調べる ! 965: // ここでは適当に 600ns とする。 ! 966: event.time = 600; ! 967: event.name = "SPC BusFree"; ! 968: event.func = (DeviceCallback_t)&SPCDevice::BusFreeCallback; ! 969: event.code = 1; // SCSI バスからのバスフリー ! 970: gScheduler->AddEvent(&event); ! 971: } ! 972: ! 973: // Select コマンド実行 ! 974: void ! 975: SPCDevice::SelectCommand() ! 976: { ! 977: std::string idstr; ! 978: uint32 data; ! 979: uint64 wait; ! 980: uint64 now; ! 981: ! 982: // ターゲット ID を求めつつ、表示用の文字列を作成 ! 983: tgtid = -1; ! 984: data = spc.temp_out; ! 985: if (data == 0) { ! 986: // ID が一切立ってない? ! 987: idstr = "TEMP==0; No IDs?"; ! 988: } else if ((data & (1 << myid)) == 0) { ! 989: // 自 ID が立ってない? ! 990: idstr = string_format("TEMP=$%02x; No BDID?", data); ! 991: } else { ! 992: // ここで少なくとも自 ID は立っている ! 993: data &= ~(1 << myid); ! 994: ! 995: if (data == 0) { ! 996: // ターゲット ID がない ! 997: idstr = string_format("#%d", myid); ! 998: } else { ! 999: tgtid = 31 - __builtin_clz(data); ! 1000: idstr = string_format("#%d -> #%d", myid, tgtid); ! 1001: } ! 1002: } ! 1003: putlog(2, "Select コマンド (%s)", idstr.c_str()); ! 1004: phase = SCSI::Phase::Selection; ! 1005: ! 1006: // アービトレーションなしの場合 ! 1007: // ! 1008: // BSY X\_______________ .. _/~~~~~~~~~~~~~~~ ! 1009: // |t_FRID| |t_BIDH| ! 1010: // DB* ________<XXXXXXXX .. XXXXXXXX><XXXXXXX ! 1011: // |t_IDSL| |t_BLSH| ! 1012: // SEL _______________/~ .. ~~~~~~~~\________ ! 1013: // |t_SHIR| ! 1014: // INTR _________________ .. _______________/~ ! 1015: // ! 1016: // t_FRID = <Min> (6 + TCL) * t_CLF, <Max> (7 + TCL) * t_CLF + 140 ! 1017: // t_IDSL = <Min> 11 * t_CLF - 80 ! 1018: // t_BIDH = <Min> t_BLSH = 2 * t_CLF ! 1019: // t_SHIR = <Max> 60 ! 1020: // ! 1021: // アービトレーションありの場合 ! 1022: // ! 1023: // BSY X\______/~~~~~~~~~~~~~~~~~~~~~~~~~~\_ .. _/^^^^^^^^^^^^^^^ ! 1024: // |t_BFBL| | |t_BIDH| ! 1025: // DB* ________<XXXXXXXXXXXXXXXXXXXXXXXXXXXX .. XXXXXXXX>________ ! 1026: // |t_ARB|t_AWSL| | |t_BLSH| ! 1027: // SEL _____________________/~~~~~~~~~~~~~~~ .. ~~~~~~~~\________ ! 1028: // |t_SIDA|t_IDBH| | ! 1029: // ATN ____________________________/~~~~~~~~ .. ~~~~~~~~~~~~~~~~~ ! 1030: // |t_SHIR| ! 1031: // INTR _____________________________________ .. _______________/~ ! 1032: // ! 1033: // t_BFBL = <Min> (6 + TCL) * t_CLF, <Max> (7 + TCL) * t_CLF + 60 ! 1034: // t_ARB = <Min> 32 * t_CLF - 60 ! 1035: // t_AWSL = <Min> 0, <Max> 80 ! 1036: // t_SIDA = <Min> 11 * t_CLF - 30 ! 1037: // t_IDBH = <Min> 2 * t_CLF - 80 ! 1038: ! 1039: // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 ! 1040: // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 ! 1041: uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min ! 1042: now = gScheduler->GetVirtTime(); ! 1043: if ((BusPhaseTime + t_BFBL) > now) { ! 1044: wait = (BusPhaseTime + t_BFBL) - now; ! 1045: } else { ! 1046: wait = 0; ! 1047: } ! 1048: ! 1049: spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); ! 1050: spc.ssts |= SPC::SSTS_SPC_BUSY; ! 1051: ! 1052: // アービトレーションフェーズ ! 1053: if ((spc.sctl & SPC::SCTL_ARBIT_EN)) { ! 1054: // アービトレーションありの場合 ! 1055: Arbitration(myid); ! 1056: ! 1057: // 実際にはここで所定の時間経過後に優先度のチェックを行って ! 1058: // アービトレーションの勝者を決めるのだが、ここでは複数のデバイスが ! 1059: // 同時にアービトレーションフェーズに入ることはないため、 ! 1060: // アービトレーションを開始したデバイスが必ずバスの使用権を得る。 ! 1061: ! 1062: ArbitrationAck(myid); ! 1063: ! 1064: uint64 t_ARB = 32 * t_CLF - 60; ! 1065: uint64 t_AWSL = 40; // 適当に中間 ! 1066: uint64 t_SIDA = 11 * t_CLF - 30; ! 1067: uint64 t_IDBH = 2 * t_CLF - 80; ! 1068: wait += t_ARB + t_AWSL + t_SIDA + t_IDBH; ! 1069: } else { ! 1070: // アービトレーションなしの場合の相当箇所までの時間 ! 1071: uint64 t_IDSL = 11 * t_CLF - 80; ! 1072: wait += t_IDSL; ! 1073: } ! 1074: ! 1075: // 後半の時間もここで足しておく ! 1076: uint64 t_BLSH = 2 * t_CLF; ! 1077: uint64 t_SHIR = 60; ! 1078: wait += t_BLSH + t_SHIR; ! 1079: ! 1080: // セレクションフェーズ ! 1081: spc.ssts |= SPC::SSTS_CONN_INIT; ! 1082: InitiatorSelection(tgtid); ! 1083: ! 1084: if (tgtid >= 0 && target[tgtid] != NULL) { ! 1085: // セレクション成功 ! 1086: ! 1087: // XXX 途中のターゲットが応答するための時間が抜けているけど ! 1088: event.time = wait; ! 1089: event.name = "SPC SelComplete"; ! 1090: event.func = (DeviceCallback_t)&SPCDevice::SelectionCompleteCallback; ! 1091: } else { ! 1092: // セレクション失敗 ! 1093: ! 1094: uint64 timeout = ((spc.tc & 0xffff00) + 15) * t_CLF * 2; ! 1095: event.time = wait + timeout; ! 1096: event.name = "SPC SelFailed"; ! 1097: event.func = (DeviceCallback_t)&SPCDevice::SelectionFailedCallback; ! 1098: } ! 1099: gScheduler->AddEvent(&event); ! 1100: } ! 1101: ! 1102: // Transfer コマンド実行 ! 1103: void ! 1104: SPCDevice::TransferCommand() ! 1105: { ! 1106: uint32 xfer_phase = GetPCTL() & 7; ! 1107: putlog(2, "SCMD Transfer Command (xfer=%s len=%d)", ! 1108: SCSI::GetPhaseName(xfer_phase), spc.tc); ! 1109: ! 1110: // Transfer コマンド発行時に REQ がアサートされていれば、SCSI バスの ! 1111: // 状態と PCTL レジスタの下位3ビットの状態を比較する。一致していれば ! 1112: // 転送、そうでなければ Service Required 割り込み。 ! 1113: // XXX Transfer コマンド発行時に REQ がアサートされていなければ、 ! 1114: // アサートされるまで待つ。 ! 1115: uint32 stat = 0; ! 1116: if (GetMSG()) { ! 1117: stat |= SPC::PCTL_MSG; ! 1118: } ! 1119: if (GetCD()) { ! 1120: stat |= SPC::PCTL_CD; ! 1121: } ! 1122: if (GetIO()) { ! 1123: stat |= SPC::PCTL_IO; ! 1124: } ! 1125: if (xfer_phase != stat) { ! 1126: SetINTS(SPC::INTS_SERV_REQ); ! 1127: return; ! 1128: } ! 1129: ! 1130: // 転送開始前に DREG キューを初期化する。 ! 1131: // MPU 側が DREG を全部読まずにフェーズを中断とかは別に出来るので。 ! 1132: spc.dreg.Clear(); ! 1133: ! 1134: // 転送開始 ! 1135: phase = xfer_phase; ! 1136: spc.ssts |= SPC::SSTS_SPC_BUSY; ! 1137: switch (xfer_phase) { ! 1138: case SCSI::Phase::Command: ! 1139: // この後 DREG にコマンドが書き込まれる ! 1140: cmds.clear(); ! 1141: // XXX どこで初期化する? ! 1142: datalen = 0; ! 1143: break; ! 1144: ! 1145: case SCSI::Phase::DataIn: ! 1146: datalen = target[tgtid]->ExecDataIn((const uint8 **)&dataptr); ! 1147: // 最初の1バイトはここで DREG に入れておかないと SSTS が変化しない。 ! 1148: EnqueueToDREG(); ! 1149: break; ! 1150: ! 1151: case SCSI::Phase::DataOut: ! 1152: datalen = target[tgtid]->ExecDataOut(&dataptr); ! 1153: // データアウトの時は DREG へ書き込まれるのを待つだけなので ! 1154: // ここでは何もしなくていい。 ! 1155: break; ! 1156: ! 1157: case SCSI::Phase::Status: ! 1158: // ステータスバイトを取り込む。 ! 1159: // 1バイトなので必ず COMPLETE になる。 ! 1160: spc.dreg.Enqueue(target[tgtid]->GetStatusByte()); ! 1161: break; ! 1162: ! 1163: case SCSI::Phase::MsgIn: ! 1164: // メッセージバイトを取り込む。 ! 1165: // 1バイトなので必ず COMPLETE になる。 ! 1166: spc.dreg.Enqueue(target[tgtid]->GetMessageByte()); ! 1167: ! 1168: break; ! 1169: } ! 1170: ! 1171: // XXX どこでやる? ! 1172: // SDGC_XFER_ENABLE なら転送要求ごとに割り込みを上げる。 ! 1173: // この場合の割り込み要因は SERR レジスタに出るようだ。 ! 1174: if ((spc.sdgc & SPC::SDGC_XFER_ENABLE)) { ! 1175: spc.serr |= SPC::SERR_XFER_OUT; ! 1176: putlog(3, "SDGC XFER_OUT Interrupt"); ! 1177: SetINTS(0); ! 1178: } ! 1179: } ! 1180: ! 1181: // ターゲットから受信したデータ (dataptr, datalen) を DREG キューに入れる。 ! 1182: // 空いてる限り常に埋まるまで入れる。 ! 1183: // データが最初から空なら何もしない。 ! 1184: void ! 1185: SPCDevice::EnqueueToDREG() ! 1186: { ! 1187: while (datalen > 0 && spc.dreg.Enqueue(*dataptr)) { ! 1188: dataptr++; ! 1189: datalen--; ! 1190: } ! 1191: } ! 1192: ! 1193: // バスフリーフェーズに移行完了のイベントコールバック ! 1194: // arg: 要因 0=SPC 1=SCSI ! 1195: void ! 1196: SPCDevice::BusFreeCallback(int arg) ! 1197: { ! 1198: putlog(2, "SCSIBus バスフリー"); ! 1199: phase = SCSI::Phase::BusFree; ! 1200: ! 1201: // SPC ビジーとかを下げる ! 1202: spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG | ! 1203: SPC::SSTS_SPC_BUSY); ! 1204: spc.ssts &= ~SPC::SSTS_IN_PROGRESS; ! 1205: ! 1206: // マニュアルが分かりにくいんだけど、 ! 1207: // o. イニシエータで SPC がバスフリーにしたとき ! 1208: // o. BusFree INT enable ビットが立っていてターゲットからのバスフリー ! 1209: // のいずれかで INTS のバスフリー検出ビットを立てる ! 1210: if (arg || spc.busfree_intr_enable) { ! 1211: SetINTS(SPC::INTS_DISCONNECTED); ! 1212: } ! 1213: } ! 1214: ! 1215: // 所定時間経過後セレクションに成功のイベントコールバック ! 1216: void ! 1217: SPCDevice::SelectionCompleteCallback(int arg) ! 1218: { ! 1219: // ほんとは信号線ごとに時間が進むのだが、全部一度に変化する :) ! 1220: TargetSelectionAck(tgtid); ! 1221: InitiatorSelectionAck(); ! 1222: target[tgtid]->Selected(); ! 1223: ! 1224: spc.ssts &= ~SPC::SSTS_SPC_BUSY; ! 1225: spc.ssts |= SPC::SSTS_IN_PROGRESS; ! 1226: SetINTS(SPC::INTS_COMPLETE); ! 1227: } ! 1228: ! 1229: // 所定時間経過後セレクションに失敗のイベントコールバック ! 1230: void ! 1231: SPCDevice::SelectionFailedCallback(int arg) ! 1232: { ! 1233: if (loglevel >= 1) { ! 1234: if (tgtid == -1) { ! 1235: putlog(2, "セレクションタイムアウト"); ! 1236: } else { ! 1237: putlog(2, "セレクションタイムアウト (#%d)", tgtid); ! 1238: } ! 1239: } ! 1240: spc.ssts &= ~SPC::SSTS_SPC_BUSY; ! 1241: // ほんとは TC は減算カウンタになってるんだが ! 1242: spc.tc = 0; ! 1243: SetINTS(SPC::INTS_TIMEOUT); ! 1244: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.