Annotation of kernel/bsd/dev/ppc/drvApple96_SCSI/Apple96SCSI.h, revision 1.1.1.1

1.1       root        1: /*
                      2:  * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
                      3:  *
                      4:  * @APPLE_LICENSE_HEADER_START@
                      5:  * 
                      6:  * Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
                      7:  * Reserved.  This file contains Original Code and/or Modifications of
                      8:  * Original Code as defined in and that are subject to the Apple Public
                      9:  * Source License Version 1.1 (the "License").  You may not use this file
                     10:  * except in compliance with the License.  Please obtain a copy of the
                     11:  * License at http://www.apple.com/publicsource and read it before using
                     12:  * this file.
                     13:  * 
                     14:  * The Original Code and all software distributed under the License are
                     15:  * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
                     16:  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
                     17:  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
                     18:  * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
                     19:  * License for the specific language governing rights and limitations
                     20:  * under the License.
                     21:  * 
                     22:  * @APPLE_LICENSE_HEADER_END@
                     23:  */
                     24: 
                     25: /**
                     26:  * Copyright (c) 1994-1996 NeXT Software, Inc. All rights reserved. 
                     27:  * Copyright 1997 Apple Computer Inc. All Rights Reserved.
                     28:  * @author  Martin Minow    mailto:[email protected]
                     29:  * @revision   1997.02.13  Initial conversion from AMDPCSCSIDriver sources.
                     30:  *
                     31:  * Apple96SCSI.h - top-level API for Apple 53C96 (Curio/DBDMA) SCSI adaptor.
                     32:  * Apple96SCSI is closely based on Doug Mitchell's AMD 53C974/79C974 driver.
                     33:  *
                     34:  * Edit History
                     35:  * 1997.02.13  MM  Initial conversion from AMDPCSCSIDriver sources.
                     36:  * 1997.03.24  MM  Normalized against AppleMeshSCSI.h
                     37:  * 1997.11.12  MM  Added support for scatter-gather lists.
                     38:  */
                     39: #define APPLE96_ALWAYS_ASSERT          0
                     40: #define APPLE96_ENABLE_GET_SET         1
                     41: #define VERY_SERIOUS_DEBUGGING         0       /* Log each method entry and exit */
                     42: #define SERIOUS_DEBUGGING              0       /* Log command, buffer, etc operation */
                     43: #define LOG_COMMAND_AND_RESULT         0       /* Log command and results */
                     44: #define LOG_ERROR_RECOVERY             0       /* Log during error recovery */
                     45: #define LOG_RESULT_ON_ERROR            0       /* Log all errors, even expected ones */
                     46: #define DUMP_USER_BUFFER               0       /* Set to buffer length to dump */
                     47: #define TIMESTAMP_AT_IOCOMPLETE                0
                     48: /*
                     49:  * Radar 2005639. Tell SCSIDisk how many threads to create.
                     50:  */
                     51: #define MAX_CLIENT_THREADS             1       /* Ask SCSIDisk to create threads */
                     52: #define ENABLE_DISCONNECT              FALSE   /* TEMP TEMP TEMP */
                     53: #define ENABLE_TAGGED_QUEUEING         FALSE   /* TEMP TEMP TEMP */
                     54: // #define USE_CURIO_METHODS           1       /* TEMP TEMP TEMP */
                     55: #define USE_CURIO_METHODS              0       /* Default to inline */
                     56: #define WRITE_CHIP_OPERATION_INTO_TIMESTAMP_LOG 1
                     57: #define APPLE96_ENABLE_GET_SET         1       /* Enable management ioctl's */
                     58: #ifndef VERY_SERIOUS_DEBUGGING
                     59: #define VERY_SERIOUS_DEBUGGING         0
                     60: #endif
                     61: #ifndef USE_CURIO_METHODS
                     62: #define USE_CURIO_METHODS              0
                     63: #endif
                     64: #ifndef WRITE_CHIP_OPERATION_INTO_TIMESTAMP_LOG
                     65: #define WRITE_CHIP_OPERATION_INTO_TIMESTAMP_LOG 0
                     66: #endif
                     67: #ifndef APPLE96_ALWAYS_ASSERT
                     68: #define APPLE96_ALWAYS_ASSERT          0
                     69: #endif
                     70: #if VERY_SERIOUS_DEBUGGING
                     71: #undef SERIOUS_DEBUGGING
                     72: #define SERIOUS_DEBUGGING              1
                     73: #endif
                     74: #ifndef SERIOUS_DEBUGGING
                     75: #define SERIOUS_DEBUGGING              0
                     76: #endif
                     77: #ifndef LOG_COMMAND_AND_RESULT
                     78: #define LOG_COMMAND_AND_RESULT         0
                     79: #endif
                     80: #ifndef LOG_ERROR_RECOVERY
                     81: #define LOG_ERROR_RECOVERY             0
                     82: #endif
                     83: #ifndef TIMESTAMP_AT_IOCOMPLETE
                     84: #define TIMESTAMP_AT_IOCOMPLETE                0
                     85: #endif
                     86: #ifndef MAX_CLIENT_THREADS
                     87: #define MAX_CLIENT_THREADS             6       /* Radar 2005639 */
                     88: #endif
                     89: 
                     90: #undef NDEBUG  /* Enable ASSERT macro */
                     91: 
                     92: #import <driverkit/IODirectDevice.h>
                     93: #import <driverkit/IOSCSIController.h>
                     94: #import <driverkit/scsiTypes.h>
                     95: #import <driverkit/IOPower.h>
                     96: #import "Apple96Types.h"
                     97: #import "Apple96Curio.h"
                     98: #import "Apple96DBDMADefs.h"
                     99: #import "Timestamp.h"
                    100: #import "Apple96DDM.h"
                    101: 
                    102: @interface Apple96_SCSI : IOSCSIController <IOPower>
                    103: {
                    104:     /*
                    105:      * These globals locate the hardware interface registers in logical and physical
                    106:      * address spaces.
                    107:      */
                    108:     PhysicalAddress    gSCSIPhysicalAddress;   /* -> NCR registers (physical)  */
                    109:     volatile UInt8     *gSCSILogicalAddress;   /* -> NCR registers (logical)   */
                    110:     UInt32             gSCSIRegisterLength;    /* == Size of NCR registers     */
                    111:     
                    112:     PhysicalAddress    gDBDMAPhysicalAddress;  /* -> DBDMA registers (physical) */
                    113:     DBDMAChannelRegisters   *gDBDMALogicalAddress;  /* -> DBDMA registers (logical) */
                    114:     UInt32             gDBDMARegisterLength;   /* == Size of DBDMA registers   */
                    115: 
                    116:     PhysicalAddress    gDBDMAChannelAddress;   /* -> DBDMA channel area (physical) */
                    117:     DBDMADescriptor    *gChannelCommandArea;   /* -> DBDMA channel area (logical)  */
                    118:     UInt32             gChannelCommandAreaSize; /* == DBDMA channel allocated size */
                    119:     UInt32             gDBDMADescriptorMax;    /* Number of DATA descriptors   */
                    120:     
                    121:     UInt32             gPageSize;              /* Logical page size            */
                    122:     UInt32             gPageMask;              /* Logical page size - 1        */
                    123:     
                    124:     /*
                    125:      * Autosense I/O requests write into this permanently-allocated area. Then, we copy
                    126:      * the sense data to the caller's SCSI command parameter block. The autosense area
                    127:      * is at the end of the descriptor area.
                    128:      */
                    129:     esense_reply_t     *gAutosenseArea;        /* -> Autosense area (logical)  */
                    130:     PhysicalAddress    gAutosenseAddress;      /* Used to autosense DMA        */
                    131:     
                    132:     /*
                    133:      * This is the message port that the kernel uses to signal hardware interrupts.
                    134:      */
                    135:     port_t             gKernelInterruptPort;
                    136: 
                    137:     /*
                    138:      * Commands are passed from exported methods to the I/O thread
                    139:      * via gIncomingCommandQueue, which is protected by gIncomingCommandLock.
                    140:      * 
                    141:      * Commands which are disconnected but not complete are kept
                    142:      * in gDisconnectedCommandQueue.
                    143:      *
                    144:      * Commands which have been dequeued from gIncomingCommandQueue by the 
                    145:      * I/O thread but which have not been started because a command is currently
                    146:      * active on the bus are kept in gPendingCommandQueue. This queue also
                    147:      * holds commands pushed back when we lose arbitration.
                    148:      *
                    149:      * The currently active command, if any, is kept in gActiveCommand.
                    150:      * Only commandBufs with op == kCommandExecute are ever placed in
                    151:      * gActiveCommand.
                    152:      */
                    153:     queue_head_t       gIncomingCommandQueue;
                    154:     id                 gIncomingCommandLock;   /* NXLock for gIncomingCommandQueue */
                    155:     queue_head_t       gPendingCommandQueue;
                    156:     queue_head_t       gDisconnectedCommandQueue;
                    157:     /*
                    158:      * This is the command we're currently execution. If NULL, the Macintosh is idle
                    159:      * (or all commands are disconnected). Normally, gCurrentTarget and gCurrentLUN
                    160:      * track the values in the active command's associated SCSI request. They are set
                    161:      * to kInvalidTarget and kInvalidLUN at initialization, command deactivation,
                    162:      * command complete, and command disconnect. They are set to valid values (with no
                    163:      * active command) during reselection. This is tricky, so look carefully at the code.
                    164:      */ 
                    165:     CommandBuffer      *gActiveCommand;        /* -> The currently executing command */
                    166:     UInt32             gCurrentTarget;         /* == The current target bus ID */
                    167:     UInt32             gCurrentLUN;            /* == The current target LUN    */
                    168:        
                    169:     /*
                    170:      * Global option flags, accessible via instance table or setIntValues. Note: some of
                    171:      * these are intended for debugging. However, users may need to disable command
                    172:      * queuing, synchronous, or fast to handle device or bus limitations. The
                    173:      * architecture-specific initialization "looks" at the device to determine whether
                    174:      * specific features (such as synchronous) are supported.
                    175:      * gOptionAutoSenseEnable      Debug only, normally set
                    176:      * gOptionCmdQueueEnable       Enable tagged queuing.
                    177:      * gOptionSyncModeEnable       Enable synchronous transfers (clear if problems)
                    178:      * gOptionFastModeEnable       Enable fast transfers (clear if problems)
                    179:      * gOptionDisconnectEnable     Debug only, normally set.
                    180:      * gOptionExtendTiming     Extended selection timing (debug, unused)
                    181:      * gOptionSyncModeSupportedByHardware  Set if this NCR chip supports synchronous.
                    182:      * gOptionFastModeSupportedByHardware  Set if this NCR chip supports fast mode.
                    183:      * gFlagIOThreadRunning        Set when I/O thread is initialized. Needed
                    184:      *                 for shutdown.
                    185:      * gFlagBusBusy            Set in the state machine when we are apparently
                    186:      *                 connected to a target (even for target selection).
                    187:      *                 Cleared in the interrupt service routine on
                    188:      *                 a disconnected or bus reset interrupt state.
                    189:      * gFlagCheckForAnotherInterrupt   Set in the interrupt service routine on
                    190:      *                 entrance to the bus finite-state automaton.
                    191:      *                 Automaton functions clear this to force an
                    192:      *                 exit from the ISR when it expects a long bus
                    193:      *                 operation.
                    194:      */
                    195:     unsigned   gOptionAutoSenseEnable          : 1,
                    196:                gOptionCmdQueueEnable           : 1,
                    197:                gOptionSyncModeEnable           : 1,
                    198:                gOptionFastModeEnable           : 1,
                    199:                gOptionExtendTiming             : 1,
                    200:                gOptionDisconnectEnable         : 1,
                    201:                gOptionSyncModeSupportedByHardware : 1,
                    202:                gOptionFastModeSupportedByHardware : 1,
                    203:                gFlagIOThreadRunning            : 1,    /* Set at init          */
                    204:                gFlagBusBusy                    : 1,
                    205:                gFlagCheckForAnotherInterrupt   : 1,    /* Loop through ISR     */
                    206:                gFlagNeedAnotherInterrupt       : 1,    /* mikej                */
                    207:                pad                             : 20;
                    208:     UInt32     scsiClockRate;              /* in MHz       */
                    209: 
                    210:     /*
                    211:      * Array of active I/O counters, one counter per lun per target. If command
                    212:      * queueing is disabled, the max value of each counter is 1. gActiveCount is
                    213:      * the sum of all elements in activeArray.
                    214:      */
                    215:     UInt8      gActiveArray[SCSI_NTARGETS][SCSI_NLUNS];
                    216:     UInt32     gActiveCount;   
                    217: 
                    218:     /*
                    219:      * These variables change during SCSI I/O operation.
                    220:      */
                    221:     BusState           gBusState;          /* The overall state automaton  */
                    222:     /*
                    223:      * These variables manage Message In bus phase. Because the message in handler
                    224:      * uses programmed I/O, gMsgInCount, gMsgInIndex, and gMsgInState are actually
                    225:      * local variables to the message reader, and are here for debugging convenience.
                    226:      */
                    227:     signed int         gMsgInCount;    /* Message bytes still to read  */
                    228:     MsgInState         gMsgInState;    /* How are we handling messages */
                    229:     UInt32             gMsgInIndex;    /* -> free spot in gMsgInBuffer */
                    230:     UInt8              gMsgInBuffer[kMessageInBufferLength];
                    231:     /*
                    232:      * These variables manage Message Out bus phase. To send a message,
                    233:      * write it into the buffer, starting at gMsgOutSendIndex and set ATN.
                    234:      * When the target enters Message Out phase, write the bytes into the
                    235:      * NCR fifo starting at gMsgOutStoreIndex. When the transfer completes
                    236:      * with MSG parity error, use the residual count to "back up" gMsgOutSendIndex
                    237:      * to resend the message.
                    238:      */
                    239:     UInt8      *gMsgOutPtr;        /* -> free spot in gMsgOutBuffer    */
                    240:     UInt8      *gMsgPutPtr;        /* -> next byte to send     */
                    241:     UInt8      gMsgOutBuffer[kMessageOutBufferLength];
                    242:     UInt8      gLastMsgOut[2];     /* Has last message out specifier   */
                    243:     /*
                    244:      * Hardware related variables
                    245:      */
                    246:     UInt8      gInitiatorID;       /* Our SCSI ID          */
                    247:     UInt8      gInitiatorIDMask;   /* BusID bitmask for selection  */
                    248: //    UInt8    gSelectionTimeout;  /* In MESH 10 msec units    */
                    249:     UInt32     gClockFrequency;    /* For selection timeout    */
                    250:     
                    251:     /*
                    252:      * commandBuf->queueTag for next I/O. This is never zero; for all requests
                    253:      * involving a T/L/Q nexus, a queue tag of zero indicates a nontagged command.
                    254:      */
                    255:     UInt8      gNextQueueTag;
                    256:     
                    257:     /*
                    258:      * Shadow current chip values.
                    259:      * gSaveStatus     rSTA register at last interrupt.
                    260:      * gSaveSeqStep        rSQS register at last interrupt
                    261:      * gSaveInterrupt      rINT register at last interrupt.
                    262:      * gCurrentBusPhase    The last bus phase (for error recovery)
                    263:      * gLastSelectionTimeout   Last selection timeout we stored in Curio
                    264:      * gLastSynchronousOffset  Last synchronous offset we stored in Curio.
                    265:      * gLastSynchronousPeriod  Last synchronous perioud we stored in Curio.
                    266:      */
                    267:     UInt8      gSaveStatus;
                    268:     UInt8      gSaveSeqStep;
                    269:     UInt8      gSaveInterrupt;
                    270:     UInt8      gCurrentBusPhase;   /* Last known SCSI phase    */
                    271:     UInt8      gLastSelectionTimeout;  /* Chip selection timeout   */
                    272:     UInt8      gLastSynchronousOffset; /* Synchronous transfer offset  */
                    273:     UInt8      gLastSynchronousPeriod; /* Synchronous period shadow    */
                    274:     /*
                    275:      * Per-target information.
                    276:      */
                    277:     PerTargetData   gPerTargetData[SCSI_NTARGETS];
                    278:        
                    279:     /*
                    280:      * Statistics support.
                    281:      */
                    282:     UInt32     gMaxQueueLen;
                    283:     UInt32     gQueueLenTotal;
                    284:     UInt32     gTotalCommands;
                    285: }
                    286: 
                    287: /**
                    288:  * Public methods
                    289:  */
                    290: /**
                    291:  * Initialize the SCSI driver.
                    292:  */
                    293: + (Boolean) probe   : deviceDescription;
                    294: /**
                    295:  * Shutdown the driver.
                    296:  */
                    297: - free;
                    298: 
                    299: /*
                    300:  * Execute a SCSI request using a single logical address range.
                    301:  * This is compatible with earlier DriverKit implementations.
                    302:  * @param scsiReq   The SCSI request command record, including the
                    303:  *     target device and LUN, the command to execute,
                    304:  *     and various control flags.
                    305:  * @param buffer    The data buffer, if any. This may be NULL if
                    306:  *     no data phase is expected.
                    307:  * @param client    The client task that "owns" the memory buffer.
                    308:  * @return  Return a bus adaptor specific error status.
                    309:  */
                    310: - (sc_status_t) executeRequest : (IOSCSIRequest *) scsiReq 
                    311:         buffer                 : (void *) buffer 
                    312:         client                 : (vm_task_t) client;
                    313: 
                    314: /*
                    315:  * Execute a SCSI request using an IOMemoryDescriptor. This allows callers to
                    316:  * provide (kernel-resident) logical scatter-gather lists. For compatibility
                    317:  * with existing implementations, the low-level SCSI device driver must first
                    318:  * ensure that executeRequest:ioMemoryDescriptor is supported by executing:
                    319:  *  [controller respondsToSelector : executeRequest:ioMemoryDescriptor]
                    320:  * @param scsiReq   The SCSI request command record, including the
                    321:  *     target device and LUN, the command to execute,
                    322:  *     and various control flags.
                    323:  * @param ioMemoryDescriptor The data buffer(s), if any. This may be NULL if
                    324:  *     no data phase is expected.
                    325:  * @param client    The client task that "owns" the memory buffer.
                    326:  * @return  Return a bus adaptor specific error status.
                    327:  */
                    328: - (sc_status_t) executeRequest
                    329:                                : (IOSCSIRequest *) scsiReq 
                    330:     ioMemoryDescriptor         : (IOMemoryDescriptor *) ioMemoryDescriptor;
                    331: 
                    332: - (sc_status_t) resetSCSIBus;
                    333: 
                    334: /**
                    335:  * Reset the SCSI bus.
                    336:  */
                    337: - (sc_status_t) resetSCSIBus;
                    338: /**
                    339:  * Reset statistics buffers.
                    340:  */
                    341: - (void)    resetStats;
                    342: 
                    343: - (unsigned)   numQueueSamples;
                    344: 
                    345: - (unsigned)   sumQueueLengths;
                    346: 
                    347: - (unsigned)   maxQueueLength;
                    348: 
                    349: /**
                    350:  * interruptOccurred is a public method called by the I/O
                    351:  * thread in IODirectDevice when an interrupt occurs.
                    352:  */
                    353: - (void)    interruptOccurred;
                    354: 
                    355: /**
                    356:  * timeoutOccurred is a public method called by the I/O
                    357:  * thread in IODirectDevice when it receives a timeout
                    358:  * message.
                    359:  */
                    360: - (void)    timeoutOccurred;
                    361: 
                    362: #if APPLE96_ENABLE_GET_SET
                    363: 
                    364: - (IOReturn) setIntValues      : (unsigned *)parameterArray
                    365:           forParameter         : (IOParameterName)parameterName
                    366:              count             : (unsigned)count;
                    367: - (IOReturn) getIntValues      : (unsigned *)parameterArray
                    368:           forParameter         : (IOParameterName)parameterName
                    369:              count             : (unsigned *)count;    // in/out
                    370: /*
                    371:  * get/setIntValues parameters.
                    372:  */
                    373: #define APPLE96_AUTOSENSE              "AutoSense"
                    374: #define APPLE96_CMD_QUEUE              "CmdQueue"
                    375: #define APPLE96_SYNC                   "Synchronous"
                    376: #define APPLE96_FAST_SCSI              "FastSCSI"
                    377: #define APPLE96_RESET_TARGETS          "ResetTargets"
                    378: #define APPLE96_RESET_TIMESTAMP                "ResetTimestamp"
                    379: #define APPLE96_ENABLE_TIMESTAMP       "EnableTimestamp"
                    380: #define APPLE96_DISABLE_TIMESTAMP      "DisableTimestamp"
                    381: #define APPLE96_PRESERVE_FIRST_TIMESTAMP "PreserveFirstTimestamp"
                    382: #define APPLE96_PRESERVE_LAST_TIMESTAMP        "PreserveLastTimestamp"
                    383: #define APPLE96_READ_TIMESTAMP         "ReadTimestamp"
                    384: #define APPLE96_STORE_TIMESTAMP                "StoreTimestamp"
                    385: /*
                    386:  * Radar 2005639. Tell SCSIDisk how many threads to create.
                    387:  * Allow access to the enable disconnect option.
                    388:  */
                    389: #define APPLE96_ENABLE_DISCONNECT      "EnableDisconnect"
                    390: #define APPLE_MAX_THREADS              "ClientThreads"
                    391: 
                    392: /*
                    393:  * Recording and setting timestamps may be done using getIntValues (this permits
                    394:  * access from non-privileged tasks.
                    395:  *  ResetTimestamp     Clear the timestamp vector - do this before starting
                    396:  *                     a sequence (no parameters)
                    397:  *  EnableTimestamp    Start recording (no parameters) (default)
                    398:  *  DisableTimestamp   Stop recording (no parameters)
                    399:  *  PreserveFirstTimestamp  Stop recording when the buffer fills (until it is emptied)
                    400:  *  PreserveLastTimestamp   Discard old values when new arrive (default)
                    401:  *  ReadTimestamp      Read a vector of timestamps (see sample below)
                    402:  *  StoreTimestamp     Store a timestamp (from user mode) (see sample below)
                    403:  * ReadTimestamp copies timestamps from the internal database to user-specified vector.
                    404:  * Because getIntValues parameters are defined in int units, the code is slighthly
                    405:  * non-obvious:
                    406:  *  TimestampDataRecord myTimestamps[123];
                    407:  *  unsigned       count;
                    408:  *  count = sizeof (myTimestamps) / sizeof (unsigned);
                    409:  *  [scsiDevice getIntValues
                    410:  *                     : (unsigned int *) myTimestamps
                    411:  *     forParameter    : APPLE96_READ_TIMESTAMP
                    412:  *     count           : &count
                    413:  *  ];
                    414:  *  count = (count * sizeof (unsigned)) / sizeof (TimestampDataRecord);
                    415:  *  for (i = 0; i < count; i++) {
                    416:  *                     Process(myTimestamps[i]);
                    417:  *  }
                    418:  * Applications can store timestamps using one of three parameter formats:
                    419:  *  unsigned       paramVector[4];
                    420:  * Tag only -- the library will supply the event time
                    421:  *  paramVector[0] = kMyTagValue;
                    422:  *  [scsiDevice getIntValues
                    423:  *                     : paramVector
                    424:  *     forParameter    : "StoreTimestamp"
                    425:  *     count           : 1
                    426:  *  ];
                    427:  * Tag plus value:
                    428:  *  paramVector[0] = kMyTagValue;
                    429:  *  paramVector[1] = 123456;
                    430:  *  [scsiDevice getIntValues
                    431:  *                     : paramVector
                    432:  *     forParameter    : "StoreTimestamp"
                    433:  *     count           : 2
                    434:  *  ];
                    435:  * Tag plus value + time:
                    436:  *  paramVector[0] = kMyTagValue;
                    437:  *  paramVector[1] = 123456;
                    438:  *  IOGetTimestamp((ns_time_t *) &paramVector[2]);
                    439:  *  [scsiDevice getIntValues
                    440:  *                     : paramVector
                    441:  *     forParameter    : "StoreTimestamp"
                    442:  *     count           : 4
                    443:  *  ];
                    444:  * Note that you can combine tag only with tag plus value plus time to measure
                    445:  * user->device latency.
                    446:  */
                    447: 
                    448: #endif /* APPLE96_ENABLE_GET_SET */
                    449: 
                    450: @end
                    451: 
                    452: 

unix.superglobalmegacorp.com

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