mirror of
https://github.com/PDP-10/klh10.git
synced 2026-01-13 07:19:32 +00:00
487 lines
13 KiB
C
487 lines
13 KiB
C
/* DVUBA.C - KLH10 Emulation of KS10 Unibus Adapters
|
||
*/
|
||
/* $Id: dvuba.c,v 2.4 2002/05/21 09:44:52 klh Exp $
|
||
*/
|
||
/* Copyright © 1992, 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: dvuba.c,v $
|
||
* Revision 2.4 2002/05/21 09:44:52 klh
|
||
* Complain about bad unibus refs only if ub_debug set
|
||
*
|
||
* Revision 2.3 2001/11/10 21:28:59 klh
|
||
* Final 2.0 distribution checkin
|
||
*
|
||
*/
|
||
|
||
/*
|
||
** Provides interface between I/O instructions and UNIBUS devices,
|
||
** including the unibus adapters as devices in their own right.
|
||
**
|
||
** Note that unibus addresses and register values are different in
|
||
** the UBA world than they are for "normal" unibus devices.
|
||
** In particular, for the UBA code:
|
||
** Addresses: 18 bits (RH of IO-address) (18 for real devs)
|
||
** Registers: 32 bits on read; 18 on write (16 for real devs)
|
||
*/
|
||
|
||
#include "klh10.h"
|
||
|
||
#if !KLH10_CPU_KS && 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_CPU_KS /* Moby conditional for entire file */
|
||
|
||
#include <stdio.h> /* For stderr if buggy */
|
||
#include <string.h>
|
||
|
||
#include "kn10def.h"
|
||
#include "kn10ops.h"
|
||
#include "kn10dev.h"
|
||
#include "kn10cpu.h"
|
||
#include "dvuba.h"
|
||
|
||
#ifdef RCSID
|
||
RCSID(dvuba_c,"$Id: dvuba.c,v 2.4 2002/05/21 09:44:52 klh Exp $")
|
||
#endif
|
||
|
||
/* Pre-declarations */
|
||
static void ubasta_write(struct ubctl *, h10_t);
|
||
static uint32 ubapag_read(struct ubctl *, dvuadr_t);
|
||
|
||
|
||
#define PILEV_UB1REQ cpu.pi.pilev_ub1req
|
||
#define PILEV_UB3REQ cpu.pi.pilev_ub3req
|
||
|
||
/* Unibus Adapter data structures.
|
||
** Leave external so CPU can access the PI request status.
|
||
** Easier debugging, too.
|
||
*/
|
||
struct ubctl dvub1, dvub3;
|
||
int ub_debug; /* Not a full debug trace, just prints warnings */
|
||
|
||
static void ub_pifun(struct device *, int);
|
||
|
||
static uint32 ub0_read(dvuadr_t); /* Special rtns for controller #0 */
|
||
static void ub0_write(dvuadr_t, h10_t);
|
||
|
||
|
||
/* DVUB_INIT - Initialize unibus code & data & devices
|
||
*/
|
||
void
|
||
dvub_init(void)
|
||
{
|
||
memset((char *)&dvub1, 0, sizeof(dvub1));
|
||
memset((char *)&dvub3, 0, sizeof(dvub3));
|
||
|
||
dvub1.ubn = 1;
|
||
dvub3.ubn = 3;
|
||
|
||
/* Remember place to deposit our PI requests with CPU. This is
|
||
** something of a hack, but will do for now.
|
||
*/
|
||
dvub1.ubpireqs = &cpu.pi.pilev_ub1req;
|
||
dvub3.ubpireqs = &cpu.pi.pilev_ub3req;
|
||
}
|
||
|
||
|
||
int
|
||
dvub_add(FILE *of,
|
||
register struct device *d,
|
||
int ubn)
|
||
{
|
||
register struct ubctl *ub;
|
||
register int i;
|
||
|
||
switch (ubn) {
|
||
case 1: ub = &dvub1; break;
|
||
case 3: ub = &dvub3; break;
|
||
default:
|
||
fprintf(of, "Cannot bind device to unibus #%d - only 1 and 3 allowed.\n",
|
||
ubn);
|
||
return FALSE;
|
||
}
|
||
|
||
if (ub->ubndevs >= KLH10_DEVUBA_MAX) {
|
||
fprintf(of, "Too many unibus #%d devices (limit is %d)\n",
|
||
ubn, KLH10_DEVUBA_MAX);
|
||
return FALSE;
|
||
}
|
||
|
||
/* Verify that device registers don't conflict with an existing device */
|
||
for (i = 0; i < ub->ubndevs; ++i) {
|
||
register struct device *d2 = ub->ubdevs[i];
|
||
|
||
/* For each existing dev, see if 1st addr comes after new dev's last,
|
||
* or last addr comes before new dev's first.
|
||
*/
|
||
if ((d->dv_aend <= d2->dv_addr) /* Comes after? */
|
||
|| (d->dv_addr >= d2->dv_aend)) { /* Comes before? */
|
||
continue; /* OK, keep going */
|
||
}
|
||
|
||
/* Overlap -- complain and fail */
|
||
fprintf(of, "Cannot bind device - Unibus register overlap:\r\n\
|
||
Device %-6s %06lo-%06lo\r\n\
|
||
Device %-6s %06lo-%06lo\r\n",
|
||
d->dv_name, (long)d->dv_addr, (long)d->dv_aend,
|
||
d2->dv_name, (long)d2->dv_addr, (long)d2->dv_aend);
|
||
return FALSE;
|
||
}
|
||
|
||
d->dv_uba = ub; /* Point to right unibus controller */
|
||
d->dv_pifun = ub_pifun; /* Use this to handle PI reqs */
|
||
|
||
i = ub->ubndevs;
|
||
ub->ubdevs[i+1] = NULL; /* Set fence in array */
|
||
ub->ubdevs[i] = d;
|
||
ub->ubdvadrs[i*2] = d->dv_addr;
|
||
ub->ubdvadrs[(i*2)+1] = d->dv_aend;
|
||
ub->ubndevs++;
|
||
return TRUE;
|
||
}
|
||
|
||
/* UB_PIFUN - Called from devices with BR # (int level) to assert.
|
||
** If nonzero, maps BR level to PI level and triggers PI.
|
||
** If zero, turns off PI request.
|
||
** Sorta painful cuz must check all other devices to be sure
|
||
** it's OK to turn off the corresponding PI level request.
|
||
*/
|
||
|
||
static void
|
||
ub_pifun(register struct device *d,
|
||
int br)
|
||
{
|
||
register struct ubctl *ub = d->dv_uba; /* Get right UBA for dev */
|
||
register int lev;
|
||
register struct device **dp;
|
||
|
||
switch (br) {
|
||
case 0: /* Turn off request for this device */
|
||
d->dv_pireq = 0; /* Turn off for specific device */
|
||
|
||
/* Now propagate turnoff up the chain */
|
||
|
||
/* Find new outstanding level, if any.
|
||
** This scan is a bit ugly.
|
||
*/
|
||
lev = 0;
|
||
for (dp = ub->ubdevs; *dp; ++dp) /* Check all known devs */
|
||
lev |= (*dp)->dv_pireq;
|
||
|
||
if (lev == *(ub->ubpireqs)) /* Compare new level with current */
|
||
return; /* No change, do nothing */
|
||
*(ub->ubpireqs) = lev; /* Changed! Set new */
|
||
pi_devupd(); /* Update main PI state */
|
||
return;
|
||
|
||
case 4:
|
||
case 5:
|
||
lev = ub->ublolev; /* BRs 4 and 5 are mapped to "low" PI */
|
||
break;
|
||
case 6:
|
||
case 7:
|
||
lev = ub->ubhilev; /* BRs 6 and 7 are mapped to "high" PI */
|
||
break;
|
||
default:
|
||
panic("ub_pireq: unknown BR level: %d", br);
|
||
}
|
||
|
||
/* If broke out of switch, triggering interrupt at level "lev" */
|
||
if (!lev) /* UBA may have no interrupt mapping */
|
||
return;
|
||
|
||
d->dv_pireq = lev; /* Set PI request for specific device */
|
||
if (*(ub->ubpireqs) & lev) /* This level PI already requested? */
|
||
return; /* Yep, needn't yell at CPU again */
|
||
|
||
*(ub->ubpireqs) |= lev; /* Nope, tell CPU about new PI! */
|
||
pi_devupd(); /* Update main PI state */
|
||
}
|
||
|
||
|
||
/* UB_DEVFIND - Look up device given its controller and unibus address.
|
||
** This is a slow but sure crock for now; it can be done much
|
||
** more cleverly with a hash table or even a sorted binary-searched
|
||
** order at runtime when devices are configured. Later...
|
||
*/
|
||
static struct device *
|
||
ub_devfind(register struct ubctl *ub,
|
||
register dvuadr_t addr)
|
||
{
|
||
register int i = 0, n = ub->ubndevs;
|
||
register dvuadr_t *ua = ub->ubdvadrs;
|
||
|
||
for (; i < n; ua += 2, ++i) {
|
||
if (ua[0] <= addr && addr < ua[1])
|
||
return ub->ubdevs[i];
|
||
}
|
||
return NULL; /* None found */
|
||
}
|
||
|
||
/* Auxiliaries */
|
||
|
||
#define ubasta_read(ub) ((uint32)((ub)->ubsta))
|
||
#define ubapag_write(ub,addr,val) ((ub)->ubpmap[(addr) - UB_UBAPAG] = (val))
|
||
|
||
static void
|
||
ub_badctl(char *fn, w10_t ioaddr, int bflag)
|
||
{
|
||
if (ub_debug)
|
||
fprintf(stderr, "%s: Bad UB ctlr in IO addr: %lo,,%lo\r\n",
|
||
fn, (long)LHGET(ioaddr), (long)RHGET(ioaddr));
|
||
pag_iofail(((paddr_t)LHGET(ioaddr))<<18 | RHGET(ioaddr), bflag);
|
||
}
|
||
|
||
static void
|
||
ub_badaddr(char *fn, dvuadr_t uaddr, int bflag)
|
||
{
|
||
if (ub_debug)
|
||
fprintf(stderr, "%s: Bad unibus IO addr: %lo\r\n", fn, (long)uaddr);
|
||
pag_iofail((paddr_t)uaddr, bflag);
|
||
}
|
||
|
||
/* UB_READ - Called by KS10 IO instructions to read the Unibus.
|
||
** UB_WRITE - Ditto to write
|
||
*/
|
||
uint32
|
||
ub_read(register w10_t ioaddr)
|
||
{
|
||
switch (LHGET(ioaddr)) { /* Check controller # */
|
||
case 0: return ub0_read((dvuadr_t)RHGET(ioaddr));
|
||
case 1: return uba_read(&dvub1, (dvuadr_t)RHGET(ioaddr));
|
||
case 3: return uba_read(&dvub3, (dvuadr_t)RHGET(ioaddr));
|
||
}
|
||
ub_badctl("ub_read", ioaddr, FALSE);
|
||
return 0; /* Never returns */
|
||
}
|
||
|
||
uint32
|
||
ub_bread(register w10_t ioaddr)
|
||
{
|
||
register dvuadr_t uaddr;
|
||
register uint32 reg;
|
||
|
||
uaddr = RHGET(ioaddr) & ~01; /* Get address w/o low bit */
|
||
switch (LHGET(ioaddr)) { /* Check controller # */
|
||
case 0: reg = ub0_read(uaddr); break;
|
||
case 1: reg = uba_read(&dvub1, uaddr); break;
|
||
case 3: reg = uba_read(&dvub3, uaddr); break;
|
||
default:
|
||
ub_badctl("ub_bread", ioaddr, TRUE);
|
||
/* Never returns */
|
||
}
|
||
if (RHGET(ioaddr) & 01)
|
||
reg = reg >> 8;
|
||
return reg & 0377;
|
||
}
|
||
|
||
void
|
||
ub_write(register w10_t ioaddr,
|
||
h10_t val)
|
||
{
|
||
switch (LHGET(ioaddr)) { /* Check controller # */
|
||
case 0: ub0_write((dvuadr_t)RHGET(ioaddr), val); break;
|
||
case 1: uba_write(&dvub1, (dvuadr_t)RHGET(ioaddr), val); break;
|
||
case 3: uba_write(&dvub3, (dvuadr_t)RHGET(ioaddr), val); break;
|
||
default:
|
||
ub_badctl("ub_write", ioaddr, FALSE);
|
||
/* Never returns */
|
||
}
|
||
}
|
||
|
||
void
|
||
ub_bwrite(register w10_t ioaddr,
|
||
register h10_t val)
|
||
{
|
||
register dvuadr_t uaddr;
|
||
register uint32 mask;
|
||
|
||
if ((uaddr = RHGET(ioaddr)) & 01) {
|
||
uaddr &= ~01;
|
||
mask = 0377 << 8;
|
||
val <<= 8;
|
||
} else {
|
||
mask = 0377;
|
||
}
|
||
val &= mask;
|
||
|
||
switch (LHGET(ioaddr)) { /* Check controller # */
|
||
case 0: ub0_write(uaddr, (ub0_read(uaddr) & ~mask) | val); break;
|
||
case 1: uba_write(&dvub1, uaddr,
|
||
(uba_read(&dvub1, uaddr) & ~mask) | val); break;
|
||
case 3: uba_write(&dvub3, uaddr,
|
||
(uba_read(&dvub3, uaddr) & ~mask) | val); break;
|
||
default:
|
||
ub_badctl("ub_bwrite", ioaddr, TRUE);
|
||
/* Never returns */
|
||
}
|
||
}
|
||
|
||
static uint32
|
||
ub0_read(dvuadr_t addr)
|
||
{
|
||
switch (addr) {
|
||
case UB_KSECCS: /* Memory Status Register */
|
||
return 0; /* Ignore writes, return 0 on reads */
|
||
}
|
||
ub_badaddr("ub0_read", addr, FALSE);
|
||
return 0; /* Never returns */
|
||
}
|
||
|
||
static void
|
||
ub0_write(dvuadr_t addr,
|
||
h10_t val)
|
||
{
|
||
switch (addr) {
|
||
case UB_KSECCS: /* Memory Status Register */
|
||
return; /* Ignore writes */
|
||
}
|
||
ub_badaddr("ub0_write", addr, FALSE);
|
||
return; /* Never returns */
|
||
}
|
||
|
||
uint32
|
||
uba_read(register struct ubctl *ub,
|
||
dvuadr_t addr)
|
||
{
|
||
register struct device *d;
|
||
|
||
if ((d = ub_devfind(ub, addr)))
|
||
return (uint32) (*(d->dv_read))(d, addr);
|
||
|
||
switch (addr) {
|
||
case UB_UBASTA: /* Unibus Status Register */
|
||
return ubasta_read(ub);
|
||
|
||
case UB_UBAMNT: /* Unibus Maintenance Register */
|
||
return 0; /* Ignore writes, return 0 on read */
|
||
}
|
||
|
||
if (UB_UBAPAG <= addr && addr < UB_UBAEND) {
|
||
return ubapag_read(ub, addr);
|
||
}
|
||
ub_badaddr("uba_read", addr, FALSE);
|
||
return 0; /* Never returns */
|
||
}
|
||
|
||
void
|
||
uba_write(register struct ubctl *ub,
|
||
register dvuadr_t addr,
|
||
h10_t val) /* UBA itself can take 18 bits */
|
||
{
|
||
register struct device *d;
|
||
|
||
if ((d = ub_devfind(ub, addr))) {
|
||
(*(d->dv_write))(d, addr, (dvureg_t)val);
|
||
return;
|
||
}
|
||
|
||
switch (addr) {
|
||
case UB_UBASTA: /* Unibus Status Register */
|
||
ubasta_write(ub, val);
|
||
return;
|
||
|
||
case UB_UBAMNT: /* Unibus Maintenance Register */
|
||
return; /* Ignore writes, return 0 on read */
|
||
}
|
||
|
||
if (UB_UBAPAG <= addr && addr < UB_UBAEND) {
|
||
ubapag_write(ub, addr, val);
|
||
return;
|
||
}
|
||
ub_badaddr("uba_write", addr, FALSE);
|
||
}
|
||
|
||
/* Write status reg.
|
||
** Note corresponding ubasta_read() is a macro.
|
||
*/
|
||
static void
|
||
ubasta_write(register struct ubctl *ub,
|
||
register h10_t val)
|
||
{
|
||
register h10_t clrbits, setbits;
|
||
|
||
/* Find bits to clear */
|
||
clrbits = (val & (UBA_BTIM|UBA_BBAD|UBA_BPAR|UBA_BNXD)) | UBA_BPWR
|
||
| (UBA_BDXF|UBA_BINI|UBA_BPIH|UBA_BPIL);
|
||
|
||
/* Find bits to set */
|
||
setbits = (val & (UBA_BDXF|UBA_BINI|UBA_BPIH|UBA_BPIL));
|
||
ub->ubsta = (ub->ubsta & ~clrbits) | setbits;
|
||
if (ub->ubsta & UBA_BINI) {
|
||
/* Do something to initialize this unibus?
|
||
** Note: ITS source says can't both init and set PI levels,
|
||
** hence the clearing done here for sake of emulation.
|
||
*/
|
||
val = (ub->ubsta &= ~(UBA_BINI|UBA_BPIH|UBA_BPIL));
|
||
}
|
||
ub->ubhilev = pilev_bits[(val & UBA_BPIH)>>3];
|
||
ub->ublolev = pilev_bits[val & UBA_BPIL];
|
||
}
|
||
|
||
/* Read paging regs.
|
||
** Note corresponding ubapag_write() is a macro
|
||
*/
|
||
static uint32
|
||
ubapag_read(register struct ubctl *ub,
|
||
dvuadr_t addr)
|
||
{
|
||
register uint32 wval;
|
||
register uint18 ret;
|
||
|
||
/* Reading, must get bits in screwy format */
|
||
ret = ub->ubpmap[addr - UB_UBAPAG];
|
||
wval = ((ret & (UBA_QRPW|UBA_Q16B|UBA_QFST|UBA_QVAL)) >> 5)
|
||
| UBA_PPVL /* Parity always valid */
|
||
| ((ret & UBA_QPAG) >> 9);
|
||
wval = (wval << 18) | (((ret & UBA_QPAG) << 9) & H10MASK);
|
||
return wval;
|
||
}
|
||
|
||
/* UBn_PIVGET - Called by PI system when responding to a PI request.
|
||
** Given a PI level mask (1 bit set),
|
||
** returns vector address for the "closest" device currently requesting
|
||
** an interrupt on that level. If none, returns 0.
|
||
*/
|
||
paddr_t
|
||
ub1_pivget(int lev)
|
||
{
|
||
register struct device **dp = dvub1.ubdevs;
|
||
|
||
/* Return first device wanting attention on given level. */
|
||
for (; *dp; dp++)
|
||
if (lev == (*dp)->dv_pireq)
|
||
return (paddr_t)(*(*dp)->dv_pivec)(*dp);
|
||
return 0;
|
||
}
|
||
|
||
paddr_t
|
||
ub3_pivget(int lev)
|
||
{
|
||
register struct device **dp = dvub3.ubdevs;
|
||
|
||
/* Return first device wanting attention on given level. */
|
||
for (; *dp; dp++)
|
||
if (lev == (*dp)->dv_pireq)
|
||
return (paddr_t)(*(*dp)->dv_pivec)(*dp);
|
||
return 0;
|
||
}
|
||
|
||
#endif /* KLH10_CPU_KS */
|