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/dvrh11.c
Olaf Seibert 9312579251 clang-suggested changes, mostly wrt -Wparentheses
warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
2016-01-11 00:24:46 +01:00

1032 lines
31 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.
/* DVRH11.C - Emulates RH11 controller for KS10
*/
/* $Id: dvrh11.c,v 2.4 2002/03/14 06:26:22 klh Exp $
*/
/* Copyright © 1997, 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: dvrh11.c,v $
* Revision 2.4 2002/03/14 06:26:22 klh
* Fixed rh11_bind to handle case of binding drive that's already been
* selected by CS2, even if by default.
*
* Revision 2.3 2001/11/10 21:28:59 klh
* Final 2.0 distribution checkin
*
*/
#include "klh10.h"
#if !KLH10_DEV_RH11 && 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_RH11 /* Moby conditional for entire file */
#include <stddef.h> /* For size_t etc */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "kn10def.h" /* This includes OSD defs */
#include "kn10dev.h"
#include "dvuba.h"
#include "dvrh11.h"
#include "dvrh20.h" /* Temp: for RHR ext reg defs */
#include "dvrpxx.h" /* Temp: for RH_DTNBA def */
#include "prmstr.h" /* For parameter parsing */
#ifdef RCSID
RCSID(dvrh11_c,"$Id: dvrh11.c,v 2.4 2002/03/14 06:26:22 klh Exp $")
#endif
struct rh11 {
struct device rh_dv; /* Generic 10 device structure */
/* RH11-specific vars */
int rh_no; /* RH number - use UBA # here */
/* RH11 internal registers */
uint18 rh_cs1; /* Only RH11 bits are set here, no drive bits */
uint18 rh_cs2;
uint18 rh_wc;
uint18 rh_ba;
/* Drive bindings & selection */
int rh_ds; /* Drive select (0-7) */
struct device *rh_dsptr;
int rh_attn; /* ATTN summary for all drives */
struct device *rh_drive[8];
uint18 rh_dt[8]; /* Drive type for each known drive */
/* Pseudo "Data Channel" vars */
int rh_dcwcnt; /* DC current xfer word count */
paddr_t rh_dcbuf; /* DC current xfer data buffer pointer */
int rh_bcnt; /* Initial block count */
int rh_dcrev; /* TRUE if doing reverse data xfer */
int rh_dcwrt; /* TRUE if doing device write */
w10_t rh_dcccw; /* DC current cmd word */
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) */
int rh_dchlt; /* TRUE if halt when current wordcount done */
};
static int nrh11s = 0;
/* static */ struct rh11 dvrh11[RH11_NSUP];
#define RHDEBUG(rh) ((rh)->rh_dv.dv_debug)
#define RHDBF(rh) ((rh)->rh_dv.dv_dbf)
/* Function predecls */
static int rh11_conf(FILE *f, char *s, struct rh11 *rh);
/* Functions provided to device vector */
static int rh11_bind(struct device *d, FILE *of, struct device *slv, int num);
static int rh11_init(struct device *d, FILE *of);
static void rh11_reset(struct device *d);
static dvureg_t rh11_pivec(struct device *d);
static dvureg_t rh11_read(struct device *d, uint18 addr);
static void rh11_write(struct device *d, uint18 addr, dvureg_t val);
/* Functions provided to slave device */
static void rh11_attn(struct device *drv, int on);
static void rh11_drerr(struct device *drv);
static int rh11_iobeg(struct device *drv, int wflg);
static int rh11_iobuf(struct device *drv, int wc, vmptr_t *avp);
static void rh11_ioend(struct device *drv, int bc);
/* Completely internal functions */
static void rh_picheck(struct rh11 *rh);
static void rh_pi(struct rh11 *rh);
static void rh_clrattn(struct rh11 *rh, int msk);
static void rh_clear(struct rh11 *rh);
static void rhdc_clear(struct rh11 *rh);
static int rhdc_ccwget(struct rh11 *rh);
/* Configuration Parameters */
#define DVRH11_PARAMS \
prmdef(RH11P_DBG, "debug"), /* Initial debug value */\
prmdef(RH11P_BR, "br"), /* BR priority */\
prmdef(RH11P_VEC, "vec"), /* Interrupt vector */\
prmdef(RH11P_ADDR,"addr") /* Unibus address */
enum {
# define prmdef(i,s) i
DVRH11_PARAMS
# undef prmdef
};
static char *rh11prmtab[] = {
# define prmdef(i,s) s
DVRH11_PARAMS
# undef prmdef
, NULL
};
/* RH11_CONF - Parse configuration string and set defaults.
** At this point, device has just been created, but not yet bound
** or initialized.
** NOTE that some strings are dynamically allocated! Someday may want
** to clean them up nicely if config fails or device is uncreated.
*/
static int
rh11_conf(FILE *f, char *s, struct rh11 *rh)
{
int i, ret = TRUE;
struct prmstate_s prm;
char buff[200];
long lval;
/* First set defaults for all configurable parameters
Unfortunately there's currently no way to access the UBA # that
we're gonna be bound to, otherwise could set up defaults. Later
fix this by giving rh11_create() a ptr to an arg structure, etc.
*/
DVDEBUG(rh) = FALSE;
prm_init(&prm, buff, sizeof(buff),
s, strlen(s),
rh11prmtab, sizeof(rh11prmtab[0]));
while ((i = prm_next(&prm)) != PRMK_DONE) {
switch (i) {
case PRMK_NONE:
fprintf(f, "Unknown RH11 parameter \"%s\"\n", prm.prm_name);
ret = FALSE;
continue;
case PRMK_AMBI:
fprintf(f, "Ambiguous RH11 parameter \"%s\"\n", prm.prm_name);
ret = FALSE;
continue;
default: /* Handle matches not supported */
fprintf(f, "Unsupported RH11 parameter \"%s\"\n", prm.prm_name);
ret = FALSE;
continue;
case RH11P_DBG: /* Parse as true/false boolean or number */
if (!prm.prm_val) /* No arg => default to 1 */
DVDEBUG(rh) = 1;
else if (!s_tobool(prm.prm_val, &DVDEBUG(rh)))
break;
continue;
case RH11P_BR: /* Parse as octal number */
if (!prm.prm_val || !s_tonum(prm.prm_val, &lval))
break;
if (lval < 4 || lval > 7) {
fprintf(f, "RH11 BR must be one of 4,5,6,7\n");
ret = FALSE;
} else
rh->rh_dv.dv_brlev = lval;
continue;
case RH11P_VEC: /* Parse as octal number */
if (!prm.prm_val || !s_tonum(prm.prm_val, &lval))
break;
if (lval < 4 || lval > 0400 || (lval&03)) {
fprintf(f, "RH11 VEC must be valid multiple of 4\n");
ret = FALSE;
} else
rh->rh_dv.dv_brvec = lval;
continue;
case RH11P_ADDR: /* Parse as octal number */
if (!prm.prm_val || !s_tonum(prm.prm_val, &lval))
break;
if (lval < (RH11R_N<<1) || (lval&037)) {
fprintf(f, "RH11 ADDR must be valid Unibus address\n");
ret = FALSE;
} else
rh->rh_dv.dv_addr = lval;
continue;
}
ret = FALSE;
fprintf(f, "RH11 param \"%s\": ", prm.prm_name);
if (prm.prm_val)
fprintf(f, "bad value syntax: \"%s\"\n", prm.prm_val);
else
fprintf(f, "missing value\n");
}
/* Param string all done, do followup checks or cleanup */
if (!rh->rh_dv.dv_brlev || !rh->rh_dv.dv_brvec || !rh->rh_dv.dv_addr) {
fprintf(f, "RH11 missing one of BR, VEC, ADDR params\n");
ret = FALSE;
}
/* Set 1st invalid addr */
rh->rh_dv.dv_aend = rh->rh_dv.dv_addr + (RH11R_N * 2);
return ret;
}
/* RH11 interface routines to KLH10 */
struct device *
dvrh11_create(FILE *f, char *s)
{
register struct rh11 *rh;
/* Allocate a RH11 device structure */
if (nrh11s >= RH11_NSUP) {
fprintf(f, "Too many RH11s, max: %d\n", RH11_NSUP);
return NULL;
}
rh = &dvrh11[nrh11s++]; /* Pick unused RH11 */
/* Various initialization stuff */
memset((char *)rh, 0, sizeof(*rh));
iodv_setnull(&rh->rh_dv); /* Init as null device */
rh->rh_dv.dv_dflags = DVFL_CTLR;
rh->rh_dv.dv_bind = rh11_bind; /* Controller, so can bind. */
rh->rh_dv.dv_init = rh11_init; /* Set up own post-bind init */
rh->rh_dv.dv_reset = rh11_reset; /* System reset (clear stuff) */
/* Unibus stuff */
rh->rh_dv.dv_pivec = rh11_pivec; /* Return PI vector */
rh->rh_dv.dv_read = rh11_read; /* Read unibus register */
rh->rh_dv.dv_write = rh11_write; /* Write unibus register */
/* Configure from parsed string and remember for init
*/
if (!rh11_conf(f, s, rh))
return NULL;
return &rh->rh_dv;
}
static int
rh11_init(struct device *d, FILE *of)
{
register struct rh11 *rh = (struct rh11 *)d;
/* Clear RH-internal registers */
rh->rh_cs1 = 0;
rh->rh_cs2 = 0;
rh->rh_wc = 0;
rh->rh_ba = 0;
rh->rh_attn = 0; /* Clear ATTN summary */
rh->rh_dsptr = NULL; /* No drive */
rh->rh_no = rh->rh_dv.dv_uba->ubn; /* Find Unibus # we're on */
return TRUE;
}
static int
rh11_bind(register struct device *d,
FILE *of,
register struct device *slv,
int num)
{
register struct rh11 *rh = (struct rh11 *)d;
if (0 <= num && num < 8) ;
else {
if (of) fprintf(of, "RH11 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 = rh11_attn; /* Set attention handler */
slv->dv_drerr = rh11_drerr; /* Set exception handler */
slv->dv_iobeg = rh11_iobeg; /* IO xfer beg */
slv->dv_iobuf = rh11_iobuf; /* IO xfer buffer setup */
slv->dv_ioend = rh11_ioend; /* IO xfer end */
rh->rh_drive[num] = slv;
rh->rh_dt[num] = 0; /* Drive not yet inited, so can't ask it */
/* Check to see if this drive is already being selected by CS2.
* If so, must set it up now, or will get spurious RAE's if PDP-10
* tries to read regs before it sets CS2 again.
*/
if (rh->rh_ds == num) {
rh->rh_dsptr = slv; /* Yes, set it up! */
rh->rh_cs2 &= ~RH_YNED; /* Turn off non-ex bit */
}
return TRUE;
}
/* RH11_RESET - Clear controller and drives
** Also does equivalent of asserting Massbus INIT, by invoking
** the reset routine for all drives.
*/
static void
rh11_reset(struct device *d)
{
register struct rh11 *rh = (struct rh11 *)d;
register int i;
rh_clear(rh); /* Clear RH-only controller stuff */
/* 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]);
}
/* PI: Return interrupt vector */
static dvureg_t
rh11_pivec(register struct device *d)
{
(*d->dv_pifun)(d, 0); /* Turn off interrupt request */
return d->dv_brvec; /* Return vector to use */
}
/* Table for mapping RH11 registers into RH20-drive registers.
** Note certain RH regs are mapped into special internal-reg numbers.
** Also note that one RH11 reg address (BUF) has no counterpart and is mapped
** into a no-op read-only reg.
*/
#define RHRX_WC RHR_N+1
#define RHRX_BA RHR_N+2
#define RHRX_CS2 RHR_N+3
static int rh11regmap[RH11R_N] = {
RHR_CSR, /* 0 RH11R_CS1 - (RH/DR) CTRL AND STATUS 1. */
RHRX_WC, /* 1 RH11R_WC - (RH) WORD COUNT. */
RHRX_BA, /* 2 RH11R_BA - (RH) UNIBUS ADDRESS. */
RHR_BAFC, /* 3 RH11R_ADR - (DR) DESIRED ADDRESS. */
RHRX_CS2, /* 4 RH11R_CS2 - (RH) CTRL AND STATUS 2. */
RHR_STS, /* 5 RH11R_STS - (DR) DRIVE STATUS. */
RHR_ER1, /* 6 RH11R_ER1 - (DR) ERROR 1. */
RHR_ATTN, /* 7 RH11R_ATN - (DR*) ATTENTION SUMMARY. */
RHR_LAH, /* 010 RH11R_LAH - (DR) LOOK AHEAD. */
RHR_SN, /* 011 RH11R_BUF - (DR) DATA BUFFER. */
RHR_MNT, /* 012 RH11R_MNT - (DR) MAINTENANCE. */
RHR_DT, /* 013 RH11R_TYP - (DR) DRIVE TYPE. */
RHR_SN, /* 014 RH11R_SER - (DR) SERIAL NUMBER. */
RHR_OFTC, /* 015 RH11R_OFS - (DR) OFFSET. */
RHR_DCY, /* 016 RH11R_CYL - (DR) DESIRED CYLINDER. */
RHR_CCY, /* 017 RH11R_CCY - (DR) CURRENT CYLINDER */
RHR_ER2, /* 020 RH11R_ER2 - (DR) ERROR 2 */
RHR_ER3, /* 021 RH11R_ER3 - (DR) ERROR 3 */
RHR_EPOS, /* 022 RH11R_POS - (DR) ECC POSITION. */
RHR_EPAT /* 023 RH11R_PAT - (DR) ECC PATTERN. */
};
static char *rh11regnam[RH11R_N] = {
"CS1", /* 0 RH11R_CS1 - (RH/DR) CTRL AND STATUS 1. */
"WC", /* 1 RH11R_WC - (RH) WORD COUNT. */
"BA", /* 2 RH11R_BA - (RH) UNIBUS ADDRESS. */
"ADR", /* 3 RH11R_ADR - (DR) DESIRED ADDRESS. */
"CS2", /* 4 RH11R_CS2 - (RH) CTRL AND STATUS 2. */
"STS", /* 5 RH11R_STS - (DR) DRIVE STATUS. */
"ER1", /* 6 RH11R_ER1 - (DR) ERROR 1. */
"ATN", /* 7 RH11R_ATN - (DR*) ATTENTION SUMMARY. */
"LAH", /* 010 RH11R_LAH - (DR) LOOK AHEAD. */
"BUF", /* 011 RH11R_BUF - (DR) DATA BUFFER. */
"MNT", /* 012 RH11R_MNT - (DR) MAINTENANCE. */
"TYP", /* 013 RH11R_TYP - (DR) DRIVE TYPE. */
"SER", /* 014 RH11R_SER - (DR) SERIAL NUMBER. */
"OFS", /* 015 RH11R_OFS - (DR) OFFSET. */
"CYL", /* 016 RH11R_CYL - (DR) DESIRED CYLINDER. */
"CCY", /* 017 RH11R_CCY - (DR) CURRENT CYLINDER */
"ER2", /* 020 RH11R_ER2 - (DR) ERROR 2 */
"ER3", /* 021 RH11R_ER3 - (DR) ERROR 3 */
"POS", /* 022 RH11R_POS - (DR) ECC POSITION. */
"PAT" /* 023 RH11R_PAT - (DR) ECC PATTERN. */
};
static dvureg_t
rh11_read(struct device *d, register uint18 addr)
{
register struct rh11 *rh = (struct rh11 *)d;
register int reg;
register uint32 dregval;
register dvureg_t val;
reg = (addr - rh->rh_dv.dv_addr) >> 1;
if (reg < 0 || reg >= RH11R_N) {
/* In theory ought to generate illegal IO register page-fail here,
but this should never happen - all addresses for this device
are being handled.
*/
panic("rh11_read: Unknown register %o", addr);
}
switch (reg) {
/* Controller register stuff */
case RH11R_CS2: val = rh->rh_cs2; break; /* CTRL AND STATUS 2 */
case RH11R_WC: val = rh->rh_wc; break; /* WORD COUNT */
case RH11R_BA: val = rh->rh_ba; break; /* UNIBUS ADDRESS */
case RH11R_ATN: val = rh->rh_attn; break; /* ATTENTION SUMMARY */
case RH11R_CS1: /* CTRL AND STATUS 1 - special combo of RH and drive */
/* Special handling is required for this one as it combines
both the RH and a selected drive. If no drive is selected,
then this access attempt must cause a Massbus Parity Error!
*/
if (!rh->rh_dsptr) {
rh->rh_cs2 |= RH_YNED; /* Non-existent drive - set on ref */
rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */
val = rh->rh_cs1;
break;
}
if ((MASK16 < (dregval =
(*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, RHR_CSR)))) {
dregval = 0;
rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */
}
val = (rh->rh_cs1 | (dregval & RH_CS1_DRIVE));
break;
default:
/* Get value from drive. */
if (rh->rh_dsptr && (MASK16 >= (dregval =
(*rh->rh_dsptr->dv_rdreg)(rh->rh_dsptr, rh11regmap[reg])))) {
val = dregval;
break;
}
/* Non-existent drive or drive register access error.
** Unclear what should be done for this case; IO page fail?
*/
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh11_read: %o.%o RAE for %s: ",
rh->rh_no, rh->rh_ds, rh11regnam[reg]);
if (!rh->rh_dsptr)
fprintf(RHDBF(rh), "no drive]\r\n");
else
fprintf(RHDBF(rh), "%lo]\r\n", (long)dregval);
}
rh->rh_cs1 |= RH_XMCP; /* Shd we trigger PI? For now, no */
return 0;
}
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_read: %o.%o %s: %lo]\r\n",
rh->rh_no, rh->rh_ds, rh11regnam[reg], (long)val);
return val;
}
/* Note from T20:
THE RH11 REQUIRES THAT A CLEAR INSTRUCTION (setting RH_YCLR in CS2) BE
FOLLOWED IMMEDIATELY BY A SELECTION OF AN EXISTANT UNIT NUMBER
OTHERWISE ANY ATTEMPT TO ACCESS A REGISTER THAT IS NOT IN THE RH (CS1)
WILL CAUSE MASSBUS PARITY ERRORS.
- Apparently CS2 CLEAR cannot simultaneously do a drive select; it causes no
drive to be selected, and another CS2 write without the clear bit
is required.
- If no drive is selected (or an invalid one is picked) apparently RH_YNED
is not set until an actual attempt is made to access (read or write)
a drive register (of which CS1 counts as one!).
- In ER1, T20 only checks RH_1PAR (Control Bus Parity Error) and
RH_1AOE (Address Overflow / Frame Count Error)
*/
static void
rh11_write(struct device *d, uint18 addr, register dvureg_t val)
{
register struct rh11 *rh = (struct rh11 *)d;
register int reg;
reg = (addr - rh->rh_dv.dv_addr) >> 1;
if (reg < 0 || reg >= RH11R_N) {
/* In theory ought to generate illegal IO register page-fail here,
but this should never happen - all addresses for this device
are being handled.
*/
panic("rh11_write: Unknown register %o", addr);
}
val &= MASK16;
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_write: %o.%o %s: %lo]\r\n",
rh->rh_no, rh->rh_ds, rh11regnam[reg], (long)val);
switch (reg) {
case RH11R_CS1: /* R/W CS1 CS1 Control/command */
/* Set RH bits first */
if (val & RH_XTRE) /* If turning off xfer err bit */
rh->rh_cs1 &= ~RH_XTRE; /* do so. */
rh->rh_cs1 &= ~(RH_XA17|RH_XA16|RH_XIE); /* Bits can set */
rh->rh_cs1 |= (val & (RH_XA17|RH_XA16|RH_XIE)); /* Set em */
/* Now set drive bits */
if (!rh->rh_dsptr) {
rh->rh_cs2 |= RH_YNED; /* Set Non-Existent Drive */
} else {
/* Give command to drive! */
if ((*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr,
RHR_CSR, (int)(val & RH_XCMD))) {
return; /* Success, all done here */
}
goto wregerr; /* Ugh, report error */
}
break; /* Go set RH_XMCP Control Bus Parity Error */
case RH11R_CS2: /* CTRL AND STATUS 2 */
{
register int drvno = val & RH_YDSK;
if (val & RH_YCLR)
rh_clear(rh); /* Clear controller (not drive) */
/* Also turns off any PI request! */
rh->rh_cs2 = ((rh->rh_cs2)&~RH_YDSK) | drvno;
rh->rh_ds = drvno;
if ((rh->rh_dsptr = rh->rh_drive[drvno])) {
rh->rh_cs2 &= ~RH_YNED; /* Drive exists */
} else {
rh->rh_cs2 |= RH_YNED; /* Non-ex drive */
}
return;
}
case RH11R_WC: /* WORD COUNT */
rh->rh_wc = val;
return;
case RH11R_BA: /* UNIBUS ADDRESS */
rh->rh_ba = val;
return;
case RH11R_ATN: /* R/W [I2] ATN AS Attention Summary */
rh_clrattn(rh, (int)val); /* Turn off ATTN for all bits set */
return;
default: /* All other registers are given to drive */
if (rh->rh_dsptr &&
(*rh->rh_dsptr->dv_wrreg)(rh->rh_dsptr, rh11regmap[reg], (int)val))
return; /* External reg write succeeded */
/* Error drops through */
wregerr:
if (RHDEBUG(rh)) {
fprintf(RHDBF(rh), "[rh11_write: %o.%o RAE for %s: ",
rh->rh_no, rh->rh_ds, rh11regnam[reg]);
if (!rh->rh_dsptr)
fprintf(RHDBF(rh), " (no drive)]");
else
fprintf(RHDBF(rh), "]");
}
break;
}
/* External reg write failed, set RH_XMCP and maybe do PI. */
rh->rh_cs1 |= RH_XMCP; /* Set Control Bus Parity Error */
if (rh->rh_cs1 & RH_XIE) {
rh_pi(rh); /* Trigger PI */
}
}
/* RH11 internal routines (internal registers etc) */
/* RH_CLEAR - Clear controller only (not drives)
*/
static void
rh_clear(register struct rh11 *rh)
{
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; for now, all RH bits */
rh->rh_cs1 = RH_XRDY; /* CTRL AND STATUS 1 */
rh->rh_cs2 = 0; /* CTRL AND STATUS 2 */
rh->rh_wc = 0;
rh->rh_ba = 0;
rh->rh_dsptr = NULL; /* No drive selected */
rh->rh_ds = 0;
}
/* RH_PICHECK - Check RH11 conditions to see if PI should be attempted.
** Possible problem: RH11 has no "done" flag, so using this routine
** may not be a good idea as it could turn off the PI for command-done.
** RH_XSC is not the equivalent as it is only set for ATTN or error,
** not for transfer-done.
*/
static void
rh_picheck(register struct rh11 *rh)
{
/* If any possible interrupt bits are set */
if (rh->rh_cs1 & (RH_XSC | RH_XTRE | RH_XMCP)) {
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! */
}
}
/* RH11_PI - trigger PI for selected RH11.
** This could perhaps be an inline macro, but for now
** having it as a function helps debug.
*/
static void
rh_pi(register struct rh11 *rh)
{
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_pi: %o]", rh->rh_dv.dv_brlev);
if (rh->rh_dv.dv_brlev /* 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_dv.dv_brlev); /* 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 rh11 *rh, int msk)
{
register int i;
register struct device *drv;
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_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 */
}
}
/* The following rh11_ functions are all called by the drive via
** the controller/drive vectors.
*/
/* RH11_ATTN - Called by drive to assert or clear its attention bit.
** Note arg is pointer to drive, not controller.
*/
static void
rh11_attn(register struct device *drv, int on)
{
register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr);
register int rbit = (1 << drv->dv_num); /* Attention bit to use */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_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 RH_XIE 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_cs1 |= RH_XSC; /* Assert Special Condition */
if (rh->rh_cs1 & RH_XIE) /* 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_cs1 & RH_XSC)) { /* ensure Spec Cond is off */
/* Changing Spec Cond state... may have to clean up PI */
rh->rh_cs1 &= ~RH_XSC; /* ensure Spec Cond is off */
if (rh->rh_dv.dv_pireq) /* If had PI request, */
rh_picheck(rh); /* Sigh, must re-check stuff */
}
}
}
}
/* RH11_DRERR - Called by drive to assert an exception error during an
** I/O transfer.
*/
static void
rh11_drerr(register struct device *drv)
{
register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_drerr]");
if (!(rh->rh_cs1 & RH_XTRE)) {
rh->rh_cs1 |= RH_XTRE; /* Assert Drive Transfer Error */
rh_pi(rh); /* Then try to trigger PI */
}
/* If I/O transfer in progress, abort it gracefully */
if (1)
rh11_ioend(drv, 1); /* Assume always have a block left */
}
/* RH11_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 rh11_iobuf, but must still call rh11_ioend.
** Else returns # of block units (sectors or records) to do I/O for.
*/
static int
rh11_iobeg(struct device *drv, int wflg)
{
register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr);
register int nblks;
register int18 wc;
rh->rh_dcwrt = wflg; /* Remember if writing */
/* Paranoia - consistency check */
if (rh->rh_ds != drv->dv_num) {
panic("rh11_iobeg: drv/ctrlr mismatch: %d/%d",
rh->rh_ds, drv->dv_num);
}
/* HACK HACK HACK - need to know drive type before we know whether
to return a meaningful block count. Block-addressed drives
are assumed to have 128 words per block (sector); later this
needs to be fixed up with better drive/controller communication.
*/
if (rh->rh_dt[drv->dv_num] == 0) {
rh->rh_dt[drv->dv_num] = (*drv->dv_rdreg)(drv, RHR_DT);
}
/* Find word count in PDP10 words (rounds down) */
wc = (-(rh->rh_wc | ~MASK16))>>1; /* Find # of PDP10 words */
if (wc == 0)
nblks = 0;
else if (rh->rh_dt[drv->dv_num] & RH_DTNBA)
nblks = 1; /* Not a block device */
else
nblks = (wc + 0177) >> 7; /* 128-wd block (sector) */
/* Do mapping and set up internal vars from the xfer registers */
if (!rhdc_ccwget(rh)) {
rh->rh_cs1 |= RH_XTRE; /* Some kind of xfer error */
rh_pi(rh); /* Try to trigger PI */
return 0; /* Tell drive we failed */
}
rh->rh_cs1 &= ~RH_XRDY; /* Channel active now! */
return rh->rh_bcnt = nblks;
}
/* RH11_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
rh11_iobuf(struct device *drv,
register int wc, /* Max 11 bits of word count, so int OK */
vmptr_t *avp)
{
register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_iobuf: %d. => ", wc);
/* If drive is updating our IO xfer status, do it. */
if (wc) {
register uint18 ba;
/* Special hacking needed for BA to update extension bits.
** Code knows that A7 and A6 are contiguous.
*/
ba = rh->rh_ba + (wc << 2); /* Get updated BA value (byte addr) */
if (ba > MASK16) { /* Overflowed 16 bits? */
/* Add overflow bit in-place into extension bits */
rh->rh_cs1 = (rh->rh_cs1 & ~(RH_XA17|RH_XA16))
| ((rh->rh_cs1 + RH_XA16) & (RH_XA17|RH_XA16));
ba &= MASK16;
}
rh->rh_ba = ba;
if (wc < 0) { /* Verify direction consistent */
if (!rh->rh_dcrev)
panic("rh11_iobuf: Neg wc for fwd xfer!");
wc = -wc; /* Make positive */
} else if (rh->rh_dcrev)
panic("rh11_iobuf: Pos wc for rev xfer!");
if (wc > rh->rh_dcwcnt) /* Verify not too big */
panic("rh11_iobuf: drive overran chan!");
rh->rh_wc = (rh->rh_wc + (wc << 1)) & MASK16; /* Add to 11-wd cnt */
if (!rhdc_ccwget(rh)) { /* Do mapping, set up our vars */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "failed]");
return 0; /* Map error! */
}
}
/* See if word count ran out */
if (rh->rh_wc == 0) {
/* Yep, RH11 has no real DC so all done now! */
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; /* Set up by ccwget */
/* Also return addr of buffer. RH11 has no DC so no skip/fill hackery */
*avp = vm_physmap(rh->rh_dcbuf);
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;
}
/* RH11_IOEND - Called by drive when I/O finished, terminate data channel
** I/O xfer. Assumes that rh11_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 RH11DO_SES.
**
** May initiate next RH11 data transfer.
*/
static void
rh11_ioend(register struct device *drv,
int bc) /* Drive's final block count */
{
register struct rh11 *rh = (struct rh11 *)(drv->dv_ctlr);
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rh11_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 RH Chan Err.
*/
#if 0 /* Apparently for RH11 nothing happens in either case? */
rh->rh_dcsts |= (rh->rh_dcwcnt ? CSW_LWCE : CSW_SWCE);
rh->rh_cond |= RH11CI_CE;
#endif
}
/* Now say command done, whatever happened. */
rh->rh_cs1 |= RH_XRDY; /* Done, channel ready */
rh_pi(rh); /* Attempt triggering PI */
}
/* RH11 "Data Channel" routines.
**
*/
/* For completeness? Nothing calls this now */
static void
rhdc_clear(register struct rh11 *rh)
{
rh->rh_dcwcnt = 0;
rh->rh_dcbuf = 0;
}
/* Set up vars for next drive-to-memory transfer
*/
static int
rhdc_ccwget(register struct rh11 *rh)
{
register int wc, wc2;
register paddr_t mem;
register h10_t map; /* Entry from UBA paging RAM */
register unsigned pagno, pagoff;
struct ubctl *ub = rh->rh_dv.dv_uba;
/* If word count reached zero, stop. */
if ((wc = rh->rh_wc) == 0) {
rh->rh_dcwcnt = 0;
return 1;
}
/* Find remaining word count in PDP10 words (rounds down) */
wc = (-(wc | ~MASK16))>>1; /* Find # of PDP10 words */
/* Check unibus map to find starting phys address and how many words
we can handle contiguously in this map before a remap is needed
*/
/* Determine memory address for sector */
mem = rh->rh_ba; /* Bus address */
mem |= (paddr_t) /* Add extended bits */
(rh->rh_cs1 & (RH_XA17|RH_XA16)) << 8;
if (mem & ~(paddr_t)0377774) { /* High bit or low 2 are no-nos */
rh->rh_cs1 |= RH_XMCP; /* Pretend bus error */
/* (maybe shd use a CS2 bit?) */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rhdc_ccwget: bad bus addr %lo]", (long)mem);
return 0;
}
mem >>= 2; /* Get PDP10-word address */
/* High 6 bits (bit 17 known clear) are index into UB map */
pagno = (mem>>9);
pagoff = mem & 0777;
map = ub->ubpmap[pagno];
if (!(map & UBA_QVAL)) { /* If map entry not valid, */
rh->rh_cs1 |= RH_XMCP; /* Pretend bus error (maybe shd use CS2?) */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rhdc_ccwget: no UB map for page %lo]",
(long)pagno);
return 0;
}
mem = ((paddr_t)(map & UBA_QPAG) << 9) | pagoff; /* Find phys addr */
/* Determine whether transfer needs to be limited because it crosses
** non-contiguous physical pages (owing to unibus map)
*/
for (wc2 = wc; ; ) {
/* Find first DEC page boundary */
if ((pagoff + wc2) <= 01000)
break; /* No boundary crossed */
/* Check next UBA page map entry for existence & validity */
if (++pagno >= UBA_UBALEN || (!(ub->ubpmap[pagno] & UBA_QVAL))) {
rh->rh_cs1 |= RH_XMCP; /* Pretend bus error */
if (RHDEBUG(rh))
fprintf(RHDBF(rh), "[rhdc_ccwget: no UB map for page %lo]",
(long)pagno);
return 0;
}
if ((ub->ubpmap[pagno] & UBA_QPAG)
!= 1+(ub->ubpmap[pagno-1] & UBA_QPAG)) {
/* Not contiguous phys page, must limit this xfer. */
wc2 -= (01000 - pagoff); /* Finish rest of current page */
wc -= wc2; /* Limit xfer */
break;
}
wc2 -= 01000;
}
rh->rh_dcwcnt = wc; /* Set up word count */
rh->rh_dcbuf = mem; /* Set up phys PDP10 address */
return 1;
}
#endif /* KLH10_DEV_RH11 */