diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/mesh.c | |
download | linux-3.10-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz linux-3.10-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 linux-3.10-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/scsi/mesh.c')
-rw-r--r-- | drivers/scsi/mesh.c | 2062 |
1 files changed, 2062 insertions, 0 deletions
diff --git a/drivers/scsi/mesh.c b/drivers/scsi/mesh.c new file mode 100644 index 00000000000..85f3a74ac42 --- /dev/null +++ b/drivers/scsi/mesh.c @@ -0,0 +1,2062 @@ +/* + * SCSI low-level driver for the MESH (Macintosh Enhanced SCSI Hardware) + * bus adaptor found on Power Macintosh computers. + * We assume the MESH is connected to a DBDMA (descriptor-based DMA) + * controller. + * + * Paul Mackerras, August 1996. + * Copyright (C) 1996 Paul Mackerras. + * + * Apr. 21 2002 - BenH Rework bus reset code for new error handler + * Add delay after initial bus reset + * Add module parameters + * + * Sep. 27 2003 - BenH Move to new driver model, fix some write posting + * issues + * To do: + * - handle aborts correctly + * - retry arbitration if lost (unless higher levels do this for us) + * - power down the chip when no device is detected + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> +#include <linux/spinlock.h> +#include <asm/dbdma.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/prom.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/hydra.h> +#include <asm/processor.h> +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <asm/pci-bridge.h> +#include <asm/macio.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> + +#include "mesh.h" + +#if 1 +#undef KERN_DEBUG +#define KERN_DEBUG KERN_WARNING +#endif + +MODULE_AUTHOR("Paul Mackerras (paulus@samba.org)"); +MODULE_DESCRIPTION("PowerMac MESH SCSI driver"); +MODULE_LICENSE("GPL"); + +static int sync_rate = CONFIG_SCSI_MESH_SYNC_RATE; +static int sync_targets = 0xff; +static int resel_targets = 0xff; +static int debug_targets = 0; /* print debug for these targets */ +static int init_reset_delay = CONFIG_SCSI_MESH_RESET_DELAY_MS; + +module_param(sync_rate, int, 0); +MODULE_PARM_DESC(sync_rate, "Synchronous rate (0..10, 0=async)"); +module_param(sync_targets, int, 0); +MODULE_PARM_DESC(sync_targets, "Bitmask of targets allowed to set synchronous"); +module_param(resel_targets, int, 0); +MODULE_PARM_DESC(resel_targets, "Bitmask of targets allowed to set disconnect"); +module_param(debug_targets, int, 0644); +MODULE_PARM_DESC(debug_targets, "Bitmask of debugged targets"); +module_param(init_reset_delay, int, 0); +MODULE_PARM_DESC(init_reset_delay, "Initial bus reset delay (0=no reset)"); + +static int mesh_sync_period = 100; +static int mesh_sync_offset = 0; +static unsigned char use_active_neg = 0; /* bit mask for SEQ_ACTIVE_NEG if used */ + +#define ALLOW_SYNC(tgt) ((sync_targets >> (tgt)) & 1) +#define ALLOW_RESEL(tgt) ((resel_targets >> (tgt)) & 1) +#define ALLOW_DEBUG(tgt) ((debug_targets >> (tgt)) & 1) +#define DEBUG_TARGET(cmd) ((cmd) && ALLOW_DEBUG((cmd)->device->id)) + +#undef MESH_DBG +#define N_DBG_LOG 50 +#define N_DBG_SLOG 20 +#define NUM_DBG_EVENTS 13 +#undef DBG_USE_TB /* bombs on 601 */ + +struct dbglog { + char *fmt; + u32 tb; + u8 phase; + u8 bs0; + u8 bs1; + u8 tgt; + int d; +}; + +enum mesh_phase { + idle, + arbitrating, + selecting, + commanding, + dataing, + statusing, + busfreeing, + disconnecting, + reselecting, + sleeping +}; + +enum msg_phase { + msg_none, + msg_out, + msg_out_xxx, + msg_out_last, + msg_in, + msg_in_bad, +}; + +enum sdtr_phase { + do_sdtr, + sdtr_sent, + sdtr_done +}; + +struct mesh_target { + enum sdtr_phase sdtr_state; + int sync_params; + int data_goes_out; /* guess as to data direction */ + struct scsi_cmnd *current_req; + u32 saved_ptr; +#ifdef MESH_DBG + int log_ix; + int n_log; + struct dbglog log[N_DBG_LOG]; +#endif +}; + +struct mesh_state { + volatile struct mesh_regs __iomem *mesh; + int meshintr; + volatile struct dbdma_regs __iomem *dma; + int dmaintr; + struct Scsi_Host *host; + struct mesh_state *next; + struct scsi_cmnd *request_q; + struct scsi_cmnd *request_qtail; + enum mesh_phase phase; /* what we're currently trying to do */ + enum msg_phase msgphase; + int conn_tgt; /* target we're connected to */ + struct scsi_cmnd *current_req; /* req we're currently working on */ + int data_ptr; + int dma_started; + int dma_count; + int stat; + int aborting; + int expect_reply; + int n_msgin; + u8 msgin[16]; + int n_msgout; + int last_n_msgout; + u8 msgout[16]; + struct dbdma_cmd *dma_cmds; /* space for dbdma commands, aligned */ + dma_addr_t dma_cmd_bus; + void *dma_cmd_space; + int dma_cmd_size; + int clk_freq; + struct mesh_target tgts[8]; + struct macio_dev *mdev; + struct pci_dev* pdev; +#ifdef MESH_DBG + int log_ix; + int n_log; + struct dbglog log[N_DBG_SLOG]; +#endif +}; + +/* + * Driver is too messy, we need a few prototypes... + */ +static void mesh_done(struct mesh_state *ms, int start_next); +static void mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs); +static void cmd_complete(struct mesh_state *ms); +static void set_dma_cmds(struct mesh_state *ms, struct scsi_cmnd *cmd); +static void halt_dma(struct mesh_state *ms); +static void phase_mismatch(struct mesh_state *ms); + + +/* + * Some debugging & logging routines + */ + +#ifdef MESH_DBG + +static inline u32 readtb(void) +{ + u32 tb; + +#ifdef DBG_USE_TB + /* Beware: if you enable this, it will crash on 601s. */ + asm ("mftb %0" : "=r" (tb) : ); +#else + tb = 0; +#endif + return tb; +} + +static void dlog(struct mesh_state *ms, char *fmt, int a) +{ + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + struct dbglog *tlp, *slp; + + tlp = &tp->log[tp->log_ix]; + slp = &ms->log[ms->log_ix]; + tlp->fmt = fmt; + tlp->tb = readtb(); + tlp->phase = (ms->msgphase << 4) + ms->phase; + tlp->bs0 = ms->mesh->bus_status0; + tlp->bs1 = ms->mesh->bus_status1; + tlp->tgt = ms->conn_tgt; + tlp->d = a; + *slp = *tlp; + if (++tp->log_ix >= N_DBG_LOG) + tp->log_ix = 0; + if (tp->n_log < N_DBG_LOG) + ++tp->n_log; + if (++ms->log_ix >= N_DBG_SLOG) + ms->log_ix = 0; + if (ms->n_log < N_DBG_SLOG) + ++ms->n_log; +} + +static void dumplog(struct mesh_state *ms, int t) +{ + struct mesh_target *tp = &ms->tgts[t]; + struct dbglog *lp; + int i; + + if (tp->n_log == 0) + return; + i = tp->log_ix - tp->n_log; + if (i < 0) + i += N_DBG_LOG; + tp->n_log = 0; + do { + lp = &tp->log[i]; + printk(KERN_DEBUG "mesh log %d: bs=%.2x%.2x ph=%.2x ", + t, lp->bs1, lp->bs0, lp->phase); +#ifdef DBG_USE_TB + printk("tb=%10u ", lp->tb); +#endif + printk(lp->fmt, lp->d); + printk("\n"); + if (++i >= N_DBG_LOG) + i = 0; + } while (i != tp->log_ix); +} + +static void dumpslog(struct mesh_state *ms) +{ + struct dbglog *lp; + int i; + + if (ms->n_log == 0) + return; + i = ms->log_ix - ms->n_log; + if (i < 0) + i += N_DBG_SLOG; + ms->n_log = 0; + do { + lp = &ms->log[i]; + printk(KERN_DEBUG "mesh log: bs=%.2x%.2x ph=%.2x t%d ", + lp->bs1, lp->bs0, lp->phase, lp->tgt); +#ifdef DBG_USE_TB + printk("tb=%10u ", lp->tb); +#endif + printk(lp->fmt, lp->d); + printk("\n"); + if (++i >= N_DBG_SLOG) + i = 0; + } while (i != ms->log_ix); +} + +#else + +static inline void dlog(struct mesh_state *ms, char *fmt, int a) +{} +static inline void dumplog(struct mesh_state *ms, int tgt) +{} +static inline void dumpslog(struct mesh_state *ms) +{} + +#endif /* MESH_DBG */ + +#define MKWORD(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static void +mesh_dump_regs(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + volatile struct dbdma_regs __iomem *md = ms->dma; + int t; + struct mesh_target *tp; + + printk(KERN_DEBUG "mesh: state at %p, regs at %p, dma at %p\n", + ms, mr, md); + printk(KERN_DEBUG " ct=%4x seq=%2x bs=%4x fc=%2x " + "exc=%2x err=%2x im=%2x int=%2x sp=%2x\n", + (mr->count_hi << 8) + mr->count_lo, mr->sequence, + (mr->bus_status1 << 8) + mr->bus_status0, mr->fifo_count, + mr->exception, mr->error, mr->intr_mask, mr->interrupt, + mr->sync_params); + while(in_8(&mr->fifo_count)) + printk(KERN_DEBUG " fifo data=%.2x\n",in_8(&mr->fifo)); + printk(KERN_DEBUG " dma stat=%x cmdptr=%x\n", + in_le32(&md->status), in_le32(&md->cmdptr)); + printk(KERN_DEBUG " phase=%d msgphase=%d conn_tgt=%d data_ptr=%d\n", + ms->phase, ms->msgphase, ms->conn_tgt, ms->data_ptr); + printk(KERN_DEBUG " dma_st=%d dma_ct=%d n_msgout=%d\n", + ms->dma_started, ms->dma_count, ms->n_msgout); + for (t = 0; t < 8; ++t) { + tp = &ms->tgts[t]; + if (tp->current_req == NULL) + continue; + printk(KERN_DEBUG " target %d: req=%p goes_out=%d saved_ptr=%d\n", + t, tp->current_req, tp->data_goes_out, tp->saved_ptr); + } +} + + +/* + * Flush write buffers on the bus path to the mesh + */ +static inline void mesh_flush_io(volatile struct mesh_regs __iomem *mr) +{ + (void)in_8(&mr->mesh_id); +} + + +/* + * Complete a SCSI command + */ +static void mesh_completed(struct mesh_state *ms, struct scsi_cmnd *cmd) +{ + (*cmd->scsi_done)(cmd); +} + + +/* Called with meshinterrupt disabled, initialize the chipset + * and eventually do the initial bus reset. The lock must not be + * held since we can schedule. + */ +static void mesh_init(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + volatile struct dbdma_regs __iomem *md = ms->dma; + + mesh_flush_io(mr); + udelay(100); + + /* Reset controller */ + out_le32(&md->control, (RUN|PAUSE|FLUSH|WAKE) << 16); /* stop dma */ + out_8(&mr->exception, 0xff); /* clear all exception bits */ + out_8(&mr->error, 0xff); /* clear all error bits */ + out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); + udelay(10); + out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->source_id, ms->host->this_id); + out_8(&mr->sel_timeout, 25); /* 250ms */ + out_8(&mr->sync_params, ASYNC_PARAMS); + + if (init_reset_delay) { + printk(KERN_INFO "mesh: performing initial bus reset...\n"); + + /* Reset bus */ + out_8(&mr->bus_status1, BS1_RST); /* assert RST */ + mesh_flush_io(mr); + udelay(30); /* leave it on for >= 25us */ + out_8(&mr->bus_status1, 0); /* negate RST */ + mesh_flush_io(mr); + + /* Wait for bus to come back */ + msleep(init_reset_delay); + } + + /* Reconfigure controller */ + out_8(&mr->interrupt, 0xff); /* clear all interrupt bits */ + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->sync_params, ASYNC_PARAMS); + out_8(&mr->sequence, SEQ_ENBRESEL); + + ms->phase = idle; + ms->msgphase = msg_none; +} + + +static void mesh_start_cmd(struct mesh_state *ms, struct scsi_cmnd *cmd) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + int t, id; + + id = cmd->device->id; + ms->current_req = cmd; + ms->tgts[id].data_goes_out = cmd->sc_data_direction == DMA_TO_DEVICE; + ms->tgts[id].current_req = cmd; + +#if 1 + if (DEBUG_TARGET(cmd)) { + int i; + printk(KERN_DEBUG "mesh_start: %p ser=%lu tgt=%d cmd=", + cmd, cmd->serial_number, id); + for (i = 0; i < cmd->cmd_len; ++i) + printk(" %x", cmd->cmnd[i]); + printk(" use_sg=%d buffer=%p bufflen=%u\n", + cmd->use_sg, cmd->request_buffer, cmd->request_bufflen); + } +#endif + if (ms->dma_started) + panic("mesh: double DMA start !\n"); + + ms->phase = arbitrating; + ms->msgphase = msg_none; + ms->data_ptr = 0; + ms->dma_started = 0; + ms->n_msgout = 0; + ms->last_n_msgout = 0; + ms->expect_reply = 0; + ms->conn_tgt = id; + ms->tgts[id].saved_ptr = 0; + ms->stat = DID_OK; + ms->aborting = 0; +#ifdef MESH_DBG + ms->tgts[id].n_log = 0; + dlog(ms, "start cmd=%x", (int) cmd); +#endif + + /* Off we go */ + dlog(ms, "about to arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); + out_8(&mr->interrupt, INT_CMDDONE); + out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); + udelay(1); + + if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { + /* + * Some other device has the bus or is arbitrating for it - + * probably a target which is about to reselect us. + */ + dlog(ms, "busy b4 arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, + mr->error, mr->fifo_count)); + for (t = 100; t > 0; --t) { + if ((in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) == 0) + break; + if (in_8(&mr->interrupt) != 0) { + dlog(ms, "intr b4 arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, + mr->error, mr->fifo_count)); + mesh_interrupt(0, (void *)ms, NULL); + if (ms->phase != arbitrating) + return; + } + udelay(1); + } + if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { + /* XXX should try again in a little while */ + ms->stat = DID_BUS_BUSY; + ms->phase = idle; + mesh_done(ms, 0); + return; + } + } + + /* + * Apparently the mesh has a bug where it will assert both its + * own bit and the target's bit on the bus during arbitration. + */ + out_8(&mr->dest_id, mr->source_id); + + /* + * There appears to be a race with reselection sometimes, + * where a target reselects us just as we issue the + * arbitrate command. It seems that then the arbitrate + * command just hangs waiting for the bus to be free + * without giving us a reselection exception. + * The only way I have found to get it to respond correctly + * is this: disable reselection before issuing the arbitrate + * command, then after issuing it, if it looks like a target + * is trying to reselect us, reset the mesh and then enable + * reselection. + */ + out_8(&mr->sequence, SEQ_DISRESEL); + if (in_8(&mr->interrupt) != 0) { + dlog(ms, "intr after disresel, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, + mr->error, mr->fifo_count)); + mesh_interrupt(0, (void *)ms, NULL); + if (ms->phase != arbitrating) + return; + dlog(ms, "after intr after disresel, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, + mr->error, mr->fifo_count)); + } + + out_8(&mr->sequence, SEQ_ARBITRATE); + + for (t = 230; t > 0; --t) { + if (in_8(&mr->interrupt) != 0) + break; + udelay(1); + } + dlog(ms, "after arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); + if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) + && (in_8(&mr->bus_status0) & BS0_IO)) { + /* looks like a reselection - try resetting the mesh */ + dlog(ms, "resel? after arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); + out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); + udelay(10); + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); + for (t = 10; t > 0 && in_8(&mr->interrupt) == 0; --t) + udelay(1); + dlog(ms, "tried reset after arb, intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); +#ifndef MESH_MULTIPLE_HOSTS + if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) + && (in_8(&mr->bus_status0) & BS0_IO)) { + printk(KERN_ERR "mesh: controller not responding" + " to reselection!\n"); + /* + * If this is a target reselecting us, and the + * mesh isn't responding, the higher levels of + * the scsi code will eventually time out and + * reset the bus. + */ + } +#endif + } +} + +/* + * Start the next command for a MESH. + * Should be called with interrupts disabled. + */ +static void mesh_start(struct mesh_state *ms) +{ + struct scsi_cmnd *cmd, *prev, *next; + + if (ms->phase != idle || ms->current_req != NULL) { + printk(KERN_ERR "inappropriate mesh_start (phase=%d, ms=%p)", + ms->phase, ms); + return; + } + + while (ms->phase == idle) { + prev = NULL; + for (cmd = ms->request_q; ; cmd = (struct scsi_cmnd *) cmd->host_scribble) { + if (cmd == NULL) + return; + if (ms->tgts[cmd->device->id].current_req == NULL) + break; + prev = cmd; + } + next = (struct scsi_cmnd *) cmd->host_scribble; + if (prev == NULL) + ms->request_q = next; + else + prev->host_scribble = (void *) next; + if (next == NULL) + ms->request_qtail = prev; + + mesh_start_cmd(ms, cmd); + } +} + +static void mesh_done(struct mesh_state *ms, int start_next) +{ + struct scsi_cmnd *cmd; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + + cmd = ms->current_req; + ms->current_req = NULL; + tp->current_req = NULL; + if (cmd) { + cmd->result = (ms->stat << 16) + cmd->SCp.Status; + if (ms->stat == DID_OK) + cmd->result += (cmd->SCp.Message << 8); + if (DEBUG_TARGET(cmd)) { + printk(KERN_DEBUG "mesh_done: result = %x, data_ptr=%d, buflen=%d\n", + cmd->result, ms->data_ptr, cmd->request_bufflen); + if ((cmd->cmnd[0] == 0 || cmd->cmnd[0] == 0x12 || cmd->cmnd[0] == 3) + && cmd->request_buffer != 0) { + unsigned char *b = cmd->request_buffer; + printk(KERN_DEBUG "buffer = %x %x %x %x %x %x %x %x\n", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); + } + } + cmd->SCp.this_residual -= ms->data_ptr; + mesh_completed(ms, cmd); + } + if (start_next) { + out_8(&ms->mesh->sequence, SEQ_ENBRESEL); + mesh_flush_io(ms->mesh); + udelay(1); + ms->phase = idle; + mesh_start(ms); + } +} + +static inline void add_sdtr_msg(struct mesh_state *ms) +{ + int i = ms->n_msgout; + + ms->msgout[i] = EXTENDED_MESSAGE; + ms->msgout[i+1] = 3; + ms->msgout[i+2] = EXTENDED_SDTR; + ms->msgout[i+3] = mesh_sync_period/4; + ms->msgout[i+4] = (ALLOW_SYNC(ms->conn_tgt)? mesh_sync_offset: 0); + ms->n_msgout = i + 5; +} + +static void set_sdtr(struct mesh_state *ms, int period, int offset) +{ + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + volatile struct mesh_regs __iomem *mr = ms->mesh; + int v, tr; + + tp->sdtr_state = sdtr_done; + if (offset == 0) { + /* asynchronous */ + if (SYNC_OFF(tp->sync_params)) + printk(KERN_INFO "mesh: target %d now asynchronous\n", + ms->conn_tgt); + tp->sync_params = ASYNC_PARAMS; + out_8(&mr->sync_params, ASYNC_PARAMS); + return; + } + /* + * We need to compute ceil(clk_freq * period / 500e6) - 2 + * without incurring overflow. + */ + v = (ms->clk_freq / 5000) * period; + if (v <= 250000) { + /* special case: sync_period == 5 * clk_period */ + v = 0; + /* units of tr are 100kB/s */ + tr = (ms->clk_freq + 250000) / 500000; + } else { + /* sync_period == (v + 2) * 2 * clk_period */ + v = (v + 99999) / 100000 - 2; + if (v > 15) + v = 15; /* oops */ + tr = ((ms->clk_freq / (v + 2)) + 199999) / 200000; + } + if (offset > 15) + offset = 15; /* can't happen */ + tp->sync_params = SYNC_PARAMS(offset, v); + out_8(&mr->sync_params, tp->sync_params); + printk(KERN_INFO "mesh: target %d synchronous at %d.%d MB/s\n", + ms->conn_tgt, tr/10, tr%10); +} + +static void start_phase(struct mesh_state *ms) +{ + int i, seq, nb; + volatile struct mesh_regs __iomem *mr = ms->mesh; + volatile struct dbdma_regs __iomem *md = ms->dma; + struct scsi_cmnd *cmd = ms->current_req; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + + dlog(ms, "start_phase nmo/exc/fc/seq = %.8x", + MKWORD(ms->n_msgout, mr->exception, mr->fifo_count, mr->sequence)); + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); + switch (ms->msgphase) { + case msg_none: + break; + + case msg_in: + out_8(&mr->count_hi, 0); + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGIN + seq); + ms->n_msgin = 0; + return; + + case msg_out: + /* + * To make sure ATN drops before we assert ACK for + * the last byte of the message, we have to do the + * last byte specially. + */ + if (ms->n_msgout <= 0) { + printk(KERN_ERR "mesh: msg_out but n_msgout=%d\n", + ms->n_msgout); + mesh_dump_regs(ms); + ms->msgphase = msg_none; + break; + } + if (ALLOW_DEBUG(ms->conn_tgt)) { + printk(KERN_DEBUG "mesh: sending %d msg bytes:", + ms->n_msgout); + for (i = 0; i < ms->n_msgout; ++i) + printk(" %x", ms->msgout[i]); + printk("\n"); + } + dlog(ms, "msgout msg=%.8x", MKWORD(ms->n_msgout, ms->msgout[0], + ms->msgout[1], ms->msgout[2])); + out_8(&mr->count_hi, 0); + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + /* + * If ATN is not already asserted, we assert it, then + * issue a SEQ_MSGOUT to get the mesh to drop ACK. + */ + if ((in_8(&mr->bus_status0) & BS0_ATN) == 0) { + dlog(ms, "bus0 was %.2x explictly asserting ATN", mr->bus_status0); + out_8(&mr->bus_status0, BS0_ATN); /* explicit ATN */ + mesh_flush_io(mr); + udelay(1); + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGOUT + seq); + out_8(&mr->bus_status0, 0); /* release explicit ATN */ + dlog(ms,"hace: after explicit ATN bus0=%.2x",mr->bus_status0); + } + if (ms->n_msgout == 1) { + /* + * We can't issue the SEQ_MSGOUT without ATN + * until the target has asserted REQ. The logic + * in cmd_complete handles both situations: + * REQ already asserted or not. + */ + cmd_complete(ms); + } else { + out_8(&mr->count_lo, ms->n_msgout - 1); + out_8(&mr->sequence, SEQ_MSGOUT + seq); + for (i = 0; i < ms->n_msgout - 1; ++i) + out_8(&mr->fifo, ms->msgout[i]); + } + return; + + default: + printk(KERN_ERR "mesh bug: start_phase msgphase=%d\n", + ms->msgphase); + } + + switch (ms->phase) { + case selecting: + out_8(&mr->dest_id, ms->conn_tgt); + out_8(&mr->sequence, SEQ_SELECT + SEQ_ATN); + break; + case commanding: + out_8(&mr->sync_params, tp->sync_params); + out_8(&mr->count_hi, 0); + if (cmd) { + out_8(&mr->count_lo, cmd->cmd_len); + out_8(&mr->sequence, SEQ_COMMAND + seq); + for (i = 0; i < cmd->cmd_len; ++i) + out_8(&mr->fifo, cmd->cmnd[i]); + } else { + out_8(&mr->count_lo, 6); + out_8(&mr->sequence, SEQ_COMMAND + seq); + for (i = 0; i < 6; ++i) + out_8(&mr->fifo, 0); + } + break; + case dataing: + /* transfer data, if any */ + if (!ms->dma_started) { + set_dma_cmds(ms, cmd); + out_le32(&md->cmdptr, virt_to_phys(ms->dma_cmds)); + out_le32(&md->control, (RUN << 16) | RUN); + ms->dma_started = 1; + } + nb = ms->dma_count; + if (nb > 0xfff0) + nb = 0xfff0; + ms->dma_count -= nb; + ms->data_ptr += nb; + out_8(&mr->count_lo, nb); + out_8(&mr->count_hi, nb >> 8); + out_8(&mr->sequence, (tp->data_goes_out? + SEQ_DATAOUT: SEQ_DATAIN) + SEQ_DMA_MODE + seq); + break; + case statusing: + out_8(&mr->count_hi, 0); + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_STATUS + seq); + break; + case busfreeing: + case disconnecting: + out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); + udelay(1); + dlog(ms, "enbresel intr/exc/err/fc=%.8x", + MKWORD(mr->interrupt, mr->exception, mr->error, + mr->fifo_count)); + out_8(&mr->sequence, SEQ_BUSFREE); + break; + default: + printk(KERN_ERR "mesh: start_phase called with phase=%d\n", + ms->phase); + dumpslog(ms); + } + +} + +static inline void get_msgin(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + int i, n; + + n = mr->fifo_count; + if (n != 0) { + i = ms->n_msgin; + ms->n_msgin = i + n; + for (; n > 0; --n) + ms->msgin[i++] = in_8(&mr->fifo); + } +} + +static inline int msgin_length(struct mesh_state *ms) +{ + int b, n; + + n = 1; + if (ms->n_msgin > 0) { + b = ms->msgin[0]; + if (b == 1) { + /* extended message */ + n = ms->n_msgin < 2? 2: ms->msgin[1] + 2; + } else if (0x20 <= b && b <= 0x2f) { + /* 2-byte message */ + n = 2; + } + } + return n; +} + +static void reselected(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + struct scsi_cmnd *cmd; + struct mesh_target *tp; + int b, t, prev; + + switch (ms->phase) { + case idle: + break; + case arbitrating: + if ((cmd = ms->current_req) != NULL) { + /* put the command back on the queue */ + cmd->host_scribble = (void *) ms->request_q; + if (ms->request_q == NULL) + ms->request_qtail = cmd; + ms->request_q = cmd; + tp = &ms->tgts[cmd->device->id]; + tp->current_req = NULL; + } + break; + case busfreeing: + ms->phase = reselecting; + mesh_done(ms, 0); + break; + case disconnecting: + break; + default: + printk(KERN_ERR "mesh: reselected in phase %d/%d tgt %d\n", + ms->msgphase, ms->phase, ms->conn_tgt); + dumplog(ms, ms->conn_tgt); + dumpslog(ms); + } + + if (ms->dma_started) { + printk(KERN_ERR "mesh: reselected with DMA started !\n"); + halt_dma(ms); + } + ms->current_req = NULL; + ms->phase = dataing; + ms->msgphase = msg_in; + ms->n_msgout = 0; + ms->last_n_msgout = 0; + prev = ms->conn_tgt; + + /* + * We seem to get abortive reselections sometimes. + */ + while ((in_8(&mr->bus_status1) & BS1_BSY) == 0) { + static int mesh_aborted_resels; + mesh_aborted_resels++; + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); + udelay(5); + dlog(ms, "extra resel err/exc/fc = %.6x", + MKWORD(0, mr->error, mr->exception, mr->fifo_count)); + } + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->sequence, SEQ_ENBRESEL); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->sync_params, ASYNC_PARAMS); + + /* + * Find out who reselected us. + */ + if (in_8(&mr->fifo_count) == 0) { + printk(KERN_ERR "mesh: reselection but nothing in fifo?\n"); + ms->conn_tgt = ms->host->this_id; + goto bogus; + } + /* get the last byte in the fifo */ + do { + b = in_8(&mr->fifo); + dlog(ms, "reseldata %x", b); + } while (in_8(&mr->fifo_count)); + for (t = 0; t < 8; ++t) + if ((b & (1 << t)) != 0 && t != ms->host->this_id) + break; + if (b != (1 << t) + (1 << ms->host->this_id)) { + printk(KERN_ERR "mesh: bad reselection data %x\n", b); + ms->conn_tgt = ms->host->this_id; + goto bogus; + } + + + /* + * Set up to continue with that target's transfer. + */ + ms->conn_tgt = t; + tp = &ms->tgts[t]; + out_8(&mr->sync_params, tp->sync_params); + if (ALLOW_DEBUG(t)) { + printk(KERN_DEBUG "mesh: reselected by target %d\n", t); + printk(KERN_DEBUG "mesh: saved_ptr=%x goes_out=%d cmd=%p\n", + tp->saved_ptr, tp->data_goes_out, tp->current_req); + } + ms->current_req = tp->current_req; + if (tp->current_req == NULL) { + printk(KERN_ERR "mesh: reselected by tgt %d but no cmd!\n", t); + goto bogus; + } + ms->data_ptr = tp->saved_ptr; + dlog(ms, "resel prev tgt=%d", prev); + dlog(ms, "resel err/exc=%.4x", MKWORD(0, 0, mr->error, mr->exception)); + start_phase(ms); + return; + +bogus: + dumplog(ms, ms->conn_tgt); + dumpslog(ms); + ms->data_ptr = 0; + ms->aborting = 1; + start_phase(ms); +} + +static void do_abort(struct mesh_state *ms) +{ + ms->msgout[0] = ABORT; + ms->n_msgout = 1; + ms->aborting = 1; + ms->stat = DID_ABORT; + dlog(ms, "abort", 0); +} + +static void handle_reset(struct mesh_state *ms) +{ + int tgt; + struct mesh_target *tp; + struct scsi_cmnd *cmd; + volatile struct mesh_regs __iomem *mr = ms->mesh; + + for (tgt = 0; tgt < 8; ++tgt) { + tp = &ms->tgts[tgt]; + if ((cmd = tp->current_req) != NULL) { + cmd->result = DID_RESET << 16; + tp->current_req = NULL; + mesh_completed(ms, cmd); + } + ms->tgts[tgt].sdtr_state = do_sdtr; + ms->tgts[tgt].sync_params = ASYNC_PARAMS; + } + ms->current_req = NULL; + while ((cmd = ms->request_q) != NULL) { + ms->request_q = (struct scsi_cmnd *) cmd->host_scribble; + cmd->result = DID_RESET << 16; + mesh_completed(ms, cmd); + } + ms->phase = idle; + ms->msgphase = msg_none; + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->sync_params, ASYNC_PARAMS); + out_8(&mr->sequence, SEQ_ENBRESEL); +} + +static irqreturn_t do_mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) +{ + unsigned long flags; + struct Scsi_Host *dev = ((struct mesh_state *)dev_id)->host; + + spin_lock_irqsave(dev->host_lock, flags); + mesh_interrupt(irq, dev_id, ptregs); + spin_unlock_irqrestore(dev->host_lock, flags); + return IRQ_HANDLED; +} + +static void handle_error(struct mesh_state *ms) +{ + int err, exc, count; + volatile struct mesh_regs __iomem *mr = ms->mesh; + + err = in_8(&mr->error); + exc = in_8(&mr->exception); + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + dlog(ms, "error err/exc/fc/cl=%.8x", + MKWORD(err, exc, mr->fifo_count, mr->count_lo)); + if (err & ERR_SCSIRESET) { + /* SCSI bus was reset */ + printk(KERN_INFO "mesh: SCSI bus reset detected: " + "waiting for end..."); + while ((in_8(&mr->bus_status1) & BS1_RST) != 0) + udelay(1); + printk("done\n"); + handle_reset(ms); + /* request_q is empty, no point in mesh_start() */ + return; + } + if (err & ERR_UNEXPDISC) { + /* Unexpected disconnect */ + if (exc & EXC_RESELECTED) { + reselected(ms); + return; + } + if (!ms->aborting) { + printk(KERN_WARNING "mesh: target %d aborted\n", + ms->conn_tgt); + dumplog(ms, ms->conn_tgt); + dumpslog(ms); + } + out_8(&mr->interrupt, INT_CMDDONE); + ms->stat = DID_ABORT; + mesh_done(ms, 1); + return; + } + if (err & ERR_PARITY) { + if (ms->msgphase == msg_in) { + printk(KERN_ERR "mesh: msg parity error, target %d\n", + ms->conn_tgt); + ms->msgout[0] = MSG_PARITY_ERROR; + ms->n_msgout = 1; + ms->msgphase = msg_in_bad; + cmd_complete(ms); + return; + } + if (ms->stat == DID_OK) { + printk(KERN_ERR "mesh: parity error, target %d\n", + ms->conn_tgt); + ms->stat = DID_PARITY; + } + count = (mr->count_hi << 8) + mr->count_lo; + if (count == 0) { + cmd_complete(ms); + } else { + /* reissue the data transfer command */ + out_8(&mr->sequence, mr->sequence); + } + return; + } + if (err & ERR_SEQERR) { + if (exc & EXC_RESELECTED) { + /* This can happen if we issue a command to + get the bus just after the target reselects us. */ + static int mesh_resel_seqerr; + mesh_resel_seqerr++; + reselected(ms); + return; + } + if (exc == EXC_PHASEMM) { + static int mesh_phasemm_seqerr; + mesh_phasemm_seqerr++; + phase_mismatch(ms); + return; + } + printk(KERN_ERR "mesh: sequence error (err=%x exc=%x)\n", + err, exc); + } else { + printk(KERN_ERR "mesh: unknown error %x (exc=%x)\n", err, exc); + } + mesh_dump_regs(ms); + dumplog(ms, ms->conn_tgt); + if (ms->phase > selecting && (in_8(&mr->bus_status1) & BS1_BSY)) { + /* try to do what the target wants */ + do_abort(ms); + phase_mismatch(ms); + return; + } + ms->stat = DID_ERROR; + mesh_done(ms, 1); +} + +static void handle_exception(struct mesh_state *ms) +{ + int exc; + volatile struct mesh_regs __iomem *mr = ms->mesh; + + exc = in_8(&mr->exception); + out_8(&mr->interrupt, INT_EXCEPTION | INT_CMDDONE); + if (exc & EXC_RESELECTED) { + static int mesh_resel_exc; + mesh_resel_exc++; + reselected(ms); + } else if (exc == EXC_ARBLOST) { + printk(KERN_DEBUG "mesh: lost arbitration\n"); + ms->stat = DID_BUS_BUSY; + mesh_done(ms, 1); + } else if (exc == EXC_SELTO) { + /* selection timed out */ + ms->stat = DID_BAD_TARGET; + mesh_done(ms, 1); + } else if (exc == EXC_PHASEMM) { + /* target wants to do something different: + find out what it wants and do it. */ + phase_mismatch(ms); + } else { + printk(KERN_ERR "mesh: can't cope with exception %x\n", exc); + mesh_dump_regs(ms); + dumplog(ms, ms->conn_tgt); + do_abort(ms); + phase_mismatch(ms); + } +} + +static void handle_msgin(struct mesh_state *ms) +{ + int i, code; + struct scsi_cmnd *cmd = ms->current_req; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + + if (ms->n_msgin == 0) + return; + code = ms->msgin[0]; + if (ALLOW_DEBUG(ms->conn_tgt)) { + printk(KERN_DEBUG "got %d message bytes:", ms->n_msgin); + for (i = 0; i < ms->n_msgin; ++i) + printk(" %x", ms->msgin[i]); + printk("\n"); + } + dlog(ms, "msgin msg=%.8x", + MKWORD(ms->n_msgin, code, ms->msgin[1], ms->msgin[2])); + + ms->expect_reply = 0; + ms->n_msgout = 0; + if (ms->n_msgin < msgin_length(ms)) + goto reject; + if (cmd) + cmd->SCp.Message = code; + switch (code) { + case COMMAND_COMPLETE: + break; + case EXTENDED_MESSAGE: + switch (ms->msgin[2]) { + case EXTENDED_MODIFY_DATA_POINTER: + ms->data_ptr += (ms->msgin[3] << 24) + ms->msgin[6] + + (ms->msgin[4] << 16) + (ms->msgin[5] << 8); + break; + case EXTENDED_SDTR: + if (tp->sdtr_state != sdtr_sent) { + /* reply with an SDTR */ + add_sdtr_msg(ms); + /* limit period to at least his value, + offset to no more than his */ + if (ms->msgout[3] < ms->msgin[3]) + ms->msgout[3] = ms->msgin[3]; + if (ms->msgout[4] > ms->msgin[4]) + ms->msgout[4] = ms->msgin[4]; + set_sdtr(ms, ms->msgout[3], ms->msgout[4]); + ms->msgphase = msg_out; + } else { + set_sdtr(ms, ms->msgin[3], ms->msgin[4]); + } + break; + default: + goto reject; + } + break; + case SAVE_POINTERS: + tp->saved_ptr = ms->data_ptr; + break; + case RESTORE_POINTERS: + ms->data_ptr = tp->saved_ptr; + break; + case DISCONNECT: + ms->phase = disconnecting; + break; + case ABORT: + break; + case MESSAGE_REJECT: + if (tp->sdtr_state == sdtr_sent) + set_sdtr(ms, 0, 0); + break; + case NOP: + break; + default: + if (IDENTIFY_BASE <= code && code <= IDENTIFY_BASE + 7) { + if (cmd == NULL) { + do_abort(ms); + ms->msgphase = msg_out; + } else if (code != cmd->device->lun + IDENTIFY_BASE) { + printk(KERN_WARNING "mesh: lun mismatch " + "(%d != %d) on reselection from " + "target %d\n", code - IDENTIFY_BASE, + cmd->device->lun, ms->conn_tgt); + } + break; + } + goto reject; + } + return; + + reject: + printk(KERN_WARNING "mesh: rejecting message from target %d:", + ms->conn_tgt); + for (i = 0; i < ms->n_msgin; ++i) + printk(" %x", ms->msgin[i]); + printk("\n"); + ms->msgout[0] = MESSAGE_REJECT; + ms->n_msgout = 1; + ms->msgphase = msg_out; +} + +/* + * Set up DMA commands for transferring data. + */ +static void set_dma_cmds(struct mesh_state *ms, struct scsi_cmnd *cmd) +{ + int i, dma_cmd, total, off, dtot; + struct scatterlist *scl; + struct dbdma_cmd *dcmds; + + dma_cmd = ms->tgts[ms->conn_tgt].data_goes_out? + OUTPUT_MORE: INPUT_MORE; + dcmds = ms->dma_cmds; + dtot = 0; + if (cmd) { + cmd->SCp.this_residual = cmd->request_bufflen; + if (cmd->use_sg > 0) { + int nseg; + total = 0; + scl = (struct scatterlist *) cmd->buffer; + off = ms->data_ptr; + nseg = pci_map_sg(ms->pdev, scl, cmd->use_sg, + cmd->sc_data_direction); + for (i = 0; i <nseg; ++i, ++scl) { + u32 dma_addr = sg_dma_address(scl); + u32 dma_len = sg_dma_len(scl); + + total += scl->length; + if (off >= dma_len) { + off -= dma_len; + continue; + } + if (dma_len > 0xffff) + panic("mesh: scatterlist element >= 64k"); + st_le16(&dcmds->req_count, dma_len - off); + st_le16(&dcmds->command, dma_cmd); + st_le32(&dcmds->phy_addr, dma_addr + off); + dcmds->xfer_status = 0; + ++dcmds; + dtot += dma_len - off; + off = 0; + } + } else if (ms->data_ptr < cmd->request_bufflen) { + dtot = cmd->request_bufflen - ms->data_ptr; + if (dtot > 0xffff) + panic("mesh: transfer size >= 64k"); + st_le16(&dcmds->req_count, dtot); + /* XXX Use pci DMA API here ... */ + st_le32(&dcmds->phy_addr, + virt_to_phys(cmd->request_buffer) + ms->data_ptr); + dcmds->xfer_status = 0; + ++dcmds; + } + } + if (dtot == 0) { + /* Either the target has overrun our buffer, + or the caller didn't provide a buffer. */ + static char mesh_extra_buf[64]; + + dtot = sizeof(mesh_extra_buf); + st_le16(&dcmds->req_count, dtot); + st_le32(&dcmds->phy_addr, virt_to_phys(mesh_extra_buf)); + dcmds->xfer_status = 0; + ++dcmds; + } + dma_cmd += OUTPUT_LAST - OUTPUT_MORE; + st_le16(&dcmds[-1].command, dma_cmd); + memset(dcmds, 0, sizeof(*dcmds)); + st_le16(&dcmds->command, DBDMA_STOP); + ms->dma_count = dtot; +} + +static void halt_dma(struct mesh_state *ms) +{ + volatile struct dbdma_regs __iomem *md = ms->dma; + volatile struct mesh_regs __iomem *mr = ms->mesh; + struct scsi_cmnd *cmd = ms->current_req; + int t, nb; + + if (!ms->tgts[ms->conn_tgt].data_goes_out) { + /* wait a little while until the fifo drains */ + t = 50; + while (t > 0 && in_8(&mr->fifo_count) != 0 + && (in_le32(&md->status) & ACTIVE) != 0) { + --t; + udelay(1); + } + } + out_le32(&md->control, RUN << 16); /* turn off RUN bit */ + nb = (mr->count_hi << 8) + mr->count_lo; + dlog(ms, "halt_dma fc/count=%.6x", + MKWORD(0, mr->fifo_count, 0, nb)); + if (ms->tgts[ms->conn_tgt].data_goes_out) + nb += mr->fifo_count; + /* nb is the number of bytes not yet transferred + to/from the target. */ + ms->data_ptr -= nb; + dlog(ms, "data_ptr %x", ms->data_ptr); + if (ms->data_ptr < 0) { + printk(KERN_ERR "mesh: halt_dma: data_ptr=%d (nb=%d, ms=%p)\n", + ms->data_ptr, nb, ms); + ms->data_ptr = 0; +#ifdef MESH_DBG + dumplog(ms, ms->conn_tgt); + dumpslog(ms); +#endif /* MESH_DBG */ + } else if (cmd && cmd->request_bufflen != 0 && + ms->data_ptr > cmd->request_bufflen) { + printk(KERN_DEBUG "mesh: target %d overrun, " + "data_ptr=%x total=%x goes_out=%d\n", + ms->conn_tgt, ms->data_ptr, cmd->request_bufflen, + ms->tgts[ms->conn_tgt].data_goes_out); + } + if (cmd->use_sg != 0) { + struct scatterlist *sg; + sg = (struct scatterlist *)cmd->request_buffer; + pci_unmap_sg(ms->pdev, sg, cmd->use_sg, cmd->sc_data_direction); + } + ms->dma_started = 0; +} + +static void phase_mismatch(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + int phase; + + dlog(ms, "phasemm ch/cl/seq/fc=%.8x", + MKWORD(mr->count_hi, mr->count_lo, mr->sequence, mr->fifo_count)); + phase = in_8(&mr->bus_status0) & BS0_PHASE; + if (ms->msgphase == msg_out_xxx && phase == BP_MSGOUT) { + /* output the last byte of the message, without ATN */ + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); + ms->msgphase = msg_out_last; + return; + } + + if (ms->msgphase == msg_in) { + get_msgin(ms); + if (ms->n_msgin) + handle_msgin(ms); + } + + if (ms->dma_started) + halt_dma(ms); + if (mr->fifo_count) { + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + } + + ms->msgphase = msg_none; + switch (phase) { + case BP_DATAIN: + ms->tgts[ms->conn_tgt].data_goes_out = 0; + ms->phase = dataing; + break; + case BP_DATAOUT: + ms->tgts[ms->conn_tgt].data_goes_out = 1; + ms->phase = dataing; + break; + case BP_COMMAND: + ms->phase = commanding; + break; + case BP_STATUS: + ms->phase = statusing; + break; + case BP_MSGIN: + ms->msgphase = msg_in; + ms->n_msgin = 0; + break; + case BP_MSGOUT: + ms->msgphase = msg_out; + if (ms->n_msgout == 0) { + if (ms->aborting) { + do_abort(ms); + } else { + if (ms->last_n_msgout == 0) { + printk(KERN_DEBUG + "mesh: no msg to repeat\n"); + ms->msgout[0] = NOP; + ms->last_n_msgout = 1; + } + ms->n_msgout = ms->last_n_msgout; + } + } + break; + default: + printk(KERN_DEBUG "mesh: unknown scsi phase %x\n", phase); + ms->stat = DID_ERROR; + mesh_done(ms, 1); + return; + } + + start_phase(ms); +} + +static void cmd_complete(struct mesh_state *ms) +{ + volatile struct mesh_regs __iomem *mr = ms->mesh; + struct scsi_cmnd *cmd = ms->current_req; + struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; + int seq, n, t; + + dlog(ms, "cmd_complete fc=%x", mr->fifo_count); + seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); + switch (ms->msgphase) { + case msg_out_xxx: + /* huh? we expected a phase mismatch */ + ms->n_msgin = 0; + ms->msgphase = msg_in; + /* fall through */ + + case msg_in: + /* should have some message bytes in fifo */ + get_msgin(ms); + n = msgin_length(ms); + if (ms->n_msgin < n) { + out_8(&mr->count_lo, n - ms->n_msgin); + out_8(&mr->sequence, SEQ_MSGIN + seq); + } else { + ms->msgphase = msg_none; + handle_msgin(ms); + start_phase(ms); + } + break; + + case msg_in_bad: + out_8(&mr->sequence, SEQ_FLUSHFIFO); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGIN + SEQ_ATN + use_active_neg); + break; + + case msg_out: + /* + * To get the right timing on ATN wrt ACK, we have + * to get the MESH to drop ACK, wait until REQ gets + * asserted, then drop ATN. To do this we first + * issue a SEQ_MSGOUT with ATN and wait for REQ, + * then change the command to a SEQ_MSGOUT w/o ATN. + * If we don't see REQ in a reasonable time, we + * change the command to SEQ_MSGIN with ATN, + * wait for the phase mismatch interrupt, then + * issue the SEQ_MSGOUT without ATN. + */ + out_8(&mr->count_lo, 1); + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg + SEQ_ATN); + t = 30; /* wait up to 30us */ + while ((in_8(&mr->bus_status0) & BS0_REQ) == 0 && --t >= 0) + udelay(1); + dlog(ms, "last_mbyte err/exc/fc/cl=%.8x", + MKWORD(mr->error, mr->exception, + mr->fifo_count, mr->count_lo)); + if (in_8(&mr->interrupt) & (INT_ERROR | INT_EXCEPTION)) { + /* whoops, target didn't do what we expected */ + ms->last_n_msgout = ms->n_msgout; + ms->n_msgout = 0; + if (in_8(&mr->interrupt) & INT_ERROR) { + printk(KERN_ERR "mesh: error %x in msg_out\n", + in_8(&mr->error)); + handle_error(ms); + return; + } + if (in_8(&mr->exception) != EXC_PHASEMM) + printk(KERN_ERR "mesh: exc %x in msg_out\n", + in_8(&mr->exception)); + else + printk(KERN_DEBUG "mesh: bs0=%x in msg_out\n", + in_8(&mr->bus_status0)); + handle_exception(ms); + return; + } + if (in_8(&mr->bus_status0) & BS0_REQ) { + out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); + ms->msgphase = msg_out_last; + } else { + out_8(&mr->sequence, SEQ_MSGIN + use_active_neg + SEQ_ATN); + ms->msgphase = msg_out_xxx; + } + break; + + case msg_out_last: + ms->last_n_msgout = ms->n_msgout; + ms->n_msgout = 0; + ms->msgphase = ms->expect_reply? msg_in: msg_none; + start_phase(ms); + break; + + case msg_none: + switch (ms->phase) { + case idle: + printk(KERN_ERR "mesh: interrupt in idle phase?\n"); + dumpslog(ms); + return; + case selecting: + dlog(ms, "Selecting phase at command completion",0); + ms->msgout[0] = IDENTIFY(ALLOW_RESEL(ms->conn_tgt), + (cmd? cmd->device->lun: 0)); + ms->n_msgout = 1; + ms->expect_reply = 0; + if (ms->aborting) { + ms->msgout[0] = ABORT; + ms->n_msgout++; + } else if (tp->sdtr_state == do_sdtr) { + /* add SDTR message */ + add_sdtr_msg(ms); + ms->expect_reply = 1; + tp->sdtr_state = sdtr_sent; + } + ms->msgphase = msg_out; + /* + * We need to wait for REQ before dropping ATN. + * We wait for at most 30us, then fall back to + * a scheme where we issue a SEQ_COMMAND with ATN, + * which will give us a phase mismatch interrupt + * when REQ does come, and then we send the message. + */ + t = 230; /* wait up to 230us */ + while ((in_8(&mr->bus_status0) & BS0_REQ) == 0) { + if (--t < 0) { + dlog(ms, "impatient for req", ms->n_msgout); + ms->msgphase = msg_none; + break; + } + udelay(1); + } + break; + case dataing: + if (ms->dma_count != 0) { + start_phase(ms); + return; + } + /* + * We can get a phase mismatch here if the target + * changes to the status phase, even though we have + * had a command complete interrupt. Then, if we + * issue the SEQ_STATUS command, we'll get a sequence + * error interrupt. Which isn't so bad except that + * occasionally the mesh actually executes the + * SEQ_STATUS *as well as* giving us the sequence + * error and phase mismatch exception. + */ + out_8(&mr->sequence, 0); + out_8(&mr->interrupt, + INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + halt_dma(ms); + break; + case statusing: + if (cmd) { + cmd->SCp.Status = mr->fifo; + if (DEBUG_TARGET(cmd)) + printk(KERN_DEBUG "mesh: status is %x\n", + cmd->SCp.Status); + } + ms->msgphase = msg_in; + break; + case busfreeing: + mesh_done(ms, 1); + return; + case disconnecting: + ms->current_req = NULL; + ms->phase = idle; + mesh_start(ms); + return; + default: + break; + } + ++ms->phase; + start_phase(ms); + break; + } +} + + +/* + * Called by midlayer with host locked to queue a new + * request + */ +static int mesh_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) +{ + struct mesh_state *ms; + + cmd->scsi_done = done; + cmd->host_scribble = NULL; + + ms = (struct mesh_state *) cmd->device->host->hostdata; + + if (ms->request_q == NULL) + ms->request_q = cmd; + else + ms->request_qtail->host_scribble = (void *) cmd; + ms->request_qtail = cmd; + + if (ms->phase == idle) + mesh_start(ms); + + return 0; +} + +/* + * Called to handle interrupts, either call by the interrupt + * handler (do_mesh_interrupt) or by other functions in + * exceptional circumstances + */ +static void mesh_interrupt(int irq, void *dev_id, struct pt_regs *ptregs) +{ + struct mesh_state *ms = (struct mesh_state *) dev_id; + volatile struct mesh_regs __iomem *mr = ms->mesh; + int intr; + +#if 0 + if (ALLOW_DEBUG(ms->conn_tgt)) + printk(KERN_DEBUG "mesh_intr, bs0=%x int=%x exc=%x err=%x " + "phase=%d msgphase=%d\n", mr->bus_status0, + mr->interrupt, mr->exception, mr->error, + ms->phase, ms->msgphase); +#endif + while ((intr = in_8(&mr->interrupt)) != 0) { + dlog(ms, "interrupt intr/err/exc/seq=%.8x", + MKWORD(intr, mr->error, mr->exception, mr->sequence)); + if (intr & INT_ERROR) { + handle_error(ms); + } else if (intr & INT_EXCEPTION) { + handle_exception(ms); + } else if (intr & INT_CMDDONE) { + out_8(&mr->interrupt, INT_CMDDONE); + cmd_complete(ms); + } + } +} + +/* Todo: here we can at least try to remove the command from the + * queue if it isn't connected yet, and for pending command, assert + * ATN until the bus gets freed. + */ +static int mesh_abort(struct scsi_cmnd *cmd) +{ + struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; + + printk(KERN_DEBUG "mesh_abort(%p)\n", cmd); + mesh_dump_regs(ms); + dumplog(ms, cmd->device->id); + dumpslog(ms); + return FAILED; +} + +/* + * Called by the midlayer with the lock held to reset the + * SCSI host and bus. + * The midlayer will wait for devices to come back, we don't need + * to do that ourselves + */ +static int mesh_host_reset(struct scsi_cmnd *cmd) +{ + struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; + volatile struct mesh_regs __iomem *mr = ms->mesh; + volatile struct dbdma_regs __iomem *md = ms->dma; + + printk(KERN_DEBUG "mesh_host_reset\n"); + + /* Reset the controller & dbdma channel */ + out_le32(&md->control, (RUN|PAUSE|FLUSH|WAKE) << 16); /* stop dma */ + out_8(&mr->exception, 0xff); /* clear all exception bits */ + out_8(&mr->error, 0xff); /* clear all error bits */ + out_8(&mr->sequence, SEQ_RESETMESH); + mesh_flush_io(mr); + udelay(1); + out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->source_id, ms->host->this_id); + out_8(&mr->sel_timeout, 25); /* 250ms */ + out_8(&mr->sync_params, ASYNC_PARAMS); + + /* Reset the bus */ + out_8(&mr->bus_status1, BS1_RST); /* assert RST */ + mesh_flush_io(mr); + udelay(30); /* leave it on for >= 25us */ + out_8(&mr->bus_status1, 0); /* negate RST */ + + /* Complete pending commands */ + handle_reset(ms); + + return SUCCESS; +} + +static void set_mesh_power(struct mesh_state *ms, int state) +{ + if (_machine != _MACH_Pmac) + return; + if (state) { + pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 1); + msleep(200); + } else { + pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 0); + msleep(10); + } +} + + +#ifdef CONFIG_PM +static int mesh_suspend(struct macio_dev *mdev, u32 state) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + unsigned long flags; + + if (state == mdev->ofdev.dev.power.power_state || state < 2) + return 0; + + scsi_block_requests(ms->host); + spin_lock_irqsave(ms->host->host_lock, flags); + while(ms->phase != idle) { + spin_unlock_irqrestore(ms->host->host_lock, flags); + msleep(10); + spin_lock_irqsave(ms->host->host_lock, flags); + } + ms->phase = sleeping; + spin_unlock_irqrestore(ms->host->host_lock, flags); + disable_irq(ms->meshintr); + set_mesh_power(ms, 0); + + mdev->ofdev.dev.power.power_state = state; + + return 0; +} + +static int mesh_resume(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + unsigned long flags; + + if (mdev->ofdev.dev.power.power_state == 0) + return 0; + + set_mesh_power(ms, 1); + mesh_init(ms); + spin_lock_irqsave(ms->host->host_lock, flags); + mesh_start(ms); + spin_unlock_irqrestore(ms->host->host_lock, flags); + enable_irq(ms->meshintr); + scsi_unblock_requests(ms->host); + + mdev->ofdev.dev.power.power_state = 0; + + return 0; +} + +#endif /* CONFIG_PM */ + +/* + * If we leave drives set for synchronous transfers (especially + * CDROMs), and reboot to MacOS, it gets confused, poor thing. + * So, on reboot we reset the SCSI bus. + */ +static int mesh_shutdown(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + volatile struct mesh_regs __iomem *mr; + unsigned long flags; + + printk(KERN_INFO "resetting MESH scsi bus(es)\n"); + spin_lock_irqsave(ms->host->host_lock, flags); + mr = ms->mesh; + out_8(&mr->intr_mask, 0); + out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); + out_8(&mr->bus_status1, BS1_RST); + mesh_flush_io(mr); + udelay(30); + out_8(&mr->bus_status1, 0); + spin_unlock_irqrestore(ms->host->host_lock, flags); + + return 0; +} + +static struct scsi_host_template mesh_template = { + .proc_name = "mesh", + .name = "MESH", + .queuecommand = mesh_queue, + .eh_abort_handler = mesh_abort, + .eh_host_reset_handler = mesh_host_reset, + .can_queue = 20, + .this_id = 7, + .sg_tablesize = SG_ALL, + .cmd_per_lun = 2, + .use_clustering = DISABLE_CLUSTERING, +}; + +static int mesh_probe(struct macio_dev *mdev, const struct of_match *match) +{ + struct device_node *mesh = macio_get_of_node(mdev); + struct pci_dev* pdev = macio_get_pci_dev(mdev); + int tgt, *cfp, minper; + struct mesh_state *ms; + struct Scsi_Host *mesh_host; + void *dma_cmd_space; + dma_addr_t dma_cmd_bus; + + switch (mdev->bus->chip->type) { + case macio_heathrow: + case macio_gatwick: + case macio_paddington: + use_active_neg = 0; + break; + default: + use_active_neg = SEQ_ACTIVE_NEG; + } + + if (macio_resource_count(mdev) != 2 || macio_irq_count(mdev) != 2) { + printk(KERN_ERR "mesh: expected 2 addrs and 2 intrs" + " (got %d,%d)\n", mesh->n_addrs, mesh->n_intrs); + return -ENODEV; + } + + if (macio_request_resources(mdev, "mesh") != 0) { + printk(KERN_ERR "mesh: unable to request memory resources"); + return -EBUSY; + } + mesh_host = scsi_host_alloc(&mesh_template, sizeof(struct mesh_state)); + if (mesh_host == NULL) { + printk(KERN_ERR "mesh: couldn't register host"); + goto out_release; + } + + /* Old junk for root discovery, that will die ultimately */ +#if !defined(MODULE) + note_scsi_host(mesh, mesh_host); +#endif + + mesh_host->base = macio_resource_start(mdev, 0); + mesh_host->irq = macio_irq(mdev, 0); + ms = (struct mesh_state *) mesh_host->hostdata; + macio_set_drvdata(mdev, ms); + ms->host = mesh_host; + ms->mdev = mdev; + ms->pdev = pdev; + + ms->mesh = ioremap(macio_resource_start(mdev, 0), 0x1000); + if (ms->mesh == NULL) { + printk(KERN_ERR "mesh: can't map registers\n"); + goto out_free; + } + ms->dma = ioremap(macio_resource_start(mdev, 1), 0x1000); + if (ms->dma == NULL) { + printk(KERN_ERR "mesh: can't map registers\n"); + iounmap(ms->mesh); + goto out_free; + } + + ms->meshintr = macio_irq(mdev, 0); + ms->dmaintr = macio_irq(mdev, 1); + + /* Space for dma command list: +1 for stop command, + * +1 to allow for aligning. + */ + ms->dma_cmd_size = (mesh_host->sg_tablesize + 2) * sizeof(struct dbdma_cmd); + + /* We use the PCI APIs for now until the generic one gets fixed + * enough or until we get some macio-specific versions + */ + dma_cmd_space = pci_alloc_consistent(macio_get_pci_dev(mdev), + ms->dma_cmd_size, + &dma_cmd_bus); + if (dma_cmd_space == NULL) { + printk(KERN_ERR "mesh: can't allocate DMA table\n"); + goto out_unmap; + } + memset(dma_cmd_space, 0, ms->dma_cmd_size); + + ms->dma_cmds = (struct dbdma_cmd *) DBDMA_ALIGN(dma_cmd_space); + ms->dma_cmd_space = dma_cmd_space; + ms->dma_cmd_bus = dma_cmd_bus + ((unsigned long)ms->dma_cmds) + - (unsigned long)dma_cmd_space; + ms->current_req = NULL; + for (tgt = 0; tgt < 8; ++tgt) { + ms->tgts[tgt].sdtr_state = do_sdtr; + ms->tgts[tgt].sync_params = ASYNC_PARAMS; + ms->tgts[tgt].current_req = NULL; + } + + if ((cfp = (int *) get_property(mesh, "clock-frequency", NULL))) + ms->clk_freq = *cfp; + else { + printk(KERN_INFO "mesh: assuming 50MHz clock frequency\n"); + ms->clk_freq = 50000000; + } + + /* The maximum sync rate is clock / 5; increase + * mesh_sync_period if necessary. + */ + minper = 1000000000 / (ms->clk_freq / 5); /* ns */ + if (mesh_sync_period < minper) + mesh_sync_period = minper; + + /* Power up the chip */ + set_mesh_power(ms, 1); + + /* Set it up */ + mesh_init(ms); + + /* XXX FIXME: error should be fatal */ + if (request_irq(ms->meshintr, do_mesh_interrupt, 0, "MESH", ms)) + printk(KERN_ERR "MESH: can't get irq %d\n", ms->meshintr); + + /* XXX FIXME: handle failure */ + scsi_add_host(mesh_host, &mdev->ofdev.dev); + scsi_scan_host(mesh_host); + + return 0; + +out_unmap: + iounmap(ms->dma); + iounmap(ms->mesh); +out_free: + scsi_host_put(mesh_host); +out_release: + macio_release_resources(mdev); + + return -ENODEV; +} + +static int mesh_remove(struct macio_dev *mdev) +{ + struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); + struct Scsi_Host *mesh_host = ms->host; + + scsi_remove_host(mesh_host); + + free_irq(ms->meshintr, ms); + + /* Reset scsi bus */ + mesh_shutdown(mdev); + + /* Shut down chip & termination */ + set_mesh_power(ms, 0); + + /* Unmap registers & dma controller */ + iounmap(ms->mesh); + iounmap(ms->dma); + + /* Free DMA commands memory */ + pci_free_consistent(macio_get_pci_dev(mdev), ms->dma_cmd_size, + ms->dma_cmd_space, ms->dma_cmd_bus); + + /* Release memory resources */ + macio_release_resources(mdev); + + scsi_host_put(mesh_host); + + return 0; +} + + +static struct of_match mesh_match[] = +{ + { + .name = "mesh", + .type = OF_ANY_MATCH, + .compatible = OF_ANY_MATCH + }, + { + .name = OF_ANY_MATCH, + .type = "scsi", + .compatible = "chrp,mesh0" + }, + {}, +}; + +static struct macio_driver mesh_driver = +{ + .name = "mesh", + .match_table = mesh_match, + .probe = mesh_probe, + .remove = mesh_remove, + .shutdown = mesh_shutdown, +#ifdef CONFIG_PM + .suspend = mesh_suspend, + .resume = mesh_resume, +#endif +}; + + +static int __init init_mesh(void) +{ + + /* Calculate sync rate from module parameters */ + if (sync_rate > 10) + sync_rate = 10; + if (sync_rate > 0) { + printk(KERN_INFO "mesh: configured for synchronous %d MB/s\n", sync_rate); + mesh_sync_period = 1000 / sync_rate; /* ns */ + mesh_sync_offset = 15; + } else + printk(KERN_INFO "mesh: configured for asynchronous\n"); + + return macio_register_driver(&mesh_driver); +} + +static void __exit exit_mesh(void) +{ + return macio_unregister_driver(&mesh_driver); +} + +module_init(init_mesh); +module_exit(exit_mesh); |