#include <IOKit/IOLib.h>
#include <IOKit/IOReturn.h>
#include <IOKit/scsi/IOSCSIDeviceInterface.h>
#include <IOKit/scsi/scsi-device/SCSIDevice.h>
#include <IOKit/storage/scsi/IOBasicSCSI.h>
#define super IOService
OSDefineMetaClass(IOBasicSCSI,IOService)
OSDefineAbstractStructors(IOBasicSCSI,IOService)
void IOBasicSCSI_gc_glue(void *object,void *param);
struct IOBasicSCSI::context *
IOBasicSCSI::allocateContext(void)
{
struct context *cx;
cx = IONew(struct context,1);
if (cx == NULL) {
return(NULL);
}
bzero(cx,sizeof(struct context));
cx->scsireq = _provider->allocCommand(kIOSCSIDevice, 0);
if (cx->scsireq == NULL) {
deleteContext(cx);
return(NULL);
}
cx->senseData = (SCSISenseData *)IOMalloc(256);
if (cx-> senseData == NULL) {
deleteContext(cx);
return(NULL);
}
bzero(cx->senseData, 256 );
cx->senseDataDesc = IOMemoryDescriptor::withAddress(cx->senseData,
256,
kIODirectionIn);
cx->sync = IOSyncer::create(false);
if (cx->sync == NULL) {
deleteContext(cx);
return(NULL);
}
cx->retryInProgress = false;
return(cx);
}
IOReturn
IOBasicSCSI::allocateInquiryBuffer(UInt8 **buf,UInt32 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::allocateTempBuffer(UInt8 **buf,UInt32 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::allocateReadCapacityBuffer(UInt8 **buf,UInt8 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
UInt32
IOBasicSCSI::createReadCdb(UInt8 *cdb,UInt32 *cdbLength,
UInt32 block,UInt32 nblks,
UInt32 *maxAutoSenseLength,
UInt32 *timeoutSeconds)
{
struct IORWcdb *c;
c = (struct IORWcdb *)cdb;
c->opcode = SOP_READ10;
c->lunbits = 0;
c->lba_3 = block >> 24;
c->lba_2 = block >> 16;
c->lba_1 = block >> 8;
c->lba_0 = block & 0xff;
c->reserved = 0;
c->count_msb = nblks >> 8;
c->count_lsb = nblks & 0xff;
c->ctlbyte = 0;
*cdbLength = 10;
*maxAutoSenseLength = 8;
*timeoutSeconds = 60;
return(0);
}
UInt32
IOBasicSCSI::createWriteCdb(UInt8 *cdb,UInt32 *cdbLength,
UInt32 block,UInt32 nblks,
UInt32 *maxAutoSenseLength,
UInt32 *timeoutSeconds)
{
struct IORWcdb *c;
c = (struct IORWcdb *)cdb;
c->opcode = SOP_WRITE10;
c->lunbits = 0;
c->lba_3 = block >> 24;
c->lba_2 = block >> 16;
c->lba_1 = block >> 8;
c->lba_0 = block & 0xff;
c->reserved = 0;
c->count_msb = nblks >> 8;
c->count_lsb = nblks & 0xff;
c->ctlbyte = 0;
*cdbLength = 10;
*maxAutoSenseLength = sizeof( SCSISenseData );
*timeoutSeconds = 60;
return(0);
}
void
IOBasicSCSI::deleteContext(struct context *cx)
{
if (cx->scsireq) {
cx->scsireq->release();
}
if (cx->senseData)
{
IOFree( cx->senseData, 256 );
}
if ( cx->senseDataDesc )
{
cx->senseDataDesc->release();
}
if (cx->memory) {
cx->memory->release();
}
if (cx->sync) {
cx->sync->release();
}
IODelete(cx,struct context,1);
}
void
IOBasicSCSI::deleteInquiryBuffer(UInt8 *buf,UInt32 size)
{
IOFree((void *)buf,size);
}
void
IOBasicSCSI::deleteTempBuffer(UInt8 *buf,UInt32 len)
{
IOFree((void *)buf,len);
}
void
IOBasicSCSI::deleteReadCapacityBuffer(UInt8 *buf,UInt32 len)
{
IOFree((void *)buf,len);
}
IOReturn
IOBasicSCSI::doInquiry(UInt8 *inqBuf,UInt32 maxLen,UInt32 *actualLen)
{
_provider->getInquiryData( inqBuf, maxLen, actualLen );
return kIOReturnSuccess;
}
IOReturn
IOBasicSCSI::doReadCapacity(UInt64 *blockSize,UInt64 *maxBlock)
{
struct context *cx;
struct IOReadCapcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt8 *buf;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOReadCapcdb *)&scsiCDB.cdb;
c->opcode = SOP_READCAP;
c->lunbits = 0;
c->lba_3 = 0;
c->lba_2 = 0;
c->lba_1 = 0;
c->lba_0 = 0;
c->reserved1 = 0;
c->reserved2 = 0;
c->reserved3 = 0;
c->ctlbyte = 0;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true );
req->setTimeout( 30000 );
*blockSize = 0;
*maxBlock = 0;
result = allocateReadCapacityBuffer(&buf,kReadCapSize);
if (result == kIOReturnSuccess) {
cx->memory = IOMemoryDescriptor::withAddress((void *)buf,
kReadCapSize,
kIODirectionIn);
req->setPointers( cx->memory, kReadCapSize, false );
queueCommand(cx,kSync,getReadCapacityPowerState());
result = simpleSynchIO(cx);
if (result == kIOReturnSuccess) {
*blockSize = (buf[4] << 24) |
(buf[5] << 16) |
(buf[6] << 8) |
(buf[7] );
*maxBlock = (buf[0] << 24) |
(buf[1] << 16) |
(buf[2] << 8) |
(buf[3] );
}
deleteReadCapacityBuffer(buf,kReadCapSize);
}
deleteContext(cx);
return(result);
}
void
IOBasicSCSI::free(void)
{
if (_inqBuf) {
deleteInquiryBuffer(_inqBuf,_inqBufSize);
_inqBuf = NULL;
}
#ifdef DISKPM
if (_powerQueue.lock) {
IOLockFree(_powerQueue.lock);
}
#endif
if (_busResetContext) {
deleteContext(_busResetContext);
}
if (_unitAttentionContext) {
deleteContext(_unitAttentionContext);
}
super::free();
}
void
IOBasicSCSI_gc_glue(void *object,void *param)
{
IOBasicSCSI *self;
struct IOBasicSCSI::context *cx;
self = (IOBasicSCSI *)object;
cx = (struct IOBasicSCSI::context *)param;
self->genericCompletion(cx);
}
void
IOBasicSCSI::setupBusResetRecovery(void)
{
IOLog("%s[IOBasicSCSI]: SCSI bus reset occurred; begin recovery.\n",getName());
_busResetContext->step = 1;
_busResetRecoveryInProgress = true;
_provider->holdQueue(kQTypeNormalQ);
}
void
IOBasicSCSI::beginBusResetRecovery(void)
{
finishBusResetRecovery();
}
void
IOBasicSCSI::busResetRecoveryCommandComplete(struct IOBasicSCSI::context *cx)
{
}
void
IOBasicSCSI::finishBusResetRecovery(void)
{
IOLog("%s[IOBasicSCSI]: SCSI bus reset recovery complete.\n",getName());
_provider->releaseQueue(kQTypeNormalQ);
_busResetRecoveryInProgress = false;
}
bool
IOBasicSCSI::unitAttentionDetected(struct IOBasicSCSI::context *cx)
{
SCSIResults scsiResults;
cx->scsireq->getResults(&scsiResults);
if (scsiResults.requestSenseDone == true) {
if ((cx->senseData->senseKey & 0x0f) == kUnitAttention) {
return(true);
}
}
return(false);
}
void
IOBasicSCSI::setupUnitAttentionRecovery(struct IOBasicSCSI::context *cx)
{
if (!_unitAttentionRecoveryInProgress) {
_unitAttentionContext->originalIOContext = cx;
_unitAttentionContext->step = 1;
_unitAttentionRecoveryInProgress = true;
beginUnitAttentionRecovery();
}
}
void
IOBasicSCSI::beginUnitAttentionRecovery(void)
{
finishUnitAttentionRecovery();
}
void
IOBasicSCSI::unitAttentionRecoveryCommandComplete(struct IOBasicSCSI::context *cx)
{
}
void
IOBasicSCSI::finishUnitAttentionRecovery(void)
{
_unitAttentionRecoveryInProgress = false;
_unitAttentionContext->originalIOContext->scsireq->execute();
}
bool
IOBasicSCSI::automaticRetry(struct IOBasicSCSI::context *cx)
{
SCSIResults scsiResults;
if (unitAttentionDetected(cx)) {
setupUnitAttentionRecovery(cx);
return(true);
}
cx->scsireq->getResults(&scsiResults);
if (scsiResults.returnCode != kIOReturnSuccess &&
scsiResults.returnCode != kIOReturnError) {
}
if (scsiResults.returnCode == kIOReturnAborted ||
scsiResults.returnCode == kIOReturnTimeout) {
if (!cx->retryInProgress) {
cx->retryInProgress = true;
cx->retryCount = kMaxRetries;
}
if (cx->retryCount > 0) {
IOLog("%s[IOBasicSCSI]: AutoRetry cx @ %08lx, cmd @ %08lx; %ld retries to go.\n",
getName(),(unsigned long)cx,(unsigned long)cx->scsireq,cx->retryCount);
cx->retryCount--;
cx->scsireq->execute();
return(true);
} else {
cx->retryInProgress = false;
return(false);
}
}
return(customAutomaticRetry(cx));
}
bool
IOBasicSCSI::customAutomaticRetry(struct IOBasicSCSI::context *cx)
{
return(false);
}
void
IOBasicSCSI::genericCompletion(struct IOBasicSCSI::context *cx)
{
switch (cx->state) {
case kSimpleSynchIO :
if (!automaticRetry(cx)) {
cx->sync->signal(kIOReturnSuccess,false);
}
break;
case kAsyncReadWrite :
if (!automaticRetry(cx)) {
RWCompletion(cx);
deleteContext(cx);
}
break;
case kHandlingRecoveryAfterBusReset :
if (!automaticRetry(cx)) {
busResetRecoveryCommandComplete(cx);
}
break;
case kHandlingUnitAttention :
unitAttentionRecoveryCommandComplete(cx);
break;
case kNone :
case kMaxStateValue :
case kAwaitingPower :
break;
}
return;
}
char *
IOBasicSCSI::getAdditionalDeviceInfoString(void)
{
return("[SCSI]");
}
UInt64
IOBasicSCSI::getBlockSize(void)
{
return(_blockSize);
}
char *
IOBasicSCSI::getProductString(void)
{
return(_product);
}
char *
IOBasicSCSI::getRevisionString(void)
{
return(_rev);
}
char *
IOBasicSCSI::getVendorString(void)
{
return(_vendor);
}
bool
IOBasicSCSI::init(OSDictionary * properties)
{
_inqBuf = NULL;
_inqBufSize = 0;
_inqLen = 0;
_vendor[8] = '\0';
_product[16] = '\0';
_rev[4] = '\0';
_readCapDone = false;
_blockSize = 0;
_maxBlock = 0;
_removable = false;
#ifdef DISKPM
_powerQueue.head = NULL;
_powerQueue.tail = NULL;
_powerQueue.lock = IOLockAlloc();
if (_powerQueue.lock == NULL) {
return(false);
}
#endif
return(super::init(properties));
}
IOReturn
IOBasicSCSI::message(UInt32 type,IOService * provider,void * argument)
{
switch (type) {
case kSCSIClientMsgBusReset :
if (!_busResetRecoveryInProgress) {
setupBusResetRecovery();
}
break;
case (kSCSIClientMsgBusReset | kSCSIClientMsgDone) :
beginBusResetRecovery();
break;
default :
return(super::message(type,provider,argument));
}
return(kIOReturnSuccess);
}
IOService *
IOBasicSCSI::probe(IOService * provider,SInt32 * score)
{
IOReturn result;
OSString * string;
if (!super::probe(provider,score)) {
return(NULL);
}
_provider = (IOSCSIDevice *)provider;
_inqBufSize = kMaxInqSize;
result = allocateInquiryBuffer(&_inqBuf,_inqBufSize);
if (result != kIOReturnSuccess) {
return(NULL);
}
result = doInquiry(_inqBuf,_inqBufSize,&_inqLen);
if (result != kIOReturnSuccess) {
return(NULL);
}
#ifdef notdef
if (_provider->getTarget() == 0) {
IOLog("**%s[IOBasicSCSI]:probe; ignoring SCSI ID %d\n",
getName(),(int)_provider->getTarget());
return(NULL);
}
#endif
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyVendorName));
if (string) {
strncpy(_vendor, string->getCStringNoCopy(), 8);
_vendor[8] = '\0';
}
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyProductName));
if (string) {
strncpy(_product, string->getCStringNoCopy(), 16);
_product[16] = '\0';
}
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyProductRevision));
if (string) {
strncpy(_rev, string->getCStringNoCopy(), 4);
_rev[4] = '\0';
}
if (deviceTypeMatches(_inqBuf,_inqLen,score)) {
return(this);
} else {
return(NULL);
}
}
void
IOBasicSCSI::dequeueCommands(void)
{
#ifdef DISKPM
struct queue *q;
IOReturn result;
q = &_powerQueue;
IOLockLock(q->lock);
while (q->head) {
cx = q->head;
if (pm_vars->myCurrentState != cx->desiredPower) {
break;
}
q->head = cx->next;
if (q->head == NULL) {
q->tail = NULL;
}
cx->state = kNone;
if (cx->isSync) {
cx->sync->signal(kIOReturnSuccess, false);
} else {
result = standardAsyncReadWriteExecute(cx);
if (result != kIOReturnSuccess) {
RWCompletion(cx);
}
}
};
IOLockUnlock(q->lock);
#endif
}
void
IOBasicSCSI::queueCommand(struct context *cx,bool isSync,UInt32 desiredPower)
{
#ifndef DISKPM //for now, just return immediately without queueing
if (isSync == kAsync) {
(void)standardAsyncReadWriteExecute(cx);
}
#else
struct queue *q;
q = &_powerQueue;
cx->next = NULL;
cx->state = kAwaitingPower;
IOLockLock(q->lock);
if (q->head == NULL) {
q->head = cx;
q->tail = q->head;
} else {
q->tail->next = cx;
q->tail = cx;
}
IOLockUnlock(q->lock);
dequeueCommands();
if (isSync) {
cx->sync->wait(false);
}
#endif //DISKPM
}
IOReturn
IOBasicSCSI::reportBlockSize(UInt64 *blockSize)
{
IOReturn result;
*blockSize = 0;
result = kIOReturnSuccess;
if (_readCapDone == false) {
result = doReadCapacity(&_blockSize,&_maxBlock);
_readCapDone = true;
}
if (result == kIOReturnSuccess) {
*blockSize = _blockSize;
}
return(result);
}
IOReturn
IOBasicSCSI::reportEjectability(bool *isEjectable)
{
*isEjectable = true;
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportLockability(bool *isLockable)
{
*isLockable = true;
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportMaxReadTransfer (UInt64 blocksize,UInt64 *max)
{
*max = blocksize * 65536;
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportMaxValidBlock(UInt64 *maxBlock)
{
IOReturn result;
*maxBlock = 0;
result = kIOReturnSuccess;
if (_readCapDone == false) {
result = doReadCapacity(&_blockSize,&_maxBlock);
_readCapDone = true;
}
if (result == kIOReturnSuccess) {
*maxBlock = _maxBlock;
}
return(result);
}
IOReturn
IOBasicSCSI::reportMaxWriteTransfer(UInt64 blocksize,UInt64 *max)
{
*max = blocksize * 65536;
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportPollRequirements(bool *pollRequired,bool *pollIsExpensive)
{
*pollIsExpensive = false;
*pollRequired = _removable;
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportRemovability(bool *isRemovable)
{
if (_inqLen > 0) {
if (_inqBuf[1] & 0x80) {
*isRemovable = true;
_removable = true;
} else {
*isRemovable = false;
_removable = false;
}
} else {
*isRemovable = false;
}
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportWriteProtection(bool *writeProtected)
{
struct context *cx;
struct IOModeSensecdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
SCSIResults scsiResults;
UInt8 *buf;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOModeSensecdb *)&scsiCDB.cdb;
c->opcode = SOP_MODESENSE;
c->lunbits = 0;
c->pagecode = 0 | 0x01;
c->reserved = 0;
c->len = kModeSenseSize;
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true );
req->setTimeout( 30000 );
result = allocateTempBuffer(&buf,kModeSenseSize);
if (result == kIOReturnSuccess) {
cx->memory = IOMemoryDescriptor::withAddress((void *)buf,
kModeSenseSize,
kIODirectionIn);
req->setPointers( cx->memory, kModeSenseSize, false );
queueCommand(cx,kSync,getReportWriteProtectionPowerState());
result = simpleSynchIO(cx);
if (result == kIOReturnUnderrun) {
cx->scsireq->getResults( &scsiResults );
if (scsiResults.bytesTransferred >= 4)
result = kIOReturnSuccess;
}
if (result == kIOReturnSuccess) {
if (buf[2] & 0x80) {
*writeProtected = true;
} else {
*writeProtected = false;
}
}
deleteTempBuffer(buf,kModeSenseSize);
}
deleteContext(cx);
return(result);
}
IOReturn
IOBasicSCSI::simpleAsynchIO(struct IOBasicSCSI::context *cx)
{
IOSCSICommand *req;
IOReturn result;
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx );
cx->state = kSimpleSynchIO;
result = req->execute();
if (result == true ) {
result = req->getResults((SCSIResults *) 0);
}
return(result);
}
IOReturn
IOBasicSCSI::simpleSynchIO(struct context *cx)
{
IOSCSICommand *req;
IOReturn result;
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx );
cx->state = kSimpleSynchIO;
result = req->execute();
if (result == true ) {
cx->sync->wait(false);
result = req->getResults((SCSIResults *) 0);
} else {
}
return(result);
}
IOReturn
IOBasicSCSI::standardAsyncReadWrite(IOMemoryDescriptor *buffer,
UInt32 block,UInt32 nblks,
IOStorageCompletion completion)
{
struct context *cx;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt32 reqSenseLength;
UInt32 timeoutSeconds;
UInt8 *cdb;
bool isWrite;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
buffer->retain();
cx->memory = buffer;
if (buffer->getDirection() == kIODirectionOut) {
isWrite = true;
} else {
isWrite = false;
}
req = cx->scsireq;
cx->completion = completion;
bzero( &scsiCDB, sizeof(scsiCDB) );
req->setPointers( buffer, nblks * getBlockSize(), isWrite );
req->setCallback( this, IOBasicSCSI_gc_glue, cx );
cx->state = kAsyncReadWrite;
cdb = (UInt8 *) &scsiCDB.cdb;
if (isWrite) {
scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&timeoutSeconds);
} else {
scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&timeoutSeconds);
}
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, reqSenseLength, false, true );
req->setTimeout( timeoutSeconds * 1000 );
queueCommand(cx,kAsync,getReadWritePowerState());
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::standardAsyncReadWriteExecute(struct context *cx)
{
return(cx->scsireq->execute());
}
IOReturn
IOBasicSCSI::standardSyncReadWrite(IOMemoryDescriptor *buffer,UInt32 block,UInt32 nblks)
{
struct context *cx;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt32 reqSenseLength;
UInt32 reqTimeoutSeconds;
UInt8 *cdb;
bool isWrite;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
cx->memory = buffer;
buffer->retain();
if (buffer->getDirection() == kIODirectionOut) {
isWrite = true;
} else {
isWrite = false;
}
bzero(&scsiCDB,sizeof(scsiCDB));
req = cx->scsireq;
req->setPointers(buffer,(nblks * getBlockSize()),isWrite);
cdb = (UInt8 *)&scsiCDB.cdb;
if (isWrite) {
scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&reqTimeoutSeconds);
} else {
scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&reqTimeoutSeconds);
}
req->setCDB(&scsiCDB);
req->setPointers(cx->senseDataDesc,reqSenseLength,false,true);
req->setTimeout(reqTimeoutSeconds * 1000);
queueCommand(cx,kSync,getReadWritePowerState());
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
bool
IOBasicSCSI::start(IOService *provider)
{
bool result;
_busResetContext = allocateContext();
if (_busResetContext == NULL) {
return(false);
}
_busResetContext->state = kHandlingRecoveryAfterBusReset;
_busResetRecoveryInProgress = false;
_unitAttentionContext = allocateContext();
if (_unitAttentionContext == NULL) {
return(false);
}
_unitAttentionContext->state = kHandlingUnitAttention;
_unitAttentionRecoveryInProgress = false;
result = provider->open(this,0,0);
if (result != true) {
IOLog("open result is false\n");
}
return(true);
}
char *
IOBasicSCSI::stringFromState(stateValue state)
{
static char *stateNames[] = {
"kNone",
"kAsyncReadWrite",
"kSimpleSynchIO",
"kHandlingUnitAttention",
"khandlingRecoveryAfterBusReset"
};
if (state < 0 || state > kMaxValidState) {
return("invalid");
}
return(stateNames[state]);
}