cregit-Linux how code gets into the kernel

Release 4.11 drivers/scsi/mesh.c

Directory: drivers/scsi
/*
 * 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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt2485.71%133.33%
Linus Torvalds (pre-git)414.29%266.67%
Total28100.00%3100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt13470.53%120.00%
Linus Torvalds (pre-git)5227.37%240.00%
Paul Mackerras42.11%240.00%
Total190100.00%5100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt10166.01%125.00%
Linus Torvalds (pre-git)5233.99%375.00%
Total153100.00%4100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt10877.70%150.00%
Linus Torvalds (pre-git)3122.30%150.00%
Total139100.00%2100.00%

#else
static inline void dlog(struct mesh_state *ms, char *fmt, int a) {}

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt1794.44%150.00%
Linus Torvalds (pre-git)15.56%150.00%
Total18100.00%2100.00%


static inline void dumplog(struct mesh_state *ms, int tgt) {}

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt1178.57%150.00%
Linus Torvalds (pre-git)321.43%150.00%
Total14100.00%2100.00%


static inline void dumpslog(struct mesh_state *ms) {}

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt763.64%150.00%
Linus Torvalds (pre-git)436.36%150.00%
Total11100.00%2100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt15461.11%125.00%
Linus Torvalds (pre-git)9638.10%250.00%
Al Viro20.79%125.00%
Total252100.00%4100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt1768.00%133.33%
Linus Torvalds (pre-git)728.00%133.33%
Al Viro14.00%133.33%
Total25100.00%3100.00%

/* * Complete a SCSI command */
static void mesh_completed(struct mesh_state *ms, struct scsi_cmnd *cmd) { (*cmd->scsi_done)(cmd); }

Contributors

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt2596.15%266.67%
Linus Torvalds (pre-git)13.85%133.33%
Total26100.00%3100.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

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)19570.40%233.33%
Benjamin Herrenschmidt4817.33%116.67%
Paul Mackerras3111.19%116.67%
Al Viro20.72%116.67%
Maximilian Attems10.36%116.67%
Total277100.00%6100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt63670.82%220.00%
Linus Torvalds (pre-git)23526.17%330.00%
Paul Mackerras161.78%220.00%
FUJITA Tomonori91.00%110.00%
Al Viro10.11%110.00%
Christoph Hellwig10.11%110.00%
Total898100.00%10100.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

PersonTokensPropCommitsCommitProp
Benjamin Herrenschmidt13781.07%250.00%
Linus Torvalds (pre-git)3218.93%250.00%
Total169100.00%4100.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