diff options
Diffstat (limited to 'scsi/adapters/scsi_53C94_hdw.c')
-rw-r--r-- | scsi/adapters/scsi_53C94_hdw.c | 2840 |
1 files changed, 2840 insertions, 0 deletions
diff --git a/scsi/adapters/scsi_53C94_hdw.c b/scsi/adapters/scsi_53C94_hdw.c new file mode 100644 index 00000000..dad9b223 --- /dev/null +++ b/scsi/adapters/scsi_53C94_hdw.c @@ -0,0 +1,2840 @@ +/* + * Mach Operating System + * Copyright (c) 1993,1992,1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS AS-IS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: scsi_53C94_hdw.h + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 9/90 + * + * Bottom layer of the SCSI driver: chip-dependent functions + * + * This file contains the code that is specific to the NCR 53C94 + * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start + * operation, and interrupt routine. + */ + +/* + * This layer works based on small simple 'scripts' that are installed + * at the start of the command and drive the chip to completion. + * The idea comes from the specs of the NCR 53C700 'script' processor. + * + * There are various reasons for this, mainly + * - Performance: identify the common (successful) path, and follow it; + * at interrupt time no code is needed to find the current status + * - Code size: it should be easy to compact common operations + * - Adaptability: the code skeleton should adapt to different chips without + * terrible complications. + * - Error handling: and it is easy to modify the actions performed + * by the scripts to cope with strange but well identified sequences + * + */ + +#include <asc.h> +#if NASC > 0 +#include <platforms.h> + +#ifdef DECSTATION +typedef unsigned char asc_register_t; +#define PAD(n) char n[3]; +#define mb() +#ifdef MACH_KERNEL +#define HAS_MAPPED_SCSI +#endif +#define ASC_PROBE_DYNAMICALLY FALSE /* established custom */ +#define DEBUG 1 +#define TRACE 1 +#endif + +#ifdef FLAMINGO +typedef unsigned int asc_register_t; +#define PAD(n) int n; /* sparse ! */ +#define mb() wbflush() /* memory barrier */ +#define ASC_PROBE_DYNAMICALLY TRUE +#define DEBUG 1 +#define TRACE 1 +#endif + +#include <mach/std_types.h> +#include <sys/types.h> +#include <chips/busses.h> +#include <scsi/compat_30.h> +#include <machine/machspl.h> + +#include <scsi/scsi.h> +#include <scsi/scsi2.h> + +#include <scsi/adapters/scsi_53C94.h> +#include <scsi/scsi_defs.h> +#include <scsi/adapters/scsi_dma.h> + +#define private static + +#ifdef PAD +typedef struct { + volatile asc_register_t asc_tc_lsb; /* rw: Transfer Counter LSB */ + PAD(pad0) + volatile asc_register_t asc_tc_msb; /* rw: Transfer Counter MSB */ + PAD(pad1) + volatile asc_register_t asc_fifo; /* rw: FIFO top */ + PAD(pad2) + volatile asc_register_t asc_cmd; /* rw: Command */ + PAD(pad3) + volatile asc_register_t asc_csr; /* r: Status */ +/*#define asc_dbus_id asc_csr /* w: Destination Bus ID */ + PAD(pad4) + volatile asc_register_t asc_intr; /* r: Interrupt */ +/*#define asc_sel_timo asc_intr /* w: (re)select timeout */ + PAD(pad5) + volatile asc_register_t asc_ss; /* r: Sequence Step */ +/*#define asc_syn_p asc_ss /* w: synchronous period */ + PAD(pad6) + volatile asc_register_t asc_flags; /* r: FIFO flags + seq step */ +/*#define asc_syn_o asc_flags /* w: synchronous offset */ + PAD(pad7) + volatile asc_register_t asc_cnfg1; /* rw: Configuration 1 */ + PAD(pad8) + volatile asc_register_t asc_ccf; /* w: Clock Conv. Factor */ + PAD(pad9) + volatile asc_register_t asc_test; /* w: Test Mode */ + PAD(pad10) + volatile asc_register_t asc_cnfg2; /* rw: Configuration 2 */ + PAD(pad11) + volatile asc_register_t asc_cnfg3; /* rw: Configuration 3 */ + PAD(pad12) + volatile asc_register_t asc_rfb; /* w: Reserve FIFO byte */ + PAD(pad13) +} asc_padded_regmap_t; + +#else /* !PAD */ + +typedef asc_regmap_t asc_padded_regmap_t; + +#endif /* !PAD */ + +#define get_reg(r,x) ((unsigned char)((r)->x)) + +#define fifo_count(r) ((r)->asc_flags & ASC_FLAGS_FIFO_CNT) +#define get_fifo(r) get_reg(r,asc_fifo) + +boolean_t asc_probe_dynamically = ASC_PROBE_DYNAMICALLY; + +/* + * We might need to use some fields usually + * handled by the DMA engine, if asked to. + * These are "dma_ptr" and "hba_dep". + */ +#define has_oddb hba_dep[0] +#define the_oddb hba_dep[1] + +/* + * A script has a three parts: a pre-condition, an action, and + * an optional command to the chip. The first triggers error + * handling if not satisfied and in our case it is a match + * of the expected and actual scsi-bus phases. + * The action part is just a function pointer, and the + * command is what the 53c90 should be told to do at the end + * of the action processing. This command is only issued and the + * script proceeds if the action routine returns TRUE. + * See asc_intr() for how and where this is all done. + */ + +typedef struct script { + unsigned char condition; /* expected state at interrupt */ + unsigned char command; /* command to the chip */ + unsigned short flags; /* unused padding */ + boolean_t (*action)(); /* extra operations */ +} *script_t; + +/* Matching on the condition value */ +#define ANY 0xff +#define SCRIPT_MATCH(csr,ir,value) ((SCSI_PHASE(csr)==(value)) || \ + (((value)==ANY) && \ + ((ir)&(ASC_INT_DISC|ASC_INT_FC)))) + +/* When no command is needed */ +#define SCRIPT_END -1 + +/* forward decls of script actions */ +boolean_t + asc_end(), /* all come to an end */ + asc_clean_fifo(), /* .. in preparation for status byte */ + asc_get_status(), /* get status from target */ + asc_put_status(), /* send status to initiator */ + asc_dma_in(), /* get data from target via dma */ + asc_dma_in_r(), /* get data from target via dma (restartable)*/ + asc_dma_out(), /* send data to target via dma */ + asc_dma_out_r(), /* send data to target via dma (restartable) */ + asc_dosynch(), /* negotiate synch xfer */ + asc_msg_in(), /* receive the disconenct message */ + asc_disconnected(), /* target has disconnected */ + asc_reconnect(); /* target reconnected */ + +/* forward decls of error handlers */ +boolean_t + asc_err_generic(), /* generic handler */ + asc_err_disconn(), /* target disconnects amidst */ + gimmeabreak(); /* drop into the debugger */ + +int asc_reset_scsibus(); +boolean_t asc_probe_target(); +private asc_wait(); + +/* + * State descriptor for this layer. There is one such structure + * per (enabled) SCSI-53c90 interface + */ +struct asc_softc { + watchdog_t wd; + asc_padded_regmap_t *regs; /* 53c90 registers */ + + scsi_dma_ops_t *dma_ops; /* DMA operations and state */ + opaque_t dma_state; + + script_t script; /* what should happen next */ + boolean_t (*error_handler)();/* what if something is wrong */ + int in_count; /* amnt we expect to receive */ + int out_count; /* amnt we are going to ship */ + + volatile char state; +#define ASC_STATE_BUSY 0x01 /* selecting or currently connected */ +#define ASC_STATE_TARGET 0x04 /* currently selected as target */ +#define ASC_STATE_COLLISION 0x08 /* lost selection attempt */ +#define ASC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */ +#define ASC_STATE_SPEC_DMA 0x20 /* special, 8 byte threshold dma */ +#define ASC_STATE_DO_RFB 0x40 /* DMA engine cannot handle odd bytes */ + + unsigned char ntargets; /* how many alive on this scsibus */ + unsigned char done; + unsigned char extra_count; /* sleazy trick to spare an interrupt */ + int dmacnt_at_end; + + scsi_softc_t *sc; /* HBA-indep info */ + target_info_t *active_target; /* the current one */ + + target_info_t *next_target; /* trying to seize bus */ + queue_head_t waiting_targets;/* other targets competing for bus */ + + unsigned char ss_was; /* districate powered on/off devices */ + unsigned char cmd_was; + + unsigned char timeout; /* cache a couple numbers */ + unsigned char ccf; + unsigned char clk; + +} asc_softc_data[NASC]; + +typedef struct asc_softc *asc_softc_t; + +asc_softc_t asc_softc[NASC]; + +/* + * Synch xfer parameters, and timing conversions + */ +int asc_min_period = 5; /* in CLKS/BYTE, e.g. 1 CLK = 40nsecs @25 Mhz */ +int asc_max_offset = 15; /* pure number */ + +int asc_to_scsi_period(a,clk) +{ + /* Note: the SCSI unit is 4ns, hence + A_P * 1,000,000,000 + ------------------- = S_P + C_Mhz * 4 + */ + return a * (250 / clk); + +} + +int scsi_period_to_asc(p,clk) +{ + register int ret; + + ret = (p * clk) / 250; + if (ret < asc_min_period) + return asc_min_period; + if ((asc_to_scsi_period(ret,clk)) < p) + return ret + 1; + return ret; +} + +#define readback(a) {register int foo; foo = a; mb();} + +#define u_min(a,b) (((a) < (b)) ? (a) : (b)) + +/* + * Definition of the controller for the auto-configuration program. + */ + +int asc_probe(), scsi_slave(), asc_go(), asc_intr(); +void scsi_attach(); + +vm_offset_t asc_std[NASC] = { 0 }; +struct bus_device *asc_dinfo[NASC*8]; +struct bus_ctlr *asc_minfo[NASC]; +struct bus_driver asc_driver = + { asc_probe, scsi_slave, scsi_attach, asc_go, asc_std, "rz", asc_dinfo, + "asc", asc_minfo, BUS_INTR_B4_PROBE}; + + +int asc_clock_speed_in_mhz[NASC] = {25,25,25,25}; /* original 3max */ + +asc_set_dmaops(unit, dmaops) + unsigned int unit; + scsi_dma_ops_t *dmaops; +{ + if (unit < NASC) + asc_std[unit] = (vm_offset_t)dmaops; +} + +/* + * Scripts + */ +struct script +asc_script_data_in[] = { /* started with SEL & DMA */ + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_data_out[] = { /* started with SEL & DMA */ + {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_try_synch[] = { + {SCSI_PHASE_MSG_OUT, ASC_CMD_I_COMPLETE,0, asc_dosynch}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_simple_cmd[] = { + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_disconnect[] = { + {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected}, +/**/ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect} +}, + +asc_script_restart_data_in[] = { /* starts after disconnect */ + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in_r}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_restart_data_out[] = { /* starts after disconnect */ + {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out_r}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +#if documentation +/* + * This is what might happen during a read + * that disconnects + */ +asc_script_data_in_wd[] = { /* started with SEL & DMA & allow disconnect */ + {SCSI_PHASE_MSG_IN, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_msg_in}, + {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect}, + {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in}, + {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo}, + {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, +#endif + +/* + * Target mode scripts + */ +asc_script_t_data_in[] = { + {SCSI_PHASE_CMD, ASC_CMD_RCV_DATA|ASC_CMD_DMA, 0, asc_dma_in_r}, + {SCSI_PHASE_DATAO, ASC_CMD_TERM, 0, asc_put_status}, + {ANY, SCRIPT_END, 0, asc_end} +}, + +asc_script_t_data_out[] = { + {SCSI_PHASE_CMD, ASC_CMD_SND_DATA|ASC_CMD_DMA, 0, asc_dma_out_r}, + {SCSI_PHASE_DATAI, ASC_CMD_TERM, 0, asc_put_status}, + {ANY, SCRIPT_END, 0, asc_end} +}; + + +#ifdef DEBUG + +#define PRINT(x) if (scsi_debug) printf x + +asc_state(regs) + asc_padded_regmap_t *regs; +{ + register unsigned char ff,csr,ir,d0,d1,cmd; + + if (regs == 0) { + if (asc_softc[0]) + regs = asc_softc[0]->regs; + else + regs = (asc_padded_regmap_t*)0xbf400000; + } + ff = get_reg(regs,asc_flags); + csr = get_reg(regs,asc_csr); +/* ir = get_reg(regs,asc_intr); nope, clears interrupt */ + d0 = get_reg(regs,asc_tc_lsb); + d1 = get_reg(regs,asc_tc_msb); + cmd = get_reg(regs,asc_cmd); + printf("dma %x ff %x csr %x cmd %x\n", + (d1 << 8) | d0, ff, csr, cmd); + return 0; +} + +asc_target_state(tgt) + target_info_t *tgt; +{ + if (tgt == 0) + tgt = asc_softc[0]->active_target; + if (tgt == 0) + return 0; + db_printf("@x%x: fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", + tgt, + tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, + tgt->cmd_ptr, (long)tgt->target_id, + (long)tgt->sync_period, (long)tgt->sync_offset, + tgt->ior, (long)tgt->done); + if (tgt->flags & TGT_DISCONNECTED){ + script_t spt; + + spt = tgt->transient_state.script; + db_printf("disconnected at "); + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(tgt->transient_state.handler, 1); + db_printf("\n"); + } + + return 0; +} + +asc_all_targets(unit) +{ + int i; + target_info_t *tgt; + for (i = 0; i < 8; i++) { + tgt = asc_softc[unit]->sc->target[i]; + if (tgt) + asc_target_state(tgt); + } +} + +asc_script_state(unit) +{ + script_t spt = asc_softc[unit]->script; + + if (spt == 0) return 0; + db_printsym(spt,1); + db_printf(": %x %x ", spt->condition, spt->command); + db_printsym(spt->action,1); + db_printf(", "); + db_printsym(asc_softc[unit]->error_handler, 1); + return 0; +} + +#define TRMAX 200 +int tr[TRMAX+3]; +int trpt, trpthi; +#define TR(x) tr[trpt++] = x +#define TRWRAP trpthi = trpt; trpt = 0; +#define TRCHECK if (trpt > TRMAX) {TRWRAP} + + +#ifdef TRACE + +#define LOGSIZE 256 +int asc_logpt; +char asc_log[LOGSIZE]; + +#define MAXLOG_VALUE 0x42 +struct { + char *name; + unsigned int count; +} logtbl[MAXLOG_VALUE]; + +/* private */ LOG(e,f) + char *f; +{ + asc_log[asc_logpt++] = (e); + if (asc_logpt == LOGSIZE) asc_logpt = 0; + if ((e) < MAXLOG_VALUE) { + logtbl[(e)].name = (f); + logtbl[(e)].count++; + } +} + +asc_print_log(skip) + int skip; +{ + register int i, j; + register unsigned char c; + + for (i = 0, j = asc_logpt; i < LOGSIZE; i++) { + c = asc_log[j]; + if (++j == LOGSIZE) j = 0; + if (skip-- > 0) + continue; + if (c < MAXLOG_VALUE) + db_printf(" %s", logtbl[c].name); + else + db_printf("-x%x", c & 0x7f); + } +} + +asc_print_stat() +{ + register int i; + register char *p; + for (i = 0; i < MAXLOG_VALUE; i++) { + if (p = logtbl[i].name) + printf("%d %s\n", logtbl[i].count, p); + } +} + +#else /*TRACE*/ +#define LOG(e,f) +#define LOGSIZE +#endif /*TRACE*/ + +#else /*DEBUG*/ +#define PRINT(x) +#define LOG(e,f) +#define LOGSIZE +#define TR(x) +#define TRCHECK +#define TRWRAP + +#endif /*DEBUG*/ + + +/* + * Probe/Slave/Attach functions + */ + +/* + * Probe routine: + * Should find out (a) if the controller is + * present and (b) which/where slaves are present. + * + * Implementation: + * Send a test-unit-ready to each possible target on the bus + * except of course ourselves. + */ +asc_probe(reg, ui) + vm_offset_t reg; + struct bus_ctlr *ui; +{ + int unit = ui->unit; + asc_softc_t asc = &asc_softc_data[unit]; + int target_id; + scsi_softc_t *sc; + register asc_padded_regmap_t *regs; + spl_t s; + boolean_t did_banner = FALSE; + + /* + * We are only called if the right board is there, + * but make sure anyways.. + */ + if (check_memory(reg, 0)) + return 0; + +#if defined(HAS_MAPPED_SCSI) + /* Mappable version side */ + ASC_probe(reg, ui); +#endif + + /* + * Initialize hw descriptor, cache some pointers + */ + asc_softc[unit] = asc; + asc->regs = (asc_padded_regmap_t *) (reg); + + if ((asc->dma_ops = (scsi_dma_ops_t *)asc_std[unit]) == 0) + /* use same as unit 0 if undefined */ + asc->dma_ops = (scsi_dma_ops_t *)asc_std[0]; + { + int dma_bsize = 16; /* bits, preferred */ + boolean_t do_rfb = FALSE; + + asc->dma_state = (*asc->dma_ops->init)(unit, reg, &dma_bsize, &do_rfb); + if (dma_bsize > 16) + asc->state |= ASC_STATE_SPEC_DMA; + if (do_rfb) + asc->state |= ASC_STATE_DO_RFB; + } + + queue_init(&asc->waiting_targets); + + asc->clk = asc_clock_speed_in_mhz[unit]; + asc->ccf = mhz_to_ccf(asc->clk); /* see .h file */ + asc->timeout = asc_timeout_250(asc->clk,asc->ccf); + + sc = scsi_master_alloc(unit, asc); + asc->sc = sc; + + sc->go = asc_go; + sc->watchdog = scsi_watchdog; + sc->probe = asc_probe_target; + asc->wd.reset = asc_reset_scsibus; + +#ifdef MACH_KERNEL + sc->max_dma_data = -1; +#else + sc->max_dma_data = scsi_per_target_virtual; +#endif + + regs = asc->regs; + + /* + * Our SCSI id on the bus. + * The user can set this via the prom on 3maxen/pmaxen. + * If this changes it is easy to fix: make a default that + * can be changed as boot arg. + */ + { + register unsigned char my_id; + + my_id = scsi_initiator_id[unit] & 0x7; + if (my_id != 7) + regs->asc_cnfg1 = my_id; mb(); + } + + /* + * Reset chip, fully. Note that interrupts are already enabled. + */ + s = splbio(); + asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA); + + sc->initiator_id = regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID; + printf("%s%d: SCSI id %d", ui->name, unit, sc->initiator_id); + + { + register target_info_t *tgt; + + tgt = scsi_slave_alloc(sc->masterno, sc->initiator_id, asc); + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + sccpu_new_initiator(tgt, tgt); + } + + if (asc_probe_dynamically) + printf("%s", ", will probe targets on demand"); + else { + + /* + * For all possible targets, see if there is one and allocate + * a descriptor for it if it is there. + */ + for (target_id = 0; target_id < 8; target_id++) { + register unsigned char csr, ss, ir, ff; + register scsi_status_byte_t status; + + /* except of course ourselves */ + if (target_id == sc->initiator_id) + continue; + + regs->asc_cmd = ASC_CMD_FLUSH; /* empty fifo */ + mb(); + delay(2); + + regs->asc_dbus_id = target_id; mb(); + regs->asc_sel_timo = asc->timeout; mb(); + + /* + * See if the unit is ready. + * XXX SHOULD inquiry LUN 0 instead !!! + */ + regs->asc_fifo = SCSI_CMD_TEST_UNIT_READY; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + regs->asc_fifo = 0; mb(); + + /* select and send it */ + regs->asc_cmd = ASC_CMD_SEL; mb(); + + /* wait for the chip to complete, or timeout */ + csr = asc_wait(regs, ASC_CSR_INT, 1); + ss = get_reg(regs,asc_ss); + ir = get_reg(regs,asc_intr); + + /* empty fifo, there is garbage in it if timeout */ + regs->asc_cmd = ASC_CMD_FLUSH; mb(); + delay(2); + + /* + * Check if the select timed out + */ + if ((ASC_SS(ss) == 0) && (ir == ASC_INT_DISC)) + /* noone out there */ + continue; + + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) { + printf( " %s%d%s", "ignoring target at ", target_id, + " cuz it acts weirdo"); + continue; + } + + printf(",%s%d", did_banner++ ? " " : " target(s) at ", + target_id); + + regs->asc_cmd = ASC_CMD_I_COMPLETE; + wbflush(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); /* ack intr */ + mb(); + + status.bits = get_fifo(regs); /* empty fifo */ + mb(); + ff = get_fifo(regs); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) + scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0); + + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); /* ack intr */ + mb(); + + /* + * Found a target + */ + asc->ntargets++; + { + register target_info_t *tgt; + tgt = scsi_slave_alloc(sc->masterno, target_id, asc); + + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + } + } + } /* asc_probe_dynamically */ + + regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb(); + + printf(".\n"); + + splx(s); + return 1; +} + +boolean_t +asc_probe_target(tgt, ior) + target_info_t *tgt; + io_req_t ior; +{ + asc_softc_t asc = asc_softc[tgt->masterno]; + boolean_t newlywed; + + newlywed = (tgt->cmd_ptr == 0); + if (newlywed) { + (*asc->dma_ops->new_target)(asc->dma_state, tgt); + } + + if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) + return FALSE; + + asc->ntargets++; + tgt->flags = TGT_ALIVE; + return TRUE; +} + +private asc_wait(regs, until, complain) + asc_padded_regmap_t *regs; +{ + int timeo = 1000000; + while ((regs->asc_csr & until) == 0) { + mb(); + delay(1); + if (!timeo--) { + if (complain) + printf("asc_wait TIMEO with x%x\n", get_reg(regs,asc_csr)); + break; + } + } + return get_reg(regs,asc_csr); +} + +asc_reset(asc, quick, special_dma) + asc_softc_t asc; +{ + char my_id; + int ccf; + asc_padded_regmap_t *regs; + + regs = asc->regs; + + /* preserve our ID for now */ + my_id = (regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID); + + /* + * Reset chip and wait till done + */ + regs->asc_cmd = ASC_CMD_RESET; + wbflush(); delay(25); + + /* spec says this is needed after reset */ + regs->asc_cmd = ASC_CMD_NOP; + wbflush(); delay(25); + + /* + * Set up various chip parameters + */ + regs->asc_ccf = asc->ccf; + wbflush(); + delay(25); + regs->asc_sel_timo = asc->timeout; mb(); + /* restore our ID */ + regs->asc_cnfg1 = my_id | ASC_CNFG1_P_CHECK; mb(); + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + mb(); + regs->asc_cnfg3 = special_dma ? (ASC_CNFG3_T8|ASC_CNFG3_ALT_DMA) : 0; + mb(); + /* zero anything else */ + ASC_TC_PUT(regs, 0); mb(); + regs->asc_syn_p = asc_min_period; mb(); + regs->asc_syn_o = 0; mb(); /* asynch for now */ + + regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb(); + + if (quick) return; + + /* + * reset the scsi bus, the interrupt routine does the rest + * or you can call asc_bus_reset(). + */ + regs->asc_cmd = ASC_CMD_BUS_RESET; mb(); +} + + +/* + * Operational functions + */ + +/* + * Start a SCSI command on a target + */ +asc_go(tgt, cmd_count, in_count, cmd_only) + target_info_t *tgt; + boolean_t cmd_only; +{ + asc_softc_t asc; + register spl_t s; + boolean_t disconn; + script_t scp; + boolean_t (*handler)(); + + LOG(1,"go"); + + asc = (asc_softc_t)tgt->hw_state; + + tgt->transient_state.cmd_count = cmd_count; /* keep it here */ + tgt->transient_state.out_count = 0; /* default */ + + (*asc->dma_ops->map)(asc->dma_state, tgt); + + disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id); + disconn = disconn && (asc->ntargets > 1); + disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id); + + /* + * Setup target state + */ + tgt->done = SCSI_RET_IN_PROGRESS; + + handler = (disconn) ? asc_err_disconn : asc_err_generic; + + switch (tgt->cur_cmd) { + case SCSI_CMD_READ: + case SCSI_CMD_LONG_READ: + LOG(2,"readop"); + scp = asc_script_data_in; + break; + case SCSI_CMD_WRITE: + case SCSI_CMD_LONG_WRITE: + LOG(0x18,"writeop"); + scp = asc_script_data_out; + break; + case SCSI_CMD_INQUIRY: + /* This is likely the first thing out: + do the synch neg if so */ + if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) { + scp = asc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + disconn = FALSE; + break; + } + case SCSI_CMD_REQUEST_SENSE: + case SCSI_CMD_MODE_SENSE: + case SCSI_CMD_RECEIVE_DIAG_RESULTS: + case SCSI_CMD_READ_CAPACITY: + case SCSI_CMD_READ_BLOCK_LIMITS: + case SCSI_CMD_READ_TOC: + case SCSI_CMD_READ_SUBCH: + case SCSI_CMD_READ_HEADER: + case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ + case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ + case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ + scp = asc_script_data_in; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_MODE_SELECT: + case SCSI_CMD_REASSIGN_BLOCKS: + case SCSI_CMD_FORMAT_UNIT: + case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ + tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd); + tgt->transient_state.out_count = + cmd_count - tgt->transient_state.cmd_count; + scp = asc_script_data_out; + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + case SCSI_CMD_TEST_UNIT_READY: + /* + * Do the synch negotiation here, unless prohibited + * or done already + */ + if (tgt->flags & TGT_DID_SYNCH) { + scp = asc_script_simple_cmd; + } else { + scp = asc_script_try_synch; + tgt->flags |= TGT_TRY_SYNCH; + cmd_only = FALSE; + disconn = FALSE; + } + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + break; + default: + LOG(0x1c,"cmdop"); + LOG(0x80+tgt->cur_cmd,0); + scp = asc_script_simple_cmd; + } + + tgt->transient_state.script = scp; + tgt->transient_state.handler = handler; + tgt->transient_state.identify = (cmd_only) ? 0xff : + (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT : + SCSI_IDENTIFY); + + if (in_count) + tgt->transient_state.in_count = + (in_count < tgt->block_size) ? tgt->block_size : in_count; + else + tgt->transient_state.in_count = 0; + + /* + * See if another target is currently selected on + * this SCSI bus, e.g. lock the asc structure. + * Note that it is the strategy routine's job + * to serialize ops on the same target as appropriate. + * XXX here and everywhere, locks! + */ + /* + * Protection viz reconnections makes it tricky. + */ + s = splbio(); + + if (asc->wd.nactive++ == 0) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + + if (asc->state & ASC_STATE_BUSY) { + /* + * Queue up this target, note that this takes care + * of proper FIFO scheduling of the scsi-bus. + */ + LOG(3,"enqueue"); + enqueue_tail(&asc->waiting_targets, (queue_entry_t) tgt); + } else { + /* + * It is down to at most two contenders now, + * we will treat reconnections same as selections + * and let the scsi-bus arbitration process decide. + */ + asc->state |= ASC_STATE_BUSY; + asc->next_target = tgt; + asc_attempt_selection(asc); + /* + * Note that we might still lose arbitration.. + */ + } + splx(s); +} + +asc_attempt_selection(asc) + asc_softc_t asc; +{ + target_info_t *tgt; + asc_padded_regmap_t *regs; + register int out_count; + + regs = asc->regs; + tgt = asc->next_target; + + LOG(4,"select"); + LOG(0x80+tgt->target_id,0); + + /* + * We own the bus now.. unless we lose arbitration + */ + asc->active_target = tgt; + + /* Try to avoid reselect collisions */ + if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) == + (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) + return; + + /* + * Cleanup the FIFO + */ + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + /* + * This value is not from spec, I have seen it failing + * without this delay and with logging disabled. That had + * about 42 extra instructions @25Mhz. + */ + delay(2);/* XXX time & move later */ + + + /* + * Init bus state variables + */ + asc->script = tgt->transient_state.script; + asc->error_handler = tgt->transient_state.handler; + asc->done = SCSI_RET_IN_PROGRESS; + + asc->out_count = 0; + asc->in_count = 0; + asc->extra_count = 0; + + /* + * Start the chip going + */ + out_count = (*asc->dma_ops->start_cmd)(asc->dma_state, tgt); + if (tgt->transient_state.identify != 0xff) { + regs->asc_fifo = tgt->transient_state.identify | tgt->lun; + mb(); + } + ASC_TC_PUT(regs, out_count); + readback(regs->asc_cmd); + + regs->asc_dbus_id = tgt->target_id; + readback(regs->asc_dbus_id); + + regs->asc_sel_timo = asc->timeout; + readback(regs->asc_sel_timo); + + regs->asc_syn_p = tgt->sync_period; + readback(regs->asc_syn_p); + + regs->asc_syn_o = tgt->sync_offset; + readback(regs->asc_syn_o); + + /* ugly little help for compiler */ +#define command out_count + if (tgt->flags & TGT_DID_SYNCH) { + command = (tgt->transient_state.identify == 0xff) ? + ASC_CMD_SEL | ASC_CMD_DMA : + ASC_CMD_SEL_ATN | ASC_CMD_DMA; /*preferred*/ + } else if (tgt->flags & TGT_TRY_SYNCH) + command = ASC_CMD_SEL_ATN_STOP; + else + command = ASC_CMD_SEL | ASC_CMD_DMA; + + /* Try to avoid reselect collisions */ + if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) != + (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) { + register int tmp; + + regs->asc_cmd = command; mb(); + /* + * Very nasty but infrequent problem here. We got/will get + * reconnected but the chip did not interrupt. The watchdog would + * fix it allright, but it stops the machine before it expires! + * Too bad we cannot just look at the interrupt register, sigh. + */ + tmp = get_reg(regs,asc_cmd); + if ((tmp != command) && (tmp == (ASC_CMD_NOP|ASC_CMD_DMA))) { + if ((regs->asc_csr & ASC_CSR_INT) == 0) { + delay(250); /* increase if in trouble */ + + if (get_reg(regs,asc_csr) == SCSI_PHASE_MSG_IN) { + /* ok, take the risk of reading the ir */ + tmp = get_reg(regs,asc_intr); mb(); + if (tmp & ASC_INT_RESEL) { + (void) asc_reconnect(asc, get_reg(regs,asc_csr), tmp); + asc_wait(regs, ASC_CSR_INT, 1); + tmp = get_reg(regs,asc_intr); mb(); + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + } else /* does not happen, but who knows.. */ + asc_reset(asc,FALSE,asc->state & ASC_STATE_SPEC_DMA); + } + } + } + } +#undef command +} + +/* + * Interrupt routine + * Take interrupts from the chip + * + * Implementation: + * Move along the current command's script if + * all is well, invoke error handler if not. + */ +asc_intr(unit, spllevel) + spl_t spllevel; +{ + register asc_softc_t asc; + register script_t scp; + register int ir, csr; + register asc_padded_regmap_t *regs; +#if defined(HAS_MAPPED_SCSI) + extern boolean_t rz_use_mapped_interface; + + if (rz_use_mapped_interface) + return ASC_intr(unit,spllevel); +#endif + + asc = asc_softc[unit]; + + LOG(5,"\n\tintr"); + /* collect ephemeral information */ + regs = asc->regs; + csr = get_reg(regs,asc_csr); mb(); + asc->ss_was = get_reg(regs,asc_ss); mb(); + asc->cmd_was = get_reg(regs,asc_cmd); mb(); + + /* drop spurious interrupts */ + if ((csr & ASC_CSR_INT) == 0) + return; + + ir = get_reg(regs,asc_intr); /* this re-latches CSR (and SSTEP) */ + mb(); + +TR(csr);TR(ir);TR(get_reg(regs,asc_cmd));TRCHECK + + /* this must be done within 250msec of disconnect */ + if (ir & ASC_INT_DISC) { + regs->asc_cmd = ASC_CMD_ENABLE_SEL; + readback(regs->asc_cmd); + } + + if (ir & ASC_INT_RESET) + return asc_bus_reset(asc); + + /* we got an interrupt allright */ + if (asc->active_target) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + +#if mips + splx(spllevel); /* drop priority */ +#endif + + if ((asc->state & ASC_STATE_TARGET) || + (ir & (ASC_INT_SEL|ASC_INT_SEL_ATN))) + return asc_target_intr(asc, csr, ir); + + /* + * In attempt_selection() we could not check the asc_intr + * register to see if a reselection was in progress [else + * we would cancel the interrupt, and it would not be safe + * anyways]. So we gave the select command even if sometimes + * the chip might have been reconnected already. What + * happens then is that we get an illegal command interrupt, + * which is why the second clause. Sorry, I'd do it better + * if I knew of a better way. + */ + if ((ir & ASC_INT_RESEL) || + ((ir & ASC_INT_ILL) && (regs->asc_cmd & ASC_CMD_SEL_ATN))) + return asc_reconnect(asc, csr, ir); + + /* + * Check for various errors + */ + if ((csr & (ASC_CSR_GE|ASC_CSR_PE)) || (ir & ASC_INT_ILL)) { + char *msg; +printf("{E%x,%x}", csr, ir); + if (csr & ASC_CSR_GE) + return;/* sit and prey? */ + + if (csr & ASC_CSR_PE) + msg = "SCSI bus parity error"; + if (ir & ASC_INT_ILL) + msg = "Chip sez Illegal Command"; + /* all we can do is to throw a reset on the bus */ + printf( "asc%d: %s%s", asc - asc_softc_data, msg, + ", attempting recovery.\n"); + asc_reset(asc, FALSE, asc->state & ASC_STATE_SPEC_DMA); + return; + } + + if ((scp = asc->script) == 0) /* sanity */ + return; + + LOG(6,"match"); + if (SCRIPT_MATCH(csr,ir,scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(asc, csr, ir)) { + asc->script = scp + 1; + regs->asc_cmd = scp->command; mb(); + } + } else + (void) (*asc->error_handler)(asc, csr, ir); +} + +asc_target_intr(asc, csr, ir) + register asc_softc_t asc; + +{ + register script_t scp; + + LOG(0x1e,"tmode"); + + if ((asc->state & ASC_STATE_TARGET) == 0) { + + /* + * We just got selected + */ + asc->state |= ASC_STATE_TARGET; + + /* + * See if this selection collided with our selection attempt + */ + if (asc->state & ASC_STATE_BUSY) + asc->state |= ASC_STATE_COLLISION; + asc->state |= ASC_STATE_BUSY; + + return asc_selected(asc, csr, ir); + + } + /* We must be executing a script here */ + scp = asc->script; + assert(scp != 0); + + LOG(6,"match"); + if (SCRIPT_MATCH(csr,ir,scp->condition)) { + /* + * Perform the appropriate operation, + * then proceed + */ + if ((*scp->action)(asc, csr, ir)) { + asc->script = scp + 1; + asc->regs->asc_cmd = scp->command; mb(); + } + } else + (void) (*asc->error_handler)(asc, csr, ir); + +} + +/* + * All the many little things that the interrupt + * routine might switch to + */ +boolean_t +asc_clean_fifo(asc, csr, ir) + register asc_softc_t asc; + +{ + register asc_padded_regmap_t *regs = asc->regs; + register char ff; + + ASC_TC_GET(regs,asc->dmacnt_at_end); + + ASC_TC_PUT(regs,0); /* stop dma engine */ + readback(regs->asc_cmd); + + LOG(7,"clean_fifo"); + + while (fifo_count(regs)) { + ff = get_fifo(regs); + mb(); + } + return TRUE; +} + +boolean_t +asc_end(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register io_req_t ior; + + LOG(8,"end"); + + asc->state &= ~ASC_STATE_TARGET; + asc->regs->asc_syn_p = 0; mb(); + asc->regs->asc_syn_o = 0; mb(); + + tgt = asc->active_target; + if ((tgt->done = asc->done) == SCSI_RET_IN_PROGRESS) + tgt->done = SCSI_RET_SUCCESS; + + asc->script = 0; + + if (asc->wd.nactive-- == 1) + asc->wd.watchdog_state = SCSI_WD_INACTIVE; + + asc_release_bus(asc); + + if (ior = tgt->ior) { + /* + * WARNING: the above might have scheduled the + * DMA engine off to someone else. Keep it in + * mind in the following code + */ + (*asc->dma_ops->end_cmd)(asc->dma_state, tgt, ior); + + LOG(0xA,"ops->restart"); + (*tgt->dev_ops->restart)( tgt, TRUE); + } + + return FALSE; +} + +boolean_t +asc_release_bus(asc) + register asc_softc_t asc; +{ + boolean_t ret = TRUE; + + LOG(9,"release"); + if (asc->state & ASC_STATE_COLLISION) { + + LOG(0xB,"collided"); + asc->state &= ~ASC_STATE_COLLISION; + asc_attempt_selection(asc); + + } else if (queue_empty(&asc->waiting_targets)) { + + asc->state &= ~ASC_STATE_BUSY; + asc->active_target = 0; + asc->script = 0; + ret = FALSE; + + } else { + + LOG(0xC,"dequeue"); + asc->next_target = (target_info_t *) + dequeue_head(&asc->waiting_targets); + asc_attempt_selection(asc); + } + return ret; +} + +boolean_t +asc_get_status(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register scsi2_status_byte_t status; + int len; + boolean_t ret; + io_req_t ior; + register target_info_t *tgt = asc->active_target; + + LOG(0xD,"get_status"); +TRWRAP + + asc->state &= ~ASC_STATE_DMA_IN; + + if (asc->state & ASC_STATE_DO_RFB) { + tgt->transient_state.has_oddb = FALSE; + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + } + + /* + * Get the last two bytes in FIFO + */ + while (fifo_count(regs) > 2) { + status.bits = get_fifo(regs); mb(); + } + + status.bits = get_fifo(regs); mb(); + + if (status.st.scsi_status_code != SCSI_ST_GOOD) { + scsi_error(asc->active_target, SCSI_ERR_STATUS, status.bits, 0); + asc->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? + SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; + } else + asc->done = SCSI_RET_SUCCESS; + + status.bits = get_fifo(regs); /* just pop the command_complete */ + mb(); + + /* if reading, move the last piece of data in main memory */ + if (len = asc->in_count) { + register int count; + + count = asc->dmacnt_at_end; + if (count) { +#if 0 + this is incorrect and besides.. + tgt->ior->io_residual = count; +#endif + len -= count; + } + regs->asc_cmd = asc->script->command; + readback(regs->asc_cmd); + + ret = FALSE; + } else + ret = TRUE; + + asc->dmacnt_at_end = 0; + (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len); + if (!ret) + asc->script++; + return ret; +} + +boolean_t +asc_put_status(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register scsi2_status_byte_t status; + register target_info_t *tgt = asc->active_target; + int len; + + LOG(0x21,"put_status"); + + asc->state &= ~ASC_STATE_DMA_IN; + + if (len = asc->in_count) { + register int count; + + ASC_TC_GET(regs,count); mb(); + if (count) + len -= count; + } + (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len); + +/* status.st.scsi_status_code = SCSI_ST_GOOD; */ + regs->asc_fifo = 0; mb(); + regs->asc_fifo = SCSI_COMMAND_COMPLETE; mb(); + + return TRUE; +} + + +boolean_t +asc_dma_in(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + register int count; + unsigned char ff = get_reg(regs,asc_flags); mb(); + + LOG(0xE,"dma_in"); + tgt = asc->active_target; + + /* + * This seems to be needed on certain rudimentary targets + * (such as the DEC TK50 tape) which apparently only pick + * up 6 initial bytes: when you add the initial IDENTIFY + * you are left with 1 pending byte, which is left in the + * FIFO and would otherwise show up atop the data we are + * really requesting. + * + * This is only speculation, though, based on the fact the + * sequence step value of 3 out of select means the target + * changed phase too quick and some bytes have not been + * xferred (see NCR manuals). Counter to this theory goes + * the fact that the extra byte that shows up is not alwyas + * zero, and appears to be pretty random. + * Note that asc_flags say there is one byte in the FIFO + * even in the ok case, but the sstep value is the right one. + * Note finally that this might all be a sync/async issue: + * I have only checked the ok case on synch disks so far. + * + * Indeed it seems to be an asynch issue: exabytes do it too. + */ + if ((tgt->sync_offset == 0) && ((ff & ASC_FLAGS_SEQ_STEP) != 0x80)) { + regs->asc_cmd = ASC_CMD_NOP; + wbflush(); + PRINT(("[tgt %d: %x while %d]", tgt->target_id, ff, tgt->cur_cmd)); + while ((ff & ASC_FLAGS_FIFO_CNT) != 0) { + ff = get_fifo(regs); mb(); + ff = get_reg(regs,asc_flags); mb(); + } + } + + asc->state |= ASC_STATE_DMA_IN; + + count = (*asc->dma_ops->start_datain)(asc->dma_state, tgt); + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if ((asc->in_count = count) == tgt->transient_state.in_count) + return TRUE; + regs->asc_cmd = asc->script->command; mb(); + asc->script = asc_script_restart_data_in; + return FALSE; +} + +asc_dma_in_r(asc, csr, ir) + register asc_softc_t asc; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + register int count; + boolean_t advance_script = TRUE; + + + LOG(0x1f,"dma_in_r"); + tgt = asc->active_target; + + asc->state |= ASC_STATE_DMA_IN; + + if (asc->in_count == 0) { + /* + * Got nothing yet, we just reconnected. + */ + register int avail; + + /* + * NOTE: if we have to handle the RFB (obb), + * the odd byte has been installed at reconnect + * time, before switching to data-in phase. Now + * we are already in data-in phase. + * It is up to the DMA engine to trim the dma_ptr + * down one byte. + */ + + count = (*asc->dma_ops->restart_datain_1) + (asc->dma_state, tgt); + + /* common case of 8k-or-less read ? */ + advance_script = (tgt->transient_state.in_count == count); + + } else { + + /* + * We received some data. + */ + register int offset, xferred; + + /* + * Problem: sometimes we get a 'spurious' interrupt + * right after a reconnect. It goes like disconnect, + * reconnect, dma_in_r, here but DMA is still rolling. + * Since there is no good reason we got here to begin with + * we just check for the case and dismiss it: we should + * get another interrupt when the TC goes to zero or the + * target disconnects. + */ + ASC_TC_GET(regs,xferred); mb(); + if (xferred != 0) + return FALSE; + + xferred = asc->in_count - xferred; + assert(xferred > 0); + + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + /* + * There should NOT be any obb issues here, + * we would have no control anyways. + */ + count = (*asc->dma_ops->restart_datain_2) + (asc->dma_state, tgt, xferred); + + asc->in_count = count; + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + regs->asc_cmd = asc->script->command; mb(); + + (*asc->dma_ops->restart_datain_3) + (asc->dma_state, tgt); + + /* last chunk ? */ + if (count == tgt->transient_state.in_count) + asc->script++; + + return FALSE; + } + + asc->in_count = count; + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if (!advance_script) { + regs->asc_cmd = asc->script->command; + readback(regs->asc_cmd); + } + return advance_script; +} + + +/* send data to target. Only called to start the xfer */ + +boolean_t +asc_dma_out(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register int reload_count; + register target_info_t *tgt; + int command; + + LOG(0xF,"dma_out"); + + ASC_TC_GET(regs, reload_count); mb(); + asc->extra_count = fifo_count(regs); + reload_count += asc->extra_count; + ASC_TC_PUT(regs, reload_count); + asc->state &= ~ASC_STATE_DMA_IN; + readback(regs->asc_cmd); + + tgt = asc->active_target; + + command = asc->script->command; + + if (reload_count == 0) reload_count = ASC_TC_MAX; + asc->out_count = reload_count; + + if (reload_count >= tgt->transient_state.out_count) + asc->script++; + else + asc->script = asc_script_restart_data_out; + + if ((*asc->dma_ops->start_dataout) + (asc->dma_state, tgt, (volatile unsigned *)®s->asc_cmd, + command, &asc->extra_count)) { + regs->asc_cmd = command; + readback(regs->asc_cmd); + } + + return FALSE; +} + +/* send data to target. Called in two different ways: + (a) to restart a big transfer and + (b) after reconnection + */ +boolean_t +asc_dma_out_r(asc, csr, ir) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + register target_info_t *tgt; + boolean_t advance_script = TRUE; + int count; + + + LOG(0x20,"dma_out_r"); + + tgt = asc->active_target; + asc->state &= ~ASC_STATE_DMA_IN; + + if (asc->out_count == 0) { + /* + * Nothing committed: we just got reconnected + */ + count = (*asc->dma_ops->restart_dataout_1) + (asc->dma_state, tgt); + + /* is this the last chunk ? */ + advance_script = (tgt->transient_state.out_count == count); + } else { + /* + * We sent some data. + */ + register int offset, xferred; + + ASC_TC_GET(regs,count); mb(); + + /* see comment above */ + if (count) { + return FALSE; + } + + count += fifo_count(regs); + count -= asc->extra_count; + xferred = asc->out_count - count; + assert(xferred > 0); + + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + count = (*asc->dma_ops->restart_dataout_2) + (asc->dma_state, tgt, xferred); + + /* last chunk ? */ + if (tgt->transient_state.out_count == count) + goto quickie; + + asc->out_count = count; + + asc->extra_count = (*asc->dma_ops->restart_dataout_3) + (asc->dma_state, tgt, + (volatile unsigned *)®s->asc_fifo); + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + regs->asc_cmd = asc->script->command; mb(); + + (*asc->dma_ops->restart_dataout_4)(asc->dma_state, tgt); + + return FALSE; + } + +quickie: + asc->extra_count = (*asc->dma_ops->restart_dataout_3) + (asc->dma_state, tgt, + (volatile unsigned *)®s->asc_fifo); + + asc->out_count = count; + + ASC_TC_PUT(regs, count); + readback(regs->asc_cmd); + + if (!advance_script) { + regs->asc_cmd = asc->script->command; + } + return advance_script; +} + +boolean_t +asc_dosynch(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char c; + int i, per, offs; + register target_info_t *tgt; + + /* + * Phase is MSG_OUT here. + * Try to go synchronous, unless prohibited + */ + tgt = asc->active_target; + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + delay(1); + + per = asc_min_period; + if (BGET(scsi_no_synchronous_xfer,asc->sc->masterno,tgt->target_id)) + offs = 0; + else + offs = asc_max_offset; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb(); + regs->asc_fifo = 3; mb(); + regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb(); + regs->asc_fifo = asc_to_scsi_period(asc_min_period,asc->clk); mb(); + regs->asc_fifo = offs; mb(); + regs->asc_cmd = ASC_CMD_XFER_INFO; + readback(regs->asc_cmd); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + /* some targets might be slow to move to msg-in */ + + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) { + + /* wait for direction bit to flip */ + csr = asc_wait(regs, SCSI_IO, 0); + ir = get_reg(regs,asc_intr); mb(); + /* Some ugly targets go stright to command phase. + "You could at least say goodbye" */ + if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) + goto did_not; + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + gimmeabreak(); + } + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + /* some targets do not even take all the bytes out */ + while (fifo_count(regs) > 0) { + c = get_fifo(regs); /* see what it says */ + mb(); + } + + if (c == SCSI_MESSAGE_REJECT) { +did_not: + printf(" did not like SYNCH xfer "); + + /* Tk50s get in trouble with ATN, sigh. */ + regs->asc_cmd = ASC_CMD_CLR_ATN; + readback(regs->asc_cmd); + + goto cmd; + } + + /* + * Receive the rest of the message + */ + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + + if (c != SCSI_EXTENDED_MESSAGE) + gimmeabreak(); + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + if (get_fifo(regs) != 3) + panic("asc_dosynch"); + + for (i = 0; i < 3; i++) { + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + regs->asc_cmd = ASC_CMD_XFER_INFO; mb(); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr);/*ack*/ mb(); + c = get_fifo(regs); mb(); + + if (i == 1) tgt->sync_period = scsi_period_to_asc(c,asc->clk); + if (i == 2) tgt->sync_offset = c; + } + +cmd: + regs->asc_cmd = ASC_CMD_MSG_ACPT; mb(); + csr = asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + /* Might have to wait a bit longer for slow targets */ + for (c = 0; SCSI_PHASE(get_reg(regs,asc_csr)) == SCSI_PHASE_MSG_IN; c++) { + mb(); + delay(2); + if (c & 0x80) break; /* waited too long */ + } + csr = get_reg(regs,asc_csr); mb(); + + /* phase should normally be command here */ + if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) { + register char *cmd = tgt->cmd_ptr; + + /* test unit ready or inquiry */ + for (i = 0; i < sizeof(scsi_command_group_0); i++) { + regs->asc_fifo = *cmd++; mb(); + } + ASC_TC_PUT(regs,0xff); mb(); + regs->asc_cmd = ASC_CMD_XFER_PAD; /*0x18*/ mb(); + + if (tgt->cur_cmd == SCSI_CMD_INQUIRY) { + tgt->transient_state.script = asc_script_data_in; + asc->script = tgt->transient_state.script; + regs->asc_syn_p = tgt->sync_period; + regs->asc_syn_o = tgt->sync_offset; mb(); + return FALSE; + } + + csr = asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr);/*ack*/ mb(); + } + + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) + csr = asc_wait(regs, SCSI_IO, 1); /* direction flip */ + +status: + if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) + gimmeabreak(); + + return TRUE; +} + +/* The other side of the coin.. */ +asc_odsynch(asc, initiator) + register asc_softc_t asc; + target_info_t *initiator; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char c; + int len, per, offs; + + /* + * Phase is MSG_OUT, we are the target and we have control. + * Any IDENTIFY messages have been handled already. + */ + initiator->flags |= TGT_DID_SYNCH; + initiator->flags &= ~TGT_TRY_SYNCH; + + /* + * We only understand synch negotiations + */ + c = get_fifo(regs); mb(); + if (c != SCSI_EXTENDED_MESSAGE) goto bad; + + /* + * This is not in the specs, but apparently the chip knows + * enough about scsi to receive the length automatically. + * So there were two bytes in the fifo at function call. + */ + len = get_fifo(regs); mb(); + if (len != 3) goto bad; + while (len) { + if (fifo_count(regs) == 0) { + regs->asc_cmd = ASC_CMD_RCV_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + } + c = get_fifo(regs); mb(); + if (len == 1) offs = c; + if (len == 2) per = c; + len--; + } + + /* + * Adjust the proposed parameters + */ + c = scsi_period_to_asc(per,asc->clk); + initiator->sync_period = c; + per = asc_to_scsi_period(c,asc->clk); + + if (offs > asc_max_offset) offs = asc_max_offset; + initiator->sync_offset = offs; + + /* + * Tell him what the deal is + */ + regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb(); + regs->asc_fifo = 3; mb(); + regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb(); + regs->asc_fifo = per; mb(); + regs->asc_fifo = offs; mb(); + regs->asc_cmd = ASC_CMD_SND_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); + + /* + * Exit conditions: fifo empty, phase undefined but non-command + */ + return; + + /* + * Something wrong, reject the message + */ +bad: + while (fifo_count(regs)) { + c = get_fifo(regs); mb(); + } + regs->asc_fifo = SCSI_MESSAGE_REJECT; mb(); + regs->asc_cmd = ASC_CMD_SND_MSG; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + c = get_reg(regs,asc_intr); mb(); +} + +/* + * The bus was reset + */ +asc_bus_reset(asc) + register asc_softc_t asc; +{ + register asc_padded_regmap_t *regs = asc->regs; + + LOG(0x1d,"bus_reset"); + + /* + * Clear bus descriptor + */ + asc->script = 0; + asc->error_handler = 0; + asc->active_target = 0; + asc->next_target = 0; + asc->state &= ASC_STATE_SPEC_DMA | ASC_STATE_DO_RFB; + queue_init(&asc->waiting_targets); + asc->wd.nactive = 0; + asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA); + + printf("asc: (%d) bus reset ", ++asc->wd.reset_count); + + /* some targets take long to reset */ + delay( scsi_delay_after_reset + + asc->sc->initiator_id * 500000); /* if multiple initiators */ + + if (asc->sc == 0) /* sanity */ + return; + + scsi_bus_was_reset(asc->sc); +} + +/* + * Disconnect/reconnect mode ops + */ + +/* get the message in via dma */ +boolean_t +asc_msg_in(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register target_info_t *tgt; + register asc_padded_regmap_t *regs = asc->regs; + unsigned char ff; + + LOG(0x10,"msg_in"); + /* must clean FIFO as in asc_dma_in, sigh */ + while (fifo_count(regs) != 0) { + ff = get_fifo(regs); mb(); + } + + (void) (*asc->dma_ops->start_msgin)(asc->dma_state, asc->active_target); + /* We only really expect two bytes, at tgt->cmd_ptr */ + ASC_TC_PUT(regs, sizeof(scsi_command_group_0)); + readback(regs->asc_cmd); + + return TRUE; +} + +/* check the message is indeed a DISCONNECT */ +boolean_t +asc_disconnect(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register char *msgs, ff; + register target_info_t *tgt; + asc_padded_regmap_t *regs; + + tgt = asc->active_target; + + (*asc->dma_ops->end_msgin)(asc->dma_state, tgt); + + /* + * Do not do this. It is most likely a reconnection + * message that sits there already by the time we + * get here. The chip is smart enough to only dma + * the bytes that correctly came in as msg_in proper, + * the identify and selection bytes are not dma-ed. + * Yes, sometimes the hardware does the right thing. + */ +#if 0 + /* First check message got out of the fifo */ + regs = asc->regs; + while (fifo_count(regs) != 0) { + *msgs++ = get_fifo(regs); + } +#endif + msgs = tgt->cmd_ptr; + + /* A SDP message preceeds it in non-completed READs */ + if ((msgs[0] == SCSI_DISCONNECT) || /* completed */ + ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* non complete */ + (msgs[1] == SCSI_DISCONNECT))) { + /* that's the ok case */ + } else + printf("[tgt %d bad SDP: x%x]", + tgt->target_id, *((unsigned short *)msgs)); + + return TRUE; +} + +/* save all relevant data, free the BUS */ +boolean_t +asc_disconnected(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register target_info_t *tgt; + + LOG(0x11,"disconnected"); + asc_disconnect(asc,csr,ir); + + tgt = asc->active_target; + tgt->flags |= TGT_DISCONNECTED; + tgt->transient_state.handler = asc->error_handler; + /* anything else was saved in asc_err_disconn() */ + + PRINT(("{D%d}", tgt->target_id)); + + asc_release_bus(asc); + + return FALSE; +} + +int asc_restore_ptr = 1; + +/* get reconnect message out of fifo, restore BUS */ +boolean_t +asc_reconnect(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; + +{ + register target_info_t *tgt; + asc_padded_regmap_t *regs; + unsigned int id; + + LOG(0x12,"reconnect"); + /* + * See if this reconnection collided with a selection attempt + */ + if (asc->state & ASC_STATE_BUSY) + asc->state |= ASC_STATE_COLLISION; + + asc->state |= ASC_STATE_BUSY; + + /* find tgt: first byte in fifo is (tgt_id|our_id) */ + regs = asc->regs; + while (fifo_count(regs) > 2) /* sanity */ { + id = get_fifo(regs); mb(); + } + if (fifo_count(regs) != 2) + gimmeabreak(); + + id = get_fifo(regs); /* must decode this now */ + mb(); + id &= ~(1 << asc->sc->initiator_id); + { + register int i; + for (i = 0; id > 1; i++) + id >>= 1; + id = i; + } + + tgt = asc->sc->target[id]; + if (tgt == 0) panic("asc_reconnect"); + + /* synch things*/ + regs->asc_syn_p = tgt->sync_period; + regs->asc_syn_o = tgt->sync_offset; + readback(regs->asc_syn_o); + + /* Get IDENTIFY message */ + { + register int i = get_fifo(regs); + if ((i & SCSI_IDENTIFY) == 0) + gimmeabreak(); + i &= 0x7; + /* If not LUN 0 see which target it is */ + if (i) { + target_info_t *tgt1; + for (tgt1 = tgt->next_lun; + tgt1 && tgt1 != tgt; + tgt1 = tgt1->next_lun) + if (tgt1->lun == i) { + tgt = tgt1; + break; + } + } + } + + if (asc->state & ASC_STATE_DO_RFB) { + if (tgt->transient_state.has_oddb) { + if (tgt->sync_period) { + regs->asc_cnfg2 = ASC_CNFG2_SCSI2 | ASC_CNFG2_RFB; + wbflush(); + regs->asc_rfb = tgt->transient_state.the_oddb; + } else { + regs->asc_fifo = tgt->transient_state.the_oddb; + } + tgt->transient_state.has_oddb = FALSE; + } else { + regs->asc_cnfg2 = ASC_CNFG2_SCSI2; + } + wbflush(); + } + + PRINT(("{R%d}", id)); + if (asc->state & ASC_STATE_COLLISION) + PRINT(("[B %d-%d]", asc->active_target->target_id, id)); + + LOG(0x80+id,0); + + asc->active_target = tgt; + tgt->flags &= ~TGT_DISCONNECTED; + + asc->script = tgt->transient_state.script; + asc->error_handler = tgt->transient_state.handler; + asc->in_count = 0; + asc->out_count = 0; + + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + + /* What if there is a RESTORE_PTR msgin ? */ + if (asc_restore_ptr) { +more_msgin: + csr = asc_wait(regs, ASC_CSR_INT, 1); + + if (SCSI_PHASE(csr) == SCSI_PHASE_MSG_IN) { + /* ack intr */ + id = get_reg(regs,asc_intr); mb(); + + /* Ok, get msg */ + regs->asc_cmd = ASC_CMD_XFER_INFO; + readback(regs->asc_cmd); + /* wait for xfer done */ + csr = asc_wait(regs, ASC_CSR_INT, 1); + + /* look at what we got */ + id = get_fifo(regs); + + if (id != SCSI_RESTORE_POINTERS) + printf("asc%d: uhu msg %x\n", asc->sc->masterno, id); + /* ack intr */ + id = get_reg(regs,asc_intr); mb(); + + /* move on */ + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + goto more_msgin; + } + } + + return FALSE; +} + + +/* We have been selected as target */ + +boolean_t +asc_selected(asc, csr, ir) + register asc_softc_t asc; + register unsigned csr, ir; +{ + register asc_padded_regmap_t *regs = asc->regs; + register unsigned char id; + target_info_t *self, *initiator; + unsigned int len; + + /* + * Find initiator's id: the head of the fifo is (init_id|our_id) + */ + + id = get_fifo(regs); /* must decode this now */ + mb(); + id &= ~(1 << asc->sc->initiator_id); + { + register int i; + for (i = 0; id > 1; i++) + id >>= 1; + id = i; + } + + /* + * See if we have seen him before + */ + self = asc->sc->target[asc->sc->initiator_id]; + initiator = asc->sc->target[id]; + if (initiator == 0) { + + initiator = scsi_slave_alloc(asc->sc->masterno, id, asc); + (*asc->dma_ops->new_target)(asc->dma_state, initiator); + + } + + if (! (initiator->flags & TGT_ONLINE) ) + sccpu_new_initiator(self, initiator); + + /* + * If selected with ATN the chip did the msg-out + * phase already, let us look at the message(s) + */ + if (ir & ASC_INT_SEL_ATN) { + register unsigned char m; + + m = get_fifo(regs); mb(); + if ((m & SCSI_IDENTIFY) == 0) + gimmeabreak(); + + csr = get_reg(regs,asc_csr); mb(); + if ((SCSI_PHASE(csr) == SCSI_PHASE_MSG_OUT) && + fifo_count(regs)) + asc_odsynch(asc, initiator); + + /* Get the command now, unless we have it already */ + mb(); + if (fifo_count(regs) < sizeof(scsi_command_group_0)) { + regs->asc_cmd = ASC_CMD_RCV_CMD; + readback(regs->asc_cmd); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + csr = get_reg(regs,asc_csr); mb(); + } + } else { + /* + * Pop away the null byte that follows the id + */ + if (get_fifo(regs) != 0) + gimmeabreak(); + + } + + /* + * Take rest of command out of fifo + */ + self->dev_info.cpu.req_pending = TRUE; + self->dev_info.cpu.req_id = id; + self->dev_info.cpu.req_cmd = get_fifo(regs); + self->dev_info.cpu.req_lun = get_fifo(regs); + + LOG(0x80+self->dev_info.cpu.req_cmd, 0); + + switch ((self->dev_info.cpu.req_cmd & SCSI_CODE_GROUP) >> 5) { + case 0: + len = get_fifo(regs) << 16; mb(); + len |= get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + break; + case 1: + case 2: + len = get_fifo(regs); /* xxx lba1 */ mb(); + len = get_fifo(regs); /* xxx lba2 */ mb(); + len = get_fifo(regs); /* xxx lba3 */ mb(); + len = get_fifo(regs); /* xxx lba4 */ mb(); + len = get_fifo(regs); /* xxx xxx */ mb(); + len = get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + break; + case 5: + len = get_fifo(regs); /* xxx lba1 */ mb(); + len = get_fifo(regs); /* xxx lba2 */ mb(); + len = get_fifo(regs); /* xxx lba3 */ mb(); + len = get_fifo(regs); /* xxx lba4 */ mb(); + len = get_fifo(regs) << 24; mb(); + len |= get_fifo(regs) << 16; mb(); + len |= get_fifo(regs) << 8; mb(); + len |= get_fifo(regs); mb(); + if (get_fifo(regs) != 0) ; + break; + default: + panic("asc_selected"); + } + self->dev_info.cpu.req_len = len; +/*if (scsi_debug) printf("[L x%x]", len);*/ + + /* xxx pop the cntrl byte away */ + if (get_fifo(regs) != 0) + gimmeabreak(); + mb(); + + /* + * Setup state + */ + asc->active_target = self; + asc->done = SCSI_RET_IN_PROGRESS; + asc->out_count = 0; + asc->in_count = 0; + asc->extra_count = 0; + + /* + * Sync params. + */ + regs->asc_syn_p = initiator->sync_period; + regs->asc_syn_o = initiator->sync_offset; + readback(regs->asc_syn_o); + + /* + * Do the up-call + */ + LOG(0xB,"tgt-mode-restart"); + (*self->dev_ops->restart)( self, FALSE); + + /* The call above has either prepared the data, + placing an ior on self, or it handled it some + other way */ + if (self->ior == 0) + return FALSE; + + /* sanity */ + if (fifo_count(regs)) { + regs->asc_cmd = ASC_CMD_FLUSH; + readback(regs->asc_cmd); + } + + /* needed by some dma code to align things properly */ + self->transient_state.cmd_count = sizeof(scsi_command_group_0); + self->transient_state.in_count = len; /* XXX */ + + (*asc->dma_ops->map)(asc->dma_state, self); + + if (asc->wd.nactive++ == 0) + asc->wd.watchdog_state = SCSI_WD_ACTIVE; + + + { + register script_t scp; + unsigned char command; + + switch (self->dev_info.cpu.req_cmd) { + case SCSI_CMD_TEST_UNIT_READY: + scp = asc_script_t_data_in+1; break; + case SCSI_CMD_SEND: + scp = asc_script_t_data_in; break; + default: + scp = asc_script_t_data_out; break; + } + + asc->script = scp; + command = scp->command; + if (!(*scp->action)(asc, csr, ir)) + return FALSE; + +/*if (scsi_debug) printf("[F%x]", fifo_count(regs));*/ + + asc->script++; + regs->asc_cmd = command; mb(); + + } + + return FALSE; +} + + +/* + * Other utilities + */ +private void +pick_up_oddb( + target_info_t *tgt) +{ + register char *lastp; + + /* State should have been updated before we get here */ + lastp = tgt->dma_ptr + tgt->transient_state.dma_offset; + + if ((vm_offset_t) lastp & 1) { + tgt->transient_state.has_oddb = TRUE; + tgt->transient_state.the_oddb = lastp[-1]; + } else + tgt->transient_state.has_oddb = FALSE; +} + + +/* + * Error handlers + */ + +/* + * Fall-back error handler. + */ +asc_err_generic(asc, csr, ir) + register asc_softc_t asc; +{ + LOG(0x13,"err_generic"); + + /* handle non-existant or powered off devices here */ + if ((ir == ASC_INT_DISC) && + (asc_isa_select(asc->cmd_was)) && + (ASC_SS(asc->ss_was) == 0)) { + /* Powered off ? */ + target_info_t *tgt = asc->active_target; + + tgt->flags = 0; + tgt->sync_period = 0; + tgt->sync_offset = 0; mb(); + asc->done = SCSI_RET_DEVICE_DOWN; + asc_end(asc, csr, ir); + return; + } + + switch (SCSI_PHASE(csr)) { + case SCSI_PHASE_STATUS: + if (asc->script[-1].condition == SCSI_PHASE_STATUS) { + /* some are just slow to get out.. */ + } else + asc_err_to_status(asc, csr, ir); + return; + break; + case SCSI_PHASE_DATAI: + if (asc->script->condition == SCSI_PHASE_STATUS) { + /* + * Here is what I know about it. We reconnect to + * the target (csr 87, ir 0C, cmd 44), start dma in + * (81 10 12) and then get here with (81 10 90). + * Dma is rolling and keeps on rolling happily, + * the value in the counter indicates the interrupt + * was signalled right away: by the time we get + * here only 80-90 bytes have been shipped to an + * rz56 running synchronous at 3.57 Mb/sec. + */ +/* printf("{P}");*/ + return; + } + break; + case SCSI_PHASE_DATAO: + if (asc->script->condition == SCSI_PHASE_STATUS) { + /* + * See comment above. Actually seen on hitachis. + */ +/* printf("{P}");*/ + return; + } + break; + case SCSI_PHASE_CMD: + /* This can be the case with drives that are not + even scsi-1 compliant and do not like to be + selected with ATN (to do the synch negot) and + go stright to command phase, regardless */ + + if (asc->script == asc_script_try_synch) { + + target_info_t *tgt = asc->active_target; + register asc_padded_regmap_t *regs = asc->regs; + + tgt->flags |= TGT_DID_SYNCH; /* only one chance */ + tgt->flags &= ~TGT_TRY_SYNCH; + + if (tgt->cur_cmd == SCSI_CMD_INQUIRY) + tgt->transient_state.script = asc_script_data_in; + else + tgt->transient_state.script = asc_script_simple_cmd; + asc->script = tgt->transient_state.script; + regs->asc_cmd = ASC_CMD_CLR_ATN; + readback(regs->asc_cmd); + asc->regs->asc_cmd = ASC_CMD_XFER_PAD|ASC_CMD_DMA; /*0x98*/ mb(); + printf(" did not like SYNCH xfer "); + return; + } + /* fall through */ + } + gimmeabreak(); +} + +/* + * Handle generic errors that are reported as + * an unexpected change to STATUS phase + */ +asc_err_to_status(asc, csr, ir) + register asc_softc_t asc; +{ + script_t scp = asc->script; + + LOG(0x14,"err_tostatus"); + while (scp->condition != SCSI_PHASE_STATUS) + scp++; + (*scp->action)(asc, csr, ir); + asc->script = scp + 1; + asc->regs->asc_cmd = scp->command; mb(); +#if 0 + /* + * Normally, we would already be able to say the command + * is in error, e.g. the tape had a filemark or something. + * But in case we do disconnected mode WRITEs, it is quite + * common that the following happens: + * dma_out -> disconnect (xfer complete) -> reconnect + * and our script might expect at this point that the dma + * had to be restarted (it didn't notice it was completed). + * And in any event.. it is both correct and cleaner to + * declare error iff the STATUS byte says so. + */ + asc->done = SCSI_RET_NEED_SENSE; +#endif +} + +/* + * Handle disconnections as exceptions + */ +asc_err_disconn(asc, csr, ir) + register asc_softc_t asc; + register unsigned char csr, ir; +{ + register asc_padded_regmap_t *regs; + register target_info_t *tgt; + int count; + boolean_t callback = FALSE; + + LOG(0x16,"err_disconn"); + + /* + * We only do msg-in cases here + */ + if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) + return asc_err_generic(asc, csr, ir); + + regs = asc->regs; + tgt = asc->active_target; + + /* + * What did we expect to happen, and what did happen. + */ + switch (asc->script->condition) { + case SCSI_PHASE_DATAO: + /* + * A data out phase was either about to be started, + * or it was in progress but more had to go out later + * [e.g. a big xfer for instance, or more than the + * DMA engine can take in one shot]. + */ + LOG(0x1b,"+DATAO"); + if (asc->out_count) { + register int xferred, offset; + + /* + * Xfer in progress. See where we stopped. + */ + ASC_TC_GET(regs,xferred); /* temporary misnomer */ + /* + * Account for prefetching, in its various forms + */ + xferred += fifo_count(regs); + xferred -= asc->extra_count; + /* + * See how much went out, how much to go. + */ + xferred = asc->out_count - xferred; /* ok now */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*asc->dma_ops->disconn_1) + (asc->dma_state, tgt, xferred); + + } else { + + /* + * A disconnection before DMA was (re)started. + */ + callback = (*asc->dma_ops->disconn_2) + (asc->dma_state, tgt); + + } + asc->extra_count = 0; + tgt->transient_state.script = asc_script_restart_data_out; + break; + + + case SCSI_PHASE_DATAI: + /* + * Same as above, the other way around + */ + LOG(0x17,"+DATAI"); + if (asc->in_count) { + register int offset, xferred; + + /* + * How much did we expect, how much did we get + */ + ASC_TC_GET(regs,count); mb(); + xferred = asc->in_count - count; + assert(xferred > 0); + +if (regs->asc_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs)); + tgt->transient_state.in_count -= xferred; + assert(tgt->transient_state.in_count > 0); + + callback = (*asc->dma_ops->disconn_3) + (asc->dma_state, tgt, xferred); + + /* + * Handle obb if we have to. DMA code has + * updated pointers and flushed buffers. + */ + if (asc->state & ASC_STATE_DO_RFB) + pick_up_oddb(tgt); + + tgt->transient_state.script = asc_script_restart_data_in; + /* + * Some silly targets disconnect after they + * have given us all the data. + */ + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else + tgt->transient_state.script = asc_script_restart_data_in; + break; + + case SCSI_PHASE_STATUS: + /* + * Either one of the above cases here. Only diff + * the DMA engine was setup to run to completion + * and (most likely) did not. + */ + ASC_TC_GET(regs,count); mb(); + if (asc->state & ASC_STATE_DMA_IN) { + register int offset, xferred; + + LOG(0x1a,"+STATUS+R"); + + + /* + * Handle brain-dead sequence: + * 1-xfer all data, disconnect + * 2-reconnect, disconnect immediately ?? + * 3-rept 2 + * 4-reconnect,complete + */ + if (asc->in_count) { + + xferred = asc->in_count - count; + assert(xferred > 0); +if (regs->asc_flags & 0xf) +printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs)); + + tgt->transient_state.in_count -= xferred; + + callback = (*asc->dma_ops->disconn_4) + (asc->dma_state, tgt, xferred); + } + /* + * Handle obb if we have to. DMA code has + * updated pointers and flushed buffers. + */ + if (asc->state & ASC_STATE_DO_RFB) + pick_up_oddb(tgt); + + tgt->transient_state.script = asc_script_restart_data_in; + + /* see previous note */ + if (tgt->transient_state.in_count == 0) + tgt->transient_state.script++; + + } else { + + /* + * Outgoing xfer, take care of prefetching. + */ + /* add what's left in the fifo */ + count += fifo_count(regs); + /* take back the extra we might have added */ + count -= asc->extra_count; + /* ..and drop that idea */ + asc->extra_count = 0; + + LOG(0x19,"+STATUS+W"); + + /* + * All done ? This is less silly than with + * READs: some disks will only say "done" when + * the data is down on the platter, and some + * people like it much better that way. + */ + if ((count == 0) && (tgt->transient_state.out_count == asc->out_count)) { + /* all done */ + tgt->transient_state.script = asc->script; + tgt->transient_state.out_count = 0; + } else { + register int xferred, offset; + + /* how much we xferred */ + xferred = asc->out_count - count; + + /* how much to go */ + tgt->transient_state.out_count -= xferred; + assert(tgt->transient_state.out_count > 0); + + callback = (*asc->dma_ops->disconn_5) + (asc->dma_state,tgt,xferred); + + tgt->transient_state.script = asc_script_restart_data_out; + } + asc->out_count = 0; + } + break; + default: + gimmeabreak(); + return; + } + asc_msg_in(asc,csr,ir); + asc->script = asc_script_disconnect; + regs->asc_cmd = ASC_CMD_XFER_INFO|ASC_CMD_DMA; + wbflush(); + /* + * Prevent a race, now. If the reselection comes quickly + * the chip will prefetch and reload the transfer counter + * register. Make sure it will stop, by reloading a zero. + */ + regs->asc_tc_lsb = 0; + regs->asc_tc_msb = 0; + if (callback) + (*asc->dma_ops->disconn_callback)(asc->dma_state, tgt); +} + +/* + * Watchdog + * + * So far I have only seen the chip get stuck in a + * select/reselect conflict: the reselection did + * win and the interrupt register showed it but.. + * no interrupt was generated. + * But we know that some (name withdrawn) disks get + * stuck in the middle of dma phases... + */ +asc_reset_scsibus(asc) + register asc_softc_t asc; +{ + register target_info_t *tgt = asc->active_target; + register asc_padded_regmap_t *regs = asc->regs; + register int ir; + + if (scsi_debug && tgt) { + int dmalen; + ASC_TC_GET(asc->regs,dmalen); mb(); + printf("Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n", + tgt->target_id, tgt->cur_cmd, + tgt->transient_state.in_count, tgt->transient_state.out_count, + asc->in_count, asc->out_count, + dmalen); + } + ir = get_reg(regs,asc_intr); mb(); + if ((ir & ASC_INT_RESEL) && (SCSI_PHASE(regs->asc_csr) == SCSI_PHASE_MSG_IN)) { + /* getting it out of the woods is a bit tricky */ + spl_t s = splbio(); + + (void) asc_reconnect(asc, get_reg(regs,asc_csr), ir); + asc_wait(regs, ASC_CSR_INT, 1); + ir = get_reg(regs,asc_intr); mb(); + regs->asc_cmd = ASC_CMD_MSG_ACPT; + readback(regs->asc_cmd); + splx(s); + } else { + regs->asc_cmd = ASC_CMD_BUS_RESET; mb(); + delay(35); + } +} + +#endif NASC > 0 + |