|
|
1.1 ! root 1: /* ! 2: * CFI parallel flash with AMD command set emulation ! 3: * ! 4: * Copyright (c) 2005 Jocelyn Mayer ! 5: * ! 6: * This library is free software; you can redistribute it and/or ! 7: * modify it under the terms of the GNU Lesser General Public ! 8: * License as published by the Free Software Foundation; either ! 9: * version 2 of the License, or (at your option) any later version. ! 10: * ! 11: * This library is distributed in the hope that it will be useful, ! 12: * but WITHOUT ANY WARRANTY; without even the implied warranty of ! 13: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ! 14: * Lesser General Public License for more details. ! 15: * ! 16: * You should have received a copy of the GNU Lesser General Public ! 17: * License along with this library; if not, write to the Free Software ! 18: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ! 19: */ ! 20: ! 21: /* ! 22: * For now, this code can emulate flashes of 1, 2 or 4 bytes width. ! 23: * Supported commands/modes are: ! 24: * - flash read ! 25: * - flash write ! 26: * - flash ID read ! 27: * - sector erase ! 28: * - chip erase ! 29: * - unlock bypass command ! 30: * - CFI queries ! 31: * ! 32: * It does not support flash interleaving. ! 33: * It does not implement boot blocs with reduced size ! 34: * It does not implement software data protection as found in many real chips ! 35: * It does not implement erase suspend/resume commands ! 36: * It does not implement multiple sectors erase ! 37: */ ! 38: ! 39: #include "vl.h" ! 40: ! 41: //#define PFLASH_DEBUG ! 42: #ifdef PFLASH_DEBUG ! 43: #define DPRINTF(fmt, args...) \ ! 44: do { \ ! 45: printf("PFLASH: " fmt , ##args); \ ! 46: } while (0) ! 47: #else ! 48: #define DPRINTF(fmt, args...) do { } while (0) ! 49: #endif ! 50: ! 51: struct pflash_t { ! 52: BlockDriverState *bs; ! 53: target_ulong base; ! 54: target_ulong sector_len; ! 55: target_ulong total_len; ! 56: int width; ! 57: int wcycle; /* if 0, the flash is read normally */ ! 58: int bypass; ! 59: int ro; ! 60: uint8_t cmd; ! 61: uint8_t status; ! 62: uint16_t ident[4]; ! 63: uint8_t cfi_len; ! 64: uint8_t cfi_table[0x52]; ! 65: QEMUTimer *timer; ! 66: ram_addr_t off; ! 67: int fl_mem; ! 68: void *storage; ! 69: }; ! 70: ! 71: static void pflash_timer (void *opaque) ! 72: { ! 73: pflash_t *pfl = opaque; ! 74: ! 75: DPRINTF("%s: command %02x done\n", __func__, pfl->cmd); ! 76: /* Reset flash */ ! 77: pfl->status ^= 0x80; ! 78: if (pfl->bypass) { ! 79: pfl->wcycle = 2; ! 80: } else { ! 81: cpu_register_physical_memory(pfl->base, pfl->total_len, ! 82: pfl->off | IO_MEM_ROMD | pfl->fl_mem); ! 83: pfl->wcycle = 0; ! 84: } ! 85: pfl->cmd = 0; ! 86: } ! 87: ! 88: static uint32_t pflash_read (pflash_t *pfl, target_ulong offset, int width) ! 89: { ! 90: target_ulong boff; ! 91: uint32_t ret; ! 92: uint8_t *p; ! 93: ! 94: DPRINTF("%s: offset %08x\n", __func__, offset); ! 95: ret = -1; ! 96: offset -= pfl->base; ! 97: boff = offset & 0xFF; ! 98: if (pfl->width == 2) ! 99: boff = boff >> 1; ! 100: else if (pfl->width == 4) ! 101: boff = boff >> 2; ! 102: switch (pfl->cmd) { ! 103: default: ! 104: /* This should never happen : reset state & treat it as a read*/ ! 105: DPRINTF("%s: unknown command state: %x\n", __func__, pfl->cmd); ! 106: pfl->wcycle = 0; ! 107: pfl->cmd = 0; ! 108: case 0x80: ! 109: /* We accept reads during second unlock sequence... */ ! 110: case 0x00: ! 111: flash_read: ! 112: /* Flash area read */ ! 113: p = pfl->storage; ! 114: switch (width) { ! 115: case 1: ! 116: ret = p[offset]; ! 117: // DPRINTF("%s: data offset %08x %02x\n", __func__, offset, ret); ! 118: break; ! 119: case 2: ! 120: #if defined(TARGET_WORDS_BIGENDIAN) ! 121: ret = p[offset] << 8; ! 122: ret |= p[offset + 1]; ! 123: #else ! 124: ret = p[offset]; ! 125: ret |= p[offset + 1] << 8; ! 126: #endif ! 127: // DPRINTF("%s: data offset %08x %04x\n", __func__, offset, ret); ! 128: break; ! 129: case 4: ! 130: #if defined(TARGET_WORDS_BIGENDIAN) ! 131: ret = p[offset] << 24; ! 132: ret |= p[offset + 1] << 16; ! 133: ret |= p[offset + 2] << 8; ! 134: ret |= p[offset + 3]; ! 135: #else ! 136: ret = p[offset]; ! 137: ret |= p[offset + 1] << 8; ! 138: ret |= p[offset + 1] << 8; ! 139: ret |= p[offset + 2] << 16; ! 140: ret |= p[offset + 3] << 24; ! 141: #endif ! 142: // DPRINTF("%s: data offset %08x %08x\n", __func__, offset, ret); ! 143: break; ! 144: } ! 145: break; ! 146: case 0x90: ! 147: /* flash ID read */ ! 148: switch (boff) { ! 149: case 0x00: ! 150: case 0x01: ! 151: ret = pfl->ident[boff & 0x01]; ! 152: break; ! 153: case 0x02: ! 154: ret = 0x00; /* Pretend all sectors are unprotected */ ! 155: break; ! 156: case 0x0E: ! 157: case 0x0F: ! 158: if (pfl->ident[2 + (boff & 0x01)] == (uint8_t)-1) ! 159: goto flash_read; ! 160: ret = pfl->ident[2 + (boff & 0x01)]; ! 161: break; ! 162: default: ! 163: goto flash_read; ! 164: } ! 165: DPRINTF("%s: ID %d %x\n", __func__, boff, ret); ! 166: break; ! 167: case 0xA0: ! 168: case 0x10: ! 169: case 0x30: ! 170: /* Status register read */ ! 171: ret = pfl->status; ! 172: DPRINTF("%s: status %x\n", __func__, ret); ! 173: /* Toggle bit 6 */ ! 174: pfl->status ^= 0x40; ! 175: break; ! 176: case 0x98: ! 177: /* CFI query mode */ ! 178: if (boff > pfl->cfi_len) ! 179: ret = 0; ! 180: else ! 181: ret = pfl->cfi_table[boff]; ! 182: break; ! 183: } ! 184: ! 185: return ret; ! 186: } ! 187: ! 188: /* update flash content on disk */ ! 189: static void pflash_update(pflash_t *pfl, int offset, ! 190: int size) ! 191: { ! 192: int offset_end; ! 193: if (pfl->bs) { ! 194: offset_end = offset + size; ! 195: /* round to sectors */ ! 196: offset = offset >> 9; ! 197: offset_end = (offset_end + 511) >> 9; ! 198: bdrv_write(pfl->bs, offset, pfl->storage + (offset << 9), ! 199: offset_end - offset); ! 200: } ! 201: } ! 202: ! 203: static void pflash_write (pflash_t *pfl, target_ulong offset, uint32_t value, ! 204: int width) ! 205: { ! 206: target_ulong boff; ! 207: uint8_t *p; ! 208: uint8_t cmd; ! 209: ! 210: /* WARNING: when the memory area is in ROMD mode, the offset is a ! 211: ram offset, not a physical address */ ! 212: if (pfl->wcycle == 0) ! 213: offset -= (target_ulong)(long)pfl->storage; ! 214: else ! 215: offset -= pfl->base; ! 216: ! 217: cmd = value; ! 218: DPRINTF("%s: offset %08x %08x %d\n", __func__, offset, value, width); ! 219: if (pfl->cmd != 0xA0 && cmd == 0xF0) { ! 220: DPRINTF("%s: flash reset asked (%02x %02x)\n", ! 221: __func__, pfl->cmd, cmd); ! 222: goto reset_flash; ! 223: } ! 224: /* Set the device in I/O access mode */ ! 225: cpu_register_physical_memory(pfl->base, pfl->total_len, pfl->fl_mem); ! 226: boff = offset & (pfl->sector_len - 1); ! 227: if (pfl->width == 2) ! 228: boff = boff >> 1; ! 229: else if (pfl->width == 4) ! 230: boff = boff >> 2; ! 231: switch (pfl->wcycle) { ! 232: case 0: ! 233: /* We're in read mode */ ! 234: check_unlock0: ! 235: if (boff == 0x55 && cmd == 0x98) { ! 236: enter_CFI_mode: ! 237: /* Enter CFI query mode */ ! 238: pfl->wcycle = 7; ! 239: pfl->cmd = 0x98; ! 240: return; ! 241: } ! 242: if (boff != 0x555 || cmd != 0xAA) { ! 243: DPRINTF("%s: unlock0 failed %04x %02x %04x\n", ! 244: __func__, boff, cmd, 0x555); ! 245: goto reset_flash; ! 246: } ! 247: DPRINTF("%s: unlock sequence started\n", __func__); ! 248: break; ! 249: case 1: ! 250: /* We started an unlock sequence */ ! 251: check_unlock1: ! 252: if (boff != 0x2AA || cmd != 0x55) { ! 253: DPRINTF("%s: unlock1 failed %04x %02x\n", __func__, boff, cmd); ! 254: goto reset_flash; ! 255: } ! 256: DPRINTF("%s: unlock sequence done\n", __func__); ! 257: break; ! 258: case 2: ! 259: /* We finished an unlock sequence */ ! 260: if (!pfl->bypass && boff != 0x555) { ! 261: DPRINTF("%s: command failed %04x %02x\n", __func__, boff, cmd); ! 262: goto reset_flash; ! 263: } ! 264: switch (cmd) { ! 265: case 0x20: ! 266: pfl->bypass = 1; ! 267: goto do_bypass; ! 268: case 0x80: ! 269: case 0x90: ! 270: case 0xA0: ! 271: pfl->cmd = cmd; ! 272: DPRINTF("%s: starting command %02x\n", __func__, cmd); ! 273: break; ! 274: default: ! 275: DPRINTF("%s: unknown command %02x\n", __func__, cmd); ! 276: goto reset_flash; ! 277: } ! 278: break; ! 279: case 3: ! 280: switch (pfl->cmd) { ! 281: case 0x80: ! 282: /* We need another unlock sequence */ ! 283: goto check_unlock0; ! 284: case 0xA0: ! 285: DPRINTF("%s: write data offset %08x %08x %d\n", ! 286: __func__, offset, value, width); ! 287: p = pfl->storage; ! 288: switch (width) { ! 289: case 1: ! 290: p[offset] &= value; ! 291: pflash_update(pfl, offset, 1); ! 292: break; ! 293: case 2: ! 294: #if defined(TARGET_WORDS_BIGENDIAN) ! 295: p[offset] &= value >> 8; ! 296: p[offset + 1] &= value; ! 297: #else ! 298: p[offset] &= value; ! 299: p[offset + 1] &= value >> 8; ! 300: #endif ! 301: pflash_update(pfl, offset, 2); ! 302: break; ! 303: case 4: ! 304: #if defined(TARGET_WORDS_BIGENDIAN) ! 305: p[offset] &= value >> 24; ! 306: p[offset + 1] &= value >> 16; ! 307: p[offset + 2] &= value >> 8; ! 308: p[offset + 3] &= value; ! 309: #else ! 310: p[offset] &= value; ! 311: p[offset + 1] &= value >> 8; ! 312: p[offset + 2] &= value >> 16; ! 313: p[offset + 3] &= value >> 24; ! 314: #endif ! 315: pflash_update(pfl, offset, 4); ! 316: break; ! 317: } ! 318: pfl->status = 0x00 | ~(value & 0x80); ! 319: /* Let's pretend write is immediate */ ! 320: if (pfl->bypass) ! 321: goto do_bypass; ! 322: goto reset_flash; ! 323: case 0x90: ! 324: if (pfl->bypass && cmd == 0x00) { ! 325: /* Unlock bypass reset */ ! 326: goto reset_flash; ! 327: } ! 328: /* We can enter CFI query mode from autoselect mode */ ! 329: if (boff == 0x55 && cmd == 0x98) ! 330: goto enter_CFI_mode; ! 331: /* No break here */ ! 332: default: ! 333: DPRINTF("%s: invalid write for command %02x\n", ! 334: __func__, pfl->cmd); ! 335: goto reset_flash; ! 336: } ! 337: case 4: ! 338: switch (pfl->cmd) { ! 339: case 0xA0: ! 340: /* Ignore writes while flash data write is occuring */ ! 341: /* As we suppose write is immediate, this should never happen */ ! 342: return; ! 343: case 0x80: ! 344: goto check_unlock1; ! 345: default: ! 346: /* Should never happen */ ! 347: DPRINTF("%s: invalid command state %02x (wc 4)\n", ! 348: __func__, pfl->cmd); ! 349: goto reset_flash; ! 350: } ! 351: break; ! 352: case 5: ! 353: switch (cmd) { ! 354: case 0x10: ! 355: if (boff != 0x555) { ! 356: DPRINTF("%s: chip erase: invalid address %04x\n", ! 357: __func__, offset); ! 358: goto reset_flash; ! 359: } ! 360: /* Chip erase */ ! 361: DPRINTF("%s: start chip erase\n", __func__); ! 362: memset(pfl->storage, 0xFF, pfl->total_len); ! 363: pfl->status = 0x00; ! 364: pflash_update(pfl, 0, pfl->total_len); ! 365: /* Let's wait 5 seconds before chip erase is done */ ! 366: qemu_mod_timer(pfl->timer, ! 367: qemu_get_clock(vm_clock) + (ticks_per_sec * 5)); ! 368: break; ! 369: case 0x30: ! 370: /* Sector erase */ ! 371: p = pfl->storage; ! 372: offset &= ~(pfl->sector_len - 1); ! 373: DPRINTF("%s: start sector erase at %08x\n", __func__, offset); ! 374: memset(p + offset, 0xFF, pfl->sector_len); ! 375: pflash_update(pfl, offset, pfl->sector_len); ! 376: pfl->status = 0x00; ! 377: /* Let's wait 1/2 second before sector erase is done */ ! 378: qemu_mod_timer(pfl->timer, ! 379: qemu_get_clock(vm_clock) + (ticks_per_sec / 2)); ! 380: break; ! 381: default: ! 382: DPRINTF("%s: invalid command %02x (wc 5)\n", __func__, cmd); ! 383: goto reset_flash; ! 384: } ! 385: pfl->cmd = cmd; ! 386: break; ! 387: case 6: ! 388: switch (pfl->cmd) { ! 389: case 0x10: ! 390: /* Ignore writes during chip erase */ ! 391: return; ! 392: case 0x30: ! 393: /* Ignore writes during sector erase */ ! 394: return; ! 395: default: ! 396: /* Should never happen */ ! 397: DPRINTF("%s: invalid command state %02x (wc 6)\n", ! 398: __func__, pfl->cmd); ! 399: goto reset_flash; ! 400: } ! 401: break; ! 402: case 7: /* Special value for CFI queries */ ! 403: DPRINTF("%s: invalid write in CFI query mode\n", __func__); ! 404: goto reset_flash; ! 405: default: ! 406: /* Should never happen */ ! 407: DPRINTF("%s: invalid write state (wc 7)\n", __func__); ! 408: goto reset_flash; ! 409: } ! 410: pfl->wcycle++; ! 411: ! 412: return; ! 413: ! 414: /* Reset flash */ ! 415: reset_flash: ! 416: if (pfl->wcycle != 0) { ! 417: cpu_register_physical_memory(pfl->base, pfl->total_len, ! 418: pfl->off | IO_MEM_ROMD | pfl->fl_mem); ! 419: } ! 420: pfl->bypass = 0; ! 421: pfl->wcycle = 0; ! 422: pfl->cmd = 0; ! 423: return; ! 424: ! 425: do_bypass: ! 426: pfl->wcycle = 2; ! 427: pfl->cmd = 0; ! 428: return; ! 429: } ! 430: ! 431: ! 432: static uint32_t pflash_readb (void *opaque, target_phys_addr_t addr) ! 433: { ! 434: return pflash_read(opaque, addr, 1); ! 435: } ! 436: ! 437: static uint32_t pflash_readw (void *opaque, target_phys_addr_t addr) ! 438: { ! 439: pflash_t *pfl = opaque; ! 440: ! 441: return pflash_read(pfl, addr, 2); ! 442: } ! 443: ! 444: static uint32_t pflash_readl (void *opaque, target_phys_addr_t addr) ! 445: { ! 446: pflash_t *pfl = opaque; ! 447: ! 448: return pflash_read(pfl, addr, 4); ! 449: } ! 450: ! 451: static void pflash_writeb (void *opaque, target_phys_addr_t addr, ! 452: uint32_t value) ! 453: { ! 454: pflash_write(opaque, addr, value, 1); ! 455: } ! 456: ! 457: static void pflash_writew (void *opaque, target_phys_addr_t addr, ! 458: uint32_t value) ! 459: { ! 460: pflash_t *pfl = opaque; ! 461: ! 462: pflash_write(pfl, addr, value, 2); ! 463: } ! 464: ! 465: static void pflash_writel (void *opaque, target_phys_addr_t addr, ! 466: uint32_t value) ! 467: { ! 468: pflash_t *pfl = opaque; ! 469: ! 470: pflash_write(pfl, addr, value, 4); ! 471: } ! 472: ! 473: static CPUWriteMemoryFunc *pflash_write_ops[] = { ! 474: &pflash_writeb, ! 475: &pflash_writew, ! 476: &pflash_writel, ! 477: }; ! 478: ! 479: static CPUReadMemoryFunc *pflash_read_ops[] = { ! 480: &pflash_readb, ! 481: &pflash_readw, ! 482: &pflash_readl, ! 483: }; ! 484: ! 485: /* Count trailing zeroes of a 32 bits quantity */ ! 486: static int ctz32 (uint32_t n) ! 487: { ! 488: int ret; ! 489: ! 490: ret = 0; ! 491: if (!(n & 0xFFFF)) { ! 492: ret += 16; ! 493: n = n >> 16; ! 494: } ! 495: if (!(n & 0xFF)) { ! 496: ret += 8; ! 497: n = n >> 8; ! 498: } ! 499: if (!(n & 0xF)) { ! 500: ret += 4; ! 501: n = n >> 4; ! 502: } ! 503: if (!(n & 0x3)) { ! 504: ret += 2; ! 505: n = n >> 2; ! 506: } ! 507: if (!(n & 0x1)) { ! 508: ret++; ! 509: n = n >> 1; ! 510: } ! 511: #if 0 /* This is not necessary as n is never 0 */ ! 512: if (!n) ! 513: ret++; ! 514: #endif ! 515: ! 516: return ret; ! 517: } ! 518: ! 519: pflash_t *pflash_register (target_ulong base, ram_addr_t off, ! 520: BlockDriverState *bs, ! 521: target_ulong sector_len, int nb_blocs, int width, ! 522: uint16_t id0, uint16_t id1, ! 523: uint16_t id2, uint16_t id3) ! 524: { ! 525: pflash_t *pfl; ! 526: target_long total_len; ! 527: ! 528: total_len = sector_len * nb_blocs; ! 529: /* XXX: to be fixed */ ! 530: if (total_len != (8 * 1024 * 1024) && total_len != (16 * 1024 * 1024) && ! 531: total_len != (32 * 1024 * 1024) && total_len != (64 * 1024 * 1024)) ! 532: return NULL; ! 533: pfl = qemu_mallocz(sizeof(pflash_t)); ! 534: if (pfl == NULL) ! 535: return NULL; ! 536: pfl->storage = phys_ram_base + off; ! 537: pfl->fl_mem = cpu_register_io_memory(0, pflash_read_ops, pflash_write_ops, pfl); ! 538: pfl->off = off; ! 539: cpu_register_physical_memory(base, total_len, ! 540: off | pfl->fl_mem | IO_MEM_ROMD); ! 541: pfl->bs = bs; ! 542: if (pfl->bs) { ! 543: /* read the initial flash content */ ! 544: bdrv_read(pfl->bs, 0, pfl->storage, total_len >> 9); ! 545: } ! 546: #if 0 /* XXX: there should be a bit to set up read-only, ! 547: * the same way the hardware does (with WP pin). ! 548: */ ! 549: pfl->ro = 1; ! 550: #else ! 551: pfl->ro = 0; ! 552: #endif ! 553: pfl->timer = qemu_new_timer(vm_clock, pflash_timer, pfl); ! 554: pfl->base = base; ! 555: pfl->sector_len = sector_len; ! 556: pfl->total_len = total_len; ! 557: pfl->width = width; ! 558: pfl->wcycle = 0; ! 559: pfl->cmd = 0; ! 560: pfl->status = 0; ! 561: pfl->ident[0] = id0; ! 562: pfl->ident[1] = id1; ! 563: pfl->ident[2] = id2; ! 564: pfl->ident[3] = id3; ! 565: /* Hardcoded CFI table (mostly from SG29 Spansion flash) */ ! 566: pfl->cfi_len = 0x52; ! 567: /* Standard "QRY" string */ ! 568: pfl->cfi_table[0x10] = 'Q'; ! 569: pfl->cfi_table[0x11] = 'R'; ! 570: pfl->cfi_table[0x12] = 'Y'; ! 571: /* Command set (AMD/Fujitsu) */ ! 572: pfl->cfi_table[0x13] = 0x02; ! 573: pfl->cfi_table[0x14] = 0x00; ! 574: /* Primary extended table address (none) */ ! 575: pfl->cfi_table[0x15] = 0x00; ! 576: pfl->cfi_table[0x16] = 0x00; ! 577: /* Alternate command set (none) */ ! 578: pfl->cfi_table[0x17] = 0x00; ! 579: pfl->cfi_table[0x18] = 0x00; ! 580: /* Alternate extended table (none) */ ! 581: pfl->cfi_table[0x19] = 0x00; ! 582: pfl->cfi_table[0x1A] = 0x00; ! 583: /* Vcc min */ ! 584: pfl->cfi_table[0x1B] = 0x27; ! 585: /* Vcc max */ ! 586: pfl->cfi_table[0x1C] = 0x36; ! 587: /* Vpp min (no Vpp pin) */ ! 588: pfl->cfi_table[0x1D] = 0x00; ! 589: /* Vpp max (no Vpp pin) */ ! 590: pfl->cfi_table[0x1E] = 0x00; ! 591: /* Reserved */ ! 592: pfl->cfi_table[0x1F] = 0x07; ! 593: /* Timeout for min size buffer write (16 �s) */ ! 594: pfl->cfi_table[0x20] = 0x04; ! 595: /* Typical timeout for block erase (512 ms) */ ! 596: pfl->cfi_table[0x21] = 0x09; ! 597: /* Typical timeout for full chip erase (4096 ms) */ ! 598: pfl->cfi_table[0x22] = 0x0C; ! 599: /* Reserved */ ! 600: pfl->cfi_table[0x23] = 0x01; ! 601: /* Max timeout for buffer write */ ! 602: pfl->cfi_table[0x24] = 0x04; ! 603: /* Max timeout for block erase */ ! 604: pfl->cfi_table[0x25] = 0x0A; ! 605: /* Max timeout for chip erase */ ! 606: pfl->cfi_table[0x26] = 0x0D; ! 607: /* Device size */ ! 608: pfl->cfi_table[0x27] = ctz32(total_len) + 1; ! 609: /* Flash device interface (8 & 16 bits) */ ! 610: pfl->cfi_table[0x28] = 0x02; ! 611: pfl->cfi_table[0x29] = 0x00; ! 612: /* Max number of bytes in multi-bytes write */ ! 613: pfl->cfi_table[0x2A] = 0x05; ! 614: pfl->cfi_table[0x2B] = 0x00; ! 615: /* Number of erase block regions (uniform) */ ! 616: pfl->cfi_table[0x2C] = 0x01; ! 617: /* Erase block region 1 */ ! 618: pfl->cfi_table[0x2D] = nb_blocs - 1; ! 619: pfl->cfi_table[0x2E] = (nb_blocs - 1) >> 8; ! 620: pfl->cfi_table[0x2F] = sector_len >> 8; ! 621: pfl->cfi_table[0x30] = sector_len >> 16; ! 622: ! 623: return pfl; ! 624: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.