Annotation of nono/vm/romemu_luna.cpp, revision 1.1.1.11

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.7   root        7: // LUNA シリーズの ROM エミュレーション共通部分
1.1.1.10  root        8: //
                      9: // IODevice
                     10: //    |
                     11: //    +-- ROMDevice (LoadROM()、ウェイト、マスク等を持つ)
                     12: //    |     |
                     13: //    |     +-- PROMDevice    (LUNA* の PROM)
                     14: //    |     +-- IPLROM1Device (X680x0 の IPLROM 後半)
                     15: //    |     +-- IPLROM2Device (X680x0 の IPLROM 前半)
                     16: //    |     +-- CGROMDevice   (X680x0 の CGROM)
                     17: //    |     |
                     18: //    |     |  +-------------------+
                     19: //    |     +--| LunaPROMEmuDevice | (LUNA PROM エミュレーションの共通部分)
                     20: //    |        +-------------------+
                     21: //    |           |
                     22: //    |           +-- Luna1PROMEmuDevice   (LUNA1 の PROM エミュレーション)
                     23: //    |           +-- Luna88kPROMEmuDevice (LUNA88K の PROM エミュレーション)
                     24: //    |
                     25: //    +-- PROM0Device   (LUNA* のブートページ切り替え用プロキシ)
                     26: //    +-- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ)
1.1.1.2   root       27: 
                     28: #include "romemu_luna.h"
1.1       root       29: #include "bt454.h"
1.1.1.9   root       30: #include "lunafb.h"
1.1       root       31: #include "mainapp.h"
                     32: #include "mk48t02.h"
                     33: #include "ram.h"
                     34: #include "sio.h"
                     35: #include "spc.h"
1.1.1.7   root       36: #include "ufs.h"
1.1       root       37: 
                     38: // コンストラクタ
                     39: LunaPROMEmuDevice::LunaPROMEmuDevice()
1.1.1.9   root       40:        : inherited("PROM")
1.1       root       41: {
1.1.1.10  root       42:        // LUNA では ROM への書き込みは何も起きない
                     43:        write_op = 0;
1.1       root       44: }
                     45: 
                     46: // デストラクタ
                     47: LunaPROMEmuDevice::~LunaPROMEmuDevice()
                     48: {
                     49: }
                     50: 
                     51: bool
                     52: LunaPROMEmuDevice::Init()
                     53: {
                     54:        // 都度都度 dynamic_cast は面倒なので、ここで一度代入しとく
                     55:        nvram = dynamic_cast<MK48T02Device*>(gRTC.get());
                     56: 
                     57:        return true;
                     58: }
                     59: 
1.1.1.2   root       60: uint64
1.1       root       61: LunaPROMEmuDevice::Read32(uint32 addr)
                     62: {
1.1.1.10  root       63:        // ROM からのロングワードアクセスでだけ謎の I/O 空間が見える。
                     64:        // addr をマスクする前に評価すること。
1.1.1.9   root       65:        if ((gMPU->GetPaddr() & 0xff000000) == baseaddr) {
                     66:                switch (addr) {
                     67:                 case ROMIO_INIT:
1.1.1.2   root       68:                        return ROM_Init();
1.1       root       69: 
1.1.1.9   root       70:                 case ROMIO_KEYIN:
1.1.1.7   root       71:                        ROM_Keyin();
                     72:                        return 0;
1.1       root       73: 
1.1.1.9   root       74:                 case ROMIO_CLOSE:
1.1.1.7   root       75:                        ROM_Close();
                     76:                        return 0;
1.1       root       77: 
1.1.1.9   root       78:                 case ROMIO_ENTRY:
1.1.1.7   root       79:                        return ROM_Entry();
1.1.1.6   root       80: 
1.1       root       81:                 default:
                     82:                        break;
                     83:                }
                     84:        }
1.1.1.2   root       85: 
1.1.1.10  root       86:        return inherited::Read32(addr);
1.1       root       87: }
                     88: 
1.1.1.7   root       89: // デバイスの初期化の共通部分。(継承クラスの ROM_Init() から呼ぶ)
                     90: void
                     91: LunaPROMEmuDevice::InitDevice()
1.1       root       92: {
                     93:        // 初期パレット
1.1.1.4   root       94:        // 偶数番は白(R/G/B=15/15/15)、奇数番は黒(0/0/0)。
                     95:        uint8 val = 0xf0;
1.1.1.7   root       96:        gBT454->Write(0, 0);    // select palette
1.1.1.4   root       97:        for (int i = 0; i < 16; i++) {
1.1.1.7   root       98:                gBT454->Write(1, val);  // R
                     99:                gBT454->Write(1, val);  // G
                    100:                gBT454->Write(1, val);  // B
1.1.1.4   root      101:                val ^= 0xf0;
                    102:        }
1.1       root      103: 
                    104:        // SCSI
                    105:        // BDID の書き込みによってホストデバイスがバスにアタッチされる構造なので。
1.1.1.7   root      106:        gSPC->Write(SPC::BDID, 7);
1.1       root      107: 
1.1.1.7   root      108:        // キーボード(chB)を初期化
                    109:        gSIO->Write(3, 0x01);           // CR1
                    110:        gSIO->Write(3, 0x10);           //    RX Int(all char)
                    111:        gSIO->Write(3, 0x03);           // CR3
                    112:        gSIO->Write(3, 0xc1);           //    RX Enable, 8bit
                    113:        gSIO->Write(3, 0x04);           // CR4
                    114:        gSIO->Write(3, 0x44);           //    Stop 1bit
                    115:        gSIO->Write(3, 0x05);           // CR5
                    116:        gSIO->Write(3, 0x68);           //    TX Enable, 8bit
                    117: }
1.1.1.2   root      118: 
1.1.1.7   root      119: // スクリーンを初期化
                    120: void
                    121: LunaPROMEmuDevice::InitScreen(const char *machine_name)
                    122: {
1.1.1.9   root      123:        text.Init(80, 32, TextScreen::Console);
                    124:        gLunafb->EmuConsoleOpen(&text);
1.1.1.2   root      125:        inputbuf.clear();
                    126:        inputpos = 0;
                    127:        is_shift = false;
1.1.1.3   root      128:        mousecnt = 0;
1.1       root      129: 
1.1.1.7   root      130:        text.Print(0, 0, "NONO %d.%d.%d Emulated ROM Monitor for %s",
                    131:                NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, machine_name);
1.1       root      132:        text.Locate(0, 2);
                    133: }
                    134: 
1.1.1.7   root      135: // ROM 処理のクローズ
                    136: void
1.1.1.2   root      137: LunaPROMEmuDevice::ROM_Close()
1.1       root      138: {
1.1.1.11! root      139:        // SIO をリセットしておく。
        !           140:        // SIO のバッファにデータが残っていると、NetBSD のブートローダが
        !           141:        // 起動してこないようだ。リセットして RXEN を落としておけば大丈夫ぽい。
        !           142:        gSIO->ResetHard();
1.1.1.7   root      143: 
1.1       root      144:        // コンソールクローズ
1.1.1.9   root      145:        gLunafb->EmuConsoleClose();
1.1       root      146: }
                    147: 
1.1.1.2   root      148: // ROM プロンプトのキー入力。
1.1.1.7   root      149: // ゲストコードの割り込みハンドラから呼ばれて、キー入力で行を構成し、
                    150: // あるいは行が完成すればコマンドを実行する。
                    151: // 実行結果により画面を更新したりメンバ変数を更新する。
                    152: void
1.1.1.2   root      153: LunaPROMEmuDevice::ROM_Keyin()
1.1       root      154: {
1.1.1.7   root      155:        uint32 lunakey = gSIO->Read(2);
1.1       root      156: 
1.1.1.3   root      157:        // マウス入力の 2, 3バイト目は無視
                    158:        if (mousecnt > 0) {
                    159:                mousecnt--;
1.1.1.7   root      160:                return;
1.1.1.3   root      161:        }
                    162: 
1.1.1.2   root      163:        // SHIFT キーだけ状態を持つので先に処理
                    164:        if (lunakey == 0x0c || lunakey == 0x0d) {       // 左右SHIFT押下
                    165:                is_shift = true;
1.1.1.7   root      166:                return;
1.1.1.2   root      167:        }
                    168:        if (lunakey == 0x8c || lunakey == 0x8d) {       // 左右SHIFT開放
1.1       root      169:                is_shift = false;
1.1.1.7   root      170:                return;
1.1.1.2   root      171:        }
1.1.1.3   root      172:        // マウスデータの1バイト目なら、後続2バイトをスキップ
                    173:        if ((lunakey & 0xf8) == 0x80) {
                    174:                mousecnt = 2;
1.1.1.7   root      175:                return;
1.1.1.3   root      176:        }
                    177:        // キー開放は無視してよい
1.1.1.2   root      178:        if ((lunakey & 0x80)) {
1.1.1.7   root      179:                return;
1.1.1.2   root      180:        }
1.1       root      181: 
1.1.1.9   root      182:        // キーコードから文字を取得
                    183:        char asciicode;
                    184:        if (is_shift) {
                    185:                asciicode = lunakey2shifttable[lunakey];
                    186:        } else {
                    187:                asciicode = lunakey2asciitable[lunakey];
                    188:        }
                    189:        if (asciicode != 0) {
                    190:                GetChar(asciicode);
                    191:        }
                    192: }
                    193: 
                    194: // キー入力ハンドラ (キー入力割り込みによって呼ばれる)。
                    195: // 引数 asciicode は概ね ASCII コード (ただし 0x1c..0x1f が上下左右)。
                    196: void
                    197: LunaPROMEmuDevice::GetChar(char asciicode)
                    198: {
                    199:        // LUNA1 の場合、
                    200:        // 実機 PROM にはカーソル左右移動の機能はなく、カーソルは常に行末にいる。
                    201:        // そのためかどうか Delete も BackSpace と同じく1文字前を消す動作をして
                    202:        // いる。カーソル位置に文字はないので、Delete をそうするのはまあ分からん
                    203:        // でもない。
                    204:        // 一方、エミュレーション ROM では、カーソルの左右移動をサポートしてみた
                    205:        // ため Delete を「カーソル位置の文字を消す」にすることは出来るが、それは
                    206:        // それで実機と乖離が広がるので、どうしたもんか。
                    207: 
                    208:        switch (asciicode) {
                    209:         case BS:
                    210:                if (inputbuf.empty() == false && inputpos != 0) {
                    211:                        inputbuf.erase(--inputpos, 1);
1.1       root      212:                }
1.1.1.2   root      213:                break;
1.1.1.9   root      214: 
                    215:         case LF:
                    216:                // 改行を起こす前にカーソル(ただの反転)を戻しておく必要がある
1.1.1.8   root      217:                text.SetAttr(TA::Normal);
1.1.1.2   root      218:                // 改行
1.1.1.9   root      219:                text.Putc(asciicode);
                    220:                prompt_y = text.GetY();
                    221:                // コマンド実行
1.1.1.7   root      222:                Command();
1.1.1.9   root      223:                // プロンプトを更新
                    224:                ClearPrompt();
1.1.1.2   root      225:                break;
1.1.1.9   root      226: 
                    227:         case LEFT:
1.1.1.2   root      228:                if (inputpos > 0) {
                    229:                        inputpos--;
1.1       root      230:                }
1.1.1.2   root      231:                break;
1.1.1.9   root      232:         case RIGHT:
1.1.1.2   root      233:                if (inputpos < inputbuf.length()) {
1.1       root      234:                        inputpos++;
                    235:                }
                    236:                break;
1.1.1.9   root      237: 
                    238:         case UP:
                    239:         case DOWN:
                    240:         case 0:        // キー割り当てなし、これは来ないはずだが一応
                    241:                return;
                    242: 
                    243:         default:       // 通常文字のはず
1.1.1.2   root      244:                inputbuf.insert(inputbuf.begin() + inputpos, asciicode);
                    245:                inputpos++;
                    246:                break;
1.1       root      247:        }
1.1.1.2   root      248:        ConsoleUpdate();
                    249: }
1.1       root      250: 
1.1.1.9   root      251: // 入力バッファを初期化する。
                    252: void
                    253: LunaPROMEmuDevice::ClearPrompt()
                    254: {
                    255:        prompt_y = text.GetY();
                    256:        // 入力行をクリア
                    257:        inputbuf.clear();
                    258:        inputpos = 0;
                    259: }
                    260: 
1.1.1.2   root      261: // 画面を更新
                    262: void
                    263: LunaPROMEmuDevice::ConsoleUpdate()
                    264: {
1.1.1.9   root      265:        int x;
                    266:        int y;
                    267: 
                    268:        // カーソルを描画する予定の行が最下行を越えていたらスクロール
                    269:        int len = prompt.length() + inputpos;
                    270:        y = prompt_y + (len / text.GetCol());
                    271:        if (y >= text.GetRow()) {
                    272:                text.ScrollUp();
                    273:                prompt_y--;
                    274:        }
                    275: 
                    276:        // プロンプト行から下を一旦全部クリア
                    277:        // (手抜きだが、見た目問題ないはず)
                    278:        text.Mode = TextScreen::Fixed;
                    279:        for (y = prompt_y; y < text.GetRow(); y++) {
                    280:                text.Locate(0, y);
                    281:                for (x = 0; x < text.GetCol(); x++) {
                    282:                        text.Putc(' ');
                    283:                }
                    284:        }
                    285:        text.Locate(0, prompt_y);
                    286:        text.Mode = TextScreen::Console;
                    287: 
                    288:        // プロンプトと現在の入力行を出力
                    289:        text.Puts(prompt.c_str());
                    290:        text.Puts(inputbuf.c_str());
                    291: 
1.1.1.8   root      292:        // カーソルを描画
1.1.1.9   root      293:        y = prompt_y + (len / text.GetCol());
                    294:        x = len % text.GetCol();
                    295:        putmsg(2, "|%s|%s| pos=%d prompt_y=%d cur=(%d,%d)",
                    296:                prompt.c_str(), inputbuf.c_str(), inputpos, prompt_y, x, y);
                    297:        text.Locate(x, y);
1.1.1.8   root      298:        text.SetAttr(TA::On);
1.1.1.9   root      299: 
1.1       root      300:        // 画面更新
1.1.1.9   root      301:        gLunafb->EmuConsoleUpdate();
                    302: }
                    303: 
                    304: // NVRAM のチェックサムを返す
                    305: uint8
                    306: LunaPROMEmuDevice::CalcNVRAMCsum() const
                    307: {
                    308:        uint8 eor = 0;
                    309:        for (uint32 addr = 0x20; addr < 0x560; addr++) {
                    310:                eor ^= nvram->Peek(addr);
                    311:        }
                    312:        return eor;
                    313: }
                    314: 
                    315: // NVRAM のチェックサムを計算して書き込む
                    316: void
                    317: LunaPROMEmuDevice::WriteNVRAMCsum()
                    318: {
                    319:        uint8 eor = CalcNVRAMCsum();
                    320: 
                    321:        nvram->Write(0x001c, eor);
                    322:        nvram->Write(0x001d, eor);
                    323:        nvram->Write(0x001e, eor);
                    324: }
                    325: 
                    326: // NVRAM のマジックとチェックサムを照合する
                    327: bool
                    328: LunaPROMEmuDevice::VerifyNVRAM() const
                    329: {
                    330:        if (nvram->PeekString(0x0004) != "<nv>") {
                    331:                return false;
                    332:        }
                    333: 
                    334:        uint8 eor = CalcNVRAMCsum();
                    335:        for (uint32 addr = 0x1c; addr < 0x1f; addr++) {
                    336:                if (nvram->Peek(addr) != eor) {
                    337:                        return false;
                    338:                }
                    339:        }
                    340:        return true;
1.1       root      341: }
                    342: 
1.1.1.10  root      343: // -X オプションで指定されたホストファイルをロードする。
1.1       root      344: // ロードできればエントリポイントを返す。
1.1.1.10  root      345: // exec_file が指定されている時に呼ぶこと。
1.1       root      346: uint32
                    347: LunaPROMEmuDevice::LoadHostFile()
                    348: {
1.1.1.10  root      349:        assert(gMainApp.exec_file);
1.1       root      350: 
1.1.1.10  root      351:        uint32 entry = gRAM->LoadFile(gMainApp.exec_file);
1.1       root      352:        if (entry) {
                    353:                text.Print("Host program loaded.  Entry point = $%08x\n",
                    354:                        entry);
                    355:        } else {
                    356:                text.Print("** Couldn't load specified host program.\n");
                    357:        }
                    358:        return entry;
                    359: }
                    360: 
1.1.1.7   root      361: // bootinfo に従ってゲストから起動先をロードする。
1.1       root      362: // 成功すればエントリポイントを返す。
                    363: // 失敗ならをエラーメッセージを errmsg にセットして 0 を返す。
                    364: uint32
1.1.1.7   root      365: LunaPROMEmuDevice::LoadGuestFile(const bootinfo_t& bootinfo)
1.1       root      366: {
1.1.1.7   root      367:        const int& dkpart = bootinfo.dkpart;
                    368:        const int& dkunit = bootinfo.dkunit;
                    369:        const std::string& dkfile = bootinfo.dkfile;
1.1       root      370: 
                    371:        // ユニット (今は SCSI ID=6 決め打ち)
                    372:        int id = 6;
                    373:        SCSITarget *target = gSPC->GetTarget(id);
                    374:        if (target == NULL) {
                    375:                errmsg = "No bootable disks";
                    376:                putmsg(0, "%s", errmsg.c_str());
                    377:                return 0;
                    378:        }
1.1.1.3   root      379:        if (target->GetDevType() != SCSI::DevType::HD) {
1.1       root      380:                errmsg = "No bootable disks";
                    381:                putmsg(0, "%s", errmsg.c_str());
                    382:                return 0;
                    383:        }
                    384: 
1.1.1.10  root      385:        SCSIDisk *hd = dynamic_cast<SCSIDisk*>(target);
1.1       root      386:        putmsg(2, "%s SCSIHD[%d] found", __func__, id);
                    387: 
                    388:        // +0 セクタ目(512byte)に Sun disklabel
                    389:        scd_dk_label dk;
1.1.1.10  root      390:        if (hd->Peek(&dk, 0, sizeof(dk)) == false) {
1.1       root      391:                putmsg(0, "SCSIHD[%d] ReadSector failed", id);
                    392:                errmsg = string_format("dk%d Read disklabel failed.", dkunit);
                    393:                return 0;
                    394:        }
                    395: 
                    396:        // +$1fc の MAGIC をチェック
                    397:        uint32 magic = be16toh(dk.magic);
                    398:        if (magic != scd_dk_label::DKL_MAGIC) {
                    399:                putmsg(0, "SCSIHD[%d] Bad magic 0x%04x (!= 0x%x)",
                    400:                        id, magic, scd_dk_label::DKL_MAGIC);
                    401:                errmsg = string_format("dk%d Bad disklabel magic.", dkunit);
                    402:                return 0;
                    403:        }
                    404: 
                    405:        // チェックサム照合は手抜きで省略
                    406: 
                    407:        // 指定パーティションの開始位置とサイズ
                    408:        if (loglevel >= 2) {
                    409:                // デバッグ用に全部表示
                    410:                for (int i = 0; i < 8; i++) {
1.1.1.7   root      411:                        putmsgn("%s partition %c: { start=%8u size=%8u }", __func__,
1.1       root      412:                                'a' + i,
                    413:                                be32toh(dk.map[i].blkno),
                    414:                                be32toh(dk.map[i].nblk));
                    415:                }
                    416:        }
                    417:        uint32 part_start = be32toh(dk.map[dkpart].blkno);
                    418:        uint32 part_size  = be32toh(dk.map[dkpart].nblk);
                    419:        putmsg(1, "%s partition=%c, start=%u size=%u", __func__,
                    420:                dkpart + 'a', part_start, part_size);
                    421: 
                    422:        // ファイルシステムをオープン (マウントっぽいイメージ)
                    423:        Filesys fsys(this);
1.1.1.7   root      424:        if (fsys.Mount(hd, part_start, part_size) == false) {
                    425:                errmsg = string_format("dk%d,%c Open ffs failed: %s",
                    426:                        dkunit, dkpart + 'a', fsys.errstr.c_str());
                    427:                putmsg(0, "%s: %s", __func__, errmsg.c_str());
1.1       root      428:                return 0;
                    429:        }
                    430: 
1.1.1.7   root      431:        // ファイルを探してオープンする
                    432:        inodefile f(this);
                    433:        if (fsys.OpenFile(f, dkfile) == false) {
                    434:                errmsg = string_format("dk%d,%c,%s open failed: %s",
                    435:                        dkunit, dkpart + 'a', dkfile.c_str(), fsys.errstr.c_str());
                    436:                putmsg(0, "%s: %s", __func__, errmsg.c_str());
1.1       root      437:                return 0;
                    438:        }
1.1.1.7   root      439: 
1.1       root      440:        // ファイル本体を読み込み
1.1.1.7   root      441:        fsys.ReadData(f);
1.1       root      442: 
                    443:        // ロード
                    444:        uint32 entry;
                    445:        std::string name = string_format("SCSIHD %d,%c:%s",
1.1.1.7   root      446:                id, dkpart + 'a', dkfile.c_str());
                    447:        entry = gRAM->LoadFile(name.c_str(), f.data.data(), f.data.size());
                    448:        if (entry == 0) {
                    449:                errmsg = string_format("dk%d,%c,%s not executable",
                    450:                        dkunit, dkpart + 'a', dkfile.c_str());
1.1       root      451:        }
                    452: 
1.1.1.7   root      453:        return entry;
1.1       root      454: }
                    455: 
1.1.1.7   root      456: // キーコードから文字への変換
                    457: /*static*/ const uint8
                    458: LunaPROMEmuDevice::lunakey2asciitable[] = {
1.1.1.9   root      459:         0,   0,   0,   0,   0,   0,   0,   0,
                    460:         0,   0,   0,   0,   0,   0,   0,   0,
                    461:         0,  BS,  LF,   0, ' ',   0,   0,   0,
                    462:         0,   0,   0,   0,  UP,LEFT,RIGHT,DOWN,
1.1.1.7   root      463:         0,   0,  '1', '2', '3', '4', '5', '6',
                    464:        '7', '8', '9', '0', '-', '^', '\\', 0,
                    465:         0,   0,  'q', 'w', 'e', 'r', 't', 'y',
                    466:        'u', 'i', 'o', 'p', '@', '[',  0,   0,
                    467:         0,   0,  'a', 's', 'd', 'f', 'g', 'h',
                    468:        'j', 'k', 'l', ';', ':', ']',  0,   0,
                    469:         0,   0,  'z', 'x', 'c', 'v', 'b', 'n',
                    470:        'm', ',', '.', '/',  0,   0,   0,   0,
                    471:         0, 0, 0, 0, 0, 0, 0, 0,        // XXX テンキー
                    472:         0, 0, 0, 0, 0, 0, 0, 0,
                    473:         0, 0, 0, 0, 0, 0, 0, 0,
                    474:         0, 0, 0, 0, 0, 0, 0, 0,
                    475: };
                    476: /*static*/ const uint8
                    477: LunaPROMEmuDevice::lunakey2shifttable[] = {
1.1.1.9   root      478:         0,   0,   0,   0,   0,   0,   0,   0,
                    479:         0,   0,   0,   0,   0,   0,   0,   0,
                    480:         0,  BS,  LF,   0, ' ',   0,   0,   0,
                    481:         0,   0,   0,   0,  UP,LEFT,RIGHT,DOWN,
                    482:         0,   0,  '!', '\"','#', '$', '%', '&',
                    483:        '\'','(', ')', ' ', '=', '~', '|',  0,          // XXX SHIFT+'0'は?
1.1.1.7   root      484:         0,   0,  'Q', 'W', 'E', 'R', 'T', 'Y',
                    485:        'U', 'I', 'O', 'P', '`', '{',  0,   0,
                    486:         0,   0,  'A', 'S', 'D', 'F', 'G', 'H',
                    487:        'J', 'K', 'L', '+', '*', '}',  0,   0,
                    488:         0,   0,  'Z', 'X', 'C', 'V', 'B', 'N',
                    489:        'M', '<', '>', '?', '_',  0,   0,   0,
                    490:         0, 0, 0, 0, 0, 0, 0, 0,        // XXX テンキー
                    491:         0, 0, 0, 0, 0, 0, 0, 0,
                    492:         0, 0, 0, 0, 0, 0, 0, 0,
                    493:         0, 0, 0, 0, 0, 0, 0, 0,
                    494: };

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.