Annotation of kernel/bsd/dev/ppc/drvPPCATA/IdeCntDma.m, revision 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: // Copyright 1997 by Apple Computer, Inc., all rights reserved.
        !            26: /*     Copyright (c) 1991-1997 NeXT Software, Inc.  All rights reserved. 
        !            27:  *
        !            28:  * IdeCntDma.m - Implementation for Dma category of Ide Controller device.
        !            29:  *
        !            30:  */
        !            31: 
        !            32: #import <driverkit/generalFuncs.h>
        !            33: #import <driverkit/kernelDriver.h>
        !            34: #import <driverkit/ppc/IODBDMA.h>
        !            35: #import <driverkit/align.h>
        !            36: #import <machdep/ppc/powermac.h>
        !            37: #import <sys/systm.h>
        !            38: 
        !            39: #import "IdeCntDma.h"
        !            40: #import "io_inline.h"
        !            41: 
        !            42: extern         IOReturn                kmem_alloc_wired();
        !            43: 
        !            44: static         void                    *bitBucketAddr;
        !            45: static         uint                    bitBucketPhysAddr;              
        !            46: 
        !            47: @implementation IdeController(Dma)
        !            48: 
        !            49: /*
        !            50:  * Called once during initialization to figure out if the hardware 
        !            51:  * supports dma
        !            52:  */
        !            53: 
        !            54: - (ide_return_t) initIdeDma:(unsigned int)unit 
        !            55: {
        !            56:     ideIoReq_t                 ideIoReq;
        !            57:     vm_offset_t                tempDmaBuf;
        !            58:     ide_return_t               status;
        !            59: 
        !            60:     /*
        !            61:      * The hardware claims to supports DMA. Verify that by trying to read a
        !            62:      * few sectors. 
        !            63:      */
        !            64:     tempDmaBuf = (vm_offset_t)IOMalloc(PAGE_SIZE);
        !            65:     if (tempDmaBuf == NULL)    
        !            66:     {
        !            67:        _dmaSupported[unit] = NO;
        !            68:        return IDER_REJECT;
        !            69:     }
        !            70:     
        !            71:     bzero((unsigned char *)&ideIoReq, sizeof(ideIoReq_t));
        !            72:     ideIoReq.cmd = IDE_READ_DMA;
        !            73:     ideIoReq.block = 0;
        !            74:     ideIoReq.blkcnt = (PAGE_SIZE/IDE_SECTOR_SIZE);
        !            75:     ideIoReq.addr = (caddr_t)tempDmaBuf;
        !            76:     ideIoReq.timeout = 5000;
        !            77:     ideIoReq.map = (struct vm_map *)IOVmTaskSelf();
        !            78:     
        !            79:     [self setTransferRate: unit UseDMA: YES];
        !            80: 
        !            81:     if ([self ideDmaRwCommon:(ideIoReq_t *)&ideIoReq] == IDER_SUCCESS)
        !            82:     {
        !            83:        status = IDER_SUCCESS;
        !            84:     } 
        !            85:     else 
        !            86:     {
        !            87:        _dmaSupported[unit] = NO;
        !            88:        status = IDER_REJECT;
        !            89:     }
        !            90:     
        !            91:     IOFree((void *)tempDmaBuf, PAGE_SIZE);
        !            92:     
        !            93:     return status;
        !            94: }
        !            95: 
        !            96: - (ide_return_t) allocDmaMemory
        !            97: {
        !            98:     IOReturn   rc;
        !            99: 
        !           100:     if ( !_ideDMACommands )
        !           101:     {
        !           102:         rc = kmem_alloc_wired(IOVmTaskSelf(), (vm_offset_t *) &_ideDMACommands, PAGE_SIZE );
        !           103: 
        !           104:         if ( rc != KERN_SUCCESS  )
        !           105:        {
        !           106:           return IDER_REJECT;
        !           107:         }
        !           108:        rc = IOPhysicalFromVirtual( IOVmTaskSelf(), (vm_offset_t)_ideDMACommands, (vm_offset_t *)&_ideDMACommandsPhys );
        !           109:         if ( rc != IO_R_SUCCESS )
        !           110:        {
        !           111:             return IDER_REJECT;
        !           112:         }
        !           113:     }   
        !           114:     
        !           115:     if ( !bitBucketAddr )
        !           116:     {
        !           117:         bitBucketAddr = IOMalloc(32);
        !           118:         bitBucketAddr = IOAlign(void *, bitBucketAddr, 16);
        !           119:         rc = IOPhysicalFromVirtual( IOVmTaskSelf(), (vm_offset_t) bitBucketAddr, (vm_offset_t *)&bitBucketPhysAddr );
        !           120:         if ( rc != IO_R_SUCCESS )
        !           121:        {
        !           122:             return IDER_REJECT;
        !           123:         }
        !           124:     }
        !           125: 
        !           126:     return IDER_SUCCESS;
        !           127: }
        !           128: 
        !           129: 
        !           130: - (ide_return_t) ideDmaRwCommon: (ideIoReq_t *)ideIoReq
        !           131: {
        !           132:     vm_offset_t                startAddr = (vm_offset_t)ideIoReq->addr;
        !           133:     unsigned                   blocksToGo = ideIoReq->blkcnt;
        !           134:     unsigned                   startBlock = ideIoReq->block;
        !           135:     ide_return_t               rc = IDER_SUCCESS;
        !           136:     BOOL                       fRead = (ideIoReq->cmd == IDE_READ_DMA);
        !           137: 
        !           138:     /*
        !           139:      * Do read or write DMA. 
        !           140:      */
        !           141:     ideIoReq->regValues = [self logToPhys:startBlock numOfBlocks:blocksToGo];
        !           142: 
        !           143:     rc = [self setupDMA: startAddr client:(vm_task_t)ideIoReq->map length:blocksToGo * IDE_SECTOR_SIZE fRead:(BOOL)fRead];
        !           144:     if ( rc != IDER_SUCCESS )
        !           145:     {
        !           146:         return ( rc );
        !           147:     }  
        !           148: 
        !           149:     rc = [self ideReadWriteDma: &ideIoReq->regValues fRead:fRead]; 
        !           150: 
        !           151:     return(rc);        
        !           152: }
        !           153: 
        !           154: - (ide_return_t) ideReadWriteDma:(ideRegsVal_t *)ideRegs fRead:(BOOL)read
        !           155: {
        !           156:     ide_return_t               rtn;
        !           157:     ideRegsAddrs_t             *rp = &_ideRegsAddrs;
        !           158:     unsigned char              status;
        !           159:     unsigned long              cfgReg;
        !           160: 
        !           161:     rtn = [self waitForDeviceReady];
        !           162:     if (rtn != IDER_SUCCESS) 
        !           163:     {
        !           164:        return (rtn);
        !           165:     }
        !           166: 
        !           167:     outb(rp->drHead,  ideRegs->drHead);
        !           168:     outb(rp->sectNum, ideRegs->sectNum);
        !           169:     outb(rp->sectCnt, ideRegs->sectCnt);
        !           170:     outb(rp->cylLow,  ideRegs->cylLow);
        !           171:     outb(rp->cylHigh, ideRegs->cylHigh);
        !           172:     
        !           173:     [self enableInterrupts];
        !           174:     outb(rp->command, (read ? IDE_READ_DMA : IDE_WRITE_DMA));
        !           175: 
        !           176:     if ( _controllerType != kControllerTypeCmd646X )
        !           177:     {
        !           178:         IODBDMAContinue( _ideDMARegs );        
        !           179:     }
        !           180:     else
        !           181:     {
        !           182:         [[self deviceDescription] configReadLong:0x70 value: &cfgReg];
        !           183:         cfgReg &= ~0x08;
        !           184:         cfgReg |= 0x01 | ((read) ? 0x08 : 0x00);
        !           185:         [[self deviceDescription] configWriteLong:0x70 value: cfgReg];
        !           186:     }
        !           187: 
        !           188:     rtn = [self ideWaitForInterrupt:(read ? IDE_READ_DMA : IDE_WRITE_DMA)
        !           189:                        ideStatus: &status];
        !           190: 
        !           191: #if 0
        !           192:   {
        !           193:     uint       dmaStatus;
        !           194: 
        !           195:     dmaStatus = IOGetDBDMAChannelStatus( _ideDMARegs );
        !           196:     kprintf( "DMA Status = %04x IDE Status = %02x\n\r", dmaStatus, status );
        !           197:   }
        !           198: #endif
        !           199: 
        !           200:     if ( _controllerType != kControllerTypeCmd646X )
        !           201:     {
        !           202:         IODBDMAStop( _ideDMARegs );
        !           203:     }
        !           204:     else
        !           205:     {
        !           206:         [[self deviceDescription] configReadLong: 0x70 value: &cfgReg];
        !           207:         cfgReg &= ~0x01;
        !           208:         [[self deviceDescription] configWriteLong:0x70 value: cfgReg];
        !           209:     }
        !           210:         
        !           211:     if ( (rtn == IDER_SUCCESS) ) 
        !           212:     {
        !           213:        if ((rtn = [self waitForDeviceReady]) == IDER_SUCCESS) 
        !           214:         {
        !           215:            if (status & (ERROR | WRITE_FAULT)) 
        !           216:             {
        !           217:                [self getIdeRegisters:ideRegs Print:"Read Dma error"];
        !           218:                return IDER_CMD_ERROR;
        !           219:            }
        !           220:            
        !           221:            /*
        !           222:            if (status & ERROR_CORRECTED) {
        !           223:                IOLog("%s: Error during data transfer (corrected).\n", 
        !           224:                            [self name]);
        !           225:            }
        !           226:            */
        !           227:        } 
        !           228:         else 
        !           229:         {
        !           230:            return IDER_CMD_ERROR;
        !           231:        }
        !           232:        
        !           233:     } 
        !           234:     else 
        !           235:     {
        !           236:        [self getIdeRegisters:ideRegs Print:NULL];
        !           237:        IODBDMAReset( _ideDMARegs );
        !           238:        return IDER_CMD_ERROR;
        !           239:     }
        !           240: 
        !           241:     return (rtn);
        !           242: }
        !           243: 
        !           244: /*
        !           245:  *
        !           246:  *
        !           247:  */
        !           248: -(ide_return_t) setupDMA:(vm_offset_t) startAddr client:(vm_task_t) client length:(int)length fRead:(BOOL)fRead 
        !           249: {
        !           250:     ide_return_t               rc;
        !           251: 
        !           252:     if ( _controllerType == kControllerTypeCmd646X )
        !           253:     {
        !           254:         rc = [self setupDMAList: startAddr client:client length:length fRead:fRead];
        !           255:     }
        !           256:     else
        !           257:     {
        !           258:         rc = [self setupDBDMA: startAddr client:client length:length fRead:fRead];
        !           259:     }
        !           260:     return rc;
        !           261: }
        !           262: 
        !           263: -(ide_return_t) setupDBDMA:(vm_offset_t) startAddr client:(vm_task_t) client length:(int)length fRead:(BOOL)fRead 
        !           264: {
        !           265:     uint               count;
        !           266:     uint               addr;
        !           267:     uint               physAddr;
        !           268:     uint               len;
        !           269:     uint               bytesLeft;
        !           270:     uint               i;
        !           271:     IOReturn           rc;
        !           272:     uint                maxDescriptors;
        !           273: 
        !           274:     /*
        !           275:      * This is for the benefit of - getTransferCount in case of a zero length transfer.
        !           276:      */
        !           277:     bzero( (unsigned char *)_ideDMACommands, sizeof(IODBDMADescriptor) );
        !           278: 
        !           279:     if ( length == 0 )
        !           280:     {
        !           281:        return IDER_SUCCESS;
        !           282:     }  
        !           283: 
        !           284:     bytesLeft = length;
        !           285:     count     = PAGE_SIZE - (startAddr & (PAGE_SIZE-1));
        !           286:     addr      = startAddr;
        !           287:  
        !           288:     maxDescriptors = MAX_IDE_DESCRIPTORS - 1 - ((length & 1) ? 1 : 0);
        !           289:    
        !           290:     for ( i=0; i < maxDescriptors && bytesLeft; i++ )
        !           291:     {
        !           292:        len = ( count > bytesLeft ) ? bytesLeft : count;
        !           293: 
        !           294:        rc = IOPhysicalFromVirtual( client, trunc_page(addr), &physAddr );
        !           295:         if ( rc != IO_R_SUCCESS )
        !           296:         {
        !           297:             IOLog("IDE Disk - IOPhysicalFromVirtual rc = %d\n\r", rc);
        !           298:             return IDER_MEMFAIL;
        !           299:         }
        !           300:         physAddr = (physAddr & ~(PAGE_SIZE-1)) | (addr & (PAGE_SIZE-1));
        !           301: 
        !           302:         IOMakeDBDMADescriptor( &_ideDMACommands[i],
        !           303:                               ((fRead) ? kdbdmaInputMore : kdbdmaOutputMore),
        !           304:                               kdbdmaKeyStream0,
        !           305:                               kdbdmaIntNever,
        !           306:                               kdbdmaBranchNever,
        !           307:                               kdbdmaWaitNever,
        !           308:                               len,
        !           309:                               physAddr );
        !           310:         bytesLeft -= len;
        !           311:         addr      += len;
        !           312:         count      = PAGE_SIZE;
        !           313:     }
        !           314: 
        !           315:     /*
        !           316:      * Note: ATAPI always transfers even byte-counts. Send the extra byte to/from the bit-bucket
        !           317:      *       if the requested transfer length is odd.
        !           318:      */
        !           319:     if ( length & 1 )
        !           320:     {
        !           321:         IOMakeDBDMADescriptor( &_ideDMACommands[i++],
        !           322:                               ((fRead) ? kdbdmaInputMore : kdbdmaOutputMore),
        !           323:                               kdbdmaKeyStream0,
        !           324:                               kdbdmaIntNever,
        !           325:                               kdbdmaBranchNever,
        !           326:                               kdbdmaWaitNever,
        !           327:                               1,
        !           328:                               bitBucketPhysAddr );
        !           329:     }
        !           330: 
        !           331: 
        !           332:     if ( bytesLeft )
        !           333:     {
        !           334:         IOLog("IDE I/O request too large - Length = %d\n\r", length);
        !           335:         return IDER_REJECT;
        !           336:     }
        !           337:  
        !           338:     IOMakeDBDMADescriptor( &_ideDMACommands[i],
        !           339:                           kdbdmaStop,
        !           340:                           kdbdmaKeyStream0,
        !           341:                           kdbdmaIntNever,
        !           342:                           kdbdmaBranchNever,
        !           343:                           kdbdmaWaitNever,
        !           344:                           0,
        !           345:                           0  );
        !           346:  
        !           347:    IOSetDBDMACommandPtr( _ideDMARegs, _ideDMACommandsPhys );
        !           348:        
        !           349:    return IDER_SUCCESS;
        !           350: }           
        !           351:  
        !           352: -(ide_return_t) setupDMAList:(vm_offset_t) startAddr client:(vm_task_t) client length:(int)length fRead:(BOOL)fRead 
        !           353: {
        !           354:     uint               count;
        !           355:     uint               addr;
        !           356:     uint               physAddr;
        !           357:     uint               len;
        !           358:     uint               bytesLeft;
        !           359:     uint               i;
        !           360:     IOReturn           rc;
        !           361:     uint                maxDescriptors;
        !           362:     ideDMAList_t       *ideDMAList;
        !           363: 
        !           364:     if ( length == 0 )
        !           365:     {
        !           366:        return IDER_SUCCESS;
        !           367:     }  
        !           368: 
        !           369:     bytesLeft = length;
        !           370:     count     = PAGE_SIZE - (startAddr & (PAGE_SIZE-1));
        !           371:     addr      = startAddr;
        !           372:  
        !           373:     maxDescriptors = MAX_IDE_DESCRIPTORS * 2;
        !           374:    
        !           375:     ideDMAList = (ideDMAList_t *) _ideDMACommands;
        !           376:     for ( i=0; i < maxDescriptors && bytesLeft; i++ )
        !           377:     {
        !           378:        len = ( count > bytesLeft ) ? bytesLeft : count;
        !           379: 
        !           380:        rc = IOPhysicalFromVirtual( client, trunc_page(addr), &physAddr );
        !           381:         if ( rc != IO_R_SUCCESS )
        !           382:         {
        !           383:             IOLog("IDE Disk - IOPhysicalFromVirtual rc = %d\n\r", rc);
        !           384:             return IDER_MEMFAIL;
        !           385:         }
        !           386:         physAddr = (physAddr & ~(PAGE_SIZE-1)) | (addr & (PAGE_SIZE-1));
        !           387: 
        !           388:         ideDMAList[i].start  = EndianSwap32( physAddr );
        !           389:         ideDMAList[i].length = EndianSwap32( len );
        !           390: 
        !           391:         bytesLeft -= len;
        !           392:         addr      += len;
        !           393:         count      = PAGE_SIZE; 
        !           394:     }
        !           395: 
        !           396:     if ( bytesLeft )
        !           397:     {
        !           398:         IOLog("IDE I/O request too large - Length = %d\n\r", length);
        !           399:         return IDER_REJECT;
        !           400:     }
        !           401: 
        !           402:     ideDMAList[i-1].length |= EndianSwap32( 0x80000000 );
        !           403: 
        !           404:     [[self deviceDescription] configWriteLong:0x74 value: _ideDMACommandsPhys];
        !           405:        
        !           406:     return IDER_SUCCESS;
        !           407: }           
        !           408: 
        !           409: 
        !           410: /*
        !           411:  * This routine makes sure the DMA channel has shutdown at the end of an 
        !           412:  * I/O request and obtains tha actual byte count transferred.
        !           413:  */
        !           414: - (uint) stopDBDMA
        !           415: {
        !           416:     uint       dmaStatus;
        !           417:     uint        xferCount;
        !           418:  
        !           419:     dmaStatus = IOGetDBDMAChannelStatus( _ideDMARegs );
        !           420:     
        !           421:     if ( dmaStatus & (kdbdmaDead | kdbdmaActive) )
        !           422:     {
        !           423:         if ( IsPowerStar() )
        !           424:         {
        !           425:             [self fixOHareDMACorruption_1];
        !           426:         }
        !           427:         else
        !           428:         {
        !           429:             IODBDMAReset( _ideDMARegs );
        !           430:         }
        !           431:     }
        !           432: 
        !           433:     xferCount = [self getDBDMATransferCount];
        !           434:     
        !           435:     if ( dmaStatus & (kdbdmaDead | kdbdmaActive) )
        !           436:     {
        !           437:         if ( IsPowerStar() )
        !           438:         {
        !           439:             [self fixOHareDMACorruption_2];
        !           440:         }
        !           441:     }
        !           442:     return xferCount;
        !           443: }
        !           444: 
        !           445: /*
        !           446:  * The OHare DMA controller and its predecessor Grand Central have a nasty habit of 
        !           447:  * clobbering bytes prior to the data buffer if the buffer is not 16-byte aligned.
        !           448:  *
        !           449:  * To fix this we check for a non-aligned DMA read operation and save the bytes
        !           450:  * prior to the start of the data buffer, and them restore them after the DMA channel
        !           451:  * is shutdown.
        !           452:  */
        !           453: 
        !           454: - (void) fixOHareDMACorruption_1
        !           455: {
        !           456:     IOReturn           rc;
        !           457:     uint               i;
        !           458:     uint               physAddr;
        !           459:     uint               virtAddr;
        !           460:     unsigned char      *p;
        !           461:     BOOL               resetDone = NO;
        !           462:     uint               dmaOp;
        !           463:     unsigned char       saveUs[16];
        !           464:     uint                len;
        !           465: 
        !           466:     do 
        !           467:     {
        !           468:         dmaOp = IOGetCCOperation(&_ideDMACommands[0])  >> 28; 
        !           469:         if ( dmaOp != kdbdmaInputMore )
        !           470:         {
        !           471:             continue;
        !           472:         }
        !           473: 
        !           474:         physAddr = IOGetCCAddress(&_ideDMACommands[0]);
        !           475:         if ( !(physAddr & 0x0000000F) )
        !           476:         {
        !           477:             continue;
        !           478:         }
        !           479: 
        !           480:         rc = IOMapPhysicalIntoIOTask( (vm_offset_t)   trunc_page(physAddr),
        !           481:                                                       PAGE_SIZE,
        !           482:                                       (vm_offset_t *) &virtAddr );
        !           483:         if ( rc != IO_R_SUCCESS )
        !           484:         {
        !           485:             continue;
        !           486:         }
        !           487: 
        !           488:         p =   (unsigned char *) ( (virtAddr & ~(PAGE_SIZE-1)) | (physAddr & 0xFF0) );
        !           489:         len = (physAddr & 0x00F);
        !           490: 
        !           491:         for ( i=0; i < len; i++ )
        !           492:         {
        !           493:             saveUs[i] = p[i];
        !           494:         }
        !           495: 
        !           496:         IODBDMAReset( _ideDMARegs );
        !           497:         resetDone = YES;
        !           498: 
        !           499:         for ( i=0; i < len; i++ )
        !           500:         {
        !           501:             p[i] = saveUs[i];
        !           502:         }
        !           503: 
        !           504:         IOUnmapPhysicalFromIOTask( (vm_offset_t)   trunc_page(virtAddr),
        !           505:                                                    PAGE_SIZE );
        !           506:     }
        !           507:     while( 0 );     
        !           508:  
        !           509:     if ( resetDone == NO )
        !           510:     {
        !           511:         IODBDMAReset( _ideDMARegs );
        !           512:     }
        !           513: }    
        !           514: 
        !           515: 
        !           516: /*
        !           517:  * The OHare II controller has a bug where if there is a pending DMA write operation
        !           518:  * that was not accepted by the device, it 'remembers' is and inserts an spurious 
        !           519:  * DMA write to the device on the next write operation. This results in the data
        !           520:  * for the next write operation to be shifted foward by two bytes.
        !           521:  *
        !           522:  */
        !           523: 
        !           524: - (void) fixOHareDMACorruption_2
        !           525: {
        !           526:     uint               dmaOp;
        !           527: 
        !           528:     dmaOp = IOGetCCOperation(&_ideDMACommands[0])  >> 28; 
        !           529:     if ( dmaOp == kdbdmaOutputMore )
        !           530:     {
        !           531:         IOPanic("Disk(ata): IDE Hardware error - unable to recover\n\r");
        !           532:     }
        !           533: }
        !           534:      
        !           535: /*
        !           536:  * Calculate the actual transfer count by reading back the DMA descriptors.
        !           537:  */
        !           538: - (uint) getDBDMATransferCount
        !           539: {
        !           540:     uint       i;
        !           541:     uint        ccResult;
        !           542:     uint        xferCount = 0;
        !           543: 
        !           544:     for ( i=0; i < MAX_IDE_DESCRIPTORS; i++ )
        !           545:     {
        !           546:         ccResult = IOGetCCResult( &_ideDMACommands[i] );
        !           547:     
        !           548:         if ( !(ccResult & kdbdmaStatusRun) )
        !           549:         {
        !           550:             break;
        !           551:         } 
        !           552:         xferCount += (IOGetCCOperation( &_ideDMACommands[i] ) & kdbdmaReqCountMask) - (ccResult & kdbdmaResCountMask); 
        !           553:     }
        !           554:     return xferCount;
        !           555: }
        !           556: 
        !           557: @end
        !           558: 

unix.superglobalmegacorp.com

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