Skip to content
atari_NCR5380.c 86.5 KiB
Newer Older
				void *d;
				unsigned long count;

				if (!cmd->SCp.this_residual && cmd->SCp.buffers_residual) {
					count = cmd->SCp.buffer->length;
					d = sg_virt(cmd->SCp.buffer);
				} else {
					count = cmd->SCp.this_residual;
					d = cmd->SCp.ptr;
				}
				/* this command setup for dma yet? */
				if ((count >= DMA_MIN_SIZE) && (sun3_dma_setup_done != cmd)) {
					if (cmd->request->cmd_type == REQ_TYPE_FS) {
						sun3scsi_dma_setup(d, count,
						                   rq_data_dir(cmd->request));
						sun3_dma_setup_done = cmd;
					}
				}
#endif
#ifdef SUN3_SCSI_VME
				dregs->csr |= CSR_INTR;
#endif
			}
#endif /* CONFIG_SUN3 */
Linus Torvalds's avatar
Linus Torvalds committed

			if (sink && (phase != PHASE_MSGOUT)) {
				NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(tmp));

				NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN |
					      ICR_ASSERT_ACK);
				while (NCR5380_read(STATUS_REG) & SR_REQ)
					;
				NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE |
					      ICR_ASSERT_ATN);
				sink = 0;
				continue;
			}

			switch (phase) {
			case PHASE_DATAOUT:
Linus Torvalds's avatar
Linus Torvalds committed
#if (NDEBUG & NDEBUG_NO_DATAOUT)
				printk("scsi%d: NDEBUG_NO_DATAOUT set, attempted DATAOUT "
				       "aborted\n", HOSTNO);
				sink = 1;
				do_abort(instance);
				cmd->result = DID_ERROR << 16;
				cmd->scsi_done(cmd);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
			case PHASE_DATAIN:
				/*
				 * If there is no room left in the current buffer in the
				 * scatter-gather list, move onto the next one.
				 */

				if (!cmd->SCp.this_residual && cmd->SCp.buffers_residual) {
					++cmd->SCp.buffer;
					--cmd->SCp.buffers_residual;
					cmd->SCp.this_residual = cmd->SCp.buffer->length;
					cmd->SCp.ptr = sg_virt(cmd->SCp.buffer);
					/* ++roman: Try to merge some scatter-buffers if
					 * they are at contiguous physical addresses.
					 */
					merge_contiguous_buffers(cmd);
					dprintk(NDEBUG_INFORMATION, "scsi%d: %d bytes and %d buffers left\n",
						   HOSTNO, cmd->SCp.this_residual,
						   cmd->SCp.buffers_residual);
				}

				/*
				 * The preferred transfer method is going to be
				 * PSEUDO-DMA for systems that are strictly PIO,
				 * since we can let the hardware do the handshaking.
				 *
				 * For this to work, we need to know the transfersize
				 * ahead of time, since the pseudo-DMA code will sit
				 * in an unconditional loop.
				 */

				/* ++roman: I suggest, this should be
				 *   #if def(REAL_DMA)
				 * instead of leaving REAL_DMA out.
				 */
Linus Torvalds's avatar
Linus Torvalds committed

#if defined(REAL_DMA)
#if !defined(CONFIG_SUN3)
				transfersize = 0;
				if (!cmd->device->borken)
					transfersize = NCR5380_dma_xfer_len(instance, cmd, phase);

				if (transfersize >= DMA_MIN_SIZE) {
					len = transfersize;
					cmd->SCp.phase = phase;
					if (NCR5380_transfer_dma(instance, &phase,
					    &len, (unsigned char **)&cmd->SCp.ptr)) {
						/*
						 * If the watchdog timer fires, all future
						 * accesses to this device will use the
						 * polled-IO. */
						scmd_printk(KERN_INFO, cmd,
							"switching to slow handshake\n");
						cmd->device->borken = 1;
						sink = 1;
						do_abort(instance);
						cmd->result = DID_ERROR << 16;
						cmd->scsi_done(cmd);
						/* XXX - need to source or sink data here, as appropriate */
					} else {
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef REAL_DMA
						/* ++roman: When using real DMA,
						 * information_transfer() should return after
						 * starting DMA since it has nothing more to
						 * do.
						 */
						return;
#else
						cmd->SCp.this_residual -= transfersize - len;
Linus Torvalds's avatar
Linus Torvalds committed
#endif
Linus Torvalds's avatar
Linus Torvalds committed
#endif /* defined(REAL_DMA) */
					NCR5380_transfer_pio(instance, &phase,
							     (int *)&cmd->SCp.this_residual,
							     (unsigned char **)&cmd->SCp.ptr);
#if defined(CONFIG_SUN3) && defined(REAL_DMA)
				/* if we had intended to dma that command clear it */
				if (sun3_dma_setup_done == cmd)
					sun3_dma_setup_done = NULL;
#endif
				break;
			case PHASE_MSGIN:
				len = 1;
				data = &tmp;
				NCR5380_write(SELECT_ENABLE_REG, 0);	/* disable reselects */
				NCR5380_transfer_pio(instance, &phase, &len, &data);
				cmd->SCp.Message = tmp;

				switch (tmp) {
				case ABORT:
				case COMMAND_COMPLETE:
					/* Accept message by clearing ACK */
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
Hannes Reinecke's avatar
Hannes Reinecke committed
					dprintk(NDEBUG_QUEUES, "scsi%d: command for target %d, lun %llu "
						  "completed\n", HOSTNO, cmd->device->id, cmd->device->lun);

					local_irq_save(flags);
					hostdata->connected = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
					cmd_free_tag(cmd);
					if (status_byte(cmd->SCp.Status) == QUEUE_FULL) {
						/* Turn a QUEUE FULL status into BUSY, I think the
						 * mid level cannot handle QUEUE FULL :-( (The
						 * command is retried after BUSY). Also update our
						 * queue size to the number of currently issued
						 * commands now.
						 */
						/* ++Andreas: the mid level code knows about
						   QUEUE_FULL now. */
						struct tag_alloc *ta = &hostdata->TagAlloc[scmd_id(cmd)][cmd->device->lun];
Hannes Reinecke's avatar
Hannes Reinecke committed
						dprintk(NDEBUG_TAGS, "scsi%d: target %d lun %llu returned "
							   "QUEUE_FULL after %d commands\n",
							   HOSTNO, cmd->device->id, cmd->device->lun,
							   ta->nr_allocated);
						if (ta->queue_size > ta->nr_allocated)
							ta->nr_allocated = ta->queue_size;
					}
Linus Torvalds's avatar
Linus Torvalds committed
#else
					hostdata->busy[cmd->device->id] &= ~(1 << cmd->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
					/* Enable reselect interrupts */
					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);

					/*
					 * I'm not sure what the correct thing to do here is :
					 *
					 * If the command that just executed is NOT a request
					 * sense, the obvious thing to do is to set the result
					 * code to the values of the stored parameters.
					 *
					 * If it was a REQUEST SENSE command, we need some way to
					 * differentiate between the failure code of the original
					 * and the failure code of the REQUEST sense - the obvious
					 * case is success, where we fall through and leave the
					 * result code unchanged.
					 *
					 * The non-obvious place is where the REQUEST SENSE failed
					 */

					if (cmd->cmnd[0] != REQUEST_SENSE)
						cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8);
					else if (status_byte(cmd->SCp.Status) != GOOD)
						cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16);
Linus Torvalds's avatar
Linus Torvalds committed

					if ((cmd->cmnd[0] == REQUEST_SENSE) &&
						hostdata->ses.cmd_len) {
						scsi_eh_restore_cmnd(cmd, &hostdata->ses);
						hostdata->ses.cmd_len = 0 ;
					}

					if ((cmd->cmnd[0] != REQUEST_SENSE) &&
					    (status_byte(cmd->SCp.Status) == CHECK_CONDITION)) {
						scsi_eh_prep_cmnd(cmd, &hostdata->ses, NULL, 0, ~0);

						dprintk(NDEBUG_AUTOSENSE, "scsi%d: performing request sense\n", HOSTNO);

						LIST(cmd,hostdata->issue_queue);
						SET_NEXT(cmd, hostdata->issue_queue);
						hostdata->issue_queue = (struct scsi_cmnd *) cmd;
						dprintk(NDEBUG_QUEUES, "scsi%d: REQUEST SENSE added to head of "
							  "issue queue\n", H_NO(cmd));
						cmd->scsi_done(cmd);
					}

					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
					/*
					 * Restore phase bits to 0 so an interrupted selection,
					 * arbitration can resume.
					 */
					NCR5380_write(TARGET_COMMAND_REG, 0);

					/* ++roman: For Falcon SCSI, release the lock on the
					 * ST-DMA here if no other commands are waiting on the
					 * disconnected queue.
					 */
					maybe_release_dma_irq(instance);
					local_irq_restore(flags);
					return;
				case MESSAGE_REJECT:
					/* Accept message by clearing ACK */
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
					/* Enable reselect interrupts */
					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
					switch (hostdata->last_message) {
					case HEAD_OF_QUEUE_TAG:
					case ORDERED_QUEUE_TAG:
					case SIMPLE_QUEUE_TAG:
						/* The target obviously doesn't support tagged
						 * queuing, even though it announced this ability in
						 * its INQUIRY data ?!? (maybe only this LUN?) Ok,
						 * clear 'tagged_supported' and lock the LUN, since
						 * the command is treated as untagged further on.
						 */
						cmd->device->tagged_supported = 0;
						hostdata->busy[cmd->device->id] |= (1 << cmd->device->lun);
						cmd->tag = TAG_NONE;
Hannes Reinecke's avatar
Hannes Reinecke committed
						dprintk(NDEBUG_TAGS, "scsi%d: target %d lun %llu rejected "
							   "QUEUE_TAG message; tagged queuing "
							   "disabled\n",
							   HOSTNO, cmd->device->id, cmd->device->lun);
						break;
					}
					break;
				case DISCONNECT:
					/* Accept message by clearing ACK */
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
					local_irq_save(flags);
					LIST(cmd,hostdata->disconnected_queue);
					SET_NEXT(cmd, hostdata->disconnected_queue);
					hostdata->connected = NULL;
					hostdata->disconnected_queue = cmd;
					local_irq_restore(flags);
Hannes Reinecke's avatar
Hannes Reinecke committed
					dprintk(NDEBUG_QUEUES, "scsi%d: command for target %d lun %llu was "
						  "moved from connected to the "
						  "disconnected_queue\n", HOSTNO,
						  cmd->device->id, cmd->device->lun);
					/*
					 * Restore phase bits to 0 so an interrupted selection,
					 * arbitration can resume.
					 */
					NCR5380_write(TARGET_COMMAND_REG, 0);

					/* Enable reselect interrupts */
					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
#ifdef SUN3_SCSI_VME
					dregs->csr |= CSR_DMA_ENABLE;
#endif
					return;
					/*
					 * The SCSI data pointer is *IMPLICITLY* saved on a disconnect
					 * operation, in violation of the SCSI spec so we can safely
					 * ignore SAVE/RESTORE pointers calls.
					 *
					 * Unfortunately, some disks violate the SCSI spec and
					 * don't issue the required SAVE_POINTERS message before
					 * disconnecting, and we have to break spec to remain
					 * compatible.
					 */
				case SAVE_POINTERS:
				case RESTORE_POINTERS:
					/* Accept message by clearing ACK */
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
					/* Enable reselect interrupts */
					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
					break;
				case EXTENDED_MESSAGE:
					/*
					 * Extended messages are sent in the following format :
					 * Byte
					 * 0		EXTENDED_MESSAGE == 1
					 * 1		length (includes one byte for code, doesn't
					 *		include first two bytes)
					 * 2		code
					 * 3..length+1	arguments
					 *
					 * Start the extended message buffer with the EXTENDED_MESSAGE
					 * byte, since spi_print_msg() wants the whole thing.
					 */
					extended_msg[0] = EXTENDED_MESSAGE;
					/* Accept first byte by clearing ACK */
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);

					dprintk(NDEBUG_EXTENDED, "scsi%d: receiving extended message\n", HOSTNO);

					len = 2;
					data = extended_msg + 1;
					phase = PHASE_MSGIN;
					NCR5380_transfer_pio(instance, &phase, &len, &data);
					dprintk(NDEBUG_EXTENDED, "scsi%d: length=%d, code=0x%02x\n", HOSTNO,
						   (int)extended_msg[1], (int)extended_msg[2]);

					if (!len && extended_msg[1] <=
					    (sizeof(extended_msg) - 1)) {
						/* Accept third byte by clearing ACK */
						NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
						len = extended_msg[1] - 1;
						data = extended_msg + 3;
						phase = PHASE_MSGIN;

						NCR5380_transfer_pio(instance, &phase, &len, &data);
						dprintk(NDEBUG_EXTENDED, "scsi%d: message received, residual %d\n",
							   HOSTNO, len);

						switch (extended_msg[2]) {
						case EXTENDED_SDTR:
						case EXTENDED_WDTR:
						case EXTENDED_MODIFY_DATA_POINTER:
						case EXTENDED_EXTENDED_IDENTIFY:
							tmp = 0;
						}
					} else if (len) {
						printk(KERN_NOTICE "scsi%d: error receiving "
						       "extended message\n", HOSTNO);
						tmp = 0;
					} else {
						printk(KERN_NOTICE "scsi%d: extended message "
							   "code %02x length %d is too long\n",
							   HOSTNO, extended_msg[2], extended_msg[1]);
						tmp = 0;
					}
					/* Fall through to reject message */

					/*
					 * If we get something weird that we aren't expecting,
					 * reject it.
					 */
				default:
					if (!tmp) {
						printk(KERN_INFO "scsi%d: rejecting message ",
						       instance->host_no);
						spi_print_msg(extended_msg);
						printk("\n");
					} else if (tmp != EXTENDED_MESSAGE)
						scmd_printk(KERN_INFO, cmd,
						            "rejecting unknown message %02x\n",
						            tmp);
						scmd_printk(KERN_INFO, cmd,
						            "rejecting unknown extended message code %02x, length %d\n",
						            extended_msg[1], extended_msg[0]);

					msgout = MESSAGE_REJECT;
					NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN);
					break;
				} /* switch (tmp) */
				break;
			case PHASE_MSGOUT:
				len = 1;
				data = &msgout;
				hostdata->last_message = msgout;
				NCR5380_transfer_pio(instance, &phase, &len, &data);
				if (msgout == ABORT) {
					local_irq_save(flags);
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
					cmd_free_tag(cmd);
Linus Torvalds's avatar
Linus Torvalds committed
#else
					hostdata->busy[cmd->device->id] &= ~(1 << cmd->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
					hostdata->connected = NULL;
					cmd->result = DID_ERROR << 16;
					NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
					maybe_release_dma_irq(instance);
					local_irq_restore(flags);
					cmd->scsi_done(cmd);
					return;
				}
				msgout = NOP;
				break;
			case PHASE_CMDOUT:
				len = cmd->cmd_len;
				data = cmd->cmnd;
				/*
				 * XXX for performance reasons, on machines with a
				 * PSEUDO-DMA architecture we should probably
				 * use the dma transfer function.
				 */
				NCR5380_transfer_pio(instance, &phase, &len, &data);
				break;
			case PHASE_STATIN:
				len = 1;
				data = &tmp;
				NCR5380_transfer_pio(instance, &phase, &len, &data);
				cmd->SCp.Status = tmp;
				break;
			default:
				printk("scsi%d: unknown phase\n", HOSTNO);
				NCR5380_dprint(NDEBUG_ANY, instance);
			} /* switch(phase) */
		} else {
			NCR5380_poll_politely(instance, STATUS_REG, SR_REQ, SR_REQ, HZ);
		}
	} /* while (1) */
Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 * Function : void NCR5380_reselect (struct Scsi_Host *instance)
 *
 * Purpose : does reselection, initializing the instance->connected
 *	field to point to the scsi_cmnd for which the I_T_L or I_T_L_Q
Linus Torvalds's avatar
Linus Torvalds committed
 *	nexus has been reestablished,
Linus Torvalds's avatar
Linus Torvalds committed
 * Inputs : instance - this instance of the NCR5380.
 *
 */


/* it might eventually prove necessary to do a dma setup on
   reselection, but it doesn't seem to be needed now -- sam */

static void NCR5380_reselect(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	SETUP_HOSTDATA(instance);
	unsigned char target_mask;
	unsigned char lun;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
	unsigned char tag;
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	unsigned char msg[3];
	int __maybe_unused len;
	unsigned char __maybe_unused *data, __maybe_unused phase;
	struct scsi_cmnd *tmp = NULL, *prev;

	/*
	 * Disable arbitration, etc. since the host adapter obviously
	 * lost, and tell an interrupted NCR5380_select() to restart.
	 */

	NCR5380_write(MODE_REG, MR_BASE);

	target_mask = NCR5380_read(CURRENT_SCSI_DATA_REG) & ~(hostdata->id_mask);

	dprintk(NDEBUG_RESELECTION, "scsi%d: reselect\n", HOSTNO);

	/*
	 * At this point, we have detected that our SCSI ID is on the bus,
	 * SEL is true and BSY was false for at least one bus settle delay
	 * (400 ns).
	 *
	 * We must assert BSY ourselves, until the target drops the SEL
	 * signal.
	 */

	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_BSY);

	while (NCR5380_read(STATUS_REG) & SR_SEL)
		;
	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);

	/*
	 * Wait for target to go into MSGIN.
	 */

	while (!(NCR5380_read(STATUS_REG) & SR_REQ))
		;

#if defined(CONFIG_SUN3) && defined(REAL_DMA)
	/* acknowledge toggle to MSGIN */
	NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(PHASE_MSGIN));

	/* peek at the byte without really hitting the bus */
	msg[0] = NCR5380_read(CURRENT_SCSI_DATA_REG);
#else
	len = 1;
	data = msg;
	phase = PHASE_MSGIN;
	NCR5380_transfer_pio(instance, &phase, &len, &data);

	if (!(msg[0] & 0x80)) {
		printk(KERN_DEBUG "scsi%d: expecting IDENTIFY message, got ", HOSTNO);
		spi_print_msg(msg);
		do_abort(instance);
		return;
	}
	lun = (msg[0] & 0x07);
Linus Torvalds's avatar
Linus Torvalds committed

#if defined(SUPPORT_TAGS) && !defined(CONFIG_SUN3)
	/* If the phase is still MSGIN, the target wants to send some more
	 * messages. In case it supports tagged queuing, this is probably a
	 * SIMPLE_QUEUE_TAG for the I_T_L_Q nexus.
	 */
	tag = TAG_NONE;
	if (phase == PHASE_MSGIN && (hostdata->flags & FLAG_TAGGED_QUEUING)) {
		/* Accept previous IDENTIFY message by clearing ACK */
		NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
		len = 2;
		data = msg + 1;
		if (!NCR5380_transfer_pio(instance, &phase, &len, &data) &&
		    msg[1] == SIMPLE_QUEUE_TAG)
			tag = msg[2];
		dprintk(NDEBUG_TAGS, "scsi%d: target mask %02x, lun %d sent tag %d at "
			   "reselection\n", HOSTNO, target_mask, lun, tag);
	}
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	/*
	 * Find the command corresponding to the I_T_L or I_T_L_Q  nexus we
	 * just reestablished, and remove it from the disconnected queue.
	 */

	for (tmp = (struct scsi_cmnd *) hostdata->disconnected_queue, prev = NULL;
	     tmp; prev = tmp, tmp = NEXT(tmp)) {
		if ((target_mask == (1 << tmp->device->id)) && (lun == tmp->device->lun)
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
		    && (tag == tmp->tag)
Linus Torvalds's avatar
Linus Torvalds committed
#endif
		    ) {
			if (prev) {
				REMOVE(prev, NEXT(prev), tmp, NEXT(tmp));
				SET_NEXT(prev, NEXT(tmp));
			} else {
				REMOVE(-1, hostdata->disconnected_queue, tmp, NEXT(tmp));
				hostdata->disconnected_queue = NEXT(tmp);
			}
			SET_NEXT(tmp, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	}

	if (!tmp) {
		printk(KERN_WARNING "scsi%d: warning: target bitmask %02x lun %d "
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
Linus Torvalds's avatar
Linus Torvalds committed
#endif
		       "not in disconnected_queue.\n",
		       HOSTNO, target_mask, lun
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
Linus Torvalds's avatar
Linus Torvalds committed
#endif
			);
		/*
		 * Since we have an established nexus that we can't do anything
		 * with, we must abort it.
		 */
		do_abort(instance);
		return;
	}
Linus Torvalds's avatar
Linus Torvalds committed

#if defined(CONFIG_SUN3) && defined(REAL_DMA)
	/* engage dma setup for the command we just saw */
	{
		void *d;
		unsigned long count;

		if (!tmp->SCp.this_residual && tmp->SCp.buffers_residual) {
			count = tmp->SCp.buffer->length;
			d = sg_virt(tmp->SCp.buffer);
		} else {
			count = tmp->SCp.this_residual;
			d = tmp->SCp.ptr;
		}
		/* setup this command for dma if not already */
		if ((count >= DMA_MIN_SIZE) && (sun3_dma_setup_done != tmp)) {
			sun3scsi_dma_setup(d, count, rq_data_dir(tmp->request));
			sun3_dma_setup_done = tmp;
		}
	}

	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ACK);
#endif

	/* Accept message by clearing ACK */
	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
Linus Torvalds's avatar
Linus Torvalds committed

#if defined(SUPPORT_TAGS) && defined(CONFIG_SUN3)
	/* If the phase is still MSGIN, the target wants to send some more
	 * messages. In case it supports tagged queuing, this is probably a
	 * SIMPLE_QUEUE_TAG for the I_T_L_Q nexus.
	 */
	tag = TAG_NONE;
	if (phase == PHASE_MSGIN && setup_use_tagged_queuing) {
		/* Accept previous IDENTIFY message by clearing ACK */
		NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
		len = 2;
		data = msg + 1;
		if (!NCR5380_transfer_pio(instance, &phase, &len, &data) &&
		    msg[1] == SIMPLE_QUEUE_TAG)
			tag = msg[2];
		dprintk(NDEBUG_TAGS, "scsi%d: target mask %02x, lun %d sent tag %d at reselection\n"
			HOSTNO, target_mask, lun, tag);
	}
#endif

	hostdata->connected = tmp;
Hannes Reinecke's avatar
Hannes Reinecke committed
	dprintk(NDEBUG_RESELECTION, "scsi%d: nexus established, target = %d, lun = %llu, tag = %d\n",
		   HOSTNO, tmp->device->id, tmp->device->lun, tmp->tag);
 * Function : int NCR5380_abort (struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Purpose : abort a command
 *
 * Inputs : cmd - the scsi_cmnd to abort, code - code to set the
 *	host byte of the result field to, if zero DID_ABORTED is
Linus Torvalds's avatar
Linus Torvalds committed
 *	used.
 *
 * Returns : SUCCESS - success, FAILED on failure.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * XXX - there is no way to abort the command that is currently
 *	 connected, you have to wait for it to complete.  If this is
Linus Torvalds's avatar
Linus Torvalds committed
 *	 a problem, we could implement longjmp() / setjmp(), setjmp()
 *	 called where the loop started in NCR5380_main().
Linus Torvalds's avatar
Linus Torvalds committed
 */

static
int NCR5380_abort(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct Scsi_Host *instance = cmd->device->host;
	SETUP_HOSTDATA(instance);
	struct scsi_cmnd *tmp, **prev;
	unsigned long flags;
Linus Torvalds's avatar
Linus Torvalds committed

	scmd_printk(KERN_NOTICE, cmd, "aborting command\n");
Linus Torvalds's avatar
Linus Torvalds committed

	NCR5380_print_status(instance);

	local_irq_save(flags);
Linus Torvalds's avatar
Linus Torvalds committed

	dprintk(NDEBUG_ABORT, "scsi%d: abort called basr 0x%02x, sr 0x%02x\n", HOSTNO,
		    NCR5380_read(BUS_AND_STATUS_REG),
		    NCR5380_read(STATUS_REG));
Linus Torvalds's avatar
Linus Torvalds committed

#if 1
	/*
	 * Case 1 : If the command is the currently executing command,
	 * we'll set the aborted flag and return control so that
	 * information transfer routine can exit cleanly.
	 */
Linus Torvalds's avatar
Linus Torvalds committed

	if (hostdata->connected == cmd) {
Linus Torvalds's avatar
Linus Torvalds committed

		dprintk(NDEBUG_ABORT, "scsi%d: aborting connected command\n", HOSTNO);
		/*
		 * We should perform BSY checking, and make sure we haven't slipped
		 * into BUS FREE.
		 */
Linus Torvalds's avatar
Linus Torvalds committed

		/*	NCR5380_write(INITIATOR_COMMAND_REG, ICR_ASSERT_ATN); */
		/*
		 * Since we can't change phases until we've completed the current
		 * handshake, we have to source or sink a byte of data if the current
		 * phase is not MSGOUT.
		 */
Linus Torvalds's avatar
Linus Torvalds committed

		/*
		 * Return control to the executing NCR drive so we can clear the
		 * aborted flag and get back into our main loop.
		 */
Linus Torvalds's avatar
Linus Torvalds committed

		if (do_abort(instance) == 0) {
			hostdata->connected = NULL;
			cmd->result = DID_ABORT << 16;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
			cmd_free_tag(cmd);
Linus Torvalds's avatar
Linus Torvalds committed
#else
			hostdata->busy[cmd->device->id] &= ~(1 << cmd->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
			maybe_release_dma_irq(instance);
			local_irq_restore(flags);
			cmd->scsi_done(cmd);
			local_irq_restore(flags);
			printk("scsi%d: abort of connected command failed!\n", HOSTNO);
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	/*
	 * Case 2 : If the command hasn't been issued yet, we simply remove it
	 *	    from the issue queue.
	 */
	for (prev = (struct scsi_cmnd **)&(hostdata->issue_queue),
	     tmp = (struct scsi_cmnd *)hostdata->issue_queue;
	     tmp; prev = NEXTADDR(tmp), tmp = NEXT(tmp)) {
		if (cmd == tmp) {
			REMOVE(5, *prev, tmp, NEXT(tmp));
			(*prev) = NEXT(tmp);
			SET_NEXT(tmp, NULL);
			tmp->result = DID_ABORT << 16;
			maybe_release_dma_irq(instance);
			local_irq_restore(flags);
			dprintk(NDEBUG_ABORT, "scsi%d: abort removed command from issue queue.\n",
				    HOSTNO);
			/* Tagged queuing note: no tag to free here, hasn't been assigned
			 * yet... */
			tmp->scsi_done(tmp);
	/*
	 * Case 3 : If any commands are connected, we're going to fail the abort
	 *	    and let the high level SCSI driver retry at a later time or
	 *	    issue a reset.
	 *
	 *	    Timeouts, and therefore aborted commands, will be highly unlikely
	 *          and handling them cleanly in this situation would make the common
	 *	    case of noresets less efficient, and would pollute our code.  So,
	 *	    we fail.
	 */
Linus Torvalds's avatar
Linus Torvalds committed

	if (hostdata->connected) {
		local_irq_restore(flags);
		dprintk(NDEBUG_ABORT, "scsi%d: abort failed, command connected.\n", HOSTNO);
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * Case 4: If the command is currently disconnected from the bus, and
	 *	there are no connected commands, we reconnect the I_T_L or
	 *	I_T_L_Q nexus associated with it, go into message out, and send
	 *      an abort message.
	 *
	 * This case is especially ugly. In order to reestablish the nexus, we
	 * need to call NCR5380_select().  The easiest way to implement this
	 * function was to abort if the bus was busy, and let the interrupt
	 * handler triggered on the SEL for reselect take care of lost arbitrations
	 * where necessary, meaning interrupts need to be enabled.
	 *
	 * When interrupts are enabled, the queues may change - so we
	 * can't remove it from the disconnected queue before selecting it
	 * because that could cause a failure in hashing the nexus if that
	 * device reselected.
	 *
	 * Since the queues may change, we can't use the pointers from when we
	 * first locate it.
	 *
	 * So, we must first locate the command, and if NCR5380_select()
	 * succeeds, then issue the abort, relocate the command and remove
	 * it from the disconnected queue.
	 */

	for (tmp = (struct scsi_cmnd *) hostdata->disconnected_queue; tmp;
	     tmp = NEXT(tmp)) {
		if (cmd == tmp) {
			local_irq_restore(flags);
			dprintk(NDEBUG_ABORT, "scsi%d: aborting disconnected command.\n", HOSTNO);
			if (NCR5380_select(instance, cmd))
Linus Torvalds's avatar
Linus Torvalds committed

			dprintk(NDEBUG_ABORT, "scsi%d: nexus reestablished.\n", HOSTNO);

			do_abort(instance);

			local_irq_save(flags);
			for (prev = (struct scsi_cmnd **)&(hostdata->disconnected_queue),
			     tmp = (struct scsi_cmnd *)hostdata->disconnected_queue;
			     tmp; prev = NEXTADDR(tmp), tmp = NEXT(tmp)) {
				if (cmd == tmp) {
					REMOVE(5, *prev, tmp, NEXT(tmp));
					*prev = NEXT(tmp);
					SET_NEXT(tmp, NULL);
					tmp->result = DID_ABORT << 16;
					/* We must unlock the tag/LUN immediately here, since the
					 * target goes to BUS FREE and doesn't send us another
					 * message (COMMAND_COMPLETE or the like)
					 */
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
					cmd_free_tag(tmp);
Linus Torvalds's avatar
Linus Torvalds committed
#else
					hostdata->busy[cmd->device->id] &= ~(1 << cmd->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
					maybe_release_dma_irq(instance);
					local_irq_restore(flags);
					tmp->scsi_done(tmp);
	/* Maybe it is sufficient just to release the ST-DMA lock... (if
	 * possible at all) At least, we should check if the lock could be
	 * released after the abort, in case it is kept due to some bug.
	 */
	maybe_release_dma_irq(instance);
	local_irq_restore(flags);

	/*
	 * Case 5 : If we reached this point, the command was not found in any of
	 *	    the queues.
	 *
	 * We probably reached this point because of an unlikely race condition
	 * between the command completing successfully and the abortion code,
	 * so we won't panic, but we will notify the user in case something really
	 * broke.
	 */
Linus Torvalds's avatar
Linus Torvalds committed

	printk(KERN_INFO "scsi%d: warning : SCSI command probably completed successfully before abortion\n", HOSTNO);
Linus Torvalds's avatar
Linus Torvalds committed

/**
 * NCR5380_bus_reset - reset the SCSI bus
 * @cmd: SCSI command undergoing EH
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Returns SUCCESS
Linus Torvalds's avatar
Linus Torvalds committed

static int NCR5380_bus_reset(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct Scsi_Host *instance = cmd->device->host;
	struct NCR5380_hostdata *hostdata = shost_priv(instance);
	int i;
	unsigned long flags;
Linus Torvalds's avatar
Linus Torvalds committed

	local_irq_save(flags);

#if (NDEBUG & NDEBUG_ANY)
	scmd_printk(KERN_INFO, cmd, "performing bus reset\n");
	NCR5380_print_status(instance);
#endif

	do_reset(instance);

	/* reset NCR registers */
	NCR5380_write(MODE_REG, MR_BASE);
	NCR5380_write(TARGET_COMMAND_REG, 0);
	NCR5380_write(SELECT_ENABLE_REG, 0);

	/* After the reset, there are no more connected or disconnected commands
	 * and no busy units; so clear the low-level status here to avoid
	 * conflicts when the mid-level code tries to wake up the affected
	 * commands!
	 */

	if (hostdata->issue_queue)
		dprintk(NDEBUG_ABORT, "scsi%d: reset aborted issued command(s)\n", H_NO(cmd));
	if (hostdata->connected)
		dprintk(NDEBUG_ABORT, "scsi%d: reset aborted a connected command\n", H_NO(cmd));
	if (hostdata->disconnected_queue)
		dprintk(NDEBUG_ABORT, "scsi%d: reset aborted disconnected command(s)\n", H_NO(cmd));

	hostdata->issue_queue = NULL;
	hostdata->connected = NULL;
	hostdata->disconnected_queue = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
	free_all_tags(hostdata);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	for (i = 0; i < 8; ++i)
		hostdata->busy[i] = 0;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef REAL_DMA
	hostdata->dma_len = 0;
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	maybe_release_dma_irq(instance);
	local_irq_restore(flags);
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
}