|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved. ! 3: * ! 4: * @APPLE_LICENSE_HEADER_START@ ! 5: * ! 6: * The contents of this file constitute Original Code as defined in and ! 7: * are subject to the Apple Public Source License Version 1.1 (the ! 8: * "License"). You may not use this file except in compliance with the ! 9: * License. Please obtain a copy of the License at ! 10: * http://www.apple.com/publicsource and read it before using this file. ! 11: * ! 12: * This Original Code and all software distributed under the License are ! 13: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER ! 14: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, ! 15: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, ! 16: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the ! 17: * License for the specific language governing rights and limitations ! 18: * under the License. ! 19: * ! 20: * @APPLE_LICENSE_HEADER_END@ ! 21: */ ! 22: ! 23: #include <IOKit/IOLib.h> ! 24: #include <IOKit/IOReturn.h> ! 25: #include <IOKit/scsi/IOSCSIDeviceInterface.h> ! 26: #include <IOKit/storage/scsi/IOBasicSCSI.h> ! 27: ! 28: ! 29: /* xxx to do: ! 30: * Consider a way to report to the superclass that this ! 31: * IO should be retried. (A return status from asyncReadWrite?) ! 32: * As for internal IOs that need retry due, for example, to a ! 33: * UA, how do we handle that in this class? Is the code in ! 34: * genericCompletion OK, even if it's ugly? ! 35: * ! 36: */ ! 37: ! 38: #define super IOService ! 39: OSDefineMetaClass(IOBasicSCSI,IOService) ! 40: OSDefineAbstractStructors(IOBasicSCSI,IOService) ! 41: ! 42: void IOBasicSCSI_gc_glue(IOService *object,void *param); ! 43: ! 44: /* Allocate a new context struct. A return of NULL means we couldn't ! 45: * allocate either the context itself or one of its members. ! 46: */ ! 47: struct IOBasicSCSI::context * ! 48: IOBasicSCSI::allocateContext(void) ! 49: { ! 50: struct context *cx; ! 51: ! 52: //xxx IOLog("allocateContext entered\n"); ! 53: ! 54: /* First, the context structure itself. */ ! 55: ! 56: cx = IONew(struct context,1); ! 57: if (cx == NULL) { ! 58: return(NULL); ! 59: } ! 60: ! 61: bzero(cx,sizeof(struct context)); ! 62: ! 63: /* Allocate all the structs and objects we need. If any allocation ! 64: * fails, we can simply call deleteContext() to free anything ! 65: * allocated so far. ! 66: */ ! 67: ! 68: cx->scsireq = _provider->allocCommand(kIOSCSIDevice, 0); ! 69: if (cx->scsireq == NULL) { ! 70: deleteContext(cx); ! 71: return(NULL); ! 72: } ! 73: ! 74: ! 75: /* Preset the completion parameters, which are the same for ! 76: * all SCSI requests we issue. Only the target function changes. ! 77: */ ! 78: // cx->scsireq->completionClient = this; ! 79: // cx->scsireq->completionParam = cx; ! 80: ! 81: /** ! 82: IOLog("allocateContext: completionClient = %08x, Param = %08x\n", ! 83: (unsigned int)cx->scsireq->completionClient, ! 84: (unsigned int)cx->scsireq->completionParam); ! 85: **/ ! 86: ! 87: cx->senseData = (SCSISenseData *)IOMalloc(256); ! 88: if (cx-> senseData == NULL) { ! 89: deleteContext(cx); ! 90: return(NULL); ! 91: } ! 92: ! 93: bzero(cx->senseData, 256 ); ! 94: ! 95: cx->senseDataDesc = IOMemoryDescriptor::withAddress(cx->senseData, ! 96: 256, ! 97: kIODirectionIn); ! 98: ! 99: ! 100: cx->sync = IOSyncer::create(false); ! 101: if (cx->sync == NULL) { ! 102: deleteContext(cx); ! 103: return(NULL); ! 104: } ! 105: ! 106: /* We defer allocation of the Memory Descriptor till later; ! 107: * it will be allocated where it's needed. ! 108: */ ! 109: ! 110: // IOLog("allocateContext returning cx = %08x\n",(unsigned int)cx); ! 111: ! 112: return(cx); ! 113: } ! 114: ! 115: IOReturn ! 116: IOBasicSCSI::allocateInquiryBuffer(UInt8 **buf,UInt32 size) ! 117: { ! 118: *buf = (UInt8 *)IOMalloc(size); ! 119: if (*buf == NULL) { ! 120: return(kIOReturnNoMemory); ! 121: } ! 122: ! 123: bzero(*buf,size); ! 124: ! 125: return(kIOReturnSuccess); ! 126: } ! 127: ! 128: IOReturn ! 129: IOBasicSCSI::allocateTempBuffer(UInt8 **buf,UInt32 size) ! 130: { ! 131: *buf = (UInt8 *)IOMalloc(size); ! 132: if (*buf == NULL) { ! 133: return(kIOReturnNoMemory); ! 134: } ! 135: ! 136: bzero(*buf,size); ! 137: ! 138: return(kIOReturnSuccess); ! 139: } ! 140: ! 141: IOReturn ! 142: IOBasicSCSI::allocateReadCapacityBuffer(UInt8 **buf,UInt8 size) ! 143: { ! 144: *buf = (UInt8 *)IOMalloc(size); ! 145: if (*buf == NULL) { ! 146: return(kIOReturnNoMemory); ! 147: } ! 148: ! 149: bzero(*buf,size); ! 150: ! 151: return(kIOReturnSuccess); ! 152: } ! 153: ! 154: UInt32 ! 155: IOBasicSCSI::createReadCdb(UInt8 *cdb,UInt32 *cdbLength, ! 156: UInt32 block,UInt32 nblks, ! 157: UInt32 *maxAutoSenseLength, ! 158: UInt32 *timeoutSeconds) ! 159: { ! 160: struct IORWcdb *c; ! 161: ! 162: c = (struct IORWcdb *)cdb; ! 163: ! 164: c->opcode = SOP_READ10; ! 165: c->lunbits = 0; ! 166: ! 167: c->lba_3 = block >> 24; ! 168: c->lba_2 = block >> 16; ! 169: c->lba_1 = block >> 8; ! 170: c->lba_0 = block & 0xff; ! 171: ! 172: c->reserved = 0; ! 173: ! 174: c->count_msb = nblks >> 8; ! 175: c->count_lsb = nblks & 0xff; ! 176: ! 177: c->ctlbyte = 0; ! 178: ! 179: *cdbLength = 10; ! 180: *maxAutoSenseLength = 8; /* do the sense */ ! 181: *timeoutSeconds = 60; ! 182: return(0); ! 183: } ! 184: ! 185: UInt32 ! 186: IOBasicSCSI::createWriteCdb(UInt8 *cdb,UInt32 *cdbLength, ! 187: UInt32 block,UInt32 nblks, ! 188: UInt32 *maxAutoSenseLength, ! 189: UInt32 *timeoutSeconds) ! 190: { ! 191: struct IORWcdb *c; ! 192: ! 193: c = (struct IORWcdb *)cdb; ! 194: ! 195: c->opcode = SOP_WRITE10; ! 196: c->lunbits = 0; ! 197: ! 198: c->lba_3 = block >> 24; ! 199: c->lba_2 = block >> 16; ! 200: c->lba_1 = block >> 8; ! 201: c->lba_0 = block & 0xff; ! 202: ! 203: c->reserved = 0; ! 204: ! 205: c->count_msb = nblks >> 8; ! 206: c->count_lsb = nblks & 0xff; ! 207: ! 208: c->ctlbyte = 0; ! 209: ! 210: *cdbLength = 10; ! 211: *maxAutoSenseLength = sizeof( SCSISenseData ); /* do the sense */ ! 212: *timeoutSeconds = 60; ! 213: return(0); ! 214: } ! 215: ! 216: void ! 217: IOBasicSCSI::deleteContext(struct context *cx) ! 218: { ! 219: // IOLog("deleteContext %08x\n",(unsigned int)cx); ! 220: ! 221: if (cx->scsireq) { ! 222: cx->scsireq->release(); ! 223: } ! 224: ! 225: // if (cx->scsiresult) { ! 226: // IODelete(cx->scsiresult,struct IOSCSIResult,1); ! 227: // } ! 228: ! 229: if (cx->senseData) ! 230: { ! 231: IOFree( cx->senseData, 256 ); ! 232: } ! 233: ! 234: if ( cx->senseDataDesc ) ! 235: { ! 236: cx->senseDataDesc->release(); ! 237: } ! 238: ! 239: if (cx->memory) { ! 240: cx->memory->release(); ! 241: } ! 242: ! 243: if (cx->sync) { ! 244: cx->sync->release(); ! 245: } ! 246: ! 247: IODelete(cx,struct context,1); ! 248: } ! 249: ! 250: void ! 251: IOBasicSCSI::deleteInquiryBuffer(UInt8 *buf,UInt32 size) ! 252: { ! 253: IOFree((void *)buf,size); ! 254: } ! 255: ! 256: void ! 257: IOBasicSCSI::deleteTempBuffer(UInt8 *buf,UInt32 len) ! 258: { ! 259: IOFree((void *)buf,len); ! 260: } ! 261: ! 262: void ! 263: IOBasicSCSI::deleteReadCapacityBuffer(UInt8 *buf,UInt32 len) ! 264: { ! 265: IOFree((void *)buf,len); ! 266: } ! 267: ! 268: #if 1 ! 269: IOReturn ! 270: IOBasicSCSI::doInquiry(UInt8 *inqBuf,UInt32 maxLen,UInt32 *actualLen) ! 271: { ! 272: _provider->getInquiryData( inqBuf, maxLen, actualLen ); ! 273: return kIOReturnSuccess; ! 274: } ! 275: #else ! 276: IOReturn ! 277: IOBasicSCSI::doInquiry(UInt8 *inqBuf,UInt32 maxLen,UInt32 *actualLen) ! 278: { ! 279: ! 280: struct context *cx; ! 281: struct IOInquirycdb *c; ! 282: IOSCSICommand *req; ! 283: SCSICDBInfo scsiCDB; ! 284: SCSIResults scsiResults; ! 285: UInt8 len; ! 286: IOReturn result; ! 287: ! 288: cx = allocateContext(); ! 289: if (cx == NULL) { ! 290: return(kIOReturnNoMemory); ! 291: } ! 292: ! 293: req = cx->scsireq; ! 294: ! 295: /* First, we do a minimal Inquiry, which all devices support. This will ! 296: * allow us to determine the full size of an Inquiry from this device. ! 297: * We then issue the full-length Inquiry immediately to get all the ! 298: * available data. ! 299: */ ! 300: ! 301: if ((int)maxLen < kMinInqSize) { /* buffer too small: programming error! */ ! 302: return(kIOReturnNoMemory); ! 303: } ! 304: ! 305: bzero( &scsiCDB, sizeof(SCSICDBInfo) ); ! 306: ! 307: c = (struct IOInquirycdb *)&scsiCDB.cdb; ! 308: c->opcode = SOP_INQUIRY; ! 309: c->lunbits = 0; ! 310: c->pagecode = 0; ! 311: c->reserved = 0; ! 312: c->len = kMinInqSize; ! 313: c->ctlbyte = 0; ! 314: ! 315: scsiCDB.cdbLength = 6; ! 316: scsiCDB.cdbFlags = 0; ! 317: ! 318: req->setCDB( &scsiCDB ); ! 319: req->setTimeout( 1000 ); ! 320: ! 321: cx->memory = IOMemoryDescriptor::withAddress(inqBuf, ! 322: kMinInqSize, ! 323: kIODirectionIn); ! 324: ! 325: req->setPointers( cx->memory, c->len, false ); ! 326: ! 327: queueCommand(cx,kSync,getInquiryPowerState()); /* queue and possibly wait for power */ ! 328: ! 329: result = simpleSynchIO(cx); ! 330: ! 331: if (result == kIOReturnSuccess) { /* the minimal inquiry succeeded */ ! 332: ! 333: /* Now obtain the full length and do a full inquiry: */ ! 334: ! 335: len = inqBuf[4]; ! 336: ! 337: if (len != 0) { /* there's additional data to get */ ! 338: len += 4; ! 339: if (len > maxLen) { /* only fill to end of buffer */ ! 340: len = maxLen; ! 341: } ! 342: c->len = len; ! 343: req->setCDB( &scsiCDB ); ! 344: req->setPointers( cx->memory, c->len, false ); ! 345: if (cx->memory->initWithAddress(inqBuf, len, kIODirectionIn)) { ! 346: /* memory descriptor was successfully reset with new length */ ! 347: result = simpleSynchIO(cx); ! 348: } ! 349: } ! 350: } ! 351: ! 352: req->getResults( &scsiResults ); ! 353: *actualLen = scsiResults.bytesTransferred; ! 354: ! 355: deleteContext(cx); ! 356: ! 357: return(result); ! 358: } ! 359: #endif ! 360: ! 361: IOReturn ! 362: IOBasicSCSI::doReadCapacity(UInt64 *blockSize,UInt64 *maxBlock) ! 363: { ! 364: struct context *cx; ! 365: struct IOReadCapcdb *c; ! 366: IOSCSICommand *req; ! 367: SCSICDBInfo scsiCDB; ! 368: UInt8 *buf; ! 369: IOReturn result; ! 370: ! 371: cx = allocateContext(); ! 372: if (cx == NULL) { ! 373: return(kIOReturnNoMemory); ! 374: } ! 375: ! 376: req = cx->scsireq; ! 377: ! 378: bzero( &scsiCDB, sizeof(SCSICDBInfo) ); ! 379: ! 380: c = (struct IOReadCapcdb *)&scsiCDB.cdb; ! 381: c->opcode = SOP_READCAP; ! 382: c->lunbits = 0; ! 383: c->lba_3 = 0; ! 384: c->lba_2 = 0; ! 385: c->lba_1 = 0; ! 386: c->lba_0 = 0; ! 387: c->reserved1 = 0; ! 388: c->reserved2 = 0; ! 389: c->reserved3 = 0; ! 390: c->ctlbyte = 0; ! 391: ! 392: scsiCDB.cdbLength = 10; ! 393: ! 394: req->setCDB( &scsiCDB ); ! 395: req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true ); ! 396: ! 397: req->setTimeout( 5000 ); ! 398: ! 399: *blockSize = 0; ! 400: *maxBlock = 0; ! 401: ! 402: result = allocateReadCapacityBuffer(&buf,kReadCapSize); ! 403: ! 404: if (result == kIOReturnSuccess) { ! 405: ! 406: cx->memory = IOMemoryDescriptor::withAddress((void *)buf, ! 407: kReadCapSize, ! 408: kIODirectionIn); ! 409: ! 410: req->setPointers( cx->memory, kReadCapSize, false ); ! 411: ! 412: /* We force the drive to be completely powered-up, including the mechanical ! 413: * components, because some drives (e.g. CDs) access the media. ! 414: */ ! 415: ! 416: queueCommand(cx,kSync,getReadCapacityPowerState()); /* queue the operation, sleep awaiting power */ ! 417: ! 418: result = simpleSynchIO(cx); ! 419: ! 420: if (result == kIOReturnSuccess) { ! 421: ! 422: *blockSize = (buf[4] << 24) | /* endian-neutral */ ! 423: (buf[5] << 16) | ! 424: (buf[6] << 8) | ! 425: (buf[7] ); ! 426: ! 427: *maxBlock = (buf[0] << 24) | /* endian-neutral */ ! 428: (buf[1] << 16) | ! 429: (buf[2] << 8) | ! 430: (buf[3] ); ! 431: } ! 432: ! 433: deleteReadCapacityBuffer(buf,kReadCapSize); ! 434: } ! 435: ! 436: deleteContext(cx); ! 437: ! 438: return(result); ! 439: } ! 440: ! 441: IOReturn ! 442: IOBasicSCSI::executeCdb(struct cdbParams *params) ! 443: { ! 444: struct context *cx; ! 445: IOSCSICommand *req; ! 446: SCSICDBInfo scsiCDB; ! 447: SCSIResults scsiResults; ! 448: IOReturn result; ! 449: bool isWrite; ! 450: ! 451: cx = allocateContext(); ! 452: if (cx == NULL) { ! 453: return(kIOReturnNoMemory); ! 454: } ! 455: ! 456: req = cx->scsireq; ! 457: ! 458: if (params->cdbLength > 16) { ! 459: params->cdbLength = 16; ! 460: } ! 461: bcopy(params->cdb,(char *)&scsiCDB.cdb[0],params->cdbLength); ! 462: scsiCDB.cdbLength = params->cdbLength; ! 463: scsiCDB.cdbFlags = 0; ! 464: ! 465: if (params->senseLength > 255) { ! 466: params->senseLength = 255; ! 467: } ! 468: req->setCDB(&scsiCDB); ! 469: req->setPointers(params->senseBuffer,params->senseLength,false,true); ! 470: ! 471: req->setTimeout(params->timeoutSeconds * 1000); ! 472: ! 473: cx->memory = params->dataBuffer; ! 474: cx->memory->retain(); ! 475: ! 476: isWrite = false; ! 477: if (params->dataLength != 0) { ! 478: if (cx->memory->getDirection() == kIODirectionIn) { ! 479: isWrite = false; ! 480: } ! 481: } ! 482: ! 483: req->setPointers(cx->memory,params->dataLength,isWrite); ! 484: ! 485: queueCommand(cx,kSync,getExecuteCDBPowerState()); /* queue the operation awaiting power */ ! 486: ! 487: result = simpleSynchIO(cx); /* issue a simple command */ ! 488: ! 489: req->getResults(&scsiResults); ! 490: ! 491: /* Now we return the sense data actual transfer counts, and device status. */ ! 492: ! 493: if (scsiResults.requestSenseDone == true) { /* we have sense data to copy */ ! 494: params->actualSenseLength = scsiResults.requestSenseLength; ! 495: } ! 496: ! 497: params->status = scsiResults.scsiStatus; /* status */ ! 498: ! 499: params->actualDataLength = scsiResults.bytesTransferred; ! 500: ! 501: deleteContext(cx); ! 502: ! 503: return(result); ! 504: } ! 505: ! 506: void ! 507: IOBasicSCSI::free(void) ! 508: { ! 509: if (_inqBuf) { ! 510: deleteInquiryBuffer(_inqBuf,_inqBufSize); ! 511: _inqBuf = NULL; ! 512: } ! 513: ! 514: #ifdef DISKPM ! 515: if (_powerQueue.lock) { ! 516: IOLockFree(_powerQueue.lock); ! 517: } ! 518: #endif ! 519: ! 520: super::free(); ! 521: } ! 522: ! 523: /* The Callback (C) entry from the SCSI provider. We just glue ! 524: * right into C++. ! 525: */ ! 526: ! 527: void ! 528: IOBasicSCSI_gc_glue(IOService *object,void *param) ! 529: { ! 530: IOBasicSCSI *self; ! 531: struct IOBasicSCSI::context *cx; ! 532: ! 533: self = (IOBasicSCSI *)object; ! 534: cx = (struct IOBasicSCSI::context *)param; ! 535: self->genericCompletion(cx); /* do it in C++ */ ! 536: } ! 537: ! 538: void ! 539: IOBasicSCSI::genericCompletion(struct IOBasicSCSI::context *cx) ! 540: { ! 541: struct context *origcx; ! 542: SCSIResults scsiResults; ! 543: ! 544: /** ! 545: IOLog("%s[IOBasicSCSI]::genericCompletion, state = %s\n", ! 546: getName(),stringFromState(cx->state)); ! 547: **/ ! 548: ! 549: /* If we're handling a Unit-Attention condition, continue looping ! 550: * until handleUnitAttention decides we're finished. ! 551: */ ! 552: if (cx->state == kHandlingUnitAttention) { ! 553: cx->step++; /* we're at the next step */ ! 554: //IOLog(" ::genericCompletion: recalling handleUnitAttention, step = %ld\n",cx->step); ! 555: handleUnitAttention(cx); ! 556: ! 557: } else { /* might be a new UA */ ! 558: ! 559: /* If we're not currently handling a Unit Attention, see if ! 560: * we just got one to handle. ! 561: */ ! 562: ! 563: cx->scsireq->getResults( &scsiResults ); ! 564: ! 565: /** ! 566: IOLog("%s[IOBasicSCSI]::genericCompletion: result = %s\n", ! 567: getName(),stringFromReturn(result)); ! 568: **/ ! 569: ! 570: /* A special case is Unit Attention, which can happen at any time. We ! 571: * call handleUnitAttention() and allow that function to switch states ! 572: * so that we can issue multiple asynch commands to restore the device ! 573: * condition. After handleUnitAttention() completes, it sets the ! 574: * state to doRetry, so we retry the original command. ! 575: */ ! 576: ! 577: if (scsiResults.requestSenseDone == true) { /* an error occurred */ ! 578: ! 579: /** ! 580: IOLog("%s[IOBasicSCSI]::genericCompletion: sense code %02x\n", ! 581: getName(),cx->scsiresult->scsiSense[02]); ! 582: **/ ! 583: ! 584: if ((cx->senseData->senseKey & 0x0f) == kUnitAttention) { ! 585: ! 586: /** ! 587: IOLog("%s[IOBasicSCSI]::genericCompletion: starting UnitAttention process\n", ! 588: getName()); ! 589: **/ ! 590: ! 591: /* Save original IO context, set state and step, and ! 592: * begin handling the Unit Attention condition. If ! 593: * we can't allocate a new context for use by ! 594: * handleUnitAttention, then the original IO will be ! 595: * completed with the Unit-Attention error. Note that ! 596: * we must actually change cx to the new context, so that ! 597: * we work properly below if handleUnitAttention simply ! 598: * returns without doing anything. ! 599: */ ! 600: origcx = cx; ! 601: cx = allocateContext(); ! 602: if (cx == NULL) { /* ugh. forget about it */ ! 603: goto noUA; ! 604: } ! 605: cx->originalIOContext = origcx; ! 606: cx->state = kHandlingUnitAttention; ! 607: cx->step = 1; ! 608: //IOLog(" ::genericCompletion: calling handleUnitAttention, step = %ld\n",cx->step); ! 609: handleUnitAttention(cx); ! 610: } ! 611: } ! 612: } ! 613: ! 614: noUA: ! 615: ! 616: /* At this point we dispatch the completion depending on our state. ! 617: * Note that we might still have to continue handling a Unit Attention ! 618: * if the call to handleUnitAttention isn't yet finished. In this case ! 619: * we just return, and get re-entered on the completion of the command ! 620: * that was issued by handleUnitAttention. ! 621: */ ! 622: ! 623: /** ! 624: IOLog("%s[IOBasicSCSI]::genericCompletion: dispatching, state = %s\n", ! 625: getName(),stringFromState(cx->state)); ! 626: **/ ! 627: ! 628: switch (cx->state) { ! 629: ! 630: case kSimpleSynchIO : ! 631: cx->sync->signal(kIOReturnSuccess, false); /* Just wake up the waiting thread: */ ! 632: break; ! 633: ! 634: case kDoneHandlingUnitAttention : /* switch back to original command & retry */ ! 635: //IOLog(" ::genericCompletion: done handling UnitAttention\n"); ! 636: origcx = cx->originalIOContext; ! 637: deleteContext(cx); ! 638: #if 0 ! 639: if (origcx->scsireq->completionAction == NULL) { ! 640: IOPanic("IOBasicSCSI::genericCompletion: completionAction is NULL!"); ! 641: } ! 642: #endif ! 643: origcx->scsireq->execute(); ! 644: break; ! 645: ! 646: case kAsyncReadWrite : /* normal r/w completion */ ! 647: RWCompletion(cx); ! 648: deleteContext(cx); ! 649: break; ! 650: ! 651: case kHandlingUnitAttention : /* still handling UA */ ! 652: break; /* just wait for next completion */ ! 653: ! 654: case kNone : /* undefined */ ! 655: case kMaxStateValue : ! 656: case kAwaitingPower : ! 657: break; ! 658: } ! 659: ! 660: return; ! 661: } ! 662: ! 663: char * ! 664: IOBasicSCSI::getAdditionalDeviceInfoString(void) ! 665: { ! 666: return("[SCSI]"); ! 667: } ! 668: ! 669: UInt64 ! 670: IOBasicSCSI::getBlockSize(void) ! 671: { ! 672: return(_blockSize); ! 673: } ! 674: ! 675: char * ! 676: IOBasicSCSI::getProductString(void) ! 677: { ! 678: return(_product); ! 679: } ! 680: ! 681: char * ! 682: IOBasicSCSI::getRevisionString(void) ! 683: { ! 684: return(_rev); ! 685: } ! 686: ! 687: char * ! 688: IOBasicSCSI::getVendorString(void) ! 689: { ! 690: return(_vendor); ! 691: } ! 692: ! 693: /* The default implementation doesn't have anything to do for Unit-Attention, ! 694: * so we just set the state to retry the original command. ! 695: */ ! 696: void ! 697: IOBasicSCSI::handleUnitAttention(struct context *cx) ! 698: { ! 699: //IOLog("IOBasicSCSI::handleUnitAttenion, step = %ld\n",cx->step); ! 700: //IOLog("IOBasicSCSI::handleUnitAttenion, nothing to do\n" ); ! 701: ! 702: cx->state = kDoneHandlingUnitAttention; ! 703: } ! 704: ! 705: bool ! 706: IOBasicSCSI::init(OSDictionary * properties) ! 707: { ! 708: _inqBuf = NULL; ! 709: _inqBufSize = 0; ! 710: _inqLen = 0; ! 711: ! 712: _vendor[8] = '\0'; ! 713: _product[16] = '\0'; ! 714: _rev[4] = '\0'; ! 715: ! 716: _readCapDone = false; ! 717: _blockSize = 0; ! 718: _maxBlock = 0; ! 719: _removable = false; ! 720: ! 721: #ifdef DISKPM ! 722: _powerQueue.head = NULL; ! 723: _powerQueue.tail = NULL; ! 724: _powerQueue.lock = IOLockAlloc(); ! 725: if (_powerQueue.lock == NULL) { ! 726: return(false); ! 727: } ! 728: IOLockInit(_powerQueue.lock); ! 729: #endif ! 730: ! 731: return(super::init(properties)); ! 732: } ! 733: ! 734: IOService * ! 735: IOBasicSCSI::probe(IOService * provider,SInt32 * score) ! 736: { ! 737: IOReturn result; ! 738: ! 739: if (!super::probe(provider,score)) { ! 740: return(NULL); ! 741: } ! 742: ! 743: _provider = (IOSCSIDevice *)provider; ! 744: ! 745: /* Do an inquiry to get the device type. The inquiry buffer will ! 746: * be deleted by free(). ! 747: */ ! 748: ! 749: _inqBufSize = kMaxInqSize; ! 750: result = allocateInquiryBuffer(&_inqBuf,_inqBufSize); ! 751: if (result != kIOReturnSuccess) { ! 752: return(NULL); ! 753: } ! 754: ! 755: result = doInquiry(_inqBuf,_inqBufSize,&_inqLen); ! 756: if (result != kIOReturnSuccess) { ! 757: return(NULL); ! 758: } ! 759: ! 760: #ifdef notdef ! 761: // xxx NEVER match for ID=0, the boot disk. This lets us ! 762: // test this driver on other disk drives. ! 763: // ! 764: if (_provider->getTarget() == 0) { ! 765: IOLog("**%s[IOBasicSCSI]:probe; ignoring SCSI ID %d\n", ! 766: getName(),(int)_provider->getTarget()); ! 767: return(NULL); ! 768: } ! 769: #endif ! 770: ! 771: if (deviceTypeMatches(_inqBuf,_inqLen)) { ! 772: ! 773: OSString * string; ! 774: ! 775: // Fetch SCSI device information from the nub. ! 776: ! 777: string = OSDynamicCast(OSString, ! 778: _provider->getProperty(kSCSIPropertyVendorName)); ! 779: if (string) { ! 780: strncpy(_vendor, string->getCStringNoCopy(), 8); ! 781: _vendor[8] = '\0'; ! 782: } ! 783: ! 784: string = OSDynamicCast(OSString, ! 785: _provider->getProperty(kSCSIPropertyProductName)); ! 786: if (string) { ! 787: strncpy(_product, string->getCStringNoCopy(), 16); ! 788: _product[16] = '\0'; ! 789: } ! 790: ! 791: string = OSDynamicCast(OSString, ! 792: _provider->getProperty(kSCSIPropertyProductRevision)); ! 793: if (string) { ! 794: strncpy(_rev, string->getCStringNoCopy(), 4); ! 795: _rev[4] = '\0'; ! 796: } ! 797: ! 798: /*** ! 799: IOLog("**%s[IOBasicSCSI]::probe; accepting %s, %s, %s, %s; SCSI ID %d\n", ! 800: getName(),getVendorString(),getProductString(),getRevisionString(), ! 801: getAdditionalDeviceInfoString(), ! 802: (int)_provider->getTarget()); ! 803: ***/ ! 804: return(this); ! 805: ! 806: } else { ! 807: return(NULL); ! 808: } ! 809: } ! 810: ! 811: void ! 812: IOBasicSCSI::dequeueCommands(void) ! 813: { ! 814: #ifdef DISKPM ! 815: struct queue *q; ! 816: IOReturn result; ! 817: ! 818: q = &_powerQueue; ! 819: ! 820: IOTakeLock(q->lock); ! 821: ! 822: /* Dequeue and execute all requests for which we have the proper power level. */ ! 823: ! 824: while (q->head) { ! 825: cx = q->head; ! 826: if (pm_vars->myCurrentState != cx->desiredPower) { ! 827: break; ! 828: } ! 829: q->head = cx->next; /* remove command from the queue */ ! 830: if (q->head == NULL) { ! 831: q->tail = NULL; ! 832: } ! 833: ! 834: cx->state = kNone; ! 835: ! 836: /* If the queued request was synchronous, all we have to do is wake it up. */ ! 837: ! 838: if (cx->isSync) { ! 839: cx->sync->signal(kIOReturnSuccess, false); /* Just wake up the waiting thread: */ ! 840: ! 841: } else { /* it's async; fire it off! */ ! 842: result = standardAsyncReadWriteExecute(cx); /* execute the async IO */ ! 843: if (result != kIOReturnSuccess) { /* provider didn't accept it! */ ! 844: RWCompletion(cx); /* force a completion */ ! 845: } ! 846: } ! 847: }; ! 848: ! 849: IOUnlock(q->lock); ! 850: #endif ! 851: } ! 852: ! 853: void ! 854: IOBasicSCSI::queueCommand(struct context *cx,bool isSync,UInt32 desiredPower) ! 855: { ! 856: #ifndef DISKPM //for now, just return immediately without queueing ! 857: /* If we're ifdefed out, we have to start async requests. Sync requests ! 858: * will just return immediately without any delay for power. ! 859: */ ! 860: if (isSync == kAsync) { ! 861: (void)standardAsyncReadWriteExecute(cx); /* execute the async IO */ ! 862: } ! 863: #else ! 864: struct queue *q; ! 865: ! 866: /* First, we enqueue the request to ensure sequencing with respect ! 867: * to other commands that may already be in the queue. ! 868: */ ! 869: ! 870: q = &_powerQueue; ! 871: ! 872: cx->next = NULL; ! 873: cx->state = kAwaitingPower; ! 874: ! 875: IOTakeLock(q->lock); ! 876: ! 877: if (q->head == NULL) { /* empty queue */ ! 878: q->head = cx; ! 879: q->tail = q->head; ! 880: ! 881: } else { /* not empty; add after tail */ ! 882: q->tail->next = cx; ! 883: q->tail = cx; ! 884: } ! 885: ! 886: /* If the command is synchronous, start by assuming we'll have to sleep ! 887: * awaiting power (and subsequent dequeuing). If, however, power is already ! 888: * right, then dequeuCommands will unlock the lock and we will continue, ! 889: * returning inline to the call site, exactly as if we were awakened. ! 890: * ! 891: * An async request will call dequeueCommands and always return immediately. ! 892: */ ! 893: ! 894: IOUnlock(q->lock); ! 895: ! 896: /* Now we try to dequeue pending commands if the power's right. */ ! 897: ! 898: dequeueCommands(); ! 899: ! 900: /* If we're synchronous, we'll wait here till dequeued. If we were ! 901: * dequeued above (and unlocked), then we'll return to allow the ! 902: * caller to continue with the command execution. ! 903: */ ! 904: ! 905: if (isSync) { ! 906: cx->sync->wait(false); /* waits here till awakened */ ! 907: } ! 908: #endif //DISKPM ! 909: } ! 910: ! 911: IOReturn ! 912: IOBasicSCSI::reportBlockSize(UInt64 *blockSize) ! 913: { ! 914: IOReturn result; ! 915: ! 916: *blockSize = 0; ! 917: result = kIOReturnSuccess; ! 918: ! 919: if (_readCapDone == false) { ! 920: result = doReadCapacity(&_blockSize,&_maxBlock); ! 921: _readCapDone = true; ! 922: } ! 923: ! 924: if (result == kIOReturnSuccess) { ! 925: *blockSize = _blockSize; ! 926: } ! 927: ! 928: return(result); ! 929: } ! 930: ! 931: IOReturn ! 932: IOBasicSCSI::reportEjectability(bool *isEjectable) ! 933: { ! 934: *isEjectable = true; /* default: if it's removable, it's ejectable */ ! 935: return(kIOReturnSuccess); ! 936: } ! 937: ! 938: IOReturn ! 939: IOBasicSCSI::reportLockability(bool *isLockable) ! 940: { ! 941: *isLockable = true; /* default: if it's removable, it's lockable */ ! 942: return(kIOReturnSuccess); ! 943: } ! 944: ! 945: IOReturn ! 946: IOBasicSCSI::reportMaxReadTransfer (UInt64 blocksize,UInt64 *max) ! 947: { ! 948: *max = blocksize * 65536; /* max blocks in a SCSI transfer */ ! 949: return(kIOReturnSuccess); ! 950: } ! 951: ! 952: IOReturn ! 953: IOBasicSCSI::reportMaxValidBlock(UInt64 *maxBlock) ! 954: { ! 955: IOReturn result; ! 956: ! 957: *maxBlock = 0; ! 958: result = kIOReturnSuccess; ! 959: ! 960: if (_readCapDone == false) { ! 961: result = doReadCapacity(&_blockSize,&_maxBlock); ! 962: _readCapDone = true; ! 963: } ! 964: ! 965: if (result == kIOReturnSuccess) { ! 966: *maxBlock = _maxBlock; ! 967: } ! 968: return(result); ! 969: } ! 970: ! 971: IOReturn ! 972: IOBasicSCSI::reportMaxWriteTransfer(UInt64 blocksize,UInt64 *max) ! 973: { ! 974: *max = blocksize * 65536; /* max blocks in a SCSI transfer */ ! 975: return(kIOReturnSuccess); ! 976: } ! 977: ! 978: IOReturn ! 979: IOBasicSCSI::reportPollRequirements(bool *pollRequired,bool *pollIsExpensive) ! 980: { ! 981: *pollIsExpensive = false; ! 982: *pollRequired = _removable; /* for now, all removables need polling */ ! 983: return(kIOReturnSuccess); ! 984: } ! 985: ! 986: IOReturn ! 987: IOBasicSCSI::reportRemovability(bool *isRemovable) ! 988: { ! 989: if (_inqLen > 0) { /* inquiry byte exists to check */ ! 990: if (_inqBuf[1] & 0x80) { /* it's removable */ ! 991: *isRemovable = true; ! 992: _removable = true; ! 993: } else { /* it's not removable */ ! 994: *isRemovable = false; ! 995: _removable = false; ! 996: } ! 997: } else { /* no byte? call it nonremovable */ ! 998: *isRemovable = false; ! 999: } ! 1000: ! 1001: return(kIOReturnSuccess); ! 1002: } ! 1003: ! 1004: /* Issue a Mode Sense to get the Mode Parameter Header but no pages. ! 1005: * Since we're only interested in the Mode Parameter Header, we just ! 1006: * issue a standard SCSI-1 6-byte command, nothing fancy. ! 1007: */ ! 1008: IOReturn ! 1009: IOBasicSCSI::reportWriteProtection(bool *writeProtected) ! 1010: { ! 1011: struct context *cx; ! 1012: struct IOModeSensecdb *c; ! 1013: IOSCSICommand *req; ! 1014: SCSICDBInfo scsiCDB; ! 1015: SCSIResults scsiResults; ! 1016: UInt8 *buf; ! 1017: IOReturn result; ! 1018: ! 1019: cx = allocateContext(); ! 1020: if (cx == NULL) { ! 1021: return(kIOReturnNoMemory); ! 1022: } ! 1023: ! 1024: req = cx->scsireq; ! 1025: ! 1026: bzero( &scsiCDB, sizeof(SCSICDBInfo) ); ! 1027: ! 1028: c = (struct IOModeSensecdb *)&scsiCDB.cdb; ! 1029: c->opcode = SOP_MODESENSE; ! 1030: c->lunbits = 0; ! 1031: c->pagecode = 0 | 0x01; /* get current settings; any page will work */ ! 1032: c->reserved = 0; ! 1033: c->len = kModeSenseSize; ! 1034: c->ctlbyte = 0; ! 1035: ! 1036: scsiCDB.cdbLength = 6; ! 1037: ! 1038: req->setCDB( &scsiCDB ); ! 1039: req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true ); ! 1040: ! 1041: req->setTimeout( 1000 ); ! 1042: ! 1043: result = allocateTempBuffer(&buf,kModeSenseSize); ! 1044: ! 1045: if (result == kIOReturnSuccess) { ! 1046: ! 1047: cx->memory = IOMemoryDescriptor::withAddress((void *)buf, ! 1048: kModeSenseSize, ! 1049: kIODirectionIn); ! 1050: ! 1051: req->setPointers( cx->memory, kModeSenseSize, false ); ! 1052: ! 1053: queueCommand(cx,kSync,getReportWriteProtectionPowerState()); /* queue the op, sleep awaiting power */ ! 1054: ! 1055: result = simpleSynchIO(cx); ! 1056: ! 1057: if (result == kIOReturnUnderrun) { ! 1058: cx->scsireq->getResults( &scsiResults ); ! 1059: if (scsiResults.bytesTransferred >= 4) ! 1060: result = kIOReturnSuccess; ! 1061: } ! 1062: ! 1063: if (result == kIOReturnSuccess) { ! 1064: if (buf[2] & 0x80) { ! 1065: *writeProtected = true; ! 1066: } else { ! 1067: *writeProtected = false; ! 1068: } ! 1069: } ! 1070: ! 1071: deleteTempBuffer(buf,kModeSenseSize); ! 1072: } ! 1073: ! 1074: deleteContext(cx); ! 1075: ! 1076: return(result); ! 1077: } ! 1078: ! 1079: /* Issue a simple, synchronous SCSI operation. The caller's supplied context ! 1080: * contains a SCSI command and Memory Descriptor. The caller is responsible ! 1081: * for deleting the context. ! 1082: */ ! 1083: ! 1084: IOReturn ! 1085: IOBasicSCSI::simpleSynchIO(struct context *cx) ! 1086: { ! 1087: IOSCSICommand *req; ! 1088: IOReturn result; ! 1089: ! 1090: if (cx == NULL) { /* safety check */ ! 1091: return(kIOReturnNoMemory); ! 1092: } ! 1093: ! 1094: /* Set completion to return to genericCompletion: */ ! 1095: ! 1096: req = cx->scsireq; ! 1097: req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx ); ! 1098: ! 1099: cx->state = kSimpleSynchIO; ! 1100: ! 1101: /** ! 1102: IOLog("%s[IOBasicSCSI]::simpleSynchIO; issuing SCSI cmd %02x\n", ! 1103: getName(),req->cdb.byte[0]); ! 1104: **/ ! 1105: /* Start the scsi request: */ ! 1106: ! 1107: //IOLog("IOBasicSCSI::simpleSynchIO, lock initted, calling SCSI\n"); ! 1108: ! 1109: #if 0 ! 1110: if (req->completionAction == NULL) { ! 1111: IOPanic("IOBasicSCSI::simpleSynchIO: completionAction is NULL!"); ! 1112: } ! 1113: if (req->completionClient == NULL) { ! 1114: IOPanic("IOBasicSCSI::simpleSynchIO: completionClient is NULL!"); ! 1115: } ! 1116: if (req->completionParam == NULL) { ! 1117: IOPanic("IOBasicSCSI::simpleSynchIO: completionParam is NULL!"); ! 1118: } ! 1119: #endif ! 1120: ! 1121: result = req->execute(); ! 1122: // result = _provider->executeRequest(req,cx->memory,cx->scsiresult); ! 1123: ! 1124: if (result == true ) { ! 1125: ! 1126: // IOLog("IOBasicSCSI::simpleSynchIO, SCSI req accepted\n"); ! 1127: ! 1128: /* Wait for it to complete by attempting to acquire a read-lock, which ! 1129: * will block until the write-lock is released by the completion routine. ! 1130: */ ! 1131: ! 1132: cx->sync->wait(false); /* waits here till unlocked at completion */ ! 1133: ! 1134: /* We're back: */ ! 1135: ! 1136: result = req->getResults((SCSIResults *) 0); ! 1137: ! 1138: /** ! 1139: if ((result != kIOReturnSuccess) && (result != 10007)) { ! 1140: IOLog("%s[IOBasicSCSI]::simpleSynchIO; err '%s' from completed req\n", ! 1141: getName(),stringFromReturn(result)); ! 1142: } ! 1143: **/ ! 1144: } else { ! 1145: /** ! 1146: IOLog("%s[IOBasicSCSI]:simpleSynchIO; err '%s' queueing SCSI req\n", ! 1147: getName(),stringFromReturn(result)); ! 1148: **/ ! 1149: } ! 1150: ! 1151: // IOLog("IOBasicSCSI: completed; result '%s'\n",stringFromReturn(result)); ! 1152: ! 1153: return(result); ! 1154: } ! 1155: ! 1156: IOReturn ! 1157: IOBasicSCSI::standardAsyncReadWrite(IOMemoryDescriptor *buffer, ! 1158: UInt32 block,UInt32 nblks, ! 1159: gdCompletionFunction action, ! 1160: IOService *target,void *param) ! 1161: { ! 1162: struct context *cx; ! 1163: IOSCSICommand *req; ! 1164: SCSICDBInfo scsiCDB; ! 1165: UInt32 reqSenseLength; ! 1166: UInt32 timeoutSeconds; ! 1167: UInt8 *cdb; ! 1168: bool isWrite; ! 1169: ! 1170: cx = allocateContext(); ! 1171: ! 1172: if (cx == NULL) { ! 1173: return(kIOReturnNoMemory); ! 1174: } ! 1175: ! 1176: buffer->retain(); /* bump the retain count */ ! 1177: ! 1178: cx->memory = buffer; ! 1179: if (buffer->getDirection() == kIODirectionOut) { ! 1180: isWrite = true; ! 1181: } else { ! 1182: isWrite = false; ! 1183: } ! 1184: ! 1185: /** ! 1186: IOLog("%s[IOBasicSCSI]::standardAsyncReadWrite; (%s) blk %ld nblks %ld\n", ! 1187: getName(),(isWrite ? "write" : "read"),block,nblks); ! 1188: **/ ! 1189: req = cx->scsireq; ! 1190: ! 1191: /* Set completion to return to rwCompletion: */ ! 1192: cx->completion.action = action; ! 1193: cx->completion.target = target; ! 1194: cx->completion.param = param; ! 1195: ! 1196: bzero( &scsiCDB, sizeof(scsiCDB) ); ! 1197: ! 1198: req->setPointers( buffer, nblks * getBlockSize(), isWrite ); ! 1199: ! 1200: req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx ); ! 1201: ! 1202: cx->state = kAsyncReadWrite; ! 1203: ! 1204: cdb = (UInt8 *) &scsiCDB.cdb; ! 1205: ! 1206: /* Allow a subclass to override the creation of the cdb and specify ! 1207: * other parameters for the operation. ! 1208: */ ! 1209: ! 1210: if (isWrite) { ! 1211: scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength, ! 1212: block,nblks, ! 1213: &reqSenseLength, ! 1214: &timeoutSeconds); ! 1215: ! 1216: } else { ! 1217: ! 1218: scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength, ! 1219: block,nblks, ! 1220: &reqSenseLength, ! 1221: &timeoutSeconds); ! 1222: } ! 1223: ! 1224: req->setCDB( &scsiCDB ); ! 1225: req->setPointers( cx->senseDataDesc, reqSenseLength, false, true ); ! 1226: req->setTimeout( timeoutSeconds * 1000 ); ! 1227: ! 1228: #if 0 ! 1229: if (req->completionAction == NULL) { ! 1230: IOPanic("IOBasicSCSI::doAsyncReadWite: completionAction is NULL!"); ! 1231: } ! 1232: #endif ! 1233: ! 1234: /* Queue the request awaiting power and return. When power comes up, ! 1235: * the request will be passed to standardAsyncReadWriteExecute. ! 1236: */ ! 1237: queueCommand(cx,kAsync,getReadWritePowerState()); /* queue and possibly wait for power */ ! 1238: ! 1239: return(kIOReturnSuccess); ! 1240: } ! 1241: ! 1242: IOReturn ! 1243: IOBasicSCSI::standardAsyncReadWriteExecute(struct context *cx) ! 1244: { ! 1245: return(cx->scsireq->execute()); ! 1246: } ! 1247: ! 1248: IOReturn ! 1249: IOBasicSCSI::standardSyncReadWrite(IOMemoryDescriptor *buffer,UInt32 block,UInt32 nblks) ! 1250: { ! 1251: struct context *cx; ! 1252: IOSCSICommand *req; ! 1253: SCSICDBInfo scsiCDB; ! 1254: UInt32 reqSenseLength; ! 1255: UInt32 reqTimeoutSeconds; ! 1256: UInt8 *cdb; ! 1257: bool isWrite; ! 1258: IOReturn result; ! 1259: ! 1260: cx = allocateContext(); ! 1261: ! 1262: if (cx == NULL) { ! 1263: return(kIOReturnNoMemory); ! 1264: } ! 1265: ! 1266: cx->memory = buffer; ! 1267: buffer->retain(); /* bump the retain count */ ! 1268: ! 1269: if (buffer->getDirection() == kIODirectionOut) { ! 1270: isWrite = true; ! 1271: } else { ! 1272: isWrite = false; ! 1273: } ! 1274: ! 1275: /** ! 1276: IOLog("%s[IOBasicSCSI]::standardSyncReadWrite; (%s) blk %ld nblks %ld\n", ! 1277: getName(),(isWrite ? "write" : "read"),block,nblks); ! 1278: **/ ! 1279: ! 1280: bzero(&scsiCDB,sizeof(scsiCDB)); ! 1281: ! 1282: req = cx->scsireq; ! 1283: req->setPointers(buffer,(nblks * getBlockSize()),isWrite); ! 1284: ! 1285: cdb = (UInt8 *)&scsiCDB.cdb; ! 1286: ! 1287: /* Allow a subclass to override the creation of the cdb and specify ! 1288: * other parameters for the operation. ! 1289: */ ! 1290: ! 1291: if (isWrite) { ! 1292: scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength, ! 1293: block,nblks, ! 1294: &reqSenseLength, ! 1295: &reqTimeoutSeconds); ! 1296: ! 1297: } else { ! 1298: ! 1299: scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength, ! 1300: block,nblks, ! 1301: &reqSenseLength, ! 1302: &reqTimeoutSeconds); ! 1303: } ! 1304: ! 1305: ! 1306: req->setCDB(&scsiCDB); ! 1307: req->setPointers(cx->senseDataDesc,reqSenseLength,false,true); ! 1308: req->setTimeout(reqTimeoutSeconds * 1000); ! 1309: ! 1310: queueCommand(cx,kSync,getReadWritePowerState()); /* queue the operation, sleep awaiting power */ ! 1311: ! 1312: result = simpleSynchIO(cx); /* issue a simple command */ ! 1313: ! 1314: deleteContext(cx); ! 1315: return(result); ! 1316: } ! 1317: ! 1318: char * ! 1319: IOBasicSCSI::stringFromState(stateValue state) ! 1320: { ! 1321: static char *stateNames[] = { ! 1322: "kNone", ! 1323: "kAsyncReadWrite", ! 1324: "kSimpleSynchIO", ! 1325: "kHandlingUnitAttention", ! 1326: "kDoneHandlingUnitAttention" ! 1327: }; ! 1328: ! 1329: if (state < 0 || state > kMaxValidState) { ! 1330: return("invalid"); ! 1331: } ! 1332: ! 1333: return(stateNames[state]); ! 1334: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.