|
|
1.1 ! root 1: /* ! 2: * DSA port driver for uda50 ! 3: * ! 4: * this just passes packets around; ! 5: * it makes calls to the routines in ra.c ! 6: * to get the packets filled in ! 7: */ ! 8: ! 9: #include "sys/param.h" ! 10: #include "sys/buf.h" ! 11: #include "sys/ubaddr.h" ! 12: #include "sys/uda.h" ! 13: #include "sys/mscp.h" ! 14: ! 15: /* ! 16: * ONEPATH tries to use only one data path per controller; ! 17: * a nice idea, but a disaster on the DW780 -- ! 18: * the UDA50 will cheerfully run transfers in parallel from ! 19: * two drives on the same data path, which confuses the ! 20: * DW780 and mixes up the data ! 21: */ ! 22: /* #define ONEPATH 1 /* don't */ ! 23: ! 24: #define hiword(x) (((long)(x)>>16)&0177777) ! 25: #define loword(x) ((long)(x)&0177777) ! 26: ! 27: extern struct ubaddr udaddr[]; ! 28: extern struct ud ud[]; ! 29: extern int udcnt; ! 30: ! 31: struct ctab { ! 32: int (*c_seql)(); ! 33: int (*c_dg)(); ! 34: int c_ctype; ! 35: } udctab[MSMAXID]; ! 36: ! 37: struct device { ! 38: short udip; ! 39: short udsa; ! 40: }; ! 41: ! 42: /* ! 43: * bits in udsa ! 44: */ ! 45: ! 46: #define ERR 0100000 ! 47: #define STEP4 040000 ! 48: #define STEP3 020000 ! 49: #define STEP2 010000 ! 50: #define STEP1 04000 ! 51: #define STEPS (STEP1|STEP2|STEP3|STEP4) ! 52: ! 53: #define IE 0200 /* step1 interrupt enable */ ! 54: #define PI 01 /* step2 purge intr enab */ ! 55: #define GO 01 /* step4 ok */ ! 56: ! 57: /* ! 58: * uda communication area ! 59: * ring sizes are chosen so that, ! 60: * with 4K byte buffers, ! 61: * one udcomm + CSIZE-sized command packets will ! 62: * fit in one buffer; ! 63: * RSIZE-sized response packets ! 64: * will fit in another ! 65: * ! 66: * ring sizes must be powers of 2 ! 67: * (they are passed as such to the port) ! 68: * ! 69: * a delicacy: ! 70: * command packets should not cross 64Kb boundaries. ! 71: * it is believed that the KDB50 gets it wrong if they do. ! 72: * hence, make CSIZE+4 evenly divide a page, ! 73: * and get it aligned sensibly in bdreset ! 74: */ ! 75: ! 76: #define NCP2 5 ! 77: #define NRP2 5 ! 78: #define NCMD (1<<NCP2) ! 79: #define NRSP (1<<NRP2) ! 80: ! 81: struct udcomm { ! 82: short ud__r0; /* reserved (ugh) */ ! 83: char ud__r1; ! 84: char ud_bdp; /* path to purge */ ! 85: short ud_cmdint; /* flag for command interrupt */ ! 86: short ud_rspint; /* flag for response interrupt */ ! 87: long ud_rsp[NRSP]; /* response pointer ring */ ! 88: long ud_cmd[NCMD]; /* command pointer ring */ ! 89: }; ! 90: ! 91: /* ! 92: * bits in ring pointers ! 93: */ ! 94: ! 95: #define DPOWN 0x80000000 /* port owns descriptor */ ! 96: #define DIE 0x40000000 /* ring transition intr enab */ ! 97: ! 98: #define CSIZE 60 /* max size of command packet */ ! 99: #define RSIZE 60 /* max size of response packet */ ! 100: #define HDRSIZE 4 /* size of the header */ ! 101: ! 102: struct udcmd { ! 103: short uc_len; /* length of message */ ! 104: char uc_tc; /* type, credits */ ! 105: char uc_cid; /* connection id */ ! 106: char uc_data[CSIZE]; ! 107: }; ! 108: ! 109: struct udrsp { ! 110: short ur_len; /* length of message */ ! 111: char ur_tc; /* type, credits */ ! 112: char ur_cid; /* connection id */ ! 113: char ur_data[RSIZE]; ! 114: }; ! 115: ! 116: /* ! 117: * message types ! 118: */ ! 119: ! 120: #define MTYPE 0xf0 /* where type lives */ ! 121: #define MTSEQL 0x00 /* sequential message */ ! 122: #define MTDG 0x10 /* datagram */ ! 123: #define MTCR 0x20 /* credit notification */ ! 124: ! 125: #define MTNC 0xf /* credits in tc */ ! 126: ! 127: /* ! 128: * etc ! 129: */ ! 130: ! 131: #define PRIINI (PZERO-3) ! 132: #define PRIPKT (PZERO-2) ! 133: #define PRICRED (PZERO-1) ! 134: ! 135: /* ! 136: * command packet to index ! 137: */ ! 138: #define udmptoi(up, mp) ((struct udcmd *)((char *)(mp) - HDRSIZE) - (up)->ud_cpkt) ! 139: ! 140: #define TIMEOUT 15 /* time between checks */ ! 141: ! 142: int udinit(), udsend(), udmap(), udunmap(); ! 143: struct mscmd *udgpkt(); ! 144: struct msportsw udport = { ! 145: udinit, udgpkt, udmap, udsend, udunmap ! 146: }; ! 147: ! 148: /* ! 149: * init the port ! 150: * returns nonzero if probably ok ! 151: * allowed to sleep ! 152: */ ! 153: ! 154: #define UDCOFF (((sizeof(struct udcomm)/sizeof(struct udcmd))+1)*sizeof(struct udcmd)) ! 155: ! 156: udinit(dev, type, force, cid, seql, dg) ! 157: unsigned int dev; ! 158: unsigned int cid; ! 159: int force; ! 160: int (*seql)(), (*dg)(); ! 161: { ! 162: register struct ud *up; ! 163: register int ubno; ! 164: struct buf *geteblk(); ! 165: extern udtimer(); ! 166: ! 167: if (dev >= udcnt) ! 168: return (0); ! 169: if (cid >= MSMAXID) ! 170: return (0); ! 171: udctab[cid].c_seql = seql; ! 172: udctab[cid].c_dg = dg; ! 173: udctab[cid].c_ctype = type; ! 174: up = &ud[dev]; ! 175: ubno = udaddr[dev].ubno; ! 176: if (up->ud_flags & UISTART && force == 0) ! 177: return (1); ! 178: if ((up->ud_addr = (struct device *)ubaddr(&udaddr[dev])) == 0) ! 179: return (0); ! 180: if (ubbadaddr(ubno, &up->ud_addr->udsa, sizeof(short))) { ! 181: printf("ud%d not present\n", dev); ! 182: return (0); ! 183: } ! 184: udrundown(dev); ! 185: if ((up->ud_flags & UINIT) == 0) { ! 186: up->ud_cbuf = geteblk(); ! 187: clrbuf(up->ud_cbuf); ! 188: up->ud_rbuf = geteblk(); ! 189: clrbuf(up->ud_rbuf); ! 190: up->ud_cbm = ubmbuf(ubno, up->ud_cbuf, 0); ! 191: up->ud_rbm = ubmbuf(ubno, up->ud_cbuf, 0); ! 192: #if ONEPATH ! 193: up->ud_bdpno = ubmapath(ubno); ! 194: #endif ! 195: up->ud_comm = (struct udcomm *)up->ud_cbuf->b_un.b_addr; ! 196: up->ud_cpkt = (struct udcmd *)(up->ud_cbuf->b_un.b_addr + UDCOFF); ! 197: up->ud_rpkt = (struct udrsp *)up->ud_rbuf->b_un.b_addr; ! 198: up->ud_flags |= UINIT; ! 199: timeout(udtimer, (caddr_t)dev, TIMEOUT * HZ); ! 200: } ! 201: return (udreset(dev)); ! 202: } ! 203: ! 204: /* ! 205: * reset device ! 206: * initially or after error or power fail ! 207: */ ! 208: ! 209: #define INITSTALL 100000 /* >>600us */ ! 210: ! 211: udreset(dev) ! 212: int dev; ! 213: { ! 214: register struct ud *up; ! 215: register struct device *rp; ! 216: register uaddr_t pa; ! 217: register int i; ! 218: ! 219: up = &ud[dev]; ! 220: rp = up->ud_addr; ! 221: up->ud_flags &=~ UIDONE; ! 222: up->ud_flags |= UISTART; ! 223: rp->udip = 0; /* hard reset */ ! 224: pa = ubadbuf(udaddr[dev].ubno, up->ud_cbuf, up->ud_cbm); ! 225: up->ud_pcomm = pa + (4 * sizeof(short)); /* -> ud_rsp */ ! 226: up->ud_pcpkt = pa + UDCOFF; ! 227: up->ud_cnext = 0; ! 228: up->ud_rnext = 0; ! 229: up->ud_credits = 0; ! 230: for (i = 0; (rp->udsa & STEP1) == 0 && i < INITSTALL; i++) ! 231: ; ! 232: if ((rp->udsa & STEP1) == 0) { ! 233: up->ud_flags &=~ UISTART; ! 234: printf("ud%d won't init\n", dev); ! 235: return (0); ! 236: } ! 237: rp->udsa = ERR | IE | (NCP2<<11) | (NRP2<<8) | (udaddr[dev].vec>>2); ! 238: return (1); ! 239: } ! 240: ! 241: /* ! 242: * finish up init, step by step ! 243: * called from interrupt code ! 244: */ ! 245: ! 246: udinintr(dev) ! 247: int dev; ! 248: { ! 249: register struct ud *up; ! 250: register struct device *rp; ! 251: register int i; ! 252: register uaddr_t pa; ! 253: ! 254: up = &ud[dev]; ! 255: rp = up->ud_addr; ! 256: if (up->ud_flags & UIDONE) { ! 257: printf("ud%d: unexpected init: sa %o\n", dev, rp->udsa); ! 258: return; ! 259: } ! 260: switch (rp->udsa & STEPS) { ! 261: case STEP1: ! 262: udreset(dev); ! 263: return; ! 264: ! 265: case STEP2: ! 266: rp->udsa = PI | loword(up->ud_pcomm); ! 267: return; ! 268: ! 269: case STEP3: ! 270: rp->udsa = hiword(up->ud_pcomm); ! 271: return; ! 272: ! 273: case STEP4: ! 274: rp->udsa = GO; ! 275: for (i = 0; i < NCMD; i++) { ! 276: up->ud_comm->ud_cmd[i] = 0; /* unnecessary */ ! 277: up->ud_cpkt[i].uc_len = CSIZE; ! 278: up->ud_cbusy[i] = FREE; ! 279: up->ud_back[i] = NOBACK; ! 280: } ! 281: pa = ubadbuf(udaddr[dev].ubno, up->ud_rbuf, up->ud_rbm); ! 282: pa += 2 * sizeof(short); ! 283: for (i = 0; i < NRSP; i++, pa += sizeof(struct udrsp)) { ! 284: up->ud_comm->ud_rsp[i] = pa | DIE | DPOWN; ! 285: up->ud_rpkt[i].ur_len = RSIZE; ! 286: } ! 287: up->ud_flags |= UIDONE | UFIRST; ! 288: wakeup((caddr_t)up); ! 289: return; ! 290: ! 291: default: ! 292: printf("ud%d init bad: sa %o\n", dev, rp->udsa); ! 293: return; ! 294: } ! 295: } ! 296: ! 297: /* ! 298: * tell the class drivers that the controller was reset ! 299: * so they can clean up ! 300: * called after controller is stopped (so it's safe to unmap things) ! 301: */ ! 302: udrundown(dev) ! 303: int dev; ! 304: { ! 305: static struct msend me; ! 306: register int i; ! 307: ! 308: me.m_sts = STRST; /* magic */ ! 309: for (i = 0; i < MSMAXID; i++) ! 310: if (udctab[i].c_seql) ! 311: (*udctab[i].c_seql)(dev, udctab[i].c_ctype, &me); ! 312: } ! 313: ! 314: /* ! 315: * allocate a packet ! 316: * and sufficient resources to send it ! 317: * eg credits ! 318: * may sleep ! 319: * ! 320: * somewhat silly assumption: ! 321: * class driver will allocate a packet, and send it right away or nearly so ! 322: */ ! 323: ! 324: struct mscmd * ! 325: udgpkt(dev) ! 326: int dev; ! 327: { ! 328: register struct ud *up; ! 329: register int i; ! 330: int s; ! 331: ! 332: up = &ud[dev]; ! 333: if (up->ud_addr->udsa & STEP1) /* was reset somehow */ ! 334: udreset(dev); ! 335: s = spl6(); ! 336: while ((up->ud_flags & UIDONE) == 0) ! 337: sleep((caddr_t)up, PRIINI); ! 338: while (up->ud_credits < 2 && (up->ud_flags & UFIRST) == 0) { ! 339: if (udpkscan(dev, 1)) ! 340: continue; ! 341: up->ud_flags |= UCWAIT; ! 342: sleep((caddr_t)&up->ud_credits, PRICRED); ! 343: } ! 344: if ((up->ud_flags & UFIRST) == 0) ! 345: up->ud_credits--; ! 346: for (;;) { ! 347: for (i = 0; i < NCMD; i++) ! 348: if (up->ud_cbusy[i] == FREE) ! 349: break; ! 350: if (i < NCMD) ! 351: break; ! 352: if (udpkscan(dev, 1) == 0 && udcmdscan(dev) == 0) { /* kludge for hung controller */ ! 353: up->ud_flags |= UPWAIT; ! 354: sleep((caddr_t)up->ud_cbusy, PRIPKT); ! 355: } ! 356: } ! 357: up->ud_cbusy[i] = NABBED; ! 358: splx(s); ! 359: return ((struct mscmd *)up->ud_cpkt[i].uc_data); ! 360: } ! 361: ! 362: /* ! 363: * map a transfer into unibus space ! 364: * and record in the buffer descriptor of a packet ! 365: * may sleep ! 366: */ ! 367: ! 368: udmap(dev, mp, bp) ! 369: int dev; ! 370: struct mscmd *mp; ! 371: register struct buf *bp; ! 372: { ! 373: register struct ud *up; ! 374: register int i; ! 375: ! 376: up = &ud[dev]; ! 377: i = udmptoi(up, mp); ! 378: #if ONEPATH ! 379: if (up->ud_cmap[i] == 0) ! 380: up->ud_cmap[i] = ubmbuf(udaddr[dev].ubno, bp, USLP); ! 381: up->ud_cmap[i] = ubinspath(up->ud_bdpno, up->ud_cmap[i]); ! 382: #else ! 383: if (up->ud_cmap[i] == 0) ! 384: up->ud_cmap[i] = ubmbuf(udaddr[dev].ubno, bp, UBDP|USLP); ! 385: #endif ! 386: up->ud_cbusy[i] |= MAPPED; ! 387: *(long *)&mp->m_buff = ubadbuf(udaddr[dev].ubno, bp, up->ud_cmap[i]); ! 388: #if ONEPATH ! 389: *(long *)&mp->m_buff |= up->ud_bdpno << 24; ! 390: #else ! 391: *(long *)&mp->m_buff |= ubmpath(up->ud_cmap[i]) << 24; ! 392: #endif ! 393: } ! 394: ! 395: /* ! 396: * free a mapped packet ! 397: * flush the bdp; ! 398: * change the path in the map to the DDP before freeing (ugh) ! 399: * so we won't free our reserved BDP too ! 400: * it is OK to free an already freed packet, mostly to help reset code ! 401: */ ! 402: ! 403: udunmap(dev, mp) ! 404: int dev; ! 405: struct mscmd *mp; ! 406: { ! 407: register struct ud *up; ! 408: register int i; ! 409: ! 410: up = &ud[dev]; ! 411: i = udmptoi(up, mp); ! 412: if (up->ud_cmap[i]) { ! 413: #if ONEPATH ! 414: ubmflush(udaddr[dev].ubno, ubmpath(up->ud_cmap[i])); ! 415: ubmfree(udaddr[dev].ubno, ubinspath(0, up->ud_cmap[i])); ! 416: #else ! 417: ubmfree(udaddr[dev].ubno, up->ud_cmap[i]); ! 418: #endif ! 419: up->ud_cmap[i] = 0; ! 420: } ! 421: up->ud_cbusy[i] = FREE; ! 422: if (up->ud_flags & UPWAIT) { ! 423: up->ud_flags &=~ UPWAIT; ! 424: wakeup((caddr_t)up->ud_cbusy); ! 425: } ! 426: } ! 427: ! 428: ! 429: /* ! 430: * send a packet ! 431: * may not sleep ! 432: * call at spl5 ! 433: */ ! 434: udsend(dev, cid, mp) ! 435: int dev; ! 436: int cid; ! 437: struct mscmd *mp; ! 438: { ! 439: register struct ud *up; ! 440: register int i; ! 441: register int j; ! 442: register struct device *rp; ! 443: ! 444: up = &ud[dev]; ! 445: up->ud_flags &=~ UFIRST; ! 446: i = udmptoi(up, mp); ! 447: up->ud_cpkt[i].uc_cid = cid; ! 448: j = up->ud_cnext++; ! 449: if (up->ud_cnext >= NCMD) ! 450: up->ud_cnext = 0; ! 451: if (up->ud_comm->ud_cmd[j] & DPOWN) ! 452: panic("udsend"); ! 453: if (up->ud_back[j] >= 0) { /* ran for a while with no free */ ! 454: udcmdscan(dev); ! 455: if (up->ud_back[j] >= 0) ! 456: panic("udsend"); ! 457: } ! 458: rp = up->ud_addr; ! 459: if (rp->udsa & ERR) { /* and now we lose a packet */ ! 460: printf("ud%d: hard error %o\n", dev, rp->udsa & 0177777); ! 461: udreset(dev); ! 462: return; ! 463: } ! 464: up->ud_back[j] = i; ! 465: up->ud_comm->ud_cmd[j] = DPOWN | DIE | ! 466: up->ud_pcpkt + (i * sizeof(struct udcmd)) + (2 * sizeof(short)); ! 467: up->ud_cbusy[i] |= SENT; ! 468: up->ud_cbusy[i] &=~ NABBED; ! 469: i = rp->udip; /* initiate polling */ ! 470: } ! 471: ! 472: /* ! 473: * interrupt routine ! 474: */ ! 475: ! 476: long ud_spur; ! 477: long ud_npr; ! 478: #define INIRETRY 5 /* TQK50 botch -- needs time to settle */ ! 479: ! 480: ud0int(dev) ! 481: int dev; ! 482: { ! 483: register struct ud *up; ! 484: register struct device *rp; ! 485: register int i; ! 486: ! 487: up = &ud[dev]; ! 488: if (dev >= udcnt || (up->ud_flags & UINIT) == 0) { ! 489: printf("ud%d: stray intr\n", dev); ! 490: return; ! 491: } ! 492: rp = up->ud_addr; ! 493: if ((up->ud_flags & UIDONE) == 0) { ! 494: for (i = 0; i < INIRETRY; i++) ! 495: if (rp->udsa & (STEPS|ERR)) ! 496: break; ! 497: if ((rp->udsa & (STEPS|ERR)) == 0) { ! 498: printf("ud%d: init lost; sa 0%o\n", dev, rp->udsa); ! 499: return; ! 500: } ! 501: } ! 502: if (rp->udsa & ERR) { ! 503: printf("ud%d: hard error %o\n", dev, rp->udsa & 0177777); ! 504: udreset(dev); ! 505: return; ! 506: } ! 507: if (rp->udsa & STEPS) { ! 508: udinintr(dev); ! 509: return; ! 510: } ! 511: if (up->ud_comm->ud_bdp == 0 ! 512: && up->ud_comm->ud_cmdint == 0 ! 513: && up->ud_comm->ud_rspint == 0) ! 514: ud_spur++; ! 515: if (up->ud_comm->ud_bdp) { ! 516: ubmflush(udaddr[dev].ubno, up->ud_comm->ud_bdp); ! 517: up->ud_comm->ud_bdp = 0; ! 518: rp->udsa = 0; /* ack purge */ ! 519: ud_npr++; ! 520: } ! 521: while (up->ud_comm->ud_cmdint) { ! 522: up->ud_comm->ud_cmdint = 0; ! 523: udcmdscan(dev); ! 524: } ! 525: while (up->ud_comm->ud_rspint) { ! 526: up->ud_comm->ud_rspint = 0; ! 527: if (udpkscan(dev, 0)) ! 528: up->ud_flags &=~ UTIMER; ! 529: } ! 530: } ! 531: ! 532: /* ! 533: * free packets which are completely sent ! 534: * (and which don't have associated map) ! 535: */ ! 536: ! 537: udcmdscan(dev) ! 538: int dev; ! 539: { ! 540: register struct ud *up; ! 541: register int i, j; ! 542: register int freed; ! 543: register struct udcomm *udc; ! 544: ! 545: up = &ud[dev]; ! 546: udc = up->ud_comm; ! 547: freed = 0; ! 548: for (j = 0; j < NCMD; j++) ! 549: if (up->ud_back[j] >= 0 ! 550: && (udc->ud_cmd[j] & DPOWN) == 0) { ! 551: i = up->ud_back[j]; ! 552: if ((up->ud_cbusy[i] & (SENT|MAPPED)) == SENT) { ! 553: up->ud_cbusy[i] = FREE; ! 554: freed++; ! 555: } ! 556: up->ud_back[j] = NOBACK; ! 557: } ! 558: if (freed && up->ud_flags & UPWAIT) ! 559: wakeup((caddr_t)up->ud_cbusy); ! 560: return (freed); ! 561: } ! 562: ! 563: /* ! 564: * check for responses from the controller ! 565: * and deal with them ! 566: * return a count for debugging purposes ! 567: * called at spl6 (because of udtimer) ! 568: */ ! 569: ! 570: int ! 571: udpkscan(dev, doall) ! 572: int dev; ! 573: int doall; ! 574: { ! 575: register struct ud *up; ! 576: register int i; ! 577: int nf; ! 578: register struct udrsp *pk; ! 579: register struct ctab *cp; ! 580: register struct udcomm *udc; /* speed */ ! 581: ! 582: up = &ud[dev]; ! 583: udc = up->ud_comm; ! 584: nf = 0; ! 585: for (i = up->ud_rnext; ; i < NRSP-1 ? i++ : (i=0)) { ! 586: if (udc->ud_rsp[i] & DPOWN) { ! 587: up->ud_rnext = i; ! 588: /* don't stop if doall? */ ! 589: break; ! 590: } ! 591: nf++; ! 592: pk = &up->ud_rpkt[i]; ! 593: up->ud_credits += pk->ur_tc & MTNC; ! 594: if (up->ud_flags & UCWAIT) { ! 595: wakeup((caddr_t)&up->ud_credits); ! 596: up->ud_flags &=~ UCWAIT; ! 597: } ! 598: if (pk->ur_cid > MSMAXID) ! 599: printf("ud%d msg id %d\n", dev, pk->ur_cid); ! 600: else { ! 601: cp = &udctab[pk->ur_cid]; ! 602: switch (pk->ur_tc & MTYPE) { ! 603: case MTSEQL: ! 604: if (cp->c_seql) ! 605: (*cp->c_seql)(dev, cp->c_ctype, (struct msend *)pk->ur_data); ! 606: break; ! 607: ! 608: case MTDG: ! 609: if (cp->c_dg) ! 610: (*cp->c_dg)(dev, cp->c_ctype, pk->ur_data); ! 611: break; ! 612: ! 613: /* default: ignore */ ! 614: } ! 615: } ! 616: pk->ur_len = RSIZE; ! 617: udc->ud_rsp[i] |= DPOWN | DIE; ! 618: } ! 619: return (nf); ! 620: } ! 621: ! 622: /* ! 623: * timeout routine ! 624: * return any waiting packets ! 625: * -- callout routines don't necessarily run at high priority. ! 626: * hence the spl6, so udpkscan won't be reentered ! 627: */ ! 628: ! 629: int ud_kicked; ! 630: ! 631: udtimer(i) ! 632: int i; ! 633: { ! 634: register struct ud *up; ! 635: register int s; ! 636: ! 637: up = &ud[i]; ! 638: if ((up->ud_flags & UINIT) == 0) ! 639: return; ! 640: if (up->ud_flags & UIDONE) { ! 641: if ((up->ud_flags & UTIMER) == 0) ! 642: up->ud_flags |= UTIMER; ! 643: else { ! 644: s = spl6(); ! 645: if (udpkscan(i, 1) && up->ud_flags & UPWAIT) { ! 646: wakeup((caddr_t)up->ud_cbusy); ! 647: ud_kicked++; ! 648: } ! 649: splx(s); ! 650: up->ud_flags &=~ UTIMER; ! 651: } ! 652: } ! 653: timeout(udtimer, (caddr_t)i, TIMEOUT * HZ); ! 654: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.