linux/drivers/scsi/arm/acornscsi.c

3088 lines
86 KiB
C
Raw Normal View History

/*
* linux/drivers/acorn/scsi/acornscsi.c
*
* Acorn SCSI 3 driver
* By R.M.King.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Abandoned using the Select and Transfer command since there were
* some nasty races between our software and the target devices that
* were not easy to solve, and the device errata had a lot of entries
* for this command, some of them quite nasty...
*
* Changelog:
* 26-Sep-1997 RMK Re-jigged to use the queue module.
* Re-coded state machine to be based on driver
* state not scsi state. Should be easier to debug.
* Added acornscsi_release to clean up properly.
* Updated proc/scsi reporting.
* 05-Oct-1997 RMK Implemented writing to SCSI devices.
* 06-Oct-1997 RMK Corrected small (non-serious) bug with the connect/
* reconnect race condition causing a warning message.
* 12-Oct-1997 RMK Added catch for re-entering interrupt routine.
* 15-Oct-1997 RMK Improved handling of commands.
* 27-Jun-1998 RMK Changed asm/delay.h to linux/delay.h.
* 13-Dec-1998 RMK Better abort code and command handling. Extra state
* transitions added to allow dodgy devices to work.
*/
#define DEBUG_NO_WRITE 1
#define DEBUG_QUEUES 2
#define DEBUG_DMA 4
#define DEBUG_ABORT 8
#define DEBUG_DISCON 16
#define DEBUG_CONNECT 32
#define DEBUG_PHASES 64
#define DEBUG_WRITE 128
#define DEBUG_LINK 256
#define DEBUG_MESSAGES 512
#define DEBUG_RESET 1024
#define DEBUG_ALL (DEBUG_RESET|DEBUG_MESSAGES|DEBUG_LINK|DEBUG_WRITE|\
DEBUG_PHASES|DEBUG_CONNECT|DEBUG_DISCON|DEBUG_ABORT|\
DEBUG_DMA|DEBUG_QUEUES)
/* DRIVER CONFIGURATION
*
* SCSI-II Tagged queue support.
*
* I don't have any SCSI devices that support it, so it is totally untested
* (except to make sure that it doesn't interfere with any non-tagging
* devices). It is not fully implemented either - what happens when a
* tagging device reconnects???
*
* You can tell if you have a device that supports tagged queueing my
* cating (eg) /proc/scsi/acornscsi/0 and see if the SCSI revision is reported
* as '2 TAG'.
*
* Also note that CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE is normally set in the config
* scripts, but disabled here. Once debugged, remove the #undef, otherwise to debug,
* comment out the undef.
*/
#undef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
/*
* SCSI-II Linked command support.
*
* The higher level code doesn't support linked commands yet, and so the option
* is undef'd here.
*/
#undef CONFIG_SCSI_ACORNSCSI_LINK
/*
* SCSI-II Synchronous transfer support.
*
* Tried and tested...
*
* SDTR_SIZE - maximum number of un-acknowledged bytes (0 = off, 12 = max)
* SDTR_PERIOD - period of REQ signal (min=125, max=1020)
* DEFAULT_PERIOD - default REQ period.
*/
#define SDTR_SIZE 12
#define SDTR_PERIOD 125
#define DEFAULT_PERIOD 500
/*
* Debugging information
*
* DEBUG - bit mask from list above
* DEBUG_TARGET - is defined to the target number if you want to debug
* a specific target. [only recon/write/dma].
*/
#define DEBUG (DEBUG_RESET|DEBUG_WRITE|DEBUG_NO_WRITE)
/* only allow writing to SCSI device 0 */
#define NO_WRITE 0xFE
/*#define DEBUG_TARGET 2*/
/*
* Select timeout time (in 10ms units)
*
* This is the timeout used between the start of selection and the WD33C93
* chip deciding that the device isn't responding.
*/
#define TIMEOUT_TIME 10
/*
* Define this if you want to have verbose explaination of SCSI
* status/messages.
*/
#undef CONFIG_ACORNSCSI_CONSTANTS
/*
* Define this if you want to use the on board DMAC [don't remove this option]
* If not set, then use PIO mode (not currently supported).
*/
#define USE_DMAC
/*
* ====================================================================================
*/
#ifdef DEBUG_TARGET
#define DBG(cmd,xxx...) \
if (cmd->device->id == DEBUG_TARGET) { \
xxx; \
}
#else
#define DBG(cmd,xxx...) xxx
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/ioport.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/stringify.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/ecard.h>
#include "../scsi.h"
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_transport_spi.h>
#include "acornscsi.h"
#include "msgqueue.h"
#include "scsi.h"
#include <scsi/scsicam.h>
#define VER_MAJOR 2
#define VER_MINOR 0
#define VER_PATCH 6
#ifndef ABORT_TAG
#define ABORT_TAG 0xd
#else
#error "Yippee! ABORT TAG is now defined! Remove this error!"
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_LINK
#error SCSI2 LINKed commands not supported (yet)!
#endif
#ifdef USE_DMAC
/*
* DMAC setup parameters
*/
#define INIT_DEVCON0 (DEVCON0_RQL|DEVCON0_EXW|DEVCON0_CMP)
#define INIT_DEVCON1 (DEVCON1_BHLD)
#define DMAC_READ (MODECON_READ)
#define DMAC_WRITE (MODECON_WRITE)
#define INIT_SBICDMA (CTRL_DMABURST)
#define scsi_xferred have_data_in
/*
* Size of on-board DMA buffer
*/
#define DMAC_BUFFER_SIZE 65536
#endif
#define STATUS_BUFFER_TO_PRINT 24
unsigned int sdtr_period = SDTR_PERIOD;
unsigned int sdtr_size = SDTR_SIZE;
static void acornscsi_done(AS_Host *host, struct scsi_cmnd **SCpntp,
unsigned int result);
static int acornscsi_reconnect_finish(AS_Host *host);
static void acornscsi_dma_cleanup(AS_Host *host);
static void acornscsi_abortcmd(AS_Host *host, unsigned char tag);
/* ====================================================================================
* Miscellaneous
*/
/* Offsets from MEMC base */
#define SBIC_REGIDX 0x2000
#define SBIC_REGVAL 0x2004
#define DMAC_OFFSET 0x3000
/* Offsets from FAST IOC base */
#define INT_REG 0x2000
#define PAGE_REG 0x3000
static inline void sbic_arm_write(AS_Host *host, unsigned int reg, unsigned int value)
{
writeb(reg, host->base + SBIC_REGIDX);
writeb(value, host->base + SBIC_REGVAL);
}
static inline int sbic_arm_read(AS_Host *host, unsigned int reg)
{
if(reg == SBIC_ASR)
return readl(host->base + SBIC_REGIDX) & 255;
writeb(reg, host->base + SBIC_REGIDX);
return readl(host->base + SBIC_REGVAL) & 255;
}
#define sbic_arm_writenext(host, val) writeb((val), (host)->base + SBIC_REGVAL)
#define sbic_arm_readnext(host) readb((host)->base + SBIC_REGVAL)
#ifdef USE_DMAC
#define dmac_read(host,reg) \
readb((host)->base + DMAC_OFFSET + ((reg) << 2))
#define dmac_write(host,reg,value) \
({ writeb((value), (host)->base + DMAC_OFFSET + ((reg) << 2)); })
#define dmac_clearintr(host) writeb(0, (host)->fast + INT_REG)
static inline unsigned int dmac_address(AS_Host *host)
{
return dmac_read(host, DMAC_TXADRHI) << 16 |
dmac_read(host, DMAC_TXADRMD) << 8 |
dmac_read(host, DMAC_TXADRLO);
}
static
void acornscsi_dumpdma(AS_Host *host, char *where)
{
unsigned int mode, addr, len;
mode = dmac_read(host, DMAC_MODECON);
addr = dmac_address(host);
len = dmac_read(host, DMAC_TXCNTHI) << 8 |
dmac_read(host, DMAC_TXCNTLO);
printk("scsi%d: %s: DMAC %02x @%06x+%04x msk %02x, ",
host->host->host_no, where,
mode, addr, (len + 1) & 0xffff,
dmac_read(host, DMAC_MASKREG));
printk("DMA @%06x, ", host->dma.start_addr);
printk("BH @%p +%04x, ", host->scsi.SCp.ptr,
host->scsi.SCp.this_residual);
printk("DT @+%04x ST @+%04x", host->dma.transferred,
host->scsi.SCp.scsi_xferred);
printk("\n");
}
#endif
static
unsigned long acornscsi_sbic_xfcount(AS_Host *host)
{
unsigned long length;
length = sbic_arm_read(host, SBIC_TRANSCNTH) << 16;
length |= sbic_arm_readnext(host) << 8;
length |= sbic_arm_readnext(host);
return length;
}
static int
acornscsi_sbic_wait(AS_Host *host, int stat_mask, int stat, int timeout, char *msg)
{
int asr;
do {
asr = sbic_arm_read(host, SBIC_ASR);
if ((asr & stat_mask) == stat)
return 0;
udelay(1);
} while (--timeout);
printk("scsi%d: timeout while %s\n", host->host->host_no, msg);
return -1;
}
static
int acornscsi_sbic_issuecmd(AS_Host *host, int command)
{
if (acornscsi_sbic_wait(host, ASR_CIP, 0, 1000, "issuing command"))
return -1;
sbic_arm_write(host, SBIC_CMND, command);
return 0;
}
static void
acornscsi_csdelay(unsigned int cs)
{
unsigned long target_jiffies, flags;
target_jiffies = jiffies + 1 + cs * HZ / 100;
local_save_flags(flags);
local_irq_enable();
while (time_before(jiffies, target_jiffies)) barrier();
local_irq_restore(flags);
}
static
void acornscsi_resetcard(AS_Host *host)
{
unsigned int i, timeout;
/* assert reset line */
host->card.page_reg = 0x80;
writeb(host->card.page_reg, host->fast + PAGE_REG);
/* wait 3 cs. SCSI standard says 25ms. */
acornscsi_csdelay(3);
host->card.page_reg = 0;
writeb(host->card.page_reg, host->fast + PAGE_REG);
/*
* Should get a reset from the card
*/
timeout = 1000;
do {
if (readb(host->fast + INT_REG) & 8)
break;
udelay(1);
} while (--timeout);
if (timeout == 0)
printk("scsi%d: timeout while resetting card\n",
host->host->host_no);
sbic_arm_read(host, SBIC_ASR);
sbic_arm_read(host, SBIC_SSR);
/* setup sbic - WD33C93A */
sbic_arm_write(host, SBIC_OWNID, OWNID_EAF | host->host->this_id);
sbic_arm_write(host, SBIC_CMND, CMND_RESET);
/*
* Command should cause a reset interrupt
*/
timeout = 1000;
do {
if (readb(host->fast + INT_REG) & 8)
break;
udelay(1);
} while (--timeout);
if (timeout == 0)
printk("scsi%d: timeout while resetting card\n",
host->host->host_no);
sbic_arm_read(host, SBIC_ASR);
if (sbic_arm_read(host, SBIC_SSR) != 0x01)
printk(KERN_CRIT "scsi%d: WD33C93A didn't give enhanced reset interrupt\n",
host->host->host_no);
sbic_arm_write(host, SBIC_CTRL, INIT_SBICDMA | CTRL_IDI);
sbic_arm_write(host, SBIC_TIMEOUT, TIMEOUT_TIME);
sbic_arm_write(host, SBIC_SYNCHTRANSFER, SYNCHTRANSFER_2DBA);
sbic_arm_write(host, SBIC_SOURCEID, SOURCEID_ER | SOURCEID_DSP);
host->card.page_reg = 0x40;
writeb(host->card.page_reg, host->fast + PAGE_REG);
/* setup dmac - uPC71071 */
dmac_write(host, DMAC_INIT, 0);
#ifdef USE_DMAC
dmac_write(host, DMAC_INIT, INIT_8BIT);
dmac_write(host, DMAC_CHANNEL, CHANNEL_0);
dmac_write(host, DMAC_DEVCON0, INIT_DEVCON0);
dmac_write(host, DMAC_DEVCON1, INIT_DEVCON1);
#endif
host->SCpnt = NULL;
host->scsi.phase = PHASE_IDLE;
host->scsi.disconnectable = 0;
memset(host->busyluns, 0, sizeof(host->busyluns));
for (i = 0; i < 8; i++) {
host->device[i].sync_state = SYNC_NEGOCIATE;
host->device[i].disconnect_ok = 1;
}
/* wait 25 cs. SCSI standard says 250ms. */
acornscsi_csdelay(25);
}
/*=============================================================================================
* Utility routines (eg. debug)
*/
#ifdef CONFIG_ACORNSCSI_CONSTANTS
static char *acornscsi_interrupttype[] = {
"rst", "suc", "p/a", "3",
"term", "5", "6", "7",
"serv", "9", "a", "b",
"c", "d", "e", "f"
};
static signed char acornscsi_map[] = {
0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 2, -1, -1, -1, -1, 3, -1, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, -1, -1, -1, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, 16, 17, 18, 19, -1, -1, 20, 4, 5, 6, 7, 8, 9, 10, 11,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
21, 22, -1, -1, -1, 23, -1, -1, 4, 5, 6, 7, 8, 9, 10, 11,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
static char *acornscsi_interruptcode[] = {
/* 0 */
"reset - normal mode", /* 00 */
"reset - advanced mode", /* 01 */
/* 2 */
"sel", /* 11 */
"sel+xfer", /* 16 */
"data-out", /* 18 */
"data-in", /* 19 */
"cmd", /* 1A */
"stat", /* 1B */
"??-out", /* 1C */
"??-in", /* 1D */
"msg-out", /* 1E */
"msg-in", /* 1F */
/* 12 */
"/ACK asserted", /* 20 */
"save-data-ptr", /* 21 */
"{re}sel", /* 22 */
/* 15 */
"inv cmd", /* 40 */
"unexpected disconnect", /* 41 */
"sel timeout", /* 42 */
"P err", /* 43 */
"P err+ATN", /* 44 */
"bad status byte", /* 47 */
/* 21 */
"resel, no id", /* 80 */
"resel", /* 81 */
"discon", /* 85 */
};
static
void print_scsi_status(unsigned int ssr)
{
if (acornscsi_map[ssr] != -1)
printk("%s:%s",
acornscsi_interrupttype[(ssr >> 4)],
acornscsi_interruptcode[acornscsi_map[ssr]]);
else
printk("%X:%X", ssr >> 4, ssr & 0x0f);
}
#endif
static
void print_sbic_status(int asr, int ssr, int cmdphase)
{
#ifdef CONFIG_ACORNSCSI_CONSTANTS
printk("sbic: %c%c%c%c%c%c ",
asr & ASR_INT ? 'I' : 'i',
asr & ASR_LCI ? 'L' : 'l',
asr & ASR_BSY ? 'B' : 'b',
asr & ASR_CIP ? 'C' : 'c',
asr & ASR_PE ? 'P' : 'p',
asr & ASR_DBR ? 'D' : 'd');
printk("scsi: ");
print_scsi_status(ssr);
printk(" ph %02X\n", cmdphase);
#else
printk("sbic: %02X scsi: %X:%X ph: %02X\n",
asr, (ssr & 0xf0)>>4, ssr & 0x0f, cmdphase);
#endif
}
static void
acornscsi_dumplogline(AS_Host *host, int target, int line)
{
unsigned long prev;
signed int ptr;
ptr = host->status_ptr[target] - STATUS_BUFFER_TO_PRINT;
if (ptr < 0)
ptr += STATUS_BUFFER_SIZE;
printk("%c: %3s:", target == 8 ? 'H' : '0' + target,
line == 0 ? "ph" : line == 1 ? "ssr" : "int");
prev = host->status[target][ptr].when;
for (; ptr != host->status_ptr[target]; ptr = (ptr + 1) & (STATUS_BUFFER_SIZE - 1)) {
unsigned long time_diff;
if (!host->status[target][ptr].when)
continue;
switch (line) {
case 0:
printk("%c%02X", host->status[target][ptr].irq ? '-' : ' ',
host->status[target][ptr].ph);
break;
case 1:
printk(" %02X", host->status[target][ptr].ssr);
break;
case 2:
time_diff = host->status[target][ptr].when - prev;
prev = host->status[target][ptr].when;
if (time_diff == 0)
printk("==^");
else if (time_diff >= 100)
printk(" ");
else
printk(" %02ld", time_diff);
break;
}
}
printk("\n");
}
static
void acornscsi_dumplog(AS_Host *host, int target)
{
do {
acornscsi_dumplogline(host, target, 0);
acornscsi_dumplogline(host, target, 1);
acornscsi_dumplogline(host, target, 2);
if (target == 8)
break;
target = 8;
} while (1);
}
static
char acornscsi_target(AS_Host *host)
{
if (host->SCpnt)
return '0' + host->SCpnt->device->id;
return 'H';
}
/*
* Prototype: cmdtype_t acornscsi_cmdtype(int command)
* Purpose : differentiate READ from WRITE from other commands
* Params : command - command to interpret
* Returns : CMD_READ - command reads data,
* CMD_WRITE - command writes data,
* CMD_MISC - everything else
*/
static inline
cmdtype_t acornscsi_cmdtype(int command)
{
switch (command) {
case WRITE_6: case WRITE_10: case WRITE_12:
return CMD_WRITE;
case READ_6: case READ_10: case READ_12:
return CMD_READ;
default:
return CMD_MISC;
}
}
/*
* Prototype: int acornscsi_datadirection(int command)
* Purpose : differentiate between commands that have a DATA IN phase
* and a DATA OUT phase
* Params : command - command to interpret
* Returns : DATADIR_OUT - data out phase expected
* DATADIR_IN - data in phase expected
*/
static
datadir_t acornscsi_datadirection(int command)
{
switch (command) {
case CHANGE_DEFINITION: case COMPARE: case COPY:
case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT:
case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER:
case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE:
case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW:
case WRITE_6: case WRITE_10: case WRITE_VERIFY:
case UPDATE_BLOCK: case WRITE_LONG: case WRITE_SAME:
case SEARCH_HIGH_12: case SEARCH_EQUAL_12: case SEARCH_LOW_12:
case WRITE_12: case WRITE_VERIFY_12: case SET_WINDOW:
case MEDIUM_SCAN: case SEND_VOLUME_TAG: case 0xea:
return DATADIR_OUT;
default:
return DATADIR_IN;
}
}
/*
* Purpose : provide values for synchronous transfers with 33C93.
* Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting
* Modified by Russell King for 8MHz WD33C93A
*/
static struct sync_xfer_tbl {
unsigned int period_ns;
unsigned char reg_value;
} sync_xfer_table[] = {
{ 1, 0x20 }, { 249, 0x20 }, { 374, 0x30 },
{ 499, 0x40 }, { 624, 0x50 }, { 749, 0x60 },
{ 874, 0x70 }, { 999, 0x00 }, { 0, 0 }
};
/*
* Prototype: int acornscsi_getperiod(unsigned char syncxfer)
* Purpose : period for the synchronous transfer setting
* Params : syncxfer SYNCXFER register value
* Returns : period in ns.
*/
static
int acornscsi_getperiod(unsigned char syncxfer)
{
int i;
syncxfer &= 0xf0;
if (syncxfer == 0x10)
syncxfer = 0;
for (i = 1; sync_xfer_table[i].period_ns; i++)
if (syncxfer == sync_xfer_table[i].reg_value)
return sync_xfer_table[i].period_ns;
return 0;
}
/*
* Prototype: int round_period(unsigned int period)
* Purpose : return index into above table for a required REQ period
* Params : period - time (ns) for REQ
* Returns : table index
* Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting
*/
static inline
int round_period(unsigned int period)
{
int i;
for (i = 1; sync_xfer_table[i].period_ns; i++) {
if ((period <= sync_xfer_table[i].period_ns) &&
(period > sync_xfer_table[i - 1].period_ns))
return i;
}
return 7;
}
/*
* Prototype: unsigned char calc_sync_xfer(unsigned int period, unsigned int offset)
* Purpose : calculate value for 33c93s SYNC register
* Params : period - time (ns) for REQ
* offset - offset in bytes between REQ/ACK
* Returns : value for SYNC register
* Copyright: Copyright (c) 1996 John Shifflett, GeoLog Consulting
*/
static
unsigned char calc_sync_xfer(unsigned int period, unsigned int offset)
{
return sync_xfer_table[round_period(period)].reg_value |
((offset < SDTR_SIZE) ? offset : SDTR_SIZE);
}
/* ====================================================================================
* Command functions
*/
/*
* Function: acornscsi_kick(AS_Host *host)
* Purpose : kick next command to interface
* Params : host - host to send command to
* Returns : INTR_IDLE if idle, otherwise INTR_PROCESSING
* Notes : interrupts are always disabled!
*/
static
intr_ret_t acornscsi_kick(AS_Host *host)
{
int from_queue = 0;
struct scsi_cmnd *SCpnt;
/* first check to see if a command is waiting to be executed */
SCpnt = host->origSCpnt;
host->origSCpnt = NULL;
/* retrieve next command */
if (!SCpnt) {
SCpnt = queue_remove_exclude(&host->queues.issue, host->busyluns);
if (!SCpnt)
return INTR_IDLE;
from_queue = 1;
}
if (host->scsi.disconnectable && host->SCpnt) {
queue_add_cmd_tail(&host->queues.disconnected, host->SCpnt);
host->scsi.disconnectable = 0;
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
DBG(host->SCpnt, printk("scsi%d.%c: moved command to disconnected queue\n",
host->host->host_no, acornscsi_target(host)));
#endif
host->SCpnt = NULL;
}
/*
* If we have an interrupt pending, then we may have been reselected.
* In this case, we don't want to write to the registers
*/
if (!(sbic_arm_read(host, SBIC_ASR) & (ASR_INT|ASR_BSY|ASR_CIP))) {
sbic_arm_write(host, SBIC_DESTID, SCpnt->device->id);
sbic_arm_write(host, SBIC_CMND, CMND_SELWITHATN);
}
/*
* claim host busy - all of these must happen atomically wrt
* our interrupt routine. Failure means command loss.
*/
host->scsi.phase = PHASE_CONNECTING;
host->SCpnt = SCpnt;
host->scsi.SCp = SCpnt->SCp;
host->dma.xfer_setup = 0;
host->dma.xfer_required = 0;
host->dma.xfer_done = 0;
#if (DEBUG & (DEBUG_ABORT|DEBUG_CONNECT))
DBG(SCpnt,printk("scsi%d.%c: starting cmd %02X\n",
host->host->host_no, '0' + SCpnt->device->id,
SCpnt->cmnd[0]));
#endif
if (from_queue) {
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
/*
* tagged queueing - allocate a new tag to this command
*/
if (SCpnt->device->simple_tags) {
SCpnt->device->current_tag += 1;
if (SCpnt->device->current_tag == 0)
SCpnt->device->current_tag = 1;
SCpnt->tag = SCpnt->device->current_tag;
} else
#endif
set_bit(SCpnt->device->id * 8 + SCpnt->device->lun, host->busyluns);
host->stats.removes += 1;
switch (acornscsi_cmdtype(SCpnt->cmnd[0])) {
case CMD_WRITE:
host->stats.writes += 1;
break;
case CMD_READ:
host->stats.reads += 1;
break;
case CMD_MISC:
host->stats.miscs += 1;
break;
}
}
return INTR_PROCESSING;
}
/*
* Function: void acornscsi_done(AS_Host *host, struct scsi_cmnd **SCpntp, unsigned int result)
* Purpose : complete processing for command
* Params : host - interface that completed
* result - driver byte of result
*/
static void acornscsi_done(AS_Host *host, struct scsi_cmnd **SCpntp,
unsigned int result)
{
struct scsi_cmnd *SCpnt = *SCpntp;
/* clean up */
sbic_arm_write(host, SBIC_SOURCEID, SOURCEID_ER | SOURCEID_DSP);
host->stats.fins += 1;
if (SCpnt) {
*SCpntp = NULL;
acornscsi_dma_cleanup(host);
SCpnt->result = result << 16 | host->scsi.SCp.Message << 8 | host->scsi.SCp.Status;
/*
* In theory, this should not happen. In practice, it seems to.
* Only trigger an error if the device attempts to report all happy
* but with untransferred buffers... If we don't do something, then
* data loss will occur. Should we check SCpnt->underflow here?
* It doesn't appear to be set to something meaningful by the higher
* levels all the time.
*/
if (result == DID_OK) {
int xfer_warn = 0;
if (SCpnt->underflow == 0) {
if (host->scsi.SCp.ptr &&
acornscsi_cmdtype(SCpnt->cmnd[0]) != CMD_MISC)
xfer_warn = 1;
} else {
if (host->scsi.SCp.scsi_xferred < SCpnt->underflow ||
host->scsi.SCp.scsi_xferred != host->dma.transferred)
xfer_warn = 1;
}
/* ANSI standard says: (SCSI-2 Rev 10c Sect 5.6.6)
* Targets which break data transfers into multiple
* connections shall end each successful connection
* (except possibly the last) with a SAVE DATA
* POINTER - DISCONNECT message sequence.
*
* This makes it difficult to ensure that a transfer has
* completed. If we reach the end of a transfer during
* the command, then we can only have finished the transfer.
* therefore, if we seem to have some data remaining, this
* is not a problem.
*/
if (host->dma.xfer_done)
xfer_warn = 0;
if (xfer_warn) {
switch (status_byte(SCpnt->result)) {
case CHECK_CONDITION:
case COMMAND_TERMINATED:
case BUSY:
case QUEUE_FULL:
case RESERVATION_CONFLICT:
break;
default:
printk(KERN_ERR "scsi%d.H: incomplete data transfer detected: result=%08X command=",
host->host->host_no, SCpnt->result);
__scsi_print_command(SCpnt->cmnd);
acornscsi_dumpdma(host, "done");
acornscsi_dumplog(host, SCpnt->device->id);
SCpnt->result &= 0xffff;
SCpnt->result |= DID_ERROR << 16;
}
}
}
if (!SCpnt->scsi_done)
panic("scsi%d.H: null scsi_done function in acornscsi_done", host->host->host_no);
clear_bit(SCpnt->device->id * 8 + SCpnt->device->lun, host->busyluns);
SCpnt->scsi_done(SCpnt);
} else
printk("scsi%d: null command in acornscsi_done", host->host->host_no);
host->scsi.phase = PHASE_IDLE;
}
/* ====================================================================================
* DMA routines
*/
/*
* Purpose : update SCSI Data Pointer
* Notes : this will only be one SG entry or less
*/
static
void acornscsi_data_updateptr(AS_Host *host, struct scsi_pointer *SCp, unsigned int length)
{
SCp->ptr += length;
SCp->this_residual -= length;
if (SCp->this_residual == 0 && next_SCp(SCp) == 0)
host->dma.xfer_done = 1;
}
/*
* Prototype: void acornscsi_data_read(AS_Host *host, char *ptr,
* unsigned int start_addr, unsigned int length)
* Purpose : read data from DMA RAM
* Params : host - host to transfer from
* ptr - DRAM address
* start_addr - host mem address
* length - number of bytes to transfer
* Notes : this will only be one SG entry or less
*/
static
void acornscsi_data_read(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
extern void __acornscsi_in(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
offset = start_addr & ((1 << 12) - 1);
writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
while (len > 0) {
unsigned int this_len;
if (len + offset > (1 << 12))
this_len = (1 << 12) - offset;
else
this_len = len;
__acornscsi_in(host->base + (offset << 1), ptr, this_len);
offset += this_len;
ptr += this_len;
len -= this_len;
if (offset == (1 << 12)) {
offset = 0;
page ++;
writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
}
}
writeb(host->card.page_reg, host->fast + PAGE_REG);
}
/*
* Prototype: void acornscsi_data_write(AS_Host *host, char *ptr,
* unsigned int start_addr, unsigned int length)
* Purpose : write data to DMA RAM
* Params : host - host to transfer from
* ptr - DRAM address
* start_addr - host mem address
* length - number of bytes to transfer
* Notes : this will only be one SG entry or less
*/
static
void acornscsi_data_write(AS_Host *host, char *ptr,
unsigned int start_addr, unsigned int length)
{
extern void __acornscsi_out(void __iomem *, char *buf, int len);
unsigned int page, offset, len = length;
page = (start_addr >> 12);
offset = start_addr & ((1 << 12) - 1);
writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
while (len > 0) {
unsigned int this_len;
if (len + offset > (1 << 12))
this_len = (1 << 12) - offset;
else
this_len = len;
__acornscsi_out(host->base + (offset << 1), ptr, this_len);
offset += this_len;
ptr += this_len;
len -= this_len;
if (offset == (1 << 12)) {
offset = 0;
page ++;
writeb((page & 0x3f) | host->card.page_reg, host->fast + PAGE_REG);
}
}
writeb(host->card.page_reg, host->fast + PAGE_REG);
}
/* =========================================================================================
* On-board DMA routines
*/
#ifdef USE_DMAC
/*
* Prototype: void acornscsi_dmastop(AS_Host *host)
* Purpose : stop all DMA
* Params : host - host on which to stop DMA
* Notes : This is called when leaving DATA IN/OUT phase,
* or when interface is RESET
*/
static inline
void acornscsi_dma_stop(AS_Host *host)
{
dmac_write(host, DMAC_MASKREG, MASK_ON);
dmac_clearintr(host);
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "stop"));
#endif
}
/*
* Function: void acornscsi_dma_setup(AS_Host *host, dmadir_t direction)
* Purpose : setup DMA controller for data transfer
* Params : host - host to setup
* direction - data transfer direction
* Notes : This is called when entering DATA I/O phase, not
* while we're in a DATA I/O phase
*/
static
void acornscsi_dma_setup(AS_Host *host, dmadir_t direction)
{
unsigned int address, length, mode;
host->dma.direction = direction;
dmac_write(host, DMAC_MASKREG, MASK_ON);
if (direction == DMA_OUT) {
#if (DEBUG & DEBUG_NO_WRITE)
if (NO_WRITE & (1 << host->SCpnt->device->id)) {
printk(KERN_CRIT "scsi%d.%c: I can't handle DMA_OUT!\n",
host->host->host_no, acornscsi_target(host));
return;
}
#endif
mode = DMAC_WRITE;
} else
mode = DMAC_READ;
/*
* Allocate some buffer space, limited to half the buffer size
*/
length = min_t(unsigned int, host->scsi.SCp.this_residual, DMAC_BUFFER_SIZE / 2);
if (length) {
host->dma.start_addr = address = host->dma.free_addr;
host->dma.free_addr = (host->dma.free_addr + length) &
(DMAC_BUFFER_SIZE - 1);
/*
* Transfer data to DMA memory
*/
if (direction == DMA_OUT)
acornscsi_data_write(host, host->scsi.SCp.ptr, host->dma.start_addr,
length);
length -= 1;
dmac_write(host, DMAC_TXCNTLO, length);
dmac_write(host, DMAC_TXCNTHI, length >> 8);
dmac_write(host, DMAC_TXADRLO, address);
dmac_write(host, DMAC_TXADRMD, address >> 8);
dmac_write(host, DMAC_TXADRHI, 0);
dmac_write(host, DMAC_MODECON, mode);
dmac_write(host, DMAC_MASKREG, MASK_OFF);
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "strt"));
#endif
host->dma.xfer_setup = 1;
}
}
/*
* Function: void acornscsi_dma_cleanup(AS_Host *host)
* Purpose : ensure that all DMA transfers are up-to-date & host->scsi.SCp is correct
* Params : host - host to finish
* Notes : This is called when a command is:
* terminating, RESTORE_POINTERS, SAVE_POINTERS, DISCONECT
* : This must not return until all transfers are completed.
*/
static
void acornscsi_dma_cleanup(AS_Host *host)
{
dmac_write(host, DMAC_MASKREG, MASK_ON);
dmac_clearintr(host);
/*
* Check for a pending transfer
*/
if (host->dma.xfer_required) {
host->dma.xfer_required = 0;
if (host->dma.direction == DMA_IN)
acornscsi_data_read(host, host->dma.xfer_ptr,
host->dma.xfer_start, host->dma.xfer_length);
}
/*
* Has a transfer been setup?
*/
if (host->dma.xfer_setup) {
unsigned int transferred;
host->dma.xfer_setup = 0;
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "cupi"));
#endif
/*
* Calculate number of bytes transferred from DMA.
*/
transferred = dmac_address(host) - host->dma.start_addr;
host->dma.transferred += transferred;
if (host->dma.direction == DMA_IN)
acornscsi_data_read(host, host->scsi.SCp.ptr,
host->dma.start_addr, transferred);
/*
* Update SCSI pointers
*/
acornscsi_data_updateptr(host, &host->scsi.SCp, transferred);
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "cupo"));
#endif
}
}
/*
* Function: void acornscsi_dmacintr(AS_Host *host)
* Purpose : handle interrupts from DMAC device
* Params : host - host to process
* Notes : If reading, we schedule the read to main memory &
* allow the transfer to continue.
* : If writing, we fill the onboard DMA memory from main
* memory.
* : Called whenever DMAC finished it's current transfer.
*/
static
void acornscsi_dma_intr(AS_Host *host)
{
unsigned int address, length, transferred;
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "inti"));
#endif
dmac_write(host, DMAC_MASKREG, MASK_ON);
dmac_clearintr(host);
/*
* Calculate amount transferred via DMA
*/
transferred = dmac_address(host) - host->dma.start_addr;
host->dma.transferred += transferred;
/*
* Schedule DMA transfer off board
*/
if (host->dma.direction == DMA_IN) {
host->dma.xfer_start = host->dma.start_addr;
host->dma.xfer_length = transferred;
host->dma.xfer_ptr = host->scsi.SCp.ptr;
host->dma.xfer_required = 1;
}
acornscsi_data_updateptr(host, &host->scsi.SCp, transferred);
/*
* Allocate some buffer space, limited to half the on-board RAM size
*/
length = min_t(unsigned int, host->scsi.SCp.this_residual, DMAC_BUFFER_SIZE / 2);
if (length) {
host->dma.start_addr = address = host->dma.free_addr;
host->dma.free_addr = (host->dma.free_addr + length) &
(DMAC_BUFFER_SIZE - 1);
/*
* Transfer data to DMA memory
*/
if (host->dma.direction == DMA_OUT)
acornscsi_data_write(host, host->scsi.SCp.ptr, host->dma.start_addr,
length);
length -= 1;
dmac_write(host, DMAC_TXCNTLO, length);
dmac_write(host, DMAC_TXCNTHI, length >> 8);
dmac_write(host, DMAC_TXADRLO, address);
dmac_write(host, DMAC_TXADRMD, address >> 8);
dmac_write(host, DMAC_TXADRHI, 0);
dmac_write(host, DMAC_MASKREG, MASK_OFF);
#if (DEBUG & DEBUG_DMA)
DBG(host->SCpnt, acornscsi_dumpdma(host, "into"));
#endif
} else {
host->dma.xfer_setup = 0;
#if 0
/*
* If the interface still wants more, then this is an error.
* We give it another byte, but we also attempt to raise an
* attention condition. We continue giving one byte until
* the device recognises the attention.
*/
if (dmac_read(host, DMAC_STATUS) & STATUS_RQ0) {
acornscsi_abortcmd(host, host->SCpnt->tag);
dmac_write(host, DMAC_TXCNTLO, 0);
dmac_write(host, DMAC_TXCNTHI, 0);
dmac_write(host, DMAC_TXADRLO, 0);
dmac_write(host, DMAC_TXADRMD, 0);
dmac_write(host, DMAC_TXADRHI, 0);
dmac_write(host, DMAC_MASKREG, MASK_OFF);
}
#endif
}
}
/*
* Function: void acornscsi_dma_xfer(AS_Host *host)
* Purpose : transfer data between AcornSCSI and memory
* Params : host - host to process
*/
static
void acornscsi_dma_xfer(AS_Host *host)
{
host->dma.xfer_required = 0;
if (host->dma.direction == DMA_IN)
acornscsi_data_read(host, host->dma.xfer_ptr,
host->dma.xfer_start, host->dma.xfer_length);
}
/*
* Function: void acornscsi_dma_adjust(AS_Host *host)
* Purpose : adjust DMA pointers & count for bytes transferred to
* SBIC but not SCSI bus.
* Params : host - host to adjust DMA count for
*/
static
void acornscsi_dma_adjust(AS_Host *host)
{
if (host->dma.xfer_setup) {
signed long transferred;
#if (DEBUG & (DEBUG_DMA|DEBUG_WRITE))
DBG(host->SCpnt, acornscsi_dumpdma(host, "adji"));
#endif
/*
* Calculate correct DMA address - DMA is ahead of SCSI bus while
* writing.
* host->scsi.SCp.scsi_xferred is the number of bytes
* actually transferred to/from the SCSI bus.
* host->dma.transferred is the number of bytes transferred
* over DMA since host->dma.start_addr was last set.
*
* real_dma_addr = host->dma.start_addr + host->scsi.SCp.scsi_xferred
* - host->dma.transferred
*/
transferred = host->scsi.SCp.scsi_xferred - host->dma.transferred;
if (transferred < 0)
printk("scsi%d.%c: Ack! DMA write correction %ld < 0!\n",
host->host->host_no, acornscsi_target(host), transferred);
else if (transferred == 0)
host->dma.xfer_setup = 0;
else {
transferred += host->dma.start_addr;
dmac_write(host, DMAC_TXADRLO, transferred);
dmac_write(host, DMAC_TXADRMD, transferred >> 8);
dmac_write(host, DMAC_TXADRHI, transferred >> 16);
#if (DEBUG & (DEBUG_DMA|DEBUG_WRITE))
DBG(host->SCpnt, acornscsi_dumpdma(host, "adjo"));
#endif
}
}
}
#endif
/* =========================================================================================
* Data I/O
*/
static int
acornscsi_write_pio(AS_Host *host, char *bytes, int *ptr, int len, unsigned int max_timeout)
{
unsigned int asr, timeout = max_timeout;
int my_ptr = *ptr;
while (my_ptr < len) {
asr = sbic_arm_read(host, SBIC_ASR);
if (asr & ASR_DBR) {
timeout = max_timeout;
sbic_arm_write(host, SBIC_DATA, bytes[my_ptr++]);
} else if (asr & ASR_INT)
break;
else if (--timeout == 0)
break;
udelay(1);
}
*ptr = my_ptr;
return (timeout == 0) ? -1 : 0;
}
/*
* Function: void acornscsi_sendcommand(AS_Host *host)
* Purpose : send a command to a target
* Params : host - host which is connected to target
*/
static void
acornscsi_sendcommand(AS_Host *host)
{
struct scsi_cmnd *SCpnt = host->SCpnt;
sbic_arm_write(host, SBIC_TRANSCNTH, 0);
sbic_arm_writenext(host, 0);
sbic_arm_writenext(host, SCpnt->cmd_len - host->scsi.SCp.sent_command);
acornscsi_sbic_issuecmd(host, CMND_XFERINFO);
if (acornscsi_write_pio(host, SCpnt->cmnd,
(int *)&host->scsi.SCp.sent_command, SCpnt->cmd_len, 1000000))
printk("scsi%d: timeout while sending command\n", host->host->host_no);
host->scsi.phase = PHASE_COMMAND;
}
static
void acornscsi_sendmessage(AS_Host *host)
{
unsigned int message_length = msgqueue_msglength(&host->scsi.msgs);
unsigned int msgnr;
struct message *msg;
#if (DEBUG & DEBUG_MESSAGES)
printk("scsi%d.%c: sending message ",
host->host->host_no, acornscsi_target(host));
#endif
switch (message_length) {
case 0:
acornscsi_sbic_issuecmd(host, CMND_XFERINFO | CMND_SBT);
acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "sending message 1");
sbic_arm_write(host, SBIC_DATA, NOP);
host->scsi.last_message = NOP;
#if (DEBUG & DEBUG_MESSAGES)
printk("NOP");
#endif
break;
case 1:
acornscsi_sbic_issuecmd(host, CMND_XFERINFO | CMND_SBT);
msg = msgqueue_getmsg(&host->scsi.msgs, 0);
acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "sending message 2");
sbic_arm_write(host, SBIC_DATA, msg->msg[0]);
host->scsi.last_message = msg->msg[0];
#if (DEBUG & DEBUG_MESSAGES)
spi_print_msg(msg->msg);
#endif
break;
default:
/*
* ANSI standard says: (SCSI-2 Rev 10c Sect 5.6.14)
* 'When a target sends this (MESSAGE_REJECT) message, it
* shall change to MESSAGE IN phase and send this message
* prior to requesting additional message bytes from the
* initiator. This provides an interlock so that the
* initiator can determine which message byte is rejected.
*/
sbic_arm_write(host, SBIC_TRANSCNTH, 0);
sbic_arm_writenext(host, 0);
sbic_arm_writenext(host, message_length);
acornscsi_sbic_issuecmd(host, CMND_XFERINFO);
msgnr = 0;
while ((msg = msgqueue_getmsg(&host->scsi.msgs, msgnr++)) != NULL) {
unsigned int i;
#if (DEBUG & DEBUG_MESSAGES)
spi_print_msg(msg);
#endif
i = 0;
if (acornscsi_write_pio(host, msg->msg, &i, msg->length, 1000000))
printk("scsi%d: timeout while sending message\n", host->host->host_no);
host->scsi.last_message = msg->msg[0];
if (msg->msg[0] == EXTENDED_MESSAGE)
host->scsi.last_message |= msg->msg[2] << 8;
if (i != msg->length)
break;
}
break;
}
#if (DEBUG & DEBUG_MESSAGES)
printk("\n");
#endif
}
/*
* Function: void acornscsi_readstatusbyte(AS_Host *host)
* Purpose : Read status byte from connected target
* Params : host - host connected to target
*/
static
void acornscsi_readstatusbyte(AS_Host *host)
{
acornscsi_sbic_issuecmd(host, CMND_XFERINFO|CMND_SBT);
acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "reading status byte");
host->scsi.SCp.Status = sbic_arm_read(host, SBIC_DATA);
}
/*
* Function: unsigned char acornscsi_readmessagebyte(AS_Host *host)
* Purpose : Read one message byte from connected target
* Params : host - host connected to target
*/
static
unsigned char acornscsi_readmessagebyte(AS_Host *host)
{
unsigned char message;
acornscsi_sbic_issuecmd(host, CMND_XFERINFO | CMND_SBT);
acornscsi_sbic_wait(host, ASR_DBR, ASR_DBR, 1000, "for message byte");
message = sbic_arm_read(host, SBIC_DATA);
/* wait for MSGIN-XFER-PAUSED */
acornscsi_sbic_wait(host, ASR_INT, ASR_INT, 1000, "for interrupt after message byte");
sbic_arm_read(host, SBIC_SSR);
return message;
}
/*
* Function: void acornscsi_message(AS_Host *host)
* Purpose : Read complete message from connected target & action message
* Params : host - host connected to target
*/
static
void acornscsi_message(AS_Host *host)
{
unsigned char message[16];
unsigned int msgidx = 0, msglen = 1;
do {
message[msgidx] = acornscsi_readmessagebyte(host);
switch (msgidx) {
case 0:
if (message[0] == EXTENDED_MESSAGE ||
(message[0] >= 0x20 && message[0] <= 0x2f))
msglen = 2;
break;
case 1:
if (message[0] == EXTENDED_MESSAGE)
msglen += message[msgidx];
break;
}
msgidx += 1;
if (msgidx < msglen) {
acornscsi_sbic_issuecmd(host, CMND_NEGATEACK);
/* wait for next msg-in */
acornscsi_sbic_wait(host, ASR_INT, ASR_INT, 1000, "for interrupt after negate ack");
sbic_arm_read(host, SBIC_SSR);
}
} while (msgidx < msglen);
#if (DEBUG & DEBUG_MESSAGES)
printk("scsi%d.%c: message in: ",
host->host->host_no, acornscsi_target(host));
spi_print_msg(message);
printk("\n");
#endif
if (host->scsi.phase == PHASE_RECONNECTED) {
/*
* ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.17)
* 'Whenever a target reconnects to an initiator to continue
* a tagged I/O process, the SIMPLE QUEUE TAG message shall
* be sent immediately following the IDENTIFY message...'
*/
if (message[0] == SIMPLE_QUEUE_TAG)
host->scsi.reconnected.tag = message[1];
if (acornscsi_reconnect_finish(host))
host->scsi.phase = PHASE_MSGIN;
}
switch (message[0]) {
case ABORT:
case ABORT_TAG:
case COMMAND_COMPLETE:
if (host->scsi.phase != PHASE_STATUSIN) {
printk(KERN_ERR "scsi%d.%c: command complete following non-status in phase?\n",
host->host->host_no, acornscsi_target(host));
acornscsi_dumplog(host, host->SCpnt->device->id);
}
host->scsi.phase = PHASE_DONE;
host->scsi.SCp.Message = message[0];
break;
case SAVE_POINTERS:
/*
* ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.20)
* 'The SAVE DATA POINTER message is sent from a target to
* direct the initiator to copy the active data pointer to
* the saved data pointer for the current I/O process.
*/
acornscsi_dma_cleanup(host);
host->SCpnt->SCp = host->scsi.SCp;
host->SCpnt->SCp.sent_command = 0;
host->scsi.phase = PHASE_MSGIN;
break;
case RESTORE_POINTERS:
/*
* ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.19)
* 'The RESTORE POINTERS message is sent from a target to
* direct the initiator to copy the most recently saved
* command, data, and status pointers for the I/O process
* to the corresponding active pointers. The command and
* status pointers shall be restored to the beginning of
* the present command and status areas.'
*/
acornscsi_dma_cleanup(host);
host->scsi.SCp = host->SCpnt->SCp;
host->scsi.phase = PHASE_MSGIN;
break;
case DISCONNECT:
/*
* ANSI standard says: (Section SCSI-2 Rev. 10c Sect 6.4.2)
* 'On those occasions when an error or exception condition occurs
* and the target elects to repeat the information transfer, the
* target may repeat the transfer either issuing a RESTORE POINTERS
* message or by disconnecting without issuing a SAVE POINTERS
* message. When reconnection is completed, the most recent
* saved pointer values are restored.'
*/
acornscsi_dma_cleanup(host);
host->scsi.phase = PHASE_DISCONNECT;
break;
case MESSAGE_REJECT:
#if 0 /* this isn't needed any more */
/*
* If we were negociating sync transfer, we don't yet know if
* this REJECT is for the sync transfer or for the tagged queue/wide
* transfer. Re-initiate sync transfer negociation now, and if
* we got a REJECT in response to SDTR, then it'll be set to DONE.
*/
if (host->device[host->SCpnt->device->id].sync_state == SYNC_SENT_REQUEST)
host->device[host->SCpnt->device->id].sync_state = SYNC_NEGOCIATE;
#endif
/*
* If we have any messages waiting to go out, then assert ATN now
*/
if (msgqueue_msglength(&host->scsi.msgs))
acornscsi_sbic_issuecmd(host, CMND_ASSERTATN);
switch (host->scsi.last_message) {
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
case HEAD_OF_QUEUE_TAG:
case ORDERED_QUEUE_TAG:
case SIMPLE_QUEUE_TAG:
/*
* ANSI standard says: (Section SCSI-2 Rev. 10c Sect 5.6.17)
* If a target does not implement tagged queuing and a queue tag
* message is received, it shall respond with a MESSAGE REJECT
* message and accept the I/O process as if it were untagged.
*/
printk(KERN_NOTICE "scsi%d.%c: disabling tagged queueing\n",
host->host->host_no, acornscsi_target(host));
host->SCpnt->device->simple_tags = 0;
set_bit(host->SCpnt->device->id * 8 + host->SCpnt->device->lun, host->busyluns);
break;
#endif
case EXTENDED_MESSAGE | (EXTENDED_SDTR << 8):
/*
* Target can't handle synchronous transfers
*/
printk(KERN_NOTICE "scsi%d.%c: Using asynchronous transfer\n",
host->host->host_no, acornscsi_target(host));
host->device[host->SCpnt->device->id].sync_xfer = SYNCHTRANSFER_2DBA;
host->device[host->SCpnt->device->id].sync_state = SYNC_ASYNCHRONOUS;
sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].sync_xfer);
break;
default:
break;
}
break;
case QUEUE_FULL:
/* TODO: target queue is full */
break;
case SIMPLE_QUEUE_TAG:
/* tag queue reconnect... message[1] = queue tag. Print something to indicate something happened! */
printk("scsi%d.%c: reconnect queue tag %02X\n",
host->host->host_no, acornscsi_target(host),
message[1]);
break;
case EXTENDED_MESSAGE:
switch (message[2]) {
#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
case EXTENDED_SDTR:
if (host->device[host->SCpnt->device->id].sync_state == SYNC_SENT_REQUEST) {
/*
* We requested synchronous transfers. This isn't quite right...
* We can only say if this succeeded if we proceed on to execute the
* command from this message. If we get a MESSAGE PARITY ERROR,
* and the target retries fail, then we fallback to asynchronous mode
*/
host->device[host->SCpnt->device->id].sync_state = SYNC_COMPLETED;
printk(KERN_NOTICE "scsi%d.%c: Using synchronous transfer, offset %d, %d ns\n",
host->host->host_no, acornscsi_target(host),
message[4], message[3] * 4);
host->device[host->SCpnt->device->id].sync_xfer =
calc_sync_xfer(message[3] * 4, message[4]);
} else {
unsigned char period, length;
/*
* Target requested synchronous transfers. The agreement is only
* to be in operation AFTER the target leaves message out phase.
*/
acornscsi_sbic_issuecmd(host, CMND_ASSERTATN);
period = max_t(unsigned int, message[3], sdtr_period / 4);
length = min_t(unsigned int, message[4], sdtr_size);
msgqueue_addmsg(&host->scsi.msgs, 5, EXTENDED_MESSAGE, 3,
EXTENDED_SDTR, period, length);
host->device[host->SCpnt->device->id].sync_xfer =
calc_sync_xfer(period * 4, length);
}
sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].sync_xfer);
break;
#else
/* We do not accept synchronous transfers. Respond with a
* MESSAGE_REJECT.
*/
#endif
case EXTENDED_WDTR:
/* The WD33C93A is only 8-bit. We respond with a MESSAGE_REJECT
* to a wide data transfer request.
*/
default:
acornscsi_sbic_issuecmd(host, CMND_ASSERTATN);
msgqueue_flush(&host->scsi.msgs);
msgqueue_addmsg(&host->scsi.msgs, 1, MESSAGE_REJECT);
break;
}
break;
#ifdef CONFIG_SCSI_ACORNSCSI_LINK
case LINKED_CMD_COMPLETE:
case LINKED_FLG_CMD_COMPLETE:
/*
* We don't support linked commands yet
*/
if (0) {
#if (DEBUG & DEBUG_LINK)
printk("scsi%d.%c: lun %d tag %d linked command complete\n",
host->host->host_no, acornscsi_target(host), host->SCpnt->tag);
#endif
/*
* A linked command should only terminate with one of these messages
* if there are more linked commands available.
*/
if (!host->SCpnt->next_link) {
printk(KERN_WARNING "scsi%d.%c: lun %d tag %d linked command complete, but no next_link\n",
instance->host_no, acornscsi_target(host), host->SCpnt->tag);
acornscsi_sbic_issuecmd(host, CMND_ASSERTATN);
msgqueue_addmsg(&host->scsi.msgs, 1, ABORT);
} else {
struct scsi_cmnd *SCpnt = host->SCpnt;
acornscsi_dma_cleanup(host);
host->SCpnt = host->SCpnt->next_link;
host->SCpnt->tag = SCpnt->tag;
SCpnt->result = DID_OK | host->scsi.SCp.Message << 8 | host->Scsi.SCp.Status;
SCpnt->done(SCpnt);
/* initialise host->SCpnt->SCp */
}
break;
}
#endif
default: /* reject message */
printk(KERN_ERR "scsi%d.%c: unrecognised message %02X, rejecting\n",
host->host->host_no, acornscsi_target(host),
message[0]);
acornscsi_sbic_issuecmd(host, CMND_ASSERTATN);
msgqueue_flush(&host->scsi.msgs);
msgqueue_addmsg(&host->scsi.msgs, 1, MESSAGE_REJECT);
host->scsi.phase = PHASE_MSGIN;
break;
}
acornscsi_sbic_issuecmd(host, CMND_NEGATEACK);
}
/*
* Function: int acornscsi_buildmessages(AS_Host *host)
* Purpose : build the connection messages for a host
* Params : host - host to add messages to
*/
static
void acornscsi_buildmessages(AS_Host *host)
{
#if 0
/* does the device need resetting? */
if (cmd_reset) {
msgqueue_addmsg(&host->scsi.msgs, 1, BUS_DEVICE_RESET);
return;
}
#endif
msgqueue_addmsg(&host->scsi.msgs, 1,
IDENTIFY(host->device[host->SCpnt->device->id].disconnect_ok,
host->SCpnt->device->lun));
#if 0
/* does the device need the current command aborted */
if (cmd_aborted) {
acornscsi_abortcmd(host->SCpnt->tag);
return;
}
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
if (host->SCpnt->tag) {
unsigned int tag_type;
if (host->SCpnt->cmnd[0] == REQUEST_SENSE ||
host->SCpnt->cmnd[0] == TEST_UNIT_READY ||
host->SCpnt->cmnd[0] == INQUIRY)
tag_type = HEAD_OF_QUEUE_TAG;
else
tag_type = SIMPLE_QUEUE_TAG;
msgqueue_addmsg(&host->scsi.msgs, 2, tag_type, host->SCpnt->tag);
}
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
if (host->device[host->SCpnt->device->id].sync_state == SYNC_NEGOCIATE) {
host->device[host->SCpnt->device->id].sync_state = SYNC_SENT_REQUEST;
msgqueue_addmsg(&host->scsi.msgs, 5,
EXTENDED_MESSAGE, 3, EXTENDED_SDTR,
sdtr_period / 4, sdtr_size);
}
#endif
}
/*
* Function: int acornscsi_starttransfer(AS_Host *host)
* Purpose : transfer data to/from connected target
* Params : host - host to which target is connected
* Returns : 0 if failure
*/
static
int acornscsi_starttransfer(AS_Host *host)
{
int residual;
if (!host->scsi.SCp.ptr /*&& host->scsi.SCp.this_residual*/) {
printk(KERN_ERR "scsi%d.%c: null buffer passed to acornscsi_starttransfer\n",
host->host->host_no, acornscsi_target(host));
return 0;
}
residual = scsi_bufflen(host->SCpnt) - host->scsi.SCp.scsi_xferred;
sbic_arm_write(host, SBIC_SYNCHTRANSFER, host->device[host->SCpnt->device->id].sync_xfer);
sbic_arm_writenext(host, residual >> 16);
sbic_arm_writenext(host, residual >> 8);
sbic_arm_writenext(host, residual);
acornscsi_sbic_issuecmd(host, CMND_XFERINFO);
return 1;
}
/* =========================================================================================
* Connection & Disconnection
*/
/*
* Function : acornscsi_reconnect(AS_Host *host)
* Purpose : reconnect a previously disconnected command
* Params : host - host specific data
* Remarks : SCSI spec says:
* 'The set of active pointers is restored from the set
* of saved pointers upon reconnection of the I/O process'
*/
static
int acornscsi_reconnect(AS_Host *host)
{
unsigned int target, lun, ok = 0;
target = sbic_arm_read(host, SBIC_SOURCEID);
if (!(target & 8))
printk(KERN_ERR "scsi%d: invalid source id after reselection "
"- device fault?\n",
host->host->host_no);
target &= 7;
if (host->SCpnt && !host->scsi.disconnectable) {
printk(KERN_ERR "scsi%d.%d: reconnected while command in "
"progress to target %d?\n",
host->host->host_no, target, host->SCpnt->device->id);
host->SCpnt = NULL;
}
lun = sbic_arm_read(host, SBIC_DATA) & 7;
host->scsi.reconnected.target = target;
host->scsi.reconnected.lun = lun;
host->scsi.reconnected.tag = 0;
if (host->scsi.disconnectable && host->SCpnt &&
host->SCpnt->device->id == target && host->SCpnt->device->lun == lun)
ok = 1;
if (!ok && queue_probetgtlun(&host->queues.disconnected, target, lun))
ok = 1;
ADD_STATUS(target, 0x81, host->scsi.phase, 0);
if (ok) {
host->scsi.phase = PHASE_RECONNECTED;
} else {
/* this doesn't seem to work */
printk(KERN_ERR "scsi%d.%c: reselected with no command "
"to reconnect with\n",
host->host->host_no, '0' + target);
acornscsi_dumplog(host, target);
acornscsi_abortcmd(host, 0);
if (host->SCpnt) {
queue_add_cmd_tail(&host->queues.disconnected, host->SCpnt);
host->SCpnt = NULL;
}
}
acornscsi_sbic_issuecmd(host, CMND_NEGATEACK);
return !ok;
}
/*
* Function: int acornscsi_reconect_finish(AS_Host *host)
* Purpose : finish reconnecting a command
* Params : host - host to complete
* Returns : 0 if failed
*/
static
int acornscsi_reconnect_finish(AS_Host *host)
{
if (host->scsi.disconnectable && host->SCpnt) {
host->scsi.disconnectable = 0;
if (host->SCpnt->device->id == host->scsi.reconnected.target &&
host->SCpnt->device->lun == host->scsi.reconnected.lun &&
host->SCpnt->tag == host->scsi.reconnected.tag) {
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
DBG(host->SCpnt, printk("scsi%d.%c: reconnected",
host->host->host_no, acornscsi_target(host)));
#endif
} else {
queue_add_cmd_tail(&host->queues.disconnected, host->SCpnt);
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
DBG(host->SCpnt, printk("scsi%d.%c: had to move command "
"to disconnected queue\n",
host->host->host_no, acornscsi_target(host)));
#endif
host->SCpnt = NULL;
}
}
if (!host->SCpnt) {
host->SCpnt = queue_remove_tgtluntag(&host->queues.disconnected,
host->scsi.reconnected.target,
host->scsi.reconnected.lun,
host->scsi.reconnected.tag);
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
DBG(host->SCpnt, printk("scsi%d.%c: had to get command",
host->host->host_no, acornscsi_target(host)));
#endif
}
if (!host->SCpnt)
acornscsi_abortcmd(host, host->scsi.reconnected.tag);
else {
/*
* Restore data pointer from SAVED pointers.
*/
host->scsi.SCp = host->SCpnt->SCp;
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
printk(", data pointers: [%p, %X]",
host->scsi.SCp.ptr, host->scsi.SCp.this_residual);
#endif
}
#if (DEBUG & (DEBUG_QUEUES|DEBUG_DISCON))
printk("\n");
#endif
host->dma.transferred = host->scsi.SCp.scsi_xferred;
return host->SCpnt != NULL;
}
/*
* Function: void acornscsi_disconnect_unexpected(AS_Host *host)
* Purpose : handle an unexpected disconnect
* Params : host - host on which disconnect occurred
*/
static
void acornscsi_disconnect_unexpected(AS_Host *host)
{
printk(KERN_ERR "scsi%d.%c: unexpected disconnect\n",
host->host->host_no, acornscsi_target(host));
#if (DEBUG & DEBUG_ABORT)
acornscsi_dumplog(host, 8);
#endif
acornscsi_done(host, &host->SCpnt, DID_ERROR);
}
/*
* Function: void acornscsi_abortcmd(AS_host *host, unsigned char tag)
* Purpose : abort a currently executing command
* Params : host - host with connected command to abort
* tag - tag to abort
*/
static
void acornscsi_abortcmd(AS_Host *host, unsigned char tag)
{
host->scsi.phase = PHASE_ABORTED;
sbic_arm_write(host, SBIC_CMND, CMND_ASSERTATN);
msgqueue_flush(&host->scsi.msgs);
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
if (tag)
msgqueue_addmsg(&host->scsi.msgs, 2, ABORT_TAG, tag);
else
#endif
msgqueue_addmsg(&host->scsi.msgs, 1, ABORT);
}
/* ==========================================================================================
* Interrupt routines.
*/
/*
* Function: int acornscsi_sbicintr(AS_Host *host)
* Purpose : handle interrupts from SCSI device
* Params : host - host to process
* Returns : INTR_PROCESS if expecting another SBIC interrupt
* INTR_IDLE if no interrupt
* INTR_NEXT_COMMAND if we have finished processing the command
*/
static
intr_ret_t acornscsi_sbicintr(AS_Host *host, int in_irq)
{
unsigned int asr, ssr;
asr = sbic_arm_read(host, SBIC_ASR);
if (!(asr & ASR_INT))
return INTR_IDLE;
ssr = sbic_arm_read(host, SBIC_SSR);
#if (DEBUG & DEBUG_PHASES)
print_sbic_status(asr, ssr, host->scsi.phase);
#endif
ADD_STATUS(8, ssr, host->scsi.phase, in_irq);
if (host->SCpnt && !host->scsi.disconnectable)
ADD_STATUS(host->SCpnt->device->id, ssr, host->scsi.phase, in_irq);
switch (ssr) {
case 0x00: /* reset state - not advanced */
printk(KERN_ERR "scsi%d: reset in standard mode but wanted advanced mode.\n",
host->host->host_no);
/* setup sbic - WD33C93A */
sbic_arm_write(host, SBIC_OWNID, OWNID_EAF | host->host->this_id);
sbic_arm_write(host, SBIC_CMND, CMND_RESET);
return INTR_IDLE;
case 0x01: /* reset state - advanced */
sbic_arm_write(host, SBIC_CTRL, INIT_SBICDMA | CTRL_IDI);
sbic_arm_write(host, SBIC_TIMEOUT, TIMEOUT_TIME);
sbic_arm_write(host, SBIC_SYNCHTRANSFER, SYNCHTRANSFER_2DBA);
sbic_arm_write(host, SBIC_SOURCEID, SOURCEID_ER | SOURCEID_DSP);
msgqueue_flush(&host->scsi.msgs);
return INTR_IDLE;
case 0x41: /* unexpected disconnect aborted command */
acornscsi_disconnect_unexpected(host);
return INTR_NEXT_COMMAND;
}
switch (host->scsi.phase) {
case PHASE_CONNECTING: /* STATE: command removed from issue queue */
switch (ssr) {
case 0x11: /* -> PHASE_CONNECTED */
/* BUS FREE -> SELECTION */
host->scsi.phase = PHASE_CONNECTED;
msgqueue_flush(&host->scsi.msgs);
host->dma.transferred = host->scsi.SCp.scsi_xferred;
/* 33C93 gives next interrupt indicating bus phase */
asr = sbic_arm_read(host, SBIC_ASR);
if (!(asr & ASR_INT))
break;
ssr = sbic_arm_read(host, SBIC_SSR);
ADD_STATUS(8, ssr, host->scsi.phase, 1);
ADD_STATUS(host->SCpnt->device->id, ssr, host->scsi.phase, 1);
goto connected;
case 0x42: /* select timed out */
/* -> PHASE_IDLE */
acornscsi_done(host, &host->SCpnt, DID_NO_CONNECT);
return INTR_NEXT_COMMAND;
case 0x81: /* -> PHASE_RECONNECTED or PHASE_ABORTED */
/* BUS FREE -> RESELECTION */
host->origSCpnt = host->SCpnt;
host->SCpnt = NULL;
msgqueue_flush(&host->scsi.msgs);
acornscsi_reconnect(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_CONNECTING, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
acornscsi_abortcmd(host, host->SCpnt->tag);
}
return INTR_PROCESSING;
connected:
case PHASE_CONNECTED: /* STATE: device selected ok */
switch (ssr) {
#ifdef NONSTANDARD
case 0x8a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
/* SELECTION -> COMMAND */
acornscsi_sendcommand(host);
break;
case 0x8b: /* -> PHASE_STATUS */
/* SELECTION -> STATUS */
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
#endif
case 0x8e: /* -> PHASE_MSGOUT */
/* SELECTION ->MESSAGE OUT */
host->scsi.phase = PHASE_MSGOUT;
acornscsi_buildmessages(host);
acornscsi_sendmessage(host);
break;
/* these should not happen */
case 0x85: /* target disconnected */
acornscsi_done(host, &host->SCpnt, DID_ERROR);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_CONNECTED, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
acornscsi_abortcmd(host, host->SCpnt->tag);
}
return INTR_PROCESSING;
case PHASE_MSGOUT: /* STATE: connected & sent IDENTIFY message */
/*
* SCSI standard says that MESSAGE OUT phases can be followed by a
* DATA phase, STATUS phase, MESSAGE IN phase or COMMAND phase
*/
switch (ssr) {
case 0x8a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
case 0x1a: /* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
/* MESSAGE OUT -> COMMAND */
acornscsi_sendcommand(host);
break;
case 0x8b: /* -> PHASE_STATUS */
case 0x1b: /* -> PHASE_STATUS */
/* MESSAGE OUT -> STATUS */
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
case 0x8e: /* -> PHASE_MSGOUT */
/* MESSAGE_OUT(MESSAGE_IN) ->MESSAGE OUT */
acornscsi_sendmessage(host);
break;
case 0x4f: /* -> PHASE_MSGIN, PHASE_DISCONNECT */
case 0x1f: /* -> PHASE_MSGIN, PHASE_DISCONNECT */
/* MESSAGE OUT -> MESSAGE IN */
acornscsi_message(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_MSGOUT, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_COMMAND: /* STATE: connected & command sent */
switch (ssr) {
case 0x18: /* -> PHASE_DATAOUT */
/* COMMAND -> DATA OUT */
if (host->scsi.SCp.sent_command != host->SCpnt->cmd_len)
acornscsi_abortcmd(host, host->SCpnt->tag);
acornscsi_dma_setup(host, DMA_OUT);
if (!acornscsi_starttransfer(host))
acornscsi_abortcmd(host, host->SCpnt->tag);
host->scsi.phase = PHASE_DATAOUT;
return INTR_IDLE;
case 0x19: /* -> PHASE_DATAIN */
/* COMMAND -> DATA IN */
if (host->scsi.SCp.sent_command != host->SCpnt->cmd_len)
acornscsi_abortcmd(host, host->SCpnt->tag);
acornscsi_dma_setup(host, DMA_IN);
if (!acornscsi_starttransfer(host))
acornscsi_abortcmd(host, host->SCpnt->tag);
host->scsi.phase = PHASE_DATAIN;
return INTR_IDLE;
case 0x1b: /* -> PHASE_STATUS */
/* COMMAND -> STATUS */
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
case 0x1e: /* -> PHASE_MSGOUT */
/* COMMAND -> MESSAGE OUT */
acornscsi_sendmessage(host);
break;
case 0x1f: /* -> PHASE_MSGIN, PHASE_DISCONNECT */
/* COMMAND -> MESSAGE IN */
acornscsi_message(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_COMMAND, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_DISCONNECT: /* STATE: connected, received DISCONNECT msg */
if (ssr == 0x85) { /* -> PHASE_IDLE */
host->scsi.disconnectable = 1;
host->scsi.reconnected.tag = 0;
host->scsi.phase = PHASE_IDLE;
host->stats.disconnects += 1;
} else {
printk(KERN_ERR "scsi%d.%c: PHASE_DISCONNECT, SSR %02X instead of disconnect?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_NEXT_COMMAND;
case PHASE_IDLE: /* STATE: disconnected */
if (ssr == 0x81) /* -> PHASE_RECONNECTED or PHASE_ABORTED */
acornscsi_reconnect(host);
else {
printk(KERN_ERR "scsi%d.%c: PHASE_IDLE, SSR %02X while idle?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_RECONNECTED: /* STATE: device reconnected to initiator */
/*
* Command reconnected - if MESGIN, get message - it may be
* the tag. If not, get command out of disconnected queue
*/
/*
* If we reconnected and we're not in MESSAGE IN phase after IDENTIFY,
* reconnect I_T_L command
*/
if (ssr != 0x8f && !acornscsi_reconnect_finish(host))
return INTR_IDLE;
ADD_STATUS(host->SCpnt->device->id, ssr, host->scsi.phase, in_irq);
switch (ssr) {
case 0x88: /* data out phase */
/* -> PHASE_DATAOUT */
/* MESSAGE IN -> DATA OUT */
acornscsi_dma_setup(host, DMA_OUT);
if (!acornscsi_starttransfer(host))
acornscsi_abortcmd(host, host->SCpnt->tag);
host->scsi.phase = PHASE_DATAOUT;
return INTR_IDLE;
case 0x89: /* data in phase */
/* -> PHASE_DATAIN */
/* MESSAGE IN -> DATA IN */
acornscsi_dma_setup(host, DMA_IN);
if (!acornscsi_starttransfer(host))
acornscsi_abortcmd(host, host->SCpnt->tag);
host->scsi.phase = PHASE_DATAIN;
return INTR_IDLE;
case 0x8a: /* command out */
/* MESSAGE IN -> COMMAND */
acornscsi_sendcommand(host);/* -> PHASE_COMMAND, PHASE_COMMANDPAUSED */
break;
case 0x8b: /* status in */
/* -> PHASE_STATUSIN */
/* MESSAGE IN -> STATUS */
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
case 0x8e: /* message out */
/* -> PHASE_MSGOUT */
/* MESSAGE IN -> MESSAGE OUT */
acornscsi_sendmessage(host);
break;
case 0x8f: /* message in */
acornscsi_message(host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_RECONNECTED, SSR %02X after reconnect?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_DATAIN: /* STATE: transferred data in */
/*
* This is simple - if we disconnect then the DMA address & count is
* correct.
*/
switch (ssr) {
case 0x19: /* -> PHASE_DATAIN */
case 0x89: /* -> PHASE_DATAIN */
acornscsi_abortcmd(host, host->SCpnt->tag);
return INTR_IDLE;
case 0x1b: /* -> PHASE_STATUSIN */
case 0x4b: /* -> PHASE_STATUSIN */
case 0x8b: /* -> PHASE_STATUSIN */
/* DATA IN -> STATUS */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
case 0x1e: /* -> PHASE_MSGOUT */
case 0x4e: /* -> PHASE_MSGOUT */
case 0x8e: /* -> PHASE_MSGOUT */
/* DATA IN -> MESSAGE OUT */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_sendmessage(host);
break;
case 0x1f: /* message in */
case 0x4f: /* message in */
case 0x8f: /* message in */
/* DATA IN -> MESSAGE IN */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_message(host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_DATAIN, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_DATAOUT: /* STATE: transferred data out */
/*
* This is more complicated - if we disconnect, the DMA could be 12
* bytes ahead of us. We need to correct this.
*/
switch (ssr) {
case 0x18: /* -> PHASE_DATAOUT */
case 0x88: /* -> PHASE_DATAOUT */
acornscsi_abortcmd(host, host->SCpnt->tag);
return INTR_IDLE;
case 0x1b: /* -> PHASE_STATUSIN */
case 0x4b: /* -> PHASE_STATUSIN */
case 0x8b: /* -> PHASE_STATUSIN */
/* DATA OUT -> STATUS */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_dma_adjust(host);
acornscsi_readstatusbyte(host);
host->scsi.phase = PHASE_STATUSIN;
break;
case 0x1e: /* -> PHASE_MSGOUT */
case 0x4e: /* -> PHASE_MSGOUT */
case 0x8e: /* -> PHASE_MSGOUT */
/* DATA OUT -> MESSAGE OUT */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_dma_adjust(host);
acornscsi_sendmessage(host);
break;
case 0x1f: /* message in */
case 0x4f: /* message in */
case 0x8f: /* message in */
/* DATA OUT -> MESSAGE IN */
host->scsi.SCp.scsi_xferred = scsi_bufflen(host->SCpnt) -
acornscsi_sbic_xfcount(host);
acornscsi_dma_stop(host);
acornscsi_dma_adjust(host);
acornscsi_message(host); /* -> PHASE_MSGIN, PHASE_DISCONNECT */
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_DATAOUT, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_STATUSIN: /* STATE: status in complete */
switch (ssr) {
case 0x1f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */
case 0x8f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */
/* STATUS -> MESSAGE IN */
acornscsi_message(host);
break;
case 0x1e: /* -> PHASE_MSGOUT */
case 0x8e: /* -> PHASE_MSGOUT */
/* STATUS -> MESSAGE OUT */
acornscsi_sendmessage(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_STATUSIN, SSR %02X instead of MESSAGE_IN?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_MSGIN: /* STATE: message in */
switch (ssr) {
case 0x1e: /* -> PHASE_MSGOUT */
case 0x4e: /* -> PHASE_MSGOUT */
case 0x8e: /* -> PHASE_MSGOUT */
/* MESSAGE IN -> MESSAGE OUT */
acornscsi_sendmessage(host);
break;
case 0x1f: /* -> PHASE_MSGIN, PHASE_DONE, PHASE_DISCONNECT */
case 0x2f:
case 0x4f:
case 0x8f:
acornscsi_message(host);
break;
case 0x85:
printk("scsi%d.%c: strange message in disconnection\n",
host->host->host_no, acornscsi_target(host));
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
acornscsi_done(host, &host->SCpnt, DID_ERROR);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_MSGIN, SSR %02X after message in?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_DONE: /* STATE: received status & message */
switch (ssr) {
case 0x85: /* -> PHASE_IDLE */
acornscsi_done(host, &host->SCpnt, DID_OK);
return INTR_NEXT_COMMAND;
case 0x1e:
case 0x8e:
acornscsi_sendmessage(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_DONE, SSR %02X instead of disconnect?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
case PHASE_ABORTED:
switch (ssr) {
case 0x85:
if (host->SCpnt)
acornscsi_done(host, &host->SCpnt, DID_ABORT);
else {
clear_bit(host->scsi.reconnected.target * 8 + host->scsi.reconnected.lun,
host->busyluns);
host->scsi.phase = PHASE_IDLE;
}
return INTR_NEXT_COMMAND;
case 0x1e:
case 0x2e:
case 0x4e:
case 0x8e:
acornscsi_sendmessage(host);
break;
default:
printk(KERN_ERR "scsi%d.%c: PHASE_ABORTED, SSR %02X?\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
default:
printk(KERN_ERR "scsi%d.%c: unknown driver phase %d\n",
host->host->host_no, acornscsi_target(host), ssr);
acornscsi_dumplog(host, host->SCpnt ? host->SCpnt->device->id : 8);
}
return INTR_PROCESSING;
}
/*
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
* Prototype: void acornscsi_intr(int irq, void *dev_id)
* Purpose : handle interrupts from Acorn SCSI card
* Params : irq - interrupt number
* dev_id - device specific data (AS_Host structure)
*/
static irqreturn_t
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
acornscsi_intr(int irq, void *dev_id)
{
AS_Host *host = (AS_Host *)dev_id;
intr_ret_t ret;
int iostatus;
int in_irq = 0;
do {
ret = INTR_IDLE;
iostatus = readb(host->fast + INT_REG);
if (iostatus & 2) {
acornscsi_dma_intr(host);
iostatus = readb(host->fast + INT_REG);
}
if (iostatus & 8)
ret = acornscsi_sbicintr(host, in_irq);
/*
* If we have a transfer pending, start it.
* Only start it if the interface has already started transferring
* it's data
*/
if (host->dma.xfer_required)
acornscsi_dma_xfer(host);
if (ret == INTR_NEXT_COMMAND)
ret = acornscsi_kick(host);
in_irq = 1;
} while (ret != INTR_IDLE);
return IRQ_HANDLED;
}
/*=============================================================================================
* Interfaces between interrupt handler and rest of scsi code
*/
/*
* Function : acornscsi_queuecmd(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
* Purpose : queues a SCSI command
* Params : cmd - SCSI command
* done - function called on completion, with pointer to command descriptor
* Returns : 0, or < 0 on error.
*/
int acornscsi_queuecmd(struct scsi_cmnd *SCpnt,
void (*done)(struct scsi_cmnd *))
{
AS_Host *host = (AS_Host *)SCpnt->device->host->hostdata;
if (!done) {
/* there should be some way of rejecting errors like this without panicing... */
panic("scsi%d: queuecommand called with NULL done function [cmd=%p]",
host->host->host_no, SCpnt);
return -EINVAL;
}
#if (DEBUG & DEBUG_NO_WRITE)
if (acornscsi_cmdtype(SCpnt->cmnd[0]) == CMD_WRITE && (NO_WRITE & (1 << SCpnt->device->id))) {
printk(KERN_CRIT "scsi%d.%c: WRITE attempted with NO_WRITE flag set\n",
host->host->host_no, '0' + SCpnt->device->id);
SCpnt->result = DID_NO_CONNECT << 16;
done(SCpnt);
return 0;
}
#endif
SCpnt->scsi_done = done;
SCpnt->host_scribble = NULL;
SCpnt->result = 0;
SCpnt->tag = 0;
SCpnt->SCp.phase = (int)acornscsi_datadirection(SCpnt->cmnd[0]);
SCpnt->SCp.sent_command = 0;
SCpnt->SCp.scsi_xferred = 0;
init_SCp(SCpnt);
host->stats.queues += 1;
{
unsigned long flags;
if (!queue_add_cmd_ordered(&host->queues.issue, SCpnt)) {
SCpnt->result = DID_ERROR << 16;
done(SCpnt);
return 0;
}
local_irq_save(flags);
if (host->scsi.phase == PHASE_IDLE)
acornscsi_kick(host);
local_irq_restore(flags);
}
return 0;
}
/*
* Prototype: void acornscsi_reportstatus(struct scsi_cmnd **SCpntp1, struct scsi_cmnd **SCpntp2, int result)
* Purpose : pass a result to *SCpntp1, and check if *SCpntp1 = *SCpntp2
* Params : SCpntp1 - pointer to command to return
* SCpntp2 - pointer to command to check
* result - result to pass back to mid-level done function
* Returns : *SCpntp2 = NULL if *SCpntp1 is the same command structure as *SCpntp2.
*/
static inline void acornscsi_reportstatus(struct scsi_cmnd **SCpntp1,
struct scsi_cmnd **SCpntp2,
int result)
{
struct scsi_cmnd *SCpnt = *SCpntp1;
if (SCpnt) {
*SCpntp1 = NULL;
SCpnt->result = result;
SCpnt->scsi_done(SCpnt);
}
if (SCpnt == *SCpntp2)
*SCpntp2 = NULL;
}
enum res_abort { res_not_running, res_success, res_success_clear, res_snooze };
/*
* Prototype: enum res acornscsi_do_abort(struct scsi_cmnd *SCpnt)
* Purpose : abort a command on this host
* Params : SCpnt - command to abort
* Returns : our abort status
*/
static enum res_abort acornscsi_do_abort(AS_Host *host, struct scsi_cmnd *SCpnt)
{
enum res_abort res = res_not_running;
if (queue_remove_cmd(&host->queues.issue, SCpnt)) {
/*
* The command was on the issue queue, and has not been
* issued yet. We can remove the command from the queue,
* and acknowledge the abort. Neither the devices nor the
* interface know about the command.
*/
//#if (DEBUG & DEBUG_ABORT)
printk("on issue queue ");
//#endif
res = res_success;
} else if (queue_remove_cmd(&host->queues.disconnected, SCpnt)) {
/*
* The command was on the disconnected queue. Simply
* acknowledge the abort condition, and when the target
* reconnects, we will give it an ABORT message. The
* target should then disconnect, and we will clear
* the busylun bit.
*/
//#if (DEBUG & DEBUG_ABORT)
printk("on disconnected queue ");
//#endif
res = res_success;
} else if (host->SCpnt == SCpnt) {
unsigned long flags;
//#if (DEBUG & DEBUG_ABORT)
printk("executing ");
//#endif
local_irq_save(flags);
switch (host->scsi.phase) {
/*
* If the interface is idle, and the command is 'disconnectable',
* then it is the same as on the disconnected queue. We simply
* remove all traces of the command. When the target reconnects,
* we will give it an ABORT message since the command could not
* be found. When the target finally disconnects, we will clear
* the busylun bit.
*/
case PHASE_IDLE:
if (host->scsi.disconnectable) {
host->scsi.disconnectable = 0;
host->SCpnt = NULL;
res = res_success;
}
break;
/*
* If the command has connected and done nothing further,
* simply force a disconnect. We also need to clear the
* busylun bit.
*/
case PHASE_CONNECTED:
sbic_arm_write(host, SBIC_CMND, CMND_DISCONNECT);
host->SCpnt = NULL;
res = res_success_clear;
break;
default:
acornscsi_abortcmd(host, host->SCpnt->tag);
res = res_snooze;
}
local_irq_restore(flags);
} else if (host->origSCpnt == SCpnt) {
/*
* The command will be executed next, but a command
* is currently using the interface. This is similar to
* being on the issue queue, except the busylun bit has
* been set.
*/
host->origSCpnt = NULL;
//#if (DEBUG & DEBUG_ABORT)
printk("waiting for execution ");
//#endif
res = res_success_clear;
} else
printk("unknown ");
return res;
}
/*
* Prototype: int acornscsi_abort(struct scsi_cmnd *SCpnt)
* Purpose : abort a command on this host
* Params : SCpnt - command to abort
* Returns : one of SCSI_ABORT_ macros
*/
int acornscsi_abort(struct scsi_cmnd *SCpnt)
{
AS_Host *host = (AS_Host *) SCpnt->device->host->hostdata;
int result;
host->stats.aborts += 1;
#if (DEBUG & DEBUG_ABORT)
{
int asr, ssr;
asr = sbic_arm_read(host, SBIC_ASR);
ssr = sbic_arm_read(host, SBIC_SSR);
printk(KERN_WARNING "acornscsi_abort: ");
print_sbic_status(asr, ssr, host->scsi.phase);
acornscsi_dumplog(host, SCpnt->device->id);
}
#endif
printk("scsi%d: ", host->host->host_no);
switch (acornscsi_do_abort(host, SCpnt)) {
/*
* We managed to find the command and cleared it out.
* We do not expect the command to be executing on the
* target, but we have set the busylun bit.
*/
case res_success_clear:
//#if (DEBUG & DEBUG_ABORT)
printk("clear ");
//#endif
clear_bit(SCpnt->device->id * 8 + SCpnt->device->lun, host->busyluns);
/*
* We found the command, and cleared it out. Either
* the command is still known to be executing on the
* target, or the busylun bit is not set.
*/
case res_success:
//#if (DEBUG & DEBUG_ABORT)
printk("success\n");
//#endif
result = SUCCESS;
break;
/*
* We did find the command, but unfortunately we couldn't
* unhook it from ourselves. Wait some more, and if it
* still doesn't complete, reset the interface.
*/
case res_snooze:
//#if (DEBUG & DEBUG_ABORT)
printk("snooze\n");
//#endif
result = FAILED;
break;
/*
* The command could not be found (either because it completed,
* or it got dropped.
*/
default:
case res_not_running:
acornscsi_dumplog(host, SCpnt->device->id);
result = FAILED;
//#if (DEBUG & DEBUG_ABORT)
printk("not running\n");
//#endif
break;
}
return result;
}
/*
* Prototype: int acornscsi_reset(struct scsi_cmnd *SCpnt)
* Purpose : reset a command on this host/reset this host
* Params : SCpnt - command causing reset
* Returns : one of SCSI_RESET_ macros
*/
int acornscsi_bus_reset(struct scsi_cmnd *SCpnt)
{
AS_Host *host = (AS_Host *)SCpnt->device->host->hostdata;
struct scsi_cmnd *SCptr;
host->stats.resets += 1;
#if (DEBUG & DEBUG_RESET)
{
int asr, ssr;
asr = sbic_arm_read(host, SBIC_ASR);
ssr = sbic_arm_read(host, SBIC_SSR);
printk(KERN_WARNING "acornscsi_reset: ");
print_sbic_status(asr, ssr, host->scsi.phase);
acornscsi_dumplog(host, SCpnt->device->id);
}
#endif
acornscsi_dma_stop(host);
/*
* do hard reset. This resets all devices on this host, and so we
* must set the reset status on all commands.
*/
acornscsi_resetcard(host);
while ((SCptr = queue_remove(&host->queues.disconnected)) != NULL)
;
return SUCCESS;
}
/*==============================================================================================
* initialisation & miscellaneous support
*/
/*
* Function: char *acornscsi_info(struct Scsi_Host *host)
* Purpose : return a string describing this interface
* Params : host - host to give information on
* Returns : a constant string
*/
const
char *acornscsi_info(struct Scsi_Host *host)
{
static char string[100], *p;
p = string;
p += sprintf(string, "%s at port %08lX irq %d v%d.%d.%d"
#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
" SYNC"
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
" TAG"
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_LINK
" LINK"
#endif
#if (DEBUG & DEBUG_NO_WRITE)
" NOWRITE (" __stringify(NO_WRITE) ")"
#endif
, host->hostt->name, host->io_port, host->irq,
VER_MAJOR, VER_MINOR, VER_PATCH);
return string;
}
int acornscsi_proc_info(struct Scsi_Host *instance, char *buffer, char **start, off_t offset,
int length, int inout)
{
int pos, begin = 0, devidx;
struct scsi_device *scd;
AS_Host *host;
char *p = buffer;
if (inout == 1)
return -EINVAL;
host = (AS_Host *)instance->hostdata;
p += sprintf(p, "AcornSCSI driver v%d.%d.%d"
#ifdef CONFIG_SCSI_ACORNSCSI_SYNC
" SYNC"
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_TAGGED_QUEUE
" TAG"
#endif
#ifdef CONFIG_SCSI_ACORNSCSI_LINK
" LINK"
#endif
#if (DEBUG & DEBUG_NO_WRITE)
" NOWRITE (" __stringify(NO_WRITE) ")"
#endif
"\n\n", VER_MAJOR, VER_MINOR, VER_PATCH);
p += sprintf(p, "SBIC: WD33C93A Address: %p IRQ : %d\n",
host->base + SBIC_REGIDX, host->scsi.irq);
#ifdef USE_DMAC
p += sprintf(p, "DMAC: uPC71071 Address: %p IRQ : %d\n\n",
host->base + DMAC_OFFSET, host->scsi.irq);
#endif
p += sprintf(p, "Statistics:\n"
"Queued commands: %-10u Issued commands: %-10u\n"
"Done commands : %-10u Reads : %-10u\n"
"Writes : %-10u Others : %-10u\n"
"Disconnects : %-10u Aborts : %-10u\n"
"Resets : %-10u\n\nLast phases:",
host->stats.queues, host->stats.removes,
host->stats.fins, host->stats.reads,
host->stats.writes, host->stats.miscs,
host->stats.disconnects, host->stats.aborts,
host->stats.resets);
for (devidx = 0; devidx < 9; devidx ++) {
unsigned int statptr, prev;
p += sprintf(p, "\n%c:", devidx == 8 ? 'H' : ('0' + devidx));
statptr = host->status_ptr[devidx] - 10;
if ((signed int)statptr < 0)
statptr += STATUS_BUFFER_SIZE;
prev = host->status[devidx][statptr].when;
for (; statptr != host->status_ptr[devidx]; statptr = (statptr + 1) & (STATUS_BUFFER_SIZE - 1)) {
if (host->status[devidx][statptr].when) {
p += sprintf(p, "%c%02X:%02X+%2ld",
host->status[devidx][statptr].irq ? '-' : ' ',
host->status[devidx][statptr].ph,
host->status[devidx][statptr].ssr,
(host->status[devidx][statptr].when - prev) < 100 ?
(host->status[devidx][statptr].when - prev) : 99);
prev = host->status[devidx][statptr].when;
}
}
}
p += sprintf(p, "\nAttached devices:\n");
shost_for_each_device(scd, instance) {
p += sprintf(p, "Device/Lun TaggedQ Sync\n");
p += sprintf(p, " %d/%d ", scd->id, scd->lun);
if (scd->tagged_supported)
p += sprintf(p, "%3sabled(%3d) ",
scd->simple_tags ? "en" : "dis",
scd->current_tag);
else
p += sprintf(p, "unsupported ");
if (host->device[scd->id].sync_xfer & 15)
p += sprintf(p, "offset %d, %d ns\n",
host->device[scd->id].sync_xfer & 15,
acornscsi_getperiod(host->device[scd->id].sync_xfer));
else
p += sprintf(p, "async\n");
pos = p - buffer;
if (pos + begin < offset) {
begin += pos;
p = buffer;
}
pos = p - buffer;
if (pos + begin > offset + length) {
scsi_device_put(scd);
break;
}
}
pos = p - buffer;
*start = buffer + (offset - begin);
pos -= offset - begin;
if (pos > length)
pos = length;
return pos;
}
static struct scsi_host_template acornscsi_template = {
.module = THIS_MODULE,
.proc_info = acornscsi_proc_info,
.name = "AcornSCSI",
.info = acornscsi_info,
.queuecommand = acornscsi_queuecmd,
.eh_abort_handler = acornscsi_abort,
.eh_bus_reset_handler = acornscsi_bus_reset,
.can_queue = 16,
.this_id = 7,
.sg_tablesize = SG_ALL,
.cmd_per_lun = 2,
.use_clustering = DISABLE_CLUSTERING,
.proc_name = "acornscsi",
};
static int __devinit
acornscsi_probe(struct expansion_card *ec, const struct ecard_id *id)
{
struct Scsi_Host *host;
AS_Host *ashost;
int ret;
ret = ecard_request_resources(ec);
if (ret)
goto out;
host = scsi_host_alloc(&acornscsi_template, sizeof(AS_Host));
if (!host) {
ret = -ENOMEM;
goto out_release;
}
ashost = (AS_Host *)host->hostdata;
ashost->base = ecardm_iomap(ec, ECARD_RES_MEMC, 0, 0);
ashost->fast = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0);
if (!ashost->base || !ashost->fast)
goto out_put;
host->irq = ec->irq;
ashost->host = host;
ashost->scsi.irq = host->irq;
ec->irqaddr = ashost->fast + INT_REG;
ec->irqmask = 0x0a;
ret = request_irq(host->irq, acornscsi_intr, IRQF_DISABLED, "acornscsi", ashost);
if (ret) {
printk(KERN_CRIT "scsi%d: IRQ%d not free: %d\n",
host->host_no, ashost->scsi.irq, ret);
goto out_put;
}
memset(&ashost->stats, 0, sizeof (ashost->stats));
queue_initialise(&ashost->queues.issue);
queue_initialise(&ashost->queues.disconnected);
msgqueue_initialise(&ashost->scsi.msgs);
acornscsi_resetcard(ashost);
ret = scsi_add_host(host, &ec->dev);
if (ret)
goto out_irq;
scsi_scan_host(host);
goto out;
out_irq:
free_irq(host->irq, ashost);
msgqueue_free(&ashost->scsi.msgs);
queue_free(&ashost->queues.disconnected);
queue_free(&ashost->queues.issue);
out_put:
ecardm_iounmap(ec, ashost->fast);
ecardm_iounmap(ec, ashost->base);
scsi_host_put(host);
out_release:
ecard_release_resources(ec);
out:
return ret;
}
static void __devexit acornscsi_remove(struct expansion_card *ec)
{
struct Scsi_Host *host = ecard_get_drvdata(ec);
AS_Host *ashost = (AS_Host *)host->hostdata;
ecard_set_drvdata(ec, NULL);
scsi_remove_host(host);
/*
* Put card into RESET state
*/
writeb(0x80, ashost->fast + PAGE_REG);
free_irq(host->irq, ashost);
msgqueue_free(&ashost->scsi.msgs);
queue_free(&ashost->queues.disconnected);
queue_free(&ashost->queues.issue);
ecardm_iounmap(ec, ashost->fast);
ecardm_iounmap(ec, ashost->base);
scsi_host_put(host);
ecard_release_resources(ec);
}
static const struct ecard_id acornscsi_cids[] = {
{ MANU_ACORN, PROD_ACORN_SCSI },
{ 0xffff, 0xffff },
};
static struct ecard_driver acornscsi_driver = {
.probe = acornscsi_probe,
.remove = __devexit_p(acornscsi_remove),
.id_table = acornscsi_cids,
.drv = {
.name = "acornscsi",
},
};
static int __init acornscsi_init(void)
{
return ecard_register_driver(&acornscsi_driver);
}
static void __exit acornscsi_exit(void)
{
ecard_remove_driver(&acornscsi_driver);
}
module_init(acornscsi_init);
module_exit(acornscsi_exit);
MODULE_AUTHOR("Russell King");
MODULE_DESCRIPTION("AcornSCSI driver");
MODULE_LICENSE("GPL");