Release 4.11 drivers/scsi/mesh.c
/*
* 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/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/string.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 <linux/pci.h>
#include <asm/dbdma.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/prom.h>
#include <asm/irq.h>
#include <asm/hydra.h>
#include <asm/processor.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.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(struct mesh_state *ms);
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 24 | 85.71% | 1 | 33.33% |
Linus Torvalds (pre-git) | 4 | 14.29% | 2 | 66.67% |
Total | 28 | 100.00% | 3 | 100.00% |
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 134 | 70.53% | 1 | 20.00% |
Linus Torvalds (pre-git) | 52 | 27.37% | 2 | 40.00% |
Paul Mackerras | 4 | 2.11% | 2 | 40.00% |
Total | 190 | 100.00% | 5 | 100.00% |
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);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 101 | 66.01% | 1 | 25.00% |
Linus Torvalds (pre-git) | 52 | 33.99% | 3 | 75.00% |
Total | 153 | 100.00% | 4 | 100.00% |
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);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 108 | 77.70% | 1 | 50.00% |
Linus Torvalds (pre-git) | 31 | 22.30% | 1 | 50.00% |
Total | 139 | 100.00% | 2 | 100.00% |
#else
static inline void dlog(struct mesh_state *ms, char *fmt, int a)
{}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 17 | 94.44% | 1 | 50.00% |
Linus Torvalds (pre-git) | 1 | 5.56% | 1 | 50.00% |
Total | 18 | 100.00% | 2 | 100.00% |
static inline void dumplog(struct mesh_state *ms, int tgt)
{}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 11 | 78.57% | 1 | 50.00% |
Linus Torvalds (pre-git) | 3 | 21.43% | 1 | 50.00% |
Total | 14 | 100.00% | 2 | 100.00% |
static inline void dumpslog(struct mesh_state *ms)
{}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 7 | 63.64% | 1 | 50.00% |
Linus Torvalds (pre-git) | 4 | 36.36% | 1 | 50.00% |
Total | 11 | 100.00% | 2 | 100.00% |
#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);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 154 | 61.11% | 1 | 25.00% |
Linus Torvalds (pre-git) | 96 | 38.10% | 2 | 50.00% |
Al Viro | 2 | 0.79% | 1 | 25.00% |
Total | 252 | 100.00% | 4 | 100.00% |
/*
* 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);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 17 | 68.00% | 1 | 33.33% |
Linus Torvalds (pre-git) | 7 | 28.00% | 1 | 33.33% |
Al Viro | 1 | 4.00% | 1 | 33.33% |
Total | 25 | 100.00% | 3 | 100.00% |
/*
* Complete a SCSI command
*/
static void mesh_completed(struct mesh_state *ms, struct scsi_cmnd *cmd)
{
(*cmd->scsi_done)(cmd);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 25 | 96.15% | 2 | 66.67% |
Linus Torvalds (pre-git) | 1 | 3.85% | 1 | 33.33% |
Total | 26 | 100.00% | 3 | 100.00% |
/* 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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 195 | 70.40% | 2 | 33.33% |
Benjamin Herrenschmidt | 48 | 17.33% | 1 | 16.67% |
Paul Mackerras | 31 | 11.19% | 1 | 16.67% |
Al Viro | 2 | 0.72% | 1 | 16.67% |
Maximilian Attems | 1 | 0.36% | 1 | 16.67% |
Total | 277 | 100.00% | 6 | 100.00% |
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 tgt=%d cmd=", cmd, id);
for (i = 0; i < cmd->cmd_len; ++i)
printk(" %x", cmd->cmnd[i]);
printk(" use_sg=%d buffer=%p bufflen=%u\n",
scsi_sg_count(cmd), scsi_sglist(cmd), scsi_bufflen(cmd));
}
#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(ms);
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(ms);
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
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 636 | 70.82% | 2 | 20.00% |
Linus Torvalds (pre-git) | 235 | 26.17% | 3 | 30.00% |
Paul Mackerras | 16 | 1.78% | 2 | 20.00% |
FUJITA Tomonori | 9 | 1.00% | 1 | 10.00% |
Al Viro | 1 | 0.11% | 1 | 10.00% |
Christoph Hellwig | 1 | 0.11% | 1 | 10.00% |
Total | 898 | 100.00% | 10 | 100.00% |
/*
* 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);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Herrenschmidt | 137 | 81.07% | 2 | 50.00% |
Linus Torvalds (pre-git) | 32 | 18.93% | 2 | 50.00% |
Total | 169 | 100.00% | 4 | 100.00% |
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