477 lines
17 KiB
C
477 lines
17 KiB
C
/*
|
|
|
|
Copyright (c) 2015-2016, John Forecast
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of John Forecast shall not
|
|
be used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from John Forecast.
|
|
|
|
*/
|
|
|
|
/* cdc1700_iofw.c: CDC1700 I/O framework
|
|
*/
|
|
#include "cdc1700_defs.h"
|
|
|
|
extern char INTprefix[];
|
|
|
|
extern void buildIOtable(void);
|
|
extern void buildDCtables(void);
|
|
extern void RaiseExternalInterrupt(DEVICE *);
|
|
extern void rebuildPending(void);
|
|
|
|
extern uint16 Areg, Qreg, IOAreg, IOQreg;
|
|
extern DEVICE *sim_devices[];
|
|
extern DEVICE *IOdev[];
|
|
|
|
extern IO_DEVICE **CMap[];
|
|
|
|
t_bool IOFWinitialized = FALSE;
|
|
|
|
/*
|
|
* This I/O framework provides an implementation of a generic device. The
|
|
* framework provides for up to 8 read and 8 write device registers. The
|
|
* read device registers may be stored and read directly from the framework
|
|
* or may cause an entry to the device-specific portion of the device
|
|
* driver. The framework may be setup to dynamically reject I/O requests
|
|
* by setting the appropriate bit in iod_rejmapR/iod_rejmapW. Note that
|
|
* access to the status/function register (register 1) cannot be rejected by
|
|
* the framework and must be implemented by the device-specific code. This
|
|
* allows a device to go "offline" while it it processing requests. Each I/O
|
|
* device using the framework uses an IO_DEVICE structure. Each IO_DEVICE
|
|
* structure may be used by up to 2 DEVICEs (e.g. TTI and TTO).
|
|
*
|
|
* The framework provides support for 3 classes of interrupts:
|
|
*
|
|
* 1. One or more of the standard 3 interrupts (DATA, EOP and ALARM)
|
|
*
|
|
* The framework handles this class by default. Most devices fall into
|
|
* this class.
|
|
*
|
|
* 2. As 1 above but one or more additional interrupts generated by other
|
|
* status bits.
|
|
*
|
|
* The IO_DEVICE structure must include the iod_intr entry to handle
|
|
* the additional interrupt(s). The CD and DP device drivers use this
|
|
* mechanism for the "Ready and not busy" interrupt.
|
|
*
|
|
* 3. Completely non-standard interrupts.
|
|
*
|
|
* Most of the framework is not used in this case. The IO_DEVICE
|
|
* structure must include the iod_raised entry to handle all of it's
|
|
* interrupts. The RTC device driver uses this mechanism.
|
|
*
|
|
*
|
|
* The following fields are present in the IO_DEVICE structure:
|
|
*
|
|
* char *iod_name; - Generic device name override
|
|
* char *iod_model; - Device model name
|
|
* enum IOdevtype iod_type; - Device type
|
|
* when driver supports multiple
|
|
* device types
|
|
* uint8 iod_equip; - Equipment number/interrupt
|
|
* uint8 iod_station; - Station number
|
|
* uint16 iod_interrupt; - Interrupt mask bit
|
|
* uint16 iod_dcbase; - Base address of DC (or zero)
|
|
* DEVICE *iod_indev; - Pointer to input device
|
|
* DEVICE *iod_outdev; - Pointer to output device
|
|
* UNIT *iod_unit; - Currently selected unit
|
|
* t_bool (*iod_reject)(IO_DEVICE *, t_bool, uint8);
|
|
* - Check if should reject I/O
|
|
* enum IOstatus (*iod_IOread)(IO_DEVICE *, uint8);
|
|
* enum IOstatus (*iod_IOwrite)(IO_DEVICE *, uint8);
|
|
* - Device read/write routines
|
|
* enum IOstatus (*iod_BDCread)(struct io_device *, uint16 *, uint8);
|
|
* enum IOstatus (*iod_BDCwrite)(struct io_device *, uint16 *, uint8);
|
|
* - Device read/write routines entered
|
|
* from 1706 buffered data channel
|
|
* void (*iod_state)(char *, DEVICE *, IO_DEVICE *);
|
|
* - Dump device state for debug
|
|
* t_bool (*iod_intr)(IO_DEVICE *);
|
|
* - Check for non-standard interrupts
|
|
* uint16 (*iod_raised)(DEVICE *);
|
|
* - For completely non-standard
|
|
* interrupt handling
|
|
* void (*iod_clear)(DEVICE *);
|
|
* - Perform clear controller operation
|
|
* uint16 iod_ienable; - Device interrupt enables
|
|
* uint16 iod_oldienable; - Previous iod_ienable
|
|
* uint16 iod_imask; - Valid device interrupts
|
|
* uint16 iod_dmask; - Valid director command bits
|
|
* uint16 iod_smask; - Valid status bits
|
|
* uint16 iod_cmask; - Status bits to clear on
|
|
* "clear interrupts"
|
|
* uint16 iod_rmask; - Register mask (vs. station addr)
|
|
* uint8 iod_regs; - # of device registers
|
|
* uint8 iod_validmask; - Bitmap of valid registers
|
|
* uint8 iod_readmap; - Bitmap of read registers
|
|
* uint8 iod_rejmapR; - Bitmaps of register R/W
|
|
* uint8 iod_rejmapW; access to be rejected
|
|
* uint8 iod_flags; - Device flags
|
|
* #define STATUS_ZERO 0x01 - Status register read returns 0
|
|
* #define DEVICE_DC 0x02 - Device is buffered data channel
|
|
* #define AQ_ONLY 0x04 - Device only works on the AQ channel
|
|
* uint8 iod_dc; - Buffered Data Channel (0 => None)
|
|
* uint16 iod_readR[8]; - Device read registers
|
|
* uint16 iod_writeR[8]; - Device write registers
|
|
* uint16 iod_prevR[8]; - Previous device write registers
|
|
* uint16 iod_forced; - Status bits forced to 1
|
|
* t_uint64 iod_event; - Available for timestamping
|
|
* uint16 iod_private; - Device-specific use
|
|
* void *iod_private2; - Device-specific use
|
|
* uint16 iod_private3; - Device-specific use
|
|
* t_bool iod_private4; - Device-specific use
|
|
* void *iod_private5; - Device-specific use
|
|
* uint16 iod_private6; - Device-specific use
|
|
* uint16 iod_private7; - Device-specific use
|
|
* uint16 iod_private8; - Device-specific use
|
|
* uint8 iod_private9; - Device-specific use
|
|
* t_bool iod_private10; - Device-specific use
|
|
*
|
|
* The macro CHANGED(iod, n) will return what bits have been changed in write
|
|
* register 'n' just after it has been written.
|
|
*
|
|
* The macro ICHANGED(iod) will return what interrupt enable bits have been
|
|
* changed just after a director function has been issued.
|
|
*/
|
|
|
|
/*
|
|
* Once-only initialization routine
|
|
*/
|
|
void fw_init(void)
|
|
{
|
|
DEVICE *dptr;
|
|
int i = 0;
|
|
|
|
/*
|
|
* Scan the device table and fill in the DEVICE back pointer(s)
|
|
*/
|
|
while ((dptr = sim_devices[i++]) != NULL) {
|
|
IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt;
|
|
uint8 interrupt = iod->iod_equip;
|
|
|
|
if ((dptr->flags & DEV_INDEV) != 0)
|
|
iod->iod_indev = dptr;
|
|
if ((dptr->flags & DEV_OUTDEV) != 0)
|
|
iod->iod_outdev = dptr;
|
|
|
|
/*
|
|
* Fill in the interrupt mask bit.
|
|
*/
|
|
iod->iod_interrupt = 1 << interrupt;
|
|
}
|
|
|
|
/*
|
|
* Build the I/O device and buffered data channel tables.
|
|
*/
|
|
buildIOtable();
|
|
buildDCtables();
|
|
|
|
IOFWinitialized = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Perform I/O operation - called directly from the IN/OUT instruction
|
|
* processing.
|
|
*/
|
|
enum IOstatus fw_doIO(DEVICE *dptr, t_bool output)
|
|
{
|
|
IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt;
|
|
uint8 rej = (output ? iod->iod_rejmapW : iod->iod_rejmapR) & ~MASK_REGISTER1;
|
|
uint8 reg;
|
|
|
|
if ((iod->iod_flags & DEVICE_DC) != 0)
|
|
reg = ((IOQreg & IO_W) - iod->iod_dcbase) >> 11;
|
|
else reg = IOQreg & iod->iod_rmask;
|
|
|
|
/*
|
|
* Check for valid device address
|
|
*/
|
|
if (reg >= iod->iod_regs)
|
|
return IO_REJECT;
|
|
|
|
/*
|
|
* Check if we should reject this request
|
|
*/
|
|
if ((rej & (1 << reg)) != 0)
|
|
return IO_REJECT;
|
|
|
|
/*
|
|
* Check if we should reject this request
|
|
*/
|
|
if (iod->iod_reject != NULL)
|
|
if ((*iod->iod_reject)(iod, output, reg))
|
|
return IO_REJECT;
|
|
|
|
if (output) {
|
|
iod->iod_prevR[reg] = iod->iod_writeR[reg];
|
|
iod->iod_writeR[reg] = Areg;
|
|
return (*iod->iod_IOwrite)(iod, reg);
|
|
}
|
|
|
|
if ((iod->iod_readmap & (1 << reg)) != 0) {
|
|
Areg = iod->iod_readR[reg];
|
|
return IO_REPLY;
|
|
}
|
|
|
|
return (*iod->iod_IOread)(iod, reg);
|
|
}
|
|
|
|
/*
|
|
* Perform I/O operation - called from the buffered data channel controller.
|
|
*/
|
|
enum IOstatus fw_doBDCIO(IO_DEVICE *iod, uint16 *data, t_bool output, uint8 reg)
|
|
{
|
|
uint8 rej = (output ? iod->iod_rejmapW : iod->iod_rejmapR) & ~MASK_REGISTER1;
|
|
DEVICE *dptr = iod->iod_indev;
|
|
enum IOstatus status;
|
|
|
|
IOAreg = *data;
|
|
|
|
/*
|
|
* Check for valid device address
|
|
*/
|
|
if (reg >= iod->iod_regs)
|
|
return IO_REJECT;
|
|
|
|
/*
|
|
* Check if we should reject this request
|
|
*/
|
|
if ((rej & (1 << reg)) != 0)
|
|
return IO_REJECT;
|
|
|
|
/*
|
|
* Check if we should reject this request
|
|
*/
|
|
if (iod->iod_reject != NULL)
|
|
if ((*iod->iod_reject)(iod, output, reg))
|
|
return IO_REJECT;
|
|
|
|
if ((dptr->dctrl & DBG_DSTATE) != 0)
|
|
if (iod->iod_state != NULL)
|
|
(*iod->iod_state)("before BDC I/O", dptr, iod);
|
|
|
|
if (output) {
|
|
iod->iod_prevR[reg] = iod->iod_writeR[reg];
|
|
iod->iod_writeR[reg] = *data;
|
|
status = (*iod->iod_BDCwrite)(iod, data, reg);
|
|
} else {
|
|
if ((iod->iod_readmap & (1 << reg)) != 0) {
|
|
*data = iod->iod_readR[reg];
|
|
|
|
if ((dptr->dctrl & DBG_DSTATE) != 0)
|
|
if (iod->iod_state != NULL)
|
|
(*iod->iod_state)("after cached BDC I/O", dptr, iod);
|
|
|
|
return IO_REPLY;
|
|
}
|
|
|
|
status = (*iod->iod_BDCread)(iod, data, reg);
|
|
}
|
|
|
|
if ((dptr->dctrl & DBG_DSTATE) != 0)
|
|
if (iod->iod_state != NULL)
|
|
(*iod->iod_state)("after BDC I/O", dptr, iod);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Devices may support multiple interrupts (DATA, EOP and ALARM are standard)
|
|
* but there is only 1 active interrupt flag (IO_ST_INT). This means that we
|
|
* must make sure that the active interrupt flag is set whenever one or more
|
|
* interrupt source is active and the interrupt(s) have been enabled.
|
|
* Interrupts are typically generated when a status flag is raised but we also
|
|
* need to handle removing an interrupt souce when a flag is dropped.
|
|
*
|
|
* In addition, some devices have non-standard interrupts and we need to
|
|
* provide a callback to a device-specific routine to check for such
|
|
* interrupts.
|
|
*/
|
|
void fw_IOintr(t_bool other, DEVICE *dev, IO_DEVICE *iod, uint16 set, uint16 clr, uint16 mask, const char *why)
|
|
{
|
|
/*
|
|
* Set/clear the requested status bits.
|
|
*/
|
|
DEVSTATUS(iod) &= ~(clr | IO_ST_INT);
|
|
DEVSTATUS(iod) |= set | iod->iod_forced;
|
|
DEVSTATUS(iod) &= (mask & iod->iod_smask);
|
|
|
|
rebuildPending();
|
|
|
|
/*
|
|
* Check for any interrupts enabled.
|
|
*/
|
|
if (ISENABLED(iod, iod->iod_imask)) {
|
|
t_bool intr = FALSE;
|
|
|
|
/*
|
|
* Check standard interrupts
|
|
*/
|
|
if ((ISENABLED(iod, IO_DIR_ALARM) &&
|
|
(((DEVSTATUS(iod) & IO_ST_ALARM) != 0))) ||
|
|
(ISENABLED(iod, IO_DIR_EOP) &&
|
|
(((DEVSTATUS(iod) & IO_ST_EOP) != 0))) ||
|
|
(ISENABLED(iod, IO_DIR_DATA) &&
|
|
(((DEVSTATUS(iod) & IO_ST_DATA) != 0))))
|
|
intr = TRUE;
|
|
|
|
/*
|
|
* If the device has non-standard interrupts, call a device-specific
|
|
* routine to determine if IO_ST_INT should be set.
|
|
*/
|
|
if (other)
|
|
if (iod->iod_intr != NULL)
|
|
if (iod->iod_intr(iod))
|
|
intr = TRUE;
|
|
|
|
if (intr) {
|
|
DEVSTATUS(iod) |= IO_ST_INT;
|
|
|
|
if (why != NULL) {
|
|
if ((dev->dctrl & DBG_DINTR) != 0)
|
|
fprintf(DBGOUT, "%s%s Interrupt - %s, Ena: %04X, Sta: %04X\r\n",
|
|
INTprefix, dev->name, why, ENABLED(iod), DEVSTATUS(iod));
|
|
RaiseExternalInterrupt(dev);
|
|
} else rebuildPending();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The following routines are only valid if the framework handles the device
|
|
* status register and the function register (register 1) handles interrupt
|
|
* enable at end of processing.
|
|
*/
|
|
/*
|
|
* 1. Devices which use IO_ST_DATA to signal end of processing.
|
|
*/
|
|
void fw_IOunderwayData(IO_DEVICE *iod, uint16 clr)
|
|
{
|
|
DEVSTATUS(iod) &= ~(clr | IO_ST_READY | IO_ST_DATA);
|
|
DEVSTATUS(iod) |= IO_ST_BUSY;
|
|
DEVSTATUS(iod) |= iod->iod_forced;
|
|
DEVSTATUS(iod) &= iod->iod_smask;
|
|
}
|
|
|
|
void fw_IOcompleteData(t_bool other, DEVICE *dev, IO_DEVICE *iod, uint16 mask, const char *why)
|
|
{
|
|
fw_IOintr(other, dev, iod, IO_ST_READY | IO_ST_DATA, IO_ST_BUSY, mask, why);
|
|
}
|
|
|
|
/*
|
|
* 2. Devices which use IO_ST_EOP to signal end of processing.
|
|
*/
|
|
void fw_IOunderwayEOP(IO_DEVICE *iod, uint16 clr)
|
|
{
|
|
DEVSTATUS(iod) &= ~(clr | IO_ST_READY | IO_ST_EOP);
|
|
DEVSTATUS(iod) |= IO_ST_BUSY;
|
|
DEVSTATUS(iod) |= iod->iod_forced;
|
|
DEVSTATUS(iod) &= iod->iod_smask;
|
|
}
|
|
|
|
void fw_IOcompleteEOP(t_bool other, DEVICE *dev, IO_DEVICE *iod, uint16 mask, const char *why)
|
|
{
|
|
fw_IOintr(other, dev, iod, IO_ST_READY | IO_ST_EOP, IO_ST_BUSY, mask, why);
|
|
}
|
|
|
|
/*
|
|
* 3. Devices which use IO_ST_EOP to signal end of processing, but do not
|
|
* drop IO_ST_READY while I/O is in progress.
|
|
*/
|
|
void fw_IOunderwayEOP2(IO_DEVICE *iod, uint16 clr)
|
|
{
|
|
DEVSTATUS(iod) &= ~(clr | IO_ST_EOP);
|
|
DEVSTATUS(iod) |= IO_ST_BUSY;
|
|
DEVSTATUS(iod) |= iod->iod_forced;
|
|
DEVSTATUS(iod) &= iod->iod_smask;
|
|
}
|
|
|
|
void fw_IOcompleteEOP2(t_bool other, DEVICE *dev, IO_DEVICE *iod, uint16 mask, const char *why)
|
|
{
|
|
fw_IOintr(other, dev, iod, IO_ST_EOP, IO_ST_BUSY, mask, why);
|
|
}
|
|
|
|
void fw_IOalarm(t_bool other, DEVICE *dev, IO_DEVICE *iod, const char *why)
|
|
{
|
|
fw_IOintr(other, dev, iod, IO_ST_ALARM, IO_ST_BUSY, 0xFFFF, why);
|
|
}
|
|
|
|
/*
|
|
* The following routine manipulates "forced" status bits. This allows
|
|
* certain status bits to remain set while the basic I/O framework assumes
|
|
* that it will manipulate such bits, for example, IO_ST_BUSY and
|
|
* IO_ST_READY for the Paper Tape Reader.
|
|
*/
|
|
void fw_setForced(IO_DEVICE *iod, uint16 mask)
|
|
{
|
|
iod->iod_forced |= mask;
|
|
DEVSTATUS(iod) |= (mask & iod->iod_smask);
|
|
}
|
|
|
|
void fw_clearForced(IO_DEVICE *iod, uint16 mask)
|
|
{
|
|
iod->iod_forced &= ~mask;
|
|
DEVSTATUS(iod) &= ~mask;
|
|
}
|
|
|
|
/*
|
|
* Generic device reject check. If the device is not ready, reject all OUTs
|
|
* unless it is to the director function register (register 1).
|
|
*/
|
|
t_bool fw_reject(IO_DEVICE *iod, t_bool output, uint8 reg)
|
|
{
|
|
if (output && (reg != 1)) {
|
|
return (DEVSTATUS(iod) & IO_ST_READY) == 0;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Generic dump routine for a simple device with a function and status
|
|
* register.
|
|
*/
|
|
void fw_state(char *where, DEVICE *dev, IO_DEVICE *iod)
|
|
{
|
|
fprintf(DBGOUT, "%s[%s %s state: Function: %04X, Status: %04x]\r\n",
|
|
INTprefix, dev->name, where, iod->FUNCTION, DEVSTATUS(iod));
|
|
}
|
|
|
|
/*
|
|
* Find a buffered data channel device which supports a specified I/O
|
|
* address. Note that since none of the current devices which can make use
|
|
* of a buffered data channel include a station address, we can just
|
|
* perform a simple range check.
|
|
*/
|
|
IO_DEVICE *fw_findChanDevice(IO_DEVICE *iod, uint16 addr)
|
|
{
|
|
DEVICE *dptr = iod->iod_indev;
|
|
DEVICE *target = IOdev[(addr & IO_EQUIPMENT) >> 7];
|
|
uint32 i;
|
|
|
|
if (target != NULL) {
|
|
for (i = 0; i < dptr->numunits; i++) {
|
|
UNIT *uptr = &dptr->units[i];
|
|
|
|
if (uptr->up8 == target->ctxt)
|
|
return (IO_DEVICE *)target->ctxt;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|