📄 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/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#endifMODULE_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_DBGstatic 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);}#elsestatic 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 voidmesh_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", 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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -