Skip to content
NCR5380.c 79.9 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/* 
 * NCR 5380 generic driver routines.  These should make it *trivial*
 *      to implement 5380 SCSI drivers under Linux with a non-trantor
 *      architecture.
 *
 *      Note that these routines also work with NR53c400 family chips.
 *
 * Copyright 1993, Drew Eckhardt
 *      Visionary Computing 
 *      (Unix and Linux consulting and custom programming)
 *      drew@colorado.edu
 *      +1 (303) 666-5836
 *
 * For more information, please consult 
 *
 * NCR 5380 Family
 * SCSI Protocol Controller
 * Databook
 *
 * NCR Microelectronics
 * 1635 Aeroplaza Drive
 * Colorado Springs, CO 80916
 * 1+ (719) 578-3400
 * 1+ (800) 334-5454
 */

/*
 * Revision 1.10 1998/9/2	Alan Cox
Linus Torvalds's avatar
Linus Torvalds committed
 * Fixed up the timer lockups reported so far. Things still suck. Looking 
 * forward to 2.3 and per device request queues. Then it'll be possible to
 * SMP thread this beast and improve life no end.
 
 * Revision 1.9  1997/7/27	Ronald van Cuijlenborg
 *				(ronald.van.cuijlenborg@tip.nl or nutty@dds.nl)
 * (hopefully) fixed and enhanced USLEEP
 * added support for DTC3181E card (for Mustek scanner)
 *

 * Revision 1.8			Ingmar Baumgart
 *				(ingmar@gonzo.schwaben.de)
 * added support for NCR53C400a card
 *

 * Revision 1.7  1996/3/2       Ray Van Tassle (rayvt@comm.mot.com)
 * added proc_info
 * added support needed for DTC 3180/3280
 * fixed a couple of bugs
 *

 * Revision 1.5  1994/01/19  09:14:57  drew
 * Fixed udelay() hack that was being used on DATAOUT phases
 * instead of a proper wait for the final handshake.
 *
 * Revision 1.4  1994/01/19  06:44:25  drew
 * *** empty log message ***
 *
 * Revision 1.3  1994/01/19  05:24:40  drew
 * Added support for TCR LAST_BYTE_SENT bit.
 *
 * Revision 1.2  1994/01/15  06:14:11  drew
 * REAL DMA support, bug fixes.
 *
 * Revision 1.1  1994/01/15  06:00:54  drew
 * Initial revision
 *
 */

/*
 * Further development / testing that should be done : 
 * 1.  Cleanup the NCR5380_transfer_dma function and DMA operation complete
 *     code so that everything does the same thing that's done at the 
 *     end of a pseudo-DMA read operation.
 *
 * 2.  Fix REAL_DMA (interrupt driven, polled works fine) -
 *     basically, transfer size needs to be reduced by one 
 *     and the last byte read as is done with PSEUDO_DMA.
 * 
 * 4.  Test SCSI-II tagged queueing (I have no devices which support 
 *      tagged queueing)
 */
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_transport_spi.h>
Linus Torvalds's avatar
Linus Torvalds committed

#if (NDEBUG & NDEBUG_LISTS)
#define LIST(x,y) {printk("LINE:%d   Adding %p to %p\n", __LINE__, (void*)(x), (void*)(y)); if ((x)==(y)) udelay(5); }
#define REMOVE(w,x,y,z) {printk("LINE:%d   Removing: %p->%p  %p->%p \n", __LINE__, (void*)(w), (void*)(x), (void*)(y), (void*)(z)); if ((x)==(y)) udelay(5); }
#else
#define LIST(x,y)
#define REMOVE(w,x,y,z)
#endif

#ifndef notyet
#undef REAL_DMA
#endif

#ifdef REAL_DMA_POLL
#undef READ_OVERRUNS
#define READ_OVERRUNS
#endif

#ifdef BOARD_REQUIRES_NO_DELAY
#define io_recovery_delay(x)
#else
#define io_recovery_delay(x)	udelay(x)
#endif

/*
 * Design
 *
 * This is a generic 5380 driver.  To use it on a different platform, 
 * one simply writes appropriate system specific macros (ie, data
 * transfer - some PC's will use the I/O bus, 68K's must use 
 * memory mapped) and drops this file in their 'C' wrapper.
 *
 * (Note from hch:  unfortunately it was not enough for the different
 * m68k folks and instead of improving this driver they copied it
 * and hacked it up for their needs.  As a consequence they lost
 * most updates to this driver.  Maybe someone will fix all these
 * drivers to use a common core one day..)
 *
 * As far as command queueing, two queues are maintained for 
 * each 5380 in the system - commands that haven't been issued yet,
 * and commands that are currently executing.  This means that an 
 * unlimited number of commands may be queued, letting 
 * more commands propagate from the higher driver levels giving higher 
 * throughput.  Note that both I_T_L and I_T_L_Q nexuses are supported, 
 * allowing multiple commands to propagate all the way to a SCSI-II device 
 * while a command is already executing.
 *
 *
 * Issues specific to the NCR5380 : 
 *
 * When used in a PIO or pseudo-dma mode, the NCR5380 is a braindead 
 * piece of hardware that requires you to sit in a loop polling for 
 * the REQ signal as long as you are connected.  Some devices are 
 * brain dead (ie, many TEXEL CD ROM drives) and won't disconnect 
 * while doing long seek operations. [...] These
Linus Torvalds's avatar
Linus Torvalds committed
 * broken devices are the exception rather than the rule and I'd rather
 * spend my time optimizing for the normal case.
 *
 * Architecture :
 *
 * At the heart of the design is a coroutine, NCR5380_main,
 * which is started from a workqueue for each NCR5380 host in the
 * system.  It attempts to establish I_T_L or I_T_L_Q nexuses by
 * removing the commands from the issue queue and calling
 * NCR5380_select() if a nexus is not established. 
 *
 * Once a nexus is established, the NCR5380_information_transfer()
 * phase goes through the various phases as instructed by the target.
 * if the target goes into MSG IN and sends a DISCONNECT message,
 * the command structure is placed into the per instance disconnected
 * queue, and NCR5380_main tries to find more work.  If the target is 
 * idle for too long, the system will try to sleep.
 *
 * If a command has disconnected, eventually an interrupt will trigger,
 * calling NCR5380_intr()  which will in turn call NCR5380_reselect
 * to reestablish a nexus.  This will run main if necessary.
 *
 * On command termination, the done function will be called as 
 * appropriate.
 *
 * SCSI pointers are maintained in the SCp field of SCSI command 
 * structures, being initialized after the command is connected
 * in NCR5380_select, and set as appropriate in NCR5380_information_transfer.
 * Note that in violation of the standard, an implicit SAVE POINTERS operation
 * is done, since some BROKEN disks fail to issue an explicit SAVE POINTERS.
 */

/*
 * Using this file :
 * This file a skeleton Linux SCSI driver for the NCR 5380 series
 * of chips.  To use it, you write an architecture specific functions 
 * and macros and include this file in your driver.
 *
 * These macros control options : 
 * AUTOPROBE_IRQ - if defined, the NCR5380_probe_irq() function will be 
 *      defined.
 * 
 * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically
 *      for commands that return with a CHECK CONDITION status. 
 *
 * DIFFERENTIAL - if defined, NCR53c81 chips will use external differential
 *      transceivers. 
 *
 * DONT_USE_INTR - if defined, never use interrupts, even if we probe or
 *      override-configure an IRQ.
 *
 * PSEUDO_DMA - if defined, PSEUDO DMA is used during the data transfer phases.
 *
 * REAL_DMA - if defined, REAL DMA is used during the data transfer phases.
 *
 * REAL_DMA_POLL - if defined, REAL DMA is used but the driver doesn't
 *      rely on phase mismatch and EOP interrupts to determine end 
 *      of phase.
 *
 * UNSAFE - leave interrupts enabled during pseudo-DMA transfers.  You
 *          only really want to use this if you're having a problem with
 *          dropped characters during high speed communications, and even
 *          then, you're going to be better off twiddling with transfersize
 *          in the high level code.
 *
 * Defaults for these will be provided although the user may want to adjust 
 * these to allocate CPU resources to the SCSI driver or "real" code.
 * 
 * These macros MUST be defined :
 * 
 * NCR5380_read(register)  - read from the specified register
 *
 * NCR5380_write(register, value) - write to the specific register 
 *
 * NCR5380_implementation_fields  - additional fields needed for this 
 *      specific implementation of the NCR5380
 *
 * Either real DMA *or* pseudo DMA may be implemented
 * REAL functions : 
 * NCR5380_REAL_DMA should be defined if real DMA is to be used.
 * Note that the DMA setup functions should return the number of bytes 
 *      that they were able to program the controller for.
 *
 * Also note that generic i386/PC versions of these macros are 
 *      available as NCR5380_i386_dma_write_setup,
 *      NCR5380_i386_dma_read_setup, and NCR5380_i386_dma_residual.
 *
 * NCR5380_dma_write_setup(instance, src, count) - initialize
 * NCR5380_dma_read_setup(instance, dst, count) - initialize
 * NCR5380_dma_residual(instance); - residual count
 *
 * PSEUDO functions :
 * NCR5380_pwrite(instance, src, count)
 * NCR5380_pread(instance, dst, count);
 *
 * The generic driver is initialized by calling NCR5380_init(instance),
 * after setting the appropriate host specific fields and ID.  If the 
 * driver wishes to autoprobe for an IRQ line, the NCR5380_probe_irq(instance,
 * possible) function may be used.
 */

static int do_abort(struct Scsi_Host *);
static void do_reset(struct Scsi_Host *);
Linus Torvalds's avatar
Linus Torvalds committed

/*
 *	initialize_SCp		-	init the scsi pointer field
 *	@cmd: command block to set up
 *
 *	Set up the internal fields in the SCSI command.
 */

static inline void initialize_SCp(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	/* 
	 * Initialize the Scsi Pointer field so that all of the commands in the 
	 * various queues are valid.
	 */

	if (scsi_bufflen(cmd)) {
		cmd->SCp.buffer = scsi_sglist(cmd);
		cmd->SCp.buffers_residual = scsi_sg_count(cmd) - 1;
		cmd->SCp.ptr = sg_virt(cmd->SCp.buffer);
Linus Torvalds's avatar
Linus Torvalds committed
		cmd->SCp.this_residual = cmd->SCp.buffer->length;
	} else {
		cmd->SCp.buffer = NULL;
		cmd->SCp.buffers_residual = 0;
		cmd->SCp.ptr = NULL;
		cmd->SCp.this_residual = 0;
 * NCR5380_poll_politely - wait for chip register value
 * @instance: controller to poll
 * @reg: 5380 register to poll
 * @bit: Bitmask to check
 * @val: Value required to exit
 * @wait: Time-out in jiffies
 *
 * Polls the chip in a reasonably efficient manner waiting for an
 * event to occur. After a short quick poll we begin to yield the CPU
 * (if possible). In irq contexts the time-out is arbitrarily limited.
 * Callers may hold locks as long as they are held in irq mode.
 *
 * Returns 0 if event occurred otherwise -ETIMEDOUT.
Linus Torvalds's avatar
Linus Torvalds committed
 */

static int NCR5380_poll_politely(struct Scsi_Host *instance,
                                 int reg, int bit, int val, int wait)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);
	unsigned long deadline = jiffies + wait;
	unsigned long n;

	/* Busy-wait for up to 10 ms */
	n = min(10000U, jiffies_to_usecs(wait));
	n *= hostdata->accesses_per_ms;
	n /= 1000;
	do {
		if ((NCR5380_read(reg) & bit) == val)
Linus Torvalds's avatar
Linus Torvalds committed
			return 0;
		cpu_relax();
	} while (n--);

	if (irqs_disabled() || in_interrupt())
		return -ETIMEDOUT;

	/* Repeatedly sleep for 1 ms until deadline */
	while (time_is_after_jiffies(deadline)) {
		schedule_timeout_uninterruptible(1);
		if ((NCR5380_read(reg) & bit) == val)
Linus Torvalds's avatar
Linus Torvalds committed
			return 0;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	return -ETIMEDOUT;
}

static struct {
	unsigned char value;
	const char *name;
} phases[] __maybe_unused = {
Linus Torvalds's avatar
Linus Torvalds committed
	{PHASE_DATAOUT, "DATAOUT"}, 
	{PHASE_DATAIN, "DATAIN"}, 
	{PHASE_CMDOUT, "CMDOUT"}, 
	{PHASE_STATIN, "STATIN"}, 
	{PHASE_MSGOUT, "MSGOUT"}, 
	{PHASE_MSGIN, "MSGIN"}, 
	{PHASE_UNKNOWN, "UNKNOWN"}
};

Al Viro's avatar
Al Viro committed
#if NDEBUG
Linus Torvalds's avatar
Linus Torvalds committed
static struct {
	unsigned char mask;
	const char *name;
} signals[] = { 
	{SR_DBP, "PARITY"}, 
	{SR_RST, "RST"}, 
	{SR_BSY, "BSY"}, 
	{SR_REQ, "REQ"}, 
	{SR_MSG, "MSG"}, 
	{SR_CD, "CD"}, 
	{SR_IO, "IO"}, 
	{SR_SEL, "SEL"}, 
	{0, NULL}
}, 
basrs[] = {
	{BASR_ATN, "ATN"}, 
	{BASR_ACK, "ACK"}, 
	{0, NULL}
}, 
icrs[] = { 
	{ICR_ASSERT_RST, "ASSERT RST"}, 
	{ICR_ASSERT_ACK, "ASSERT ACK"}, 
	{ICR_ASSERT_BSY, "ASSERT BSY"}, 
	{ICR_ASSERT_SEL, "ASSERT SEL"}, 
	{ICR_ASSERT_ATN, "ASSERT ATN"}, 
	{ICR_ASSERT_DATA, "ASSERT DATA"}, 
	{0, NULL}
}, 
mrs[] = { 
	{MR_BLOCK_DMA_MODE, "MODE BLOCK DMA"}, 
	{MR_TARGET, "MODE TARGET"}, 
	{MR_ENABLE_PAR_CHECK, "MODE PARITY CHECK"}, 
	{MR_ENABLE_PAR_INTR, "MODE PARITY INTR"}, 
	{MR_MONITOR_BSY, "MODE MONITOR BSY"}, 
	{MR_DMA_MODE, "MODE DMA"}, 
	{MR_ARBITRATE, "MODE ARBITRATION"}, 
	{0, NULL}
};

/**
 *	NCR5380_print	-	print scsi bus signals
 *	@instance:	adapter state to dump
 *
 *	Print the SCSI bus signals for debugging purposes
 *
 *	Locks: caller holds hostdata lock (not essential)
 */

static void NCR5380_print(struct Scsi_Host *instance)
{
	unsigned char status, data, basr, mr, icr, i;

	data = NCR5380_read(CURRENT_SCSI_DATA_REG);
	status = NCR5380_read(STATUS_REG);
	mr = NCR5380_read(MODE_REG);
	icr = NCR5380_read(INITIATOR_COMMAND_REG);
	basr = NCR5380_read(BUS_AND_STATUS_REG);

	printk("STATUS_REG: %02x ", status);
	for (i = 0; signals[i].mask; ++i)
		if (status & signals[i].mask)
			printk(",%s", signals[i].name);
	printk("\nBASR: %02x ", basr);
	for (i = 0; basrs[i].mask; ++i)
		if (basr & basrs[i].mask)
			printk(",%s", basrs[i].name);
	printk("\nICR: %02x ", icr);
	for (i = 0; icrs[i].mask; ++i)
		if (icr & icrs[i].mask)
			printk(",%s", icrs[i].name);
	printk("\nMODE: %02x ", mr);
	for (i = 0; mrs[i].mask; ++i)
		if (mr & mrs[i].mask)
			printk(",%s", mrs[i].name);
	printk("\n");
}


/* 
 *	NCR5380_print_phase	-	show SCSI phase
 *	@instance: adapter to dump
 *
 * 	Print the current SCSI phase for debugging purposes
 *
 *	Locks: none
 */

static void NCR5380_print_phase(struct Scsi_Host *instance)
{
	unsigned char status;
	int i;

	status = NCR5380_read(STATUS_REG);
	if (!(status & SR_REQ))
		printk("scsi%d : REQ not asserted, phase unknown.\n", instance->host_no);
	else {
		for (i = 0; (phases[i].value != PHASE_UNKNOWN) && (phases[i].value != (status & PHASE_MASK)); ++i);
		printk("scsi%d : phase %s\n", instance->host_no, phases[i].name);
	}
}
#endif


static int probe_irq __initdata;
Linus Torvalds's avatar
Linus Torvalds committed

/**
 *	probe_intr	-	helper for IRQ autoprobe
 *	@irq: interrupt number
 *	@dev_id: unused
 *	@regs: unused
 *
 *	Set a flag to indicate the IRQ in question was received. This is
 *	used by the IRQ probe code.
 */
 
static irqreturn_t __init probe_intr(int irq, void *dev_id)
Linus Torvalds's avatar
Linus Torvalds committed
{
	probe_irq = irq;
	return IRQ_HANDLED;
}

/**
 *	NCR5380_probe_irq	-	find the IRQ of an NCR5380
 *	@instance: NCR5380 controller
 *	@possible: bitmask of ISA IRQ lines
 *
 *	Autoprobe for the IRQ line used by the NCR5380 by triggering an IRQ
 *	and then looking to see what interrupt actually turned up.
 *
 *	Locks: none, irqs must be enabled on entry
 */

static int __init __maybe_unused NCR5380_probe_irq(struct Scsi_Host *instance,
						int possible)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) instance->hostdata;
	unsigned long timeout;
	int trying_irqs, i, mask;

	for (trying_irqs = 0, i = 1, mask = 2; i < 16; ++i, mask <<= 1)
		if ((mask & possible) && (request_irq(i, &probe_intr, 0, "NCR-probe", NULL) == 0))
Linus Torvalds's avatar
Linus Torvalds committed
			trying_irqs |= mask;

	timeout = jiffies + msecs_to_jiffies(250);
	probe_irq = NO_IRQ;
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * A interrupt is triggered whenever BSY = false, SEL = true
	 * and a bit set in the SELECT_ENABLE_REG is asserted on the 
	 * SCSI bus.
	 *
	 * Note that the bus is only driven when the phase control signals
	 * (I/O, C/D, and MSG) match those in the TCR, so we must reset that
	 * to zero.
	 */

	NCR5380_write(TARGET_COMMAND_REG, 0);
	NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask);
	NCR5380_write(OUTPUT_DATA_REG, hostdata->id_mask);
	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_DATA | ICR_ASSERT_SEL);

	while (probe_irq == NO_IRQ && time_before(jiffies, timeout))
		schedule_timeout_uninterruptible(1);
Linus Torvalds's avatar
Linus Torvalds committed
	
	NCR5380_write(SELECT_ENABLE_REG, 0);
	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);

	for (i = 1, mask = 2; i < 16; ++i, mask <<= 1)
Linus Torvalds's avatar
Linus Torvalds committed
		if (trying_irqs & mask)
			free_irq(i, NULL);

	return probe_irq;
}

/**
 *	NCR58380_info - report driver and host information
 *	@instance: relevant scsi host instance
Linus Torvalds's avatar
Linus Torvalds committed
 *
 *	For use as the host template info() handler.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 *	Locks: none
 */

static const char *NCR5380_info(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);

	return hostdata->info;
}

static void prepare_info(struct Scsi_Host *instance)
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);

	snprintf(hostdata->info, sizeof(hostdata->info),
	         "%s, io_port 0x%lx, n_io_port %d, "
	         "base 0x%lx, irq %d, "
	         "can_queue %d, cmd_per_lun %d, "
	         "sg_tablesize %d, this_id %d, "
	         "options { %s} ",
	         instance->hostt->name, instance->io_port, instance->n_io_port,
	         instance->base, instance->irq,
	         instance->can_queue, instance->cmd_per_lun,
	         instance->sg_tablesize, instance->this_id,
	         hostdata->flags & FLAG_NCR53C400     ? "NCR53C400 "     : "",
	         hostdata->flags & FLAG_DTC3181E      ? "DTC3181E "      : "",
	         hostdata->flags & FLAG_NO_PSEUDO_DMA ? "NO_PSEUDO_DMA " : "",
	         hostdata->flags & FLAG_TOSHIBA_DELAY ? "TOSHIBA_DELAY "  : "",
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef AUTOPROBE_IRQ
	         "AUTOPROBE_IRQ "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef DIFFERENTIAL
	         "DIFFERENTIAL "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef REAL_DMA
	         "REAL_DMA "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef REAL_DMA_POLL
	         "REAL_DMA_POLL "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef PARITY
	         "PARITY "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef PSEUDO_DMA
	         "PSEUDO_DMA "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef UNSAFE
	         "UNSAFE "
#endif
	         "");
Linus Torvalds's avatar
Linus Torvalds committed
}

/**
 *	NCR5380_print_status 	-	dump controller info
 *	@instance: controller to dump
 *
 *	Print commands in the various queues, called from NCR5380_abort 
 *	and NCR5380_debug to aid debugging.
 *
 *	Locks: called functions disable irqs
 */

static void __maybe_unused NCR5380_print_status(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	NCR5380_dprint(NDEBUG_ANY, instance);
	NCR5380_dprint_phase(NDEBUG_ANY, instance);
}

Linus Torvalds's avatar
Linus Torvalds committed
/******************************************/
/*
 * /proc/scsi/[dtc pas16 t128 generic]/[0-ASC_NUM_BOARD_SUPPORTED]
 *
 * *buffer: I/O buffer
 * **start: if inout == FALSE pointer into buffer where user read should start
 * offset: current offset
 * length: length of buffer
 * hostno: Scsi_Host host_no
 * inout: TRUE - user is writing; FALSE - user is reading
 *
 * Return the number of bytes read from or written
 */

static int __maybe_unused NCR5380_write_info(struct Scsi_Host *instance,
	char *buffer, int length)
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);

	hostdata->spin_max_r = 0;
	hostdata->spin_max_w = 0;
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
static
void lprint_Scsi_Cmnd(struct scsi_cmnd *cmd, struct seq_file *m);
Linus Torvalds's avatar
Linus Torvalds committed
static
void lprint_command(unsigned char *cmd, struct seq_file *m);
Linus Torvalds's avatar
Linus Torvalds committed
static
void lprint_opcode(int opcode, struct seq_file *m);
Linus Torvalds's avatar
Linus Torvalds committed

static int __maybe_unused NCR5380_show_info(struct seq_file *m,
	struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata;
	struct scsi_cmnd *ptr;
Linus Torvalds's avatar
Linus Torvalds committed

	hostdata = (struct NCR5380_hostdata *) instance->hostdata;

	seq_printf(m, "Highwater I/O busy spin counts: write %d, read %d\n",
	        hostdata->spin_max_w, hostdata->spin_max_r);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	spin_lock_irq(instance->host_lock);
	if (!hostdata->connected)
		seq_printf(m, "scsi%d: no currently connected command\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
	else
		lprint_Scsi_Cmnd((struct scsi_cmnd *) hostdata->connected, m);
	seq_printf(m, "scsi%d: issue_queue\n", instance->host_no);
	for (ptr = (struct scsi_cmnd *) hostdata->issue_queue; ptr; ptr = (struct scsi_cmnd *) ptr->host_scribble)
		lprint_Scsi_Cmnd(ptr, m);
Linus Torvalds's avatar
Linus Torvalds committed

	seq_printf(m, "scsi%d: disconnected_queue\n", instance->host_no);
	for (ptr = (struct scsi_cmnd *) hostdata->disconnected_queue; ptr; ptr = (struct scsi_cmnd *) ptr->host_scribble)
		lprint_Scsi_Cmnd(ptr, m);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_unlock_irq(instance->host_lock);
	return 0;
static void lprint_Scsi_Cmnd(struct scsi_cmnd *cmd, struct seq_file *m)
Linus Torvalds's avatar
Linus Torvalds committed
{
	seq_printf(m, "scsi%d : destination target %d, lun %llu\n", cmd->device->host->host_no, cmd->device->id, cmd->device->lun);
	seq_puts(m, "        command = ");
	lprint_command(cmd->cmnd, m);
static void lprint_command(unsigned char *command, struct seq_file *m)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i, s;
	lprint_opcode(command[0], m);
Linus Torvalds's avatar
Linus Torvalds committed
	for (i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i)
		seq_printf(m, "%02x ", command[i]);
static void lprint_opcode(int opcode, struct seq_file *m)
Linus Torvalds's avatar
Linus Torvalds committed
{
	seq_printf(m, "%2d (0x%02x)", opcode, opcode);
Linus Torvalds's avatar
Linus Torvalds committed
}


/**
 *	NCR5380_init	-	initialise an NCR5380
 *	@instance: adapter to configure
 *	@flags: control flags
 *
 *	Initializes *instance and corresponding 5380 chip,
 *      with flags OR'd into the initial flags value.
 *
 *	Notes : I assume that the host, hostno, and id bits have been
 *      set correctly.  I don't care about the irq and other fields. 
 *
 *	Returns 0 for success
 *
 *	Locks: interrupts must be enabled when we are called 
 */

static int NCR5380_init(struct Scsi_Host *instance, int flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
Linus Torvalds's avatar
Linus Torvalds committed
	struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) instance->hostdata;
	unsigned long deadline;
Linus Torvalds's avatar
Linus Torvalds committed

	if(in_interrupt())
		printk(KERN_ERR "NCR5380_init called with interrupts off!\n");

	hostdata->id_mask = 1 << instance->this_id;
	for (i = hostdata->id_mask; i <= 0x80; i <<= 1)
		if (i > hostdata->id_mask)
			hostdata->id_higher_mask |= i;
	for (i = 0; i < 8; ++i)
		hostdata->busy[i] = 0;
#ifdef REAL_DMA
	hostdata->dmalen = 0;
#endif
	hostdata->connected = NULL;
	hostdata->issue_queue = NULL;
	hostdata->disconnected_queue = NULL;
	
	INIT_DELAYED_WORK(&hostdata->coroutine, NCR5380_main);
	hostdata->work_q = alloc_workqueue("ncr5380_%d",
	                        WQ_UNBOUND | WQ_MEM_RECLAIM,
	                        1, instance->host_no);
	if (!hostdata->work_q)
		return -ENOMEM;

Linus Torvalds's avatar
Linus Torvalds committed
	/* The CHECK code seems to break the 53C400. Will check it later maybe */
	if (flags & FLAG_NCR53C400)
		hostdata->flags = FLAG_HAS_LAST_BYTE_SENT | flags;
	else
		hostdata->flags = FLAG_CHECK_LAST_BYTE_SENT | flags;

	hostdata->host = instance;

	prepare_info(instance);

Linus Torvalds's avatar
Linus Torvalds committed
	NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE);
	NCR5380_write(MODE_REG, MR_BASE);
	NCR5380_write(TARGET_COMMAND_REG, 0);
	NCR5380_write(SELECT_ENABLE_REG, 0);

	/* Calibrate register polling loop */
	i = 0;
	deadline = jiffies + 1;
	do {
		cpu_relax();
	} while (time_is_after_jiffies(deadline));
	deadline += msecs_to_jiffies(256);
	do {
		NCR5380_read(STATUS_REG);
		++i;
		cpu_relax();
	} while (time_is_after_jiffies(deadline));
	hostdata->accesses_per_ms = i / 256;

Linus Torvalds's avatar
Linus Torvalds committed

/**
 * NCR5380_maybe_reset_bus - Detect and correct bus wedge problems.
 * @instance: adapter to check
 *
 * If the system crashed, it may have crashed with a connected target and
 * the SCSI bus busy. Check for BUS FREE phase. If not, try to abort the
 * currently established nexus, which we know nothing about. Failing that
 * do a bus reset.
 *
 * Note that a bus reset will cause the chip to assert IRQ.
 *
 * Returns 0 if successful, otherwise -ENXIO.
 */

static int NCR5380_maybe_reset_bus(struct Scsi_Host *instance)
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);
Linus Torvalds's avatar
Linus Torvalds committed

	for (pass = 1; (NCR5380_read(STATUS_REG) & SR_BSY) && pass <= 6; ++pass) {
		switch (pass) {
		case 1:
		case 3:
		case 5:
			shost_printk(KERN_ERR, instance, "SCSI bus busy, waiting up to five seconds\n");
			NCR5380_poll_politely(instance,
			                      STATUS_REG, SR_BSY, 0, 5 * HZ);
Linus Torvalds's avatar
Linus Torvalds committed
			break;
		case 2:
			shost_printk(KERN_ERR, instance, "bus busy, attempting abort\n");
Linus Torvalds's avatar
Linus Torvalds committed
			do_abort(instance);
			break;
		case 4:
			shost_printk(KERN_ERR, instance, "bus busy, attempting reset\n");
Linus Torvalds's avatar
Linus Torvalds committed
			do_reset(instance);
			/* Wait after a reset; the SCSI standard calls for
			 * 250ms, we wait 500ms to be on the safe side.
			 * But some Toshiba CD-ROMs need ten times that.
			 */
			if (hostdata->flags & FLAG_TOSHIBA_DELAY)
				msleep(2500);
			else
				msleep(500);
Linus Torvalds's avatar
Linus Torvalds committed
			break;
		case 6:
			shost_printk(KERN_ERR, instance, "bus locked solid\n");
Linus Torvalds's avatar
Linus Torvalds committed
			return -ENXIO;
		}
	}
	return 0;
}

/**
 *	NCR5380_exit	-	remove an NCR5380
 *	@instance: adapter to remove
 */

static void NCR5380_exit(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) instance->hostdata;

	cancel_delayed_work_sync(&hostdata->coroutine);
	destroy_workqueue(hostdata->work_q);
 * NCR5380_queue_command - queue a command
 * @instance: the relevant SCSI adapter
 * @cmd: SCSI command
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * cmd is added to the per-instance issue queue, with minor
 * twiddling done to the host specific fields of cmd.  If the
 * main coroutine is not running, it is restarted.
Linus Torvalds's avatar
Linus Torvalds committed
 */

static int NCR5380_queue_command(struct Scsi_Host *instance,
                                 struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);
	struct scsi_cmnd *tmp;
	unsigned long flags;
Linus Torvalds's avatar
Linus Torvalds committed

#if (NDEBUG & NDEBUG_NO_WRITE)
	switch (cmd->cmnd[0]) {
	case WRITE_6:
	case WRITE_10:
		printk("scsi%d : WRITE attempted with NO_WRITE debugging flag set\n", instance->host_no);
		cmd->result = (DID_ERROR << 16);
		cmd->scsi_done(cmd);
Linus Torvalds's avatar
Linus Torvalds committed
		return 0;
	}
#endif				/* (NDEBUG & NDEBUG_NO_WRITE) */

	/* 
	 * We use the host_scribble field as a pointer to the next command  
	 * in a queue 
	 */

	cmd->host_scribble = NULL;
	cmd->result = 0;

	spin_lock_irqsave(instance->host_lock, flags);

Linus Torvalds's avatar
Linus Torvalds committed
	/* 
	 * Insert the cmd into the issue queue. Note that REQUEST SENSE 
	 * commands are added to the head of the queue since any command will
	 * clear the contingent allegiance condition that exists and the 
	 * sense data is only guaranteed to be valid while the condition exists.
	 */

	if (!(hostdata->issue_queue) || (cmd->cmnd[0] == REQUEST_SENSE)) {
		LIST(cmd, hostdata->issue_queue);
		cmd->host_scribble = (unsigned char *) hostdata->issue_queue;
		hostdata->issue_queue = cmd;
	} else {
		for (tmp = (struct scsi_cmnd *) hostdata->issue_queue; tmp->host_scribble; tmp = (struct scsi_cmnd *) tmp->host_scribble);
Linus Torvalds's avatar
Linus Torvalds committed
		LIST(cmd, tmp);
		tmp->host_scribble = (unsigned char *) cmd;
	}
	spin_unlock_irqrestore(instance->host_lock, flags);

	dprintk(NDEBUG_QUEUES, "scsi%d : command added to %s of queue\n", instance->host_no, (cmd->cmnd[0] == REQUEST_SENSE) ? "head" : "tail");
Linus Torvalds's avatar
Linus Torvalds committed

	/* Run the coroutine if it isn't already running. */
	/* Kick off command processing */
	queue_delayed_work(hostdata->work_q, &hostdata->coroutine, 0);
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;
}

/**
 *	NCR5380_main	-	NCR state machines
 *
 *	NCR5380_main is a coroutine that runs as long as more work can 
 *      be done on the NCR5380 host adapters in a system.  Both 
 *      NCR5380_queue_command() and NCR5380_intr() will try to start it 
 *      in case it is not running.
 * 
 *	Locks: called as its own thread with no locks held. Takes the
 *	host lock and called routines may take the isa dma lock.
 */

static void NCR5380_main(struct work_struct *work)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata =
		container_of(work, struct NCR5380_hostdata, coroutine.work);
Linus Torvalds's avatar
Linus Torvalds committed
	struct Scsi_Host *instance = hostdata->host;
	struct scsi_cmnd *tmp, *prev;
Linus Torvalds's avatar
Linus Torvalds committed
	int done;
	
	spin_lock_irq(instance->host_lock);
	do {
		/* Lock held here */
		done = 1;
		if (!hostdata->connected) {
			dprintk(NDEBUG_MAIN, "scsi%d : not connected\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
			/*
			 * Search through the issue_queue for a command destined
			 * for a target that's not busy.
			 */
			for (tmp = (struct scsi_cmnd *) hostdata->issue_queue, prev = NULL; tmp; prev = tmp, tmp = (struct scsi_cmnd *) tmp->host_scribble)
Linus Torvalds's avatar
Linus Torvalds committed
			{
				if (prev != tmp)
Hannes Reinecke's avatar
Hannes Reinecke committed
				    dprintk(NDEBUG_LISTS, "MAIN tmp=%p   target=%d   busy=%d lun=%llu\n", tmp, tmp->device->id, hostdata->busy[tmp->device->id], tmp->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
				/*  When we find one, remove it from the issue queue. */
Hannes Reinecke's avatar
Hannes Reinecke committed
				if (!(hostdata->busy[tmp->device->id] &
				      (1 << (u8)(tmp->device->lun & 0xff)))) {
Linus Torvalds's avatar
Linus Torvalds committed
					if (prev) {
						REMOVE(prev, prev->host_scribble, tmp, tmp->host_scribble);
						prev->host_scribble = tmp->host_scribble;
					} else {
						REMOVE(-1, hostdata->issue_queue, tmp, tmp->host_scribble);
						hostdata->issue_queue = (struct scsi_cmnd *) tmp->host_scribble;
Linus Torvalds's avatar
Linus Torvalds committed
					}
					tmp->host_scribble = NULL;

					/* 
					 * Attempt to establish an I_T_L nexus here. 
					 * On success, instance->hostdata->connected is set.
					 * On failure, we must add the command back to the
					 *   issue queue so we can keep trying. 
					 */
Hannes Reinecke's avatar
Hannes Reinecke committed
					dprintk(NDEBUG_MAIN|NDEBUG_QUEUES, "scsi%d : main() : command for target %d lun %llu removed from issue_queue\n", instance->host_no, tmp->device->id, tmp->device->lun);
Linus Torvalds's avatar
Linus Torvalds committed
	
					/*
					 * REQUEST SENSE commands are issued without tagged
					 * queueing, even on SCSI-II devices because the
					 * contingent allegiance condition exists for the
					 * entire unit.
					 */

					if (!NCR5380_select(instance, tmp)) {
Linus Torvalds's avatar
Linus Torvalds committed
					} else {
Linus Torvalds's avatar
Linus Torvalds committed
						LIST(tmp, hostdata->issue_queue);
						tmp->host_scribble = (unsigned char *) hostdata->issue_queue;
						hostdata->issue_queue = tmp;
						done = 0;
						dprintk(NDEBUG_MAIN|NDEBUG_QUEUES, "scsi%d : main(): select() failed, returned to issue_queue\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
					}
					if (hostdata->connected)
Linus Torvalds's avatar
Linus Torvalds committed
					/* lock held here still */
				}	/* if target/lun is not busy */
			}	/* for */
			/* exited locked */
		}	/* if (!hostdata->connected) */
		if (hostdata->connected
#ifdef REAL_DMA
		    && !hostdata->dmalen
#endif
		    ) {
			dprintk(NDEBUG_MAIN, "scsi%d : main() : performing information transfer\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
			NCR5380_information_transfer(instance);
			dprintk(NDEBUG_MAIN, "scsi%d : main() : done set false\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
			done = 0;
Linus Torvalds's avatar
Linus Torvalds committed
	} while (!done);
	
	spin_unlock_irq(instance->host_lock);
}

#ifndef DONT_USE_INTR

/**
 * 	NCR5380_intr	-	generic NCR5380 irq handler
 *	@irq: interrupt number
 *	@dev_id: device info
 *
 *	Handle interrupts, reestablishing I_T_L or I_T_L_Q nexuses
 *      from the disconnected queue, and restarting NCR5380_main() 
 *      as required.
 *
 *	Locks: takes the needed instance locks
 */

static irqreturn_t NCR5380_intr(int dummy, void *dev_id)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct Scsi_Host *instance = dev_id;
Linus Torvalds's avatar
Linus Torvalds committed
	struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) instance->hostdata;
	int done;
	unsigned char basr;
	unsigned long flags;

	dprintk(NDEBUG_INTR, "scsi : NCR5380 irq %d triggered\n",
		instance->irq);
Linus Torvalds's avatar
Linus Torvalds committed

	do {
		done = 1;
		spin_lock_irqsave(instance->host_lock, flags);
		/* Look for pending interrupts */
		basr = NCR5380_read(BUS_AND_STATUS_REG);
		/* XXX dispatch to appropriate routine if found and done=0 */
		if (basr & BASR_IRQ) {
			NCR5380_dprint(NDEBUG_INTR, instance);
			if ((NCR5380_read(STATUS_REG) & (SR_SEL | SR_IO)) == (SR_SEL | SR_IO)) {
				done = 0;
				dprintk(NDEBUG_INTR, "scsi%d : SEL interrupt\n", instance->host_no);
Linus Torvalds's avatar
Linus Torvalds committed
				NCR5380_reselect(instance);
				(void) NCR5380_read(RESET_PARITY_INTERRUPT_REG);
			} else if (basr & BASR_PARITY_ERROR) {
				dprintk(NDEBUG_INTR, "scsi%d : PARITY interrupt\n", instance->host_no);