1
0
mirror of https://github.com/PDP-10/klh10.git synced 2026-02-05 16:05:30 +00:00
Files
PDP-10.klh10/src/dvrh20.c
Olaf Seibert 58b59dbaa1 Overlay panda-dist/klh10-2.0h
by the late MRC, Mark Crispin.
Source: probably the former http://panda.com/tops-20/ .
panda-dist.tar.gz dated Mar 30  2007.
2015-04-27 23:07:21 +02:00

1154 lines
35 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* DVRH20.C - Emulates RH20 disk devices for KL10
*/
/* $Id: dvrh20.c,v 2.4 2003/02/23 18:16:54 klh Exp $
*/
/* Copyright © 1993, 2001 Kenneth L. Harrenstien
** All Rights Reserved
**
** This file is part of the KLH10 Distribution. Use, modification, and
** re-distribution is permitted subject to the terms in the file
** named "LICENSE", which contains the full text of the legal notices
** and should always accompany this Distribution.
**
** This software is provided "AS IS" with NO WARRANTY OF ANY KIND.
**
** This notice (including the copyright and warranty disclaimer)
** must be included in all copies or derivations of this software.
*/
/*
* $Log: dvrh20.c,v $
* Revision 2.4 2003/02/23 18:16:54 klh
* Add <string.h> to predeclare memset.
*
* Revision 2.3 2001/11/10 21:28:59 klh
* Final 2.0 distribution checkin
*
*/
#include "klh10.h"
#if !KLH10_DEV_RH20 && CENV_SYS_DECOSF
/* Stupid gubbish needed to prevent OSF/1 AXP compiler from
** halting merely because compiled file is empty!
*/
static int decosfcclossage;
#endif
#if KLH10_DEV_RH20 /* Moby conditional for entire file */
#include <stddef.h> /* For size_t etc */
#include <string.h> /* For memset etc */
#include <errno.h>
#include <stdio.h>
#include "kn10def.h" /* This includes OSD defs */
#include "kn10dev.h"
#include "dvuba.h"
#include "dvrh20.h"
#ifdef RCSID
RCSID(dvrh20_c,"$Id: dvrh20.c,v 2.4 2003/02/23 18:16:54 klh Exp $")
#endif
struct rh20 {
struct device rh_dv; /* Generic 10 device structure */
/* RH20-specific vars */
int rh_no; /* RH20 Unit number (0-7) */
uint18 rh_cond; /* CONI bits */
int rh_pilev; /* PI level mask (0 if PIA=0) */
/* "Prep reg" settings */
uint18 rh_prep; /* Prep reg LH */
int rh_rs; /* Register select */
int rh_ds; /* Drive select */
struct device *rh_dsptr;
uint32 rh_reg[8]; /* RH20 Internal registers */
int rh_sbarf; /* TRUE if SBAR was set */
int rh_bcnt; /* Current block count from PTCR */
/* Channel vars */
int rh_eptoff; /* Offset of channel area in EPT */
paddr_t rh_clp; /* DC "PC" - Current Command List Pointer */
uint18 rh_dcsts; /* DC status flag bits (LH) */
w10_t rh_dcccw; /* DC current cmd word */
int rh_dcwcnt; /* DC cmd word count */
paddr_t rh_dcbuf; /* DC cmd data buffer pointer */
int rh_dchlt; /* TRUE if halt when current wordcount done */
int rh_dcrev; /* TRUE if doing reverse data xfer */
int rh_dcwrt; /* TRUE if doing device write */
/* Drive bindings */
struct device *rh_drive[8];
int rh_attn; /* ATTN summary for all drives */
};
/* Macro for simpler access to RH20 internal regs */
#define RH20REG(c,r) ((c)->rh_reg[(r)-RH20_1ST])
static int nrh20s = 0;
/* static */ struct rh20 dvrh20[RH20_NSUP];
#define RHDEBUG(rh) ((rh)->rh_dv.dv_debug)
#define RHDBF(rh) ((rh)->rh_dv.dv_dbf)
/* Function predecls */
/* Functions provided to device vector */
static w10_t rh20_pifnwd(struct device *d);
static void rh20_cono(struct device *d, h10_t erh);
static w10_t rh20_coni(struct device *d);
static void rh20_datao(struct device *d, w10_t w);
static w10_t rh20_datai(struct device *d);
static int rh20_bind(struct device *d, FILE *of, struct device *u, int num);
static int rh20_init(struct device *d, FILE *of);
static void rh20_reset(struct device *d);
#if 0
static int rh20_readin(); /* Readin boot, if supported */
#endif
/* Functions provided to slave device */
static void rh20_attn(struct device *drv, int on);
static void rh20_drerr(struct device *drv);
static int rh20_iobeg(struct device *drv, int wflg);
static int rh20_iobuf(struct device *drv, register int wc, vmptr_t *avp);
static void rh20_ioend(struct device *drv, int bc);
/* Completely internal functions */
static void rh_clear(struct rh20 *rh);
static void rh_picheck(struct rh20 *rh);
static void rh_pi(struct rh20 *rh);
static void rh_xfrbeg(struct rh20 *rh);
static void rh_clrattn(struct rh20 *rh, int msk);
static void rhdc_init(struct rh20 *rh, int mbc);
static void rhdc_clear(struct rh20 *rh);
static int rhdc_ccwget(struct rh20 *rh);
/* RH20 interface routines to KLH10 */
struct device *
dvrh20_create(FILE *f, char *s)
{
register struct rh20 *rh;
/* Parse string to determine which device to use, config, etc etc
** But for now, just allocate sequentially. Hack.
*/
if (nrh20s >= RH20_NSUP) {
fprintf(f, "Too many RH20s, max: %d\n", RH20_NSUP);
return NULL;
}
rh = &dvrh20[nrh20s++]; /* Pick unused RH20 */
/* Initialize generic device part of RH20 struct */
iodv_setnull(&rh->rh_dv); /* Initialize as null device */
rh->rh_dv.dv_dflags = DVFL_CTLR;
rh->rh_dv.dv_pifnwd = rh20_pifnwd;
rh->rh_dv.dv_cono = rh20_cono;
rh->rh_dv.dv_coni = rh20_coni;
rh->rh_dv.dv_datao = rh20_datao;
rh->rh_dv.dv_datai = rh20_datai;
rh->rh_dv.dv_bind = rh20_bind; /* Controller, so can bind. */
rh->rh_dv.dv_init = rh20_init; /* Set up own post-bind init */
rh->rh_dv.dv_reset = rh20_reset; /* System reset (clear stuff) */
return &rh->rh_dv;
}
static int
rh20_bind(struct device *d, /* Our device (controller) */
FILE *of,
register struct device *slv, /* Slave device */
int num)
{
register struct rh20 *rh = (struct rh20 *)d;
if (0 <= num && num < 8) ;
else {
if (of) fprintf(of, "RH20 can only bind up to 8 drives (#%d invalid).\n", num);
return FALSE;
}
/* Backpointer and device # (dv_ctlr, dv_num) are already set up. */
slv->dv_attn = rh20_attn; /* Set attention handler */
slv->dv_drerr = rh20_drerr; /* Set exception handler */
slv->dv_iobeg = rh20_iobeg; /* IO xfer beg */
slv->dv_iobuf = rh20_iobuf; /* IO xfer buffer setup */
slv->dv_ioend = rh20_ioend; /* IO xfer end */
rh->rh_drive[num] = slv;
return TRUE;
}
static int
rh20_init(register struct device *d, FILE *of)
{
register struct rh20 *rh = (struct rh20 *)d;
/* Transform device # into RH20 unit # */
if (DEVRH20(0) <= rh->rh_dv.dv_num && rh->rh_dv.dv_num < DEVRH20(8)) {
rh->rh_no = (rh->rh_dv.dv_num - DEVRH20(0)) >> 2;
} else {
if (of) fprintf(of, "Trying to init RH20 with non-Massbus device #\n");
return FALSE;
}
rhdc_init(rh, rh->rh_no); /* Initialize data channel for this MBC */
rh->rh_cond = 0; /* Clear all CONI bits */
rh->rh_prep = 0; /* Clear prep reg */
rh->rh_rs = rh->rh_ds = 0; /* Default selections 0 */
rh->rh_dsptr = NULL; /* No drive */
memset((char *)&(rh->rh_reg[0]), 0, sizeof(rh->rh_reg));
rh->rh_sbarf = 0;
rh->rh_attn = 0; /* Clear ATTN summary */
return TRUE;
}
static void
rh20_reset(struct device *d)
{
rh_clear((struct rh20 *)d);
}
/* PI: Get function word */
static w10_t
rh20_pifnwd(struct device *d)
{
register w10_t w;
#if 0
/* Clear PI request?
** This is probably not correct; I suspect PI stays on until condition
** is explicitly turned off with CONO, etc.
*/
if (rh->rh_dv.dv_pireq) /* If PI request outstanding, */
(*rh->rh_dv.dv_pifun)(rh, 0); /* clear it. */
#endif
/* Return PI function word; only low 9 bits of addr are used. */
LRHSET(w, PIFN_ASPEPT | PIFN_FVEC,
RH20REG((struct rh20 *)d, RH20_IVIR) & H10MASK);
return w;
}
/* CONO 18-bit conds out
** Args D, ERH
** Returns nothing
*/
static insdef_cono(rh20_cono)
{
register struct rh20 *rh = (struct rh20 *)d;
register uint18 cond = erh;
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_cono: %lo]\r\n", (long)erh);
/* B24 - Clears RAE and associated PI req */
if (cond & RH20CO_CRAE) {
rh->rh_cond &= ~RH20CI_RAE;
}
/* B25 - Clear Massbus Controller. Resets RH20 and all drives to
** power-up state. Will hang channel if xfer in progress,
** unless RCLP also set.
*/
if (cond & RH20CO_CMC) {
rh_clear(rh);
}
/* B26 - Clear all xfer error indicators (CONI 18-23, 26) */
if (cond & RH20CO_TEC) {
rh->rh_cond &= ~(
RH20CI_DPE /* B18 - Data Parity Error */
| RH20CI_DEE /* B19 - Drive Exception (xfer error) */
| RH20CI_LWCE /* B20 - Long Word Count Error */
| RH20CI_SWCE /* B21 - Short Word Count Error */
| RH20CI_CE /* B22 - Channel Error */
| RH20CI_DR /* B23 - Drive Response Error (ie no-ex) */
| RH20CI_DOE /* B26 - Data Overrun */
);
}
/* B27 - MassBus Enable
** Note this doesn't actually do anything - code always assumes it's
** enabled, to avoid time-wasting checks.
*/
if (cond & RH20CO_MBE) {
rh->rh_cond |= RH20CI_MBE; /* Same bit, could just mask in */
}
/* B28 - Reset Cmd List Ptr (inits channel) */
if (cond & RH20CO_RCLP) {
rhdc_clear(rh);
}
/* B29 - Clears cmd file xfer logic.
** (Used if previous data xfer caused xfer error, CONI bits 18-23, 26).
*/
if (cond & RH20CO_DSCRF) {
rh->rh_cond &= ~(RH20CI_SCRF | RH20CI_PCRF); /* No regs loaded */
rh->rh_sbarf = 0; /* No SBAR either */
}
/* B30 - Allow Massbus ATTN to generate PI.
** Unlike most of the other CONO bits, the state of this one is
** always significant; MBAE is turned off if not set.
*/
if (cond & RH20CO_MEAE) {
rh->rh_cond |= RH20CI_MBAE; /* B30 - Enable PI on RH20CI_MBA */
} else
rh->rh_cond &= ~RH20CI_MBAE; /* B30 - Enable PI on RH20CI_MBA */
/* B31 - Stop Transfer. (nothing cleared) */
if (cond & RH20CO_ST) {
/* Ugh, nothing we can really do about this. */
}
/* B32 - Clear Cmd Done, clears CONI bit 32 */
if (cond & RH20CO_CCMD) {
#if 1 /* Avoid T20 ILLGO BUGHLTs for stacked xfers! */
/* Start xfer if turning off a live CMD bit and STCR has a pending
** xfer command; assume T20 int handler has fired.
*/
if ((rh->rh_cond & (RH20CI_CMD|RH20CI_PCRF|RH20CI_SCRF))
== (RH20CI_CMD|RH20CI_SCRF)) {
/* Both CMD and SCRF are on, and PCRF is off -- start new xfer! */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_cono 2nd xfrbeg!]");
rh->rh_cond &= ~RH20CI_CMD; /* Clear it */
rh_xfrbeg(rh);
} else
#endif
rh->rh_cond &= ~RH20CI_CMD; /* Clear it */
}
/* B33-35 - PI channel assignment (0 = none) */
rh->rh_cond = (rh->rh_cond & ~RH20CI_PIA)
| (cond & RH20CO_PIA);
rh->rh_pilev = (1 << (7-(cond & RH20CO_PIA))) & 0177; /* 0 if PIA=0 */
if (rh->rh_dv.dv_pireq && (rh->rh_dv.dv_pireq != rh->rh_pilev)) {
/* Changed PIA while PI outstanding; flush it, let picheck re-req */
(*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* clear it. */
}
/* Check for any changes to PI status */
rh_picheck(rh);
}
/* CONI 36-bit conds in
** Args D
** Returns condition word
*/
static insdef_coni(rh20_coni)
{
register w10_t w;
if (RHDEBUG((struct rh20 *)d))
fprintf(RHDBF((struct rh20 *)d), "[rh20_coni: %lo]",
(long)((struct rh20 *)d)->rh_cond);
LRHSET(w, 0, ((struct rh20 *)d)->rh_cond);
return w;
}
/* DATAO word out
** Args D, W
** Returns nothing
*/
static insdef_datao(rh20_datao)
{
register struct rh20 *rh = (struct rh20 *)d;
/* First make sure it's OK to do anything at all, by checking RAE.
** Note that the setting of DRAES in the current DATAO has no effect on
** the initial test -- although it does affect the handling of RAE if
** it is raised during the current DATAO.
*/
if ((rh->rh_cond & RH20CI_RAE) && !(rh->rh_prep & RH20DO_DRAES)) {
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_datao: %o.? %lo,,%lo RAE-ignored]\r\n",
rh->rh_no, (long)LHGET(w), (long)RHGET(w));
return; /* Ugh, RAE but no DRAES, so do nothing */
}
/* Now always set prep reg values.
** Note that CBEP is ignored.
*/
rh->rh_prep = LHGET(w) &
(RH20DO_RS | RH20DO_LR | RH20DO_DRAES | RH20DO_DS);
rh->rh_rs = (rh->rh_prep >> 12) & 077;
rh->rh_ds = rh->rh_prep & RH20DO_DS;
rh->rh_dsptr = rh->rh_drive[rh->rh_ds];
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_datao: %o.%o RS%o <= %lo,,%lo]\r\n",
rh->rh_no, rh->rh_ds, rh->rh_rs,
(long)LHGET(w), (long)RHGET(w));
/* Now see whether to actually load anything else */
if (!(rh->rh_prep & RH20DO_LR))
return; /* Nope, just prep reg, we're done. */
switch (rh->rh_rs) {
default:
if (rh->rh_rs <= 037) {
/* External register.
** In the real RH20, external registers cannot be updated if
** a command file transfer (using PBAR&PTCR) is in progress;
** the RH20 waits until the massbus is free.
** This may become a consideration for future asynch operation.
** Hopefully no monitor tries to do this.
*/
/* Note RHR_ATTN needs special handling, just as for DATAI.
*/
if (rh->rh_rs == RHR_ATTN) { /* Attn summary? */
/* Turn off ATTN bit for all drive bits provided.
*/
rh_clrattn(rh, (int)RHGET(w)&MASK16);
return;
}
if (rh->rh_dsptr &&
(*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr,
rh->rh_rs, ((int)RHGET(w))&MASK16))
return; /* External reg write succeeded */
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh20_datao: %o.%o RAE %o",
rh->rh_no, rh->rh_ds, rh->rh_rs);
if (!rh->rh_dsptr)
fprintf(RHDBF(rh), " (no drive)]");
else
fprintf(RHDBF(rh), "]");
}
/* External reg write failed, set RAE and maybe do PI. */
rh->rh_cond |= RH20CI_RAE; /* Set RAE */
if (!(rh->rh_prep & RH20DO_DRAES)) {
rh_pi(rh); /* Trigger PI */
}
}
return; /* Unspecified RH20 reg (040-067), just do nothing */
/* Internal RH20 registers! */
/* Big hairy case -- this is what ultimately starts a data xfer!! */
case RH20_STCR: /* R/W Secondary Transfer Control Reg */
RH20REG(rh, RH20_STCR) = W10_U32(w); /* First load STCR */
if (rh->rh_cond & RH20CI_PCRF) { /* Primary regs loaded? */
rh->rh_cond |= RH20CI_SCRF; /* Yep, say STCR loaded */
return;
}
#if 1 /* Avoid T20 ILLGO BUGHLTs for stacked xfers! */
/* Don't start xfer if CMD bit still set -- means T20 hasn't yet
** responded to the xfer-done interrupt!
*/
if (rh->rh_cond & RH20CI_CMD) {
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh20_datao postponed 2nd xfer!]");
}
rh->rh_cond |= RH20CI_SCRF; /* Yep, say STCR loaded */
return;
}
#endif
rh_xfrbeg(rh); /* Nothing in PTCR, start xfer! */
return;
case RH20_SBAR: /* R/W Secondary Block Address Reg */
rh->rh_sbarf = TRUE; /* Say SBAR loaded */
break; /* Then go load it */
/* Simple can't-write cases */
case RH20_PBAR: /* RO Primary Block Address Reg */
case RH20_PTCR: /* RO Primary Transfer Control Reg */
case RH20_RR: /* RO Read Reg (Diagnostic only) */
return; /* Can't write, so no-op these */
/* Simple write-register cases */
case RH20_IVIR: /* R/W Interrupt Vector Index Reg */
case RH20_WR: /* WO Write Reg (Diagnostic only) */
case RH20_DCR: /* WO Diagnostic Control Reg (Diags only) */
break; /* Just do simple write */
}
/* Do a simple internal register write */
RH20REG(rh, rh->rh_rs) = W10_U32(w); /* Simple write */
}
/* DATAI word in
** Args D
** Returns data word
*/
static insdef_datai(rh20_datai)
{
register struct rh20 *rh = (struct rh20 *)d;
register w10_t w;
/* For the RH20 the register & unit selected is specified by the
** preparation register, set by a previous DATAO.
*/
if (rh->rh_rs >= RH20_1ST) {
/* Internal register, simple and always succeeds */
LRHSET(w, (RH20REG(rh, rh->rh_rs)>>H10BITS)&H10MASK,
(RH20REG(rh, rh->rh_rs)&H10MASK));
} else if (rh->rh_rs <= 037) {
/* External register.
** Note RHR_ATTN needs to be handled specially, as it must collect
** attention status of all drives!
*/
register uint32 erdata;
if (rh->rh_rs == RHR_ATTN) {
LRHSET(w, RH20DI_TRA, rh->rh_attn); /* Special summary */
} else if (rh->rh_dsptr && (MASK16 >= (erdata =
(*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, rh->rh_rs)))) {
LRHSET(w, RH20DI_TRA, erdata);
} else {
/* Non-existent drive or register access error.
** Set the RAE CONI bit and possibly interrupt.
*/
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh20_datai: %o.%o RAE %o: ",
rh->rh_no, rh->rh_ds, rh->rh_rs);
if (!rh->rh_dsptr)
fprintf(RHDBF(rh), "no drive]");
else
fprintf(RHDBF(rh), "%lo]", (long)erdata);
}
LRHSET(w, RH20DI_CBPE, 0);
rh->rh_cond |= RH20CI_RAE; /* Set RAE */
if (!(rh->rh_prep & RH20DO_DRAES)) {
rh_pi(rh); /* Trigger PI */
}
}
} else {
/* Unspecified RH20 register (040-067) just does nothing */
LRHSET(w, 0, 0);
}
/* Always stuff in RS and LR fields from prep reg */
LHSET(w, (LHGET(w) & ~(RH20DO_RS|RH20DO_LR))
| ((RH20DO_RS|RH20DO_LR) & rh->rh_prep));
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_datai: %o.%o RS%o => %lo,,%lo]\r\n",
rh->rh_no, rh->rh_ds, rh->rh_rs,
(long)LHGET(w), (long)RHGET(w));
return w;
}
/* RH20 internal routines (internal registers etc) */
/* RH_CLEAR - Clear controller
** Also does equivalent of asserting Massbus INIT, by invoking
** the reset routine for all drives.
*/
static void
rh_clear(register struct rh20 *rh)
{
register int i;
if (rh->rh_dv.dv_pireq) /* If PI request outstanding, */
(*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* clear it. */
/* Stop and flush any in-progress xfer? */
/* Need to figure out just which bits get cleared. */
#if 1 /* For now, all of them */
rh->rh_cond = 0; /* Clear all CONI bits */
rh->rh_prep = 0; /* Clear prep reg */
rh->rh_rs = rh->rh_ds = 0; /* Default selections 0 */
rh->rh_dsptr = NULL; /* No drive */
memset((char *)&(rh->rh_reg[0]), 0, sizeof(rh->rh_reg));
rh->rh_sbarf = 0;
#endif
/* Clear all drives for this controller */
for (i = 0; i < 8; ++i)
if (rh->rh_drive[i])
(*(rh->rh_drive[i]->dv_reset))(rh->rh_drive[i]);
}
/* RH_PICHECK - Check RH20 conditions to see if PI should be attempted.
*/
static void
rh_picheck(register struct rh20 *rh)
{
/* If any possible interrupt bits are set */
if (rh->rh_cond & (RH20CI_RAE | RH20CI_MBA | RH20CI_CMD)) {
if ((rh->rh_cond & RH20CI_CMD) /* CMD DONE always interrupts */
|| ((rh->rh_cond & RH20CI_MBA) && (rh->rh_cond & RH20CI_MBAE))
|| ((rh->rh_cond & RH20CI_RAE) && (!(rh->rh_prep & RH20DO_DRAES)))) {
rh_pi(rh);
return;
}
}
/* Here, shouldn't be requesting PI, so if our request bit is set,
** turn it off.
*/
if (rh->rh_dv.dv_pireq) { /* If set while shouldn't be, */
(*rh->rh_dv.dv_pifun)(&rh->rh_dv, 0); /* Clear it! */
}
}
/* RH20_PI - trigger PI for selected RH20.
** This could perhaps be an inline macro, but for now
** having it as a function helps debug.
*/
static void
rh_pi(register struct rh20 *rh)
{
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_pi: %o]", rh->rh_pilev);
if (rh->rh_pilev /* If have non-zero PIA */
&& !(rh->rh_dv.dv_pireq)) { /* and not already asking for PI */
(*rh->rh_dv.dv_pifun)(&rh->rh_dv, rh->rh_pilev); /* then do it! */
}
}
/* RH_CLRATTN - Clear ATTN bit for all masked drives.
** Forces call even if it seems unnecessary, just to be safe.
*/
static void
rh_clrattn(register struct rh20 *rh, int msk)
{
register int i;
register struct device *drv;
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh_clrattn: %o]", msk);
msk &= 0377; /* Allow only 8 bits for 8 drives */
for (i = 0; msk; ++i, msk >>= 1) {
if ((msk & 01) && (drv = rh->rh_drive[i]))
(*drv->dv_wrreg)(drv, RHR_ATTN, 1); /* Tell drv to clr its ATTN */
}
}
/*
NOTE:
This code does not fully emulate the RH20's secondary transfer
registers, for the following reasons:
- Only TOPS-20 uses this feature (which it calls a "stacked" transfer),
and only for disk. TOPS-10 doesn't use it.
- The T20 code has a timing problem as follows:
T20 is doing a so-called "stacked" transfer using the ability of the
RH20 to store a backup xfer command and start it when the primary xfer
is done. But then T20 sometimes gets confused -- when it checks the
logout area to see what the last CCW pointed to, it finds a page number
that, while correct (is one past the last phys xfer finished), is NOT what
T20 expects; T20 compares it against the result expected for the FIRST
transfer, not the second one, and promptly dies with an ILLGO BUGHLT.
Somehow, the 2nd transfer is not only being initiated but also
completed before T20 expects it to be.
The odd thing about all this is that this doesn't happen for the
synchronous case, where transfers complete immediately; you can't get
much more extreme than an "instantaneous" disk! Somehow, the fact
that the "done" interrupt is delayed is making things different. It's
not even a case of having the wrong "xfer in progress" bits set,
because T20 isn't even doing a CONI in between the two transfer loads
(verified by debug output).
The theory so far is something like this:
[A1] RH PI suspended.
[A2] T20 initiates 1st xfer.
[A3] RH PI allowed.
...
[B1] RH PI suspended.
[B2] T20 initiates 2nd (stacked) xfer.
[B3] RH PI allowed.
If T20 expects to receive a distinct PI int for each xfer, then
this sequence will lose if:
- Xfer A completes after [B1]
- Xfer B completes before [B3]
Which is what appears to be happening when the asynch disk
hits ILLGO. That is, xfer A completes in the middle of B2, and
xfer B is started as soon as the appropriate B2 DATAO is given,
but although the RH20 makes a PI request, no interrupt is taken
until after xfer B is done as well!
If this is correct, this would explain why synch disk wins, cuz the
pending PI would happen in the window between A and B, and T20 is
happy. This would also explain why fast asynch loses, cuz xfer B
happens much faster than T20 expects.
The solution currently adopted here is for the RH20 to defer the 2nd
xfer until the OS explicitly acknowledges the first xfer by turning
off the DONE bit in the RH20 CONI word.
This is not what a real RH20 does, but a real RH20 is guaranteed of some
non-trivial length of time for a transfer operation to complete. An
alternative solution, which only makes sense if the following conditions
hold:
1) T20
2) disk performance bottleneck
3) using raw partition
4) frequent stacked xfers
THEN:
Could try a sort of read-ahead or write-ahead hack where the
RP subproc carried out both xfers, so that the data actually gets
transferred at top speed, but the RH side hides that fact from the 10
and the 2nd transfer appears not to be done until CMD-DONE is turned
off for the 1st transfer; then for the 2nd xfer the RH doesn't actually
do I/O but instead just rigs the channel logout area and says 2nd
transfer is done.
But for now, the wait-for-CMD-clear solution should work.
*/
/* RH_XFRBEG - Called to start a data transfer.
** Assumes no xfer in progress, and STCR has stuff;
** takes whatever is there, plus (optionally)
** whatever is in SBAR, and starts the xfer going.
*/
static void
rh_xfrbeg(register struct rh20 *rh)
{
register struct device *drv;
register uint32 bar, tcr;
register int barf;
rh->rh_cond &= ~RH20CI_SCRF; /* Clear SCR Full flag */
rh->rh_cond |= RH20CI_PCRF; /* Set PCR Full flag */
barf = rh->rh_sbarf;
bar = RH20REG(rh, RH20_PBAR) = RH20REG(rh, RH20_SBAR);
tcr = RH20REG(rh, RH20_PTCR) = RH20REG(rh, RH20_STCR);
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh_xfrbeg: TCR %lo", (long)tcr);
if (rh->rh_sbarf) fprintf(RHDBF(rh), " BAR %lo", (long)bar);
fprintf(RHDBF(rh), "]\r\n");
}
/* What to do about old values of secondary xfer regs?
** I doubt the real hardware clears them, but to help debugging,
** this seems reasonable.
*/
RH20REG(rh, RH20_SBAR) = 0;
RH20REG(rh, RH20_STCR) = 0;
rh->rh_sbarf = 0; /* This *must* be cleared */
if (tcr & (RH20DO_RCLP<<H10BITS)) /* Check flag in LH */
rhdc_clear(rh); /* Clear and reset data channel PC */
/* Get selected device */
if (!(drv = rh->rh_drive[(tcr >> H10BITS) & RH20DO_DS])) {
/* Ugh, non-existent drive! Set CONI DR bit and trigger PI. */
rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Response Error */
rh_pi(rh); /* Trigger PI */
return;
}
/* If SBAR was set, then give that to drive first.
** It appears that the drive select field in the BAR reg is ignored;
** the actual drive is whatever the TCR specifies!
*/
if (barf) {
#if 0 /* This was going off constantly */
/* Do debug check - BAR and TCR better have same drive select! */
if ((bar & RH20DO_DS) != (tcr & RH20DO_DS)) {
if (rh->rh_dv.dv_dbf)
fprintf(rh->rh_dv.dv_dbf,
"[rh_xfrbeg: drive select mismatch, bar=%lo, tcr=%lo]\r\n",
(long)bar, (long)tcr);
}
#endif
if (!(*drv->dv_wrreg)(drv, RHR_BAFC, (int)(bar & MASK16))) {
/* Ugh, drive complained about setting BAR??? */
panic("rh_xfrbeg: BAR set failure");
rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Resp Err */
rh_pi(rh); /* Trigger PI */
return;
}
}
/* Set up positive block count. Note field is interpreted as negative, and
** in the real RH20 the count is done when adding 1 causes overflow.
** Thus if the field is all 0s it is interpreted as -2000, not as 0.
*/
rh->rh_bcnt = - (((tcr>>6)&(RH20DO_NBC>>6)) | ~(RH20DO_NBC>>6));
/* OK, ready to start xfer... */
if (!(*drv->dv_wrreg)(drv, RHR_CSR, (int)(tcr & MASK16))) {
/* Ugh, drive complained about setting CSR??? */
panic("rh_xfrbeg: CSR set failure");
rh->rh_cond |= RH20CI_DR|RH20CI_CMD; /* Drive Resp Err */
rh_pi(rh); /* Trigger PI */
return;
}
/* That's all, drive's code does the rest! */
}
/* The following rh20_ functions are all called by the drive via
** the controller/drive vectors.
*/
/* RH20_ATTN - Called by drive to assert or clear its attention bit.
** Note arg is pointer to rh20drv struct.
*/
static void
rh20_attn(register struct device *drv, int on)
{
register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr);
register int rbit = (1 << drv->dv_num); /* Attention bit to use */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_attn: bit %o %s]",
rbit, on ? "on" : "off");
/* If turning ATN bit on, always interrupt if enabled, even if ATN
was already on. This is because it could have been previously
turned on while RH20CI_MBAE was off, and thus is not a reliable sign
of whether an interrupt is already pending.
*/
if (on) {
rh->rh_attn |= rbit; /* Add new attention bit! */
rh->rh_cond |= RH20CI_MBA; /* Assert Massbus Attention */
if (rh->rh_cond & RH20CI_MBAE) /* If OK for attn to int */
rh_pi(rh); /* then try to trigger PI! */
} else {
if (rh->rh_attn & rbit) {
rh->rh_attn &= ~rbit; /* Turn off attention bit */
if (!rh->rh_attn /* If no longer have any ATTNs */
&& (rh->rh_cond & RH20CI_MBA)) { /* ensure MB attn is off */
/* Changing MBA state... may have to clean up PI */
rh->rh_cond &= ~RH20CI_MBA; /* ensure MB attn is off */
if (rh->rh_dv.dv_pireq) /* If had PI request, */
rh_picheck(rh); /* Sigh, must re-check stuff */
}
}
}
}
/* RH20_DRERR - Called by drive to assert an exception error during an
** I/O transfer.
*/
static void
rh20_drerr(register struct device *drv)
{
register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_drerr]");
if (!(rh->rh_cond & RH20CI_DEE)) {
rh->rh_cond |= RH20CI_DEE; /* Assert Drive Exception Error */
rh_pi(rh); /* Then try to trigger PI */
}
/* If I/O transfer in progress, abort it gracefully */
if (1)
rh20_ioend(drv, 1); /* Assume always have a block left */
}
/* RH20_IOBEG - Called by drive to set up data channel just prior to
** starting an I/O xfer.
** Returns 0 if failure; drive should stop immediately and not
** invoke rhdv_iobuf, but must still call rhdv_ioend.
** Else returns # of block units (sectors or records) to do I/O for.
*/
static int
rh20_iobeg(struct device *drv, int wflg)
{
register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr);
rh->rh_dcsts = CSW_CLRSET; /* Clear channel status */
rh->rh_dcwrt = wflg; /* Remember if writing */
if (!rhdc_ccwget(rh)) {
rh->rh_cond |= RH20CI_CE /* Say Channel Error */
| RH20CI_CMD; /* and Command Done */
rh_pi(rh); /* Try to trigger PI */
return 0; /* Tell drive we failed */
}
rh->rh_cond &= ~RH20CI_CNR; /* Channel active now! */
return rh->rh_bcnt;
}
/* RH20_IOBUF - Called by drive to query data channel to find source
** or dest buffer for I/O xfer. Can be called repeatedly for one xfer.
** Caller must provide # of words used from the last call (0 if none).
** In particular, final transfer MUST invoke this to tell the channel
** how many words were actually used.
**
** If returns 0, no data or space left.
** If returns +, OK, *avp NULL if skipping, else vmptr to mem.
** If returns -, wants to do reverse transfer.
** Not clear whether buffer pointer points to LAST word of
** block to write, or ONE PAST the last word. Assuming
** the former, based on DEC DC doc (p. DATA/2-7) desc of address.
*/
static int
rh20_iobuf(struct device *drv,
register int wc, /* Max 11 bits of word count, so int OK */
vmptr_t *avp)
{
register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_iobuf: %d. => ", wc);
if (!rh->rh_dcwcnt) { /* Nothing left */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "0]\r\n");
return 0;
}
if (wc) {
/* If drive is updating our IO xfer status, do it. */
/* Update buffer pointer, assuming wc has correct direction sign */
if (rh->rh_dcbuf)
rh->rh_dcbuf += wc;
if (wc < 0) { /* Verify direction consistent */
if (!rh->rh_dcrev)
panic("rh20_iobuf: Neg wc for fwd xfer!");
wc = -wc; /* Make positive */
} else if (rh->rh_dcrev)
panic("rh20_iobuf: Pos wc for rev xfer!");
if ((rh->rh_dcwcnt -= wc) < 0) /* Update remaining wd cnt */
panic("rh20_iobuf: drive overran chan!");
/* See if word count ran out */
if (rh->rh_dcwcnt == 0) {
/* Yep, was this the last cmd (halt or last xfer?)
** If not, try to get another data xfer CCW.
*/
if (rh->rh_dchlt || !rhdc_ccwget(rh)) {
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "0]\r\n");
return 0; /* Done, or no more */
}
}
}
/* Here, we still have a positive count, so return that. */
wc = rh->rh_dcwcnt;
/* Also return addr of buffer. But there are a few special cases
** to check for, sigh...
*/
if (rh->rh_dcbuf)
*avp = vm_physmap(rh->rh_dcbuf);
else { /* Ugh! Special fill or skip hack. */
if (rh->rh_dcwrt) { /* If writing, use fill wds from EPT */
*avp = vm_physmap(cpu.mr_ebraddr + EPT_CB0);
if (wc > 4)
wc = 4;
} else {
*avp = NULL; /* Tell drive to skip input */
}
}
if (rh->rh_dcrev) /* If reverse xfer, negate count */
wc = -wc;
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "%d. (%lo)]\r\n", wc, (long)(rh->rh_dcbuf));
return wc;
}
/* RH20_IOEND - Called by drive when I/O finished, terminate data channel
** I/O xfer. Assumes that rh20_iobuf has been called to update
** status after each transfer or sub-transfer, so channel word count
** is accurate reflection of data xfer.
** State may be one of:
** Error - either in drive, or from data channel. Assumption is
** that error has set all appropriate error bits (specifically
** Channel Error if channel status must be stored), and
** triggered PI as well.
**
** Drive stopped early due to no more DC buffer space. Indicated by
** non-zero returned block count.
** Drive stopped normally, block count 0.
**
** Checks to see whether to set Short/Long WC Error.
** Stores channel status if requested by PTCR bit RH20DO_SES.
**
** May initiate next RH20 data transfer.
*/
static void
rh20_ioend(register struct device *drv,
int bc) /* Drive's final block count */
{
register struct rh20 *rh = (struct rh20 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh20_ioend: %d.]\r\n", bc);
/* Update final IO xfer status */
if (bc || rh->rh_dcwcnt) {
/* Drive still has stuff it wanted to do.
** Set channel Short WC if WC==0, or Long WC if WC != 0,
** and RH20 Chan Err.
*/
rh->rh_dcsts |= (rh->rh_dcwcnt ? CSW_LWCE : CSW_SWCE);
rh->rh_cond |= RH20CI_CE;
}
/* See whether to store channel status */
if ((RH20REG(rh, RH20_PTCR) /* If wanted state stored */
& (RH20DO_SES<<H10BITS))
|| (rh->rh_cond & RH20CI_CE)) { /* or any kind of chan error */
register vmptr_t vp;
register w10_t w;
vp = vm_physmap(cpu.mr_ebraddr + rh->rh_eptoff);
/* Store status bits and current cmd list ptr */
if (rh->rh_dcwcnt)
rh->rh_dcsts |= CSW_NOWC0;
LRHSET(w, rh->rh_dcsts | ((rh->rh_clp >> H10BITS)&CSW_HIADR),
rh->rh_clp & H10MASK);
vm_pset(vp+RH20_EPT_CST(0), w);
/* Reconstruct current CCW and store it */
LRHSET(w, (LHGET(rh->rh_dcccw)&CCW_OP)
| (rh->rh_dcwcnt<<4) | ((rh->rh_dcbuf>>H10BITS)&CCW_ADR),
rh->rh_dcbuf & H10MASK);
vm_pset(vp+RH20_EPT_CCW(0), w);
}
/* Now say command done, whatever happened. */
rh->rh_cond &= ~RH20CI_PCRF; /* Primary regs now free */
rh->rh_cond |= RH20CI_CMD | RH20CI_CNR; /* Done, channel ready */
rh_pi(rh); /* Attempt triggering PI */
/* Now if no errors, check for secondary TCR and initiate it? */
/* NO -- see explanation at start of rh_xfrbeg!! */
}
/* RH20 Data Channel routines.
**
** init/reset - called by RH20
** ccwget - called by RH20 when drive wants to start xfer or needs more
** data/buffer space from channel.
*/
static void
rhdc_init(register struct rh20 *rh,
int mbc) /* Massbus controller # (0-7) */
{
rh->rh_eptoff = mbc * 4; /* Offset of EPT area */
rhdc_clear(rh);
}
static void
rhdc_clear(register struct rh20 *rh)
{
/* Reset channel PC to point at 1st wd of channel area in EPT */
rh->rh_clp = rh->rh_eptoff + cpu.mr_ebraddr;
rh->rh_dcsts = CSW_CLRSET; /* Clear status */
LRHSET(rh->rh_dcccw, 0, 0);
rh->rh_dcwcnt = 0;
rh->rh_dcbuf = 0;
rh->rh_dchlt = TRUE;
}
static int
rhdc_ccwget(register struct rh20 *rh)
{
register w10_t w;
register paddr_t pa;
register int wc;
/* Grovel down cmd list until hit valid xfer cmd, use that to set up */
for (;;) {
/* Fetch current cmd word */
w = vm_pget(vm_physmap(rh->rh_clp)); /* Get cmd word */
pa = W10_U32(w) & MASK22;
wc = (LHGET(w) & CCW_CNT) >> (22-18);
switch (LHGET(w) & CCW_OP) {
case CCW_HLT:
/* Use addr as CLP for next call, and do stuff to halt.
** Should this cause a "Short Word Count" channel error, or
** something else since transfer was never started?
** For now, pretend zero word count, fail as if short WC.
*/
rh->rh_clp = pa; /* Set new "PC" */
rh->rh_dcbuf = pa; /* Also set here in case status stored */
rh->rh_dcccw = w; /* Remember current cmd */
rh->rh_dcwcnt = 0;
rh->rh_dchlt = TRUE;
rh->rh_dcrev = 0;
return 0;
case CCW_JMP:
rh->rh_clp = pa; /* Set up CLP and loop to effect jump */
continue;
case CCW_XFR: /* Forward transfer, no halt */
rh->rh_dchlt = 0;
rh->rh_dcrev = 0;
break;
case CCW_XFR|CCW_XHLT: /* Forward transfer, halt */
rh->rh_dchlt = TRUE;
rh->rh_dcrev = 0;
break;
case CCW_XFR|CCW_XREV|CCW_XHLT:
rh->rh_dchlt = 0;
rh->rh_dcrev = TRUE;
break;
case CCW_XFR|CCW_XREV:
rh->rh_dchlt = TRUE;
rh->rh_dcrev = TRUE;
break;
default:
/* Perhaps assert Channel Error (RH20CI_CE) here? */
panic("rhdc_ccwget: Bad CCW op %lo", (long)(LHGET(w) & CCW_OP));
return 0;
}
rh->rh_dcccw = w; /* Remember current cmd */
rh->rh_dcwcnt = wc; /* Set up word count */
rh->rh_dcbuf = pa; /* Set up address */
rh->rh_clp = (rh->rh_clp + 1) & MASK22; /* Bump "PC" */
return 1;
}
}
#endif /* KLH10_DEV_RH20 */