Skip to content
atari_NCR5380.c 90.5 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
Linus Torvalds's avatar
Linus Torvalds committed
 *	architecture.
 *
 *	Note that these routines also work with NR53c400 family chips.
 *
 * Copyright 1993, Drew Eckhardt
 *	Visionary Computing
Linus Torvalds's avatar
Linus Torvalds committed
 *	(Unix and Linux consulting and custom programming)
 *	drew@colorado.edu
Linus Torvalds's avatar
Linus Torvalds committed
 *	+1 (303) 666-5836
 *
 * DISTRIBUTION RELEASE 6.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * For more information, please consult
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * NCR 5380 Family
 * SCSI Protocol Controller
 * Databook
 *
 * NCR Microelectronics
 * 1635 Aeroplaza Drive
 * Colorado Springs, CO 80916
 * 1+ (719) 578-3400
 * 1+ (800) 334-5454
 */

/*
 * ++roman: To port the 5380 driver to the Atari, I had to do some changes in
 * this file, too:
 *
 *  - Some of the debug statements were incorrect (undefined variables and the
 *    like). I fixed that.
 *
 *  - In information_transfer(), I think a #ifdef was wrong. Looking at the
 *    possible DMA transfer size should also happen for REAL_DMA. I added this
 *    in the #if statement.
 *
 *  - When using real DMA, information_transfer() should return in a DATAOUT
 *    phase after starting the DMA. It has nothing more to do.
 *
 *  - The interrupt service routine should run main after end of DMA, too (not
 *    only after RESELECTION interrupts). Additionally, it should _not_ test
 *    for more interrupts after running main, since a DMA process may have
 *    been started and interrupts are turned on now. The new int could happen
 *    inside the execution of NCR5380_intr(), leading to recursive
 *    calls.
 *
 *  - I've added a function merge_contiguous_buffers() that tries to
 *    merge scatter-gather buffers that are located at contiguous
 *    physical addresses and can be processed with the same DMA setup.
 *    Since most scatter-gather operations work on a page (4K) of
 *    4 buffers (1K), in more than 90% of all cases three interrupts and
 *    DMA setup actions are saved.
 *
 * - I've deleted all the stuff for AUTOPROBE_IRQ, REAL_DMA_POLL, PSEUDO_DMA
 *    and USLEEP, because these were messing up readability and will never be
 *    needed for Atari SCSI.
Linus Torvalds's avatar
Linus Torvalds committed
 * - I've revised the NCR5380_main() calling scheme (relax the 'main_running'
 *   stuff), and 'main' is executed in a bottom half if awoken by an
 *   interrupt.
 *
 * - The code was quite cluttered up by "#if (NDEBUG & NDEBUG_*) printk..."
 *   constructs. In my eyes, this made the source rather unreadable, so I
 *   finally replaced that by the *_PRINTK() macros.
 *
 */

/*
 * Further development / testing that should be done :
 * 1.  Test linked command handling code after Eric is ready with
Linus Torvalds's avatar
Linus Torvalds committed
 *     the high level code.
 */
#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)						\
	do {							\
		printk("LINE:%d   Adding %p to %p\n",		\
		       __LINE__, (void*)(x), (void*)(y));	\
		if ((x) == (y))					\
			udelay(5);				\
	} while (0)
#define REMOVE(w, x, y, z)					\
	do {							\
		printk("LINE:%d   Removing: %p->%p  %p->%p \n",	\
		       __LINE__, (void*)(w), (void*)(x),	\
		       (void*)(y), (void*)(z));			\
		if ((x) == (y))					\
			udelay(5);				\
	} while (0)
Linus Torvalds's avatar
Linus Torvalds committed
#else
#define LIST(x,y)
#define REMOVE(w,x,y,z)
#endif

#ifndef notyet
#undef LINKED
#endif

/*
 * Design
 * Issues :
 *
 * The other Linux SCSI drivers were written when Linux was Intel PC-only,
 * and specifically for each board rather than each chip.  This makes their
 * adaptation to platforms like the Mac (Some of which use NCR5380's)
 * more difficult than it has to be.
 *
 * Also, many of the SCSI drivers were written before the command queuing
 * routines were implemented, meaning their implementations of queued
Linus Torvalds's avatar
Linus Torvalds committed
 * commands were hacked on rather than designed in from the start.
 *
 * When I designed the Linux SCSI drivers I figured that
Linus Torvalds's avatar
Linus Torvalds committed
 * while having two different SCSI boards in a system might be useful
 * for debugging things, two of the same type wouldn't be used.
 * Well, I was wrong and a number of users have mailed me about running
 * multiple high-performance SCSI boards in a server.
 *
 * Finally, when I get questions from users, I have no idea what
Linus Torvalds's avatar
Linus Torvalds committed
 * revision of my driver they are running.
 *
 * This driver attempts to address these problems :
 * This is a generic 5380 driver.  To use it on a different platform,
Linus Torvalds's avatar
Linus Torvalds committed
 * one simply writes appropriate system specific macros (ie, data
 * transfer - some PC's will use the I/O bus, 68K's must use
Linus Torvalds's avatar
Linus Torvalds committed
 * memory mapped) and drops this file in their 'C' wrapper.
 *
 * As far as command queueing, two queues are maintained for
Linus Torvalds's avatar
Linus Torvalds committed
 * 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
Linus Torvalds's avatar
Linus Torvalds committed
 * while a command is already executing.
 *
 * To solve the multiple-boards-in-the-same-system problem,
Linus Torvalds's avatar
Linus Torvalds committed
 * there is a separate instance structure for each instance
 * of a 5380 in the system.  So, multiple NCR5380 drivers will
 * be able to coexist with appropriate changes to the high level
 * SCSI code.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * A NCR5380_PUBLIC_REVISION macro is provided, with the release
 * number (updated for each public release) printed by the
 * NCR5380_print_options command, which should be called from the
Linus Torvalds's avatar
Linus Torvalds committed
 * wrapper detect function, so that I know what release of the driver
 * users are using.
 *
 * Issues specific to the NCR5380 :
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * 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
Linus Torvalds's avatar
Linus Torvalds committed
 * while doing long seek operations.
Linus Torvalds's avatar
Linus Torvalds committed
 * The workaround for this is to keep track of devices that have
 * disconnected.  If the device hasn't disconnected, for commands that
 * should disconnect, we do something like
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * while (!REQ is asserted) { sleep for N usecs; poll for M usecs }
 *
 * Some tweaking of N and M needs to be done.  An algorithm based
Linus Torvalds's avatar
Linus Torvalds committed
 * on "time to data" would give the best results as long as short time
 * to datas (ie, on the same track) were considered, however 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 when not running by the interrupt handler,
 * timer, and queue command function.  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.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * 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 USLEEP
 * was defined, and 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
Linus Torvalds's avatar
Linus Torvalds committed
 * appropriate.
 *
 * SCSI pointers are maintained in the SCp field of SCSI command
Linus Torvalds's avatar
Linus Torvalds committed
 * 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
Linus Torvalds's avatar
Linus Torvalds committed
 * and macros and include this file in your driver.
 *
 * These macros control options :
Linus Torvalds's avatar
Linus Torvalds committed
 * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically
 *	for commands that return with a CHECK CONDITION status.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * LINKED - if defined, linked commands are supported.
 *
 * REAL_DMA - if defined, REAL DMA is used during the data transfer phases.
 *
 * SUPPORT_TAGS - if defined, SCSI-2 tagged queuing is used where possible
 *
 * These macros MUST be defined :
Linus Torvalds's avatar
Linus Torvalds committed
 * NCR5380_read(register)  - read from the specified register
 *
 * NCR5380_write(register, value) - write to the specific register
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Either real DMA *or* pseudo DMA may be implemented
 * REAL functions :
Linus Torvalds's avatar
Linus Torvalds committed
 * 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
Linus Torvalds's avatar
Linus Torvalds committed
 *	that they were able to program the controller for.
 *
 * Also note that generic i386/PC versions of these macros are
Linus Torvalds's avatar
Linus Torvalds committed
 *	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);
 *
 * If nothing specific to this implementation needs doing (ie, with external
 * hardware), you must also define
 *
Linus Torvalds's avatar
Linus Torvalds committed
 * NCR5380_queue_command
 * NCR5380_reset
 * NCR5380_abort
 * NCR5380_proc_info
 *
 * to be the global entry points into the specific driver, ie
Linus Torvalds's avatar
Linus Torvalds committed
 * #define NCR5380_queue_command t128_queue_command.
 *
 * If this is not done, the routines will be defined as static functions
 * with the NCR5380* names and the user must provide a globally
 * accessible wrapper function.
 *
 * The generic driver is initialized by calling NCR5380_init(instance),
 * after setting the appropriate host specific fields and ID.  If the
Linus Torvalds's avatar
Linus Torvalds committed
 * driver wishes to autoprobe for an IRQ line, the NCR5380_probe_irq(instance,
 * possible) function may be used.  Before the specific driver initialization
 * code finishes, NCR5380_print_options should be called.
 */

static struct Scsi_Host *first_instance = NULL;
static struct scsi_host_template *the_template = NULL;
Linus Torvalds's avatar
Linus Torvalds committed

/* Macros ease life... :-) */
#define	SETUP_HOSTDATA(in)				\
    struct NCR5380_hostdata *hostdata =			\
	(struct NCR5380_hostdata *)(in)->hostdata
#define	HOSTDATA(in) ((struct NCR5380_hostdata *)(in)->hostdata)

#define	NEXT(cmd)		((Scsi_Cmnd *)(cmd)->host_scribble)
#define	SET_NEXT(cmd,next)	((cmd)->host_scribble = (void *)(next))
#define	NEXTADDR(cmd)		((Scsi_Cmnd **)&(cmd)->host_scribble)
Linus Torvalds's avatar
Linus Torvalds committed

#define	HOSTNO		instance->host_no
#define	H_NO(cmd)	(cmd)->device->host->host_no

#ifdef SUPPORT_TAGS

/*
 * Functions for handling tagged queuing
 * =====================================
 *
 * ++roman (01/96): Now I've implemented SCSI-2 tagged queuing. Some notes:
 *
 * Using consecutive numbers for the tags is no good idea in my eyes. There
 * could be wrong re-usings if the counter (8 bit!) wraps and some early
 * command has been preempted for a long time. My solution: a bitfield for
 * remembering used tags.
 *
 * There's also the problem that each target has a certain queue size, but we
 * cannot know it in advance :-( We just see a QUEUE_FULL status being
 * returned. So, in this case, the driver internal queue size assumption is
 * reduced to the number of active tags if QUEUE_FULL is returned by the
 * target. The command is returned to the mid-level, but with status changed
 * to BUSY, since --as I've seen-- the mid-level can't handle QUEUE_FULL
 * correctly.
 *
 * We're also not allowed running tagged commands as long as an untagged
 * command is active. And REQUEST SENSE commands after a contingent allegiance
 * condition _must_ be untagged. To keep track whether an untagged command has
 * been issued, the host->busy array is still employed, as it is without
 * support for tagged queuing.
 *
 * One could suspect that there are possible race conditions between
 * is_lun_busy(), cmd_get_tag() and cmd_free_tag(). But I think this isn't the
 * case: is_lun_busy() and cmd_get_tag() are both called from NCR5380_main(),
 * which already guaranteed to be running at most once. It is also the only
 * place where tags/LUNs are allocated. So no other allocation can slip
 * between that pair, there could only happen a reselection, which can free a
 * tag, but that doesn't hurt. Only the sequence in cmd_free_tag() becomes
 * important: the tag bit must be cleared before 'nr_allocated' is decreased.
 */

/* -1 for TAG_NONE is not possible with unsigned char cmd->tag */
#undef TAG_NONE
#define TAG_NONE 0xff

typedef struct {
	DECLARE_BITMAP(allocated, MAX_TAGS);
	int nr_allocated;
	int queue_size;
Linus Torvalds's avatar
Linus Torvalds committed
} TAG_ALLOC;

static TAG_ALLOC TagAlloc[8][8];	/* 8 targets and 8 LUNs */
static void __init init_tags(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int target, lun;
	TAG_ALLOC *ta;

	if (!setup_use_tagged_queuing)
		return;

	for (target = 0; target < 8; ++target) {
		for (lun = 0; lun < 8; ++lun) {
			ta = &TagAlloc[target][lun];
			bitmap_zero(ta->allocated, MAX_TAGS);
			ta->nr_allocated = 0;
			/* At the beginning, assume the maximum queue size we could
			 * support (MAX_TAGS). This value will be decreased if the target
			 * returns QUEUE_FULL status.
			 */
			ta->queue_size = MAX_TAGS;
		}
Linus Torvalds's avatar
Linus Torvalds committed
	}
}


/* Check if we can issue a command to this LUN: First see if the LUN is marked
 * busy by an untagged command. If the command should use tagged queuing, also
 * check that there is a free tag and the target's queue won't overflow. This
 * function should be called with interrupts disabled to avoid race
 * conditions.
Linus Torvalds's avatar
Linus Torvalds committed

static int is_lun_busy(Scsi_Cmnd *cmd, int should_be_tagged)
Linus Torvalds's avatar
Linus Torvalds committed
{
	SETUP_HOSTDATA(cmd->device->host);

	if (hostdata->busy[cmd->device->id] & (1 << cmd->device->lun))
		return 1;
	if (!should_be_tagged ||
	    !setup_use_tagged_queuing || !cmd->device->tagged_supported)
		return 0;
	if (TagAlloc[cmd->device->id][cmd->device->lun].nr_allocated >=
	    TagAlloc[cmd->device->id][cmd->device->lun].queue_size) {
		TAG_PRINTK("scsi%d: target %d lun %d: no free tags\n",
			   H_NO(cmd), cmd->device->id, cmd->device->lun);
		return 1;
	}
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
}


/* Allocate a tag for a command (there are no checks anymore, check_lun_busy()
 * must be called before!), or reserve the LUN in 'busy' if the command is
 * untagged.
 */

static void cmd_get_tag(Scsi_Cmnd *cmd, int should_be_tagged)
Linus Torvalds's avatar
Linus Torvalds committed
{
	SETUP_HOSTDATA(cmd->device->host);

	/* If we or the target don't support tagged queuing, allocate the LUN for
	 * an untagged command.
	 */
	if (!should_be_tagged ||
	    !setup_use_tagged_queuing || !cmd->device->tagged_supported) {
		cmd->tag = TAG_NONE;
		hostdata->busy[cmd->device->id] |= (1 << cmd->device->lun);
		TAG_PRINTK("scsi%d: target %d lun %d now allocated by untagged "
			   "command\n", H_NO(cmd), cmd->device->id, cmd->device->lun);
	} else {
		TAG_ALLOC *ta = &TagAlloc[cmd->device->id][cmd->device->lun];

		cmd->tag = find_first_zero_bit(ta->allocated, MAX_TAGS);
		set_bit(cmd->tag, ta->allocated);
		ta->nr_allocated++;
		TAG_PRINTK("scsi%d: using tag %d for target %d lun %d "
			   "(now %d tags in use)\n",
			   H_NO(cmd), cmd->tag, cmd->device->id,
			   cmd->device->lun, ta->nr_allocated);
	}
Linus Torvalds's avatar
Linus Torvalds committed
}


/* Mark the tag of command 'cmd' as free, or in case of an untagged command,
 * unlock the LUN.
 */

static void cmd_free_tag(Scsi_Cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	SETUP_HOSTDATA(cmd->device->host);

	if (cmd->tag == TAG_NONE) {
		hostdata->busy[cmd->device->id] &= ~(1 << cmd->device->lun);
		TAG_PRINTK("scsi%d: target %d lun %d untagged cmd finished\n",
			   H_NO(cmd), cmd->device->id, cmd->device->lun);
	} else if (cmd->tag >= MAX_TAGS) {
		printk(KERN_NOTICE "scsi%d: trying to free bad tag %d!\n",
		       H_NO(cmd), cmd->tag);
	} else {
		TAG_ALLOC *ta = &TagAlloc[cmd->device->id][cmd->device->lun];
		clear_bit(cmd->tag, ta->allocated);
		ta->nr_allocated--;
		TAG_PRINTK("scsi%d: freed tag %d for target %d lun %d\n",
			   H_NO(cmd), cmd->tag, cmd->device->id, cmd->device->lun);
	}
static void free_all_tags(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int target, lun;
	TAG_ALLOC *ta;

	if (!setup_use_tagged_queuing)
		return;

	for (target = 0; target < 8; ++target) {
		for (lun = 0; lun < 8; ++lun) {
			ta = &TagAlloc[target][lun];
			bitmap_zero(ta->allocated, MAX_TAGS);
			ta->nr_allocated = 0;
		}
Linus Torvalds's avatar
Linus Torvalds committed
	}
}

#endif /* SUPPORT_TAGS */


/*
 * Function: void merge_contiguous_buffers( Scsi_Cmnd *cmd )
 *
 * Purpose: Try to merge several scatter-gather requests into one DMA
 *    transfer. This is possible if the scatter buffers lie on
 *    physical contiguous addresses.
 *
 * Parameters: Scsi_Cmnd *cmd
 *    The command to work on. The first scatter buffer's data are
 *    assumed to be already transfered into ptr/this_residual.
 */

static void merge_contiguous_buffers(Scsi_Cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	unsigned long endaddr;
Linus Torvalds's avatar
Linus Torvalds committed
#if (NDEBUG & NDEBUG_MERGING)
	unsigned long oldlen = cmd->SCp.this_residual;
	int cnt = 1;
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	for (endaddr = virt_to_phys(cmd->SCp.ptr + cmd->SCp.this_residual - 1) + 1;
	     cmd->SCp.buffers_residual &&
Geert Uytterhoeven's avatar
Geert Uytterhoeven committed
	     virt_to_phys(sg_virt(&cmd->SCp.buffer[1])) == endaddr;) {
		MER_PRINTK("VTOP(%p) == %08lx -> merging\n",
Geert Uytterhoeven's avatar
Geert Uytterhoeven committed
			   page_address(sg_page(&cmd->SCp.buffer[1])), endaddr);
Linus Torvalds's avatar
Linus Torvalds committed
#if (NDEBUG & NDEBUG_MERGING)
Linus Torvalds's avatar
Linus Torvalds committed
#endif
		++cmd->SCp.buffer;
		--cmd->SCp.buffers_residual;
		cmd->SCp.this_residual += cmd->SCp.buffer->length;
		endaddr += cmd->SCp.buffer->length;
	}
Linus Torvalds's avatar
Linus Torvalds committed
#if (NDEBUG & NDEBUG_MERGING)
	if (oldlen != cmd->SCp.this_residual)
		MER_PRINTK("merged %d buffers from %p, new length %08x\n",
			   cnt, cmd->SCp.ptr, cmd->SCp.this_residual);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
}

/*
 * Function : void initialize_SCp(Scsi_Cmnd *cmd)
 *
 * Purpose : initialize the saved data pointers for cmd to point to the
Linus Torvalds's avatar
Linus Torvalds committed
 *	start of the buffer.
 *
 * Inputs : cmd - Scsi_Cmnd structure to have pointers reset.
 */

static inline void initialize_SCp(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.
Linus Torvalds's avatar
Linus Torvalds committed
	 */

	if (cmd->use_sg) {
		cmd->SCp.buffer = (struct scatterlist *)cmd->request_buffer;
		cmd->SCp.buffers_residual = cmd->use_sg - 1;
		cmd->SCp.ptr = sg_virt(cmd->SCp.buffer);
		cmd->SCp.this_residual = cmd->SCp.buffer->length;
		/* ++roman: Try to merge some scatter-buffers if they are at
		 * contiguous physical addresses.
		 */
		merge_contiguous_buffers(cmd);
	} else {
		cmd->SCp.buffer = NULL;
		cmd->SCp.buffers_residual = 0;
		cmd->SCp.ptr = (char *)cmd->request_buffer;
		cmd->SCp.this_residual = cmd->request_bufflen;
	}
Linus Torvalds's avatar
Linus Torvalds committed
}

#include <linux/delay.h>

#if NDEBUG
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_ENABLE_EOP_INTR,"MODE EOP INTR"},
	{MR_MONITOR_BSY, "MODE MONITOR BSY"},
	{MR_DMA_MODE, "MODE DMA"}, {MR_ARBITRATE, "MODE ARBITRATION"},
	{0, NULL}
};
Linus Torvalds's avatar
Linus Torvalds committed

/*
 * Function : void NCR5380_print(struct Scsi_Host *instance)
 *
 * Purpose : print the SCSI bus signals for debugging purposes
 *
 * Input : instance - which NCR5380
 */

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

	local_irq_save(flags);
	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);
	local_irq_restore(flags);
	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");
Linus Torvalds's avatar
Linus Torvalds committed
}

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

Linus Torvalds's avatar
Linus Torvalds committed
 * Function : void NCR5380_print_phase(struct Scsi_Host *instance)
 *
 * Purpose : print the current SCSI phase for debugging purposes
 *
 * Input : instance - which NCR5380
 */

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

	status = NCR5380_read(STATUS_REG);
	if (!(status & SR_REQ))
		printk(KERN_DEBUG "scsi%d: REQ not asserted, phase unknown.\n", HOSTNO);
	else {
		for (i = 0; (phases[i].value != PHASE_UNKNOWN) &&
		     (phases[i].value != (status & PHASE_MASK)); ++i)
			;
		printk(KERN_DEBUG "scsi%d: phase %s\n", HOSTNO, phases[i].name);
	}
Linus Torvalds's avatar
Linus Torvalds committed
}

#else /* !NDEBUG */

/* dummies... */
static inline void NCR5380_print(struct Scsi_Host *instance)
{
};
static inline void NCR5380_print_phase(struct Scsi_Host *instance)
{
};
Linus Torvalds's avatar
Linus Torvalds committed

#endif

/*
 * ++roman: New scheme of calling NCR5380_main()
Linus Torvalds's avatar
Linus Torvalds committed
 * If we're not in an interrupt, we can call our main directly, it cannot be
 * already running. Else, we queue it on a task queue, if not 'main_running'
 * tells us that a lower level is already executing it. This way,
 * 'main_running' needs not be protected in a special way.
 *
 * queue_main() is a utility function for putting our main onto the task
 * queue, if main_running is false. It should be called only from a
 * interrupt or bottom half.
 */

#include <linux/workqueue.h>
#include <linux/interrupt.h>

static volatile int main_running;
static DECLARE_WORK(NCR5380_tqueue, NCR5380_main);
Linus Torvalds's avatar
Linus Torvalds committed

static inline void queue_main(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	if (!main_running) {
		/* If in interrupt and NCR5380_main() not already running,
		   queue it on the 'immediate' task queue, to be processed
		   immediately after the current interrupt processing has
		   finished. */
		schedule_work(&NCR5380_tqueue);
	}
	/* else: nothing to do: the running NCR5380_main() will pick up
	   any newly queued command. */
static inline void NCR5380_all_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	static int done = 0;
	if (!done) {
		INI_PRINTK("scsi : NCR5380_all_init()\n");
		done = 1;
	}
Linus Torvalds's avatar
Linus Torvalds committed
/*
 * Function : void NCR58380_print_options (struct Scsi_Host *instance)
 *
 * Purpose : called by probe code indicating the NCR5380 driver
 *	     options that were selected.
 *
 * Inputs : instance, pointer to this instance.  Unused.
 */

static void __init NCR5380_print_options(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	printk(" generic options"
#ifdef AUTOSENSE
	       " AUTOSENSE"
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef REAL_DMA
	       " REAL DMA"
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef PARITY
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef SUPPORT_TAGS
	       " SCSI-2 TAGGED QUEUING"
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	       );
	printk(" generic release=%d", NCR5380_PUBLIC_RELEASE);
Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 * Function : void NCR5380_print_status (struct Scsi_Host *instance)
 *
 * Purpose : print commands in the various queues, called from
 *	NCR5380_abort and NCR5380_debug to aid debugging.
 *
 * Inputs : instance, pointer to this instance.
Linus Torvalds's avatar
Linus Torvalds committed
 */

static void NCR5380_print_status(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	char *pr_bfr;
	char *start;
	int len;

	NCR_PRINT(NDEBUG_ANY);
	NCR_PRINT_PHASE(NDEBUG_ANY);

	pr_bfr = (char *)__get_free_page(GFP_ATOMIC);
	if (!pr_bfr) {
		printk("NCR5380_print_status: no memory for print buffer\n");
		return;
	}
	len = NCR5380_proc_info(instance, pr_bfr, &start, 0, PAGE_SIZE, 0);
	pr_bfr[len] = 0;
	printk("\n%s\n", pr_bfr);
	free_page((unsigned long)pr_bfr);
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
*/

#undef SPRINTF
#define SPRINTF(fmt,args...)							\
	do {									\
		if (pos + strlen(fmt) + 20 /* slop */ < buffer + length)	\
			pos += sprintf(pos, fmt , ## args);			\
	} while(0)
static char *lprint_Scsi_Cmnd(Scsi_Cmnd *cmd, char *pos, char *buffer, int length);

static int NCR5380_proc_info(struct Scsi_Host *instance, char *buffer,
			     char **start, off_t offset, int length, int inout)
Linus Torvalds's avatar
Linus Torvalds committed
{
	char *pos = buffer;
	struct NCR5380_hostdata *hostdata;
	Scsi_Cmnd *ptr;
	unsigned long flags;
	off_t begin = 0;
#define check_offset()					\
	do {						\
		if (pos - buffer < offset - begin) {	\
			begin += pos - buffer;		\
			pos = buffer;			\
		}					\
	} while (0)

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

	if (inout)			/* Has data been written to the file ? */
		return -ENOSYS;		/* Currently this is a no-op */
	SPRINTF("NCR5380 core release=%d.\n", NCR5380_PUBLIC_RELEASE);
	check_offset();
	local_irq_save(flags);
	SPRINTF("NCR5380: coroutine is%s running.\n",
		main_running ? "" : "n't");
Linus Torvalds's avatar
Linus Torvalds committed
	check_offset();
	if (!hostdata->connected)
		SPRINTF("scsi%d: no currently connected command\n", HOSTNO);
	else
		pos = lprint_Scsi_Cmnd((Scsi_Cmnd *) hostdata->connected,
				       pos, buffer, length);
	SPRINTF("scsi%d: issue_queue\n", HOSTNO);
	check_offset();
	for (ptr = (Scsi_Cmnd *)hostdata->issue_queue; ptr; ptr = NEXT(ptr)) {
		pos = lprint_Scsi_Cmnd(ptr, pos, buffer, length);
		check_offset();
	}
Linus Torvalds's avatar
Linus Torvalds committed

	SPRINTF("scsi%d: disconnected_queue\n", HOSTNO);
Linus Torvalds's avatar
Linus Torvalds committed
	check_offset();
	for (ptr = (Scsi_Cmnd *) hostdata->disconnected_queue; ptr;
	     ptr = NEXT(ptr)) {
		pos = lprint_Scsi_Cmnd(ptr, pos, buffer, length);
		check_offset();
	}
Linus Torvalds's avatar
Linus Torvalds committed

	local_irq_restore(flags);
	*start = buffer + (offset - begin);
	if (pos - buffer < offset - begin)
		return 0;
	else if (pos - buffer - (offset - begin) < length)
		return pos - buffer - (offset - begin);
	return length;
static char *lprint_Scsi_Cmnd(Scsi_Cmnd *cmd, char *pos, char *buffer, int length)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i, s;
	unsigned char *command;
	SPRINTF("scsi%d: destination target %d, lun %d\n",
		H_NO(cmd), cmd->device->id, cmd->device->lun);
	SPRINTF("        command = ");
	command = cmd->cmnd;
	SPRINTF("%2d (0x%02x)", command[0], command[0]);
	for (i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i)
		SPRINTF(" %02x", command[i]);
	SPRINTF("\n");
	return pos;
Linus Torvalds's avatar
Linus Torvalds committed
 * Function : void NCR5380_init (struct Scsi_Host *instance)
 *
 * Purpose : initializes *instance and corresponding 5380 chip.
 *
 * Inputs : instance - instantiation of the 5380 driver.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Notes : I assume that the host, hostno, and id bits have been
 *	set correctly.  I don't care about the irq and other fields.
 *
Linus Torvalds's avatar
Linus Torvalds committed
 */

static int NCR5380_init(struct Scsi_Host *instance, int flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i;
	SETUP_HOSTDATA(instance);

	NCR5380_all_init();

	hostdata->aborted = 0;
	hostdata->id_mask = 1 << instance->this_id;
	hostdata->id_higher_mask = 0;
	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;
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef SUPPORT_TAGS
	init_tags();
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#if defined (REAL_DMA)
	hostdata->dma_len = 0;
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	hostdata->targets_present = 0;
	hostdata->connected = NULL;
	hostdata->issue_queue = NULL;
	hostdata->disconnected_queue = NULL;
	hostdata->flags = FLAG_CHECK_LAST_BYTE_SENT;

	if (!the_template) {
		the_template = instance->hostt;
		first_instance = instance;
	}
Linus Torvalds's avatar
Linus Torvalds committed

#ifndef AUTOSENSE
	if ((instance->cmd_per_lun > 1) || (instance->can_queue > 1))
		printk("scsi%d: WARNING : support for multiple outstanding commands enabled\n"
		       "        without AUTOSENSE option, contingent allegiance conditions may\n"
		       "        be incorrectly cleared.\n", HOSTNO);
Linus Torvalds's avatar
Linus Torvalds committed
#endif /* def AUTOSENSE */

	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);
Linus Torvalds's avatar
Linus Torvalds committed

/*
 * Function : int NCR5380_queue_command (Scsi_Cmnd *cmd,
 *	void (*done)(Scsi_Cmnd *))
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Purpose :  enqueues a SCSI command
 *
 * Inputs : cmd - SCSI command, done - function called on completion, with
 *	a pointer to the command descriptor.
Linus Torvalds's avatar
Linus Torvalds committed
 * Returns : 0
 *
 * Side effects :
 *      cmd is added to the per instance issue_queue, with minor
 *	twiddling done to the host specific fields of cmd.  If the
Linus Torvalds's avatar
Linus Torvalds committed
 *	main coroutine is not running, it is restarted.
 *
 */

static int NCR5380_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *))
Linus Torvalds's avatar
Linus Torvalds committed
{
	SETUP_HOSTDATA(cmd->device->host);
	Scsi_Cmnd *tmp;
	int oldto;
	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(KERN_NOTICE "scsi%d: WRITE attempted with NO_WRITE debugging flag set\n",
		       H_NO(cmd));
		cmd->result = (DID_ERROR << 16);
		done(cmd);
		return 0;
	}
Linus Torvalds's avatar
Linus Torvalds committed
#endif /* (NDEBUG & NDEBUG_NO_WRITE) */

#ifdef NCR5380_STATS
# if 0
	if (!hostdata->connected && !hostdata->issue_queue &&
	    !hostdata->disconnected_queue) {
		hostdata->timebase = jiffies;
	}
Linus Torvalds's avatar
Linus Torvalds committed
# endif
# ifdef NCR5380_STAT_LIMIT
	if (cmd->request_bufflen > NCR5380_STAT_LIMIT)
Linus Torvalds's avatar
Linus Torvalds committed
# endif
		switch (cmd->cmnd[0]) {
		case WRITE:
		case WRITE_6:
		case WRITE_10:
			hostdata->time_write[cmd->device->id] -= (jiffies - hostdata->timebase);
			hostdata->bytes_write[cmd->device->id] += cmd->request_bufflen;
			hostdata->pendingw++;
			break;
		case READ:
		case READ_6:
		case READ_10:
			hostdata->time_read[cmd->device->id] -= (jiffies - hostdata->timebase);
			hostdata->bytes_read[cmd->device->id] += cmd->request_bufflen;
			hostdata->pendingr++;
			break;
		}
Linus Torvalds's avatar
Linus Torvalds committed
#endif

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

	SET_NEXT(cmd, NULL);
	cmd->scsi_done = done;

	cmd->result = 0;

	/*
	 * 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.
	 */

	local_irq_save(flags);
	/* ++guenther: now that the issue queue is being set up, we can lock ST-DMA.
	 * Otherwise a running NCR5380_main may steal the lock.
	 * Lock before actually inserting due to fairness reasons explained in
	 * atari_scsi.c. If we insert first, then it's impossible for this driver
	 * to release the lock.
	 * Stop timer for this command while waiting for the lock, or timeouts
	 * may happen (and they really do), and it's no good if the command doesn't
	 * appear in any of the queues.
	 * ++roman: Just disabling the NCR interrupt isn't sufficient here,
	 * because also a timer int can trigger an abort or reset, which would
	 * alter queues and touch the lock.
	 */
	if (!IS_A_TT()) {
		/* perhaps stop command timer here */
		falcon_get_lock();
		/* perhaps restart command timer here */
	}
	if (!(hostdata->issue_queue) || (cmd->cmnd[0] == REQUEST_SENSE)) {
		LIST(cmd, hostdata->issue_queue);
		SET_NEXT(cmd, hostdata->issue_queue);
		hostdata->issue_queue = cmd;
	} else {
		for (tmp = (Scsi_Cmnd *)hostdata->issue_queue;
		     NEXT(tmp); tmp = NEXT(tmp))