|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1993 NeXT Computer, Inc. ! 3: * ! 4: * AHAThread.m - I/O thread methods for Adaptec 1542 driver. ! 5: * ! 6: * HISTORY ! 7: * ! 8: * 13 Apr 1993 Doug Mitchell at NeXT ! 9: * Split off from AHAController.m. ! 10: */ ! 11: ! 12: #import "AHAThread.h" ! 13: #import "AHATypes.h" ! 14: #import "AHAInline.h" ! 15: #import "AHAControllerPrivate.h" ! 16: #import "scsivar.h" ! 17: #import <driverkit/generalFuncs.h> ! 18: #import <driverkit/kernelDriver.h> ! 19: #import <kernserv/prototypes.h> ! 20: #import <sys/param.h> ! 21: ! 22: static void ahaTimeout(void *arg); ! 23: ! 24: #define AUTO_SENSE_ENABLE 1 ! 25: ! 26: /* ! 27: * Template for timeout message. ! 28: */ ! 29: static msg_header_t timeoutMsgTemplate = { ! 30: 0, // msg_unused ! 31: 1, // msg_simple ! 32: sizeof(msg_header_t), // msg_size ! 33: MSG_TYPE_NORMAL, // msg_type ! 34: PORT_NULL, // msg_local_port ! 35: PORT_NULL, // msg_remote_port - TO ! 36: // BE FILLED IN ! 37: IO_TIMEOUT_MSG // msg_id ! 38: }; ! 39: ! 40: @implementation AHAController(IOThread) ! 41: ! 42: /* ! 43: * I/O thread version of -executeRequest:buffer:client. ! 44: * The approximate logic is: ! 45: * Build up an internal ccb describing this request ! 46: * Put it on the queue of pending commands ! 47: * Run as many pending commands as possible ! 48: * ! 49: * Returns non-zero if no ccb was available for the command. This case ! 50: * must be handled gracefully by the caller by enqueueing the request on ! 51: * commandQ. ! 52: */ ! 53: - (int)threadExecuteRequest : (AHACommandBuf *)cmdBuf ! 54: { ! 55: struct ccb *ccb; ! 56: IOSCSIRequest *scsiReq = cmdBuf->scsiReq; ! 57: ! 58: ddm_thr("threadExecuteRequest cmdBuf 0x%x\n", cmdBuf, 2,3,4,5); ! 59: ! 60: ccb = [self allocCcb:(scsiReq->maxTransfer ? YES : NO)]; ! 61: if(ccb == NULL) { ! 62: return 1; ! 63: } ! 64: if([self ccbFromCmd:cmdBuf ccb:ccb]) { ! 65: /* ! 66: * Command reject. Error status is in ! 67: * cmdBuf->scsiReq->driverStatus. ! 68: * Notify caller and clean up. ! 69: */ ! 70: [self freeCcb:ccb]; ! 71: [cmdBuf->cmdLock lock]; ! 72: [cmdBuf->cmdLock unlockWith:CMD_COMPLETE]; ! 73: return 0; ! 74: } ! 75: ! 76: /* ! 77: * Make sure we'll be able to time this command out. This should be ! 78: * rare, so we don't particularly care about how efficient it is. ! 79: */ ! 80: ccb->timeoutPort = interruptPortKern; ! 81: IOScheduleFunc(ahaTimeout, ccb, scsiReq->timeoutLength); ! 82: ! 83: /* ! 84: * Stick this command on the list of pending ones, and run them. ! 85: */ ! 86: queue_enter(&pendingQ, ccb, struct ccb *, ccbQ); ! 87: [self runPendingCommands]; ! 88: return 0; ! 89: ! 90: } ! 91: ! 92: /* ! 93: * I/O thread version of -resetSCSIBus. ! 94: * We also interpret this to mean we should reset the board. ! 95: * cmdBuf == NULL indicates a call from within the I/O thread for ! 96: * a reason other than -resetSCSIBus (e.g., timeout recovery). ! 97: */ ! 98: - (void)threadResetBus : (AHACommandBuf *)cmdBuf ! 99: { ! 100: ! 101: aha_ctrl_reg_t ctrl = { 0 }; ! 102: struct ccb *ccb; ! 103: queue_head_t *q; ! 104: ! 105: ddm_thr("threadResetBus\n", 1,2,3,4,5); ! 106: ! 107: /* ! 108: * Abort all outstanding and pending commands. ! 109: */ ! 110: for(q=&outstandingQ; q!=&pendingQ; q=&pendingQ) { ! 111: while(!queue_empty(q)) { ! 112: ccb = (struct ccb *)queue_first(q); ! 113: queue_remove(q, ccb, struct ccb *, ccbQ); ! 114: if(q == &outstandingQ) { ! 115: ASSERT(outstandingCount != 0); ! 116: outstandingCount--; ! 117: } ! 118: [self commandCompleted:ccb reason:CS_Reset]; ! 119: } ! 120: } ! 121: ! 122: /* ! 123: * Now reset the hardware. ! 124: */ ! 125: aha_reset_board(ioBase, ahaBoardId); ! 126: aha_setup_mb_area(ioBase, ahaMbArea, ahaCcb); ! 127: ! 128: ctrl.scsi_rst = 1; ! 129: aha_put_ctrl(ioBase, ctrl); ! 130: ! 131: IOLog("Resetting SCSI Bus...\n"); ! 132: IOSleep(10000); ! 133: ! 134: /* ! 135: * Notify caller of completion if appropriate. ! 136: */ ! 137: if(cmdBuf) { ! 138: ddm_thr("threadResetBus: I/O complete on cmdBuf 0x%x\n", ! 139: cmdBuf, 2,3,4,5); ! 140: cmdBuf->result = SR_IOST_GOOD; ! 141: [cmdBuf->cmdLock lock]; ! 142: [cmdBuf->cmdLock unlockWith:CMD_COMPLETE]; ! 143: } ! 144: } ! 145: ! 146: /* ! 147: * Build a ccb from the specified AHACommandBuf. Returns non-zero on error ! 148: * (i.e., on command reject from this method). In that case, error status ! 149: * is in cmdBuf->scsiReq->driverStatus. ! 150: */ ! 151: - (int) ccbFromCmd:(AHACommandBuf *)cmdBuf ccb:(struct ccb *)ccb ! 152: { ! 153: IOSCSIRequest *scsiReq = cmdBuf->scsiReq; ! 154: union cdb *cdbp = &scsiReq->cdb; ! 155: int cdb_ctrl; ! 156: vm_offset_t addr, phys; ! 157: vm_size_t len; ! 158: unsigned int pages; ! 159: unsigned int cmdlen; ! 160: ! 161: /* ! 162: * Figure out what kind of cdb we've been given ! 163: * and snag the ctrl byte ! 164: */ ! 165: switch (SCSI_OPGROUP(cdbp->cdb_opcode)) { ! 166: ! 167: case OPGROUP_0: ! 168: cmdlen = sizeof (struct cdb_6); ! 169: cdb_ctrl = cdbp->cdb_c6.c6_ctrl; ! 170: break; ! 171: ! 172: case OPGROUP_1: ! 173: case OPGROUP_2: ! 174: cmdlen = sizeof (struct cdb_10); ! 175: cdb_ctrl = cdbp->cdb_c10.c10_ctrl; ! 176: break; ! 177: ! 178: case OPGROUP_5: ! 179: cmdlen = sizeof (struct cdb_12); ! 180: cdb_ctrl = cdbp->cdb_c12.c12_ctrl; ! 181: break; ! 182: ! 183: /* ! 184: * Group 6 and 7 commands allow a user-specified CDB length. ! 185: */ ! 186: case OPGROUP_6: ! 187: if(scsiReq->cdbLength) ! 188: cmdlen = scsiReq->cdbLength; ! 189: else ! 190: cmdlen = sizeof (struct cdb_6); ! 191: cdb_ctrl = 0; ! 192: break; ! 193: ! 194: case OPGROUP_7: ! 195: if(scsiReq->cdbLength) ! 196: cmdlen = scsiReq->cdbLength; ! 197: else ! 198: cmdlen = sizeof (struct cdb_10); ! 199: cdb_ctrl = 0; ! 200: break; ! 201: ! 202: default: ! 203: scsiReq->driverStatus = SR_IOST_CMDREJ; ! 204: return 1; ! 205: } ! 206: ! 207: /* ! 208: * Make sure nothing unreasonable has been asked of us ! 209: */ ! 210: if ((cdb_ctrl & CTRL_LINKFLAG) != CTRL_NOLINK) { ! 211: scsiReq->driverStatus = SR_IOST_CMDREJ; ! 212: return 1; ! 213: } ! 214: ! 215: addr = (vm_offset_t)cmdBuf->buffer; ! 216: len = scsiReq->maxTransfer; ! 217: ! 218: if (len > 0) ! 219: pages = (round_page(addr+len) - trunc_page(addr)) / PAGE_SIZE; ! 220: else ! 221: pages = 0; ! 222: ! 223: ccb->cdb = *cdbp; ! 224: ccb->cdb_len = cmdlen; ! 225: ! 226: ccb->data_in = scsiReq->read; ! 227: ccb->data_out = !scsiReq->read; ! 228: ccb->target = scsiReq->target; ! 229: ccb->lun = scsiReq->lun; ! 230: #if AUTO_SENSE_ENABLE ! 231: ccb->reqsense_len = sizeof(esense_reply_t); ! 232: #else AUTO_SENSE_ENABLE ! 233: ccb->reqsense_len = 1; /* no auto reqsense */ ! 234: #endif AUTO_SENSE_ENABLE ! 235: ! 236: /* ! 237: * Note Adaptec does not support command queueing. Synchronous ! 238: * negotiation can only be disabled by jumper. Disconnects can ! 239: * not be disabled. ! 240: */ ! 241: ! 242: ccb->cmdBuf = cmdBuf; ! 243: ccb->total_xfer_len = 0; ! 244: IOGetTimestamp(&ccb->startTime); ! 245: ! 246: /* ! 247: * Set up the DMA address and length. If we have more than one page, ! 248: * then chances are that we'll have to use scatter/gather to collect ! 249: * all the physical pages into a single transfer. ! 250: */ ! 251: if (pages == 0) { ! 252: aha_put_24(0, ccb->data_addr); ! 253: aha_put_24(0, ccb->data_len); ! 254: ccb->oper = AHA_CCB_INITIATOR_RESID; ! 255: } ! 256: else if (pages == 1) { ! 257: ! 258: if(IOPhysicalFromVirtual(cmdBuf->client, addr, &phys)) { ! 259: IOLog("%s: Can\'t get physical address\n", ! 260: [self name]); ! 261: scsiReq->driverStatus = SR_IOST_INT; ! 262: return 1; ! 263: } ! 264: ! 265: ccb->dmaList[0] = [self createDMABufferFor:&phys ! 266: length:len read:scsiReq->read ! 267: needsLowMemory:YES limitSize:NO]; ! 268: ! 269: if (ccb->dmaList[0] == NULL) { ! 270: [self abortDMA:ccb->dmaList length:len]; ! 271: scsiReq->driverStatus = SR_IOST_INT; ! 272: return 1; ! 273: } ! 274: ! 275: aha_put_24(phys, ccb->data_addr); ! 276: aha_put_24(len, ccb->data_len); ! 277: ! 278: ccb->oper = AHA_CCB_INITIATOR_RESID; ! 279: ccb->total_xfer_len = len; ! 280: } ! 281: else { ! 282: vm_offset_t lastPhys = 0; ! 283: unsigned int sgEntry = 0; ! 284: unsigned int maxEntries = MIN(pages, AHA_SG_COUNT); ! 285: IOEISADMABuffer *dmaBuf = ccb->dmaList; ! 286: ! 287: for (sgEntry=0; sgEntry < maxEntries; sgEntry++) { ! 288: struct aha_sg *sg = &ccb->sg_list[sgEntry]; ! 289: unsigned int thisLength; ! 290: ! 291: thisLength = MIN(len, round_page(addr+1) - addr); ! 292: ! 293: if(IOPhysicalFromVirtual(cmdBuf->client, ! 294: addr, &phys)) { ! 295: IOLog("%s: Can\'t get physical address\n", ! 296: [self name]); ! 297: [self abortDMA:ccb->dmaList ! 298: length:ccb->total_xfer_len]; ! 299: scsiReq->driverStatus = SR_IOST_INT; ! 300: return 1; ! 301: } ! 302: *dmaBuf = [self createDMABufferFor:&phys ! 303: length:thisLength ! 304: read:scsiReq->read ! 305: needsLowMemory:YES limitSize:NO]; ! 306: ! 307: if (*dmaBuf == NULL) { ! 308: [self abortDMA:ccb->dmaList ! 309: length:ccb->total_xfer_len]; ! 310: scsiReq->driverStatus = SR_IOST_INT; ! 311: return 1; ! 312: } ! 313: ! 314: aha_put_24(phys, sg->addr); ! 315: aha_put_24(thisLength, sg->len); ! 316: ! 317: ccb->total_xfer_len += thisLength; ! 318: ! 319: addr += thisLength; ! 320: len -= thisLength; ! 321: lastPhys = phys; ! 322: dmaBuf++; ! 323: } ! 324: ! 325: if(IOPhysicalFromVirtual(IOVmTaskSelf(), ! 326: (unsigned)ccb->sg_list, ! 327: &phys)) { ! 328: IOLog("%s: Can\'t get physical address of ccb\n", ! 329: [self name]); ! 330: IOPanic("AHAController"); ! 331: } ! 332: aha_put_24(phys, ccb->data_addr); ! 333: aha_put_24(sgEntry * sizeof(struct aha_sg), ccb->data_len); ! 334: ! 335: ccb->oper = AHA_CCB_INITIATOR_RESID_SG; ! 336: } ! 337: ! 338: return 0; ! 339: } ! 340: ! 341: /* ! 342: * If any commands pending, and the controller's queue is not full, ! 343: * run the new commands. ! 344: */ ! 345: - runPendingCommands ! 346: { ! 347: unsigned int cmdsToRun; ! 348: struct ccb *ccb; ! 349: ! 350: cmdsToRun = AHA_QUEUE_SIZE - outstandingCount; ! 351: ! 352: while (cmdsToRun > 0 && !queue_empty(&pendingQ)) { ! 353: ! 354: /* ! 355: * Dequeue pending command and add to the outstanding queue. ! 356: */ ! 357: ccb = (struct ccb *) queue_first(&pendingQ); ! 358: queue_remove(&pendingQ, ccb, struct ccb *, ccbQ); ! 359: if (!ccb) ! 360: break; ! 361: ! 362: queue_enter(&outstandingQ, ccb, struct ccb *, ccbQ); ! 363: outstandingCount++; ! 364: ! 365: /* ! 366: * Let 'er rip... ! 367: */ ! 368: ccb->mb_out->mb_stat = AHA_MB_OUT_START; ! 369: aha_start_scsi(ioBase); ! 370: ! 371: /* ! 372: * Accumulate some simple statistics: the max queue length ! 373: * and enough info to compute a running average of the queue ! 374: * length. ! 375: */ ! 376: maxQueueLen = MAX(maxQueueLen, outstandingCount); ! 377: queueLenTotal += outstandingCount; ! 378: totalCommands++; ! 379: ! 380: cmdsToRun--; ! 381: } ! 382: return self; ! 383: } ! 384: ! 385: /* ! 386: * A command is done. Figure out what happened, and notify the ! 387: * client appropriately. Called upon detection of I/O complete interrupt, ! 388: * timeout detection, or when we reset the bus and blow off pending ! 389: * commands. ! 390: */ ! 391: - (void)commandCompleted : (struct ccb *) ccb ! 392: reason : (completeStatus)reason ! 393: { ! 394: ns_time_t currentTime; ! 395: IOSCSIRequest *scsiReq; ! 396: AHACommandBuf *cmdBuf = ccb->cmdBuf; ! 397: ! 398: ASSERT(cmdBuf != NULL); ! 399: scsiReq = cmdBuf->scsiReq; ! 400: ASSERT(scsiReq != NULL); ! 401: ! 402: ddm_thr("commandCompleted: ccb 0x%x cmdBuf 0x%x reason %d\n", ! 403: ccb, cmdBuf, reason, 4,5); ! 404: ! 405: scsiReq->scsiStatus = ccb->target_status; ! 406: ! 407: switch(reason) { ! 408: case CS_Timeout: ! 409: scsiReq->driverStatus = SR_IOST_IOTO; ! 410: break; ! 411: case CS_Reset: ! 412: scsiReq->driverStatus = SR_IOST_RESET; ! 413: break; ! 414: case CS_Complete: ! 415: switch (ccb->host_status) { ! 416: ! 417: /* ! 418: * Handle success and data overrun/underrun. We can handle ! 419: * overrun/underrun as a normal case because the controller ! 420: * sets the data_len field to be the actual number of bytes ! 421: * transferred regardless of overrun. ! 422: */ ! 423: case AHA_HOST_SUCCESS: ! 424: case AHA_HOST_DATA_OVRUN: ! 425: [self completeDMA:ccb->dmaList ! 426: length:scsiReq->maxTransfer]; ! 427: scsiReq->bytesTransferred = ccb->total_xfer_len - ! 428: aha_get_24(ccb->data_len); ! 429: ! 430: /* ! 431: * Everything looks good. Make sure the SCSI status byte ! 432: * is cool before we really say everything is hunky-dory. ! 433: */ ! 434: if (scsiReq->scsiStatus == STAT_GOOD) ! 435: scsiReq->driverStatus = SR_IOST_GOOD; ! 436: else if (scsiReq->scsiStatus == STAT_CHECK) { ! 437: if(AUTO_SENSE_ENABLE) { ! 438: ! 439: esense_reply_t *sensePtr; ! 440: ! 441: scsiReq->driverStatus = SR_IOST_CHKSV; ! 442: ! 443: /* ! 444: * Sense data starts immediately after the actual ! 445: * cdb area we use, not an entire union cdb. ! 446: */ ! 447: sensePtr = (esense_reply_t *) ! 448: (((char *)&ccb->cdb) + ccb->cdb_len); ! 449: scsiReq->senseData = *sensePtr; ! 450: } ! 451: else { ! 452: scsiReq->driverStatus = SR_IOST_CHKSNV; ! 453: } ! 454: } ! 455: else ! 456: scsiReq->driverStatus = ST_IOST_BADST; ! 457: break; ! 458: ! 459: case AHA_HOST_SEL_TIMEOUT: ! 460: [self abortDMA:ccb->dmaList length:scsiReq->maxTransfer]; ! 461: scsiReq->driverStatus = SR_IOST_SELTO; ! 462: break; ! 463: ! 464: default: ! 465: IOLog("AHA interrupt: bad status %x\n", ccb->host_status); ! 466: [self abortDMA:ccb->dmaList length:scsiReq->maxTransfer]; ! 467: scsiReq->driverStatus = SR_IOST_INVALID; ! 468: break; ! 469: } /* switch host_status */ ! 470: } /* switch status */ ! 471: ! 472: IOGetTimestamp(¤tTime); ! 473: scsiReq->totalTime = currentTime - ccb->startTime; ! 474: cmdBuf->result = scsiReq->driverStatus; ! 475: ! 476: /* ! 477: * Wake up client. ! 478: */ ! 479: ddm_thr("commandCompleted: I/O complete on cmdBuf 0x%x\n", ! 480: cmdBuf, 2,3,4,5); ! 481: [cmdBuf->cmdLock lock]; ! 482: [cmdBuf->cmdLock unlockWith:CMD_COMPLETE]; ! 483: ! 484: /* ! 485: * Free the CCB and clean up possible pending timeout. ! 486: */ ! 487: (void) IOUnscheduleFunc(ahaTimeout, ccb); ! 488: [self freeCcb:ccb]; ! 489: } ! 490: ! 491: /* ! 492: * Alloc/free ccb's. These only come from the array ahaCcb[]. ! 493: * If we can't find one, return NULL - caller will have to try ! 494: * again later. ! 495: */ ! 496: - (struct ccb *)allocCcb : (BOOL)doDMA ! 497: { ! 498: struct ccb *ccb; ! 499: int i; ! 500: ! 501: if(numFreeCcbs == 0) { ! 502: ddm_thr("allocCcb: numFreeCcbs = 0\n", 1,2,3,4,5); ! 503: return NULL; ! 504: } ! 505: ! 506: /* ! 507: * Since numFreeCcbs is non-zero, there has to be one available ! 508: * in ahaCcb[]. ! 509: */ ! 510: ccb = ahaCcb; ! 511: while (ccb <= &ahaCcb[AHA_QUEUE_SIZE - 1] && ccb->in_use) { ! 512: ccb++; ! 513: } ! 514: if (ccb > &ahaCcb[AHA_QUEUE_SIZE - 1]) { ! 515: IOPanic("AHAController: out of ccbs"); ! 516: } ! 517: numFreeCcbs--; ! 518: ccb->in_use = TRUE; ! 519: ! 520: /* ! 521: * Null out dmaList. ! 522: */ ! 523: for(i=0; i<AHA_SG_COUNT; i++) { ! 524: ccb->dmaList[i] = NULL; ! 525: } ! 526: ! 527: /* ! 528: * Acquire the reentrant DMA lock. This is a nop on EISA machines. ! 529: * ! 530: * Although -reserveDMALock is reentrant for multiple threads on ! 531: * one device, it is *not* reentrant for one thread. Thus we should ! 532: * only call it if we don't already hold the lock. ! 533: * Also, avoid this if we're not going to do any DMA. ! 534: */ ! 535: if(doDMA && (++dmaLockCount == 1)) { ! 536: ddm_thr("allocCcb: calling reserveDMALock\n", 1,2,3,4,5); ! 537: [super reserveDMALock]; ! 538: } ! 539: ddm_thr("allocCcb: returning 0x%x\n", ccb, 2,3,4,5); ! 540: ! 541: return ccb; ! 542: } ! 543: ! 544: - (void)freeCcb : (struct ccb *)ccb ! 545: { ! 546: BOOL didDMA = (ccb->total_xfer_len ? YES : NO); ! 547: ! 548: ddm_thr("freeCcb: ccb 0x%x\n", ccb, 2,3,4,5); ! 549: ccb->in_use = FALSE; ! 550: numFreeCcbs++; ! 551: if(didDMA && (--dmaLockCount == 0)) { ! 552: ddm_thr("freeCcb: calling releaseDMALock\n", ! 553: 1,2,3,4,5); ! 554: [super releaseDMALock]; ! 555: } ! 556: } ! 557: ! 558: - (void) completeDMA:(IOEISADMABuffer *) dmaList length:(unsigned int) xferLen ! 559: { ! 560: IOEISADMABuffer *buf = &dmaList[0]; ! 561: int i; ! 562: ! 563: for (i = 0; i < AHA_SG_COUNT; i++, buf++) { ! 564: if(*buf) { ! 565: [self freeDMABuffer:*buf]; ! 566: } ! 567: else { ! 568: return; ! 569: } ! 570: } ! 571: } ! 572: ! 573: ! 574: - (void) abortDMA:(IOEISADMABuffer *) dmaList length:(unsigned int) xferLen ! 575: { ! 576: IOEISADMABuffer *buf = &dmaList[0]; ! 577: int i; ! 578: ! 579: for (i = 0; i < AHA_SG_COUNT; i++, buf++) { ! 580: if(*buf) { ! 581: [self abortDMABuffer:*buf]; ! 582: } ! 583: else { ! 584: return; ! 585: } ! 586: } ! 587: } ! 588: ! 589: @end ! 590: ! 591: ! 592: /* ! 593: * Handle timeouts. We just send a timeout message to the I/O thread ! 594: * so it wakes up. ! 595: */ ! 596: static void ! 597: ahaTimeout(void *arg) ! 598: { ! 599: ! 600: struct ccb *ccb = arg; ! 601: msg_header_t msg = timeoutMsgTemplate; ! 602: msg_return_t mrtn; ! 603: ! 604: if(!ccb->in_use) { ! 605: /* ! 606: * Race condition - this CCB got completed another way. ! 607: * No problem. ! 608: */ ! 609: return; ! 610: } ! 611: msg.msg_remote_port = ccb->timeoutPort; ! 612: IOLog("AHA timeout\n"); ! 613: if(mrtn = msg_send_from_kernel(&msg, MSG_OPTION_NONE, 0)) { ! 614: IOLog("ahaTimeout: msg_send_from_kernel() returned %d\n", ! 615: mrtn); ! 616: } ! 617: } ! 618:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.