| /* | |
| 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; | |
| } |