📄 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. */#include <linux/config.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/types.h>#include <linux/string.h>#include <linux/malloc.h>#include <linux/blk.h>#include <linux/proc_fs.h>#include <linux/stat.h>#include <linux/tqueue.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/feature.h>#include "scsi.h"#include "hosts.h"#include "mesh.h"/* * To do: * - handle aborts correctly * - retry arbitration if lost (unless higher levels do this for us) */#define MESH_NEW_STYLE_EH#if 1#undef KERN_DEBUG#define KERN_DEBUG KERN_WARNING#endif#if CONFIG_SCSI_MESH_SYNC_RATE == 0int mesh_sync_period = 100;int mesh_sync_offset = 0;#elseint mesh_sync_period = 1000 / CONFIG_SCSI_MESH_SYNC_RATE; /* ns */int mesh_sync_offset = 15;#endifint mesh_sync_targets = 0xff; /* targets to set synchronous (bitmap) */int mesh_resel_targets = 0xff; /* targets that we let disconnect (bitmap) */int mesh_debug_targets = 0; /* print debug for these targets */unsigned char use_active_neg = 0; /* bit mask for SEQ_ACTIVE_NEG if used */#define ALLOW_SYNC(tgt) ((mesh_sync_targets >> (tgt)) & 1)#define ALLOW_RESEL(tgt) ((mesh_resel_targets >> (tgt)) & 1)#define ALLOW_DEBUG(tgt) ((mesh_debug_targets >> (tgt)) & 1)#define DEBUG_TARGET(cmd) ((cmd) && ALLOW_DEBUG((cmd)->target))#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};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 */ Scsi_Cmnd *current_req; u32 saved_ptr; int want_abort;#ifdef MESH_DBG int log_ix; int n_log; struct dbglog log[N_DBG_LOG];#endif};struct mesh_state { volatile struct mesh_regs *mesh; int meshintr; volatile struct dbdma_regs *dma; int dmaintr; struct Scsi_Host *host; struct mesh_state *next; Scsi_Cmnd *request_q; 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 */ 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 */ int clk_freq; struct mesh_target tgts[8]; void *dma_cmd_space; struct device_node *ofnode;#ifndef MESH_NEW_STYLE_EH Scsi_Cmnd *completed_q; Scsi_Cmnd *completed_qtail; struct tq_struct tqueue;#endif#ifdef MESH_DBG int log_ix; int n_log; struct dbglog log[N_DBG_SLOG];#endif};#ifdef MESH_DBGstatic void dlog(struct mesh_state *ms, char *fmt, int a);static void dumplog(struct mesh_state *ms, int tgt);static void dumpslog(struct mesh_state *ms);#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 struct mesh_state *all_meshes;static void mesh_init(struct mesh_state *);static int mesh_notify_reboot(struct notifier_block *, unsigned long, void *);static void mesh_dump_regs(struct mesh_state *);static void mesh_start(struct mesh_state *);static void mesh_start_cmd(struct mesh_state *, Scsi_Cmnd *);#ifndef MESH_NEW_STYLE_EHstatic void finish_cmds(void *);#endifstatic void add_sdtr_msg(struct mesh_state *);static void set_sdtr(struct mesh_state *, int, int);static void start_phase(struct mesh_state *);static void get_msgin(struct mesh_state *);static int msgin_length(struct mesh_state *);static void cmd_complete(struct mesh_state *);static void phase_mismatch(struct mesh_state *);static void reselected(struct mesh_state *);static void handle_reset(struct mesh_state *);static void handle_error(struct mesh_state *);static void handle_exception(struct mesh_state *);static void mesh_interrupt(int, void *, struct pt_regs *);static void do_mesh_interrupt(int, void *, struct pt_regs *);static void handle_msgin(struct mesh_state *);static void mesh_done(struct mesh_state *, int);static void mesh_completed(struct mesh_state *, Scsi_Cmnd *);static void set_dma_cmds(struct mesh_state *, Scsi_Cmnd *);static void halt_dma(struct mesh_state *);static int data_goes_out(Scsi_Cmnd *);static void do_abort(struct mesh_state *ms);static struct notifier_block mesh_notifier = { mesh_notify_reboot, NULL, 0};intmesh_detect(Scsi_Host_Template *tp){ struct device_node *mesh; int nmeshes, tgt, *cfp, minper; struct mesh_state *ms, **prev_statep; struct Scsi_Host *mesh_host; void *dma_cmd_space; if (_machine == _MACH_Pmac) { use_active_neg = (find_devices("mac-io") ? 0 : SEQ_ACTIVE_NEG); } else { /* CHRP mac-io */ use_active_neg = SEQ_ACTIVE_NEG; } nmeshes = 0; prev_statep = &all_meshes; /* * On powermacs, the MESH node has device_type "mesh". * On chrp machines, its device_type is "scsi" with * "chrp,mesh0" as its `compatible' property. */ mesh = find_devices("mesh"); if (mesh == 0) mesh = find_compatible_devices("scsi", "chrp,mesh0"); for (; mesh != 0; mesh = mesh->next) { if (mesh->n_addrs != 2 || mesh->n_intrs != 2) { printk(KERN_ERR "mesh: expected 2 addrs and 2 intrs" " (got %d,%d)", mesh->n_addrs, mesh->n_intrs); continue; } mesh_host = scsi_register(tp, sizeof(struct mesh_state)); if (mesh_host == 0) { printk(KERN_ERR "mesh: couldn't register host"); continue; } mesh_host->unique_id = nmeshes;#if !defined(MODULE) note_scsi_host(mesh, mesh_host);#endif ms = (struct mesh_state *) mesh_host->hostdata; if (ms == 0) panic("no mesh state"); memset(ms, 0, sizeof(*ms)); ms->host = mesh_host; ms->ofnode = mesh; ms->mesh = (volatile struct mesh_regs *) ioremap(mesh->addrs[0].address, 0x1000); ms->dma = (volatile struct dbdma_regs *) ioremap(mesh->addrs[1].address, 0x1000); ms->meshintr = mesh->intrs[0].line; ms->dmaintr = mesh->intrs[1].line; /* Space for dma command list: +1 for stop command, +1 to allow for aligning. */ dma_cmd_space = kmalloc((mesh_host->sg_tablesize + 2) * sizeof(struct dbdma_cmd), GFP_KERNEL); if (dma_cmd_space == 0) panic("mesh: couldn't allocate dma command space"); ms->dma_cmds = (struct dbdma_cmd *) DBDMA_ALIGN(dma_cmd_space); memset(ms->dma_cmds, 0, (mesh_host->sg_tablesize + 1) * sizeof(struct dbdma_cmd)); ms->dma_cmd_space = dma_cmd_space; ms->current_req = 0; 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 = 0; }#ifndef MESH_NEW_STYLE_EH ms->tqueue.routine = finish_cmds; ms->tqueue.data = ms;#endif *prev_statep = ms; prev_statep = &ms->next; 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; feature_set(mesh, FEATURE_MESH_enable); mdelay(200); mesh_init(ms); if (request_irq(ms->meshintr, do_mesh_interrupt, 0, "MESH", ms)) { printk(KERN_ERR "MESH: can't get irq %d\n", ms->meshintr); } ++nmeshes; } if ((_machine == _MACH_Pmac) && (nmeshes > 0)) register_reboot_notifier(&mesh_notifier); return nmeshes;}intmesh_release(struct Scsi_Host *host){ struct mesh_state *ms = (struct mesh_state *) host->hostdata; if (ms == 0) return 0; if (ms->mesh) iounmap((void *) ms->mesh); if (ms->dma) iounmap((void *) ms->dma); kfree(ms->dma_cmd_space); free_irq(ms->meshintr, ms); feature_clear(ms->ofnode, FEATURE_MESH_enable); return 0;}intmesh_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)){ unsigned long flags; struct mesh_state *ms; cmd->scsi_done = done; cmd->host_scribble = NULL; ms = (struct mesh_state *) cmd->host->hostdata; save_flags(flags); cli(); 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); restore_flags(flags); return 0;}intmesh_abort(Scsi_Cmnd *cmd){ struct mesh_state *ms = (struct mesh_state *) cmd->host->hostdata; printk(KERN_DEBUG "mesh_abort(%p)\n", cmd); mesh_dump_regs(ms); dumplog(ms, cmd->target); dumpslog(ms); return SCSI_ABORT_SNOOZE;}static voidmesh_dump_regs(struct mesh_state *ms){ volatile struct mesh_regs *mr = ms->mesh; volatile struct dbdma_regs *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); }}intmesh_reset(Scsi_Cmnd *cmd, unsigned how){ struct mesh_state *ms = (struct mesh_state *) cmd->host->hostdata; volatile struct mesh_regs *mr = ms->mesh; volatile struct dbdma_regs *md = ms->dma; unsigned long flags; int ret; printk(KERN_DEBUG "mesh_reset %x\n", how); ret = SCSI_RESET_BUS_RESET; save_flags(flags); cli(); 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 */ if (how & SCSI_RESET_SUGGEST_HOST_RESET) { out_8(&mr->sequence, SEQ_RESETMESH); ret |= SCSI_RESET_HOST_RESET; 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); } out_8(&mr->bus_status1, BS1_RST); /* assert RST */ udelay(30); /* leave it on for >= 25us */ out_8(&mr->bus_status1, 0); /* negate RST */#ifdef DO_ASYNC_RESET if (how & SCSI_RESET_ASYNCHRONOUS) { restore_flags(flags); ret |= SCSI_RESET_PENDING; } else#endif { handle_reset(ms); restore_flags(flags);#ifndef MESH_NEW_STYLE_EH finish_cmds(ms);#endif ret |= SCSI_RESET_SUCCESS; } return ret;}/* * 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 intmesh_notify_reboot(struct notifier_block *this, unsigned long code, void *x){ struct mesh_state *ms; volatile struct mesh_regs *mr; if (code == SYS_DOWN) { printk(KERN_INFO "resetting MESH scsi bus(es)\n"); for (ms = all_meshes; ms != 0; ms = ms->next) { 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); udelay(30); out_8(&mr->bus_status1, 0); } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -