|
|
Sample Programs from NeXSTEP 3.3
/*
* Copyright (c) 1993 NeXT Computer, Inc.
*
* AHAThread.m - I/O thread methods for Adaptec 1542 driver.
*
* HISTORY
*
* 13 Apr 1993 Doug Mitchell at NeXT
* Split off from AHAController.m.
*/
#import "AHAThread.h"
#import "AHATypes.h"
#import "AHAInline.h"
#import "AHAControllerPrivate.h"
#import "scsivar.h"
#import <driverkit/generalFuncs.h>
#import <driverkit/kernelDriver.h>
#import <kernserv/prototypes.h>
#import <sys/param.h>
static void ahaTimeout(void *arg);
#define AUTO_SENSE_ENABLE 1
/*
* Template for timeout message.
*/
static msg_header_t timeoutMsgTemplate = {
0, // msg_unused
1, // msg_simple
sizeof(msg_header_t), // msg_size
MSG_TYPE_NORMAL, // msg_type
PORT_NULL, // msg_local_port
PORT_NULL, // msg_remote_port - TO
// BE FILLED IN
IO_TIMEOUT_MSG // msg_id
};
@implementation AHAController(IOThread)
/*
* I/O thread version of -executeRequest:buffer:client.
* The approximate logic is:
* Build up an internal ccb describing this request
* Put it on the queue of pending commands
* Run as many pending commands as possible
*
* Returns non-zero if no ccb was available for the command. This case
* must be handled gracefully by the caller by enqueueing the request on
* commandQ.
*/
- (int)threadExecuteRequest : (AHACommandBuf *)cmdBuf
{
struct ccb *ccb;
IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
ddm_thr("threadExecuteRequest cmdBuf 0x%x\n", cmdBuf, 2,3,4,5);
ccb = [self allocCcb:(scsiReq->maxTransfer ? YES : NO)];
if(ccb == NULL) {
return 1;
}
if([self ccbFromCmd:cmdBuf ccb:ccb]) {
/*
* Command reject. Error status is in
* cmdBuf->scsiReq->driverStatus.
* Notify caller and clean up.
*/
[self freeCcb:ccb];
[cmdBuf->cmdLock lock];
[cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
return 0;
}
/*
* Make sure we'll be able to time this command out. This should be
* rare, so we don't particularly care about how efficient it is.
*/
ccb->timeoutPort = interruptPortKern;
IOScheduleFunc(ahaTimeout, ccb, scsiReq->timeoutLength);
/*
* Stick this command on the list of pending ones, and run them.
*/
queue_enter(&pendingQ, ccb, struct ccb *, ccbQ);
[self runPendingCommands];
return 0;
}
/*
* I/O thread version of -resetSCSIBus.
* We also interpret this to mean we should reset the board.
* cmdBuf == NULL indicates a call from within the I/O thread for
* a reason other than -resetSCSIBus (e.g., timeout recovery).
*/
- (void)threadResetBus : (AHACommandBuf *)cmdBuf
{
aha_ctrl_reg_t ctrl = { 0 };
struct ccb *ccb;
queue_head_t *q;
ddm_thr("threadResetBus\n", 1,2,3,4,5);
/*
* Abort all outstanding and pending commands.
*/
for(q=&outstandingQ; q!=&pendingQ; q=&pendingQ) {
while(!queue_empty(q)) {
ccb = (struct ccb *)queue_first(q);
queue_remove(q, ccb, struct ccb *, ccbQ);
if(q == &outstandingQ) {
ASSERT(outstandingCount != 0);
outstandingCount--;
}
[self commandCompleted:ccb reason:CS_Reset];
}
}
/*
* Now reset the hardware.
*/
aha_reset_board(ioBase, ahaBoardId);
aha_setup_mb_area(ioBase, ahaMbArea, ahaCcb);
ctrl.scsi_rst = 1;
aha_put_ctrl(ioBase, ctrl);
IOLog("Resetting SCSI Bus...\n");
IOSleep(10000);
/*
* Notify caller of completion if appropriate.
*/
if(cmdBuf) {
ddm_thr("threadResetBus: I/O complete on cmdBuf 0x%x\n",
cmdBuf, 2,3,4,5);
cmdBuf->result = SR_IOST_GOOD;
[cmdBuf->cmdLock lock];
[cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
}
}
/*
* Build a ccb from the specified AHACommandBuf. Returns non-zero on error
* (i.e., on command reject from this method). In that case, error status
* is in cmdBuf->scsiReq->driverStatus.
*/
- (int) ccbFromCmd:(AHACommandBuf *)cmdBuf ccb:(struct ccb *)ccb
{
IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
union cdb *cdbp = &scsiReq->cdb;
int cdb_ctrl;
vm_offset_t addr, phys;
vm_size_t len;
unsigned int pages;
unsigned int cmdlen;
/*
* Figure out what kind of cdb we've been given
* and snag the ctrl byte
*/
switch (SCSI_OPGROUP(cdbp->cdb_opcode)) {
case OPGROUP_0:
cmdlen = sizeof (struct cdb_6);
cdb_ctrl = cdbp->cdb_c6.c6_ctrl;
break;
case OPGROUP_1:
case OPGROUP_2:
cmdlen = sizeof (struct cdb_10);
cdb_ctrl = cdbp->cdb_c10.c10_ctrl;
break;
case OPGROUP_5:
cmdlen = sizeof (struct cdb_12);
cdb_ctrl = cdbp->cdb_c12.c12_ctrl;
break;
/*
* Group 6 and 7 commands allow a user-specified CDB length.
*/
case OPGROUP_6:
if(scsiReq->cdbLength)
cmdlen = scsiReq->cdbLength;
else
cmdlen = sizeof (struct cdb_6);
cdb_ctrl = 0;
break;
case OPGROUP_7:
if(scsiReq->cdbLength)
cmdlen = scsiReq->cdbLength;
else
cmdlen = sizeof (struct cdb_10);
cdb_ctrl = 0;
break;
default:
scsiReq->driverStatus = SR_IOST_CMDREJ;
return 1;
}
/*
* Make sure nothing unreasonable has been asked of us
*/
if ((cdb_ctrl & CTRL_LINKFLAG) != CTRL_NOLINK) {
scsiReq->driverStatus = SR_IOST_CMDREJ;
return 1;
}
addr = (vm_offset_t)cmdBuf->buffer;
len = scsiReq->maxTransfer;
if (len > 0)
pages = (round_page(addr+len) - trunc_page(addr)) / PAGE_SIZE;
else
pages = 0;
ccb->cdb = *cdbp;
ccb->cdb_len = cmdlen;
ccb->data_in = scsiReq->read;
ccb->data_out = !scsiReq->read;
ccb->target = scsiReq->target;
ccb->lun = scsiReq->lun;
#if AUTO_SENSE_ENABLE
ccb->reqsense_len = sizeof(esense_reply_t);
#else AUTO_SENSE_ENABLE
ccb->reqsense_len = 1; /* no auto reqsense */
#endif AUTO_SENSE_ENABLE
/*
* Note Adaptec does not support command queueing. Synchronous
* negotiation can only be disabled by jumper. Disconnects can
* not be disabled.
*/
ccb->cmdBuf = cmdBuf;
ccb->total_xfer_len = 0;
IOGetTimestamp(&ccb->startTime);
/*
* Set up the DMA address and length. If we have more than one page,
* then chances are that we'll have to use scatter/gather to collect
* all the physical pages into a single transfer.
*/
if (pages == 0) {
aha_put_24(0, ccb->data_addr);
aha_put_24(0, ccb->data_len);
ccb->oper = AHA_CCB_INITIATOR_RESID;
}
else if (pages == 1) {
if(IOPhysicalFromVirtual(cmdBuf->client, addr, &phys)) {
IOLog("%s: Can\'t get physical address\n",
[self name]);
scsiReq->driverStatus = SR_IOST_INT;
return 1;
}
ccb->dmaList[0] = [self createDMABufferFor:&phys
length:len read:scsiReq->read
needsLowMemory:YES limitSize:NO];
if (ccb->dmaList[0] == NULL) {
[self abortDMA:ccb->dmaList length:len];
scsiReq->driverStatus = SR_IOST_INT;
return 1;
}
aha_put_24(phys, ccb->data_addr);
aha_put_24(len, ccb->data_len);
ccb->oper = AHA_CCB_INITIATOR_RESID;
ccb->total_xfer_len = len;
}
else {
vm_offset_t lastPhys = 0;
unsigned int sgEntry = 0;
unsigned int maxEntries = MIN(pages, AHA_SG_COUNT);
IOEISADMABuffer *dmaBuf = ccb->dmaList;
for (sgEntry=0; sgEntry < maxEntries; sgEntry++) {
struct aha_sg *sg = &ccb->sg_list[sgEntry];
unsigned int thisLength;
thisLength = MIN(len, round_page(addr+1) - addr);
if(IOPhysicalFromVirtual(cmdBuf->client,
addr, &phys)) {
IOLog("%s: Can\'t get physical address\n",
[self name]);
[self abortDMA:ccb->dmaList
length:ccb->total_xfer_len];
scsiReq->driverStatus = SR_IOST_INT;
return 1;
}
*dmaBuf = [self createDMABufferFor:&phys
length:thisLength
read:scsiReq->read
needsLowMemory:YES limitSize:NO];
if (*dmaBuf == NULL) {
[self abortDMA:ccb->dmaList
length:ccb->total_xfer_len];
scsiReq->driverStatus = SR_IOST_INT;
return 1;
}
aha_put_24(phys, sg->addr);
aha_put_24(thisLength, sg->len);
ccb->total_xfer_len += thisLength;
addr += thisLength;
len -= thisLength;
lastPhys = phys;
dmaBuf++;
}
if(IOPhysicalFromVirtual(IOVmTaskSelf(),
(unsigned)ccb->sg_list,
&phys)) {
IOLog("%s: Can\'t get physical address of ccb\n",
[self name]);
IOPanic("AHAController");
}
aha_put_24(phys, ccb->data_addr);
aha_put_24(sgEntry * sizeof(struct aha_sg), ccb->data_len);
ccb->oper = AHA_CCB_INITIATOR_RESID_SG;
}
return 0;
}
/*
* If any commands pending, and the controller's queue is not full,
* run the new commands.
*/
- runPendingCommands
{
unsigned int cmdsToRun;
struct ccb *ccb;
cmdsToRun = AHA_QUEUE_SIZE - outstandingCount;
while (cmdsToRun > 0 && !queue_empty(&pendingQ)) {
/*
* Dequeue pending command and add to the outstanding queue.
*/
ccb = (struct ccb *) queue_first(&pendingQ);
queue_remove(&pendingQ, ccb, struct ccb *, ccbQ);
if (!ccb)
break;
queue_enter(&outstandingQ, ccb, struct ccb *, ccbQ);
outstandingCount++;
/*
* Let 'er rip...
*/
ccb->mb_out->mb_stat = AHA_MB_OUT_START;
aha_start_scsi(ioBase);
/*
* Accumulate some simple statistics: the max queue length
* and enough info to compute a running average of the queue
* length.
*/
maxQueueLen = MAX(maxQueueLen, outstandingCount);
queueLenTotal += outstandingCount;
totalCommands++;
cmdsToRun--;
}
return self;
}
/*
* A command is done. Figure out what happened, and notify the
* client appropriately. Called upon detection of I/O complete interrupt,
* timeout detection, or when we reset the bus and blow off pending
* commands.
*/
- (void)commandCompleted : (struct ccb *) ccb
reason : (completeStatus)reason
{
ns_time_t currentTime;
IOSCSIRequest *scsiReq;
AHACommandBuf *cmdBuf = ccb->cmdBuf;
ASSERT(cmdBuf != NULL);
scsiReq = cmdBuf->scsiReq;
ASSERT(scsiReq != NULL);
ddm_thr("commandCompleted: ccb 0x%x cmdBuf 0x%x reason %d\n",
ccb, cmdBuf, reason, 4,5);
scsiReq->scsiStatus = ccb->target_status;
switch(reason) {
case CS_Timeout:
scsiReq->driverStatus = SR_IOST_IOTO;
break;
case CS_Reset:
scsiReq->driverStatus = SR_IOST_RESET;
break;
case CS_Complete:
switch (ccb->host_status) {
/*
* Handle success and data overrun/underrun. We can handle
* overrun/underrun as a normal case because the controller
* sets the data_len field to be the actual number of bytes
* transferred regardless of overrun.
*/
case AHA_HOST_SUCCESS:
case AHA_HOST_DATA_OVRUN:
[self completeDMA:ccb->dmaList
length:scsiReq->maxTransfer];
scsiReq->bytesTransferred = ccb->total_xfer_len -
aha_get_24(ccb->data_len);
/*
* Everything looks good. Make sure the SCSI status byte
* is cool before we really say everything is hunky-dory.
*/
if (scsiReq->scsiStatus == STAT_GOOD)
scsiReq->driverStatus = SR_IOST_GOOD;
else if (scsiReq->scsiStatus == STAT_CHECK) {
if(AUTO_SENSE_ENABLE) {
esense_reply_t *sensePtr;
scsiReq->driverStatus = SR_IOST_CHKSV;
/*
* Sense data starts immediately after the actual
* cdb area we use, not an entire union cdb.
*/
sensePtr = (esense_reply_t *)
(((char *)&ccb->cdb) + ccb->cdb_len);
scsiReq->senseData = *sensePtr;
}
else {
scsiReq->driverStatus = SR_IOST_CHKSNV;
}
}
else
scsiReq->driverStatus = ST_IOST_BADST;
break;
case AHA_HOST_SEL_TIMEOUT:
[self abortDMA:ccb->dmaList length:scsiReq->maxTransfer];
scsiReq->driverStatus = SR_IOST_SELTO;
break;
default:
IOLog("AHA interrupt: bad status %x\n", ccb->host_status);
[self abortDMA:ccb->dmaList length:scsiReq->maxTransfer];
scsiReq->driverStatus = SR_IOST_INVALID;
break;
} /* switch host_status */
} /* switch status */
IOGetTimestamp(¤tTime);
scsiReq->totalTime = currentTime - ccb->startTime;
cmdBuf->result = scsiReq->driverStatus;
/*
* Wake up client.
*/
ddm_thr("commandCompleted: I/O complete on cmdBuf 0x%x\n",
cmdBuf, 2,3,4,5);
[cmdBuf->cmdLock lock];
[cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
/*
* Free the CCB and clean up possible pending timeout.
*/
(void) IOUnscheduleFunc(ahaTimeout, ccb);
[self freeCcb:ccb];
}
/*
* Alloc/free ccb's. These only come from the array ahaCcb[].
* If we can't find one, return NULL - caller will have to try
* again later.
*/
- (struct ccb *)allocCcb : (BOOL)doDMA
{
struct ccb *ccb;
int i;
if(numFreeCcbs == 0) {
ddm_thr("allocCcb: numFreeCcbs = 0\n", 1,2,3,4,5);
return NULL;
}
/*
* Since numFreeCcbs is non-zero, there has to be one available
* in ahaCcb[].
*/
ccb = ahaCcb;
while (ccb <= &ahaCcb[AHA_QUEUE_SIZE - 1] && ccb->in_use) {
ccb++;
}
if (ccb > &ahaCcb[AHA_QUEUE_SIZE - 1]) {
IOPanic("AHAController: out of ccbs");
}
numFreeCcbs--;
ccb->in_use = TRUE;
/*
* Null out dmaList.
*/
for(i=0; i<AHA_SG_COUNT; i++) {
ccb->dmaList[i] = NULL;
}
/*
* Acquire the reentrant DMA lock. This is a nop on EISA machines.
*
* Although -reserveDMALock is reentrant for multiple threads on
* one device, it is *not* reentrant for one thread. Thus we should
* only call it if we don't already hold the lock.
* Also, avoid this if we're not going to do any DMA.
*/
if(doDMA && (++dmaLockCount == 1)) {
ddm_thr("allocCcb: calling reserveDMALock\n", 1,2,3,4,5);
[super reserveDMALock];
}
ddm_thr("allocCcb: returning 0x%x\n", ccb, 2,3,4,5);
return ccb;
}
- (void)freeCcb : (struct ccb *)ccb
{
BOOL didDMA = (ccb->total_xfer_len ? YES : NO);
ddm_thr("freeCcb: ccb 0x%x\n", ccb, 2,3,4,5);
ccb->in_use = FALSE;
numFreeCcbs++;
if(didDMA && (--dmaLockCount == 0)) {
ddm_thr("freeCcb: calling releaseDMALock\n",
1,2,3,4,5);
[super releaseDMALock];
}
}
- (void) completeDMA:(IOEISADMABuffer *) dmaList length:(unsigned int) xferLen
{
IOEISADMABuffer *buf = &dmaList[0];
int i;
for (i = 0; i < AHA_SG_COUNT; i++, buf++) {
if(*buf) {
[self freeDMABuffer:*buf];
}
else {
return;
}
}
}
- (void) abortDMA:(IOEISADMABuffer *) dmaList length:(unsigned int) xferLen
{
IOEISADMABuffer *buf = &dmaList[0];
int i;
for (i = 0; i < AHA_SG_COUNT; i++, buf++) {
if(*buf) {
[self abortDMABuffer:*buf];
}
else {
return;
}
}
}
@end
/*
* Handle timeouts. We just send a timeout message to the I/O thread
* so it wakes up.
*/
static void
ahaTimeout(void *arg)
{
struct ccb *ccb = arg;
msg_header_t msg = timeoutMsgTemplate;
msg_return_t mrtn;
if(!ccb->in_use) {
/*
* Race condition - this CCB got completed another way.
* No problem.
*/
return;
}
msg.msg_remote_port = ccb->timeoutPort;
IOLog("AHA timeout\n");
if(mrtn = msg_send_from_kernel(&msg, MSG_OPTION_NONE, 0)) {
IOLog("ahaTimeout: msg_send_from_kernel() returned %d\n",
mrtn);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.