Skip to content
Snippets Groups Projects
atari_NCR5380.c 86.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
 *
 * 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.
 *
 */

/* Adapted for the sun3 by Sam Creasey. */

#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

/*
 * Design
 *
 * 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.
 *
 *
 * 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
 * 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.
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 the target is
 * idle for too long, the system will try to sleep.
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * 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
 *
 * DIFFERENTIAL - if defined, NCR53c81 chips will use external differential
 *	transceivers.
 *
Linus Torvalds's avatar
Linus Torvalds committed
 * 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
 *
 * NCR5380_implementation_fields  - additional fields needed for this
 *      specific implementation of the NCR5380
 *
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);
 *
 * 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.
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)		((struct scsi_cmnd *)(cmd)->host_scribble)
#define	SET_NEXT(cmd,next)	((cmd)->host_scribble = (void *)(next))
#define	NEXTADDR(cmd)		((struct 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

static int do_abort(struct Scsi_Host *);
static void do_reset(struct Scsi_Host *);

Linus Torvalds's avatar
Linus Torvalds committed
#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.
 */

static void __init init_tags(struct NCR5380_hostdata *hostdata)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int target, lun;
	struct tag_alloc *ta;
	if (!(hostdata->flags & FLAG_TAGGED_QUEUING))
		return;

	for (target = 0; target < 8; ++target) {
		for (lun = 0; lun < 8; ++lun) {
			ta = &hostdata->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(struct scsi_cmnd *cmd, int should_be_tagged)
Linus Torvalds's avatar
Linus Torvalds committed
{
Hannes Reinecke's avatar
Hannes Reinecke committed
	u8 lun = cmd->device->lun;
	SETUP_HOSTDATA(cmd->device->host);

Hannes Reinecke's avatar
Hannes Reinecke committed
	if (hostdata->busy[cmd->device->id] & (1 << lun))
		return 1;
	if (!should_be_tagged ||
	    !(hostdata->flags & FLAG_TAGGED_QUEUING) ||
	    !cmd->device->tagged_supported)
	if (hostdata->TagAlloc[scmd_id(cmd)][lun].nr_allocated >=
	    hostdata->TagAlloc[scmd_id(cmd)][lun].queue_size) {
		dprintk(NDEBUG_TAGS, "scsi%d: target %d lun %d: no free tags\n",
Hannes Reinecke's avatar
Hannes Reinecke committed
			   H_NO(cmd), cmd->device->id, 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(struct scsi_cmnd *cmd, int should_be_tagged)
Linus Torvalds's avatar
Linus Torvalds committed
{
Hannes Reinecke's avatar
Hannes Reinecke committed
	u8 lun = cmd->device->lun;
	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 ||
	    !(hostdata->flags & FLAG_TAGGED_QUEUING) ||
	    !cmd->device->tagged_supported) {
		cmd->tag = TAG_NONE;
Hannes Reinecke's avatar
Hannes Reinecke committed
		hostdata->busy[cmd->device->id] |= (1 << lun);
		dprintk(NDEBUG_TAGS, "scsi%d: target %d lun %d now allocated by untagged "
Hannes Reinecke's avatar
Hannes Reinecke committed
			   "command\n", H_NO(cmd), cmd->device->id, lun);
		struct tag_alloc *ta = &hostdata->TagAlloc[scmd_id(cmd)][lun];

		cmd->tag = find_first_zero_bit(ta->allocated, MAX_TAGS);
		set_bit(cmd->tag, ta->allocated);
		ta->nr_allocated++;
		dprintk(NDEBUG_TAGS, "scsi%d: using tag %d for target %d lun %d "
			   "(now %d tags in use)\n",
			   H_NO(cmd), cmd->tag, cmd->device->id,
Hannes Reinecke's avatar
Hannes Reinecke committed
			   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(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
Hannes Reinecke's avatar
Hannes Reinecke committed
	u8 lun = cmd->device->lun;
	SETUP_HOSTDATA(cmd->device->host);

	if (cmd->tag == TAG_NONE) {
Hannes Reinecke's avatar
Hannes Reinecke committed
		hostdata->busy[cmd->device->id] &= ~(1 << lun);
		dprintk(NDEBUG_TAGS, "scsi%d: target %d lun %d untagged cmd finished\n",
Hannes Reinecke's avatar
Hannes Reinecke committed
			   H_NO(cmd), cmd->device->id, 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 {
		struct tag_alloc *ta = &hostdata->TagAlloc[scmd_id(cmd)][lun];
		clear_bit(cmd->tag, ta->allocated);
		ta->nr_allocated--;
		dprintk(NDEBUG_TAGS, "scsi%d: freed tag %d for target %d lun %d\n",
Hannes Reinecke's avatar
Hannes Reinecke committed
			   H_NO(cmd), cmd->tag, cmd->device->id, lun);
static void free_all_tags(struct NCR5380_hostdata *hostdata)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int target, lun;
	struct tag_alloc *ta;
	if (!(hostdata->flags & FLAG_TAGGED_QUEUING))
		return;

	for (target = 0; target < 8; ++target) {
		for (lun = 0; lun < 8; ++lun) {
			ta = &hostdata->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( struct scsi_cmnd *cmd )
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * 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: struct scsi_cmnd *cmd
Linus Torvalds's avatar
Linus Torvalds committed
 *    The command to work on. The first scatter buffer's data are
Lucas De Marchi's avatar
Lucas De Marchi committed
 *    assumed to be already transferred into ptr/this_residual.
Linus Torvalds's avatar
Linus Torvalds committed
 */

static void merge_contiguous_buffers(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
#if !defined(CONFIG_SUN3)
	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;) {
		dprintk(NDEBUG_MERGING, "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)
		dprintk(NDEBUG_MERGING, "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
#endif /* !defined(CONFIG_SUN3) */
/**
 * initialize_SCp - init the scsi pointer field
 * @cmd: command block to set up
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Set up the internal fields in the SCSI command.
Linus Torvalds's avatar
Linus Torvalds committed
 */

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

static int NCR5380_poll_politely(struct Scsi_Host *instance,
                                 int reg, int bit, int val, int wait)
	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)
	} 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
#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

/**
 * NCR5380_print - print scsi bus signals
 * @instance: adapter state to dump
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Print the SCSI bus signals for debugging purposes
Linus Torvalds's avatar
Linus Torvalds committed
 */

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

/**
 * NCR5380_print_phase - show SCSI phase
 * @instance: adapter to dump
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Print the current SCSI phase for debugging purposes
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Locks: none
Linus Torvalds's avatar
Linus Torvalds committed
 */

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

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_TAGGED_QUEUING ? "TAGGED_QUEUING " : "",
	         hostdata->flags & FLAG_TOSHIBA_DELAY  ? "TOSHIBA_DELAY "  : "",
#ifdef DIFFERENTIAL
	         "DIFFERENTIAL "
#endif
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef REAL_DMA
	         "REAL_DMA "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef PARITY
	         "PARITY "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef SUPPORT_TAGS
	         "SUPPORT_TAGS "
Linus Torvalds's avatar
Linus Torvalds committed
#endif
/**
 * NCR5380_print_status - dump controller info
 * @instance: controller to dump
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Print commands in the various queues, called from NCR5380_abort
 * to aid debugging.
Linus Torvalds's avatar
Linus Torvalds committed
 */

static void lprint_Scsi_Cmnd(struct scsi_cmnd *cmd)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i, s;
	unsigned char *command;
Hannes Reinecke's avatar
Hannes Reinecke committed
	printk("scsi%d: destination target %d, lun %llu\n",
		H_NO(cmd), cmd->device->id, cmd->device->lun);
	printk(KERN_CONT "        command = ");
	command = cmd->cmnd;
	printk(KERN_CONT "%2d (0x%02x)", command[0], command[0]);
	for (i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i)
		printk(KERN_CONT " %02x", command[i]);
	printk("\n");
static void __maybe_unused NCR5380_print_status(struct Scsi_Host *instance)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct NCR5380_hostdata *hostdata;
	struct scsi_cmnd *ptr;
	unsigned long flags;
	NCR5380_dprint(NDEBUG_ANY, instance);
	NCR5380_dprint_phase(NDEBUG_ANY, instance);

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

	local_irq_save(flags);
	if (!hostdata->connected)
		printk("scsi%d: no currently connected command\n", HOSTNO);
		lprint_Scsi_Cmnd((struct scsi_cmnd *) hostdata->connected);
	printk("scsi%d: issue_queue\n", HOSTNO);
	for (ptr = (struct scsi_cmnd *)hostdata->issue_queue; ptr; ptr = NEXT(ptr))
		lprint_Scsi_Cmnd(ptr);
Linus Torvalds's avatar
Linus Torvalds committed

	printk("scsi%d: disconnected_queue\n", HOSTNO);
	for (ptr = (struct scsi_cmnd *) hostdata->disconnected_queue; ptr;
	     ptr = NEXT(ptr))
		lprint_Scsi_Cmnd(ptr);
Linus Torvalds's avatar
Linus Torvalds committed

	local_irq_restore(flags);
	printk("\n");
static void show_Scsi_Cmnd(struct scsi_cmnd *cmd, struct seq_file *m)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i, s;
	unsigned char *command;
Hannes Reinecke's avatar
Hannes Reinecke committed
	seq_printf(m, "scsi%d: destination target %d, lun %llu\n",
		H_NO(cmd), cmd->device->id, cmd->device->lun);
	seq_puts(m, "        command = ");
	command = cmd->cmnd;
	seq_printf(m, "%2d (0x%02x)", command[0], command[0]);
	for (i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i)
		seq_printf(m, " %02x", command[i]);
static int __maybe_unused NCR5380_show_info(struct seq_file *m,
                                            struct Scsi_Host *instance)
{
	struct NCR5380_hostdata *hostdata;
	struct scsi_cmnd *ptr;
	unsigned long flags;

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

	local_irq_save(flags);
	if (!hostdata->connected)
		seq_printf(m, "scsi%d: no currently connected command\n", HOSTNO);
	else
		show_Scsi_Cmnd((struct scsi_cmnd *) hostdata->connected, m);
	seq_printf(m, "scsi%d: issue_queue\n", HOSTNO);
	for (ptr = (struct scsi_cmnd *)hostdata->issue_queue; ptr; ptr = NEXT(ptr))
		show_Scsi_Cmnd(ptr, m);

	seq_printf(m, "scsi%d: disconnected_queue\n", HOSTNO);
	for (ptr = (struct scsi_cmnd *) hostdata->disconnected_queue; ptr;
	     ptr = NEXT(ptr))
		show_Scsi_Cmnd(ptr, m);

	local_irq_restore(flags);
	return 0;
}
Linus Torvalds's avatar
Linus Torvalds committed

/**
 * NCR5380_init - initialise an NCR5380
 * @instance: adapter to configure
 * @flags: control flags
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * Initializes *instance and corresponding 5380 chip,
 * with flags OR'd into the initial flags value.
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.
 * Returns 0 for success
Linus Torvalds's avatar
Linus Torvalds committed
 */

static int __init NCR5380_init(struct Scsi_Host *instance, int flags)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int i;
	SETUP_HOSTDATA(instance);
	unsigned long deadline;
	hostdata->host = instance;
	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(hostdata);
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->connected = NULL;
	hostdata->issue_queue = NULL;
	hostdata->disconnected_queue = NULL;
	hostdata->flags = flags;
	INIT_WORK(&hostdata->main_task, 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

	prepare_info(instance);

	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

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

/**
 * 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);
	int pass;

	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);
			break;
		case 2:
			shost_printk(KERN_ERR, instance, "bus busy, attempting abort\n");
			do_abort(instance);
			break;
		case 4:
			shost_printk(KERN_ERR, instance, "bus busy, attempting reset\n");
			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);
			break;
		case 6:
			shost_printk(KERN_ERR, instance, "bus locked solid\n");
			return -ENXIO;
		}
	}
	return 0;
}

/**
 * NCR5380_exit - remove an NCR5380
 * @instance: adapter to remove
 *
 * Assumes that no more work can be queued (e.g. by NCR5380_intr).
 */

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

	cancel_work_sync(&hostdata->main_task);
	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(KERN_NOTICE "scsi%d: WRITE attempted with NO_WRITE debugging flag set\n",
		       H_NO(cmd));
		cmd->result = (DID_ERROR << 16);
		cmd->scsi_done(cmd);
Linus Torvalds's avatar
Linus Torvalds committed
#endif /* (NDEBUG & NDEBUG_NO_WRITE) */

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

	SET_NEXT(cmd, NULL);
	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.
	 */

	/* ++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 (!NCR5380_acquire_dma_irq(instance))
		return SCSI_MLQUEUE_HOST_BUSY;

	local_irq_save(flags);

	/*
	 * 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);
		SET_NEXT(cmd, hostdata->issue_queue);
		hostdata->issue_queue = cmd;
	} else {
		for (tmp = (struct scsi_cmnd *)hostdata->issue_queue;
		     NEXT(tmp); tmp = NEXT(tmp))
			;
		LIST(cmd, tmp);
		SET_NEXT(tmp, cmd);
	}
	local_irq_restore(flags);

	dprintk(NDEBUG_QUEUES, "scsi%d: command added to %s of queue\n", H_NO(cmd),
		  (cmd->cmnd[0] == REQUEST_SENSE) ? "head" : "tail");

	/* Kick off command processing */
	queue_work(hostdata->work_q, &hostdata->main_task);
static inline void maybe_release_dma_irq(struct Scsi_Host *instance)
{
	struct NCR5380_hostdata *hostdata = shost_priv(instance);

	/* Caller does the locking needed to set & test these data atomically */
	if (!hostdata->disconnected_queue &&
	    !hostdata->issue_queue &&
	    !hostdata->connected &&
	    !hostdata->retain_dma_intr)
		NCR5380_release_dma_irq(instance);
}

/**
 * NCR5380_main - NCR state machines
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * 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.
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, main_task);
	struct Scsi_Host *instance = hostdata->host;
	struct scsi_cmnd *tmp, *prev;