From 1897dfeb9488023e5b25a878585e989fb52e6650 Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Wed, 20 Jul 2016 21:05:02 -0700 Subject: [PATCH] CDC1700: Initial import of new simulator from John Forecast --- CDC1700/CDC1700-Diagnostics.txt | 280 ++++ CDC1700/CDC1700-MSOS.txt | 588 ++++++++ CDC1700/CDC1700.txt | 198 +++ CDC1700/cdc1700_cd.c | 1327 +++++++++++++++++ CDC1700/cdc1700_cpu.c | 1548 +++++++++++++++++++ CDC1700/cdc1700_dc.c | 795 ++++++++++ CDC1700/cdc1700_defs.h | 919 ++++++++++++ CDC1700/cdc1700_dev1.c | 1497 +++++++++++++++++++ CDC1700/cdc1700_dis.c | 325 ++++ CDC1700/cdc1700_dp.c | 1102 ++++++++++++++ CDC1700/cdc1700_io.c | 584 ++++++++ CDC1700/cdc1700_iofw.c | 477 ++++++ CDC1700/cdc1700_lp.c | 654 ++++++++ CDC1700/cdc1700_mt.c | 1973 +++++++++++++++++++++++++ CDC1700/cdc1700_rtc.c | 341 +++++ CDC1700/cdc1700_sym.c | 339 +++++ CDC1700/cdc1700_sys.c | 342 +++++ README.md | 2 + Visual Studio Projects/CDC1700.vcproj | 357 +++++ Visual Studio Projects/Simh.sln | 9 + descrip.mms | 42 +- makefile | 18 +- 22 files changed, 13713 insertions(+), 4 deletions(-) create mode 100644 CDC1700/CDC1700-Diagnostics.txt create mode 100644 CDC1700/CDC1700-MSOS.txt create mode 100644 CDC1700/CDC1700.txt create mode 100644 CDC1700/cdc1700_cd.c create mode 100644 CDC1700/cdc1700_cpu.c create mode 100644 CDC1700/cdc1700_dc.c create mode 100644 CDC1700/cdc1700_defs.h create mode 100644 CDC1700/cdc1700_dev1.c create mode 100644 CDC1700/cdc1700_dis.c create mode 100644 CDC1700/cdc1700_dp.c create mode 100644 CDC1700/cdc1700_io.c create mode 100644 CDC1700/cdc1700_iofw.c create mode 100644 CDC1700/cdc1700_lp.c create mode 100644 CDC1700/cdc1700_mt.c create mode 100644 CDC1700/cdc1700_rtc.c create mode 100644 CDC1700/cdc1700_sym.c create mode 100644 CDC1700/cdc1700_sys.c create mode 100644 Visual Studio Projects/CDC1700.vcproj diff --git a/CDC1700/CDC1700-Diagnostics.txt b/CDC1700/CDC1700-Diagnostics.txt new file mode 100644 index 00000000..c4b53f9c --- /dev/null +++ b/CDC1700/CDC1700-Diagnostics.txt @@ -0,0 +1,280 @@ +Running Diagnostics on the CDC 1700 Simulator +============================================= + +1. Overview + + The diagnostics system (System Maintenance Monitor) is available from +bitsavers.org as bits/CDC/1700_Cyber18/20100517/SYSTEM17_SMM_DIAGS.TAP. The +simulator is able to boot this tape and has successfully run the following +diagnostics with some caveats (for more detailed information see +pdf/CDC/1700/SMM17 at bitsavers.org). + + In some cases the diagnostics fail to run correctly on the simulator +for various reasons: + + - Some tests require hardware modifications or special cabling + - Some tests perform timing checks which are difficult to implement + in the simulator without further documentation or access to the + diagnostic source code + - Some tests check for features which are not implemented in the + simulator. E.G. The disk controllers write Checkwords (CRC) for each + sector written but the documentation does not specify the CRC + algorithm in sufficient detail to allow the simulator to correctly + simulate the feature. + + +Partial listing from the maintenance monitor: + +FF QL1 12166 04EB Quick Look 1 (basic instruction set test) + Passed +FE QL2 10196 095C Quick Look 2 (more detailed instruction set test) + Passed with and without protect mode enabled +01 COM 12166 0990 Comprehensive instruction set test + Passed +03 PTP 12166 05B8 1723/77 Paper Tape Punch Test + Passed (Interrupt + character mode) +04 PTR 12166 03C8 1721/77 Paper Tape Reader Test + Passed (Interrupt + Character mode) +05 TTY 10226 093B 1711/1712/1713 Teletype Test + Passed +08 DP1 12166 0EE6 1738 Disk Pack Test + Passed (Single/dual 854, overlapped seeks) +0A BD1 10076 08BD 1706 Buffered Data Channel Test + Passed (Need to use non-interrupt TTY output) +0C LP1 11056 07E7 1740/1742/93x2 Line Printer Test + Passed +15 MT3 09306 0A96 1732 Magnetic Tape Test + Passed sections 0 - 5. Section 6 fails +1C CMD 12166 07C6 4KW Command Test + Passed +4B PET 12166 0C14 1732-2/1732-3 Magnetic Tape Test + Passed in programmed I/O mode + Failed in DMA mode +91 RTC 10216 04DA System 17 Real Time Clock Test + Passed + +In addition the following tests were run but are not available on the above +tape: + +7A MDC 10186 1CE9 1733-2 Multiple Drive Cartridge Disk Test + Fails seek timing and checkword tests + + +2. Running diagnostics + + Detailed information on running the System Maintenance Monitor can be +found at bitsavers.org in "pdf/cdc/1700/smm17". Here is a sample test run of +the COMmand test (test 01), a CPU instruction test, using the default settings +for running the test: + +Sample test run of the COMmand test (test 01), a CPU instruction test. Note +that SMM uses a private bootstrap and we have to load that into the simulator +by hand (User input to SMM17 is marked with "<==="): + + CDC1700 simulator V4.0-0 Beta git commit id: 9d2079df + sim> attach mt0 SYSTEM17_SMM_DIAGS.TAP + sim> do -v CDC1700/smm32KMTboot + CDC1700/smm32KMTboot-6> echo Loading SMM17 MT bootstrap (32KW) + Loading SMM17 MT bootstrap (32KW) + CDC1700/smm32KMTboot-7> echo + + CDC1700/smm32KMTboot-9> set cpu sstop + CDC1700/smm32KMTboot-10> set mt0 locked + CDC1700/smm32KMTboot-11> set dca interrupt=F + CDC1700/smm32KMTboot-12> boot -S mt0 + + PROGRAM PROTECT NOT SET PROTECTED TEST NOT RUN + + SMM17 VERSION 4.0 CP2F + COPYRIGHT CONTROL DATA CORP. 1974 + + + + BUILD TEST LIST + 0000 / 0000? 0101 <==================== High byte is test # (COM) + Low byte is repetition count + 0000 / 0000? + + + COM001 COMMAND TEST. + IA = 0A00, FC = 01 CP2F, VERSION 4.0 + + 0121 / 020D? <========================= Return to keep defaults + + 0044 / 0BB8? <========================= + + + A1 Q1 A2 Q2 + 0121 020D 0044 0BB8 + + 0124 020D 0001 0A17 + 0124 / 020D? + Simulation stopped, P: 0837 (P 0DFE INQ $FE) + +If a test terminates with A1 containing xxy4 (where xx is the test # and +y is the number of register pairs to be displayed) it was successful. If it +terminates with A1 containing xxy8, the test failed and the remaining +registers indicate what and where the failure occured (format is test +dependent). + + +3. Scripts for running SMM17 + + These scripts allow SMM17 to be run with various test features +enabled. All of the scripts assume that a bootable SMM17 tape image is +attached to mt0. + +3.1 Basic SMM17 boot sequence + + echo Loading SMM17 + echo + set cpu sstop + set mt0 locked + boot -S mt0 + +3.2 Use Buffered Data Channel #1 for tape access + + echo Loading SMM17 - Uses BDC #1 + echo + set cpu sstop + set mt0 locked + set dca interrupt=F + boot -DS mt0 + +3.3 Load MBS and use non-interrupt driven TTY output + + echo Loading SMM17 + echo - MBS loaded + echo - Non-interrupt driven TTY output + echo + set cpu sstop + set cpu sskip + set mt0 locked + boot -S mt0 + c + d a 31E0 + set cpu nosskip + set cpu nosstop + c + + +4. Notes on specific diagnostic tests + + MBS (Monitor Based Subroutine package) must be loaded for tests 0F + and 4B. + + Test 0F (BD2, 1706/1716 Data Channel Test) requires a 7-track tape + for section 4 (relies on the controller masking the output to 6-bits). + + Test 0F (BD2, 1706/1716 Data Channel Test) gets an error 0004 with + Q set to 001C! The problem seems to be related to returning 7-track + status in Director Status 2 (if I disable setting IO_ST2_7TRACK + everything works correctly for sections 2 and 4). + + Test 4A (MTX, 1731/1732 Mag Tape Test) hangs while writing to the + TTY. The source listing (60411400C...) does not match the code + loaded from the magtape image. + + Test 4B (PET, 1732-2/-3 Mag Tape subsystem test) can only be used if + the mag tape controller type is set to 1732-3. The test can access + the controller in 2 modes; programmed I/O which only works up to + 800 BPI and DMA (called DSA) which works up to 1600 BPI. In programmed + I/O mode the tests complete successfully. In DSA mode, the test + fails during the first read from tape (writes work successfully) with + a timeout waiting for "end of operation". My current hypothesis is + that the controller asserts "end of operation" when all of the data + has been transferred to the CPU but delays generating an interrupt + until it has processed the CRC at the end of the block. I would need + to look at the test source listing before making any changes. + + Test 7A (MDC, 1733-2 Cartridge Disk Controller) passes most test. + The seek timing test fails - I don't want to debug this without + source listings. The checkword test fails since we don't store the + checkword data in the emulated storage and it is not clear there + is sufficient documentation to be able to construct a valid checkword + on the fly. + + +5. Directory listing of the SMM17 tape (with descriptions) + +BIN QL + +FF QL1 12166 04E8 Quick Look 1 +FE QL2 10196 095C Quick Look 2 +FC DPC 09146 0685 Disk Call-up Program +00 SMM 11046 10A3 System Maintenance Monitor +01 COM 12166 0990 Command Test +02 MY2 12166 0753 Memory Test +03 PTP 12166 05B8 1723/77 Paper Tape Punch Test +04 PTR 12166 03C8 1721/77 Paper Tape Reader Test +05 TTY 10226 093B 1711/12/13 Teletype Test +07 MT1 07046 0980 1731 Magnetic Tape Test +08 DP1 12166 0EE6 1738 Disk Pack Test +09 RPT 12166 023D Random Protect Test +0A BD1 10076 08BD 1706 Buffered Data Channel Test +0B SC1 12166 0A2E Satellite Coupler Test +0C LP1 11056 07E7 1740/42/93X2 Line Printer Test +0D CRP 10154 0C46 1728 Card Reader/Punch Test +0E MT2 11036 0BCA 1731 Magnetic Tape Test +0F BD2 10076 0A0B 1706/1716 Data Channel Test +10 DTB 10154 08A4 1700/8000 - 8049-A, 211 Display Test +11 DSC 04122 092F 1700 Data Set Controller Test +12 MY1 10186 0834 Memory Test +13 CR3 11056 0B46 1729-2/3 Card Reader Test +14 MEM 07046 0612 Memory Test +17 CR2 10154 0AE1 1726/405 Card Reader Test +1C CMD 12166 07C6 4K Command Test +1D DDT 07046 0BB1 1745-2 Display Test. Version 4.0 +1E MOS 07046 05AF MOS Memory Test +1F MTS 10206 053B 1731/32 Special Test +20 DS1 11056 0989 1747 DSC Test +23 LP5 10196 0F06 1742-120 Line Printer Test W/595-4 Train +27 DP3 10154 1517 1700 Disk Subsystem Test +2D UD3 08012 0971 3000 Channel Simulator Program Update +30 RX1 09246 148E 1700/FF104/955 System Test +31 LR1 09247 16C4 Unknown +32 LDR 09096 0182 Unknown +34 RX4 09166 12E9 Unknown +35 OCR 12146 1727 1735/915 OCR035 Test +36 AQM 11086 1327 DJ814A A/Q Communications Multiplexer (NUMOD) Test +3B DMP 10154 01F9 Printer/TTY Dump +3D SAS 08012 0EE6 3000 Channel Simulator Assembler +3E EDT 12166 0805 17X4 Library Editing Routine +40 DDC 11086 0B5B 1745/1746/211 Display Station Test +42 CLK 08176 0994 10126 Interval Timer - Day Clock Test +43 CTC 10216 1C90 1749 Communications Terminal Test +4A MTX 10076 11A8 1731/1732 Mag Tape Test +4B PET 12166 0C14 1732-2/1732-3 Magnetic Tape Test +4C HOR 10233 0FC9 1500 Series Remote Peripheral Controller Diagnostic +56 BC2 07155 15AC SC17/1700 FR101 MEM/COM/IFP Test +57 UD1 01303 0774 1700 Source/6000 TVC Update +59 BC3 07155 0967 SC17/1700/FR101/FR113 Interface Test +60 KEY 04016 0D8F Cyberdata Key Entry Station Test +61 RST 12146 0FD3 979 Reader/Sorter Test +69 PAD 11086 0CCF Punch the 1726/405 Test Deck +6F DG4 02022 2340 1744/274 Digigraphic Test +70 GT0 10154 0623 GPGT Troubleshooting Test +71 GT1 10086 2880 GPGT DCI Test +72 GT2 07155 2842 GPGT Display Quality Test +73 GT3 10154 0C8B GPGT Light Pen And Keyboard Test +74 GT4 12112 0A25 GPGT Communications Test +75 GT5 10154 0827 GPGT Communications Test (12 Bit Interface) +76 GT6 10154 0C87 GPGT Specification Vertification Test +78 CDD 07046 19AD 1739 Cartridge Disk Controller Test +80 DRM 11086 2702 BG504 Drum Test +81 CTR 07046 092C FR117 Event Counter Subsystem Test +83 DIO 11096 06F3 1553/54 1544/45 Digital Input/Output Subsystem Test +84 DP5 07046 0F7C 1738 Quick Look Test +86 ACC 07155 0A81 Asynchronous Communication Controller (DJ815-A) Test +87 SCC 07155 0BCD Synchronous Communication Controller (FJ606-A) Test +88 CPC 10216 0E5C 1725-1 Card Punch Test +89 BSC 11196 0CC3 Cyberdata Bisync Controller Test +8A HFP 07046 1CDE 1781-a Hardware Floating Point Unit Test +90 IOM 11096 0B8B IOM Mother Unit Digital Input/Output Test +91 RTC 10216 04DA System 17 Real Time Clock Test +92 PT1 10205 0617 1720-1 Paper Tape Punch Test +93 PT2 10205 05B1 1720-1 Paper Tape Reader Test +D0 CPY 07046 02CC Tape To Tape Copy +F0 CPV 10216 03FB Card Punch Verify Utility +FB REP 10216 14E2 Replace/Update 17X4 SMM Disk Library +FD INS 10216 0965 Mass Storage Maintenance System Installation Program diff --git a/CDC1700/CDC1700-MSOS.txt b/CDC1700/CDC1700-MSOS.txt new file mode 100644 index 00000000..386fa304 --- /dev/null +++ b/CDC1700/CDC1700-MSOS.txt @@ -0,0 +1,588 @@ +Installing MSOS 5 on the CDC 1700 Simulator +=========================================== + +1. Overview. + + A Mass Storage Operating System Version 5 (MSOS 5) distribution tape +which will run on the CDC 1700 Simulator is available from bitsaver.org as +bits/CDC/1700_Cyber18/20100524/MSOS5_SL136.tap. At the time, CDC would have +tailored a distribution tape for the customers specific hardware and this +tape was built for a 64KW 1784 system used by Exxon. Not all of the +peripherals included in the system are implemented on the simulator due to +lack of documentation but enough are available to perform a basic +installation. + +2. Installation + + Installation of MSOS 5 occurs in 2 phases: + + - Phase 1 + + In Phase 1, the distribution tape is booted and the initializer + lays down a skeleton system on the cartridge disk. + + - Phase 2 + + In Phase 2, the skeleton system is booted from the cartridge disk + and the batch subsystem is started to read and process a sequence + of batch jobs to complete the installation. + +It is important that both phases are within the same execution of the CDC +1700 Simulator since phase 1 leaves the tape correctly positioned at the +start of the batch jobs for phase 2. + + The device drivers built into this version of MSOS 5 use specific +versions of the magtape and line printer controllers. The type of these +controllers must be set before attaching host data files. If the controller +types are not set correctly, phase 1 of the installation will complete +successfully but the batch subsystem will refuse to start. + +Sample installation run of MSOS 5 (user input to MSOS is marked with "<==="): + + CDC1700 simulator V4.0-0 Beta git commit id: 9d2079df + sim> set cpu mode65k,64k + sim> set mt type=1732-3 + sim> att mt0 MSOS5_SL136.tap + sim> set lp type=1742 + sim> att lp MSOSinstall.lpt + LP: creating new file + sim> att cd0 MSOS5.dsk + CD: creating new file + sim> boot mt0 + + MSOS 5.0 SYSTEM INITIALIZER + FWA OF CONTRL = 5000 + + + DATE MM/DD/YY + 07/14/88 <============================ Enter date + + Q + *I,3 <================================= Distribution media is tape + *I,3 + + Q + *V <=================================== Start installation + *V + *V + *S,SYSMON,$3031 + *S,SYSDAY,$3236 + *S,SYSYER,$3832 + *S,SYSLVL,$3230 + *V + *V 1700 MASS STORAGE OPERATING SYSTEM - VER 5.0 + *V + *V EXXON DEVELOPMENT SYSTEM + *V + *YM,LIBEDT,1 + *YM,LOADSD,2 + *YM,JOBENT,3 + *YM,JOBPRO,4 + *YM,PROTEC,5 + *YM,JPLOAD,6 + *YM,JPCHGE,7 + *YM,JPT13,8 + *YM,JCRDV4,9 + *YM,JLGOV4,10 + *YM,JPSTV4,11 + *YM,NAMEV4,12 + *YM,JPFLV4,13 + *YM,AFILV4,14 + *YM,RESTOR,15 + *YM,RCOVER,16 + *YM,BRKPT,17 + *YM,ODEBUG,18 + *YM,SYSCOP,19 + *YM,SYSSEG,20 + *YM,MIPRO,21 + *YM,TDFUNC,22 + *YM,EFSTOR,23 + *YM,EFLIST,24 + *YM,VERIFY,25 + *YM,SCMM17,26 + *YM,DUMMY1,27 + *YM,DUMMY2,28 + *YM,DUMMY3,29 + *YM,DUMMY4,30 + *YM,DUMMY5,31 + *YM,DUMMY6,32 + *YM,DUMMY7,33 + *YM,DUMMY8,34 + *YM,DUMMY9,35 + *YM,DUMMY0,36 + *S,N4,$0800 + *S,END0V4,$7FFF + *S,BGNMON,$8000 + *S,MSIZV4,$FFFF + *S,SECTOR,$5BFA + * + *L SYSTEM DATA PROGRAM + CS7C17 + SYSDAT 0000 EXXON DEVELOPMENT SYSTEM SUMMARY-122 + *L SPACE REQUEST PROCESSOR + SPACE 1C21 DECK-ID M29 MSOS 5.0 SUMMARY-136 + * + * SYSTEM CORE RESIDENT PROGRAMS + * + *LP MONITOR + NMONI 8000 DECK-ID M10 MSOS 5.0 SUMMARY-136 + RDISP 804D DECK-ID M23 MSOS 5.0 SUMMARY-110 + RW 81FF DECK-ID M09 MSOS 5.0 SUMMARY-110 + T14 82AD DECK-ID M26 MSOS 5.0 SUMMARY-110 + T16 82BE DECK-ID M04 MSOS 5.0 SUMMARY-110 + PARAME 82C9 DECK-ID M03 MSOS 5.0 SUMMARY-110 + COMMON 833A DECK-ID M13 MSOS 5.0 SUMMARY-110 + NIPROC 8361 DECK-ID M12 MSOS 5.0 SUMMARY-118 + ALVOL 83F3 DECK-ID M16 MSOS 5.0 SUMMARY-110 + OFVOL 8410 DECK-ID M15 MSOS 5.0 SUMMARY-110 + ALCORE 841D DECK-ID M17 MSOS 5.0 SUMMARY-110 + DCORE 84CB DECK-ID M19 MSOS 5.0 SUMMARY-110 + PRTCDR 8634 DECK-ID M18 MSOS 5.0 SUMMARY-110 + NFNR 884E DECK-ID M21 MSOS 5.0 SUMMARY-141 + NCMPRQ 88C7 DECK-ID M20 MSOS 5.0 SUMMARY-110 + MAKQ 88F7 DECK-ID M08 MSOS 5.0 SUMMARY-110 + ADEV 8925 DECK-ID M22 MSOS 5.0 SUMMARY-132 + TMINT 8A98 DECK-ID M06 MSOS 5.0 SUMMARY-116 + DTIMER 8B2A DECK-ID M05 MSOS 5.0 SUMMARY-136 + TOD 8B4D DECK-ID M25 MSOS 5.0 SUMMARY-110 + MINT 8B9C DECK-ID M07 MSOS 5.0 SUMMARY-116 + TRVEC 8CC6 DECK-ID M14 MSOS 5.0 SUMMARY-116 + *LP DEBUGGING / CHECKOUT + SNAPOL 8D19 DECK-ID M02 MSOS 5.0 SUMMARY-110 + DMP42X 8DD2 DECK-ID B17 PERIPH. DRIVERS 1.0B SUMMARY-106 + B17332 8EA1 DECK-ID B11 PERIPH. DRIVERS 1.0B SUMMARY-106 + *LP FILE MANAGER + FILMGR 8EF3 DECK-ID F01 FILE MANAGER 1.0 SUMMARY-124 + RSPCV4 91DC DECK-ID F02 FILE MANAGER SUMMARY-116 + SRHFIS 92CE DECK-ID F03 FILE MANAGER SUMMARY-110 + *LP CORE RESIDENT DRIVERS + EFDATA 94A0 DECK-ID M27 MSOS 5.0 SUMMARY-110 + DUMMY 960D DECK-ID M30 MSOS 5.0 SUMMARY-110 + D1711 9630 DECK-ID B27 PERIPH. DRIVERS 1.0B SUMMARY-106 + D17332 97D8 DECK-ID B34 PERIPH. DRIVERS 1.0B SUMMARY-119 + D1752 9A77 DECK-ID A40 PERIPH. DRIVERS 1.0A SUMMARY-106 + DPSDSK 9C81 DECK-ID C90 PERIPH. DRIVERS 1.2C SUMMARY-122 + REWCK 9CB2 DECK-ID B78 PERIPH. DRIVERS 1.0B SUMMARY-106 + MMEXEC 9CCD DECK-ID M01 MSOS 5.0 SUMMARY-132 + D17432 9E60 DECK-ID B35 PERIPH. DRIVERS 1.0B SUMMARY-115 + *LP REENTRANT FORTRAN RUNTIME LIBRARY + FORTR A0A9 DECK-ID A01 FTN 3.3 RUNTIME SUMMARY-102 + Q8PRMR A1ED DECK-ID B01 FTN 3.3 RUNTIME SUMMARY-102 + PARABR A217 DECK-ID B02 FTN 3.3 RUNTIME SUMMARY-102 + Q8F2IR A228 DECK-ID B03 FTN 3.3 RUNTIME SUMMARY-102 + ABSR A2D4 DECK-ID B04 FTN 3.3 RUNTIME SUMMARY-102 + SQRTFR A2ED DECK-ID B05 FTN 3.3 RUNTIME SUMMARY-102 + SIGNR A348 DECK-ID B06 FTN 3.3 RUNTIME SUMMARY-102 + FXFLTR A374 DECK-ID B07 FTN 3.3 RUNTIME SUMMARY-102 + EXPR A3AC DECK-ID B08 FTN 3.3 RUNTIME SUMMARY-102 + ALOGR A44C DECK-ID B09 FTN 3.3 RUNTIME SUMMARY-102 + TANHR A4C3 DECK-ID B10 FTN 3.3 RUNTIME SUMMARY-102 + SNCSR A52F DECK-ID B11 FTN 3.3 RUNTIME SUMMARY-102 + ATANR A5F9 DECK-ID B12 FTN 3.3 RUNTIME SUMMARY-102 + Q8QIOR A697 DECK-ID C01 FTN 3.3 RUNTIME SUMMARY-102 + BINARR A7FC DECK-ID C02 FTN 3.3 RUNTIME SUMMARY-102 + IOCODR A847 DECK-ID D01 FTN 3.3 RUNTIME SUMMARY-102 + INITLR A87B DECK-ID D02 FTN 3.3 RUNTIME SUMMARY-102 + RSTORR A88A DECK-ID D03 FTN 3.3 RUNTIME SUMMARY-102 + GETCHR A89A DECK-ID D04 FTN 3.3 RUNTIME SUMMARY-102 + IPACKR A8B4 DECK-ID D05 FTN 3.3 RUNTIME SUMMARY-102 + UPDATR A8F2 DECK-ID D06 FTN 3.3 RUNTIME SUMMARY-102 + DECPLR A8FF DECK-ID D07 FTN 3.3 RUNTIME SUMMARY-102 + INTGRR A925 DECK-ID D08 FTN 3.3 RUNTIME SUMMARY-102 + SPACER A952 DECK-ID D09 FTN 3.3 RUNTIME SUMMARY-102 + HOLR A96A DECK-ID D10 FTN 3.3 RUNTIME SUMMARY-102 + DCHXR A9FE DECK-ID D11 FTN 3.3 RUNTIME SUMMARY-102 + HXASCR AA71 DECK-ID D12 FTN 3.3 RUNTIME SUMMARY-102 + AFMTOR AAC4 DECK-ID D13 FTN 3.3 RUNTIME SUMMARY-102 + RFMTOR AAEE DECK-ID D14 FTN 3.3 RUNTIME SUMMARY-102 + AFMTIR AB07 DECK-ID D15 FTN 3.3 RUNTIME SUMMARY-102 + RFMTIR AB35 DECK-ID D16 FTN 3.3 RUNTIME SUMMARY-102 + ASCHXR AB4C DECK-ID D17 FTN 3.3 RUNTIME SUMMARY-102 + HXDCR AB87 DECK-ID D18 FTN 3.3 RUNTIME SUMMARY-102 + FLOTIR AC17 DECK-ID D19 FTN 3.3 RUNTIME SUMMARY-102 + FOUTR AC61 DECK-ID D20 FTN 3.3 RUNTIME SUMMARY-102 + EOUTR ACEB DECK-ID D21 FTN 3.3 RUNTIME SUMMARY-102 + EWRITR ADD3 DECK-ID D22 FTN 3.3 RUNTIME SUMMARY-102 + INTI1R ADDF DECK-ID D23 FTN 3.3 RUNTIME SUMMARY-102 + FORMTR ADFC DECK-ID D24 FTN 3.3 RUNTIME SUMMARY-115 + Q8QFIR AFFD DECK-ID D25 FTN 3.3 RUNTIME SUMMARY-102 + Q8QFLR B017 DECK-ID D26 FTN 3.3 RUNTIME SUMMARY-102 + Q8QFXR B046 DECK-ID D27 FTN 3.3 RUNTIME SUMMARY-102 + HEXAR B07D DECK-ID D28 FTN 3.3 RUNTIME SUMMARY-102 + HEXDR B095 DECK-ID D29 FTN 3.3 RUNTIME SUMMARY-102 + ASCIIR B0B2 DECK-ID D30 FTN 3.3 RUNTIME SUMMARY-102 + DECHXR B0C7 DECK-ID D31 FTN 3.3 RUNTIME SUMMARY-102 + AFORMR B0E7 DECK-ID D32 FTN 3.3 RUNTIME SUMMARY-102 + RFORMR B103 DECK-ID D33 FTN 3.3 RUNTIME SUMMARY-102 + FLOTGR B11F DECK-ID D34 FTN 3.3 RUNTIME SUMMARY-102 + FLOTR B13B DECK-ID B14 FTN 3.3 RUNTIME SUMMARY-112 + COMFPR B389 DECK-ID B15 FTN 3.3 RUNTIME SUMMARY-102 + SGDBLR B49C DECK-ID E01 FTN 3.3 RUNTIME SUMMARY-102 + Q8D2IR B4B0 DECK-ID E02 FTN 3.3 RUNTIME SUMMARY-102 + DABSR B542 DECK-ID E03 FTN 3.3 RUNTIME SUMMARY-102 + DSQRTR B55E DECK-ID E04 FTN 3.3 RUNTIME SUMMARY-102 + DSIGNR B5DE DECK-ID E05 FTN 3.3 RUNTIME SUMMARY-102 + DEXPR B60B DECK-ID E08 FTN 3.3 RUNTIME SUMMARY-102 + DLOGR B6C7 DECK-ID E09 FTN 3.3 RUNTIME SUMMARY-102 + DSNCSR B76D DECK-ID E11 FTN 3.3 RUNTIME SUMMARY-102 + DATANR B882 DECK-ID E12 FTN 3.3 RUNTIME SUMMARY-132 + Q8QDFR B958 DECK-ID E14 FTN 3.3 RUNTIME SUMMARY-102 + DOUTR B974 DECK-ID E15 FTN 3.3 RUNTIME SUMMARY-102 + DFLOTR BA7E DECK-ID E13 FTN 3.3 RUNTIME SUMMARY-102 + DRSTRR BEDC DECK-ID E10 FTN 3.3 RUNTIME SUMMARY-102 + NXTLOC BF12 NEXT AVAILABLE LOCATION + * + * SYSTEM MASS RESIDENT PROGRAMS + * + *M LIBEDT 1 + LIBEDT 0203 DECK-ID M35 MSOS 5.0 SUMMARY-122 + *M LOADSD 2 + LOAD1 0243 DECK-ID M36 MSOS 5.0 SUMMARY-110 + BRNCH1 0236 DECK-ID M37 MSOS 5.0 SUMMARY-132 + PG2KRD 0398 DECK-ID O69 MSOS 5.0 SUMMARY-116 + LIDRV1 03C9 DECK-ID M38 MSOS 5.0 SUMMARY-110 + LCDRV1 041A DECK-ID M39 MSOS 5.0 SUMMARY-110 + LMDRV1 0447 DECK-ID M40 MSOS 5.0 SUMMARY-110 + LLDRV1 0466 DECK-ID M41 MSOS 5.0 SUMMARY-110 + ADJOF1 0474 DECK-ID M42 MSOS 5.0 SUMMARY-110 + CNVRT1 0480 DECK-ID M43 MSOS 5.0 SUMMARY-110 + LSTOT1 0498 DECK-ID M44 MSOS 5.0 SUMMARY-110 + LINK11 04EB DECK-ID M45 MSOS 5.0 SUMMARY-110 + LOADR1 052E DECK-ID M46 MSOS 5.0 SUMMARY-116 + NAMPR1 05A7 DECK-ID M47 MSOS 5.0 SUMMARY-110 + RBDBZ1 0648 DECK-ID M48 MSOS 5.0 SUMMARY-110 + ENTEX1 0741 DECK-ID M49 MSOS 5.0 SUMMARY-110 + XFRPR1 0777 DECK-ID M50 MSOS 5.0 SUMMARY-110 + STBASE 0788 DECK-ID M51 MSOS 5.0 SUMMARY-110 + LNKENT 0870 DECK-ID M52 MSOS 5.0 SUMMARY-110 + LNKCR1 088C DECK-ID M53 MSOS 5.0 SUMMARY-110 + PATCH 08CD DECK-ID M54 MSOS 5.0 SUMMARY-110 + TBSCH1 090F DECK-ID M55 MSOS 5.0 SUMMARY-110 + HASH 0957 DECK-ID M56 MSOS 5.0 SUMMARY-110 + TBSTR1 096F DECK-ID M57 MSOS 5.0 SUMMARY-110 + PAGE 09C4 DECK-ID M58 MSOS 5.0 SUMMARY-136 + PROGLD 0AB5 DECK-ID M59 MSOS 5.0 SUMMARY-110 + SCAN1 0BB7 DECK-ID M60 MSOS 5.0 SUMMARY-110 + CHPU1 0C7D DECK-ID M61 MSOS 5.0 SUMMARY-110 + ADJOV2 0C8A DECK-ID M62 MSOS 5.0 SUMMARY-110 + ADRPR1 0CA3 DECK-ID M63 MSOS 5.0 SUMMARY-110 + *M JOBENT 3 + JOBENT 0266 DECK-ID M64 MSOS 5.0 SUMMARY-116 + T11 0112 DECK-ID M65 MSOS 5.0 SUMMARY-110 + T7 0149 DECK-ID M66 MSOS 5.0 SUMMARY-110 + T5 026B DECK-ID M67 MSOS 5.0 SUMMARY-110 + T3 02C4 DECK-ID M68 MSOS 5.0 SUMMARY-110 + *S,N1,P + *M JOBPRO 4 + JOBPRO 026E DECK-ID M69 MSOS 5.0 SUMMARY-132 + ONE 023D DECK-ID M70 MSOS 5.0 SUMMARY-110 + TWO 0240 DECK-ID M71 MSOS 5.0 SUMMARY-110 + THREE 0243 DECK-ID M72 MSOS 5.0 SUMMARY-110 + + * * * U N P A T C H E D E X T E R N A L S * * * + BATLST + *S,N2,P + *M PROTEC 5 + UPROTK 0275 DECK-ID M73 MSOS 5.0 SUMMARY-115 + JBKILL 044C DECK-ID M75 MSOS 5.0 SUMMARY-110 + *M JPLOAD 6 + JPLOAD 0282 DECK-ID M76 MSOS 5.0 SUMMARY-110 + *M JPCHGE 7 + JPCHGE 0287 DECK-ID M77 MSOS 5.0 SUMMARY-110 + ASCHEX 013E DECK-ID M78 MSOS 5.0 SUMMARY-110 + *M JPT13 8 + T13 028C DECK-ID M79 MSOS 5.0 SUMMARY-110 + *M JCRDV4 9 + JCRDV4 0291 DECK-ID M80 MSOS 5.0 SUMMARY-136 + *M JLGOV4 10 + JLGOV4 0295 DECK-ID M81 MSOS 5.0 SUMMARY-110 + *M JPSTV4 11 + JPSTV4 0298 DECK-ID M84 MSOS 5.0 SUMMARY-110 + *M NAMEV4 12 + NAMEV4 029A DECK-ID M85 MSOS 5.0 SUMMARY-110 + *M JPFLV4 13 + JPFLV4 02A2 DECK-ID M82 MSOS 5.0 SUMMARY-110 + *M AFILV4 14 + JPF2V4 02A9 DECK-ID M83 MSOS 5.0 SUMMARY-110 + *M RESTOR 15 + RESTOR 02B3 DECK-ID M86 MSOS 5.0 SUMMARY-114 + *M RCOVER 16 + RCOVER 02B6 DECK-ID M87 MSOS 5.0 SUMMARY-110 + OUTSEL 0144 DECK-ID M88 MSOS 5.0 SUMMARY-110 + RDMPV4 0189 DECK-ID M89 MSOS 5.0 SUMMARY-110 + MASDMP 0249 DECK-ID M90 MSOS 5.0 SUMMARY-110 + *M BRKPT 17 + BRKPT1 02BF DECK-ID N01 MSOS 5.0 SUMMARY-110 + *M ODEBUG 18 + ODBUG1 02CC DECK-ID N07 MSOS 5.0 SUMMARY-116 + GETREQ 0120 DECK-ID N08 MSOS 5.0 SUMMARY-110 + LHXREQ 0240 DECK-ID N09 MSOS 5.0 SUMMARY-110 + DPCREQ 0300 DECK-ID N10 MSOS 5.0 SUMMARY-132 + SCNREQ 03C0 DECK-ID N11 MSOS 5.0 SUMMARY-116 + SETREQ 0480 DECK-ID N12 MSOS 5.0 SUMMARY-110 + MBCREQ 04E0 DECK-ID N13 MSOS 5.0 SUMMARY-110 + SCHREQ 05A0 DECK-ID N14 MSOS 5.0 SUMMARY-116 + SPEREQ 0660 DECK-ID N15 MSOS 5.0 SUMMARY-113 + CPPREQ 06C0 DECK-ID N16 MSOS 5.0 SUMMARY-110 + SPPREQ 0720 DECK-ID N17 MSOS 5.0 SUMMARY-110 + ADHREQ 0780 DECK-ID N18 MSOS 5.0 SUMMARY-110 + SBHREQ 07E0 DECK-ID N19 MSOS 5.0 SUMMARY-110 + ALCREQ 0840 DECK-ID N20 MSOS 5.0 SUMMARY-116 + RELREQ 0900 DECK-ID N21 MSOS 5.0 SUMMARY-110 + DACREQ 0960 DECK-ID N22 MSOS 5.0 SUMMARY-116 + PTHREQ 0A80 DECK-ID N23 MSOS 5.0 SUMMARY-116 + MTRREQ 0BA0 DECK-ID N24 MSOS 5.0 SUMMARY-116 + MSDREQ 0C00 DECK-ID N25 MSOS 5.0 SUMMARY-116 + CLUREQ 0CC0 DECK-ID N26 MSOS 5.0 SUMMARY-110 + WCDREQ 0D20 DECK-ID N27 MSOS 5.0 SUMMARY-116 + LASREQ 0D80 DECK-ID N28 MSOS 5.0 SUMMARY-110 + DASREQ 0EA0 DECK-ID N29 MSOS 5.0 SUMMARY-116 + MLUREQ 0F60 DECK-ID N30 MSOS 5.0 SUMMARY-110 + DPTREQ 0FC0 DECK-ID N31 MSOS 5.0 SUMMARY-116 + SLDREQ 1080 DECK-ID N32 MSOS 5.0 SUMMARY-116 + CWAREQ 10E0 DECK-ID N33 MSOS 5.0 SUMMARY-132 + DMHREQ 1200 DECK-ID N34 MSOS 5.0 SUMMARY-116 + SMNREQ 12C0 DECK-ID N35 MSOS 5.0 SUMMARY-116 + SMPREQ 1440 DECK-ID N36 MSOS 5.0 SUMMARY-116 + LSPREQ 1500 DECK-ID N37 MSOS 5.0 SUMMARY-116 + DSPREQ 15C0 DECK-ID N38 MSOS 5.0 SUMMARY-116 + DMSREQ 16E0 DECK-ID N39 MSOS 5.0 SUMMARY-116 + LSOREQ 1860 DECK-ID N40 MSOS 5.0 SUMMARY-116 + CCCREQ 1980 DECK-ID N41 MSOS 5.0 SUMMARY-116 + CCMREQ 1AA0 DECK-ID N42 MSOS 5.0 SUMMARY-116 + CMMREQ 1BC0 DECK-ID N43 MSOS 5.0 SUMMARY-116 + MMMREQ 1CE0 DECK-ID N44 MSOS 5.0 SUMMARY-116 + LICREQ 1E60 DECK-ID N45 MSOS 5.0 SUMMARY-118 + LIOREQ 1FE0 DECK-ID N46 MSOS 5.0 SUMMARY-116 + LAMREQ 2160 DECK-ID N47 MSOS 5.0 SUMMARY-116 + DDPREQ 2280 DECK-ID N48 MSOS 5.0 SUMMARY-116 + LDPREQ 23A0 DECK-ID N49 MSOS 5.0 SUMMARY-116 + LDOREQ 24C0 DECK-ID N50 MSOS 5.0 SUMMARY-116 + DMDREQ 2640 DECK-ID N51 MSOS 5.0 SUMMARY-116 + WDKREQ 27C0 DECK-ID N52 MSOS 5.0 SUMMARY-116 + LSTREQ 28E0 DECK-ID N53 MSOS 5.0 SUMMARY-116 + PRINT 2A60 DECK-ID N54 MSOS 5.0 SUMMARY-116 + GETFLD 2B20 DECK-ID N55 MSOS 5.0 SUMMARY-110 + ASHX 2BE0 DECK-ID N56 MSOS 5.0 SUMMARY-110 + DMPBUF 2C40 DECK-ID N57 MSOS 5.0 SUMMARY-116 + ASCDEC 2D00 DECK-ID N58 MSOS 5.0 SUMMARY-110 + HXAS 2D60 DECK-ID N59 MSOS 5.0 SUMMARY-110 + DECDMP 2DC0 DECK-ID N60 MSOS 5.0 SUMMARY-116 + FETMM 2E80 DECK-ID N61 MSOS 5.0 SUMMARY-132 + PNTMD 2FA0 DECK-ID N62 MSOS 5.0 SUMMARY-116 + MASOT 3060 DECK-ID N63 MSOS 5.0 SUMMARY-116 + CONFM 3180 DECK-ID N64 MSOS 5.0 SUMMARY-116 + GETINT 32A0 DECK-ID N65 MSOS 5.0 SUMMARY-116 + FLCVSG 3360 DECK-ID N66 MSOS 5.0 SUMMARY-116 + FLCVDB 3480 DECK-ID N67 MSOS 5.0 SUMMARY-116 + NAMEMS 35A0 DECK-ID N68 MSOS 5.0 SUMMARY-110 + DCONV 3A20 DECK-ID N69 MSOS 5.0 SUMMARY-110 + LAZY2 3B40 DECK-ID N70 MSOS 5.0 SUMMARY-110 + ODDFLT 3C00 DECK-ID N71 MSOS 5.0 SUMMARY-110 + ODDFTN 3CC0 DECK-ID N72 MSOS 5.0 SUMMARY-110 + ECONV 4080 DECK-ID N73 MSOS 5.0 SUMMARY-110 + LAZY1 4140 DECK-ID N74 MSOS 5.0 SUMMARY-110 + ODFLOT 4200 DECK-ID N75 MSOS 5.0 SUMMARY-110 + ODFXFL 4440 DECK-ID N76 MSOS 5.0 SUMMARY-110 + *M SYSCOP 19 + SYSCOP 0384 DECK-ID N77 MSOS 5.0 SUMMARY-110 + *M SYSSEG 20 + CO1ST 0389 DECK-ID N78 MSOS 5.0 SUMMARY-110 + CO2ND 04E0 DECK-ID N79 MSOS 5.0 SUMMARY-136 + CO3RD 0D80 DECK-ID N80 MSOS 5.0 SUMMARY-110 + COLAST 1500 DECK-ID N81 MSOS 5.0 SUMMARY-110 + *M MIPRO 21 + MIPRO 03C2 DECK-ID N06 MSOS 5.0 SUMMARY-110 + + * * * U N P A T C H E D E X T E R N A L S * * * + CRIMPT + INDACS + TSUTIL + *M TDFUNC 22 + TDFUNC 03C9 DECK-ID O67 MSOS 5.0 SUMMARY-110 + *M EFSTOR 23 + EFSTOR 03CD DECK-ID N04 MSOS 5.0 SUMMARY-110 + *M EFLIST 24 + EFLIST 03D2 DECK-ID N05 MSOS 5.0 SUMMARY-123 + *M VERIFY 25 + VERFY1 03F0 DECK-ID O24 MSOS 5.0 SUMMARY-110 + *M SCMM17 26 + SCMEXC 03F5 DECK-ID O54 MSOS 5.0 SUMMARY-117 + *M DUMMY1 27 + *M DUMMY2 28 + *M DUMMY3 29 + *M DUMMY4 30 + *M DUMMY5 31 + *M DUMMY6 32 + *M DUMMY7 33 + *M DUMMY8 34 + *M DUMMY9 35 + *M DUMMY0 36 + * + * MASS RESIDENT DRIVERS + * + *M COSY DRIVER + DCOSY 0405 DECK-ID M34 MSOS 5.0 SUMMARY-114 + NXTLOC 02F0 NEXT AVAILABLE LOCATION + *S,SCOSY,S + *S,LCOSY,P + *M 1732-3 616 MAG TAPE + D17323 040D DECK-ID B33 PERIPH. DRIVERS 1.0B SUMMARY-114 + D327TR 0442 DECK-ID B36 PERIPH. DRIVERS 1.0B SUMMARY-106 + NXTLOC 0565 NEXT AVAILABLE LOCATION + *S,S17323,S + *S,L17323,P + *M 1732-3 616 MAG TAPE LONG RECORD + D7323L 041C DECK-ID B38 PERIPH. DRIVERS 1.0B SUMMARY-114 + NXTLOC 0539 NEXT AVAILABLE LOCATION + *S,S7323L,S + *S,L7323L,P + *M PSEUDO TAPE + DPSUDO 042A DECK-ID F04 FILE MANAGER SUMMARY-114 + NXTLOC 03CD NEXT AVAILABLE LOCATION + *S,SPSUDO,S + *S,LPSUDO,P + *M 1742-30/120 LINE PRINTER + D42312 0435 DECK-ID B37 PERIPH. DRIVERS 1.0B SUMMARY-132 + T5954 01AF DECK-ID B77 PERIPH. DRIVERS 1.0B SUMMARY-112 + NXTLOC 023F NEXT AVAILABLE LOCATION + *S,S42312,S + *S,L42312,P + *M 1728-430 CARD READER/PUNCH - 026 FORMAT + D1728 043B DECK-ID A30 PERIPH. DRIVERS 1.0A SUMMARY-106 + CR026 03A2 DECK-ID A16 PERIPH. DRIVERS 1.0A SUMMARY-106 + CP026 03C2 DECK-ID A14 PERIPH. DRIVERS 1.0A SUMMARY-106 + NXTLOC 0402 NEXT AVAILABLE LOCATION + *S,S1728,S + *S,L1728,P + *M 1728-430 CARD READER/PUNCH - 029 FORMAT + D1728 0446 DECK-ID A30 PERIPH. DRIVERS 1.0A SUMMARY-106 + CR029 03A2 DECK-ID A17 PERIPH. DRIVERS 1.0A SUMMARY-106 + CP029 03C2 DECK-ID A15 PERIPH. DRIVERS 1.0A SUMMARY-106 + NXTLOC 0402 NEXT AVAILABLE LOCATION + * + * MASS RESIDENT FILE MANAGER + * + *M + DEFFIL 0451 DECK-ID F05 FILE MANAGER SUMMARY-116 + FILSPC 0186 DECK-ID F06 FILE MANAGER SUMMARY-110 + RPEND 0233 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP01,S + *M + RELFIL 0457 DECK-ID F08 FILE MANAGER SUMMARY-110 + RELSPC 0092 DECK-ID F09 FILE MANAGER SUMMARY-110 + RPEND 0141 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP02,S + *M + DEFIDX 045B DECK-ID F10 FILE MANAGER SUMMARY-110 + SQRTFM 0098 DECK-ID F11 FILE MANAGER SUMMARY-110 + FILSPC 00A8 DECK-ID F06 FILE MANAGER SUMMARY-110 + RPEND 0155 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP03,S + *M + LOKFIL 045F DECK-ID F12 FILE MANAGER SUMMARY-110 + RPEND 004D DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP04,S + *M + UNLFIL 0460 DECK-ID F13 FILE MANAGER SUMMARY-110 + RPEND 003D DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP05,S + *M + STOSEQ 0461 DECK-ID F14 FILE MANAGER SUMMARY-110 + FILSPC 00C6 DECK-ID F06 FILE MANAGER SUMMARY-110 + RPEND 0173 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP06,S + *M + STODIR 0465 DECK-ID F15 FILE MANAGER SUMMARY-110 + RPEND 008D DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP07,S + *M + STOIDX 0467 DECK-ID F20 FILE MANAGER SUMMARY-110 + HASHCD 0341 DECK-ID F16 FILE MANAGER SUMMARY-110 + GETKID 034C DECK-ID F17 FILE MANAGER SUMMARY-110 + FILSPC 0373 DECK-ID F06 FILE MANAGER SUMMARY-110 + RPEND 0420 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP08,S + *M + EDFDMY 0472 DECK-ID F27 FILE MANAGER SUMMARY-116 + FILSPC 0472 DECK-ID F06 FILE MANAGER SUMMARY-110 + RPEND 00AF DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMREDF,S + *M + EDRDMY 0474 DECK-ID F28 FILE MANAGER SUMMARY-116 + RTNSPC 0474 DECK-ID F19 FILE MANAGER SUMMARY-110 + RPEND 00A2 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMREDR,S + *M + RTVSEQ 0476 DECK-ID F18 FILE MANAGER SUMMARY-110 + RTNSPC 018B DECK-ID F19 FILE MANAGER SUMMARY-110 + RPEND 022B DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP09,S + *M + RTVDIR 047C DECK-ID F21 FILE MANAGER SUMMARY-110 + RTNSPC 0115 DECK-ID F19 FILE MANAGER SUMMARY-110 + RPEND 01B5 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP10,S + *M + RTVIDX 0481 DECK-ID F22 FILE MANAGER SUMMARY-110 + HASHCD 0258 DECK-ID F16 FILE MANAGER SUMMARY-110 + GETKID 0263 DECK-ID F17 FILE MANAGER SUMMARY-110 + RTNSPC 028A DECK-ID F19 FILE MANAGER SUMMARY-110 + RPEND 032A DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP11,S + *M + RTVIDO 048A DECK-ID F23 FILE MANAGER SUMMARY-118 + GETKID 0260 DECK-ID F17 FILE MANAGER SUMMARY-110 + RTNSPC 0287 DECK-ID F19 FILE MANAGER SUMMARY-110 + RPEND 0327 DECK-ID F07 FILE MANAGER SUMMARY-110 + *S,FMRP12,S + *M + FMDUMY 0493 DECK-ID F24 FILE MANAGER SUMMARY-110 + *S,FMREND,S + *S,JFILV4,S SPECIFY THE JOB FILE TABLE SPACE + *M,JFILV4+$40 + *T END OF SYSTEM + + * * * U N P A T C H E D E X T E R N A L S * * * + PARITY + POWERU + INITIALIZATION COMPLETED - YOU MAY AUTOLOAD + Simulation stopped, P: 5914 ( 18FF JMP* $FF) + sim> autoload cd + sim> run 0 + + + + MSOS 5.0--PSR LEVEL 120 01/26/82 + + + SET PROGRAM PROTECT + + Simulation stopped, P: 1E13 ( 4CF0 STQ* ($F0)) + sim> set cpu protect + sim> c + + + EXXON DEVELOPMENT SYSTEM + + + 65K MODE + + + CHECKING FILES - OK + + + ENTER DATE/TIME MMDDYYHHMM + + 0714881208 <============================ Enter date/time + DATE: 14 JUL 88 TIME: 1208:00 + MI <==================================== ^G (Control + G) for + manual interrupt + *BATCH <================================ Start batch subsystem + *CTO, MSOS 5.0 INSTALLATION COMPLETED - YOU MAY AUTOLOAD + Simulation stopped, P: 8592 (P 18FB JMP* $FB) + sim> q + Goodbye + + +Output from the batch jobs is sent to the line printer. diff --git a/CDC1700/CDC1700.txt b/CDC1700/CDC1700.txt new file mode 100644 index 00000000..6af4551e --- /dev/null +++ b/CDC1700/CDC1700.txt @@ -0,0 +1,198 @@ +CDC 1700 Simulator +================== + +1. Overview. + + The CDC 1700 was a 16-bit mini-computer produced by the Control Data +Corporation with deliveries beginning in May 1966. + + +2. Hardware + + This simulator provides support for a subset of available peripherals, +mostly from the early period of its release: + + device name Hardware + + CPU 1714 CPU with up to 32KW of memory in 4KW increments + 1705 Interrupt/Data channel option + + Optional extensions: + + 64KW of memory with indirect addressing changes + Character addressing option + + RTC 10336-1 Real-time clock + Default tick interval is 10mSec, can be changed with + "set rtc rate=" command, where is + {1usec|10usec|100usec|1msec|10msec|100msec|1second}. + [ Equipment address: 0xD] + + DCA Data channels providing DMA access to non-DMA + DCB peripherals. The configuration is currently fixed + DCC with DCA providing access to the magtape controller + if it is configured as a 1732-A. + + TTI 1711-A Half-duplex console terminal. + TTO [Equipment address: 0x1, Station address: 1] + + PTR 1721-A Paper tape reader + [Equipment address: 0x1, Station address: 2] + + PTP 1723-A Paper tape punch + [Equipment address: 0x1, Station address: 4] + + MT 1732-A or 1732-3 Magtape controller, 3 9-track drives + (MT0 - MT2) and 1 7-track drive (MT3). The type of + the controller may be chaged using the command + "set mt type={1732-a|1732-3}". The 1732-A controller + supports 200, 556 and 800 BPI on the 9 track drives + and allows DMA access via a 1706 Buffered Data + Channel (device DCA). The 1733-3 controller supports + 556, 800 and 1600 BPI on the 9 track drives and + includes DSA (DMA) access built into the controller. + Access via a 1706 Buffered Data Channel is not + available. + [Equipment address: 0x7] + + LP 1740 or 1742-30 Line printer (Upper case only). The + type of the printer may be changed with the command + "set lp type={1740|1742}; The 1742-30 is compatible + with the 1742-120 but not allow the print train + configuration to be loaded by software. + [Equipment address: 0x4] + + DP 1738-B Disk pack controller. Supports up to 2 + disk pack drives: + + 853 drive - 3072000 bytes + 854 drive - 6236160 bytes + + [Equipment address: 0x3] + + CD 1733-2 Cartridge disk pack controller. Supports up to + 4 cartridge disk drives. Each drive supports a fixed + and a removeable disk: + + 856-2 drive - 2260608 bytes + 856-4 drive - 4543488 bytes + + [Equipment address: 0x3] + +Notes: + DP and CD use the same equipment address so only 1 of them may be + enabled (default is for CD to be enabled). + + Both controllers use checkwords (CRC) for validating the consistency + of the data stored on disk. The DP controller exports a single status + bit indicating whether the checkword is valid or not (the simulator + always sets this to valid). The CD controller also exports a single + status bit which is handled in the same way as the DP controller. + It also makes the actual checkword value available. The simulator + always returns a checkword value of 0 so any attempt to verify the + checkword (such as some diagnostics) will fail. + + +3. Loading Software + + The simulator implements 2 mechanisms for loading software which +follow the hardware mechanisms provided by the real hardware: + + - Magtape bootstraps toggled in through the front panel + - Autoload mechanism for disk drives + +3.1 Magtape bootstraps + + Software may be loaded from a magtape drive by using the SIMH command +"boot mtx" (currently only drive 0 is supported) which loads an appropriate +bootstrap into memory. This bootstrap loads the next record from the tape and +starts execution of the loaded code. + +3.1.1 Default bootstrap + + The default bootstrap is used for loading operating system distribution +tapes and is loaded at location 0. Register A is set according to the amount +os memory installed in the system: + + - 0x5000 if 32KW or greater is installed + - 0x4000 if 24KW or 28KW is installed + - 0x2000 if less than 24KW is installed + +3.1.2 SMM17 bootstrap + + SMM17 is the System Maintenance Monitor and uses a tailored bootstrap +which is loaded into the highest addressed 4KW module installed in the system. +The following switches may be used with the "boot" command for special +handling: + + -S load the SMM17 bootstrap rather than the default bootstrap + -D configure the SMM17 bootstrap to use Buffered Data Channel #1 + to access the magtape controller + +3.2 Disk autoload + + The autoload mechanism loads track 0 from logical drive 0 of a disk +controller to memory location 0, it does not start execution of the loaded +code (some of the diagnostics code relies on this). Both DP and CD controllers +support the autoload command ("autoload dp" or "autoload cd") which is +normally followed by a "run 0" command to start execution of the loaded code. + +4. Operating The Simulator + +4.1 CPU + + The control panel of a physical CDC 1700 system has a number of +switches which control the operation of the CPU. In the simulator, these +switches can be turned on and off with the commands "set cpu xxx" and +"set cpu noxxx". By default, all the switches are off. + +4.1.1 Selective Stop (set cpu sstop/nosstop) + + The Selective Stop switch controls how the "Selective Stop (SLS)" +instruction executes. If the switch is Off, SLS executes as a no-operation. +If the switch is On, SLS executes as a halt instruction. Continuing after +the halt causes the CPU to execute the instruction following SLS. + +4.1.2 Selective Skip (set cpu sskip/nosskip) + + The Selective Skip switch controls how the SWS and SWN skip +instruction execute. SWS will skip if the switch is set and SWN will skip if +the switch is not set. Both the Selective Stop and Selective Skip switches +are used extensively to control execution of the System Maintenance Monitor. + +4.1.3 Protect (set cpu protect/noprotect) + + Each word of memory on the CDC 1700 series consists of 18-bits; 16-bits +of data/instructions, a parity bit (which is not implemented in the simulator) +and a program protect bit. If the Protect switch is Off, any program may +reference any word of memory. If the Protect switch is On, there are a set of +rules which control how memory accesses work and when to generate a program +protect violation - see one of the 1700 reference manuals on bitsavers.org for +exact details. + + The simulator fully implements CPU protect mode allowing protected +operating systems such as MSOS 5 to execute. It does not support peripheral +protect operation which allows unprotected programs to directly access +some unprotected peripherals. + + Operating systems and other programs which run with the Protect +switch on usually start up with the Protect switch off, manipulate the +protect bits in memory (using the CPB/SPB instructions) and then ask the +operator to set the Protect switch on. + +4.2 Teletype + + The console teletype on some of the earlier models of the CDC 1700 +series had a "Manual Interrupt" button so that the operator could get the +attention of the operating system. Later models used a display terminal and +used the "Control+G" key conbination to generate a Manual Interrupt. The +Simulator follows this convention of using the "Control+G" key combination. +The actual key combination may be changed by issuing the command +"deposit tti intrkey xx" where xx is desired key code in hex. + + +5. Software + + There is some software available at bitsavers.org in the +bits/CDC/1700_Cyber18 directory, see CDC1700_Diagnostics.txt and +CDC1700_MSOS.txt for more details. diff --git a/CDC1700/cdc1700_cd.c b/CDC1700/cdc1700_cd.c new file mode 100644 index 00000000..cf3e92bb --- /dev/null +++ b/CDC1700/cdc1700_cd.c @@ -0,0 +1,1327 @@ +/* + + 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_cd.c: cartridge disk drive controller support + * Simh devices: cd0, cd1, cd2, cd3, cd4, cd5, cd6, cd7 + */ +#include "cdc1700_defs.h" + +#define CYLADRSTATUS iod_readR[2] +#define CWA iod_readR[3] +#define CWSTATUS iod_readR[4] +#define DCYLSTATUS iod_readR[5] +#define BUFLEN iod_buflen + +extern char INTprefix[]; + +extern void RaiseExternalInterrupt(DEVICE *); + +extern t_bool doDirectorFunc(DEVICE *, t_bool); +extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); +extern void fw_IOunderwayEOP2(IO_DEVICE *, uint16); +extern void fw_IOcompleteEOP2(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_IOalarm(t_bool, DEVICE *, IO_DEVICE *, const char *); +extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); + +extern void rebuildPending(void); + +extern t_stat checkReset(DEVICE *, uint8); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_protected(UNIT *, int32, CONST char *, void *); +extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); + +extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); + +extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); +extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); + +extern uint16 LoadFromMem(uint16); +extern t_bool IOStoreToMem(uint16, uint16, t_bool); + +extern uint16 M[], Areg, IOAreg; + +extern t_bool IOFWinitialized; + +extern t_bool ExecutionStarted; + +extern UNIT cpu_unit; + +static t_stat show_drive(FILE *, UNIT *, int32, CONST void *); + +t_stat set_cd856_2(UNIT *, int32, CONST char *, void *); +t_stat set_cd856_4(UNIT *, int32, CONST char *, void *); + +static t_stat show_addressing(FILE *, UNIT *, int32, CONST void *); + +t_stat set_cartfirst(UNIT *, int32, CONST char *, void *); +t_stat set_fixedfirst(UNIT *, int32, CONST char *, void *); + +/* Constants */ + +#define CD_NUMWD (96) /* words/sector */ +#define CD_NUMBY (CD_NUMWD * sizeof(uint16)) +#define CD_NUMSC (29) /* sectors/cylinder */ +#define CD_856_2CY (203) /* cylinders for 856-2 drive */ +#define CD_856_4CY (406) /* cylinders for 856-4 drive */ +#define CD_SURF (2) /* # of surfaces */ +#define CD856_2_SIZE (CD_SURF * CD_856_2CY * CD_NUMSC * CD_NUMBY) +#define CD856_4_SIZE (CD_SURF * CD_856_4CY * CD_NUMSC * CD_NUMBY) + +#define CDLBA(i) \ + ((i->surface * i->maxcylinder * CD_NUMSC) + (i->cylinder * CD_NUMSC) + i->sector) + +/* + * Disk address fields + */ +#define CD_CYL_MASK 0xFF80 /* Cylinder address mask */ +#define CD_CYL_SHIFT 7 /* and shift */ +#define CD_SURF_MASK 0x0040 /* Surface mask */ +#define CD_SURF_SHIFT 6 /* and shift */ +#define CD_DISK_MASK 0x0020 /* Disk mask */ +#define CD_DISK_SHIFT 5 /* and shift */ +#define CD_SECTOR_MASK 0x001F /* Sector mask */ + +#define CD_CHECKWD_MASK 0x0FFF /* Checkword value mask */ + +#define CD_SEEK_COMP0 0x0001 /* Drive 0 seek complete */ +#define CD_SEEK_COMP1 0x0002 /* Drive 1 seek complete */ +#define CD_SEEK_COMP2 0x0004 /* Drive 2 seek complete */ +#define CD_SEEK_COMP3 0x0008 /* Drive 3 seek complete */ +#define CD_SEEK_COMP \ + (CD_SEEK_COMP0 | CD_SEEK_COMP1 | CD_SEEK_COMP2 | CD_SEEK_COMP3) + +#define CD_NUMDR 4 /* # drives */ + +struct cdio_unit { + char name[4]; /* Drive name */ + uint16 state; /* Current status of the drive */ +#define CD_IDLE 0x0000 /* Idle */ +#define CD_SEEK 0x0001 /* Seeking */ +#define CD_WRITE 0x0002 /* Write data */ +#define CD_READ 0x0003 /* Read data */ +#define CD_COMPARE 0x0004 /* Compare data */ +#define CD_CHECKWORD 0x0005 /* Checkword check (NOOP) */ +#define CD_WRITEADDR 0x0006 /* Write address */ +#define CD_RTZS 0x0007 /* Return to zero seek */ + uint16 buf[CD_NUMWD]; /* Sector buffer */ + uint16 maxcylinder; /* Max cylinder # */ + uint16 cylinder; /* Current cylinder */ + uint16 sector; /* Current sector */ + uint8 surface; /* Current surface */ + uint8 disk; /* Current physical disk */ + uint8 requested; /* Current requested disk */ +#define CD_NONE 0xFF + uint16 sectorAddr; /* Current sector address */ + UNIT *ondrive[2]; /* Units which are part of drive */ + UNIT *active; /* Currently active unit */ + uint16 seekComplete; /* Drive seek complete mask */ + t_bool oncyl; /* Unit on-cylinder status */ + t_bool busy; /* Drive busy status */ +} CDunits[CD_NUMDR]; + +enum cdio_status { + CDIO_MORE, /* More I/O pending */ + CDIO_DONE, /* I/O processing completed */ + CDIO_PROTECT, /* Protect fault */ + CDIO_MISMATCH, /* Compare mismatch */ + CDIO_ADDRERR /* Addressing error */ +}; + +t_stat cd_svc(UNIT *); +t_stat cd_reset(DEVICE *); +t_stat cd_attach(UNIT *, CONST char *); +t_stat cd_detach(UNIT *); + +void CDstate(const char *, DEVICE *, IO_DEVICE *); +t_bool CDreject(IO_DEVICE *, t_bool, uint8); +enum IOstatus CDin(IO_DEVICE *, uint8); +enum IOstatus CDout(IO_DEVICE *, uint8); +t_bool CDintr(IO_DEVICE *); + +/* + 1733-2 Cartridge Disk Drive Controller + + Addresses + Computer Instruction + Q Register Output From A Input to A + (Bits 02-00) + + 000 Load Buffer Clear Controller + 001 Director Function Director Status + 010 Load Address Cylinder Address Status + 011 Write Current Word Address Status + 100 Read Checkword Status + 101 Compare Drive Cylinder Status + 110 Checkword Check Illegal + 111 Write Address Illegal + + Operations: + + Load Buffer + + 15 14 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | + +-----------------------------------------------------------+ + Buffer Length + + Director Function + + 15 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | | | | | X | X | | | | | X | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | + +---+ | | | | | Clr Interrupts + | | | | | Ready and not Busy + | | | | | Interrupt Req. + | | | | EOP Interrupt Req. + | | | Interrupt on Alarm + | | Unit de-select + | Unit Select + Unit Select Code + + Load Address, Checkword Check, Write Address or Cylinder Address Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + +-------------------------------+ | | +---------------+ + Cylinder | | Sector + 856-2: 0-202 | | 0-28 + 856-4: 0-405 | Disk + Surface (0 - top, 1 - bottom) + + Write, Read or Compare + + 15 14 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | + +-----------------------------------------------------------+ + FWA + + Status Response: + + Director Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | | Ready + | | | | | | | | | | | | | | Busy + | | | | | | | | | | | | | Interrupt + | | | | | | | | | | | | On Cylinder + | | | | | | | | | | | End of Operation + | | | | | | | | | | Alarm + | | | | | | | | | No Compare + | | | | | | | | Protected + | | | | | | | Checkword Error + | | | | | | Lost Data + | | | | | Address Error + | | | | Controller Seek Error + | | | Single Density + | | Storage Parity Error + | Protect Fault + Drive Seek Error + + + Cylinder Address Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + +-------------------------------+ | | +---------------+ + Cylinder | | Sector + 856-2: 0-202 | | 0-28 + 856-4: 0-405 | Disk + Surface (0 - top, 1 - bottom) + + Checkword Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 0 | 0 | 0 | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | + +-------------------------------------------+ + Checkword from last sector operated on + + + Drive Cylinder Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | X | X | X | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + +-------------------------------+ | | | Seek Compl. 0 + True Cylinder Address | | Seek Compl. 1 + | Seek Compl. 2 + Seek Compl. 3 + */ + +IO_DEVICE CDdev = IODEV(NULL, "1733-2", 1733, 3, 0xFF, 0, + CDreject, CDin, CDout, NULL, NULL, + CDstate, CDintr, NULL, NULL, + 0x7F, 8, + MASK_REGISTER0 | MASK_REGISTER1 | MASK_REGISTER2 | \ + MASK_REGISTER3 | MASK_REGISTER4 | MASK_REGISTER5 | \ + MASK_REGISTER6 | MASK_REGISTER7, + MASK_REGISTER1 | MASK_REGISTER2 | MASK_REGISTER3 | \ + MASK_REGISTER4 | MASK_REGISTER5, + MASK_REGISTER6 | MASK_REGISTER7, 0, + 0, 0, CDunits); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_drive iod_private2 /* Currently selected drive */ +#define iod_buflen iod_private3 /* Remaining buffer length */ + +/* CD data structures + + cd_dev CD device descriptor + cd_unit CD units + cd_reg CD register list + cd_mod CD modifier list +*/ + +UNIT cd_unit[] = { + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[0], &cd_unit[1] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[0], &cd_unit[0] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[1], &cd_unit[3] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[1], &cd_unit[2] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[2], &cd_unit[5] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[2], &cd_unit[4] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[3], &cd_unit[7] + }, + { UDATA(&cd_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_856_4, CD856_4_SIZE), + 0, 0, 0, 0, 0, &CDunits[3], &cd_unit[6] + } +}; + +REG cd_reg[] = { + { HRDATA(FUNCTION, CDdev.FUNCTION, 16) }, + { HRDATA(STATUS, CDdev.STATUS, 16) }, + { HRDATA(IENABLE, CDdev.IENABLE, 16) }, + /*** more ***/ + { NULL } +}; + +MTAB cd_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "1733-2", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "EQUIPMENT", &set_equipment, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, "DRIVE", NULL, NULL, &show_drive, NULL }, + { MTAB_XTD|MTAB_VUN, 0, NULL, "856-2", &set_cd856_2, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, NULL, "856-4", &set_cd856_4, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + /*** should protect be per-unit? ***/ + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "ADDRESSING", NULL, NULL, &show_addressing, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "CARTFIRST", &set_cartfirst, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "FIXEDFIRST", &set_fixedfirst, NULL, NULL }, + { 0 } +}; + +DEBTAB cd_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "ERROR", DBG_DERROR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DERROR | DBG_DLOC }, + { NULL } +}; + +DEVICE cd_dev = { + "CD", cd_unit, cd_reg, cd_mod, + CD_NUMDR * 2, 10, 31, 1, 8, 8, + NULL, NULL, &cd_reset, + NULL, &cd_attach, &cd_detach, + &CDdev, + DEV_DEBUG | DEV_DISK | DEV_DISABLE | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT, + 0, cd_deb, + NULL, + NULL +}; + +/* + * Display cartridge drive type + */ +static t_stat show_drive(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + int32 fixed, u; + + if (uptr == NULL) + return SCPE_IERR; + + u = uptr - cd_dev.units; + fixed = u & 0x01; + if ((cd_dev.flags & DEV_FIXED) != 0) + fixed ^= 0x01; + + fprintf(st, "drive %u, %s, %s", u >> 1, + ((uptr->flags & UNIT_856_4) != 0) ? "856-4" : "856-2", + (fixed != 0) ? "Fixed" : "Cartridge"); + return SCPE_OK; +} + +/* + * Set drive type to 856-2. If execution has started, disallow device type + * changes. Note that the drive contains 2 physical disks and they must + * both be changed together. + */ +t_stat set_cd856_2(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + UNIT *uptr2; + + if (uptr == NULL) + return SCPE_IERR; + + uptr2 = (UNIT *)uptr->up8; + + if ((uptr->flags & UNIT_856_4) != 0) { + if (((uptr->flags & UNIT_ATT) != 0) || ((uptr2->flags & UNIT_ATT) != 0)) + return SCPE_ALATT; + + if (ExecutionStarted) + return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); + + uptr->flags &= ~UNIT_856_4; + uptr->capac = CD856_2_SIZE; + uptr2->flags &= ~UNIT_856_4; + uptr2->capac = CD856_2_SIZE; + } + return SCPE_OK; +} + +/* + * Set drive type to 856-4. If execution has started, disallow device type + * changes. Note that the drive contains 2 physical disks and they must + * both be changed together. + */ +t_stat set_cd856_4(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + UNIT *uptr2; + + if (uptr == NULL) + return SCPE_IERR; + + uptr2 = (UNIT *)uptr->up8; + + if ((uptr->flags & UNIT_856_4) == 0) { + if (((uptr->flags & UNIT_ATT) != 0) || ((uptr2->flags & UNIT_ATT) != 0)) + return SCPE_ALATT; + + if (ExecutionStarted) + return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); + + uptr->flags |= UNIT_856_4; + uptr->capac = CD856_4_SIZE; + uptr2->flags |= UNIT_856_4; + uptr2->capac = CD856_4_SIZE; + } + return SCPE_OK; +} + +/* + * Display the device addressing mode + */ +static t_stat show_addressing(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if ((cd_dev.flags & DEV_FIXED) == 0) + fprintf(st, "Addressing: Cartridge first"); + else fprintf(st, "Addressing: Fixed first"); + return SCPE_OK; +} + +/* + * Set device to "Cartridge first" addressing + */ +t_stat set_cartfirst(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + cd_dev.flags &= ~DEV_FIXED; + return SCPE_OK; +} + +/* + * Set device to "Fixed first" addressing + */ +t_stat set_fixedfirst(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + cd_dev.flags |= DEV_FIXED; + return SCPE_OK; +} + +/* + * Dump the current internal state of the CD device. + */ +const char *CDstateStr[] = { + "Idle", "Seek", "Write", "Read", "Compare", "Checkword", "WriteAddr", "RTZS" +}; + +void CDstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + struct cdio_unit *iou = (struct cdio_unit *)iod->iod_drive; + int fixed, i; + const char *active = "None"; + + fixed = ((cd_dev.flags & DEV_FIXED) != 0) ? 0 : 1; + + if (iou != NULL) { + if (iou->active != NULL) { + if (iou->active == iou->ondrive[0]) + active = "0"; + if (iou->active == iou->ondrive[1]) + active = "1"; + } + } + + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Sel: %s,%s]\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->STATUS, iod->IENABLE, + iou != NULL ? iou->name : "None", active); + fprintf(DBGOUT, + "%s[%s: CAS: %04X, CWA: %04X, CWS: %04X, DCS: %04X, LEN: %04X]\r\n", + INTprefix, dev->name, + iod->CYLADRSTATUS, iod->CWA, iod->CWSTATUS, + iod->DCYLSTATUS, iod->BUFLEN); + + for (i = 0; i < CD_NUMDR; i++) { + UNIT *uptr = &cd_unit[i * 2]; + UNIT *uptr2 = &cd_unit[(i * 2) + 1]; + + iou = &CDunits[i]; + + if (((uptr->flags & UNIT_ATT) != 0) || ((uptr2->flags & UNIT_ATT) != 0)) { + /*** more to print ***/ + fprintf(DBGOUT, + "%s[%d: State: %s, OnCyl: %s, Busy: %s]\r\n", + INTprefix, i, CDstateStr[iou->state], + iou->oncyl ? "Yes" : "No", + iou->busy ? "Yes" : "No"); + if ((uptr->flags & UNIT_ATT) != 0) + fprintf(DBGOUT, + "%s %s attached\r\n", INTprefix, + fixed == 0 ? "Fixed" : "Cartridge"); + if ((uptr2->flags & UNIT_ATT) != 0) + fprintf(DBGOUT, + "%s %s attached\r\n", INTprefix, + fixed == 1 ? "Fixed" : "Cartridge"); + } + } +} + +/* + * Determine if a non-standard interrupt condition is present. + */ +t_bool CDintr(IO_DEVICE *iod) +{ + return (ISENABLED(iod, IO_1733_RBINT) && + ((DEVSTATUS(iod) & (IO_ST_READY | IO_ST_BUSY)) == IO_ST_READY)); +} + +/* + * Load and validate disk address in the A register + */ +static t_bool LoadDiskAddress(UNIT *uptr, struct cdio_unit *iou, uint16 state) +{ + uint16 numcy = ((uptr->flags & UNIT_856_4) != 0) ? CD_856_4CY : CD_856_2CY; + uint16 current = iou->cylinder; + + /* + * Abort immediately if the disk address is invalid + */ + if ((((IOAreg & CD_CYL_MASK) >> CD_CYL_SHIFT) >= numcy) || + ((IOAreg & CD_SECTOR_MASK) >= CD_NUMSC)) + return FALSE; + + CDdev.CYLADRSTATUS = iou->sectorAddr = IOAreg; + + iou->maxcylinder = numcy; + + /* + * Split the address into separate fields. + */ + iou->cylinder = (IOAreg & CD_CYL_MASK) >> CD_CYL_SHIFT; + iou->sector = IOAreg & CD_SECTOR_MASK; + iou->surface = (IOAreg & CD_SURF_MASK) >> CD_SURF_SHIFT; + iou->disk = iou->requested = (IOAreg & CD_DISK_MASK) >> CD_DISK_SHIFT; + if ((cd_dev.flags & DEV_FIXED) != 0) + iou->disk ^= 0x01; + + iou->active = iou->ondrive[iou->disk]; + + CDdev.DCYLSTATUS &= ~iou->seekComplete; + + /* + * This optimization is undocumented but is inferred from the MSOS + * device driver. + */ + if (ISENABLED(&CDdev, IO_DIR_EOP)) { + /* + * If we are already at the requested cylinder, bypass the seek and leave + * on-cylinder status set. + */ + if (iou->cylinder == current) { + CDdev.STATUS |= IO_1733_ONCYL; + iou->oncyl = TRUE; + return TRUE; + } + } + + CDdev.STATUS &= ~IO_1733_ONCYL; + + iou->busy = TRUE; + iou->oncyl = FALSE; + iou->state = state; + return TRUE; +} + +/* + * Set up a disk I/O operation with the A register containing FWA. + */ +static void StartCDDiskIO(UNIT *uptr, struct cdio_unit *iou, uint16 state) +{ + CDdev.CWA = IOAreg; + + CDdev.STATUS &= IO_ST_READY | IO_1733_ONCYL | IO_ST_PROT | IO_1733_SINGLE; + + fw_IOunderwayEOP2(&CDdev, 0); + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sCD - Start I/O, cur: %04X, len: %04X, state: %s\r\n", + INTprefix, CDdev.CWA, CDdev.BUFLEN, CDstateStr[state]); + + CDdev.DCYLSTATUS &= ~iou->seekComplete; + + iou->state = state; + sim_activate(uptr, CD_IO_WAIT); +} + +/* + * Increment sector # and update sector address. Note that I/O occurs on + * side 0 followed by side 1 before moving to the next cylinder. + */ +void CDDiskIOIncSector(struct cdio_unit *iou) +{ + if (iou->disk != CD_NONE) { + if (++iou->sector >= CD_NUMSC) { + iou->sector = 0; + iou->surface ^= 1; + if (iou->surface == 0) + iou->cylinder++; + } + iou->sectorAddr = + ((iou->cylinder << CD_CYL_SHIFT) | (iou->surface << CD_SURF_SHIFT) | + (iou->disk << CD_DISK_SHIFT)) | iou->sector; + CDdev.CYLADRSTATUS = iou->sectorAddr; + } +} + +/* + * Initiate a read operation on a disk. + */ +static enum cdio_status CDDiskIORead(UNIT *uptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + uint32 lba = CDLBA(iou); + int i; + + if (iou->cylinder >= iou->maxcylinder) + return CDIO_ADDRERR; + + CDdev.DCYLSTATUS &= ~CD_CYL_MASK; + CDdev.DCYLSTATUS |= (iou->cylinder << CD_CYL_SHIFT); + + sim_fseeko(uptr->fileref, lba * CD_NUMBY, SEEK_SET); + sim_fread(iou->buf, sizeof(uint16), CD_NUMWD, uptr->fileref); + + for (i = 0; i < CD_NUMWD; i++) { + /*** TODO: fix protect check ***/ + if (!IOStoreToMem(CDdev.CWA, iou->buf[i], TRUE)) + return CDIO_PROTECT; + + CDdev.CWA++; + if (--CDdev.BUFLEN == 0) { + CDDiskIOIncSector(iou); + return CDIO_DONE; + } + } + CDDiskIOIncSector(iou); + return CDIO_MORE; +} + +/* + * Initiate a write operation on a disk. + */ +static enum cdio_status CDDiskIOWrite(UNIT *uptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + uint32 lba = CDLBA(iou); + t_bool fill = FALSE; + int i; + + if (iou->cylinder >= iou->maxcylinder) + return CDIO_ADDRERR; + + for (i = 0; i < CD_NUMWD; i++) { + if (!fill) { + iou->buf[i] = LoadFromMem(CDdev.CWA); + CDdev.CWA++; + if (--CDdev.BUFLEN == 0) + fill = TRUE; + } else iou->buf[i] = 0; + } + + CDdev.DCYLSTATUS &= ~CD_CYL_MASK; + CDdev.DCYLSTATUS |= (iou->cylinder << CD_CYL_SHIFT); + + sim_fseeko(uptr->fileref, lba * CD_NUMBY, SEEK_SET); + sim_fwrite(iou->buf, sizeof(uint16), CD_NUMWD, uptr->fileref); + CDDiskIOIncSector(iou); + return fill ? CDIO_DONE : CDIO_MORE; +} + +/* + * Initiate a compare operation on a disk. + */ +static enum cdio_status CDDiskIOCompare(UNIT *uptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + uint32 lba = CDLBA(iou); + int i; + + if (iou->cylinder >= iou->maxcylinder) + return CDIO_ADDRERR; + + CDdev.DCYLSTATUS &= ~CD_CYL_MASK; + CDdev.DCYLSTATUS |= (iou->cylinder << CD_CYL_SHIFT); + + sim_fseeko(uptr->fileref, lba * CD_NUMBY, SEEK_SET); + sim_fread(iou->buf, sizeof(uint16), CD_NUMWD, uptr->fileref); + + for (i = 0; i < CD_NUMWD; i++) { + if (iou->buf[i] != LoadFromMem(CDdev.CWA)) + return CDIO_MISMATCH; + + CDdev.CWA++; + if (--CDdev.BUFLEN == 0) { + CDDiskIOIncSector(iou); + return CDIO_DONE; + } + } + CDDiskIOIncSector(iou); + return CDIO_MORE; +} + +/* + * Perform read/write/compare sector operations from within the unit + * service routine. + */ +void CDDiskIO(UNIT *uptr, uint16 iotype) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + const char *error = "Unknown"; + enum cdio_status status; + + switch (iotype) { + case CD_WRITE: + status = CDDiskIOWrite(uptr); + break; + + case CD_READ: + status = CDDiskIORead(uptr); + break; + + case CD_COMPARE: + status = CDDiskIOCompare(uptr); + break; + } + + /* + * Update the drive cylinder and cylinder address status registers if + * the I/O was successful + */ + if ((status == CDIO_MORE) || (status == CDIO_DONE)) { + CDdev.CYLADRSTATUS = + (iou->cylinder << CD_CYL_SHIFT) | (iou->surface << CD_SURF_SHIFT) | + (iou->requested << CD_DISK_SHIFT) | iou->sector; + } + + switch (status) { + case CDIO_MORE: + sim_activate(uptr, CD_IO_WAIT); + break; + + case CDIO_PROTECT: + CDdev.STATUS |= IO_1733_SPROT; + error = "Protection Fault"; + goto err; + + case CDIO_ADDRERR: + CDdev.STATUS |= IO_1733_ADDRERR; + error = "Address Error"; + err: + iou->state = CD_IDLE; + + if ((cd_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sCD - ReadWrite/Compare failed - %s\r\n", + INTprefix, error); + + fw_IOalarm(FALSE, &cd_dev, &CDdev, "Alarm"); + break; + + case CDIO_MISMATCH: + CDdev.STATUS |= IO_1733_NOCOMP; + /* FALLTHROUGH */ + + case CDIO_DONE: + iou->state = CD_IDLE; + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sCD - Read/Write/Compare transfer complete\r\n", INTprefix); + + fw_IOcompleteEOP2(TRUE, &cd_dev, &CDdev, 0xFFFF, "Transfer complete"); + break; + } +} + +/* Unit service */ + +t_stat cd_svc(UNIT *uptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + const char *why; + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[CD: cd_svc() entry]\r\n", INTprefix); + if ((cd_dev.dctrl & DBG_DSTATE) != 0) + CDstate("svc_entry", &cd_dev, &CDdev); + } + + switch (iou->state) { + case CD_IDLE: + /* + * Unit is idle, nothing to do. + */ + break; + + case CD_RTZS: + why = "RTZS done"; + + iou->cylinder = 0; + iou->surface = 0; + iou->disk = (cd_dev.flags & DEV_FIXED) != 0 ? 1 : 0; + iou->sector = 0; + iou->sectorAddr = iou->disk << CD_DISK_SHIFT; + goto seekdone; + + case CD_SEEK: + why = "Seek complete"; + + seekdone: + iou->state = CD_IDLE; + iou->busy = FALSE; + iou->oncyl = TRUE; + + CDdev.DCYLSTATUS &= ~CD_CYL_MASK; + CDdev.DCYLSTATUS |= (iou->cylinder << CD_CYL_SHIFT) | iou->seekComplete; + + /* + * If this is the currently selected drive, update controller status + * and possibly generate an interrupt. + */ + if (CDdev.iod_drive == iou) { + CDdev.STATUS |= IO_1733_ONCYL; + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%sCD - %s\r\n", INTprefix, why); + + if ((CDdev.STATUS & IO_ST_BUSY) == 0) + fw_IOcompleteEOP2(FALSE, &cd_dev, &CDdev, 0xFFFF, why); + } + break; + + case CD_WRITE: + case CD_READ: + case CD_COMPARE: + CDDiskIO(uptr, iou->state); + break; + + case CD_WRITEADDR: + why = "Write Address"; + goto WriteAddrDone; + + case CD_CHECKWORD: + why = "Checkword Check"; + + WriteAddrDone: + iou->state = CD_IDLE; + iou->oncyl = TRUE; + iou->busy = FALSE; + + /* + * Set sector address to the start of this track. + */ + iou->sector = 0; + CDdev.CYLADRSTATUS = iou->sectorAddr = + (iou->cylinder << CD_CYL_SHIFT) | (iou->surface << CD_SURF_SHIFT) | + (iou->disk << CD_DISK_SHIFT) | iou->sector; + + CDdev.STATUS |= IO_ST_EOP | IO_1733_ONCYL; + CDdev.STATUS &= ~IO_ST_BUSY; + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%sCD - %s complete\r\n", INTprefix, why); + + fw_IOintr(TRUE, &cd_dev, &CDdev, 0, 0, 0xFFFF, why); + break; + } + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[CD: cd_svc() exit]\r\n", INTprefix); + if ((cd_dev.dctrl & DBG_DSTATE) != 0) + CDstate("svc_exit", &cd_dev, &CDdev); + } + return SCPE_OK; +} + +/* Reset routine */ + +static t_stat CDreset(DEVICE *dptr) +{ + struct cdio_unit *iou; + int i; + + DEVRESET(&CDdev); + + if ((cd_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "CD - Reset\r\n"); + + CDunits[0].ondrive[0] = &cd_unit[0]; + CDunits[0].ondrive[1] = &cd_unit[1]; + CDunits[1].ondrive[0] = &cd_unit[2]; + CDunits[1].ondrive[1] = &cd_unit[3]; + CDunits[2].ondrive[0] = &cd_unit[4]; + CDunits[2].ondrive[1] = &cd_unit[5]; + CDunits[3].ondrive[0] = &cd_unit[6]; + CDunits[3].ondrive[1] = &cd_unit[7]; + + for (i = 0; i < CD_NUMDR; i++) { + /* + * Cancel any I/O in progress + */ + sim_cancel(&cd_unit[i * 2]); + sim_cancel(&cd_unit[(i * 2) + 1]); + + CDunits[i].name[0] = '0' + i; + CDunits[i].name[1] = '\0'; + + CDunits[i].state = CD_IDLE; + CDunits[i].disk = CD_NONE; + CDunits[i].busy = FALSE; + CDunits[i].oncyl = FALSE; + if (((CDunits[i].ondrive[0]->flags & UNIT_ATT) != 0) || + ((CDunits[i].ondrive[1]->flags & UNIT_ATT) != 0)) + CDunits[i].oncyl = TRUE; + + CDunits[i].seekComplete = 1 << i; + } + + CDdev.STATUS = 0; + if ((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) + if (((iou->ondrive[0]->flags & UNIT_ATT) != 0) || + ((iou->ondrive[1]->flags & UNIT_ATT) != 0)) + CDdev.STATUS |= IO_ST_READY; + + CDdev.CYLADRSTATUS = + CDdev.CWA = + CDdev.CWSTATUS = + CDdev.DCYLSTATUS = + CDdev.BUFLEN = 0; + + return SCPE_OK; +} + +t_stat cd_reset(DEVICE *dptr) +{ + t_stat r; + + if (IOFWinitialized) + if ((dptr->flags & DEV_DIS) == 0) + if ((r = checkReset(dptr, CDdev.iod_equip) == SCPE_OK)) { + r = CDreset(dptr); + + /* + * Cancel any selected drive. + */ + CDdev.iod_drive = NULL; + } + return r; +} + + +/* Attach routine */ + +t_stat cd_attach(UNIT *uptr, CONST char *cptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + const char *drivetype = ((uptr->flags & UNIT_856_4) != 0) ? "856-4" : "856-2"; + t_addr capac = ((uptr->flags & UNIT_856_4) != 0) ? CD856_4_SIZE : CD856_2_SIZE; + t_stat r; + + uptr->capac = capac; + r = attach_unit(uptr, cptr); + if (r != SCPE_OK) + return r; + + /* + * If this is a newly created file, set the drive size appropriately. + */ + if (sim_fsize_ex(uptr->fileref) == 0) + sim_set_fsize(uptr->fileref, capac); + + if (sim_fsize_ex(uptr->fileref) != capac) { + detach_unit(uptr); + uptr->capac = capac; + return sim_messagef(SCPE_OPENERR, "Incorrect file size"); + } + /* + * Set unit to cylinder 0, surface 0, sector 0 and indicate not on-cylinder, + */ + iou->cylinder = 0; + iou->surface = 0; + iou->sector = 0; + iou->oncyl = FALSE; + + return SCPE_OK; +} + +/* Detach routine */ + +t_stat cd_detach(UNIT *uptr) +{ + struct cdio_unit *iou = (struct cdio_unit *)uptr->up7; + t_stat stat; + + sim_cancel(uptr); + stat = detach_unit(uptr); + + iou->oncyl = FALSE; + if (iou->disk != CD_NONE) + if (iou->ondrive[iou->disk] == uptr) + iou->disk = CD_NONE; + + return SCPE_OK; +} + +/* Check if I/O should be rejected */ + +t_bool CDreject(IO_DEVICE *iod, t_bool output, uint8 reg) +{ + struct cdio_unit *iou = (struct cdio_unit *)iod->iod_drive; + + if (output) { + switch (reg) { + /* + * Director function + */ + case 0x01: + /*** TODO: Check protect status ***/ + return (CDdev.STATUS & IO_ST_BUSY) != 0; + + /* + * Load Buffer/Write/Checkword Check/Write Address + */ + case 0x00: + case 0x03: + case 0x06: + case 0x07: + /*** TODO: Check protect status ***/ + /* FALLTHROUGH */ + + /* + * Load Address/Read/Compare + */ + case 0x02: + case 0x04: + case 0x05: + return ((DEVSTATUS(iod) & + (IO_ST_READY | IO_ST_BUSY | IO_1733_ONCYL)) != + (IO_ST_READY | IO_1733_ONCYL)); + } + } + return FALSE; +} + +/* Perform I/O */ + +enum IOstatus CDin(IO_DEVICE *iod, uint8 reg) +{ + /* + * All input requests other than Clear Controller should be handled by + * the I/O framework. + */ + if (reg == 0) { + struct cdio_unit *iou; + + CDreset(&cd_dev); + + if ((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) { + int first = ((cd_dev.flags & DEV_FIXED) != 0) ? 1 : 0; + + if ((iou->ondrive[first]->flags & UNIT_ATT) != 0) + iou->active = iou->ondrive[first]; + else iou->active = iou->ondrive[first ^ 0x01]; + + iou->busy = TRUE; + iou->state = CD_RTZS; + sim_activate(iou->active, CD_RTZS_WAIT); + } + return IO_REPLY; + } + return IO_REJECT; +} + +enum IOstatus CDout(IO_DEVICE *iod, uint8 reg) +{ + UNIT *uptr; + struct cdio_unit *iou; + + switch (reg) { + /* + * Load Buffer + */ + case 0x00: + CDdev.BUFLEN = IOAreg; + CDdev.STATUS &= IO_ST_READY | IO_1733_ONCYL | IO_ST_PROT | IO_1733_SINGLE; + break; + + /* + * Director function + */ + case 0x01: + /* + * Clear interrupt active and end of operation + */ + CDdev.STATUS &= ~(IO_ST_INT | IO_ST_EOP); + + /* + * Changing the device interrupt mask does not cause an interrupt if + * any of the newly masked conditions are true. + */ + doDirectorFunc(&cd_dev, TRUE); + + /* + * Handle select/de-select. + */ + if ((IOAreg & (IO_1733_USEL | IO_1733_UDSEL)) != 0) { + uint16 unit = (IOAreg & IO_1733_USC) >> 9; + struct cdio_unit *iou = &CDunits[unit]; + + if ((IOAreg & IO_1733_UDSEL) != 0) { + /*** TODO: Check protect conditions ***/ + } + + if ((IOAreg & IO_1733_USEL) != 0) { + CDdev.iod_drive = NULL; + CDdev.STATUS &= ~(IO_1733_ONCYL | IO_ST_BUSY | IO_ST_READY); + + if (((iou->ondrive[0]->flags & UNIT_ATT) != 0) || + ((iou->ondrive[1]->flags & UNIT_ATT) != 0)) { + CDdev.iod_drive = iou; + CDdev.STATUS |= IO_ST_READY; + + if (iou->active == NULL) { + int first = ((cd_dev.flags & DEV_FIXED) != 0) ? 1 : 0; + + if ((iou->ondrive[first]->flags & UNIT_ATT) != 0) + iou->active = iou->ondrive[first]; + else iou->active = iou->ondrive[first ^ 0x01]; + } + + if (iou->oncyl) { + CDdev.STATUS |= IO_1733_ONCYL; + CDdev.CYLADRSTATUS = iou->sectorAddr; + } + } + } + } + break; + + /* + * Load Address + */ + case 0x02: + if (((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) && + ((uptr = iou->active) != NULL) && !iou->busy && iou->oncyl ) { + if (LoadDiskAddress(uptr, iou, CD_SEEK)) { + CDdev.STATUS &= IO_ST_READY | IO_1733_ONCYL | IO_ST_PROT | IO_1733_SINGLE; + /* + * If IO_1733_ONCYL is set, we must already be at the requested + * cylinder and no seek will be required. + */ + if ((CDdev.STATUS & IO_1733_ONCYL) != 0) + break; + + sim_activate(uptr, CD_SEEK_WAIT); + } else { + if ((cd_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sCD - Bad Load Address (%04X)\r\n", INTprefix, Areg); + + fw_IOintr(FALSE, &cd_dev, &CDdev, IO_1733_ADDRERR | IO_ST_EOP |IO_ST_ALARM, 0, 0xFFFF, "Bad load address"); + } + } else return IO_REJECT; + break; + + /* + * Write + */ + case 0x03: + if (((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) && + ((uptr = iou->active) != NULL) && + ((uptr->flags & UNIT_ATT) != 0)) { + StartCDDiskIO(uptr, iou, CD_WRITE); + } else return IO_REJECT; + break; + + /* + * Read + */ + case 0x04: + if (((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) && + ((uptr = iou->active) != NULL) && + ((uptr->flags & UNIT_ATT) != 0)) { + StartCDDiskIO(uptr, iou, CD_READ); + } else return IO_REJECT; + break; + + /* + * Compare + */ + case 0x05: + if (((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) && + ((uptr = iou->active) != NULL) && + ((uptr->flags & UNIT_ATT) != 0)) { + StartCDDiskIO(uptr, iou, CD_COMPARE); + } else return IO_REJECT; + break; + + /* + * Checkword check + */ + case 0x06: + if (((iou = (struct cdio_unit *)CDdev.iod_drive) != NULL) && + ((uptr = iou->active) != NULL) && + ((uptr->flags & UNIT_ATT) != 0)) { + if (LoadDiskAddress(uptr, iou, CD_CHECKWORD)) { + CDdev.STATUS &= IO_ST_READY | IO_1733_ONCYL | IO_ST_PROT | IO_1733_SINGLE; + CDdev.STATUS |= IO_ST_BUSY; + sim_activate(uptr, CD_SEEK_WAIT); + } else { + if ((cd_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sCD: Bad Checkword Address (%04X)\r\n", + INTprefix, Areg); + + fw_IOintr(FALSE, &cd_dev, &CDdev, IO_1733_ADDRERR | IO_ST_EOP | IO_ST_ALARM, 0, 0xFFFF, "Bad checkword"); + } + } else return IO_REJECT; + break; + } + rebuildPending(); + return IO_REPLY; +} + +/* + * Autoload support + */ +t_stat CDautoload(void) +{ + UNIT *uptr = &cd_unit[(cd_dev.flags & DEV_FIXED) ==0 ? 0 : 1]; + + if ((uptr->flags & UNIT_ATT) != 0) { + uint32 i; + + for (i = 0; i < CD_NUMSC; i++) { + t_offset offset = i * CD_NUMBY; + void * buf = &M[i * CD_NUMWD]; + + sim_fseeko(uptr->fileref, offset, SEEK_SET); + if (sim_fread(buf, sizeof(uint16), CD_NUMWD, uptr->fileref) != CD_NUMWD) + return SCPE_IOERR; + } + return SCPE_OK; + } + return SCPE_UNATT; +} diff --git a/CDC1700/cdc1700_cpu.c b/CDC1700/cdc1700_cpu.c new file mode 100644 index 00000000..3ae42e25 --- /dev/null +++ b/CDC1700/cdc1700_cpu.c @@ -0,0 +1,1548 @@ +/* + + 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_cpu.c: CDC1700 CPU simulator + */ + +/* + * Implementation notes: + * + * 1. Interrupts. There is very little technical details about the interrupt + * system available in the documentation. The following information has + * been deduced from the SMM diagnostic routines. + * + * - Device interrupts + * + * Device interrupts are level-triggered. A device driver may lower the + * the interrupt trigger by: + * + * - Issue a "Clear Controller" command + * - Issue a "Clear Interrupts" command + * - Issue a device-dependent command + * (e.g. on PTP, output a new value) + * + * - CPU interrupts (Power fail, parity and protect fault) + * + * CPU interrupts are edge-triggered. The interrupt trigger is + * automatically lowered when the CPU starts processing interrupt 0. + * + * 2. There is no documention on relative timing. For example, the paper tape + * punch diagnostic enables Alarm+Data interrupts and assumes that it will + * be able to execute some number of instructions before the interrupt + * occurs. How many instructions should we delay if interrupts are enabled + * and all conditions are met to deliver the interrupt immediately? + * + * 3. Some peripherals, notably the teletypewriter, do not have a protected + * status bit. Does this mean that any application can directly affect + * them? + * + * - The teletypewriter may be addressed by either a protected or a + * nonprotected instruction (see SC17 Reference Manual). + * + * 4. The 1740/1742 line printer controllers are incorrectly documented as + * having the status register at offset 3, it is at offset 1 like all + * other peripherals. + * + * 5. For the 1738 disk pack controller, what is the correct response if an + * operation is initiated with no drive selected? For now, we'll reject + * the request. + * + * 6. For the 1706-A buffered data channel, what interrupt is used to signal + * "End of Operation"? A channel-specific interrupt or a pass-through + * interrupt from the device being controlled or some other? + */ + +#include "cdc1700_defs.h" + +uint16 M[MAXMEMSIZE]; +uint8 P[MAXMEMSIZE]; + +t_uint64 Instructions; +uint16 Preg, Areg, Qreg, Mreg, CAenable, OrigPreg, Pending, IOAreg, IOQreg; +uint8 Pfault, Protected, lastP, Oflag, PARflag, INTflag, DEFERflag; + +t_bool ExecutionStarted = FALSE; +uint16 CharAddrMode[16]; + +uint16 INTlevel; + +char INTprefix[8]; + +t_bool FirstRejSeen = FALSE; +uint32 CountRejects = 0; + +t_bool FirstAddr = TRUE; + +extern int disassem(char *, uint16, t_bool, t_bool, t_bool); + +extern enum IOstatus doIO(t_bool, DEVICE **); +extern void fw_init(void); +extern void rebuildPending(void); + +extern void dev1Interrupts(char *); + +t_stat cpu_reset(DEVICE *); +t_stat cpu_set_size(UNIT *, int32, CONST char *, void *); +t_stat cpu_ex(t_value *, t_addr, UNIT *, int32); +t_stat cpu_dep(t_value, t_addr, UNIT *uptr, int32 sw); + +#define UNIT_V_STOPSW (UNIT_V_UF + 1) /* Selective STOP switch */ +#define UNIT_STOPSW (1 << UNIT_V_STOPSW) +#define UNIT_V_SKIPSW (UNIT_V_UF + 2) /* Selective SKIP switch */ +#define UNIT_SKIPSW (1 << UNIT_V_SKIPSW) +#define UNIT_V_MODE65K (UNIT_V_UF + 3) /* 32K/65K mode switch */ +#define UNIT_MODE65K (1 << UNIT_V_MODE65K) +#define UNIT_V_CHAR (UNIT_V_UF + 4) /* Character addressing */ +#define UNIT_CHAR (1 << UNIT_V_CHAR) +#define UNIT_V_PROT (UNIT_V_UF + 5) +#define UNIT_PROT (1 << UNIT_V_PROT) +#define UNIT_V_MSIZE (UNIT_V_UF + 6) /* Memory size */ +#define UNIT_MSIZE (1 << UNIT_V_MSIZE) + +IO_DEVICE CPUdev = IODEV(NULL, "1704", CPU, 0, 0xFF, 0, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, NULL); + +/* CPU data structures + + cpu_dev CPU device descriptor + cpu_unit CPU unit + cpu_reg CPU register list + cpu_mod CPU modifier list +*/ +UNIT cpu_unit = { UDATA(NULL, UNIT_FIX+UNIT_BINK, DEFAULTMEMSIZE) }; + +REG cpu_reg[] = { + { HRDATA(P, Preg, 16) }, + { HRDATA(A, Areg, 16) }, + { HRDATA(Q, Qreg, 16) }, + { HRDATA(M, Mreg, 16) }, + { HRDATA(O, Oflag, 1) }, + { HRDATA(CH, CAenable, 1) }, + { HRDATA(PAR, PARflag, 1) }, + { HRDATA(INT, INTflag, 1) }, + { HRDATA(DEFER, DEFERflag, 1) }, + { HRDATA(PENDING, Pending, 1) }, + { HRDATA(PFAULT, Pfault, 1) }, + { NULL } +}; + +MTAB cpu_mod[] = { + { UNIT_STOPSW, UNIT_STOPSW, "Selective Stop", "SSTOP", NULL }, + { UNIT_STOPSW, 0, "No Selective Stop", "NOSSTOP", NULL }, + { UNIT_SKIPSW, UNIT_SKIPSW, "Selective Skip", "SSKIP", NULL }, + { UNIT_SKIPSW, 0, "No Selective Skip", "NOSSKIP", NULL }, + { UNIT_MODE65K, UNIT_MODE65K, "65K Addressing Mode", "MODE65K", NULL }, + { UNIT_MODE65K, 0, "32K Addressing Mode", "MODE32K", NULL }, + { UNIT_CHAR, UNIT_CHAR, "Character Addressing", "CHAR", NULL }, + { UNIT_CHAR, 0, "No Character Addressing", "NOCHAR", NULL }, + { UNIT_PROT, UNIT_PROT, "Program Protect", "PROTECT", NULL }, + { UNIT_PROT, 0, "", "NOPROTECT", NULL }, + { UNIT_MSIZE, 4096, NULL, "4K", &cpu_set_size }, + { UNIT_MSIZE, 8192, NULL, "8K", &cpu_set_size }, + { UNIT_MSIZE, 16384, NULL, "16K", &cpu_set_size }, + { UNIT_MSIZE, 32768, NULL, "32K", &cpu_set_size }, +#if MAXMEMSIZE > 32768 + { UNIT_MSIZE, 65536, NULL, "64K", &cpu_set_size }, +#endif + { MTAB_XTD | MTAB_VDV, 0, "1714", NULL, NULL, NULL }, + { 0 } +}; + +#define DBG_ALL \ + (DBG_DISASS | DBG_TRACE | DBG_TARGET | DBG_INPUT | DBG_OUTPUT | DBG_FULL) + +DEBTAB cpu_deb[] = { + { "DISASSEMBLE", DBG_DISASS }, + { "IDISASSEMBLE", DBG_IDISASS }, + { "INTERRUPT", DBG_INTR }, + { "TRACE", DBG_TRACE }, + { "ITRACE", DBG_ITRACE }, + { "TARGET", DBG_TARGET }, + { "INPUT", DBG_INPUT }, + { "OUTPUT", DBG_OUTPUT }, + { "IO", DBG_INPUT | DBG_OUTPUT }, + { "INTLVL", DBG_INTLVL }, + { "PROTECT", DBG_PROTECT }, + { "MISSING", DBG_MISSING }, + { "FULL", DBG_ALL }, + { NULL } +}; + +DEVICE cpu_dev = { + "CPU", &cpu_unit, cpu_reg, cpu_mod, + 1, 16, 16, 1, 16, 16, + &cpu_ex, &cpu_dep, &cpu_reset, + NULL, NULL, NULL, + &CPUdev, + DEV_DEBUG | DEV_NOEQUIP, 0, cpu_deb, + NULL, + NULL +}; + +/* + * Table of instructions which store to memory + */ +static t_bool storagemode[] = { + FALSE, FALSE, FALSE, FALSE, /* SPECIAL, JMP, MUI, DVI */ + TRUE, FALSE, TRUE, TRUE, /* STQ, RTJ, STA, SPA */ + FALSE, FALSE, FALSE, FALSE, /* ADD, SUB, AND, EOR */ + FALSE, TRUE, FALSE, FALSE /* LDA, RAO, LDQ, ADQ */ +}; + +/* + * Table of parity values + */ +static uint8 parity[256] = { + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 +}; + +/* + * Table of interrupt bits + */ +static uint16 interruptBit[] = { + 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000 +}; + +/* + * Reset routine + */ +t_stat cpu_reset(DEVICE *dptr) +{ + int i; + + INTlevel = 0; + CAenable = 0; + + Pending = 0; + + fw_init(); + + sim_brk_types = sim_brk_dflt = SWMASK('E'); + Pfault = FALSE; + + FirstRejSeen = FALSE; + CountRejects = 0; + + /* + * Reset the saved character addressing mode for each interrupt level. + */ + for (i = 0; i < 16; i++) + CharAddrMode[i] = 0; + + return SCPE_OK; +} + +/* + * Memory size change + */ +t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint16 mc = 0; + uint32 i; + + if ((val <= 0) || (val > MAXMEMSIZE)) + return SCPE_ARG; + + for (i = val; i < cpu_unit.capac; i++) + mc |= M[i]; + + if ((mc != 0) && (!get_yn("Really truncate memory [N]?", FALSE))) + return SCPE_OK; + + cpu_unit.capac = val; + for (i = cpu_unit.capac; i < MAXMEMSIZE; i++) + M[i] = 0; + return SCPE_OK; +} + +/* + * Memory examine + */ +t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ + if (addr >= cpu_unit.capac) + return SCPE_NXM; + if (vptr != NULL) + *vptr = M[addr]; + return SCPE_OK; +} + +/* + * Memory deposit + */ +t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ + if (addr >= cpu_unit.capac) + return SCPE_NXM; + M[addr] = TRUNC16(val); + return SCPE_OK; +} + +/* + * Dump the current register contents on debugging output. + */ +void dumpRegisters(void) +{ + fprintf(DBGOUT, + "%s[A: %04X, Q: %04X, M: %04X, Ovf: %d, Pfault: %d, I: %d, D: %d]", + INTprefix, Areg, Qreg, Mreg, Oflag, Pfault, INTflag, DEFERflag); +} + +/* + * Indicate processor is running in protected mode. + */ +t_bool inProtectedMode(void) +{ + return (cpu_unit.flags & UNIT_PROT) != 0; +} + +/* + * Returns CPU interrupt status. This always returns 0 since the interrupt + * has already been set in the Pending register. + */ +uint16 cpuINTR(DEVICE *dptr) +{ + return 0; +} + +/* + * Raise an internal interrupt. Used for Power Fail, Parity Error and + * Program Protect Fault. Only Program Protect Fault can occur in emulation. + */ +void RaiseInternalInterrupt(void) +{ + if ((cpu_dev.dctrl & DBG_INTR) != 0) { + fprintf(DBGOUT, + "%sINT(0)[A: %04X, Q: %04X, M: %04X, Ovf: %d, Pfault: %d, I: %d, D: %d]\r\n", + INTprefix, Areg, Qreg, Mreg, Oflag, Pfault, INTflag, DEFERflag); + } + Pending |= 1; +} + +/* + * Raise an external interrupt associated with a peripheral device. + */ +void RaiseExternalInterrupt(DEVICE *dev) +{ + IO_DEVICE *iod = IODEVICE(dev); + uint16 Opending = Pending; + + /* + * Don't touch the STATUS register if the device has completely + * non-standard interrupts. + */ + if (iod->iod_raised == NULL) + iod->STATUS |= IO_ST_INT; + + rebuildPending(); + + if ((cpu_dev.dctrl & DBG_INTR) != 0) { + uint16 level = iod->iod_equip; + + fprintf(DBGOUT, + "%sINT(%d, %s)[A: %04X, Q: %04X, M: %04X, P: %04x->%04x, Ovf: %d, I: %d, D: %d]\r\n", + INTprefix, level, dev->name, Areg, Qreg, Mreg, Opending, Pending, + Oflag, INTflag, DEFERflag); + } +} + +/* + * Memory reference routines + */ + +/* + * Reads are always allowed + */ +uint16 LoadFromMem(uint16 addr) +{ + return M[MEMADDR(addr)]; +} + +/* + * Writes require checking for protected mode. This routine returns TRUE + * if the write succeeded and FALSE if the write failed and an interrupt + * has been scheduled. + */ +t_bool StoreToMem(uint16 addr, uint16 value) +{ + if ((cpu_unit.flags & UNIT_PROT) != 0) { + if (!Protected) { + if (P[MEMADDR(addr)]) { + if ((cpu_dev.dctrl & DBG_PROTECT) != 0) { + fprintf(DBGOUT, + "%sProtect fault storing to memory at %04x => %04X\r\n", + INTprefix, OrigPreg, addr); + } + Pfault = TRUE; + RaiseInternalInterrupt(); + return FALSE; + } + } + } + M[MEMADDR(addr)] = value; + return TRUE; +} + +/* + * I/O devices can maintain their own protected status. Perform similar + * checking as StoreToMem() using the device protected status but do not + * generate a "protect fault" since the error will be reported back through + * the device status. Return TRUE if the write succeeded and FALSE if the + * write failed due to a protect failure. + */ +t_bool IOStoreToMem(uint16 addr, uint16 value, t_bool prot) +{ + if ((cpu_unit.flags & UNIT_PROT) != 0) { + if (!prot) { + if (P[MEMADDR(addr)]) { + return FALSE; + } + } + } + M[MEMADDR(addr)] = value; + return TRUE; +} + +/* + * The 1700 adder is a 16-bit one's complement subtractive adder which + * eliminates minus zero in all but one case (the only case is when minus zero + * is added to minus zero). + */ +uint16 doSUB(uint16 a, uint16 b) +{ + uint32 ea = EXTEND16(a); + uint32 eb = EXTEND16(b); + uint32 result = ea - eb; + + if (((a - b) & 0x10000) != 0) + result -= 1; + + if (((result & 0x18000) != 0x18000) && + ((result & 0x18000) != 0x00000)) + Oflag = 1; + + return TRUNC16(result); +} +uint16 doADD(uint16 a, uint16 b) +{ + return doSUB(a, TRUNC16(~b)); +} + +/* + * Internal operations such as address computations do not modify the + * overflow flag. + */ +uint16 doADDinternal(uint16 a, uint16 b) +{ + uint32 result = a - TRUNC16(~b); + + if ((result & 0x10000) != 0) + result -= 1; + + return TRUNC16(result); +} + +/* + * For multiply, we do the actual multiply in the positive domain and adjust + * the resulting sign based on the input values. + */ +void doMUL(uint16 a) +{ + uint32 val1, result = 0; + uint16 sign = Areg ^ a; + int i; + + val1 = ABS(Areg) & 0xFFFF; + a = ABS(a); + + /* + * Accumulate the result via shift and add. + */ + for (i = 0; i < 15; i++) { + if ((a & 1) != 0) + result += val1; + val1 <<= 1; + a >>= 1; + } + + if ((sign & SIGN) != 0) + result = ~result; + + Qreg = result >> 16; + Areg = TRUNC16(result); +} + +/* + * For divide, we once again do the actual division in the positive domain + * and adjust the resulting signs based on the input values. + */ +void doDIV(uint16 a) +{ + uint32 result = 0, divisor, remainder = (Qreg << 16) | Areg; + uint32 mask = 1; + uint8 sign = 0, rsign = 0; + + if ((Qreg & SIGN) != 0) { + remainder = ~remainder; + sign++; + rsign++; + } + + divisor = ABS(a) & 0xFFFF; + + if ((a & SIGN) != 0) + sign++; + + /* + * The documentation does not specify the result of a divide by zero. + * Hopefully, the diagnostics will provide some insight. Until then we + * just set the overflow flag and return saturated positive values. + */ + if (divisor == 0) { + Oflag = 1; + Areg = Qreg = MAXPOS; + return; + } + + while (divisor < remainder) { + divisor <<= 1; + mask <<= 1; + } + + do { + if (remainder >= divisor) { + remainder -= divisor; + result += mask; + } + divisor >>= 1; + mask >>= 1; + } while (mask != 0); + + /* + * Again the documentation does not specify whether the result/remainder + * can be negative zero. For now I'm going to assume that they cannot. + */ + if ((result & 0xFFFF8000) != 0) + Oflag = 1; + + if ((result & 0x7FFF) != 0) + if ((sign & 1) != 0) + result = ~result; + if ((remainder & 0x7FFF) != 0) + if (rsign != 0) + remainder = ~remainder; + + Areg = TRUNC16(result); + Qreg = TRUNC16(remainder); +} + +/* + * Compute the effective address of an instruction + */ +t_stat getEffectiveAddr(uint16 p, uint16 instr, uint16 *addr) +{ + uint16 count = MAXINDIRECT; + uint16 delta = instr & OPC_ADDRMASK; + uint32 result = delta; + + if (delta == 0) { + result = Preg; + INCP; + + switch (instr & (MOD_RE | MOD_IN)) { + /* + * Mode 0, delta == 0 does not follow the regular addressing model + * of the other modes. + */ + case 0: + if (!storagemode[(instr & OPC_MASK) >> 12] || + ((instr & (MOD_I1 | MOD_I2)) != 0)) + result = LoadFromMem(result); + break; + + case MOD_RE: + result = doADDinternal(result, LoadFromMem(result)); + break; + + case MOD_RE | MOD_IN: + result = doADDinternal(result, LoadFromMem(result)); + /* FALLTHROUGH */ + + case MOD_IN: + result = LoadFromMem(result); + + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + while (result & 0x8000) { + if (--count == 0) + return SCPE_LOOP; + result = LoadFromMem(result & 0x7FFF); + } + } + break; + } + } else { + switch (instr & (MOD_RE | MOD_IN)) { + case 0: + break; + + case MOD_RE: + result = doADDinternal(EXTEND8(result), p); + break; + + case MOD_RE | MOD_IN: + result = doADDinternal(EXTEND8(result), p); + /* FALLTHROUGH */ + + case MOD_IN: + result = LoadFromMem(result); + + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + while (result & 0x8000) { + if (--count == 0) + return SCPE_LOOP; + result = LoadFromMem(result & 0x7FFF); + } + } + break; + } + } + + /* + * Handle indexing + */ + if ((instr & MOD_I1) != 0) + result = doADDinternal(result, Qreg); + if ((instr & MOD_I2) != 0) + result = doADDinternal(result, LoadFromMem(0xFF)); + + *addr = result; + return SCPE_OK; +} + +/* + * Similar effective address calculation routines without modifying the + * CPU registers. + */ + +/* + * Compute the effective address of an instruction + */ +t_stat disEffectiveAddr(uint16 p, uint16 instr, uint16 *base, uint16 *addr) +{ + uint16 count = MAXINDIRECT; + uint16 delta = instr & OPC_ADDRMASK; + uint32 result = delta; + + if (delta == 0) { + result = MEMADDR(p + 1); + + switch (instr & (MOD_RE | MOD_IN)) { + case 0: + if ((instr & (MOD_I1 | MOD_I2)) != 0) + result = LoadFromMem(result); + break; + + case MOD_RE: + result = doADDinternal(result, LoadFromMem(result)); + break; + + case MOD_RE | MOD_IN: + result = doADDinternal(result, LoadFromMem(result)); + /* FALLTHROUGH */ + + case MOD_IN: + result = LoadFromMem(result); + + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + while (result & 0x8000) { + if (--count == 0) + return SCPE_LOOP; + result = LoadFromMem(result & 0x7FFF); + } + } + break; + } + } else { + switch (instr & (MOD_RE | MOD_IN)) { + case 0: + break; + + case MOD_RE: + result = doADDinternal(EXTEND8(result), p); + break; + + case MOD_RE | MOD_IN: + result = doADDinternal(EXTEND8(result), p); + /* FALLTHROUGH */ + + case MOD_IN: + result = LoadFromMem(result); + + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + while (result & 0x8000) { + if (--count == 0) + return SCPE_LOOP; + result = LoadFromMem(result & 0x7FFF); + } + } + break; + } + } + + *base = result; + + /* + * Handle indexing + */ + if ((instr & MOD_I1) != 0) + result = doADDinternal(result, Qreg); + if ((instr & MOD_I2) != 0) + result = doADDinternal(result, LoadFromMem(0xFF)); + + *addr = result; + return SCPE_OK; +} + +/* + * Execute a single instruction on the current CPU. Register P must be + * pointing at the instruction to execute. + */ +t_stat executeAnInstruction(void) +{ + DEVICE *dev; + uint16 instr, operand, operand1, operand2, from; + uint32 temp; + t_stat status; + + INTprefix[0] = '\0'; + + if ((cpu_dev.dctrl & DBG_INTLVL) != 0) + sprintf(INTprefix, "%02d> ", INTlevel); + + if (INTflag && !DEFERflag) { + if ((operand = Pending & Mreg) != 0) { + int i, maxIntr = INTR_1705; + + for (i = 0; i < maxIntr; i++) { + if ((operand & interruptBit[i]) != 0) { + operand1 = INTERRUPT_BASE + (4 * i); + operand2 = from = Preg; + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + operand2 = (operand2 & 0x7FFF) | (Oflag ? 0x8000 : 0); + Oflag = 0; + } + + Protected = TRUE; + StoreToMem(operand1, operand2); + Preg = operand1 + 1; + INTflag = 0; + INTlevel++; + + if ((cpu_unit.flags & UNIT_CHAR) != 0) { + CharAddrMode[i] = CAenable; + CAenable = 0; + } + + if (FirstRejSeen) { + fprintf(DBGOUT, + "%s %u Rejects terminated by interrupt\r\n", + INTprefix, CountRejects); + FirstRejSeen = FALSE; + CountRejects = 0; + } + + if ((cpu_dev.dctrl & DBG_INTR) != 0) { + if (i == 1) { + char intbuf[32]; + char *buf = &intbuf[0]; + + dev1Interrupts(buf); + if (buf[0] == ' ') + buf++; + + fprintf(DBGOUT, + "%s===> Device 1 Stations [%s]\n", + INTprefix, buf); + } + fprintf(DBGOUT, + "%s===> Interrupt %d entered at 0x%04X, from %04X, Inst: %llu\r\n", + INTprefix, i, Preg, from, Instructions); + } + + if (i == 0) + Pending &= 0xFFFE; + + if ((cpu_dev.dctrl & DBG_INTLVL) != 0) + sprintf(INTprefix, "%02d> ", INTlevel); + + if (sim_brk_summ && sim_brk_test(Preg, SWMASK('E'))) { + /* + * This was not really an instruction execution. + */ + sim_interval++; + return SCPE_IBKPT; + } + break; + } + } + } + } + + DEFERflag = 0; + + if (((cpu_dev.dctrl & DBG_TRACE) != 0) || + (((cpu_dev.dctrl & DBG_ITRACE) != 0) && (INTlevel != 0))) { + fprintf(DBGOUT, + "%sA:%04X Q:%04X I:%04X M:%04X Ovf:%d Pfault: %d Inst:%llu\r\n", + INTprefix, Areg, Qreg, LoadFromMem(0xFF), + Mreg, Oflag, Pfault, Instructions); + } + + if (((cpu_dev.dctrl & DBG_DISASS) != 0) || + (((cpu_dev.dctrl & DBG_IDISASS) != 0) && (INTlevel != 0))) { + char buf[128]; + t_bool target = (cpu_dev.dctrl & DBG_TARGET) != 0; + + disassem(buf, Preg, TRUE, target, TRUE); + fprintf(DBGOUT, "%s%s\r\n", INTprefix, buf); + } + + /* + * Get the next instruction, moving the current PC to the next word address. + * We need to save the PC of the current instruction so that we can pass it + * into the operand calculation routine(s). + */ + OrigPreg = Preg; + lastP = Protected; + Protected = P[MEMADDR(OrigPreg)]; + + instr = LoadFromMem(OrigPreg); + INCP; + + /* + * Check for protected mode operation where we are about to execute a + * protected instruction and the previous instruction was unprotected. + */ + if ((cpu_unit.flags & UNIT_PROT) != 0) { + if (!lastP && Protected) { + if ((cpu_dev.dctrl & DBG_PROTECT) != 0) { + fprintf(DBGOUT, + "%sProtect fault, unprotected after protected at %04X\r\n", + INTprefix, OrigPreg); + } + Pfault = TRUE; + RaiseInternalInterrupt(); + /* + * Make sure we skip over the failing instruction. + */ + if ((instr & OPC_MASK) != 0) { + if ((instr & OPC_ADDRMASK) == 0) { + INCP; + } + } + + /* + * Execute this instructionas an unprotected Selective Stop. + */ + if ((cpu_unit.flags & UNIT_STOPSW) != 0) { + dumpRegisters(); + return SCPE_SSTOP; + } + return SCPE_OK; + } + } + + Instructions++; + + switch (instr & OPC_MASK) { + case OPC_ADQ: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Qreg = doADD(Qreg, operand); + break; + + case OPC_LDQ: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Qreg = operand; + break; + + case OPC_RAO: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + StoreToMem(operand, doADD(LoadFromMem(operand), 1)); + break; + + case OPC_LDA: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + + if ((cpu_unit.flags & UNIT_CHAR) != 0) { + uint16 xxx = operand; + if (CAenable != 0) { + if ((LoadFromMem(0xFF) & 0x01) == 0) + operand >>= 8; + operand = (Areg & 0xFF00) | (operand & 0xFF); + fprintf(DBGOUT, + "CM LDA at P: %04X, A: %04X, I: %04X, SRC: %04X, Result: %04X\r\n", + OrigPreg, Areg, LoadFromMem(0xFF), xxx, operand); + } + } + Areg = operand; + break; + + case OPC_EOR: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Areg ^= operand; + break; + + case OPC_AND: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Areg &= operand; + break; + + case OPC_SUB: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Areg = doSUB(Areg, operand); + break; + + case OPC_ADD: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + Areg = doADD(Areg, operand); + break; + + case OPC_SPA: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (StoreToMem(operand, Areg)) { + temp = parity[Areg & 0xFF] + parity[(Areg >> 8) & 0xFF]; + if ((temp & 1) != 0) + Areg = 0; + else Areg = 1; + } + break; + + case OPC_STA: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + + if ((cpu_unit.flags & UNIT_CHAR) != 0) { + if (CAenable != 0) { + operand1 = LoadFromMem(operand); + if ((LoadFromMem(0xFF) & 0x01) == 0) + operand1 = (operand1 & 0xFF) | ((Areg << 8) & 0xFF00); + else operand1 = (operand1 & 0xFF00) | (Areg & 0xFF); + StoreToMem(operand, operand1); + break; + } + } + StoreToMem(operand, Areg); + break; + + case OPC_RTJ: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + StoreToMem(operand, Preg); + Preg = operand; + INCP; + break; + + case OPC_STQ: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + StoreToMem(operand, Qreg); + break; + + case OPC_DVI: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + doDIV(operand); + break; + + case OPC_MUI: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + if (!ISCONSTANT(instr)) + operand = LoadFromMem(operand); + doMUL(operand); + break; + + case OPC_JMP: + if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK) + return status; + Preg = operand; + break; + + case OPC_SPECIAL: + switch (instr & OPC_SPECIALMASK) { + case OPC_SLS: + if ((cpu_unit.flags & UNIT_STOPSW) != 0) { + dumpRegisters(); + return SCPE_SSTOP; + } + break; + + case OPC_SKIPS: + switch (instr & (OPC_SKIPS | OPC_SKIPMASK)) { + case OPC_SAZ: + if (Areg == 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SAN: + if (Areg != 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SAP: + if ((Areg & SIGN) == 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SAM: + if ((Areg & SIGN) != 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SQZ: + if (Qreg == 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SQN: + if (Qreg != 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SQP: + if ((Qreg & SIGN) == 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SQM: + if ((Qreg & SIGN) != 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SWS: + if ((cpu_unit.flags & UNIT_SKIPSW) != 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SWN: + if ((cpu_unit.flags & UNIT_SKIPSW) == 0) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SOV: + if (Oflag) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + Oflag = 0; + break; + + case OPC_SNO: + if (!Oflag) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + Oflag = 0; + break; + + /* + * The emulator does not generate/check storage parity, so these + * skips always operate as though parity is valid. + */ + case OPC_SPE: + break; + + case OPC_SNP: + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + + case OPC_SPF: + if (Pfault) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + Pfault = FALSE; + rebuildPending(); + break; + + case OPC_SNF: + if (!Pfault) + Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT); + break; + } + break; + + case OPC_INP: + if ((cpu_dev.dctrl & DBG_INPUT) != 0) + if (!FirstRejSeen) + fprintf(DBGOUT, + "%sINP:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n", + INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag); + + switch (doIO(FALSE, &dev)) { + case IO_REPLY: + if (FirstRejSeen) { + fprintf(DBGOUT, + "%s %u Rejects terminated by a Reply\r\n", + INTprefix, CountRejects); + FirstRejSeen = FALSE; + CountRejects = 0; + } + if ((cpu_dev.dctrl & DBG_INPUT) != 0) + fprintf(DBGOUT, "%sINP: ==> REPLY, A: %04X\r\n", + INTprefix, Areg); + break; + + case IO_REJECT: + if ((cpu_dev.dctrl & DBG_INPUT) != 0) + if (!FirstRejSeen) + fprintf(DBGOUT, "%sINP: ==> REJECT\r\n", INTprefix); + Preg = doADDinternal(Preg, EXTEND8(instr & OPC_MODMASK)); + if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0)) + return SCPE_REJECT; + + /* + * Check if reject forces the instruction to restart. If so, + * reduce a sequence of Reject logs into a single entry. + */ + if (Preg == OrigPreg) { + if ((dev != NULL) && ((dev->dctrl & DBG_DFIRSTREJ) != 0)) { + if (!FirstRejSeen) { + FirstRejSeen = TRUE; + CountRejects = 1; + } + } else CountRejects++; + } + break; + + case IO_INTERNALREJECT: + if ((cpu_dev.dctrl & DBG_INPUT) != 0) + fprintf(DBGOUT, "%sINP: ==> INTERNALREJECT\r\n", INTprefix); + Preg = doADDinternal(OrigPreg, EXTEND8(instr & OPC_MODMASK)); + if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0)) + return SCPE_REJECT; + break; + } + break; + + case OPC_OUT: + if ((cpu_dev.dctrl & DBG_OUTPUT) != 0) + if (!FirstRejSeen) + fprintf(DBGOUT, + "%sOUT:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n", + INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag); + + switch (doIO(TRUE, &dev)) { + case IO_REPLY: + if (FirstRejSeen) { + fprintf(DBGOUT, + "%s %u Rejects terminated by a Reply\r\n", + INTprefix, CountRejects); + FirstRejSeen = FALSE; + CountRejects = 0; + } + if ((cpu_dev.dctrl & DBG_OUTPUT) != 0) + fprintf(DBGOUT, "%sOUT: ==> REPLY\r\n", INTprefix); + break; + + case IO_REJECT: + if ((cpu_dev.dctrl & DBG_OUTPUT) != 0) + fprintf(DBGOUT, "%sOUT: ==> REJECT\r\n", INTprefix); + Preg = doADDinternal(Preg, EXTEND8(instr & OPC_MODMASK)); + if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0)) + return SCPE_REJECT; + + /* + * Check if reject forces the instruction to restart. If so, + * reduce a sequence of Reject logs into a single entry. + */ + if (Preg == OrigPreg) { + if ((dev != NULL) && ((dev->dctrl & DBG_DFIRSTREJ) != 0)) { + if (!FirstRejSeen) { + FirstRejSeen = TRUE; + CountRejects = 1; + } + } else CountRejects++; + } + break; + + case IO_INTERNALREJECT: + if ((cpu_dev.dctrl & DBG_OUTPUT) != 0) + fprintf(DBGOUT, "%sOUT: ==> INTERNALREJECT\r\n", INTprefix); + Preg = doADDinternal(OrigPreg, EXTEND8(instr & OPC_MODMASK)); + if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0)) + return SCPE_REJECT; + break; + } + break; + + /* + * The following instructions (EIN, IIN, SPB, CPB and EXI) + * generate a protect fault if the protect switch is set and + * the instruction is not protected. If the system is unable + * to handle the interrupt (interrupts disabled or interrupt 0 + * masked), the instruction executes as a "Selective Stop". Note + * that the character addressing instructions are a subset + * of IIN and have to be checked separately. + */ + case OPC_IIN: + if ((cpu_unit.flags & UNIT_CHAR) != 0) { + if ((instr & 0xFF) != 0) { + switch (instr) { + case OPC_ECA: + CAenable = 1; + break; + + case OPC_DCA: + CAenable = 0; + break; + } + break; + } + } + + case OPC_EIN: + case OPC_SPB: + case OPC_CPB: + case OPC_EXI: + if ((cpu_unit.flags & UNIT_PROT) != 0) { + if (!Protected) { + if ((cpu_dev.dctrl & DBG_PROTECT) != 0) { + fprintf(DBGOUT, + "%sProtect fault EIN/SPB/CPB/EXI at %04X\r\n", + INTprefix, OrigPreg); + } + Pfault = TRUE; + RaiseInternalInterrupt(); + + /* + * Execute this instruction as though it was a "Selective Stop". + */ + if ((cpu_unit.flags & UNIT_STOPSW) != 0) { + dumpRegisters(); + return SCPE_SSTOP; + } + break; + } + } + + /* + * Execute the instruction. + */ + switch (instr & OPC_SPECIALMASK) { + case OPC_EIN: + if ((cpu_dev.dctrl & DBG_INTR) != 0) { + fprintf(DBGOUT, + "%sEIN:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n", + INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag); + } + + INTflag = DEFERflag = 1; + break; + + case OPC_IIN: + if ((cpu_dev.dctrl & DBG_INTR) != 0) { + fprintf(DBGOUT, + "%sIIN:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n", + INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag); + } + + INTflag = 0; + break; + + case OPC_SPB: + SETPROTECT(Qreg); + break; + + case OPC_CPB: + CLRPROTECT(Qreg); + break; + + case OPC_EXI: + operand = instr & OPC_MODMASK; + if ((operand & 0xC3) != 0) { + Preg = OrigPreg; + return SCPE_INVEXI; + } + + if ((cpu_dev.dctrl & DBG_INTR) != 0) + fprintf(DBGOUT, "%s<=== Interrupt %d exit [M: %04X]\r\n", + INTprefix, (operand >> 2) & 0xF, Mreg); + + Preg = operand2 = LoadFromMem(INTERRUPT_BASE + operand); + if ((cpu_unit.flags & UNIT_MODE65K) == 0) { + Preg &= 0x7FFF; + Oflag = operand2 & 0x8000 ? 1 : 0; + } + if (INTlevel != 0) + INTlevel--; + INTflag = 1; + + if ((cpu_unit.flags & UNIT_CHAR) != 0) { + CAenable = CharAddrMode[(operand >> 2) & 0xF]; + CharAddrMode[(operand >> 2) & 0xF] = 0; + } + break; + } + break; + + case OPC_INTER: + /* + * Protection fault if the instruction is not protected and + * modifies M + */ + if ((cpu_unit.flags & UNIT_PROT) != 0) { + if ((instr & MOD_D_M) != 0) { + if (!Protected) { + if ((cpu_dev.dctrl & DBG_PROTECT) != 0) { + fprintf(DBGOUT, + "%sProtect fault INTER to M at %04X\r\n", + INTprefix, OrigPreg); + } + Pfault = TRUE; + RaiseInternalInterrupt(); + + /* + * Execute the instruction as a "Selective Stop". + */ + if ((cpu_unit.flags & UNIT_STOPSW) != 0) { + dumpRegisters(); + return SCPE_SSTOP; + } + break; + } + } + } + operand1 = instr & MOD_O_A ? Areg : 0xFFFF; + switch (instr & (MOD_O_Q | MOD_O_M)) { + case 0: + operand2 = 0xFFFF; + break; + + case MOD_O_M: + operand2 = Mreg; + break; + + case MOD_O_Q: + operand2 = Qreg; + break; + + case MOD_O_M | MOD_O_Q: + operand2 = Qreg | Mreg; + break; + } + + switch (instr & (MOD_LP | MOD_XR)) { + case 0: + operand = doADD(operand1, operand2); + break; + + case MOD_XR: + operand = operand1 ^ operand2; + break; + + case MOD_LP: + operand = operand1 & operand2; + break; + + case MOD_XR | MOD_LP: + operand = ~(operand1 & operand2); + break; + } + + if ((instr & MOD_D_A) != 0) + Areg = operand; + if ((instr & MOD_D_Q) != 0) + Qreg = operand; + if ((instr & MOD_D_M) != 0) { + if ((cpu_dev.dctrl & DBG_INTR) != 0) + fprintf(DBGOUT, "%s<=== M changed from %04X to %04X\r\n", + INTprefix, Mreg, operand); + Mreg = operand; + } + break; + + case OPC_INA: + Areg = doADD(Areg, EXTEND8(instr & OPC_MODMASK)); + break; + + case OPC_ENA: + Areg = EXTEND8(instr & OPC_MODMASK); + break; + + case OPC_NOP: + break; + + case OPC_ENQ: + Qreg = EXTEND8(instr & OPC_MODMASK); + break; + + case OPC_INQ: + Qreg = doADD(Qreg, EXTEND8(instr & OPC_MODMASK)); + break; + + case OPC_SHIFTS: + /* Assume shifts without A or Q are a NOP */ + if ((instr & (MOD_S_A | MOD_S_Q)) != 0) { + int i, count = instr & OPC_SHIFTCOUNT; + uint32 temp32; + + if (count) { + switch (instr & (OPC_SHIFTS | OPC_SHIFTMASK)) { + case OPC_QRS: + temp32 = Qreg; + for (i = 0; i < count; i++) { + temp32 >>= 1; + if ((temp32 & 0x4000) != 0) + temp32 |= SIGN; + } + Qreg = TRUNC16(temp32); + break; + + case OPC_ARS: + temp32 = Areg; + for (i = 0; i < count; i++) { + temp32 >>= 1; + if ((temp32 & 0x4000) != 0) + temp32 |= SIGN; + } + Areg = TRUNC16(temp32); + break; + + case OPC_LRS: + temp32 = (Qreg << 16) | Areg; + for (i = 0; i < count; i++) { + temp32 >>= 1; + if ((temp32 & 0x40000000) != 0) + temp32 |= 0x80000000; + } + Areg = TRUNC16(temp32); + Qreg = TRUNC16(temp32 >> 16); + break; + + case OPC_QLS: + temp32 = Qreg; + for (i = 0; i < count; i++) { + temp32 <<= 1; + if ((temp32 & 0x10000) != 0) + temp32 |= 1; + } + Qreg = TRUNC16(temp32); + break; + + case OPC_ALS: + temp32 = Areg; + for (i = 0; i < count; i++) { + temp32 <<= 1; + if ((temp32 & 0x10000) != 0) + temp32 |= 1; + } + Areg = TRUNC16(temp32); + break; + + case OPC_LLS: + temp32 = (Qreg << 16) | Areg; + for (i = 0; i < count; i++) { + uint32 sign = temp32 & 0x80000000; + + temp32 <<= 1; + if (sign) + temp32 |= 1; + } + Areg = TRUNC16(temp32); + Qreg = TRUNC16(temp32 >> 16); + break; + } + } + } + break; + } + break; + } + return SCPE_OK; +} + +t_stat sim_instr(void) +{ + t_stat reason = 0; + + ExecutionStarted = TRUE; + + while (reason == 0) { + if (sim_interval <= 0) { + if ((reason = sim_process_event()) != 0) + break; + } + + if (sim_brk_summ && sim_brk_test(Preg, SWMASK('E'))) + return SCPE_IBKPT; + + reason = executeAnInstruction(); + sim_interval--; + + if (reason == SCPE_OK) + if (sim_step && (--sim_step <= 0)) + reason = SCPE_STOP; + } + return reason; +} diff --git a/CDC1700/cdc1700_dc.c b/CDC1700/cdc1700_dc.c new file mode 100644 index 00000000..332b08cd --- /dev/null +++ b/CDC1700/cdc1700_dc.c @@ -0,0 +1,795 @@ +/* + + 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_dc.c: CDC1700 Buffered data channel support + * Simh devices: dca, dcb, dcc + */ + +#include "cdc1700_defs.h" + +extern char INTprefix[]; + +extern uint16 Areg, Preg, Qreg, IOAreg, IOQreg, M[]; + +extern t_bool IOFWinitialized; + +extern DEVICE *IOdev[]; +extern UNIT cpu_unit; + +extern uint16 LoadFromMem(uint16); +extern t_bool IOStoreToMem(uint16, uint16, t_bool); + +extern void rebuildPending(void); +extern void RaiseExternalInterrupt(DEVICE *); + +extern IO_DEVICE *fw_findChanDevice(IO_DEVICE *, uint16); +extern enum IOstatus fw_doIO(DEVICE *, t_bool); +extern enum IOstatus fw_doBDCIO(IO_DEVICE *, uint16 *, t_bool, uint8); + +extern uint16 LoadFromMem(uint16); +extern t_bool IOStoreToMem(uint16, uint16, t_bool); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); + +t_stat set_intr(UNIT *uptr, int32 val, CONST char *, void *); +t_stat show_intr(FILE *, UNIT *, int32, CONST void *); +t_stat show_target(FILE *, UNIT *, int32, CONST void *); + +t_stat dc_svc(UNIT *); +t_stat dc_reset(DEVICE *); + +void DCstate(const char *, DEVICE *, IO_DEVICE *); +t_bool DCreject(IO_DEVICE *, t_bool, uint8); +enum IOstatus DCin(IO_DEVICE *, uint8); +enum IOstatus DCout(IO_DEVICE *, uint8); + +/* + 1706-A Buffered Data Channel + + Addresses (A maximum of 3 1706-A's may be attached to a 1700 series system) + + Computer Instruction + Q Register Output From A Input To A + (Bits 11-15) + + #1 #2 #3 + 00010 00111 01100 Direct Output Direct Input + 00011 01000 01101 Function Terminate Buffer + 00100 01001 01110 Buffered Output 1706-A Status + 00101 01010 01111 Buffered Input 1706-A Current Address + + Operations: + + Function + + 15 14 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | + | +---------------------------------------------------+ EOP Interrupt + | | Request + | Not defined + Set/Clear condition bits 0 - 14 + + Status Response: + + Status + + 15 10 7 5 3 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | | | X | | X | | X | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | + | | | | | | Ready + | | | | | Busy + | | | | Interrupt + | | | End of Operation + | | Program Protect Fault + | Device Reject + Device Reply + */ + +IO_DEVICE DCAdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_1_A, + DCreject, DCin, DCout, NULL, NULL, + DCstate, NULL, NULL, NULL, + 0x7F, 4, + MASK_REGISTER0 | MASK_REGISTER1 | \ + MASK_REGISTER2 | MASK_REGISTER3, + MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL); + +IO_DEVICE DCBdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_2_A, + DCreject, DCin, DCout, NULL, NULL, + DCstate, NULL, NULL, NULL, + 0x7F, 4, + MASK_REGISTER0 | MASK_REGISTER1 | \ + MASK_REGISTER2 | MASK_REGISTER3, + MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL); + +IO_DEVICE DCCdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_3_A, + DCreject, DCin, DCout, NULL, NULL, + DCstate, NULL, NULL, NULL, + 0x7F, 4, + MASK_REGISTER0 | MASK_REGISTER1 | \ + MASK_REGISTER2 | MASK_REGISTER3, + MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_lastIO iod_private +#define iod_target iod_private2 +#define iod_svcstate iod_private3 +#define iod_CWA iod_private6 +#define iod_LWA iod_private7 +#define iod_nextAddr iod_private8 +#define iod_reg iod_private9 + +/* + * Define current state of the 1706-A with respect to the Direct Storage + * Access Bus. + */ +#define IO_BDC_IDLE 0x00 +#define IO_BDC_STARTR 0x01 /* Start read sequence */ +#define IO_BDC_STARTW 0x02 /* Start write sequence */ +#define IO_BDC_READING 0x03 /* Read sequence in progress */ +#define IO_BDC_WRITING 0x04 /* Write sequence in progress */ +#define IO_BDC_DONE 0x05 /* Transfer has completed */ + +/* Buffered Data Channel (DC) data structures + + dca_dev DC device descriptor + dcb_dev DC device descriptor + dcc_dev DC device descriptor + dca_unit DC unit + dcb_unit DC unit + dcc_unit DC unit + dc_reg DC register list + dc_mod DC modifier list +*/ +UNIT dca_unit[] = { + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) } +}; +UNIT dcb_unit[] = { + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) } +}; +UNIT dcc_unit[] = { + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) }, + {UDATA(&dc_svc, UNIT_DISABLE, 0) } +}; + +REG dca_reg[] = { + { HRDATA(STATUS, DCAdev.iod_readR[2], 16) }, + { HRDATA(CWA, DCAdev.iod_CWA, 16) }, + { HRDATA(NEXT, DCAdev.iod_nextAddr, 16) }, + { HRDATA(LWA, DCAdev.iod_LWA, 16) }, + { HRDATA(IENABLE, DCAdev.IENABLE, 16) }, + { NULL } +}; + +REG dcb_reg[] = { + { HRDATA(STATUS, DCBdev.iod_readR[2], 16) }, + { HRDATA(CWA, DCBdev.iod_CWA, 16) }, + { HRDATA(NEXT, DCBdev.iod_nextAddr, 16) }, + { HRDATA(LWA, DCBdev.iod_LWA, 16) }, + { HRDATA(IENABLE, DCBdev.IENABLE, 16) }, + { NULL } +}; + +REG dcc_reg[] = { + { HRDATA(STATUS, DCCdev.iod_readR[2], 16) }, + { HRDATA(CWA, DCCdev.iod_CWA, 16) }, + { HRDATA(NEXT, DCCdev.iod_nextAddr, 16) }, + { HRDATA(LWA, DCCdev.iod_LWA, 16) }, + { HRDATA(IENABLE, DCCdev.IENABLE, 16) }, + { NULL } +}; + +MTAB dc_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "1706-A", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, "TARGET", NULL, NULL, &show_target, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "INTERRUPT", &set_intr, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "INTERRUPT", NULL, NULL, &show_intr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { 0 } +}; + +DEBTAB dc_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "LOCATION", DBG_DLOC }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DLOC }, + { NULL } +}; + +DEVICE dca_dev = { + "DCA", dca_unit, dca_reg, dc_mod, + 0, 16, 16, 1, 16, 16, + NULL, NULL, &dc_reset, + NULL, NULL, NULL, + &DCAdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +DEVICE dcb_dev = { + "DCB", dcb_unit, dcb_reg, dc_mod, + 0, 16, 16, 1, 16, 16, + NULL, NULL, &dc_reset, + NULL, NULL, NULL, + &DCBdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +DEVICE dcc_dev = { + "DCC", dcc_unit, dcc_reg, dc_mod, + 0, 16, 16, 1, 16, 16, + NULL, NULL, &dc_reset, + NULL, NULL, NULL, + &DCCdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static DEVICE *dc_devices[IO_1706_MAX] = { + &dca_dev, &dcb_dev, &dcc_dev +}; + +/* + * Dump the current state of the Buffered Data Channel. + */ +const char *DCstateStr[] = { + "Idle", "StartR", "StartW", "Read", "Write", "Done" +}; + +void DCstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[%s %s: Sta: %04X, %s, ena: %04X, cur: %04X, next: %04X, last: %04X, reg: %d]\r\n", + INTprefix, dev->name, where, + DCSTATUS(iod), DCstateStr[iod->iod_svcstate], ENABLED(iod), + iod->iod_CWA, iod->iod_nextAddr, iod->iod_LWA, iod->iod_reg); +} + +/* + * Display device description. + */ +static const char *description(DEVICE *dptr) +{ + return "1706-A"; +} + +/* + * Unit service + */ +t_stat dc_svc(UNIT *uptr) +{ + DEVICE *dptr; + enum IOstatus status; + uint16 temp; + + if ((dptr = find_dev_from_unit(uptr)) != NULL) { + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + IO_DEVICE *target = (IO_DEVICE *)iod->iod_target; + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() entry", iod->iod_indev, iod); + + switch (iod->iod_svcstate) { + case IO_BDC_IDLE: + return SCPE_OK; + + case IO_BDC_STARTR: + case IO_BDC_STARTW: + if ((dptr->dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%s%s - Start %s on %s, current: %04X, last: %04X\r\n", + INTprefix, dptr->name, + iod->iod_svcstate == IO_BDC_STARTR ? "input" : "output", + target == NULL ? "no device" : target->iod_indev->name, + iod->iod_CWA, iod->iod_LWA); + + iod->iod_svcstate = + iod->iod_svcstate == IO_BDC_STARTR ? IO_BDC_READING : IO_BDC_WRITING; + sim_activate(uptr, DC_IO_WAIT); + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - started", iod->iod_indev, iod); + + return SCPE_OK; + + case IO_BDC_READING: + if (target != NULL) { + if ((target->STATUS & IO_ST_EOP) != 0) + goto ioDone; + + if (iod->iod_CWA == iod->iod_LWA) { + /* + * Transfer complete - complete status change and, optionally, + * generate interrupt. + */ + iod->iod_svcstate = IO_BDC_DONE; + sim_activate(uptr, DC_EOP_WAIT); + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - read complete", iod->iod_indev, iod); + + return SCPE_OK; + } + + DCSTATUS(iod) &= ~(IO_1706_REPLY | IO_1706_REJECT); + iod->iod_nextAddr = iod->iod_CWA + 1; + + status = fw_doBDCIO(target, &temp, FALSE, iod->iod_reg); + + switch (status) { + case IO_REPLY: + DCSTATUS(iod) |= IO_1706_REPLY; + if (!IOStoreToMem(iod->iod_CWA, temp, TRUE)) { + DCSTATUS(iod) |= IO_1706_PROT; + /*** TODO: Signal protect fault ***/ + } + iod->iod_CWA++; + + if ((dptr->dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%s%s - Read %04X\r\n", + INTprefix, dptr->name, temp); + break; + + case IO_REJECT: + case IO_INTERNALREJECT: + DCSTATUS(iod) |= IO_1706_REJECT; + break; + } + } else DCSTATUS(iod) |= IO_1706_REJECT; + sim_activate(uptr, DC_IO_WAIT); + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - reading", iod->iod_indev, iod); + + return SCPE_OK; + + case IO_BDC_WRITING: + if (target != NULL) { + if ((target->STATUS & IO_ST_EOP) != 0) + goto ioDone; + + if (iod->iod_CWA == iod->iod_LWA) { + /* + * Transfer complete - complete status change and, optionally, + * generate interrupt. + */ + iod->iod_svcstate = IO_BDC_DONE; + sim_activate(uptr, DC_EOP_WAIT); + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - write complete", iod->iod_indev, iod); + + return SCPE_OK; + } + + DCSTATUS(iod) &= ~(IO_1706_REPLY | IO_1706_REJECT); + iod->iod_nextAddr = iod->iod_CWA + 1; + + temp = LoadFromMem(iod->iod_CWA); + status = fw_doBDCIO(target, &temp, TRUE, iod->iod_reg); + + switch (status) { + case IO_REPLY: + DCSTATUS(iod) |= IO_1706_REPLY; + iod->iod_CWA++; + break; + + case IO_REJECT: + case IO_INTERNALREJECT: + DCSTATUS(iod) |= IO_1706_REJECT; + break; + } + } else DCSTATUS(iod) |= IO_1706_REJECT; + sim_activate(uptr, DC_IO_WAIT); + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - writing", iod->iod_indev, iod); + + return SCPE_OK; + + case IO_BDC_DONE: + /* + * The transfer has completed as far as the 1706-A is concerned. + */ + ioDone: + iod->iod_svcstate = IO_BDC_IDLE; + + DCSTATUS(iod) |= IO_ST_EOP; + DCSTATUS(iod) &= ~IO_ST_BUSY; + + if (ISENABLED(iod, IO_DIR_EOP) && (iod->iod_equip != 0)) { + DEVICE *dptr = iod->iod_indev; + + if ((dptr->dctrl & DBG_DINTR) != 0) + fprintf(DBGOUT, + "%s%s - Generate EOP interrupt\r\n", + INTprefix, dptr->name); + DCSTATUS(iod) |= IO_ST_INT; + RaiseExternalInterrupt(dptr); + } + + if ((dptr->dctrl & DBG_DSTATE) != 0) + DCstate("dc_svc() - EOP set", iod->iod_indev, iod); + + return SCPE_OK; + } + } + return SCPE_NXDEV; +} + +/* + * Reset routine + */ +t_stat dc_reset(DEVICE *dptr) +{ + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + DEVRESET(iod); + + DCSTATUS(iod) = IO_ST_READY; + + return SCPE_OK; +} + +/* + * Set the interrupt level for a buffered data channel. + */ +t_stat set_intr(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + IO_DEVICE *iod = (IO_DEVICE *)uptr->up7; + t_value v; + t_stat r; + + if (cptr == NULL) + return SCPE_ARG; + + v = get_uint(cptr, DEV_RDX, 15, &r); + if (r != SCPE_OK) + return r; + if (v == 0) + return SCPE_ARG; + + iod->iod_equip = v; + iod->iod_interrupt = 1 << v; + return SCPE_OK; +} + +/* + * Display the current interrupt level. + */ +t_stat show_intr(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + IO_DEVICE *iod = (IO_DEVICE *)uptr->up7; + + if (iod->iod_equip != 0) { + fprintf(st, "Interrupt: "); + fprint_val(st, (t_value)iod->iod_equip, DEV_RDX, 8, PV_LEFT); + } else fprintf(st, "Interrupt: None"); + return SCPE_OK; +} + +/* + * Display buffered data channel target device and equipment address + */ +t_stat show_target(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + DEVICE *dptr; + IO_DEVICE *iod; + + if (uptr == NULL) + return SCPE_IERR; + + iod = (IO_DEVICE *)uptr->up8; + dptr = iod->iod_indev; + + fprintf(st, "Target: %s (%s), Equip: %d", + sim_dname(dptr), iod->iod_model, iod->iod_equip); + return SCPE_OK; +} + +/* + * Check if I/O should be rejected. I/O allowed if: + * + * Reg. Write (OUT) Read (INP) + * + * 00 Not busy Not busy + * 01 Not busy Always allowed + * 02 Not busy Always allowed + * 03 Not busy Always allowed + */ +t_bool DCreject(IO_DEVICE *iod, t_bool output, uint8 reg) +{ + if (output || (reg == 0)) + return (DCSTATUS(iod) & IO_ST_BUSY) != 0; + + return FALSE; +} + +/* + * Start a buffered data channel transfer. Note that target may be NULL if + * an attempt is made to access a device which is not connected to the + * buffered data channel. We need to delay starting the transaction so that + * there is sufficient time to grab the current bufferered data channel + * status and terminate the transfer before starting the actual transfer. + * The diagnostics check for this particular case. + */ +enum IOstatus DCxfer(IO_DEVICE *iod, IO_DEVICE *target, t_bool output) +{ + DEVICE *dptr = (DEVICE *)iod->iod_indev; + + iod->iod_LWA = LoadFromMem(IOAreg); + iod->iod_CWA = iod->iod_nextAddr = ++IOAreg; + iod->iod_target = target; + if (target != NULL) + iod->iod_reg = IOQreg & target->iod_rmask; + + DCSTATUS(iod) &= ~IO_ST_EOP; + DCSTATUS(iod) |= IO_ST_BUSY; + + iod->iod_svcstate = output ? IO_BDC_STARTW : IO_BDC_STARTR; + sim_activate(&dptr->units[0], DC_START_WAIT); + + if ((dptr->dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%s%s - starting %s transfer, cur: %04X, last: %04X\r\n", + INTprefix, dptr->name, output ? "output" : "input", + iod->iod_CWA, iod->iod_LWA); + + return IO_REPLY; +} + +/* + * Perform a buffered data channel input operation + */ +enum IOstatus DCin(IO_DEVICE *iod, uint8 reg) +{ + IO_DEVICE *target; + enum IOstatus status; + + /* + * If the "Continue" bit is set in Q, use the last I/O address and treat the + * request as a direct input/output operation. + */ + if ((IOQreg & IO_CONTINUE) != 0) { + IOQreg = iod->iod_lastIO; + reg = 0; + } else iod->iod_lastIO = IOQreg; + + /* + * The framework filters out INP requests for the status register. + */ + switch (reg) { + /* + * Perform a direct input request from the target device. + */ + case 0x00: + /* + * Find the target device to be used. + */ + if ((target = fw_findChanDevice(iod, IOQreg)) == NULL) + return IO_REJECT; + + if ((target->iod_indev->dctrl & DBG_DSTATE) != 0) + if (target->iod_state != NULL) + (*target->iod_state)("before direct in", target->iod_indev, target); + + status = fw_doIO(target->iod_indev, FALSE); + + if ((target->iod_indev->dctrl & DBG_DSTATE) != 0) + if (target->iod_state != NULL) + (*target->iod_state)("after direct in", target->iod_indev, target); + + return status; + + /* + * Terminate buffer, 1706 Current Address. + */ + case 0x01: + iod->iod_svcstate = IO_BDC_IDLE; + DCSTATUS(iod) &= ~IO_ST_BUSY; + /* FALLTHROUGH */ + + /* + * 1706 Current Address. May be the next address depending on where we + * are in the actual transfer sequence. + */ + case 0x03: + Areg = iod->iod_nextAddr; + return IO_REPLY; + } + return IO_REJECT; +} + +/* + * Perform a buffered data channel output operation + */ +enum IOstatus DCout(IO_DEVICE *iod, uint8 reg) +{ + IO_DEVICE *target; + enum IOstatus status; + + /* + * If the "Continue" bit is set in Q, use the last I/O address and treat the + * request as a direct input/output operation. + */ + if ((IOQreg & IO_CONTINUE) != 0) { + IOQreg = iod->iod_lastIO; + reg = 0; + } else iod->iod_lastIO = IOQreg; + + /* + * Find the target device to be used. If the target device is not connected + * to the buffered data channel, the REJECT will eventually be processed + * in dc_svc(). + */ + target = fw_findChanDevice(iod, IOQreg); + + if ((target == NULL) && (reg == 0x00)) + return IO_REJECT; + + switch (reg) { + /* + * Perform a direct output request to the target device. + */ + case 0x00: + if ((target->iod_indev->dctrl & DBG_DSTATE) != 0) + if (target->iod_state != NULL) + (*target->iod_state)("before direct out", target->iod_indev, target); + + status = fw_doIO(target->iod_indev, TRUE); + + if ((target->iod_indev->dctrl & DBG_DSTATE) != 0) + if (target->iod_state != NULL) + (*target->iod_state)("after direct out", target->iod_indev, target); + + return status; + + /* + * Command function to the 1706-A. + */ + case 0x01: + if ((IOAreg & IO_1706_EOP) != 0) { + iod->OLDIENABLE = iod->IENABLE; + if ((IOAreg & IO_1706_SET) != 0) + iod->IENABLE |= IO_DIR_EOP; + else iod->IENABLE &= ~IO_DIR_EOP; + + DCSTATUS(iod) &= ~(IO_ST_INT | IO_ST_EOP); + rebuildPending(); + } + return IO_REPLY; + + /* + * Initiate buffered output on the 1706-A. + */ + case 0x02: + return DCxfer(iod, target, TRUE); + + /* + * Initiate buffered input on the 1706-A. + */ + case 0x03: + return DCxfer(iod, target, FALSE); + } + return IO_REJECT; +} + +/* + * Build the buffered data channel tables. + */ +void buildDCtables(void) +{ + int i; + uint8 chan; + DEVICE *dptr; + UNIT *uptr; + + dca_dev.numunits = 0; + dcb_dev.numunits = 0; + dcc_dev.numunits = 0; + + dca_dev.units[0].up7 = &DCAdev; + dcb_dev.units[0].up7 = &DCBdev; + dcc_dev.units[0].up7 = &DCCdev; + + for (i = 0; i < 16; i++) { + if ((dptr = IOdev[i]) != NULL) { + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + if (((chan = iod->iod_dc) != 0) && + ((iod->iod_flags & AQ_ONLY) == 0)) { + dptr = dc_devices[IDX_FROM_CHAN(chan)]; + uptr = &dptr->units[dptr->numunits]; + if (dptr->numunits < IO_1706_DEVS) { + uptr->up8 = iod; + dptr->numunits++; + } + } + } + } +} + +/* + * Create bit map of interrupts asserted by the Buffered Data Channels. + */ +uint16 dcINTR(void) +{ + uint16 result = 0; + + if ((DCSTATUS(&DCAdev) & IO_ST_INT) != 0) + result |= DCAdev.iod_interrupt; + if ((DCSTATUS(&DCBdev) & IO_ST_INT) != 0) + result |= DCBdev.iod_interrupt; + if ((DCSTATUS(&DCCdev) & IO_ST_INT) != 0) + result |= DCCdev.iod_interrupt; + + return result; +} diff --git a/CDC1700/cdc1700_defs.h b/CDC1700/cdc1700_defs.h new file mode 100644 index 00000000..c61187d7 --- /dev/null +++ b/CDC1700/cdc1700_defs.h @@ -0,0 +1,919 @@ +/* + + 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_defs.h: CDC1700 simulator definitions + */ + +#ifndef _CDC1700_DEFS_H +#define _CDC1700_DEFS_H + +#include "sim_defs.h" +#include "sim_tape.h" + +#define DBGOUT (sim_deb != NULL ? sim_deb : stdout) + +/* + * Private status codes + */ +#define SCPE_LOOP 1 /* Indirect addressing loop */ +#define SCPE_SSTOP 2 /* Selective stop */ +#define SCPE_INVEXI 3 /* Invalid bit in EXI delta */ +#define SCPE_IBKPT 4 /* Breakpoint */ +#define SCPE_REJECT 5 /* Stop on reject */ + +/* + * Private device flags + */ +#define DEV_V_REJECT (DEV_V_UF + 1) /* Stop on reject enabled */ +#define DEV_V_NOEQUIP (DEV_V_UF + 2) /* Not an equipment device */ +#define DEV_V_INDEV (DEV_V_UF + 3) /* Input device for IO_DEVICE */ +#define DEV_V_OUTDEV (DEV_V_UF + 4) /* Output device for IO_DEVICE */ +#define DEV_V_PROTECT (DEV_V_UF + 5) /* Device supports protection */ +#define DEV_V_PROTECTED (DEV_V_UF + 6) /* Device protection enabled */ +#define DEV_V_REVERSE (DEV_V_UF + 7) /* DP reverse addressing */ +#define DEV_V_FIXED (DEV_V_UF + 7) /* CDD fixed drive first addressing */ + +#define DEV_REJECT (1 << DEV_V_REJECT) +#define DEV_NOEQUIP (1 << DEV_V_NOEQUIP) +#define DEV_INDEV (1 << DEV_V_INDEV) +#define DEV_OUTDEV (1 << DEV_V_OUTDEV) +#define DEV_PROTECT (1 << DEV_V_PROTECT) +#define DEV_PROTECTED (1 << DEV_V_PROTECTED) +#define DEV_REVERSE (1 << DEV_V_REVERSE) +#define DEV_FIXED (1 << DEV_V_FIXED) + +/* + * CPU debug flags + */ +#define DBG_V_DISASS 0 /* Disassemble on execution */ +#define DBG_V_IDISASS 1 /* Disassemble during interrupt */ +#define DBG_V_INTR 2 /* Indicate interrupt execution */ +#define DBG_V_TRACE 3 /* Trace register content */ +#define DBG_V_ITRACE 4 /* Trace during interrupt */ +#define DBG_V_TARGET 5 /* Output target address */ +#define DBG_V_INPUT 6 /* Trace input operations */ +#define DBG_V_OUTPUT 7 /* Trace output operations */ +#define DBG_V_FULL 8 /* Full Trace (emulator debug) */ +#define DBG_V_INTLVL 9 /* Prefix with interrupt level */ +#define DBG_V_PROTECT 10 /* Display protect fault information */ +#define DBG_V_MISSING 11 /* Display access to missing devices */ + +#define DBG_DISASS (1 << DBG_V_DISASS) +#define DBG_IDISASS (1 << DBG_V_IDISASS) +#define DBG_INTR (1 << DBG_V_INTR) +#define DBG_TRACE (1 << DBG_V_TRACE) +#define DBG_ITRACE (1 << DBG_V_ITRACE) +#define DBG_TARGET (1 << DBG_V_TARGET) +#define DBG_INPUT (1 << DBG_V_INPUT) +#define DBG_OUTPUT (1 << DBG_V_OUTPUT) +#define DBG_FULL (1 << DBG_V_FULL) +#define DBG_INTLVL (1 << DBG_V_INTLVL) +#define DBG_PROTECT (1 << DBG_V_PROTECT) +#define DBG_MISSING (1 << DBG_V_MISSING) + +/* + * Default device radix + */ +#define DEV_RDX 16 + +/* + * Private unit flags + */ +#define UNIT_V_7TRACK (MTUF_V_UF + 0) /* 7-track tape transport */ +#define UNIT_V_WPROT (MTUF_V_UF + 1) /* Write protected */ + +#define UNIT_V_854 (UNIT_V_UF + 0) /* 854 vs. 853 disk pack drive */ +#define UNIT_V_856_4 (UNIT_V_UF + 0) /* 856_4 vs. 856_2 drive */ + +#define UNIT_7TRACK (1 << UNIT_V_7TRACK) +#define UNIT_WPROT (1 << UNIT_V_WPROT) +#define UNIT_854 (1 << UNIT_V_854) +#define UNIT_856_4 (1 << UNIT_V_856_4) + +/* + * CPU + */ +#define MAXMEMSIZE 65536 +#define DEFAULTMEMSIZE 32768 + +/* + * Compute the actual memory address based on the amount of memory installed + * on the system. Note we only support power of 2 memories like the real + * hardware - the system reference manual says that 12K and 24K systems are + * possible but not supported by standard software. + */ +#define MEMADDR(p) ((p) & (cpu_unit.capac - 1)) + +/* + * Protect bit access + */ +#define SETPROTECT(a) P[MEMADDR(a)] = 1 +#define CLRPROTECT(a) P[MEMADDR(a)] = 0 +#define ISPROTECTED(a) P[MEMADDR(a)] + +/* + * Max count of indirect addressing. Used to avoid infinite loops. + */ +#define MAXINDIRECT 10000 + +/* + * Register access + */ +#define INCP Preg = MEMADDR(Preg + 1) + +/* + * I/O operations + */ +enum IOstatus { + IO_REPLY, /* Device sent a reply */ + IO_REJECT, /* Device sent a reject */ + IO_INTERNALREJECT /* I/O rejected internally */ +}; + +#define SIGN 0x8000 +#define MAXPOS 0x7FFF +#define MAXNEG 0xFFFF +#define ABS(v) (((v) & SIGN) ? ~(v) : (v)) + +/* + * Instruction layout: + * + * Storage reference instructions: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | op |re|in|i1|i2| delta | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * Register reference instructions: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | 0 0 0 0| F1 | modifier | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * Inter-register instructions: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | 0 0 0 0| 1 0 0 0|lp|xr| origin | dest | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * Shift instructions: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | 0 0 0 0| 1 1 1 1|LR| A| Q| count | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * Skip instructions: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | 0 0 0 0| 0 0 0 1| type | count | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + */ +#define MOD_RE 0x800 +#define MOD_IN 0x400 +#define MOD_I1 0x200 +#define MOD_I2 0x100 + +#define ISCONSTANT(i) ((i & (MOD_RE | MOD_IN | 0xFF)) == 0) + +#define OPC_MASK 0xF000 +#define OPC_ADQ 0xF000 +#define OPC_LDQ 0xE000 +#define OPC_RAO 0xD000 +#define OPC_LDA 0xC000 +#define OPC_EOR 0xB000 +#define OPC_AND 0xA000 +#define OPC_SUB 0x9000 +#define OPC_ADD 0x8000 +#define OPC_SPA 0x7000 +#define OPC_STA 0x6000 +#define OPC_RTJ 0x5000 +#define OPC_STQ 0x4000 +#define OPC_DVI 0x3000 +#define OPC_MUI 0x2000 +#define OPC_JMP 0x1000 +#define OPC_SPECIAL 0x0000 + +#define OPC_SPECIALMASK 0x0F00 + +#define OPC_SLS 0x0000 + +#define OPC_SKIPS 0x0100 +#define OPC_SKIPMASK 0x00F0 +#define OPC_SKIPCOUNT 0x000F + +#define OPC_SAZ (OPC_SKIPS | 0x00) +#define OPC_SAN (OPC_SKIPS | 0x10) +#define OPC_SAP (OPC_SKIPS | 0x20) +#define OPC_SAM (OPC_SKIPS | 0x30) +#define OPC_SQZ (OPC_SKIPS | 0x40) +#define OPC_SQN (OPC_SKIPS | 0x50) +#define OPC_SQP (OPC_SKIPS | 0x60) +#define OPC_SQM (OPC_SKIPS | 0x70) +#define OPC_SWS (OPC_SKIPS | 0x80) +#define OPC_SWN (OPC_SKIPS | 0x90) +#define OPC_SOV (OPC_SKIPS | 0xA0) +#define OPC_SNO (OPC_SKIPS | 0xB0) +#define OPC_SPE (OPC_SKIPS | 0xC0) +#define OPC_SNP (OPC_SKIPS | 0xD0) +#define OPC_SPF (OPC_SKIPS | 0xE0) +#define OPC_SNF (OPC_SKIPS | 0xF0) + +#define OPC_INP 0x0200 +#define OPC_OUT 0x0300 +#define OPC_EIN 0x0400 +#define OPC_IIN 0x0500 +#define OPC_ECA 0x0580 +#define OPC_DCA 0x05C0 +#define OPC_SPB 0x0600 +#define OPC_CPB 0x0700 + +#define OPC_INTER 0x0800 + #define MOD_LP 0x80 + #define MOD_XR 0x40 + #define MOD_O_A 0x20 + #define MOD_O_Q 0x10 + #define MOD_O_M 0x08 + #define MOD_D_A 0x04 + #define MOD_D_Q 0x02 + #define MOD_D_M 0x01 +#define OPC_AAM (OPC_INTER | MOD_O_A | MOD_O_M) +#define OPC_AAQ (OPC_INTER | MOD_O_A | MOD_O_Q) +#define OPC_AAB (OPC_INTER | MOD_O_A | MOD_O_Q | MOD_O_M) +#define OPC_CLR (OPC_INTER | MOD_XR) +#define OPC_TCM (OPC_INTER | MOD_XR | MOD_O_M) +#define OPC_TCQ (OPC_INTER | MOD_XR | MOD_O_Q) +#define OPC_TCB (OPC_INTER | MOD_XR | MOD_O_Q | MOD_O_M) +#define OPC_TCA (OPC_INTER | MOD_XR | MOD_O_A) +#define OPC_EAM (OPC_INTER | MOD_XR | MOD_O_A | MOD_O_M) +#define OPC_EAQ (OPC_INTER | MOD_XR | MOD_O_A | MOD_O_Q) +#define OPC_EAB (OPC_INTER | MOD_XR | MOD_O_A | MOD_O_Q | MOD_O_M) +#define OPC_SET (OPC_INTER | MOD_LP) +#define OPC_TRM (OPC_INTER | MOD_LP | MOD_O_M) +#define OPC_TRQ (OPC_INTER | MOD_LP | MOD_O_Q) +#define OPC_TRB (OPC_INTER | MOD_LP | MOD_O_Q | MOD_O_M) +#define OPC_TRA (OPC_INTER | MOD_LP | MOD_O_A) +#define OPC_LAM (OPC_INTER | MOD_LP | MOD_O_A | MOD_O_M) +#define OPC_LAQ (OPC_INTER | MOD_LP | MOD_O_A | MOD_O_Q) +#define OPC_LAB (OPC_INTER | MOD_LP | MOD_O_A | MOD_O_Q | MOD_O_M) +#define OPC_CAM (OPC_INTER | MOD_LP | MOD_XR | MOD_O_A | MOD_O_M) +#define OPC_CAQ (OPC_INTER | MOD_LP | MOD_XR | MOD_O_A | MOD_O_Q) +#define OPC_CAB (OPC_INTER | MOD_LP | MOD_XR | MOD_O_A | MOD_O_Q | MOD_O_M) + +#define OPC_INA 0x0900 +#define OPC_ENA 0x0A00 +#define OPC_NOP 0x0B00 +#define OPC_ENQ 0x0C00 +#define OPC_INQ 0x0D00 +#define OPC_EXI 0x0E00 + +#define OPC_MODMASK 0x00FF +#define EXTEND16(v) ((v) & 0x8000) ? (v) | 0xFFFF0000 : (v) +#define EXTEND8(v) ((v) & 0x80) ? (v) | 0xFF00 : (v) +#define EXTEND4(v) ((v) & 0x8) ? (v) | 0xFFF0 : (v) +#define TRUNC16(v) ((v) & 0xFFFF) +#define CANEXTEND8(v) (((v) & 0xFF80) == 0xFF80) + +#define OPC_SHIFTS 0x0F00 +#define OPC_SHIFTMASK 0x00E0 +#define MOD_LR 0x80 +#define MOD_S_A 0x40 +#define MOD_S_Q 0x20 +#define OPC_SHIFTCOUNT 0x001F +#define OPC_ADDRMASK 0x00FF + +#define OPC_QRS (OPC_SHIFTS | MOD_S_Q) +#define OPC_ARS (OPC_SHIFTS | MOD_S_A) +#define OPC_LRS (OPC_SHIFTS | MOD_S_A | MOD_S_Q) +#define OPC_QLS (OPC_SHIFTS | MOD_LR | MOD_S_Q) +#define OPC_ALS (OPC_SHIFTS | MOD_LR | MOD_S_A) +#define OPC_LLS (OPC_SHIFTS | MOD_LR | MOD_S_A | MOD_S_Q) + +/* + * Interrupt vector definitions + */ +#define INTERRUPT_BASE 0x100 +#define INTERRUPT_00 (INTERRUPT_BASE + 0x00) +#define INTERRUPT_01 (INTERRUPT_BASE + 0x04) +#define INTERRUPT_02 (INTERRUPT_BASE + 0x08) +#define INTERRUPT_03 (INTERRUPT_BASE + 0x0C) +#define INTERRUPT_04 (INTERRUPT_BASE + 0x10) +#define INTERRUPT_05 (INTERRUPT_BASE + 0x14) +#define INTERRUPT_06 (INTERRUPT_BASE + 0x18) +#define INTERRUPT_07 (INTERRUPT_BASE + 0x1C) +#define INTERRUPT_08 (INTERRUPT_BASE + 0x20) +#define INTERRUPT_09 (INTERRUPT_BASE + 0x24) +#define INTERRUPT_10 (INTERRUPT_BASE + 0x28) +#define INTERRUPT_11 (INTERRUPT_BASE + 0x2C) +#define INTERRUPT_12 (INTERRUPT_BASE + 0x30) +#define INTERRUPT_13 (INTERRUPT_BASE + 0x34) +#define INTERRUPT_14 (INTERRUPT_BASE + 0x38) +#define INTERRUPT_15 (INTERRUPT_BASE + 0x3C) + +#define INTR_BASIC 2 +#define INTR_1705 16 + +/* + * I/O definitions. + */ +#define IO_CONTINUE 0x8000 +#define IO_W 0x7800 +#define IO_EQUIPMENT 0x0780 +#define IO_COMMAND 0x007F +/* + * Standard director functions + */ +#define IO_DIR_STOP 0x0040 /* Stop motion */ +#define IO_DIR_START 0x0020 /* Start motion */ +#define IO_DIR_ALARM 0x0010 /* Alarm int req. */ +#define IO_DIR_EOP 0x0008 /* End of operation int req. */ +#define IO_DIR_DATA 0x0004 /* Data int req. */ +#define IO_DIR_CINT 0x0002 /* Clear interrupts */ +#define IO_DIR_CCONT 0x0001 /* Clear controller */ + +/* + * Illegal combination of functions - Start + Stop + */ +#define STARTSTOP(v) (((v) & \ + (IO_DIR_START | IO_DIR_STOP)) == \ + (IO_DIR_START | IO_DIR_STOP)) +/* + * Standard status bits + */ +#define IO_ST_PARITY 0x0100 /* Parity error */ +#define IO_ST_PROT 0x0080 /* Protected */ +#define IO_ST_LOST 0x0040 /* Lost data */ +#define IO_ST_ALARM 0x0020 /* Alarm */ +#define IO_ST_EOP 0x0010 /* End of operation */ +#define IO_ST_DATA 0x0008 /* Data */ +#define IO_ST_INT 0x0004 /* Interrupt */ +#define IO_ST_BUSY 0x0002 /* Device busy */ +#define IO_ST_READY 0x0001 /* Device ready */ + +/* + * The following type/values may be used to differentiate processing when + * a single device driver is used to emulate multiple controller types (e.g. + * the LP device driver emulates both 1740 and 1742-30/-120 controllers). + */ +enum IOdevtype { + IOtype_default, /* Initial value */ + IOtype_dev1, /* Device specific values */ + IOtype_dev2, + IOtype_dev3, + IOtype_dev4, + IOtype_dev5, + IOtype_dev6, + IOtype_dev7, + IOtype_dev8 +}; + +/* + * I/O framework device structure + */ +struct io_device { + const char *iod_name; + const char *iod_model; + enum IOdevtype iod_type; + uint8 iod_equip; + uint8 iod_station; + uint16 iod_interrupt; + uint16 iod_dcbase; + DEVICE *iod_indev; + DEVICE *iod_outdev; + UNIT *iod_unit; + t_bool (*iod_reject)(struct io_device *, t_bool, uint8); + enum IOstatus (*iod_IOread)(struct io_device *, uint8); + enum IOstatus (*iod_IOwrite)(struct io_device *, uint8); + enum IOstatus (*iod_BDCread)(struct io_device *, uint16 *, uint8); + enum IOstatus (*iod_BDCwrite)(struct io_device *, uint16 *, uint8); + void (*iod_state)(const char *, DEVICE *, struct io_device *); + t_bool (*iod_intr)(struct io_device *); + uint16 (*iod_raised)(DEVICE *); + void (*iod_clear)(DEVICE *); + uint16 iod_ienable; + uint16 iod_oldienable; + uint16 iod_imask; + uint16 iod_dmask; + uint16 iod_smask; + uint16 iod_cmask; + uint16 iod_rmask; + uint8 iod_regs; + uint8 iod_validmask; + uint8 iod_readmap; + uint8 iod_rejmapR; + uint8 iod_rejmapW; + uint8 iod_flags; + uint8 iod_dc; + uint16 iod_readR[8]; + uint16 iod_writeR[8]; + uint16 iod_prevR[8]; + uint16 iod_forced; + t_uint64 iod_event; + uint16 iod_private; + void *iod_private2; + uint16 iod_private3; + t_bool iod_private4; + const char *iod_private5; + uint16 iod_private6; + uint16 iod_private7; + uint16 iod_private8; + uint8 iod_private9; + t_bool iod_private10; +}; +#define STATUS iod_readR[1] +#define DEVSTATUS(iod) ((iod)->iod_readR[1]) +#define DCSTATUS(iod) ((iod)->iod_readR[2]) +#define FUNCTION iod_writeR[1] +#define IENABLE iod_ienable +#define OLDIENABLE iod_oldienable +#define ENABLED(iod) ((iod)->iod_ienable) +#define ISENABLED(iod, mask) \ + (((iod)->iod_ienable & mask) != 0) + +#define SETSTICKY(iod, reg, value) \ + ((iod)->iod_sticky[reg] |= value) +#define CLRSTICKY(iod, reg, value) \ + ((iod)->iod_sticky[reg] &= ~value) + +#define MASK_REGISTER0 0x01 +#define MASK_REGISTER1 0x02 +#define MASK_REGISTER2 0x04 +#define MASK_REGISTER3 0x08 +#define MASK_REGISTER4 0x10 +#define MASK_REGISTER5 0x20 +#define MASK_REGISTER6 0x40 +#define MASK_REGISTER7 0x80 + +#define STATUS_ZERO 0x01 +#define DEVICE_DC 0x02 +#define AQ_ONLY 0x04 + +#define IODEV(name, model, id, equ, sta, base, busy, ior, iow, bdcr, bdcw, dump, intr, raised, clear, mask, regs, valid, map, rejR, rejW, flags, dc, devspec) \ + { name, model, IOtype_default, equ, sta, 0, base, \ + NULL, NULL, NULL, \ + busy, ior, iow, bdcr, bdcw, dump, intr, raised, clear, \ + 0, 0, IO_##id##_INTR, IO_##id##_DIRMSK, IO_##id##_STMSK, \ + IO_##id##_STCINT | IO_ST_INT, \ + mask, regs, valid, map, rejR, rejW, flags, dc, \ + { 0, 0, 0, 0, 0, 0, 0, 0, }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, }, \ + { 0, 0, 0, 0, 0, 0, 0, 0, }, \ + 0, 0, 0, devspec, 0, FALSE, NULL, 0, 0, 0, 0, FALSE \ + } + +typedef struct io_device IO_DEVICE; + +#define IODEVICE(dev) ((IO_DEVICE *)dev->ctxt) + +#define CHANGED(iod, n) ((iod)->iod_writeR[n] ^ (iod)->iod_prevR[n]) +#define ICHANGED(iod) ((iod)->iod_ienable ^ (iod)->iod_oldienable) + +#define DEVRESET(iod) \ + (iod)->iod_ienable = 0; \ + (iod)->iod_oldienable = 0; \ + (iod)->iod_forced = 0 + +/* + * Routine type to return interrupt mask for a device. + */ +typedef uint16 devINTR(DEVICE *); + +/* + * Generic device flags + */ +#define DBG_V_DTRACE 0 /* Trace device ops */ +#define DBG_V_DSTATE 1 /* Dump device state */ +#define DBG_V_DINTR 2 /* Trace device interrupts */ +#define DBG_V_DERROR 3 /* Trace device errors */ +#define DBG_V_LOC 4 /* Dump instruction location */ +#define DBG_V_FIRSTREJ 5 /* Dump only first Reject */ +#define DBG_SPECIFIC 6 /* Start of device-specific */ + /* flags */ + +#define DBG_DTRACE (1 << DBG_V_DTRACE) +#define DBG_DSTATE (1 << DBG_V_DSTATE) +#define DBG_DINTR (1 << DBG_V_DINTR) +#define DBG_DERROR (1 << DBG_V_DERROR) +#define DBG_DLOC (1 << DBG_V_LOC) +#define DBG_DFIRSTREJ (1 << DBG_V_FIRSTREJ) + +/* + * Device specific values + */ + +/* + * CPU is treated as a device but has no interrupts, director functions or + * status bits. + */ +#define IO_CPU_INTR 0 +#define IO_CPU_DIRMSK 0 +#define IO_CPU_STMSK 0 +#define IO_CPU_STCINT 0 + +/* + * 1706-A Buffered Data Channel + */ +#define IO_1706_1_A 0x1000 /* 1706-A #1 */ +#define IO_1706_1_B 0x1800 +#define IO_1706_1_C 0x2000 +#define IO_1706_1_D 0x2800 +#define IO_1706_2_A 0x3800 /* 1706-A #2 */ +#define IO_1706_2_B 0x4000 +#define IO_1706_2_C 0x4800 +#define IO_1706_2_D 0x5000 +#define IO_1706_3_A 0x6000 /* 1706-A #3 */ +#define IO_1706_3_B 0x6800 +#define IO_1706_3_C 0x7000 +#define IO_1706_3_D 0x7800 + +#define IO_1706_SET 0x8000 /* Set/clear for ones */ +#define IO_1706_EOP 0x0001 /* Enable interrupt on EOP */ + +#define IO_1706_PROT 0x0040 /* Program protect fault */ +#define IO_1706_REPLY 0x0200 /* Device reply */ +#define IO_1706_REJECT 0x0100 /* Device reject */ +#define IO_1706_STMSK (IO_1706_REPLY | IO_1706_REJECT | IO_1706_PROT | \ + IO_ST_EOP | IO_ST_BUSY | IO_ST_READY) + +/* + * A 1706-A has no interrupts, director functions or status bits of it's own + * that are accessible through normal I/O requests. + */ +#define IO_DC_INTR 0 +#define IO_DC_DIRMSK 0 +#define IO_DC_STMSK 0 +#define IO_DC_STCINT 0 + +#define IO_1706_MAX 3 /* Max # of 1706's allowed */ +#define IO_1706_DEVS 8 /* Max devices per channel */ + +#define IDX_FROM_CHAN(c) (c - 1) + +/* + * 1711-A/B, 1712-A Teletypewriter + */ +#define IO_1711_A 0x0090 +#define IO_1711_B 0x0091 + +#define IO_1711_SREAD 0x0200 /* Select read mode */ +#define IO_1711_SWRITE 0x0100 /* Select write mode */ +#define IO_1711_DIRMSK (IO_1711_SREAD | IO_1711_SWRITE | IO_DIR_ALARM | \ + IO_DIR_EOP | IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) +#define IO_1711_INTR (IO_DIR_ALARM |IO_DIR_EOP | IO_DIR_DATA) + +#define IO_1711_MANUAL 0x0800 /* Manual interrupt */ +#define IO_1711_MON 0x0400 /* Motor on */ +#define IO_1711_RMODE 0x0200 /* Read mode */ +#define IO_1711_STMSK (IO_1711_MANUAL | IO_1711_MON | IO_1711_RMODE | \ + IO_ST_LOST | IO_ST_ALARM | IO_ST_EOP \ + | IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | IO_ST_READY) +#define IO_1711_STCINT (IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA) + +/* + * 1721-A/B/C/D, 1722-A/B Paper Tape Reader + */ +#define IO_1721_A 0x00A0 +#define IO_1721_B 0x00A1 + +#define IO_1721_DIRMSK (IO_DIR_STOP | IO_DIR_START | IO_DIR_ALARM | \ + IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) +#define IO_1721_INTR (IO_DIR_ALARM | IO_DIR_DATA) + +#define IO_1721_POWERON 0x0400 /* Power on */ +#define IO_1721_MOTIONF 0x0200 /* Paper motion failure */ +#define IO_1721_EXIST 0x0100 /* Existence code */ +#define IO_1721_STMSK (IO_1721_POWERON | IO_1721_MOTIONF | IO_1721_EXIST | \ + IO_ST_PROT | IO_ST_LOST | IO_ST_ALARM | \ + IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | IO_ST_READY) +#define IO_1721_STCINT (IO_ST_ALARM | IO_ST_DATA) + +/* + * 1723-A/B, 1724-A/B Paper Tape Punch + */ +#define IO_1723_A 0x00C0 +#define IO_1723_B 0x00C1 + +#define IO_1723_DIRMSK (IO_DIR_STOP | IO_DIR_START | IO_DIR_ALARM | \ + IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) +#define IO_1723_INTR (IO_DIR_ALARM | IO_DIR_DATA) + +#define IO_1723_TAPELOW 0x0800 /* Tape supply low */ +#define IO_1723_POWERON 0x0400 /* Power on */ +#define IO_1723_BREAK 0x0200 /* Tape break */ +#define IO_1723_EXIST 0x0100 /* Existence code */ +#define IO_1723_STMSK (IO_1723_TAPELOW | IO_1723_POWERON | IO_1723_BREAK | \ + IO_1723_EXIST | IO_ST_PROT | IO_ST_ALARM | \ + IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | IO_ST_READY) +#define IO_1723_STCINT (IO_ST_ALARM | IO_ST_DATA) + +/* + * 1726 Card Reader + */ +#define IO_1726_GATE 0x0200 /* gate card */ +#define IO_1726_NHOL 0x0400 /* Negate hollerith to acsii */ +#define IO_1726_RHOL 0x0800 /* Rel. hollerith to ascii */ +#define IO_1726_RELOAD 0x1000 /* Reload memory */ + +#define IO_1726_DIRMSK (IO_1726_RELOAD | IO_1726_RHOL | IO_1726_NHOL | \ + IO_1726_GATE | IO_DIR_ALARM | IO_DIR_EOP | \ + IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) +#define IO_1726_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA) + +#define IO_1726_ERROR 0x0100 /* Error */ +#define IO_1726_BINARY 0x0200 /* Binary card */ +#define IO_1726_SEP 0x0400 /* Separator card */ +#define IO_1726_FEED 0x0800 /* Fail to feed */ +#define IO_1726_JAM 0x1000 /* Stacker full or jam */ +#define IO_1726_EMPTY 0x2000 /* Input tray empty */ +#define IO_1726_EOF 0x4000 /* End of file */ +#define IO_1726_PWROFF 0x8000 /* Motor power off */ +#define IO_1726_STMSK (IO_1726_PWROFF | IO_1726_EOF | IO_1726_EMPTY | \ + IO_1726_JAM | IO_1726_FEED | IO_1726_SEP | \ + IO_1726_BINARY | IO_1726_ERROR | IO_ST_PROT | \ + IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA | \ + IO_ST_INT | IO_ST_BUSY | IO_ST_READY) +#define IO_1726_STCINT (IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA) + +/* + * 1729-A/B Card Reader + */ +#define IO_1729_A 0x00E0 +#define IO_1729_B 0x00E1 + +#define IO_1729_IEOR 0x0008 /* Interrupt on End of Record */ + +#define IO_1729_DIRMSK (IO_DIR_STOP | IO_DIR_START | IO_DIR_ALARM | \ + IO_1729_IEOR | IO_DIR_DATA | IO_DIR_CINT | \ + IO_DIR_CCONT) +#define IO_1729_INTR (IO_DIR_ALARM | IO_1729_IEOR | IO_DIR_DATA) + +#define IO_1729_EMPTY 0x0200 /* Read station empty */ +#define IO_1729_EXIST 0x0100 /* Existence code */ +#define IO_1729_EOR 0x0010 /* End of Record */ +#define IO_1729_STMSK (IO_1729_EMPTY | IO_1729_EXIST | IO_ST_PROT | \ + IO_ST_LOST | IO_ST_ALARM | IO_1729_EOR | \ + IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | \ + IO_ST_READY) +#define IO_1729_STCINT (IO_ST_ALARM | IO_1729_EOR | IO_ST_DATA) + +/* + * 1732-3 Magnetic Tape Controller + */ +#define IO_1732_WRITE 0x0080 /* Write motion */ +#define IO_1732_READ 0x0100 /* Read motion */ +#define IO_1732_BACKSP 0x0180 /* Backspace */ +#define IO_1732_WFM 0x0280 /* Write file mark/tape mark */ +#define IO_1732_SFWD 0x0300 /* Search FM/TM forward */ +#define IO_1732_SBACK 0x0380 /* Search FM/TM backward */ +#define IO_1732_REWL 0x0400 /* Rewind load */ +#define IO_1732_MOTION 0x0780 /* Motion field mask */ + +#define IO_1732_LRT 0x1000 /* Select low read threshold */ +#define IO_1732_DESEL 0x0800 /* Deselect tape unit */ +#define IO_1732_SEL 0x0400 /* Select tape unit */ +#define IO_1732_UNIT 0x0180 /* Unit mask */ +#define IO_1732_ASSEM 0x0040 /* Assembly/Disassembly */ +#define IO_1732_1600 0x0020 /* Select 1600 BPI */ +#define IO_1732_556 0x0010 /* Select 556 BPI */ +#define IO_1732_800 0x0008 /* Select 800 BPI */ +#define IO_1732_BINARY 0x0004 /* Binary mode */ +#define IO_1732_BCD 0x0002 /* BCD mode */ +#define IO_1732_PARITY 0x0006 /* Parity mask */ +#define IO_1732_CHAR 0x0001 /* Character mode */ + + /* 1732-A/B changes */ +#define IO_1732A_REWU 0x0600 /* Rewind and unload */ +#define IO_1732A_UNIT 0x0380 /* Unit mask */ +#define IO_1732A_200 0x0020 /* Select 200 BPI */ + +#define IO_1732_DIRMSK (IO_1732_MOTION | IO_DIR_ALARM | IO_DIR_EOP | \ + IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) + +#define IO_1732_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA) + +#define IO_1732_PROT 0x8000 /* Protect fault */ +#define IO_1732_SPE 0x4000 /* Storage parity error */ +#define IO_1732_FILL 0x2000 /* Odd byte record in */ + /* assembly mode */ +#define IO_1732_ACTIVE 0x1000 /* Controller active */ +#define IO_1732_FMARK 0x0800 /* At file mark */ +#define IO_1732_BOT 0x0400 /* At BOT */ +#define IO_1732_EOT 0x0200 /* At EOT */ +#define IO_1732_STMSK (IO_1732_PROT | IO_1732_SPE | IO_1732_FILL | \ + IO_1732_ACTIVE | IO_1732_FMARK | IO_1732_BOT | \ + IO_1732_EOT | IO_ST_PARITY | IO_ST_PROT | \ + IO_ST_LOST | IO_ST_ALARM | IO_ST_EOP | \ + IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | \ + IO_ST_READY) +#define IO_1732_STCINT (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA) + +#define IO_ST2_LRT 0x0200 /* Low read threshold */ +#define IO_ST2_IDABORT 0x0100 /* ID abort */ +#define IO_ST2_PETRANS 0x0080 /* PE capable transport */ +#define IO_ST2_PELOST 0x0040 /* PE - lost data */ +#define IO_ST2_PEWARN 0x0020 /* PE - error warning */ +#define IO_ST2_WENABLE 0x0010 /* Write enable */ +#define IO_ST2_7TRACK 0x0008 /* Seven track transport */ +#define IO_ST2_1600 0x0004 /* 1600 BPI */ +#define IO_ST2_800 0x0002 /* 800 BPI */ +#define IO_ST2_556 0x0001 /* 556 BPI */ +#define IO_1732_ST2MSK (IO_ST2_LRT | IO_ST2_IDABORT | IO_ST2_PETRANS | \ + IO_ST2_PELOST | IO_ST2_PEWARN | IO_ST2_WENABLE | \ + IO_ST2_7TRACK | IO_ST2_1600 | IO_ST2_800 | \ + IO_ST2_556) +#define IO_1732A_ST2MSK (IO_ST2_WENABLE | IO_ST2_7TRACK | IO_ST2_800 | \ + IO_ST2_556) + +/* + * 1733-2 Cartridge Disk Drive Controller + */ +#define IO_1733_USC 0x0600 /* Unit select code */ +#define IO_1733_USEL 0x0100 /* Unit select */ +#define IO_1733_UDSEL 0x0080 /* Unit de-select */ +#define IO_1733_RBINT 0x0004 /* Ready and not busy int */ +#define IO_1733_DIRMSK (IO_1733_USC | IO_1733_USEL | IO_1733_UDSEL | \ + IO_DIR_ALARM | IO_DIR_EOP | IO_1733_RBINT | \ + IO_DIR_CINT) +#define IO_1733_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_1733_RBINT) + +#define IO_1733_DSEEK 0x8000 /* Drive seek error */ +#define IO_1733_SPROT 0x4000 /* Storage protect fault */ +#define IO_1733_SPAR 0x2000 /* Storage parity error */ +#define IO_1733_SINGLE 0x1000 /* Single density */ +#define IO_1733_CSEEK 0x0800 /* Controller seek error */ +#define IO_1733_ADDRERR 0x0400 /* Address error */ +#define IO_1733_LOST 0x0200 /* Lost data */ +#define IO_1733_CWERR 0x0100 /* Checkword error */ +#define IO_1733_NOCOMP 0x0040 /* No compare */ +#define IO_1733_ONCYL 0x0008 /* On cylinder */ +#define IO_1733_STMSK (IO_1733_DSEEK | IO_1733_SPROT | IO_1733_SPAR | \ + IO_1733_SINGLE | IO_1733_CSEEK | IO_1733_ADDRERR | \ + IO_1733_LOST | IO_1733_CWERR | IO_ST_PROT | \ + IO_1733_NOCOMP | IO_ST_ALARM | IO_ST_EOP | \ + IO_1733_ONCYL | IO_ST_INT | IO_ST_BUSY | \ + IO_ST_READY) +#define IO_1733_STCINT (IO_ST_ALARM | IO_ST_EOP) + +/* + * 1738-A/B Disk Pack Controller + */ +#define IO_1738_USC 0x0200 /* Unit select code */ +#define IO_1738_USEL 0x0100 /* Unit select */ +#define IO_1738_REL 0x0080 /* Release */ +#define IO_1738_RBINT 0x0004 /* Ready and not busy int */ +#define IO_1738_DIRMSK (IO_1738_USC | IO_1738_USEL | IO_1738_REL | \ + IO_DIR_ALARM | IO_DIR_EOP | IO_1738_RBINT | \ + IO_DIR_CINT) +#define IO_1738_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_1738_RBINT) + +#define IO_1738_SPROT 0x4000 /* Storage protect fault */ +#define IO_1738_SPAR 0x2000 /* Storage parity error */ +#define IO_1738_DEFECT 0x1000 /* Defective track */ +#define IO_1738_ADDRERR 0x0800 /* Address error */ +#define IO_1738_SKERR 0x0400 /* Seek error */ +#define IO_1738_LOST 0x0200 /* Lost data */ +#define IO_1738_CWERR 0x0100 /* Checkword error */ +#define IO_1738_NOCOMP 0x0040 /* No compare */ +#define IO_1738_ONCYL 0x0008 /* On cylinder */ +#define IO_1738_STMSK (IO_1738_SPROT | IO_1738_SPAR | IO_1738_DEFECT | \ + IO_1738_ADDRERR | IO_1738_SKERR | IO_1738_LOST | \ + IO_1738_CWERR | IO_ST_PROT | IO_1738_NOCOMP | \ + IO_ST_ALARM | IO_ST_EOP | IO_1738_ONCYL | \ + IO_ST_INT | IO_ST_BUSY | IO_ST_READY) +#define IO_1738_STCINT (IO_ST_ALARM | IO_ST_EOP) + +/* + * 1740 - Line Printer Controller + */ +#define IO_1740_CPRINT 0x0001 /* Clear printer */ +#define IO_1740_DIRMSK (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA | \ + IO_DIR_CINT | IO_1740_CPRINT) +#define IO_1740_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA) + +#define IO_1740_L12 0x4000 /* Level 12 */ +#define IO_1740_L7 0x0200 /* Level 7 */ +#define IO_1740_L6 0x0100 /* Level 6 */ +#define IO_1740_L5 0x0080 /* Level 5 */ +#define IO_1740_L4 0x0040 /* Level 4 */ +#define IO_1740_L3 0x0020 /* Level 3 */ +#define IO_1740_L2 0x0010 /* Level 2 */ +#define IO_1740_L1 0x0008 /* Level 1 */ +#define IO_1740_DSP 0x0004 /* Double space */ +#define IO_1740_SSP 0x0002 /* Single space */ +#define IO_1740_PRINT 0x0001 /* Print */ +#define IO_1740_DIR2MSK (IO_1740_L12 | IO_1740_L7 | IO_1740_L6 | \ + IO_1740_L5 | IO_1740_L4 | IO_1740_L3 | \ + IO_1740_L2 | IO_1740_L1 | IO_1740_DSP | \ + IO_1740_SSP | IO_1740_PRINT) + +#define IO_1740_LEVELS (IO_1740_L1 | IO_1740_L2 | IO_1740_L3 | \ + IO_1740_L4 | IO_1740_L5 | IO_1740_L6 | \ + IO_1740_L7 | IO_1740_L12) + +#define IO_1740_MOTION (IO_1740_SSP | IO_1740_DSP | IO_1740_LEVELS) + +#define IO_1740_STMSK (IO_ST_PROT | IO_ST_ALARM | IO_ST_EOP | \ + IO_ST_DATA | IO_ST_INT | IO_ST_BUSY | \ + IO_ST_READY) +#define IO_1740_STCINT (IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA) + +/* + * 1742-30/-120 Line Printer + */ +#define IO_1742_PRINT 0x0020 /* Print */ +#define IO_1742_DIRMSK (IO_1742_PRINT | IO_DIR_ALARM | IO_DIR_EOP | \ + IO_DIR_DATA | IO_DIR_CINT | IO_DIR_CCONT) +#define IO_1742_INTR (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA) + +#define IO_1742_LIMAGE 0x0100 /* Load image (-120 only) */ +#define IO_1742_ERROR 0x0040 /* Device error */ + +#define IO_1742_STMSK (IO_1742_LIMAGE | IO_ST_PROT | IO_1742_ERROR | \ + IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA | \ + IO_ST_INT | IO_ST_BUSY | IO_ST_READY) + +#define IO_1742_STCINT (IO_ST_ALARM | IO_ST_EOP | IO_ST_DATA) + +/* + * 10336-1 Real-Time Clock + */ +#define IO_10336_ACK 0x0002 /* Acknowledge interrupt */ +#define IO_10336_STOP 0x0040 /* Stop clock */ +#define IO_10336_START 0x0080 /* Start clock */ +#define IO_10336_DIS 0x4000 /* Disable interrupt */ +#define IO_10336_ENA 0x8000 /* Enable interrupt */ + +#define IO_10336_DIRMSK (IO_10336_ENA | IO_10336_DIS | IO_10336_START | \ + IO_10336_STOP | IO_10336_ACK | IO_DIR_CCONT) + +#define IO_10336_INTR 0 +#define IO_10336_STMSK 0 +#define IO_10336_STCINT 0 + +/* + * Timing parameters + */ +#define TT_OUT_WAIT 200 /* TTY output wait time */ +#define TT_IN_XFER 60 /* TTY input transfer delay */ +#define TT_IN_MOTION 500 /* TTY paper motion delay */ +#define PTP_OUT_WAIT 500 /* Punch output wait time */ +#define PTR_IN_WAIT 450 /* Reader input wait time */ +#define LP_OUT_WAIT 15 /* Line printer output wait time */ +#define LP_PRINT_WAIT 3000 /* Print line wait time */ +#define LP_CC_WAIT 300 /* Control char wait time */ + +#define MT_MOTION_WAIT 150 /* Magtape motion delay */ +#define MT_RDATA_DELAY 46 /* Read data avail. period */ +#define MT_WDATA_DELAY 46 /* Write data ready period */ + +#define MT_200_WAIT 134 /* I/O latency (200 bpi) */ +#define MT_556_WAIT 48 /* I/O latency (556 bpi) */ +#define MT_800_WAIT 33 /* I/O latency (800 bpi) */ +#define MT_1600_WAIT 16 /* I/O latency (1600 bpi) */ + +#define MT_MIN_WAIT 10 /* Minimum I/O latency */ +#define MT_REC_WAIT 100 /* Magtape record latency */ +#define MT_TM_WAIT 200 /* Magtape write TM latency */ +#define MT_EOP_WAIT 100 /* Magtape EOP latency */ + +#define DP_XFER_WAIT 300 /* Control info xfer time */ +#define DP_SEEK_WAIT 2000 /* Seek wait time */ +#define DP_IO_WAIT 1000 /* Sector I/O wait time */ + +#define CD_SEEK_WAIT 1100 /* Seek wait time */ +#define CD_IO_WAIT 800 /* Sector I/O wait time */ +#define CD_RTZS_WAIT 200 /* Return to zero seek wait */ + +#define DC_START_WAIT 4 /* Startup delay */ +#define DC_IO_WAIT 4 /* I/O transfer delay */ +#define DC_EOP_WAIT 5 /* EOP delay */ + +#endif diff --git a/CDC1700/cdc1700_dev1.c b/CDC1700/cdc1700_dev1.c new file mode 100644 index 00000000..e4d03fb7 --- /dev/null +++ b/CDC1700/cdc1700_dev1.c @@ -0,0 +1,1497 @@ +/* + + 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_dev1.c: equipment number 1 I/O device support + * Simh devices: tti, tto, ptr, ptp, cdr + */ + +#include "cdc1700_defs.h" + +extern char INTprefix[]; + +extern void fw_IOunderwayData(IO_DEVICE *, uint16); +extern void fw_IOcompleteData(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); +extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); +extern void fw_setForced(IO_DEVICE *, uint16); +extern void fw_clearForced(IO_DEVICE *, uint16); + +extern void rebuildPending(void); + +extern void RaiseExternalInterrupt(DEVICE *); + +extern t_bool doDirectorFunc(DEVICE *, t_bool); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); +extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); + +extern t_stat set_protected(UNIT *, int32, CONST char *, void *); +extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); + +extern uint16 Areg, IOAreg; + +extern t_bool IOFWinitialized; + +t_stat tti_svc(UNIT *); +t_stat tto_svc(UNIT *); +t_stat tti_reset(DEVICE *); +t_stat tto_reset(DEVICE *); + +void TTIstate(const char *, DEVICE *, IO_DEVICE *); +void TTOstate(const char *, DEVICE *, IO_DEVICE *); +void TTstate(const char *, DEVICE *, IO_DEVICE *); +uint16 TTrebuild(void); +t_bool TTreject(IO_DEVICE *, t_bool, uint8); +enum IOstatus TTin(IO_DEVICE *, uint8); +enum IOstatus TTout(IO_DEVICE *, uint8); + +/* + 1711-A/B, 1712-A Teletypewriter + + Addresses + Computer Instruction + Q Register Output From A Input to A + + 0090 Write Read + 0091 Director Function Director Status + + + Operations: + + Director Function + + 15 10 9 8 7 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | | | X | X | X | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | + | | | | | | Clr Controller + | | | | | Clr Interrupts + | | | | Data Interrupt Req. + | | | Interrupt on EOP + | | Interrupt on Alarm + | Select Write Mode + Select Read Mode + + Status Response: + + Director Status + + 15 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | | | | X | X | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | + | | | | | | | | | Ready + | | | | | | | | Busy + | | | | | | | Interrupt + | | | | | | Data + | | | | | End of Operation + | | | | Alarm + | | | Lost Data + | | Read Mode + | Motor On + Manual Interrupt + + The director status bits are distributed across the 3 IO_DEVICE data + structures (TTdev, TTIdev and TTOdev). Global status bits will be held + in TTdev and per-device status bits in the appropriate input or output + device: + + IO_ST_READY TTdev + IO_ST_BUSY TTdev + IO_ST_INT TTIdev/TTOdev + IO_ST_DATA TTIdev/TTOdev + IO_ST_EOP TTIdev/TTOdev (equivalent to "Not Busy") + IO_ST_ALARM TTIdev/TTOdev + IO_ST_LOST TTdev + IO_1711_RMODE TTIdev (set)/TTOdev (clear) + IO_1711_MON TTdev + IO_1711_MANUAL TTdev +*/ + +IO_DEVICE TTIdev = IODEV("TTI", "1711-A", 1711, 1, 1, 0, + NULL, NULL, NULL, NULL, NULL, + TTIstate, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, 0, 0, 0, 0, NULL); + +IO_DEVICE TTOdev = IODEV("TTO", "1711-A", 1711, 1, 1, 0, + NULL, NULL, NULL, NULL, NULL, + TTOstate, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, 0, 0, 0, 0, NULL); + +IO_DEVICE TTdev = IODEV("TT", "1711-A", 1711, 1, 1, 0, + TTreject, TTin, TTout, NULL, NULL, + TTstate, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + 0, 0, 0, 0, 0, NULL); + +/* + * Define usage for "private IO_DEVICE data areas + */ +#define iod_holdFull iod_private4 +#define iod_indelay iod_private9 +#define iod_rmode iod_private4 + +#define IODP_TTI_XFER 0x01 /* Transfer to data hold reg */ +#define IODP_TTI_MOTION 0x02 /* Paper motion delay */ + +#define IO_1711_CONTR (IO_1711_MANUAL | IO_1711_MON | IO_ST_LOST | \ + IO_ST_BUSY | IO_ST_READY); +#define IO_1711_IDEVICE (IO_ST_ALARM | IO_ST_EOP | IO_ST_INT | IO_ST_DATA) +#define IO_1711_ODEVICE (IO_ST_ALARM | IO_ST_EOP | IO_ST_INT | IO_ST_DATA) + +/* TTI data structures + + tti_dev TTI device descriptor + tti_unit TTI unit descriptor + tti_reg TTI register list + tti_mod TTI modifiers list +*/ + +uint8 tti_manualIntr = 0x7; + +#define TTUF_V_HDX (TTUF_V_UF + 0) +#define TTUF_HDX (1 << TTUF_V_HDX) + +UNIT tti_unit = { UDATA(&tti_svc, UNIT_IDLE+TT_MODE_KSR+TTUF_HDX, 0), KBD_POLL_WAIT }; + +REG tti_reg[] = { + { HRDATA(MODE, TTdev.iod_rmode, 8) }, + { HRDATA(FUNCTION, TTdev.FUNCTION, 16) }, + { HRDATA(STATUS, TTdev.STATUS, 16) }, + { HRDATA(IENABLE, TTIdev.IENABLE, 16) }, + { HRDATA(INTRKEY, tti_manualIntr, 8) }, + { NULL } +}; + +MTAB tti_mod[] = { + { TTUF_HDX, 0, "full duplex", "FDX", NULL }, + { TTUF_HDX, TTUF_HDX, "half duplex", "HDX", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "1711-A", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { 0 } +}; + +DEBTAB tti_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, + { NULL } +}; + +DEVICE tti_dev = { + "TTI", &tti_unit, tti_reg, tti_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &tti_reset, + NULL, NULL, NULL, + &TTdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV, 0, tti_deb, + NULL, + NULL +}; + +/* TTO data structures + + tto_dev TTO device descriptor + tto_unit TTO unit descriptor + tto_reg TTO register list + tto_mod TTO modifiers list +*/ + +UNIT tto_unit = { UDATA(&tto_svc, TT_MODE_KSR+TTUF_HDX, 0), TT_OUT_WAIT }; + +REG tto_reg[] = { + { HRDATA(MODE, TTdev.iod_rmode, 8) }, + { HRDATA(FUNCTION, TTdev.FUNCTION, 16) }, + { HRDATA(STATUS, TTdev.STATUS, 16) }, + { HRDATA(IENABLE, TTOdev.IENABLE, 16) }, + { NULL } +}; + +MTAB tto_mod[] = { + { TTUF_HDX, 0, "full duplex", "FDX", NULL }, + { TTUF_HDX, TTUF_HDX, "half duplex", "HDX", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "1711-A", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { 0 } +}; + + +DEBTAB tto_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, + { NULL } +}; + +DEVICE tto_dev = { + "TTO", &tto_unit, tto_reg, tto_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &tto_reset, + NULL, NULL, NULL, + &TTdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_OUTDEV, 0, tto_deb, + NULL, + NULL +}; + +/* + * Support routines for terminal physical input device + */ + +/* + * Dump the current state of the TTI device + */ +void TTIstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + char temp[32]; + + temp[0] = '\0'; + + if (TTIdev.iod_holdFull) + sprintf(temp, ", Hold full (%02X)", tti_unit.buf & 0xFF); + + fprintf(DBGOUT, + "%s[TTI %s: Func: %04X, Sta: %04X, Ena: %04X, Dly: %c%s]\r\n", + INTprefix, where, + TTIdev.FUNCTION, TTIdev.STATUS, TTIdev.IENABLE, + TTIdev.iod_indelay + '0', temp); +} + +/* Unit service */ + +t_stat tti_svc(UNIT *uptr) +{ + int32 c, out; + + if (TTIdev.iod_indelay != 0) { + /* + * Waiting for functions related to character input: + * + * 1. Transfering the character from the TTY to the hold buffer. + * 2. Wait for carriage control motion (CR, LF etc) + */ + if ((TTIdev.iod_indelay & IODP_TTI_XFER) != 0) { + TTIdev.iod_indelay &= ~IODP_TTI_XFER; + TTIdev.iod_holdFull = TRUE; + if ((tti_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[TTI: tti_svc() transfer complete]\r\n", INTprefix); + + if ((TTIdev.iod_indelay & IODP_TTI_MOTION) != 0) { + sim_activate(uptr, TT_IN_MOTION); + if ((tti_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[TTI: tti_svc() motion delay]\r\n", INTprefix); + + return SCPE_OK; + } + } + + if ((TTIdev.iod_indelay & IODP_TTI_MOTION) != 0) { + TTIdev.iod_indelay &= ~IODP_TTI_MOTION; + if ((tti_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[TTI: tti_svc() motion delay complete\r\n", INTprefix); + } + + TTdev.STATUS &= IO_ST_BUSY; + TTIdev.STATUS |= IO_ST_EOP; + + fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Motion delay"); + TTrebuild(); + + /* + * Resume normal polling + */ + sim_activate(uptr, uptr->wait); + return SCPE_OK; + } + + /* + * Restart the poller. + */ + sim_activate(uptr, uptr->wait); + + if ((c = sim_poll_kbd()) < SCPE_KFLAG) + return c; /* No character or error */ + + out = c & 0xfF; + + if (out == tti_manualIntr) { + if ((tti_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[TTI: tti_svc() manual interrupt]\r\n", INTprefix); + + TTdev.STATUS |= IO_1711_MANUAL; + TTIdev.STATUS |= IO_ST_INT; + RaiseExternalInterrupt(&tti_dev); + return SCPE_OK; + } + + if ((c & SCPE_BREAK) != 0) + c = 0; + else c = sim_tt_inpcvt(c, TT_GET_MODE(uptr->flags) | TTUF_KSR); + + if (TTIdev.iod_holdFull) { + if ((tti_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[TTI: tti_svc() hold register full]\r\n", INTprefix); + + TTIdev.STATUS |= IO_ST_ALARM | IO_ST_LOST; + fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Lost char"); + TTrebuild(); + return SCPE_OK; + } + + if (((uptr->flags & TTUF_HDX) != 0) && out && + ((out = sim_tt_outcvt(out, TT_GET_MODE(uptr->flags) | TTUF_KSR)) >= 0)) { + sim_putchar(out); + tto_unit.pos++; + } + uptr->buf = c; + uptr->pos++; + + /* + * Start a delay while the input character is transferred from the TTY to + * the hold buffer. + */ + TTIdev.iod_indelay = IODP_TTI_XFER; + if ((out == '\r') || (out == '\n') || (out == '\f')) + TTIdev.iod_indelay |= IODP_TTI_MOTION; + + sim_cancel(uptr); + sim_activate(uptr, TT_IN_XFER); + + TTdev.STATUS |= IO_ST_BUSY; + TTIdev.STATUS |= IO_ST_DATA; + + if ((tti_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[TTI: tti_svc() transfer started]\r\n", INTprefix); + if ((tti_dev.dctrl & DBG_DSTATE) != 0) + TTIstate("tti_svc", &tti_dev, &TTIdev); + } + + fw_IOintr(FALSE, &tti_dev, &TTIdev, 0, 0, 0xFFFF, "Input char"); + TTrebuild(); + + return SCPE_OK; +} + +/* Reset routine */ + +t_stat tti_reset(DEVICE *dptr) +{ + /*** Reset TT? ***/ + TTIdev.STATUS = IO_1711_RMODE; + TTIdev.iod_holdFull = FALSE; + TTIdev.iod_indelay = 0; + + tti_unit.buf = 0; + + if (!sim_is_running) + sim_activate(&tti_unit, tti_unit.wait); + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus TTIin(IO_DEVICE *iod, uint8 reg) +{ + /* + * The logical TT device driver only passes INP operations for the data + * register (0x90). + */ + TTIdev.iod_holdFull = FALSE; + Areg = tti_unit.buf; + + TTdev.STATUS &= ~IO_ST_BUSY; + TTIdev.STATUS |= IO_ST_EOP; + TTIdev.STATUS &= ~(IO_ST_INT | IO_ST_DATA); + TTrebuild(); + rebuildPending(); + + return IO_REPLY; +} + +void TTIcint(void) +{ + /* + * Clear all interrupt enables + */ + TTIdev.iod_ienable = 0; + TTIdev.iod_oldienable = 0; + + /* + * Clear all pending interrupts + */ + TTIdev.STATUS &= ~IO_1711_IDEVICE; + if (TTIdev.iod_holdFull) + TTIdev.STATUS |= IO_ST_DATA; +} + +void TTIccont(void) +{ + tti_reset(&tti_dev); + TTIdev.iod_holdFull = FALSE; + + TTIcint(); +} + +/* + * Support routines for terminal physical output device + */ + +/* + * Dump the current state of the TTO device + */ +void TTOstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + char temp[32]; + + temp[0] = '\0'; + + if (TTOdev.iod_holdFull) + sprintf(temp, ", Hold full (%02X)", tto_unit.buf & 0xFF); + + fprintf(DBGOUT, + "%s[TTO %s: Func: %04X, Sta: %04X, Ena: %04X%s]\r\n", + INTprefix, where, + TTOdev.FUNCTION, TTOdev.STATUS, TTOdev.IENABLE, temp); +} + +/* Unit service */ + +t_stat tto_svc(UNIT *uptr) +{ + int32 c; + t_stat r; + + c = sim_tt_outcvt(uptr->buf, TT_GET_MODE(uptr->flags) | TTUF_KSR); + if (c >= 0) { + if ((r = sim_putchar_s(c)) != SCPE_OK) { + sim_activate(uptr, uptr->wait); /* Try again later */ + return (r == SCPE_STALL) ? SCPE_OK : r; + } + } + + TTOdev.iod_holdFull = FALSE; + TTOdev.STATUS |= IO_ST_EOP | IO_ST_DATA; + TTdev.STATUS &= ~IO_ST_BUSY; + + if ((tto_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[TTO: tto_svc()]\r\n", INTprefix); + if ((tto_dev.dctrl & DBG_DSTATE) != 0) + TTOstate("tto_svc", &tto_dev, &TTOdev); + } + + uptr->pos++; + + fw_IOintr(FALSE, &tto_dev, &TTOdev, 0, 0, 0xFFFF, "Output done"); + TTrebuild(); + + return SCPE_OK; +} + +/* Reset routine */ + +t_stat tto_reset(DEVICE *dptr) +{ + /*** Reset TT? ***/ + TTOdev.STATUS = IO_ST_DATA; + TTOdev.iod_holdFull = FALSE; + + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus TTOout(IO_DEVICE *iod, uint8 reg) +{ + /* + * The logical TT device driver only passes OUT operations for the data + * register (0x90). + */ + sim_activate(&tto_unit, tto_unit.wait); + + tto_unit.buf = Areg; + TTOdev.iod_holdFull = TRUE; + + TTOdev.STATUS &= ~(IO_ST_EOP | IO_ST_INT | IO_ST_DATA); + TTdev.STATUS |= IO_ST_BUSY; + TTrebuild(); + rebuildPending(); + + return IO_REPLY; +} + +void TTOcint(void) +{ + /* + * Clear all interrupt enables + */ + TTOdev.iod_ienable = 0; + TTOdev.iod_oldienable = 0; + + /* + * Clear all pending interrupts + */ + TTOdev.STATUS &= ~IO_1711_ODEVICE; + + if (!TTOdev.iod_holdFull) + TTOdev.STATUS |= IO_ST_DATA; +} + +void TTOccont(void) +{ + tto_reset(&tto_dev); + sim_cancel(&tto_unit); + + TTOcint(); +} + +/* + * Support routines for the terminal logical device + */ + +/* + * Dump the current internal state of the TT device. Note that "dev" is + * not used by this routine since it is a logical device which has no + * associated physical device. + */ +void TTstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[TT %s: Func: %04X, Sta: %04X, Mode: %c]\r\n", + INTprefix, where, iod->FUNCTION, iod->STATUS, + iod->iod_rmode ? 'R' : 'W'); + + if ((tti_dev.dctrl & DBG_DSTATE) != 0) + TTIstate(where, &tti_dev, &TTIdev); + + if ((tto_dev.dctrl & DBG_DSTATE) != 0) + TTOstate(where, &tto_dev, &TTOdev); +} + +/* + * Reset routine + */ +void TTreset(void) +{ + TTdev.STATUS = IO_1711_MON | IO_ST_READY; + TTdev.iod_rmode = TRUE; +} + +/* Rebuild the director status register */ + +uint16 TTrebuild(void) +{ + TTdev.STATUS &= IO_1711_CONTR; + if (TTdev.iod_rmode) { + TTdev.STATUS |= (TTIdev.STATUS & IO_1711_IDEVICE) | IO_1711_RMODE; + } else TTdev.STATUS |= TTOdev.STATUS & IO_1711_ODEVICE; + TTdev.STATUS |= IO_1711_MON | IO_ST_READY; + + return TTdev.STATUS; +} + +/* Check if I/O should be rejected */ + +t_bool TTreject(IO_DEVICE *iod, t_bool output, uint8 reg) +{ + if (reg == 0) { + if (output) + return (TTdev.STATUS & IO_ST_BUSY) != 0; + return !TTIdev.iod_holdFull; + } + + if (output) { + uint16 func = Areg & IO_1711_DIRMSK; + + if (func != 0) { + if ((func & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA | \ + IO_DIR_CINT | IO_DIR_CCONT)) != 0) + return FALSE; + + /* + * Select read/write mode must be set - reject if both are set. + */ + if ((func & (IO_1711_SREAD | IO_1711_SWRITE)) == + (IO_1711_SREAD | IO_1711_SWRITE)) + return TRUE; + + return (TTdev.STATUS & IO_ST_BUSY) != 0; + } + } + return FALSE; +} + +/* Perform I/O */ + +enum IOstatus TTin(IO_DEVICE *iod, uint8 reg) +{ + /* + * Certain invalid operations will already have been rejected in TTreject(). + */ + if (reg == 0) + return TTIin(&TTIdev, reg); + + Areg = TTrebuild(); + return IO_REPLY; +} + +enum IOstatus TTout(IO_DEVICE *iod, uint8 reg) +{ + t_bool rmode, changed = FALSE; + DEVICE *dptr; + + /* + * Certain invalid operation will already have been rejected in TTreject(). + */ + if (reg == 0) + return TTOout(&TTOdev, reg); + + if ((IOAreg & IO_DIR_CCONT) != 0) { + /* + * Clear both sides of the controller and switch to read-mode. + */ + if (((tti_dev.dctrl & DBG_DSTATE) != 0) || + ((tto_dev.dctrl & DBG_DSTATE)!= 0)) + fprintf(DBGOUT, "%s[TT: Controller Reset\r\n", INTprefix); + + TTIccont(); + TTOccont(); + TTdev.STATUS &= ~IO_ST_BUSY; + + TTdev.iod_rmode = TRUE; + } + + if ((IOAreg & IO_DIR_CINT) != 0) { + /* + * Clear interrupts for the currently active mode + */ + if (TTdev.iod_rmode) + TTIcint(); + else TTOcint(); + TTdev.STATUS &= ~IO_1711_MANUAL; + } + + /* + * If Clear Controller or Clear Interrupts was set, don't process + * read/write select bits. + */ + if ((IOAreg & (IO_DIR_CINT | IO_DIR_CCONT)) == 0) { + /* + * Check for switching modes. We've already rejected a request to select + * both modes. + */ + if ((IOAreg & IO_1711_SREAD) != 0) + TTdev.iod_rmode = TRUE; + if ((IOAreg & IO_1711_SWRITE) != 0) { + TTdev.iod_rmode = FALSE; + TTIdev.STATUS &= ~IO_ST_LOST; + } + } + + rebuildPending(); + + rmode = TTdev.iod_rmode; + + if ((IOAreg & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA)) != 0) { + iod = rmode ? &TTIdev : &TTOdev; + dptr = rmode ? &tti_dev : &tto_dev; + + iod->iod_oldienable = iod->iod_ienable; + iod->iod_ienable |= IOAreg & (IO_DIR_ALARM | IO_DIR_EOP | IO_DIR_DATA); + changed = iod->iod_ienable != iod->iod_oldienable; + } + + if (changed) + if (!rmode) + fw_IOintr(FALSE, dptr, iod, 0, 0, 0xFFFF, "Can output"); + + TTrebuild(); + + return IO_REPLY; +} + +t_stat ptr_svc(UNIT *); +t_stat ptr_reset(DEVICE *); +t_stat ptr_attach(UNIT *, CONST char *); +t_stat ptr_detach(UNIT *); + +enum IOstatus PTRin(IO_DEVICE *, uint8); +enum IOstatus PTRout(IO_DEVICE *, uint8); + +/* + 1721-A/B/C/D, 1722-A/B Paper Tape Reader + + Addresses + Computer Instruction + Q Register Output From A Input to A + + 00A0 Read + 00A1 Director Function Director Status + + + Operations: + + Director Function + + 15 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | X | X | X | | | | X | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + | | | | | Clr Controller + | | | | Clr Interrupts + | | | Data Interrupt Req. + | | Interrupt on Alarm + | Start Motion + Stop Motion + Status Response: + + Director Status + + 15 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | | | | | | | X | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | + | | | | | | | | | Ready + | | | | | | | | Busy + | | | | | | | Interrupt + | | | | | | Data + | | | | | Alarm + | | | | Lost Data + | | | Protected + | | Existence Code + | Paper Motion Failure + Power On + */ + +IO_DEVICE PTRdev = IODEV(NULL, "1721-A", 1721, 1, 2, 0, + fw_reject, PTRin, PTRout, NULL, NULL, + NULL, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, 0, 0, 0, 0, NULL); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_PTRmotion iod_private +/* + * Current state of the device (STOPPED/STARTED). + */ +#define IODP_PTRSTOPPED 0x0000 +#define IODP_PTRSTARTED 0x0001 +#define IODP_PTR_MASK 0x0001 + +/* PTR data structures + + ptr_dev PTR device descriptor + ptr_unit PTR unit descriptor + ptr_reg PTR register list + ptr_mod PTR modifiers list +*/ + +UNIT ptr_unit = { + UDATA(&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), PTR_IN_WAIT +}; + +REG ptr_reg[] = { + { HRDATA(FUNCTION, PTRdev.FUNCTION, 16) }, + { HRDATA(STATUS, PTRdev.STATUS, 16) }, + { HRDATA(IENABLE, PTRdev.IENABLE, 16)}, + { NULL } +}; + +MTAB ptr_mod[] = { + { MTAB_XTD | MTAB_VDV, 0, "1721-A", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT", NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { 0 } +}; + +DEBTAB ptr_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, + { NULL } +}; + +DEVICE ptr_dev = { + "PTR", &ptr_unit, ptr_reg, ptr_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &ptr_reset, + NULL, &ptr_attach, &ptr_detach, + &PTRdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_PROTECT, 0, ptr_deb +}; + +/* + * Dump the current internal state of the PTR device. + */ +const char *PTRprivateState[2] = { + "", "In Motion" +}; + +void PTRstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Private: %s\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->STATUS, iod->IENABLE, + PTRprivateState[iod->iod_PTRmotion & IODP_PTR_MASK]); +} + +/* Unit service */ + +t_stat ptr_svc(UNIT *uptr) +{ + int32 temp; + + if ((ptr_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[PTR: ptr_svc() entry]\r\n", INTprefix); + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("svc entry", &ptr_dev, &PTRdev); + } + + if ((uptr->flags & UNIT_ATT) == 0) { + if ((ptr_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%s[PTR: ptr_svc() exit - no attached file]\r\n", INTprefix); + return SCPE_OK; + } + + if ((temp = getc(uptr->fileref)) == EOF) { + if (feof(uptr->fileref)) { + if ((ptr_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[PTR: ptr_svc() exit - EOF]\r\n", INTprefix); + + /* + * We've run off the end of the tape. Indicate motion failure and alarm + * status and generate an interrupt if requested. + */ + fw_IOintr(FALSE, &ptr_dev, &PTRdev, IO_ST_ALARM | IO_1721_MOTIONF, IO_ST_READY, 0xFFFF, "End of tape"); + return SCPE_OK; + } else perror("PTR I/O error"); + clearerr(uptr->fileref); + if ((ptr_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%s[PTR: ptr_svc() exit - Read Error]\r\n", INTprefix); + return SCPE_IOERR; + } + uptr->buf = temp & 0xFF; + uptr->pos++; + + fw_IOcompleteData(FALSE, &ptr_dev, &PTRdev, 0xFFFF, "Read Complete"); + + if ((ptr_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, + "%s[PTR: ptr_svc() exit => %2X]\r\n", INTprefix, uptr->buf); + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("svc exit", &ptr_dev, &PTRdev); + } + return SCPE_OK; +} + +/* Reset routine */ + +t_stat ptr_reset(DEVICE *dptr) +{ + DEVRESET(&PTRdev); + + PTRdev.iod_PTRmotion = IODP_PTRSTOPPED; + + if ((ptr_unit.flags & UNIT_ATT) != 0) + fw_setForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); + + ptr_unit.buf = 0; + return SCPE_OK; +} + +/* Attach routine */ + +t_stat ptr_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + if ((r = attach_unit(uptr, cptr)) != SCPE_OK) + return r; + + fw_setForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); + return SCPE_OK; +} + +/* Detach routine */ + +t_stat ptr_detach(UNIT *uptr) +{ + if ((uptr->flags & UNIT_ATT) == 0) + return SCPE_OK; + + fw_clearForced(&PTRdev, IO_1721_POWERON | IO_ST_READY); + return detach_unit(uptr); +} + +/* Perform I/O */ + +enum IOstatus PTRin(IO_DEVICE *iod, uint8 reg) +{ + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("before", &ptr_dev, &PTRdev); + + /* + * The framework only passes IN operations for the data register (0x90) + */ + Areg = (Areg & 0xFF00) | ptr_unit.buf; + fw_IOunderwayData(&PTRdev, 0); + if (PTRdev.iod_PTRmotion == IODP_PTRSTARTED) + sim_activate(&ptr_unit, ptr_unit.wait); + + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("after", &ptr_dev, &PTRdev); + return IO_REPLY; +} + +enum IOstatus PTRout(IO_DEVICE *iod, uint8 reg) +{ + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("before", &ptr_dev, &PTRdev); + + switch (reg) { + case 0x00: + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("after", &ptr_dev, &PTRdev); + return IO_REJECT; + + case 0x01: + doDirectorFunc(&ptr_dev, FALSE); + + if ((IOAreg & IO_DIR_START) != 0) { + fw_setForced(&PTRdev, IO_ST_BUSY); + PTRdev.iod_PTRmotion = IODP_PTRSTARTED; + } + if ((IOAreg & IO_DIR_STOP) != 0) { + fw_clearForced(&PTRdev, IO_ST_BUSY); + PTRdev.iod_PTRmotion = IODP_PTRSTOPPED; + } + + if (PTRdev.iod_PTRmotion == IODP_PTRSTARTED) + sim_activate(&ptr_unit, ptr_unit.wait); + } + if ((ptr_dev.dctrl & DBG_DSTATE) != 0) + PTRstate("after", &ptr_dev, &PTRdev); + return IO_REPLY; +} + +t_stat ptp_svc(UNIT *); +t_stat ptp_reset(DEVICE *); + +enum IOstatus PTPin(IO_DEVICE *, uint8); +enum IOstatus PTPout(IO_DEVICE *, uint8); + +/* + 1723-A/B, 1724-A/B Paper Tape Punch + + Addresses + Computer Instruction + Q Register Output From A Input to A + + 00C0 Write + 00C1 Director Function Director Status + + Operations: + + Director Function + + 15 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | X | X | X | | | | X | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + | | | | | Clr Controller + | | | | Clr Interrupts + | | | Data Interrupt Req. + | | Interrupt on Alarm + | Start Motion + Stop Motion + + Status Response: + + Director Status + + 15 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | | | | | | X | | X | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | + | | | | | | | | | Ready + | | | | | | | | Busy + | | | | | | | Interrupt + | | | | | | Data + | | | | | Alarm + | | | | Protected + | | | Existence Code + | | Tape Break + | Power On + Tape Supply Low + */ + +IO_DEVICE PTPdev = IODEV(NULL, "1723-A", 1723, 1, 4, 0, + fw_reject, PTPin, PTPout, NULL, NULL, + NULL, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, 0, 0, 0, 0, NULL); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_PTPdelay iod_private + +/* + * Current delay state of the device. + */ +#define IODP_PTPINTRWAIT 0x0001 +#define IODP_PTPDATAWAIT 0x0002 +#define IODP_PTP_MASK (IODP_PTPINTRWAIT | IODP_PTPDATAWAIT) + +/* PTP data structures + + ptp_dev PTP device descriptor + ptp_unit PTP unit descriptor + ptp_reg PTP register list + ptp_mod PTP modifiers list +*/ + +UNIT ptp_unit = { + UDATA(&ptp_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), PTP_OUT_WAIT +}; + +REG ptp_reg[] = { + { HRDATA(FUNCTION, PTPdev.FUNCTION, 16) }, + { HRDATA(STATUS, PTPdev.STATUS, 16) }, + { HRDATA(IENABLE, PTPdev.IENABLE, 16)}, + { NULL } +}; + +MTAB ptp_mod[] = { + { MTAB_XTD | MTAB_VDV, 0, "1723-A", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT", NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { 0 } +}; + +DEBTAB ptp_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, + { NULL } +}; + +DEVICE ptp_dev = { + "PTP", &ptp_unit, ptp_reg, ptp_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &ptp_reset, + NULL, NULL, NULL, + &PTPdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_OUTDEV | DEV_PROTECT, 0, ptp_deb, + NULL, + NULL +}; + +/* + * Dump the current internal state of the PTP device. + */ +const char *PTPprivateState[4] = { + "", "INTRWAIT", "DATAWAIT", "DATAWAIT,INTRWAIT" +}; + +void PTPstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Private: %s\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->STATUS, iod->IENABLE, + PTPprivateState[iod->iod_PTPdelay & IODP_PTP_MASK]); +} + +/* Unit service */ + +t_stat ptp_svc(UNIT *uptr) +{ + if ((ptp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[PTP: ptp_svc() entry]\r\n", INTprefix); + if ((ptp_dev.dctrl & DBG_DSTATE) != 0) + PTPstate("svc entry", &ptp_dev, &PTPdev); + } + + if ((PTPdev.iod_PTPdelay & IODP_PTPINTRWAIT) != 0) { + /* + * Generate an interrupt indicating that the motor is up to speed. + */ + PTPdev.iod_PTPdelay &= ~IODP_PTPINTRWAIT; + fw_IOintr(FALSE, &ptp_dev, &PTPdev, 0, 0, 0xFFFF, "Up to speed"); + + if ((PTPdev.iod_PTPdelay & IODP_PTP_MASK) != 0) + sim_activate(&ptp_unit, ptp_unit.wait); + goto done; + } + + if ((PTPdev.iod_PTPdelay & IODP_PTPDATAWAIT) != 0) { + /* + * Now process the actual output of data to be punched. + */ + PTPdev.iod_PTPdelay &= ~IODP_PTPDATAWAIT; + + if ((uptr->flags & UNIT_ATT) != 0) { + if (putc(uptr->buf, uptr->fileref) == EOF) { + perror("PTP I/O error"); + clearerr(uptr->fileref); + } else uptr->pos++; + } + + fw_IOcompleteData(FALSE, &ptp_dev, &PTPdev, 0xFFFF, "Output complete"); + } + + done: + if ((ptp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[PTP: ptp_svc() exit]\r\n", INTprefix); + if ((ptp_dev.dctrl & DBG_DSTATE) != 0) + PTPstate("svc exit", &ptp_dev, &PTPdev); + } + return SCPE_OK; +} + +/* Reset routine */ + +t_stat ptp_reset(DEVICE *dptr) +{ + DEVRESET(&PTPdev); + + PTPdev.iod_PTPdelay = 0; + fw_setForced(&PTPdev, IO_1723_POWERON | IO_ST_READY); + + ptp_unit.buf = 0; + if (!sim_is_running) + sim_activate(&ptp_unit, ptp_unit.wait); + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus PTPin(IO_DEVICE *iod, uint8 reg) +{ + /* + * The framework only passes IN operations for the data register (0x90) + */ + return IO_REJECT; +} + +enum IOstatus PTPout(IO_DEVICE *iod, uint8 reg) +{ + if ((ptp_dev.dctrl & DBG_DSTATE) != 0) + PTPstate("before", &ptp_dev, &PTPdev); + + switch (reg) { + case 0x00: + PTPdev.iod_PTPdelay |= IODP_PTPDATAWAIT; + ptp_unit.buf = Areg; + + fw_IOunderwayData(&PTPdev, IO_ST_INT); + + rebuildPending(); + + sim_activate(&ptp_unit, ptp_unit.wait); + + if ((ptp_dev.dctrl & DBG_DSTATE) != 0) + PTPstate("after", &ptp_dev, &PTPdev); + break; + + case 0x01: + /* + * Check for illegal combination of commands + */ + if (STARTSTOP(Areg)) + return IO_REJECT; + + if (doDirectorFunc(&ptp_dev, FALSE)) { + /* + * The device interrupt mask has been explicitly changed. If the + * interrupt on data was just set and the device is ready, generate + * a delayed interrupt. + */ + if ((ICHANGED(&PTPdev) & IO_DIR_DATA) != 0) { + if ((PTPdev.STATUS & IO_ST_READY) != 0) { + if ((PTPdev.iod_PTPdelay & IODP_PTP_MASK) == 0) { + if ((ptp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sPTP: Mask change interrupt\n", INTprefix); + sim_activate(&ptp_unit, ptp_unit.wait); + PTPdev.iod_PTPdelay |= IODP_PTPINTRWAIT; + } + } + } + } + + if (IOAreg != 0) { + if ((IOAreg & (IO_DIR_START | IO_DIR_STOP)) != 0) { + sim_activate(&ptp_unit, 5 * ptp_unit.wait); + PTPdev.iod_PTPdelay |= IODP_PTPINTRWAIT; + if ((IOAreg & IO_DIR_START) != 0) { + fw_setForced(&PTPdev, IO_ST_BUSY); + PTPdev.STATUS |= IO_ST_DATA; + } + if ((IOAreg & IO_DIR_STOP) != 0) { + fw_clearForced(&PTPdev, IO_ST_BUSY); + PTPdev.STATUS &= ~IO_ST_DATA; + } + } + } + + if ((ptp_dev.dctrl & DBG_DSTATE) != 0) + PTPstate("after", &ptp_dev, &PTPdev); + break; + } + return IO_REPLY; +} + +t_stat cdr_svc(UNIT *); +t_stat cdr_reset(DEVICE *); + +enum IOstatus CDRin(IO_DEVICE *, uint8); +enum IOstatus CDRout(IO_DEVICE *, uint8); + +/* + 1729-A/B Card Reader + + Addresses + Computer Instruction + Q Register Output From A Input to A + + 00E0 Read + 00E1 Director Function Director Status + + + Operations: + + Director Function + + 15 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | X | X | X | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | + | | | | | | Clr Controller + | | | | | Clr Interrupts + | | | | Data Interrupt Req. + | | | Interrupt on End of Record + | | Interrupt on Alarm + | Start Motion + Stop Motion + + Status Response: + + Director Status + + 15 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | + | | | | | | | | | Ready + | | | | | | | | Busy + | | | | | | | Interrupt + | | | | | | Data + | | | | | End Of Record + | | | | Alarm + | | | Lost Data + | | Protected + | Existence Code + Read Station Empty + + */ + +IO_DEVICE CDRdev = IODEV(NULL, "1729", 1729, 1, 6, 0, + fw_reject, CDRin, CDRout, NULL, NULL, + NULL, NULL, NULL, NULL, + 0xF, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, 0, 0, 0, 0, NULL); + +/* CDR data structures + + cdr_dev CDR device descriptor + cdr_unit CDR unit descriptor + cdr_reg CDR register list + cdr_mod CDR modifiers list +*/ + +UNIT cdr_unit = { + UDATA(&cdr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), SERIAL_IN_WAIT +}; + +REG cdr_reg[] = { + { HRDATA(FUNCTION, CDRdev.FUNCTION, 16) }, + { HRDATA(STATUS, CDRdev.STATUS, 16) }, + { HRDATA(IENABLE, CDRdev.IENABLE, 16)}, + { NULL } +}; + +MTAB cdr_mod[] = { + { MTAB_XTD | MTAB_VDV, 0, "1729", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT", NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { 0 } +}; + +DEBTAB cdr_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DLOC }, + { NULL } +}; + +DEVICE cdr_dev = { + "CDR", &cdr_unit, cdr_reg, cdr_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &cdr_reset, + NULL, NULL, NULL, + &CDRdev, + DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_PROTECT, 0, cdr_deb, + NULL, + NULL +}; + +/* Unit service */ + +t_stat cdr_svc(UNIT *uptr) +{ + /*** TODO: Implement Card Reader support ***/ + return SCPE_OK; +} + +/* Reset routine */ + +t_stat cdr_reset(DEVICE *dptr) +{ + DEVRESET(&CDRdev); + + ptr_unit.buf = 0; + if (!sim_is_running) + sim_activate(&cdr_unit, cdr_unit.wait); + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus CDRin(IO_DEVICE *iod, uint8 reg) +{ + /* + * The framework only passes IN operations for the data register (0x90) + */ + Areg = cdr_unit.buf; + CDRdev.STATUS &= IO_ST_BUSY | IO_ST_DATA; + return IO_REPLY; +} + +enum IOstatus CDRout(IO_DEVICE *iod, uint8 reg) +{ + switch (reg) { + case 0x00: + return IO_REJECT; + + case 0x01: + doDirectorFunc(&cdr_dev, FALSE); + /*** TODO: Process local director functions ***/ + break; + } + return IO_REPLY; +} + +/* + * Return device 1 interrupt status. If any of the sub-devices have their + * interrupt status active, return the device 1 interrupt mask bit. + */ +uint16 dev1INTR(DEVICE *dptr) +{ + uint16 status; + + status = TTIdev.STATUS | TTOdev.STATUS | PTRdev.STATUS | PTPdev.STATUS | CDRdev.STATUS; + + return (status & IO_ST_INT) != 0 ? 1 << 1 : 0; +} + +/* + * Update a buffer indicating which device 1 stations are asserting interrupt + * status. + */ +void dev1Interrupts(char *buf) +{ + buf[0] = '\0'; + + if ((TTIdev.STATUS & IO_ST_INT) != 0) + strcat(buf, " TTI"); + if ((TTOdev.STATUS & IO_ST_INT) != 0) + strcat(buf, "TTO"); + if ((PTRdev.STATUS & IO_ST_INT) != 0) + strcat(buf, " PTR"); + if ((PTPdev.STATUS & IO_ST_INT) != 0) + strcat(buf, " PTP"); + if ((CDRdev.STATUS & IO_ST_INT) != 0) + strcat(buf, " CDR"); +} diff --git a/CDC1700/cdc1700_dis.c b/CDC1700/cdc1700_dis.c new file mode 100644 index 00000000..77a90bf9 --- /dev/null +++ b/CDC1700/cdc1700_dis.c @@ -0,0 +1,325 @@ +/* + + 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_dis.c: CDC1700 disassembler + */ + +#include +#include +#include +#include "cdc1700_defs.h" + +extern uint16 Areg, Mreg, Preg, Qreg, RelBase; +extern uint16 LoadFromMem(uint16); +extern uint8 P[]; +extern DEVICE cpu_dev; +extern UNIT cpu_unit; +extern t_stat disEffectiveAddr(uint16, uint16, uint16 *, uint16 *); +extern uint16 doADDinternal(uint16, uint16); + +const char *opName[] = { + "???", "JMP", "MUI", "DVI", "STQ", "RTJ", "STA", "SPA", + "ADD", "SUB", "AND", "EOR", "LDA", "RAO", "LDQ", "ADQ" +}; + +const char *idxName[] = { + "", ",I", ",Q", ",B" +}; + +const char *spcName[] = { + "SLS", "???", "INP", "OUT", "EIN", "IIN", "SPB", "CPB", + "???", "INA", "ENA", "NOP", "ENQ", "INQ", "EXI", "???" +}; + +const char *skpName[] = { + "SAZ", "SAN", "SAP", "SAM", "SQZ", "SQN", "SQP", "SQM", + "SWS", "SWN", "SOV", "SNO", "SPE", "SNP", "SPF", "SNF" +}; + +const char *interName[] = { + "SET", "TRM", "TRQ", "TRB", "TRA", "AAM", "AAQ", "AAB", + "CLR", "TCM", "TCQ", "TCB", "TCA", "EAM", "EAQ", "EAB", + "SET", "TRM", "TRQ", "TRB", "TRA", "LAM", "LAQ", "LAB", + "NOOP", NULL, NULL, NULL, NULL, "CAM", "CAQ", "CAB" +}; + +const char *destName[] = { + "", "M", "Q", "Q,M", "A", "A,M", "A,Q", "A,Q,M" +}; + + +const char *shiftName[] = { + NULL, "QRS", "ARS", "LRS", NULL, "QLS", "ALS", "LLS" +}; + +/* + * Generate a single line of text for an instruction. Format is: + * + * c xxxx yyyy zzzz + * + * where: + * + * |P Normal/Protected location + * xxxx Memory address of instruction in hex + * yyyy First word of instruction in hex + * zzzz Second word of inctruction in hex, replaced by spaces if + * not present + * Disassmbled instruction + * Optional target address and contents + * + * Returns: + * # of words consumed by the instruction + */ + +int disassem(char *buf, uint16 addr, t_bool dbg, t_bool targ, t_bool exec) +{ + int consumed = 1; + char prot = ISPROTECTED(addr) ? 'P' : ' '; + char mode[8], optional[8], temp[8], decoded[64]; + const char *spc, *shift, *inter, *dest; + uint16 instr = LoadFromMem(addr); + uint16 delta = instr & OPC_ADDRMASK; + uint16 t; + + strcpy(optional, " "); + strcpy(decoded, "UNDEF"); + + if ((instr & OPC_MASK) != 0) { + strcpy(decoded, opName[(instr & OPC_MASK) >> 12]); + + if ((instr & MOD_RE) == 0) + strcpy(mode, delta == 0 ? "+ " : "- "); + else strcpy(mode, "* "); + + switch (instr & OPC_MASK) { + case OPC_ADQ: + case OPC_LDQ: + case OPC_LDA: + case OPC_EOR: + case OPC_AND: + case OPC_SUB: + case OPC_ADD: + case OPC_DVI: + case OPC_MUI: + if (ISCONSTANT(instr)) + strcat(mode, "="); + break; + } + + if (delta == 0) { + consumed++; + sprintf(optional, "%04X", LoadFromMem(addr + 1)); + sprintf(temp, "$%04X", LoadFromMem(addr + 1)); + } else sprintf(temp, "$%02X", delta); + + strcat(decoded, mode); + if ((instr & MOD_IN) != 0) + strcat(decoded, "("); + strcat(decoded, temp); + if ((instr & MOD_IN) != 0) + strcat(decoded, ")"); + + strcat(decoded, idxName[(instr & (MOD_I1 | MOD_I2)) >> 8]); + } else { + spc = spcName[(instr & OPC_SPECIALMASK) >> 8]; + + switch (instr & OPC_SPECIALMASK) { + case OPC_EIN: + case OPC_IIN: + case OPC_SPB: + case OPC_CPB: + case OPC_NOP: + sprintf(decoded, "%s", spc); + break; + + case OPC_EXI: + sprintf(decoded, "%s $%02X", spc, instr & OPC_MODMASK); + break; + + case OPC_SKIPS: + sprintf(decoded, "%s $%01X", + skpName[(instr & OPC_SKIPMASK) >> 4], instr & OPC_SKIPCOUNT); + break; + + case OPC_SLS: + case OPC_INP: + case OPC_OUT: + case OPC_INA: + case OPC_ENA: + case OPC_ENQ: + case OPC_INQ: + sprintf(decoded, "%s $%02X", spc, delta); + break; + + case OPC_INTER: + t = instr & (MOD_LP | MOD_XR | MOD_O_A | MOD_O_Q | MOD_O_M); + inter = interName[t >> 3]; + dest = destName[instr & (MOD_D_A | MOD_D_Q | MOD_D_M)]; + if (inter != NULL) + sprintf(decoded, "%s %s", inter, dest); + break; + + case OPC_SHIFTS: + shift = shiftName[(instr & OPC_SHIFTMASK) >> 5]; + if (shift != NULL) + sprintf(decoded, "%s $%X", shift, instr & OPC_SHIFTCOUNT); + break; + } + } + + if (dbg) { + sprintf(buf, "%c %04X %04X %s %s", + prot, addr, instr, optional, decoded); + } else { + sprintf(buf, "%c %04X %s %s", + prot, instr, optional, decoded); + } + if (targ) { + const char *rel = ""; + t_bool indJmp = FALSE; + uint8 more = 0; + uint16 taddr, taddr2, base; + + switch (instr & OPC_MASK) { + case OPC_ADQ: + case OPC_LDQ: + case OPC_RAO: + case OPC_LDA: + case OPC_EOR: + case OPC_AND: + case OPC_SUB: + case OPC_ADD: + case OPC_SPA: + case OPC_STA: + case OPC_STQ: + case OPC_DVI: + case OPC_MUI: + if (((instr & (MOD_IN | MOD_I1 | MOD_I2)) == 0) || exec) + if (disEffectiveAddr(addr, instr, &base, &taddr) == SCPE_OK) { + more = cpu_dev.dctrl & DBG_FULL ? 2 : 1; + taddr2 = taddr; + if (((instr & (MOD_RE | MOD_IN | MOD_I1 | MOD_I2)) == MOD_RE) && + ((sim_switches & SWMASK('R')) != 0)) { + taddr2 -= RelBase; + rel = "*"; + } + } + break; + + case OPC_JMP: + if (((instr & (MOD_IN | MOD_I1 | MOD_I2)) == MOD_IN) & !dbg) { + if (disEffectiveAddr(addr, instr & ~MOD_IN, &base, &taddr) == SCPE_OK) { + taddr2 = taddr; + indJmp = TRUE; + if (((instr & MOD_RE) != 0) && ((sim_switches & SWMASK('R')) != 0)) { + taddr2 -= RelBase; + rel = "*"; + } + break; + } + } + /* FALLTHROUGH */ + + case OPC_RTJ: + if (((instr & (MOD_IN | MOD_I1 | MOD_I2)) != 0) & !dbg) + break; + + if (disEffectiveAddr(addr, instr, &base, &taddr) == SCPE_OK) { + more = cpu_dev.dctrl & DBG_FULL ? 2 : 1; + taddr2 = taddr; + if (((instr & (MOD_RE | MOD_IN | MOD_I1 | MOD_I2)) == MOD_RE) && + ((sim_switches & SWMASK('R')) != 0)) { + taddr2 -= RelBase; + rel = "*"; + } + } + break; + + case OPC_SPECIAL: + switch (instr & OPC_SPECIALMASK) { + case OPC_SLS: + break; + + case OPC_SKIPS: + taddr = doADDinternal(MEMADDR(addr + 1), instr & OPC_SKIPCOUNT); + taddr2 = taddr; + if ((sim_switches & SWMASK('R')) != 0) + taddr2 -= RelBase; + more = 1; + break; + + case OPC_SPB: + case OPC_CPB: + if (exec) { + taddr = Qreg; + taddr2 = taddr; + if ((sim_switches & SWMASK('R')) != 0) + taddr2 -= RelBase; + more = 1; + } + break; + + case OPC_INP: + case OPC_OUT: + case OPC_EIN: + case OPC_IIN: + case OPC_INTER: + case OPC_INA: + case OPC_ENA: + case OPC_NOP: + case OPC_ENQ: + case OPC_INQ: + case OPC_EXI: + case OPC_SHIFTS: + break; + } + break; + } + + if (more || indJmp) { + int i, count = 48 - strlen(buf); + + for (i = 0; i < count; i++) + strcat(buf, " "); + + buf += strlen(buf); + if (indJmp) { + sprintf(buf, "[ => (%04X%s)", taddr2, rel); + } else { + if (more == 1) + sprintf(buf, "[ => %04X%s %s {%04X}", taddr2, rel, + P[MEMADDR(taddr)] ? "(P)" : "", + LoadFromMem(taddr)); + else sprintf(buf, "[ => %04X%s (B:%04X%s) %s {%04X}", + taddr2, rel, base, rel, + P[MEMADDR(taddr)] ? "(P)" : "", + LoadFromMem(taddr)); + } + } + } + return consumed; +} diff --git a/CDC1700/cdc1700_dp.c b/CDC1700/cdc1700_dp.c new file mode 100644 index 00000000..0c2b147a --- /dev/null +++ b/CDC1700/cdc1700_dp.c @@ -0,0 +1,1102 @@ +/* + + 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_dp.c: disk pack controller support + * Simh devices: dp0, dp1 + */ +#include "cdc1700_defs.h" + +#define ADDRSTATUS iod_readR[2] + +extern char INTprefix[]; + +extern void RaiseExternalInterrupt(DEVICE *); + +extern t_bool doDirectorFunc(DEVICE *, t_bool); +extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); +extern void fw_IOunderwayEOP(IO_DEVICE *, uint16); +extern void fw_IOcompleteEOP(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_IOalarm(t_bool, DEVICE *, IO_DEVICE *, const char *); +extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); + +extern t_stat checkReset(DEVICE *, uint8); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_protected(UNIT *, int32, CONST char *, void *); +extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); + +extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); + +extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); +extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); + +extern uint16 LoadFromMem(uint16); +extern t_bool IOStoreToMem(uint16, uint16, t_bool); + +extern uint16 M[], Areg, IOAreg; + +extern t_bool IOFWinitialized; + +extern t_bool ExecutionStarted; + +extern UNIT cpu_unit; + +static t_stat show_drive(FILE *, UNIT *, int32, CONST void *); + +t_stat set_dp853(UNIT *, int32, CONST char *, void *); +t_stat set_dp854(UNIT *, int32, CONST char *, void *); + +static t_stat show_addressing(FILE *, UNIT *, int32, CONST void *); + +t_stat set_normal(UNIT *, int32, CONST char *, void *); +t_stat set_reverse(UNIT *, int32, CONST char *, void *); + +/* Constants */ + +#define DP_NUMWD (96) /* words/sector */ +#define DP_NUMBY (DP_NUMWD * sizeof(uint16)) +#define DP_NUMSC (16) /* sectors/track */ +#define DP_NUMTR (10) /* tracks/cylinder */ +#define DP_853CY (100) /* cylinders for 853 drive */ +#define DP_854CY (203) /* cylinders for 854 drive */ +#define DP853_SIZE (DP_853CY * DP_NUMTR * DP_NUMSC * DP_NUMBY) +#define DP854_SIZE (DP_854CY * DP_NUMTR * DP_NUMSC * DP_NUMBY) + +#define DPLBA(i) \ + ((i->cylinder * DP_NUMSC * DP_NUMTR) + (i->head * DP_NUMSC) + i->sector) + +#define DP_NUMDR 2 /* # drives */ + +struct dpio_unit { + uint16 state; /* Current state of the drive */ +#define DP_IDLE 0x0000 /* Idle */ +#define DP_XFER 0x0001 /* Control info transfer */ +#define DP_SEEK 0x0002 /* Seeking */ +#define DP_WRITE 0x0003 /* Write data */ +#define DP_READ 0x0004 /* Read data */ +#define DP_COMPARE 0x0005 /* Compare data */ +#define DP_CHECKWORD 0x0006 /* Checkword check (NOOP) */ +#define DP_WRITEADDR 0x0007 /* Write address (NOOP) */ + uint16 CWA; /* Current memory address */ + uint16 LWA; /* LWA + 1 for transfer */ + uint16 sectorRA; /* Sector Record Address */ + uint16 cylinder; /* Current cylinder # */ + uint16 head; /* Current head # */ + uint16 sector; /* Current sector # */ + uint16 buf[DP_NUMWD]; /* Sector buffer */ + t_bool oncyl; /* Unit on-cylinder status */ +} DPunits[DP_NUMDR]; + +t_bool DPbusy = FALSE; /* Controller vs. unit busy */ + +enum dpio_status { + DPIO_MORE, /* More I/O pending */ + DPIO_DONE, /* I/O processing completed */ + DPIO_PROTECT, /* Protect fault */ + DPIO_MISMATCH, /* Compare mismatch */ + DPIO_ADDRERR /* Addressing error */ +}; + +t_stat dp_svc(UNIT *); +t_stat dp_reset(DEVICE *); +t_stat dp_attach(UNIT *, CONST char *); +t_stat dp_detach(UNIT *); + +void DPstate(const char *, DEVICE *, IO_DEVICE *); +t_bool DPreject(IO_DEVICE *, t_bool, uint8); +enum IOstatus DPin(IO_DEVICE *, uint8); +enum IOstatus DPout(IO_DEVICE *, uint8); +t_bool DPintr(IO_DEVICE *); + +/* + 1738-A/B Disk Pack Controller + + Addresses + Computer Instruction + Q Register Output From A Input to A + (Bits 02-00) + + 001 Director Function Director Status + 010 Load Address Address Register Status + 011 Write + 100 Read + 101 Compare + 110 Checkword Check + 111 Write Address + + Operations: + + Director Function + + 15 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | | | | X | X | | | | | X | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | + | | | | | | Clr Interrupts + | | | | | Ready and not Busy + | | | | | Interrupt Req. + | | | | EOP Interrupt Req. + | | | Interrupt on Alarm + | | Release + | Unit Select + Unit Select Code + + Load Address, Checkword Check, Write Address or Address Register Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + +---------------------------+ +-----------+ +-----------+ + Cylinder Head Sector + 853: 0-99 0-9 0-15 + 854: 0-202 + + Write, Read or Compare + + 15 14 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | + +-------------------------------------------------------+ + FWA - 1 + + Status Response: + + Director Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | Ready + | | | | | | | | | | | | | Busy + | | | | | | | | | | | | Interrupt + | | | | | | | | | | | On Cylinder + | | | | | | | | | | End of Operation + | | | | | | | | | Alarm + | | | | | | | | No Compare + | | | | | | | Protected + | | | | | | Checkword Error + | | | | | Lost Data + | | | | Seek Error + | | | Address Error + | | Defective Track + | Storage Parity Error + Protect Fault + + Address Register Status + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + +---------------------------+ +-----------+ +-----------+ + Cylinder Head Sector + 853: 0-99 0-9 0-15 + 854: 0-202 + */ + +IO_DEVICE DPdev = IODEV(NULL, "1738-B", 1738, 3, 0xFF, 0, + DPreject, DPin, DPout, NULL, NULL, + DPstate, DPintr, NULL, NULL, + 0x7F, 8, + MASK_REGISTER1 | MASK_REGISTER2 | MASK_REGISTER3 | \ + MASK_REGISTER4 | MASK_REGISTER5 | MASK_REGISTER6 | \ + MASK_REGISTER7, + MASK_REGISTER1 | MASK_REGISTER2, + MASK_REGISTER0, MASK_REGISTER0, + 0, 0, DPunits); + +/* DP data structures + + dp_dev DP device descriptor + dp_unit DP units + dp_reg DP register list + dp_mod DP modifier list +*/ + +UNIT dp_unit[] = { + { UDATA(&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_854, DP854_SIZE), + 0, 0, 0, 0, 0, &DPunits[0] + }, + { UDATA(&dp_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_854, DP854_SIZE), + 0, 0, 0, 0, 0, &DPunits[1] + }, +}; + +REG dp_reg[] = { + { HRDATA(FUNCTION, DPdev.FUNCTION, 16) }, + { HRDATA(STATUS, DPdev.STATUS, 16) }, + { HRDATA(IENABLE, DPdev.IENABLE, 16) }, + { HRDATA(ADDRSTATUS, DPdev.ADDRSTATUS, 16) }, + { NULL } +}; + +MTAB dp_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "1738-B", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "EQUIPMENT", &set_equipment, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, "DRIVE", NULL, NULL, &show_drive, NULL }, + { MTAB_XTD|MTAB_VUN, 0, NULL, "853", &set_dp853, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, NULL, "854", &set_dp854, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL,"PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL}, + { MTAB_XTD|MTAB_VDV, 0, "ADDRESSING", NULL, NULL, &show_addressing, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NORMAL", &set_normal, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "REVERSE", &set_reverse, NULL, NULL }, + { 0 } +}; + +DEBTAB dp_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "ERROR", DBG_DERROR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DERROR | DBG_DLOC }, + { NULL } +}; + +DEVICE dp_dev = { + "DP", dp_unit, dp_reg, dp_mod, + DP_NUMDR, 10, 31, 1, 8, 8, + NULL, NULL, &dp_reset, + NULL, &dp_attach, &dp_detach, + &DPdev, + DEV_DEBUG | DEV_DISK | DEV_DISABLE | \ + DEV_DIS | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT, + 0, dp_deb, + NULL, + NULL +}; + +/* + * Display disk pack drive type + */ +static t_stat show_drive(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if ((uptr->flags & UNIT_854) != 0) + fprintf(st, "854 drive"); + else fprintf(st, "853 drive"); + return SCPE_OK; +} + +/* + * Set drive type to 853. If execution has started, disallow device type + * changes. + */ +t_stat set_dp853(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if ((uptr->flags & UNIT_854) != 0) { + if ((uptr->flags & UNIT_ATT) != 0) + return SCPE_ALATT; + + if (ExecutionStarted) + return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); + + uptr->flags &= ~UNIT_854; + uptr->capac = DP853_SIZE; + } + return SCPE_OK; +} + +/* + * Set drive type to 854. If execution has started, disallow device type + * changes. + */ +t_stat set_dp854(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if ((uptr->flags & UNIT_854) == 0) { + if ((uptr->flags & UNIT_ATT) != 0) + return SCPE_ALATT; + + if (ExecutionStarted) + return sim_messagef(SCPE_IERR, "Unable to change drive type after execution started\n"); + + uptr->flags |= UNIT_854; + uptr->capac = DP854_SIZE; + } + return SCPE_OK; +} + +/* + * Display the device addressing mode + */ +static t_stat show_addressing(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if ((dp_dev.flags & DEV_REVERSE) == 0) + fprintf(st, "Addressing: Normal"); + else fprintf(st, "Addressing: Reverse"); + return SCPE_OK; +} + +/* + * Set device to normal addressing. + */ +t_stat set_normal(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + dp_dev.flags &= ~DEV_REVERSE; + return SCPE_OK; +} + +/* + * Set device to reverse addressing. + */ +t_stat set_reverse(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + dp_dev.flags |= DEV_REVERSE; + return SCPE_OK; +} + +/* + * Dump the current internal state of the DP device. + */ +const char *DPstateStr[] = { + "Idle", "Xfer", "Seek", "Write", "Read", "Compare", "Checkword", "WriteAddr" +}; + +void DPstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Sta: %04X, Ena: %04X, Sel: %s, Busy: %s]\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->STATUS, iod->IENABLE, + iod->iod_unit == NULL ? "None" : + (iod->iod_unit == dev->units ? "0" : "1"), + DPbusy ? "Yes" : "No"); + if ((dp_unit[0].flags & UNIT_ATT) != 0) + fprintf(DBGOUT, + "%s[0: State: %s, Cur: %04X, Last: %04X, RA: %04X, Oncyl: %s]\r\n", + INTprefix, DPstateStr[DPunits[0].state], DPunits[0].CWA, + DPunits[0].LWA, DPunits[0].sectorRA, + DPunits[0].oncyl ? "Yes" : "No"); + if ((dp_unit[1].flags & UNIT_ATT) != 0) + fprintf(DBGOUT, + "%s[1: State: %s, Cur: %04X, Last: %04X, RA: %04X, Oncyl: %s]\r\n", + INTprefix, DPstateStr[DPunits[1].state], DPunits[1].CWA, + DPunits[1].LWA, DPunits[1].sectorRA, + DPunits[1].oncyl ? "Yes" : "No"); +} + +/* + * Determine if a non-standard interrupt condition is present. + */ +t_bool DPintr(IO_DEVICE *iod) +{ + return (ISENABLED(iod, IO_1738_RBINT) && + ((DEVSTATUS(iod) & (IO_ST_READY | IO_ST_BUSY)) == IO_ST_READY)); +} + +/* + * Load and validate disk address in the A register + */ +static t_bool LoadDiskAddress(UNIT *uptr, struct dpio_unit *iou, uint16 state) +{ + uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; + + iou->oncyl = FALSE; + DPdev.ADDRSTATUS = iou->sectorRA = IOAreg; + + /* + * Split the disk address into separate fields. + */ + iou->cylinder = (IOAreg >> 8) & 0xFF; + iou->head = (IOAreg >> 4) & 0xF; + iou->sector = IOAreg & 0xF; + + if ((iou->cylinder >= numcy) || (iou->head >= DP_NUMTR)) + return FALSE; + + iou->state = state; + return TRUE; +} + +/* + * Set up a disk I/O operation with the A register containing FWA - 1. + */ +static void StartDPDiskIO(UNIT *uptr, struct dpio_unit *iou, uint16 state) +{ + iou->LWA = LoadFromMem(IOAreg); + iou->CWA = ++IOAreg; + + DPbusy = TRUE; + + DPdev.STATUS &= IO_ST_READY | IO_ST_PROT | IO_1738_ONCYL; + fw_IOunderwayEOP(&DPdev, 0); + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sDP - Start I/O, current: %04X, last: %04X, state: %d\r\n", + INTprefix, iou->CWA, iou->LWA, state); + + if (iou->CWA == iou->LWA) { + /* + * This is an empty I/O request, complete it immediately. + */ + DPbusy = FALSE; + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%sDP - Empty I/O request\r\n", INTprefix); + + fw_IOcompleteEOP(FALSE, &dp_dev, &DPdev, 0xFFFF, "Null transfer complete"); + return; + } + + iou->state = state; + sim_activate(uptr, DP_IO_WAIT); +} + +/* + * Increment sector # and update Sector Record Address. + */ +static void DPDiskIOIncSector(struct dpio_unit *iou) +{ + if (++iou->sector >= DP_NUMSC) { + iou->sector = 0; + if (++iou->head >= DP_NUMTR) { + iou->head = 0; + iou->cylinder++; + } + } + iou->sectorRA = ((iou->cylinder << 8) | (iou->head << 4)) | iou->sector; + DPdev.ADDRSTATUS = iou->sectorRA; +} + +/* + * Initiate a read operation on a disk. + */ +static enum dpio_status DPDiskIORead(UNIT *uptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; + uint32 lba = DPLBA(iou); + int i; + + if (iou->cylinder >= numcy) + return DPIO_ADDRERR; + + sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); + sim_fread(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); + + for (i = 0; i < DP_NUMWD; i++) { + /*** TODO: fix protect check ***/ + if (!IOStoreToMem(iou->CWA, iou->buf[i], TRUE)) + return DPIO_PROTECT; + + iou->CWA++; + if (iou->CWA == iou->LWA) { + DPDiskIOIncSector(iou); + return DPIO_DONE; + } + } + DPDiskIOIncSector(iou); + return DPIO_MORE; +} + +/* + * Initiate a write operation on a disk. + */ +static enum dpio_status DPDiskIOWrite(UNIT *uptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; + uint32 lba = DPLBA(iou); + t_bool fill = FALSE; + int i; + + if (iou->cylinder >= numcy) + return DPIO_ADDRERR; + + for (i = 0; i < DP_NUMWD; i++) { + if (!fill) { + iou->buf[i] = LoadFromMem(iou->CWA); + iou->CWA++; + if (iou->CWA == iou->LWA) + fill = TRUE; + } else iou->buf[i] = 0; + } + + sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); + sim_fwrite(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); + DPDiskIOIncSector(iou); + return fill ? DPIO_DONE : DPIO_MORE; +} + +/* + * Initiate a compare operation on a disk. + */ +static enum dpio_status DPDiskIOCompare(UNIT *uptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + uint16 numcy = ((uptr->flags & UNIT_854) != 0) ? DP_854CY : DP_853CY; + uint32 lba = DPLBA(iou); + int i; + + if (iou->cylinder >= numcy) + return DPIO_ADDRERR; + + sim_fseeko(uptr->fileref, lba * DP_NUMBY, SEEK_SET); + sim_fread(iou->buf, sizeof(uint16), DP_NUMWD, uptr->fileref); + + for (i = 0; i < DP_NUMWD; i++) { + if (iou->buf[i] != LoadFromMem(iou->CWA)) + return DPIO_MISMATCH; + + iou->CWA++; + if (iou->CWA == iou->LWA) { + DPDiskIOIncSector(iou); + return DPIO_DONE; + } + } + DPDiskIOIncSector(iou); + return DPIO_MORE; +} + +/* + * Perform read/write/compare sector operations from within the unit + * service routine. + */ +void DPDiskIO(UNIT *uptr, uint16 iotype) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + const char *error = "Unknown"; + enum dpio_status status; + + switch (iotype) { + case DP_WRITE: + status = DPDiskIOWrite(uptr); + break; + + case DP_READ: + status = DPDiskIORead(uptr); + break; + + case DP_COMPARE: + status = DPDiskIOCompare(uptr); + break; + } + + switch (status) { + case DPIO_MORE: + sim_activate(uptr, DP_IO_WAIT); + break; + + case DPIO_PROTECT: + DPdev.STATUS |= IO_1738_SPROT; + error = "Protection Fault"; + goto err; + + case DPIO_ADDRERR: + DPdev.STATUS |= IO_1738_ADDRERR; + error = "Address Error"; + err: + iou->state = DP_IDLE; + + DPbusy = FALSE; + + if ((dp_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sDP - Read/Write/Compare failed - %s\r\n", + INTprefix, error); + + fw_IOalarm(FALSE, &dp_dev, &DPdev, "Alarm"); + break; + + case DPIO_MISMATCH: + DPdev.STATUS |= IO_1738_NOCOMP; + /* FALLTHROUGH */ + + case DPIO_DONE: + iou->state = DP_IDLE; + + DPbusy = FALSE; + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sDP - Read/Write/Compare transfer complete\r\n", INTprefix); + + fw_IOcompleteEOP(TRUE, &dp_dev, &DPdev, 0xFFFF, "Transfer complete"); + break; + } +} + +/* Unit service */ + +t_stat dp_svc(UNIT *uptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[DP: dp_svc() entry]\r\n", INTprefix); + if ((dp_dev.dctrl & DBG_DSTATE) != 0) + DPstate("svc_entry", &dp_dev, &DPdev); + } + + switch (iou->state) { + case DP_IDLE: + /* + * Unit is idle, nothing to do. + */ + break; + + case DP_XFER: + /* + * Transfer of positioning information is complete. + */ + iou->state = DP_SEEK; + sim_activate(uptr, DP_SEEK_WAIT); + + /* + * If this is the currently selected unit, update controller status + * and possibly ganerate an interrupt. + */ + if (DPdev.iod_unit == uptr) { + DPdev.STATUS |= IO_ST_EOP; + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sDP - Load Address positioning transfer complete\r\n", + INTprefix); + + fw_IOintr(FALSE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Load address"); + } + break; + + case DP_SEEK: + iou->state = DP_IDLE; + iou->oncyl = TRUE; + + DPdev.STATUS &= ~IO_ST_BUSY; + + /* + * If this is the currently selected unit, update controller status + * and possibly generate an interrupt. + */ + if (DPdev.iod_unit == uptr) { + DPdev.STATUS |= IO_1738_ONCYL; + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%sDP - Seek complete\r\n", INTprefix); + + fw_IOintr(TRUE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Seek complete"); + } + break; + + case DP_WRITE: + case DP_READ: + case DP_COMPARE: + DPDiskIO(uptr, iou->state); + break; + + case DP_CHECKWORD: + iou->state = DP_IDLE; + iou->oncyl = TRUE; + + /* + * Set Sector Record Address to the start of the next track. + */ + iou->sector = 0; + if (++iou->head >= DP_NUMTR) { + iou->head = 0; + iou->cylinder++; + } + iou->sectorRA = ((iou->cylinder << 8) | (iou->head << 4)) | iou->sector; + DPdev.ADDRSTATUS = iou->sectorRA; + + DPdev.STATUS |= IO_ST_EOP | IO_1738_ONCYL; + DPdev.STATUS &= ~IO_ST_BUSY; + DPbusy = FALSE; + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, "%sDP - Checkword transfer complete\r\n", INTprefix); + + if ((DPdev.STATUS & (IO_ST_READY | IO_ST_BUSY)) == IO_ST_READY) + fw_IOintr(TRUE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Checkword transfer"); + + fw_IOintr(FALSE, &dp_dev, &DPdev, 0, 0, 0xFFFF, "Checkword"); + break; + + case DP_WRITEADDR: + break; + } + + if ((dp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[DP: dp_svc() exit]\r\n", INTprefix); + if ((dp_dev.dctrl & DBG_DSTATE) != 0) + DPstate("svc_exit", &dp_dev, &DPdev); + } + return SCPE_OK; +} + +/* Reset routine */ + +t_stat dp_reset(DEVICE *dptr) +{ + t_stat r; + + if (IOFWinitialized) + if ((dptr->flags & DEV_DIS) == 0) + if ((r = checkReset(dptr, DPdev.iod_equip)) != SCPE_OK) + return r; + + DPbusy = FALSE; + + /* + * Cancel any existing unit selection. + */ + DPdev.iod_unit = NULL; + + /* + * Clear on-cylinder status + */ + DPdev.STATUS &= ~IO_1738_ONCYL; + + return SCPE_OK; +} + +/* Attach routine */ + +t_stat dp_attach(UNIT *uptr, CONST char *cptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + const char *drivetype = ((uptr->flags & UNIT_854) != 0) ? "854" : "853"; + t_addr capac = ((uptr->flags & UNIT_854) != 0) ? DP854_SIZE : DP853_SIZE; + t_stat r; + + uptr->capac = capac; + r = attach_unit(uptr, cptr); + if (r != SCPE_OK) + return r; + + /* + * If this is a newly created file, set the drive size appropriately. + */ + if (sim_fsize_ex(uptr->fileref) == 0) + sim_set_fsize(uptr->fileref, capac); + + if (sim_fsize_ex(uptr->fileref) != capac) { + if (ExecutionStarted) { + detach_unit(uptr); + return sim_messagef(SCPE_OPENERR, "Unable to autosize drive after execution started\n"); + } + /* + * This is not the correct size according the drive type but this is the + * first attach. Force the drive to match the size of the disk. + */ + switch (sim_fsize_ex(uptr->fileref)) { + case DP854_SIZE: + uptr->capac = DP854_SIZE; + uptr->flags |= UNIT_854; + break; + + case DP853_SIZE: + uptr->capac = DP853_SIZE; + uptr->flags &= ~UNIT_854; + break; + + default: + detach_unit(uptr); + return sim_messagef(SCPE_OPENERR, "Unsupported disk size\n"); + } + } + /* + * Set unit to cylinder 0, head 0, sector 0 and indicate on-cylinder. + */ + iou->cylinder = 0; + iou->head = 0; + iou->sector = 0; + iou->oncyl = TRUE; + + return SCPE_OK; +} + +/* Detach routine */ + +t_stat dp_detach(UNIT *uptr) +{ + struct dpio_unit *iou = (struct dpio_unit *)uptr->up7; + t_stat stat; + + sim_cancel(uptr); + stat = detach_unit(uptr); + iou->oncyl = FALSE; + + return stat; +} + +/* Check if I/O should be rejected */ + +t_bool DPreject(IO_DEVICE *iod, t_bool output, uint8 reg) +{ + if (output) { + switch (reg) { + /* + * Director function + */ + case 0x01: + /*** TODO: Check protect status ***/ + return DPbusy; + + /* + * Write Address - always unsupported + */ + case 0x07: + return TRUE; + + /* + * Write/Checkword Check + */ + case 0x03: + case 0x06: + /*** TODO: Check protect status ***/ + /* FALLTHROUGH */ + + /* + * Load Address/Read/Compare + */ + case 0x02: + case 0x04: + case 0x05: + return ((DEVSTATUS(iod) & + (IO_ST_READY | IO_ST_BUSY | IO_1738_ONCYL)) != + (IO_ST_READY | IO_1738_ONCYL)); + } + } + return FALSE; +} + +/* Perform I/O */ + +enum IOstatus DPin(IO_DEVICE *iod, uint8 reg) +{ + /* + * All input requests should be handled by the I/O framework. + */ + return IO_REJECT; +} + +enum IOstatus DPout(IO_DEVICE *iod, uint8 reg) +{ + UNIT *uptr; + struct dpio_unit *iou; + + switch (reg) { + /* + * Director function + */ + case 0x01: + /* + * Reject the request if both select and release are set + */ + if ((IOAreg & (IO_1738_USEL | IO_1738_REL)) == (IO_1738_USEL | IO_1738_REL)) + return IO_REJECT; + + if (doDirectorFunc(&dp_dev, TRUE)) { + /* + * The device interrupt mask has been explicitly changed. If the + * device state is such that an interrupt can occur, generate it now. + */ + + /* + * Note: don't check for "Ready and not Busy Interrupt" here since + * it's defined as "Next Ready and not Busy", i.e. defer until the + * next opportunity. + */ + if ((ICHANGED(&DPdev) & IO_DIR_EOP) != 0) { + if ((DPdev.STATUS & IO_ST_EOP) != 0) { + if ((dp_dev.dctrl & DBG_DINTR) != 0) + fprintf(DBGOUT, + "%sDP: Mask change EOP interrupt\r\n", INTprefix); + RaiseExternalInterrupt(&dp_dev); + } + } + } + + /* + * Handle select/release. + */ + if ((IOAreg & (IO_1738_USEL | IO_1738_REL)) != 0) { + uint16 unit = (IOAreg & IO_1738_USC) >> 9; + + if ((dp_dev.flags & DEV_REVERSE) != 0) + unit ^= 1; + + DPdev.STATUS &= ~IO_ST_READY; + if ((IOAreg & IO_1738_USEL) != 0) { + DPdev.iod_unit = &dp_unit[unit]; + if ((dp_unit[unit].flags & UNIT_ATT) != 0) { + DPdev.STATUS |= IO_ST_READY; + iou = (struct dpio_unit *)dp_unit[unit].up7; + if (iou->oncyl) { + DPdev.STATUS |= IO_1738_ONCYL; + DPdev.ADDRSTATUS = iou->sectorRA; + } + if ((iou->state == DP_XFER) || (iou->state == DP_SEEK) || DPbusy) + DPdev.STATUS |= IO_ST_BUSY; + } + } + + if ((IOAreg & IO_1738_REL) != 0) { + /*** TODO: check protect conditions ***/ + DPdev.iod_unit = NULL; + DPdev.STATUS &= ~(IO_1738_ONCYL | IO_ST_BUSY); + if (DPbusy) + DPdev.STATUS |= IO_ST_BUSY; + } + } + break; + + /* + * Load address + */ + case 0x02: + if ((uptr = DPdev.iod_unit) != NULL) { + iou = (struct dpio_unit *)uptr->up7; + + if (LoadDiskAddress(uptr, iou, DP_XFER)) { + DPdev.STATUS &= IO_ST_READY | IO_ST_PROT; + DPdev.STATUS |= IO_ST_BUSY; + sim_activate(uptr, DP_XFER_WAIT); + } else { + if ((dp_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sDP: Bad Load Address (%04X)\r\n", INTprefix, Areg); + + fw_IOintr(FALSE, &dp_dev, &DPdev, IO_1738_ADDRERR | IO_ST_EOP | IO_ST_ALARM, 0, 0xFFFF, "Bad load address"); + } + } else return IO_REJECT; + break; + + /* + * Write + */ + case 0x03: + if ((uptr = DPdev.iod_unit) != NULL) { + iou = (struct dpio_unit *)uptr->up7; + + StartDPDiskIO(uptr, iou, DP_WRITE); + } else return IO_REJECT; + break; + + /* + * Read + */ + case 0x04: + if ((uptr = DPdev.iod_unit) != NULL) { + iou = (struct dpio_unit *)uptr->up7; + + StartDPDiskIO(uptr, iou, DP_READ); + } else return IO_REJECT; + break; + + /* + * Compare + */ + case 0x05: + if ((uptr = DPdev.iod_unit) != NULL) { + iou = (struct dpio_unit *)uptr->up7; + + StartDPDiskIO(uptr, iou, DP_COMPARE); + } else return IO_REJECT; + break; + + /* + * Checkword check + */ + case 0x06: + if ((uptr = DPdev.iod_unit) != NULL) { + iou = (struct dpio_unit *)uptr->up7; + if (LoadDiskAddress(uptr, iou, DP_CHECKWORD)) { + DPdev.STATUS &= IO_ST_READY | IO_ST_PROT | IO_1738_ONCYL; + DPdev.STATUS |= IO_ST_BUSY; + DPbusy = TRUE; + sim_activate(uptr, DP_XFER_WAIT); + } else { + if ((dp_dev.dctrl & DBG_DERROR) != 0) + fprintf(DBGOUT, + "%sDP: Bad Checkword Address (%04X)\r\n", + INTprefix, Areg); + + fw_IOintr(FALSE, &dp_dev, &DPdev, IO_1738_ADDRERR | IO_ST_EOP | IO_ST_ALARM, 0, 0xFFFF, "Bad checkword"); + } + } else return IO_REJECT; + break; + } + return IO_REPLY; +} + +/* + * Autoload support + */ +t_stat DPautoload(void) +{ + UNIT *uptr = &dp_unit[(dp_dev.flags & DEV_REVERSE) == 0 ? 0 : 1]; + + if ((uptr->flags & UNIT_ATT) != 0) { + uint32 i; + + for (i = 0; i < DP_NUMSC; i++) { + t_offset offset = i * DP_NUMBY; + void *buf = &M[i * DP_NUMWD]; + + sim_fseeko(uptr->fileref, offset, SEEK_SET); + if (sim_fread(buf, sizeof(uint16), DP_NUMWD, uptr->fileref) != DP_NUMWD) + return SCPE_IOERR; + } + return SCPE_OK; + } + return SCPE_UNATT; +} diff --git a/CDC1700/cdc1700_io.c b/CDC1700/cdc1700_io.c new file mode 100644 index 00000000..b454944b --- /dev/null +++ b/CDC1700/cdc1700_io.c @@ -0,0 +1,584 @@ +/* + + 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_io.c: CDC1700 I/O subsystem + */ + +#include "cdc1700_defs.h" + +extern char INTprefix[]; + +extern t_bool inProtectedMode(void); +extern uint16 dev1INTR(DEVICE *); +extern uint16 cpuINTR(DEVICE *); +extern uint16 dcINTR(void); + +extern void RaiseExternalInterrupt(DEVICE *); + +extern enum IOstatus fw_doIO(DEVICE *, t_bool); + +extern uint16 Areg, Mreg, Preg, OrigPreg, Qreg, Pending, IOAreg, IOQreg, M[]; +extern uint8 Protected, INTflag; +extern t_uint64 Instructions; + +extern t_bool FirstRejSeen; +extern uint32 CountRejects; + +extern DEVICE cpu_dev, dca_dev, dcb_dev, dcc_dev, tti_dev, tto_dev, + ptr_dev, ptp_dev, cdr_dev; + +static const char *status[] = { + "REPLY", "REJECT", "INTERNALREJECT" +}; + +/* + * The I/O sub-system uses the Q-register to provide controller addressing: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | W | E | Command | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * If W is non-zero, it addresses a 1706-A buffered data channel. If W + * is zero, it addresses a non-buffered controller. + * + * Note that buffered operations (DMA) can be performed by certain controllers + * (e.g. The Disk Pack Controller) using DSA (Direct Storage Access). + */ + +typedef enum IOstatus devIO(DEVICE *, t_bool); + +/* + * There can be up to 16 equipment addresses. + */ +devIO *IOcall[16]; +DEVICE *IOdev[16]; +devINTR *IOintr[16]; + +/* + * Display device debug status + */ +t_stat show_debug(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + DEVICE *dptr; + t_bool first = TRUE; + DEBTAB *tab; + + if (uptr == NULL) + return SCPE_IERR; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + tab = dptr->debflags; + if (tab == NULL) + return SCPE_IERR; + + fprintf(st, "Debug: "); + if (dptr->dctrl != 0) { + uint32 dctrl = dptr->dctrl; + + while ((tab->name != NULL) && (dctrl != 0)) { + if ((dctrl & tab->mask) != 0) { + if (!first) + fprintf(st, ","); + fprintf(st, "%s", tab->name); + dctrl &= ~tab->mask; + first = FALSE; + } + tab++; + } + } else fprintf(st, "None"); + return SCPE_OK; +} + +/* + * Display equipment/station address, buffered data channel and optional + * additional information: + * + * Stop on Reject status + * Protected status + */ +t_stat show_addr(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + DEVICE *dptr; + IO_DEVICE *iod; + + if (uptr == NULL) + return SCPE_IERR; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + iod = (IO_DEVICE *)dptr->ctxt; + fprintf(st, "equip: 0x"); + fprint_val(st, (t_value)iod->iod_equip, DEV_RDX, 16, PV_LEFT); + if (iod->iod_station != 0xFF) { + fprintf(st, ", station: "); + fprint_val(st, (t_value)iod->iod_station, DEV_RDX, 8, PV_LEFT); + } + if (iod->iod_dc != 0) + fprintf(st, ", Buffered Data Channel: %c", '0' + iod->iod_dc); + + if ((dptr->flags & DEV_REJECT) != 0) + fprintf(st, ", Stop on Reject"); + if ((dptr->flags & DEV_PROTECTED) != 0) + fprintf(st, ", Protected"); + return SCPE_OK; +} + +/* + * Device stop on reject handling. + */ +t_stat set_stoponrej(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + + if (cptr != NULL) + return SCPE_ARG; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + dptr->flags |= DEV_REJECT; + return SCPE_OK; +} + +t_stat clr_stoponrej(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + + if (cptr != NULL) + return SCPE_ARG; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + dptr->flags &= ~DEV_REJECT; + return SCPE_OK; +} + +/* + * Protected device. + */ +t_stat set_protected(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + + if (cptr != NULL) + return SCPE_ARG; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + dptr->flags |= DEV_PROTECTED; + return SCPE_OK; +} + +t_stat clear_protected(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + + if (cptr != NULL) + return SCPE_ARG; + + dptr = find_dev_from_unit(uptr); + if (dptr == NULL) + return SCPE_IERR; + + dptr->flags &= ~DEV_PROTECTED; + return SCPE_OK; +} + +/* + * Device interrupt handling + */ + +/* + * Interrupt status for a non-existent device + */ +uint16 noneINTR(DEVICE *dptr) +{ + return 0; +} + +/* + * Generic device interrupt status + */ +uint16 deviceINTR(DEVICE *dptr) +{ + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + if ((iod->iod_flags & STATUS_ZERO) != 0) + return 0; + + return (DEVSTATUS(iod) & IO_ST_INT) != 0 ? iod->iod_interrupt : 0; +} + +/* + * Rebuild the pending interrupt status based on the current status of + * each device. + */ +void rebuildPending(void) +{ + int i; + + /* + * Leave the CPU interrupt pending bit alone. + */ + Pending &= 1; + + for (i = 0; i < 16; i++) { + devINTR *rtn = IOintr[i]; + + Pending |= rtn(IOdev[i]); + } + + Pending |= dcINTR(); +} + +/* + * Handle generic director function(s) for a device. The function request is + * in IOAreg and the bits will be cleared in IOAreg as they are processed. + * Return TRUE if an explicit change was made to the device interrupt mask. + */ +t_bool doDirectorFunc(DEVICE *dptr, t_bool allowStacked) +{ + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + /* + * Mask out unsupported commands + */ + IOAreg &= iod->iod_dmask; + + if ((IOAreg & (IO_DIR_CINT | IO_DIR_CCONT)) != 0) { + if ((IOAreg & IO_DIR_CCONT) != 0) { + /* + * Preferentially use a device specific "Clear Controller" routine + * over the device reset routine. + */ + if (iod->iod_clear != NULL) { + iod->iod_clear(dptr); + } else { + if (dptr->reset != NULL) + dptr->reset(dptr); + } + } + + /* + * Clear all interrupt enables. + */ + iod->iod_ienable = 0; + iod->iod_oldienable = 0; + + /* + * Clear all pending interrupts. + */ + DEVSTATUS(iod) &= ~iod->iod_cmask; + + rebuildPending(); + + /* + * The device may allow other commands to be stacked along with Clear + * Interrupts and Clear Controller. + */ + if (!allowStacked) { + IOAreg = 0; + return FALSE; + } + IOAreg &= ~(IO_DIR_CINT | IO_DIR_CCONT); + } + + if ((IOAreg & iod->iod_imask) != 0) { + /* + * This request is enabling one or more interrupts. + */ + iod->iod_oldienable = iod->iod_ienable; + iod->iod_ienable |= Areg & iod->iod_imask; + IOAreg &= ~iod->iod_imask; + return TRUE; + } + return FALSE; +} + +/* + * Perform an I/O operation. Note that the "Continue" bit is only supported + * on the 1706 buffered data channel devices since it is not relevant in the + * emulation environment. + */ +enum IOstatus doIO(t_bool output, DEVICE **device) +{ + enum IOstatus result; + DEVICE *dev; + devIO *rtn; + const char *name; + IO_DEVICE *iod; + + /* + * Make a private copy of Areg and Qreg for use by I/O routines + */ + IOAreg = Areg; + IOQreg = Qreg; + + /* + * Get the target device and access routine + */ + dev = IOdev[((IOQreg & IO_EQUIPMENT) >> 7) & 0xF]; + rtn = IOcall[((IOQreg & IO_EQUIPMENT) >> 7) & 0xF]; + + if ((((IOQreg & IO_EQUIPMENT) >> 7) & 0xF) == 1) { + /* + * Device address 1 requires special processing. This address + * multiplexes the console teletypewriter, the paper tape reader and + * punch and the card reader using different station addresses: + * + * 001 - 1711/1712/1713 teletypewriter + * 010 - 1721/1722 paper tape reader + * 100 - 1723/1724 paper tape punch + * 110 - 1729 card reader + */ + switch ((IOQreg >> 4) & 0x7) { + case 0x01: + dev = &tti_dev; + break; + + case 0x02: + dev = &ptr_dev; + break; + + case 0x04: + dev = &ptp_dev; + break; + + case 0x06: + dev = &cdr_dev; + break; + + default: + return IO_INTERNALREJECT; + } + } + + if ((IOQreg & IO_W) != 0) { + /* + * Buffered data channel access. + */ + + /* + * Check if this device is only accessible on the AQ channel. + */ + if (dev != NULL) { + iod = (IO_DEVICE *)dev->ctxt; + + if ((iod->iod_flags & AQ_ONLY) != 0) { + *device = dev; + return IO_INTERNALREJECT; + } + } + + switch (IOQreg & IO_W) { + /* + * 1706-A Channel #1 + */ + case IO_1706_1_A: + case IO_1706_1_B: + case IO_1706_1_C: + case IO_1706_1_D: + dev = &dca_dev; + break; + + /* + * 1706-A Channel #2 + */ + case IO_1706_2_A: + case IO_1706_2_B: + case IO_1706_2_C: + case IO_1706_2_D: + dev = &dcb_dev; + break; + + /* + * 1706-A Channel #3 + */ + case IO_1706_3_A: + case IO_1706_3_B: + case IO_1706_3_C: + case IO_1706_3_D: + dev = &dcc_dev; + break; + + default: + return IO_INTERNALREJECT; + } + rtn = fw_doIO; + } + + *device = dev; + + if (dev != NULL) { + iod = (IO_DEVICE *)dev->ctxt; + name = iod->iod_name != NULL ? iod->iod_name : dev->name; + + if ((dev->dctrl & DBG_DTRACE) != 0) { + if (!FirstRejSeen) { + /* Trace I/O before operation */ + if ((Qreg & IO_W) != 0) + fprintf(DBGOUT, + "%s[%s: %s, A: %04X, Q: %04X (%04X/%04X), M: %04X, I: %c]\r\n", + INTprefix, name, output ? "OUT" : "INP", Areg, Qreg, + Qreg & IO_W, Qreg & (IO_EQUIPMENT | IO_COMMAND), Mreg, + INTflag ? '1' : '0'); + else fprintf(DBGOUT, + "%s[%s: %s, A: %04X, Q: %04X, M: %04X, I: %c]\r\n", + INTprefix, name, output ? "OUT" : "INP", Areg, Qreg, + Mreg, INTflag ? '1' : '0'); + if ((dev->dctrl & DBG_DSTATE) != 0) { + if (iod->iod_state != NULL) + (*iod->iod_state)("before", dev, iod); + } + } + } + + if ((dev->dctrl & DBG_DLOC) != 0) { + if (!FirstRejSeen) { + /* + * Trace location of the I/O instruction + instruction count + */ + fprintf(DBGOUT, "%s[%s: P: %04X, Inst: %llu]\r\n", + INTprefix, name, OrigPreg, Instructions); + } + } + + /* + * Reject I/O requests from non-protected instructions to protected + * devices unless it is a status register read. + */ + if (inProtectedMode()) { + if (!Protected) { + if ((dev->flags & DEV_PROTECT) != 0) { + if ((dev->flags & DEV_PROTECTED) == 0) { + if (output || ((Qreg & iod->iod_rmask) != 1)) { + if ((cpu_dev.dctrl & DBG_PROTECT) != 0) { + fprintf(DBGOUT, + "%sProtect REJECT\r\n", INTprefix); + } + return IO_REJECT; + } + } + } + } + } + } + result = rtn(dev, output); + if (dev != NULL) { + if ((dev->dctrl & DBG_DTRACE) != 0) { + if (!FirstRejSeen || (result == IO_REPLY)) { + /* Trace I/O after operation */ + if ((dev->dctrl & DBG_DSTATE) != 0) { + if (iod->iod_state != NULL) + (*iod->iod_state)("after", dev, iod); + } + if (output) + fprintf(DBGOUT, "%s[%s: => %s]\r\n", + INTprefix, name, status[result]); + else fprintf(DBGOUT, "%s[%s: => %s, A: %04X]\r\n", + INTprefix, name, status[result], Areg); + } + } + } + return result; +} + +/* + * Default I/O routine for devices which are not present + */ +static enum IOstatus notPresent(DEVICE *dev, t_bool output) +{ + if ((cpu_dev.dctrl & DBG_MISSING) != 0) { + fprintf(DBGOUT, + "%sAccess to missing device (Q: %04X, Equipment: %2u)\r\n", + INTprefix, Qreg, (Qreg & 0x7800) >> 7); + } + return IO_INTERNALREJECT; +} + +/* + * Build the I/O call table according to the enabled devices + */ +void buildIOtable(void) +{ + DEVICE *dptr; + int i; + + /* + * By default, all devices are marked "not present" + */ + for (i = 0; i < 16; i++) { + IOdev[i] = NULL; + IOcall[i] = notPresent; + IOintr[i] = noneINTR; + } + + /* + * Scan the device table and add equipment devices. + */ + i = 0; + while ((dptr = sim_devices[i++]) != NULL) { + if ((dptr->flags & (DEV_NOEQUIP | DEV_DIS)) == 0) { + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + IOdev[iod->iod_equip] = dptr; + IOcall[iod->iod_equip] = fw_doIO; + IOintr[iod->iod_equip] = + iod->iod_raised != NULL ? iod->iod_raised : deviceINTR; + } + } + + /* + * Set up fixed equipment code devices + */ + IOcall[1] = fw_doIO; + IOintr[1] = dev1INTR; + + IOintr[0] = cpuINTR; +} + +/* + * Load bootstrap code into memory + */ +void loadBootstrap(uint16 *code, int len, uint16 base, uint16 start) +{ + while (len--) { + M[base++] = *code++; + } + Preg = start; +} diff --git a/CDC1700/cdc1700_iofw.c b/CDC1700/cdc1700_iofw.c new file mode 100644 index 00000000..cc88b84a --- /dev/null +++ b/CDC1700/cdc1700_iofw.c @@ -0,0 +1,477 @@ +/* + + 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; +} diff --git a/CDC1700/cdc1700_lp.c b/CDC1700/cdc1700_lp.c new file mode 100644 index 00000000..207c4a32 --- /dev/null +++ b/CDC1700/cdc1700_lp.c @@ -0,0 +1,654 @@ +/* + + 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_lp.c: 1740 and 1742-30 line printer support + * Simh devices: lp + */ +#include "cdc1700_defs.h" + +#define COLUMNS 136 + +#define DEVTYPE_1740 IOtype_dev1 /* Device is 1740 */ +#define DEVTYPE_1742 IOtype_dev2 /* Device is 1742-30 */ + +#define FUNCTION2 iod_writeR[3] /* 1740 only */ + +/* + * Printer mapping table. Maps from the 7-bit device character set to 8-bit + * ASCII. If The mapping is 0xFF, the character is illegal and results in the + * ALARM status bit being raised. + */ +uint8 LPmap[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +extern char INTprefix[]; + +extern void fw_IOalarm(t_bool, DEVICE *, IO_DEVICE *, const char *); +extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); +extern void fw_IOunderwayData(IO_DEVICE *, uint16); +extern void fw_IOcompleteData(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_IOunderwayEOP(IO_DEVICE *, uint16); +extern void fw_IOcompleteEOP(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_setForced(IO_DEVICE *, uint16); +extern void fw_clearForced(IO_DEVICE *, uint16); + +extern void RaiseExternalInterrupt(DEVICE *); + +extern t_bool doDirectorFunc(DEVICE *, t_bool); + +extern t_stat checkReset(DEVICE *, uint8); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); +extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); + +extern t_stat set_protected(UNIT *, int32, CONST char *, void *); +extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); + +extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); + +extern uint16 Areg, IOAreg; + +extern t_bool IOFWinitialized; + +t_stat lp_show_type(FILE *, UNIT *, int32, CONST void *); +t_stat lp_set_type(UNIT *, int32, CONST char *, void *); + +t_stat lp_svc(UNIT *); +t_stat lp_reset(DEVICE *); + +void LPstate(const char *, DEVICE *, IO_DEVICE *); +enum IOstatus LPin(IO_DEVICE *, uint8); +enum IOstatus LPout(IO_DEVICE *, uint8); + +uint8 LPbuf[COLUMNS]; + +/* + 1740, 1742-30 Line Printer + + Addresses + Computer Instruction + Q Register Output From A Input to A + (Bits 01-00) + + 00 Write + 01 Director Function 1 Director Status + 11 Director Function 2 + + Notes: + 1. The documentation is incorrect about the location of Director Status. + Confirmed by the SMM17 LP1 diagnostic code. + + 2. Device register 3 (Director Function 2) is only present on the 1740 + Controller. + + Operations: + + Director Function 1 + + 15 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | X | X | X | X | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + | | | | | Clr Printer + | | | | Clr Interrupts + | | | Data Interrupt Req. + | | EOP Interrupt Req. + | Interrupt on Alarm + Print (1742-30 only) + + Director Function 2 (1740 only) + + 15 14 13 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | | X | X | X | X | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | + | | | | | | | | | | Print + | | | | | | | | | Single Space + | | | | | | | | Double Space + | | | | | | | Level 1 + | | | | | | Level 2 + | | | | | Level 3 + | | | | Level 4 + | | | Level 5 + | | Level 6 + | Level 7 + Level 12 + + Status Response: + + Director Status + + 15 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | X | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | + | | | | | | | | Ready + | | | | | | | Busy + | | | | | | Interrupt + | | | | | Data + | | | | End of Operation + | | | Alarm + | | Error (1742-30 only) + | Protected + Load Image (1742 only) +*/ + +IO_DEVICE LPdev = IODEV(NULL, "Line Printer", 1740, 4, 0xFF, 0, + fw_reject, LPin, LPout, NULL, NULL, + NULL, NULL, NULL, NULL, + 0x7F, 4, + MASK_REGISTER0 | MASK_REGISTER1 | MASK_REGISTER3, + MASK_REGISTER1, + MASK_REGISTER0 | MASK_REGISTER2, MASK_REGISTER2, + 0, 0, LPbuf); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_LPstate iod_private +#define iod_LPbuffer iod_private2 +#define iod_LPcolumn iod_private3 +#define iod_LPccpend iod_private4 /* 1742-30 only */ +#define iod_LPoverwrite iod_private10 /* 1740 only */ + +/* + * Current state of the device. + */ +#define IODP_LPNONE 0x0000 +#define IODP_LPCHARWAIT 0x0001 +#define IODP_LPPRINTWAIT 0x0002 +#define IODP_LPCCWAIT 0x0003 + +/* LP data structures + + lp_dev LP device descriptor + lp_unit LP unit descriptor + lp_reg LP register list + lp_mod LP modifiers list +*/ + +UNIT lp_unit = { + UDATA(&lp_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), LP_OUT_WAIT +}; + +REG lp_reg_1740[] = { + { HRDATA(FUNCTION, LPdev.FUNCTION, 16) }, + { HRDATA(FUNCTION2, LPdev.FUNCTION2, 16) }, + { HRDATA(STATUS, LPdev.STATUS, 16) }, + { HRDATA(IENABLE, LPdev.IENABLE, 16) }, + { NULL } +}; + +REG lp_reg_1742[] = { + { HRDATA(FUNCTION, LPdev.FUNCTION, 16) }, + { HRDATA(STATUS, LPdev.STATUS, 16) }, + { HRDATA(IENABLE, LPdev.IENABLE, 16) }, + { NULL } +}; + +MTAB lp_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "TYPE", &lp_set_type, NULL, NULL, + "TYPE={1740|1742}" }, + { MTAB_XTD|MTAB_VDV, 0, "TYPE", NULL, NULL, &lp_show_type, NULL, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "EQUIPMENT", &set_equipment, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { 0 } +}; + +/* + * LP debug flags + */ +#define DBG_V_CC (DBG_SPECIFIC+0)/* Carriage control characters */ + +#define DBG_CC (1 << DBG_V_CC) + +DEBTAB lp_deb[] = { + { "TRACE", DBG_DTRACE }, + { "STATE", DBG_DSTATE }, + { "INTR", DBG_DINTR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DLOC }, + { "CC", DBG_CC }, + { NULL } +}; + +DEVICE lp_dev = { + "LP", &lp_unit, NULL, lp_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &lp_reset, + NULL, NULL, NULL, + &LPdev, + DEV_DEBUG | DEV_DISABLE | DEV_OUTDEV | DEV_PROTECT, 0, lp_deb, + NULL, + NULL +}; + +t_stat lp_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + switch (LPdev.iod_type) { + case DEVTYPE_1740: + fprintf(st, "1740"); + break; + + case DEVTYPE_1742: + fprintf(st, "1742-30"); + break; + + default: + return SCPE_IERR; + } + return SCPE_OK; +} + +t_stat lp_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) + return SCPE_IERR; + if ((uptr->flags & UNIT_ATT) != 0) + return SCPE_ALATT; + + if (!strcmp(cptr, "1740")) { + LPdev.iod_type = DEVTYPE_1740; + LPdev.iod_model = "1740"; + lp_dev.registers = lp_reg_1740; + } else { + if (!strcmp(cptr, "1742") || + !strcmp(cptr, "1742-30")) { + LPdev.iod_type = DEVTYPE_1742; + LPdev.iod_model = "1742-30"; + lp_dev.registers = lp_reg_1742; + } else return SCPE_ARG; + } + return SCPE_OK; +} + +/* + * Dump the current internal state of the LP device. + */ +const char *LPprivateState[4] = { + "", "CHARWAIT", "PRINTWAIT", "CCWAIT" +}; + +void LPstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Func2: %04X, Sta: %04X, Ena: %04x, Count: %d, \ +Private: %s%s\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->FUNCTION2, iod->STATUS, iod->IENABLE, + iod->iod_LPcolumn, + LPprivateState[iod->iod_LPstate], + iod->iod_LPoverwrite ? ",Overwrite" : ""); +} + +/* Unit service */ + +t_stat lp_svc(UNIT *uptr) +{ + if ((lp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[LP: lp_svc() entry]\r\n", INTprefix); + if ((lp_dev.dctrl & DBG_DSTATE) != 0) + LPstate("svc_entry", &lp_dev, &LPdev); + } + + switch (LPdev.iod_LPstate) { + case IODP_LPCHARWAIT: + /* + * Generate an interrupt indicating that the device can accept more + * characters. + */ + LPdev.iod_LPstate = IODP_LPNONE; + + fw_IOcompleteData(FALSE, &lp_dev, &LPdev, 0xFFFF, "Output done"); + break; + + case IODP_LPPRINTWAIT: + /* + * Generate an interrupt indicating that the print/motion operation + * has completed. + */ + LPdev.iod_LPstate = IODP_LPNONE; + if (LPdev.iod_type == DEVTYPE_1742) + LPdev.iod_LPccpend = TRUE; + + fw_IOcompleteEOP(FALSE, &lp_dev, &LPdev, 0xFFFF, "EOP interrupt"); + break; + + case IODP_LPCCWAIT: + /* + * Generate an interrupt indicating that the motion operation has + * completed. + */ + LPdev.iod_LPstate = IODP_LPNONE; + LPdev.iod_LPccpend = FALSE; + + fw_IOcompleteData(FALSE, &lp_dev, &LPdev, 0xFFFF, "Control Char. Done"); + break; + + default: + return SCPE_IERR; + } + + if ((lp_dev.dctrl & DBG_DTRACE) != 0) { + fprintf(DBGOUT, "%s[LP: lp_svc() exit]\r\n", INTprefix); + if ((lp_dev.dctrl & DBG_DSTATE) != 0) + LPstate("svc exit", &lp_dev, &LPdev); + } + return SCPE_OK; +} + +/* Reset routine */ + +t_stat lp_reset(DEVICE *dptr) +{ + t_stat r; + + if (LPdev.iod_type == IOtype_default) { + /* + * Setup the default device type. + */ + LPdev.iod_type = DEVTYPE_1740; + LPdev.iod_model = "1740"; + lp_dev.registers = lp_reg_1740; + } + + if (IOFWinitialized) + if ((dptr->flags & DEV_DIS) == 0) + if ((r = checkReset(dptr, LPdev.iod_equip)) != SCPE_OK) + return r; + + DEVRESET(&LPdev); + + LPdev.iod_LPcolumn = 0; + if (LPdev.iod_type == DEVTYPE_1742) + LPdev.iod_LPccpend = TRUE; + + fw_setForced(&LPdev, IO_ST_READY); + + LPdev.STATUS |= IO_ST_DATA | IO_ST_EOP; + + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus LPin(IO_DEVICE *iod, uint8 reg) +{ + /* + * The framework only passes IN operations for the data register. + */ + return IO_REJECT; +} + +enum IOstatus LPout(IO_DEVICE *iod, uint8 reg) +{ + uint8 *buffer = (uint8 *)iod->iod_LPbuffer; + t_bool printwait = FALSE, changed; + + /* + * 1742-30 does not have a register 3 + */ + if (reg == 0x03) + if (LPdev.iod_type == DEVTYPE_1742) + return IO_REJECT; + + if ((lp_dev.dctrl & DBG_DSTATE) != 0) + LPstate("before", &lp_dev, &LPdev); + + switch (reg) { + case 0x00: + if (iod->iod_LPcolumn < COLUMNS) { + if (iod->iod_type == DEVTYPE_1740) { + uint8 ch1, ch2; + + ch1 = (Areg >> 8) & 0x7F; + ch2 = Areg & 0x7F; + + if ((LPmap[ch1] == 0xFF) || (LPmap[ch2] == 0xFF)) { + if ((lp_dev.dctrl & DBG_DTRACE) != 0) + fprintf(DBGOUT, + "%sLP: Invalid code (0x%02x, 0x%02x) ==> [0x%02x, 0x%02x]\r\\n", + INTprefix, ch1, ch2, LPmap[ch1], LPmap[ch2]); + fw_IOalarm(FALSE, &lp_dev, iod, "Invalid code"); + break; + } + /* + * Put both characters in the print buffer. + */ + buffer[iod->iod_LPcolumn++] = LPmap[ch1]; + buffer[iod->iod_LPcolumn++] = LPmap[ch2]; + } + + if (iod->iod_type == DEVTYPE_1742) { + uint8 ch = LPmap[Areg & 0x7F]; + + /* + * If this is the first character after a "Print" command, it + * controls the vertical motion of the paper. + * TODO: Implement format tape operations. + * For now, all format tape operations generate a single + * space motion + */ + if (LPdev.iod_LPccpend) { + const char *ccontrol = "\n"; + + if ((lp_dev.dctrl & DBG_CC) != 0) + fprintf(DBGOUT, "%s[LP: CC: 0x%02X]\r\n", INTprefix, Areg); + + if ((Areg & 0x40) == 0) { + switch (Areg & 0x03) { + case 0x0: + ccontrol = "\r"; + break; + + case 0x1: + ccontrol = "\n"; + break; + + case 0x2: + ccontrol = "\n\n"; + break; + + case 0x3: + ccontrol = "\n\n\n"; + break; + } + } else { + /*** TODO: implement format tape decoding ***/ + } + + if ((lp_unit.flags & UNIT_ATT) != 0) { + if (fputs(ccontrol, lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + } + fw_IOunderwayData(&LPdev, 0); + + LPdev.iod_LPstate = IODP_LPCCWAIT; + sim_activate(&lp_unit, LP_CC_WAIT); + break; + } + + /* + * Put non-control characters in the print buffer. + */ + if (ch != 0xFF) + buffer[iod->iod_LPcolumn++] = ch; + } + + fw_IOunderwayData(&LPdev, 0); + + LPdev.iod_LPstate = IODP_LPCHARWAIT; + sim_activate(&lp_unit, lp_unit.wait); + } + break; + + case 0x01: + changed = doDirectorFunc(&lp_dev, TRUE); + + if ((Areg & (IO_DIR_CINT | IO_DIR_CCONT)) != 0) + LPdev.STATUS |= IO_ST_DATA | IO_ST_EOP; + + if (changed) { + /* + * The device interrupt mask has been explicitly changed. If the + * interrupt on data was just set and the device can accept more + * data, generate an interrupt. + */ + if ((ICHANGED(&LPdev) & IO_DIR_DATA) != 0) { + if ((LPdev.STATUS & IO_ST_DATA) != 0) { + if ((lp_dev.dctrl & DBG_DINTR) != 0) + fprintf(DBGOUT, + "%sLP: DATA Interrupt from mask change\r\n", INTprefix); + RaiseExternalInterrupt(&lp_dev); + } + } + } + + if (iod->iod_type == DEVTYPE_1742) { + if ((Areg & IO_1742_PRINT) != 0) { + LPdev.STATUS &= ~IO_ST_ALARM; + if (iod->iod_LPcolumn != 0) { + if ((lp_unit.flags & UNIT_ATT) != 0) { + int i; + + for (i = 0; i < iod->iod_LPcolumn; i++) { + if (putc(buffer[i], lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + } + } + } + fw_IOunderwayEOP(&LPdev, IO_ST_INT); + + LPdev.iod_LPstate = IODP_LPPRINTWAIT; + sim_activate(&lp_unit, LP_PRINT_WAIT); + } + } + break; + + case 0x3: + if ((Areg & (IO_1740_MOTION | IO_1740_PRINT)) != 0) { + /* + * Here we need to print something, buffered data or vertical motion. + * Note that we will try to do the "right" thing with respect to + * stacked operations even though the physical hardware may not be + * able to do so. + */ + if ((Areg & IO_1740_PRINT) != 0) { + LPdev.STATUS &= ~IO_ST_ALARM; + if (iod->iod_LPcolumn != 0) { + if ((lp_unit.flags & UNIT_ATT) != 0) { + int i; + + if (iod->iod_LPoverwrite) { + if (putc('\r', lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + } + + for (i = 0; i < iod->iod_LPcolumn; i++) { + if (putc(buffer[i], lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + } + iod->iod_LPoverwrite = TRUE; + } + } + printwait = TRUE; + } + + if ((Areg & IO_1740_MOTION) != 0) { + /*** TODO: Implement format tape operations. + For now, all operations generate a single space motion ***/ + if ((lp_unit.flags & UNIT_ATT) != 0) { + if (putc('\n', lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + if ((Areg & IO_1740_DSP) != 0) { + if (putc('\n', lp_unit.fileref) == EOF) { + perror("LP I/O error"); + clearerr(lp_unit.fileref); + } + } + } + iod->iod_LPoverwrite = FALSE; + printwait = TRUE; + } + if (printwait) { + fw_IOunderwayEOP(&LPdev, IO_ST_INT); + + LPdev.iod_LPstate = IODP_LPPRINTWAIT; + sim_activate(&lp_unit, LP_PRINT_WAIT); + } + } + break; + + default: + if ((lp_dev.dctrl & DBG_DSTATE) != 0) + fprintf(DBGOUT, "%sLP: REJECT response\r\n", INTprefix); + return IO_REJECT; + } + if ((lp_dev.dctrl & DBG_DSTATE) != 0) + LPstate("after", &lp_dev, &LPdev); + + return IO_REPLY; +} + diff --git a/CDC1700/cdc1700_mt.c b/CDC1700/cdc1700_mt.c new file mode 100644 index 00000000..4231db0e --- /dev/null +++ b/CDC1700/cdc1700_mt.c @@ -0,0 +1,1973 @@ +/* + + 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_mt.c: 1732-A/B and 1732-3 magtape device support + * Simh devices: mt0, mt1, mt2, mt3 + */ + +/* + * Notes: + * + * 1. When writing a tape record in direct mode (programmed I/O), the + * documentation states "Whenever the computer breaks the continuity of the + * computer word outputs, the controller initiates an End of Record sequence." + * + * Since there is no explicit command sequence to initiate an End of Record + * operation, we need to estimate how many instructions to delay after a + * data OUT operation before terminating the current record. The tape drives + * operate at a maximum of 37.5 inches per second, so given the recording + * density we can compute the maximum output data rate and hence the time + * delay between 2 characters written to the tape. In addition, since we are + * emulating a 1732 controller, we need to take into account the character + * assembly operating mode where each data OUT instruction writes 2 chatacters + * to the tape. If we assume an average execution time of 1 microsecond per + * instruction (to keep the arithmetic simple), we get the following table: + * + * Density (bpi) Data Rate Instr. Delay Char Assembly Delay + * + * 200 7.50K char/sec 134 Instrs. 268 Instrs. + * 556 20.85K char/sec 48 Instrs. 96 Instrs. + * 800 30.00K char/sec 33 Instrs. 66 Instrs. + * 1600 60.00K char/sec 16 Instrs. 33 Instrs. + * + * The emulation does not need to be very strict with regard to timing: + * + * - Using instruction counts is not a very accurate representation of + * real time. 1784-2 instruction execution times range from 0.600 to + * 12.8 uSec so timing can easily be off by almost a factor of 2. + * See definition of LOOSETIMING below. + * + * - This does mean that SMM17 timing diagnostics may fail since SMM + * uses a timing loop of it's own. + * + * The PET diagnostic implies that the interrupt rate is too high for 1600 BPI + * access to be supported in direct mode. + * + * 2. This driver supports 3 modes of access: + * + * 1 - Direct mode (programmed I/O) + * 2 - Buffered mode (DSA or DMA mode) + * 3 - 1706 buffered data channel access + * + * The buffered data channel access is only supported if the controller is + * configured as a 1732-A (set mt type=1732-A). + * + */ +#include +#include "cdc1700_defs.h" +#include "sim_tape.h" + +#define LOOSETIMING(t) (((t) * 3)/ 2) + +#define DEVTYPE_1732_A IOtype_dev1 /* Controller is 1732-A */ +#define DEVTYPE_1732_3 IOtype_dev2 /* Controller is 1732-3 */ + +#define STATUS2 iod_readR[2] +#define BUFFEREDIO iod_writeR[3] +#define CURADDRESS iod_readR[3] + +extern char INTprefix[]; + +extern uint16 LoadFromMem(uint16); +extern t_bool IOStoreToMem(uint16, uint16, t_bool); + +extern t_bool doDirectorFunc(DEVICE *, t_bool); +extern void fw_IOcompleteEOP(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *); +extern void fw_IOunderwayEOP(IO_DEVICE *, uint16); +extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *); +extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8); +extern void fw_setForced(IO_DEVICE *, uint16); +extern void fw_clearForced(IO_DEVICE *, uint16); + +extern void loadBootstrap(uint16 *, int, uint16, uint16); + +extern t_stat checkReset(DEVICE *, uint8); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_protected(UNIT *, int32, CONST char *, void *); +extern t_stat clear_protected(UNIT *, int32, CONST char *, void *); + +extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *); +extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *); + +extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); + +extern void buildDCtables(void); + +extern uint16 M[], Areg, IOAreg; +extern t_uint64 Instructions; + +extern t_bool IOFWinitialized; + +extern UNIT cpu_unit; + +t_stat mt_show_transport(FILE *, UNIT *, int32, CONST void *); +t_stat mt_show_type(FILE *, UNIT *, int32, CONST void *); +t_stat mt_set_type(UNIT *, int32, CONST char *, void *); + +#define DENS u3 + +/* + * Nine-track magnetic tape bootstrap + */ +static uint16 mtbootstrap9[] = { + 0x6819, /* 00: STA* $19 */ + 0x6819, /* 01: STA* $19 */ + 0xE000, /* 02: LDQ+ $382 */ + 0x0382, /* 0x1382 for 1706 No. 1 */ + 0xC813, /* 04: LDA* $13 */ + 0x03FE, /* 05: OUT $-1 */ + 0x0DFE, /* 06: INQ $-1 */ + 0xC811, /* 07: LDA* $11 */ + 0x03FE, /* 08: OUT $-1 */ + 0x0DFE, /* 09: INQ $-1 */ + 0x0203, /* 0A: INP $03 */ + 0x6C0F, /* 0B: STA* ($0F) */ + 0xD80E, /* 0C: RAO* $0E */ + 0x18FC, /* 0D: JMP* $FC */ + 0x0D01, /* 0E: INQ $1 */ + 0x0B00, /* 0F: NOP */ + 0x02FE, /* 10: INP $-1 */ + 0x0FCB, /* 11: ALS $0B */ + 0x0131, /* 12: SAM $1 */ + 0x18F5, /* 13: JMP* $F5 */ + 0xC804, /* 14: LDA* $04 */ + 0x03FE, /* 15: OUT $-1 */ + 0x1C03, /* 16: JMP* ($03) */ + 0x044C, /* 17: DATA */ + 0x0100, /* 18: */ + 0x0000, /* 19: */ + 0x0000 /* 1A: */ +}; +#define MTBOOTLEN9 (sizeof(mtbootstrap9) / sizeof(uint16)) + +/* + * Seven-track magnetic tape bootstrap + */ +static uint16 mtbootstrap7[] = { + 0x0500, /* 00: IIN */ + 0x6824, /* 01: STA* $24 */ + 0x6824, /* 02: STA* $24 */ + 0xE000, /* 03: LDQ+ $0382 */ + 0x0382, /* 0x1382 for 1706 No. 1 */ + 0xC81E, /* 05: LDA* $1E */ + 0x03FE, /* 06: OUT $-1 */ + 0x0DFE, /* 07: INQ $-1 */ + 0xC81C, /* 08: LDA* $1C */ + 0x03FE, /* 09: OUT $-1 */ + 0x0DFE, /* 0A: INQ $-1 */ + 0x0A00, /* 0B: ENA $00 */ + 0x020D, /* 0C: INP $0D */ + 0x0FCA, /* 0D: ALS $0A */ + 0x0821, /* 0E: */ + 0x0A00, /* 0F: ENA $00 */ + 0x02FE, /* 10: INP $-1 */ + 0x0FC4, /* 11: ALS $04 */ + 0x0869, /* 12: EAM M */ + 0x0A00, /* 13: ENA $00 */ + 0x02FE, /* 14: INP $-1 */ + 0x0F42, /* 15: ARS $02 */ + 0x086C, /* 16: EAM A */ + 0x6C0F, /* 17: STA* ($0F) */ + 0xD80E, /* 18: RAO* $0E */ + 0x18F1, /* 19: JMP* $F1 */ + 0x0D01, /* 1A: */ + 0x0B00, /* 1B: */ + 0x02FE, /* 1C: */ + 0x0FCB, /* 1D: */ + 0x0131, /* 1E: */ + 0x18EA, /* 1F: */ + 0xC804, /* 20: */ + 0x03FE, /* 21: */ + 0x1C03, /* 22: */ + 0x0414, /* 23: */ + 0x0100, /* 24: */ + 0x0000, /* 25: */ + 0x0000 /* 26: */ +}; +#define MTBOOTLEN7 (sizeof(mtbootstrap7) / sizeof(uint16)) + +/* + * SMM17 bootstraps + */ +static uint16 smm17boot9[] = { + 0x68FE, /* xFE0: MTBOOT STA* *-1 */ + 0xE000, /* xFE1: LDQ =N$WESD */ + 0x0382, /* xFE2: EQUIP $382 */ + 0xC000, /* xFE3: LDA =N$44C */ + 0x044C, + 0x03FE, /* xFE5: OUT -1 */ + 0x09B3, /* xFE6: INA -$400-$44C */ + 0x0DFE, /* xFE7: INQ -1 */ + 0x03FE, /* xFE8: OUT -1 */ + 0x0F42, /* xFE9: ARS 2 */ + 0x03FE, /* xFEA: OUT -1 */ + 0x0DFE, /* xFEB: INQ -1 */ + 0x02FE, /* xFEC: MT1 INP -1 */ + 0x6CF1, /* xFED: STA* (MTBOOT-1) */ + 0x0102, /* xFEE: SAZ ENDBT-*-1 */ + 0xD8EF, /* xFEF: RAO* MTBOOT-1 */ + 0x18FB, /* xFF0: JMP* MT1 */ + 0x1007 /* xFF1: ENDBT JMP- QL ENTRY */ +}; +#define SMM17BOOTLEN9 (sizeof(smm17boot9) / sizeof(uint16)) + +static uint16 smm17boot7[] = { + 0x68FE, /* xFE0: MTBOOT STA* *-1 */ + 0xE000, /* xFE1: LDQ =N$WESD */ + 0x0382, /* xFE2: EQUIP $382 */ + 0xC000, /* xFE3: LDA =N$405 */ + 0x0405, + 0x03FE, /* xFE5: OUT -1 */ + 0x09FB, /* xFE6: INA -4 */ + 0x0DFE, /* xFE7: INQ -1 */ + 0x03FE, /* xFE8: OUT -1 */ + 0x0F42, /* xFE9: ARS 2 */ + 0x03FE, /* xFEA: OUT -1 */ + 0x0DFE, /* xFEB: INQ -1 */ + 0x0A00, /* xFEC: ENA 0 */ + 0x1807, /* xFED: JMP- MT2 */ + 0x02FE, /* xFEE: MT1 INP -1 */ + 0x0F42, /* xFEF: ARS 2 */ + 0xBCEE, /* xFF0: EOR* (MTBOOT-1) */ + 0x010A, /* xFF1: SAZ ENDBT-*-1 */ + 0x7CEC, /* xFF2: SPA* (MTBOOT-1) */ + 0xD8EB, /* xFF3: RAO* MTBOOT-1 */ + 0x02FE, /* xFF4: MT2 INP -1 */ + 0x0FCA, /* xFF5: ALS 10 */ + 0x7CE8, /* xFF6: SPA* (MTBOOT-1) */ + 0x02FE, /* xFF7: INP -1 */ + 0x0FC4, /* xFF8: ALS 4 */ + 0xBCE5, /* xFF9: EOR* (MTBOOT-1) */ + 0x7CE4, /* xFFA: SPA* (MTBOOT-1) */ + 0x18F2, /* xFFB: JMP* MT1 */ + 0x1007 /* xFFC: JMP- QL ENTRY */ +}; +#define SMM17BOOTLEN7 (sizeof(smm17boot7) / sizeof(uint16)) + +/* + * Shared I/O buffer. Note that this is larger than the max possible memory + * so the only way to handle such large records is to use non-DMA with + * dynamic processing of the data. + */ +#define MTSIZ 131072 +uint8 MTbuf[MTSIZ]; +t_mtrlnt MToffset, MTremain; +enum { MT_IDLE, MT_READING, MT_WRITING, MT_READTMO, MT_WRITETMO, MT_DSADONE } MTmode; + +t_stat mt_svc(UNIT *); +t_stat mt_reset(DEVICE *); +t_stat mt_boot(int32, DEVICE *); +t_stat mt_attach(UNIT *, CONST char *); +t_stat mt_detach(UNIT *); +t_stat mt_vlock(UNIT *, int32 val, CONST char *cptr, void *desc); + +void MTstate(const char *, DEVICE *, IO_DEVICE *); +void MTclear(DEVICE *); +t_bool MTreject(IO_DEVICE *, t_bool, uint8); +enum IOstatus MTin(IO_DEVICE *, uint8); +enum IOstatus MTout(IO_DEVICE *, uint8); +enum IOstatus MTBDCin(IO_DEVICE *, uint16 *, uint8); +enum IOstatus MTBDCout(IO_DEVICE *, uint16 *, uint8); + +/* + 1732-3 Magnetic Tape Controller + + Addresses + Computer Instruction + Q Register Output From A Input to A + + 00 Write Read + 01 Control Function Director Status 1 + 10 Unit Select Director Status 2 + 11 Buffered I/O Current Address + + Operations: + + Control Function + + 15 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | | | | | X | X | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | + +-----------+ | | | | Clr Controller + | | | | Clr Interrupts + | | | Data Interrupt Req. + | | Interrupt on EOP + | Interrupt on Alarm + | + Motion Control: + 0001 Write Motion + 0010 Read Motion + 0011 Backspace + 0101 Write File Mark/Tape Mark + 0110 Search File Mark/Tape Mark Forward + 0111 Search File Mark/Tape Mark Backward + 1000 Rewind Load + 1100 Rewind Unload (1732-A only) + + Unit Select + + 15 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | + | | | | +---+ | | | | | | Character Mode + | | | | | | | | | | BCD mode + | | | | | | | | | Binary mode + | | | | | | | | Select 800 BPI + | | | | | | | Select 556 BPI + | | | | | | Select 1600 BPI (1732-A 200 BPI) + | | | | | Assembly/Disassembly + | | | | Tape Unit (0-3) + | | | (1732-A only, additional unit select bit) + | | Select Tape Unit + | Deselect Tape Unit + Select Low Read Threshold (1733-3 only) + + Status Response: + + Director Status 1 + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | | Ready + | | | | | | | | | | | | | | Busy + | | | | | | | | | | | | | Interrupt + | | | | | | | | | | | | Data + | | | | | | | | | | | End of Operation + | | | | | | | | | | Alarm + | | | | | | | | | Lost Data + | | | | | | | | Protected + | | | | | | | Parity Error + | | | | | | End of Tape + | | | | | Beginning of Tape + | | | | File Mark + | | | Controller Active + | | Fill + | Storage Parity Error (1733-3 only) + Protect Fault (1733-3 only) + + Director Status 2 + + 15 9 8 7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X | X | X | X | X | X | | | | | | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | | | | | + | | | | | | | | | 556 BPI + | | | | | | | | 800 BPI + | | | | | | | 1600 BPI (1733-3 only) + | | | | | | Seven Track + | | | | | Write Enable + | | | | PE - Warning + | | | PE - Lost Data + | | PE - transport + | ID - Abort + Low Read Threshold + +*/ + +IO_DEVICE MTdev = IODEV(NULL, "Magtape", 1732, 7, 0xFF, 0, + MTreject, MTin, MTout, MTBDCin, MTBDCout, + MTstate, NULL, NULL, MTclear, + 0x7F, 4, + MASK_REGISTER0 | MASK_REGISTER1 | MASK_REGISTER2 | \ + MASK_REGISTER3, + MASK_REGISTER1 | MASK_REGISTER2 | MASK_REGISTER3, + 0, 0, 0, 1, NULL); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_mode iod_private /* operating mode */ +#define iod_delay iod_private3 /* current delay reason */ +#define iod_wasWriting iod_private4 /* writing was in progress */ +#define iod_reason iod_private5 /* reason for EOP */ +#define iod_CWA iod_readR[3] /* current DSA address */ +#define iod_LWA iod_private6 /* last word address */ +#define iod_DSApending iod_private10 /* DSA request pending */ + +/* + * Define delay functions other than the standard motion commands. The low + * 7 bits are available, zero is reserved to mean no pending delay. + */ +#define IO_DELAY_RDATA 0x01 /* Delay IO_ST_DATA for read*/ +#define IO_DELAY_WDATA 0x02 /* and write */ +#define IO_DELAY_RTMO 0x03 /* Read record timeout */ +#define IO_DELAY_WTMO 0x04 /* Write record timeout */ +#define IO_DELAY_EOP 0x05 /* EOP delay */ +#define IO_DSA_READ 0x06 /* DSA Read operation */ +#define IO_DSA_WRITE 0x07 /* DSA Write operation */ +#define IO_LOCAL_MASK 0x7F + +/* MT data structures + + mt_dev MT device descriptor + mt_unit MT units + mt_reg MT register list + mt_mod MT modifier list +*/ + +#define MT_NUMDR 4 /* # drives */ + +UNIT mt_unit[] = { + { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) }, + { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) }, + { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) }, + { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+UNIT_7TRACK, 0) }, +}; + +REG mt_reg_1732_A[] = { + { HRDATA(FUNCTION, MTdev.FUNCTION, 16) }, + { HRDATA(STATUS, MTdev.STATUS, 16) }, + { HRDATA(UNITSEL, MTdev.iod_writeR[2], 16) }, + { HRDATA(STATUS2, MTdev.STATUS2, 16) }, + { HRDATA(IENABLE, MTdev.IENABLE, 16) }, + { NULL } +}; + +REG mt_reg_1732_3[] = { + { HRDATA(FUNCTION, MTdev.FUNCTION, 16) }, + { HRDATA(STATUS, MTdev.STATUS, 16) }, + { HRDATA(UNITSEL, MTdev.iod_writeR[2], 16) }, + { HRDATA(STATUS2, MTdev.STATUS2, 16) }, + { HRDATA(IENABLE, MTdev.IENABLE, 16) }, + { HRDATA(BUFFEREDIO, MTdev.BUFFEREDIO, 16) }, + { HRDATA(CURADDRESS, MTdev.CURADDRESS, 16) }, + { HRDATA(LASTADDRESS, MTdev.iod_LWA, 16) }, + { NULL } +}; + +MTAB mt_mod[] = { + { MTUF_WLK, 0, "write enabled", "WRITEENABLED", &mt_vlock }, + { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", &mt_vlock }, + { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, + { MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY", &sim_tape_set_capac, &sim_tape_show_capac, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "TYPE", &mt_set_type, NULL, NULL, + "TYPE={1732-A|1732-3}" }, + { MTAB_XTD|MTAB_VDV, 0, "TYPE", NULL, NULL, &mt_show_type, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "EQUIPMENT", &set_equipment, NULL, NULL }, + { MTAB_XTD|MTAB_VUN, 0, "TRANSPORT", NULL, NULL, &mt_show_transport, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT", &set_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT", &clr_stoponrej, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT", &set_protected, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT", &clear_protected, NULL, NULL }, + { 0 } +}; + +/* + * MT debug flags + */ +#define DBG_V_OPS (DBG_SPECIFIC+0)/* Trace operations */ +#define DBG_V_READ (DBG_SPECIFIC+1)/* Dump read records */ +#define DBG_V_RDATA (DBG_SPECIFIC+2)/* Read through reg. 0 */ +#define DBG_V_WDATA (DBG_SPECIFIC+3)/* Write through reg. 0 */ +#define DBG_V_MTIO (DBG_SPECIFIC+4)/* Trace library routine calls */ +#define DBG_V_DENS (DBG_SPECIFIC+5)/* Trace density select changes */ +#define DBG_V_SELECT (DBG_SPECIFIC+6)/* Trace drive select/de-select */ + +#define DBG_OPS (1 << DBG_V_OPS) +#define DBG_READ (1 << DBG_V_READ) +#define DBG_RDATA (1 << DBG_V_RDATA) +#define DBG_WDATA (1 << DBG_V_WDATA) +#define DBG_MTIO (1 << DBG_V_MTIO) +#define DBG_DENS (1 << DBG_V_DENS) +#define DBG_SELECT (1 << DBG_V_SELECT) + +DEBTAB mt_deb[] = { + { "STATE", DBG_DSTATE }, + { "TRACE", DBG_DTRACE }, + { "INTR", DBG_DINTR }, + { "LOCATION", DBG_DLOC }, + { "FIRSTREJ", DBG_DFIRSTREJ }, + { "OPS", DBG_OPS }, + { "READ", DBG_READ }, + { "RDATA", DBG_RDATA }, + { "WDATA", DBG_WDATA }, + { "MTIO", DBG_MTIO }, + { "DENS", DBG_DENS }, + { "SELECT", DBG_SELECT }, + { NULL } +}; + +DEVICE mt_dev = { + "MT", mt_unit, NULL, mt_mod, + MT_NUMDR, 10, 31, 1, 8, 8, + NULL, NULL, &mt_reset, + &mt_boot, &mt_attach, &mt_detach, + &MTdev, + DEV_DEBUG | DEV_TAPE | DEV_DISABLE | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT, + 0, mt_deb, + NULL, + NULL +}; + +/* MT trace routine */ + +void mt_trace(UNIT *uptr, const char *what, t_stat st, t_bool xfer) +{ + int32 u = uptr - mt_dev.units; + const char *status = NULL; + + switch (st) { + case MTSE_OK: + status = "OK"; + break; + + case MTSE_TMK: + status = "Tape Mark"; + break; + + case MTSE_UNATT: + status = "Unattached"; + break; + + case MTSE_IOERR: + status = "IO Error"; + break; + + case MTSE_INVRL: + status = "Invalid Record Length"; + break; + + case MTSE_FMT: + status = "Invalid Format"; + break; + + case MTSE_BOT: + status = "Beginning Of Tape"; + break; + + case MTSE_EOM: + status = "End Of Medium"; + break; + + case MTSE_RECE: + status = "Error In Record"; + break; + + case MTSE_WRP: + status = "Write Protected"; + break; + + case MTSE_LEOT: + status = "Logical end of tape"; + break; + + case MTSE_RUNAWAY: + status = "Tape runaway"; + break; + } + + if (status != NULL) { + if (xfer) + fprintf(DBGOUT, "MT%d: %s, bytes %d - %s\r\n", u, what, MTremain, status)\ + ; + else fprintf(DBGOUT, "MT%d: %s - %s\r\n", u, what, status); + } else fprintf(DBGOUT, "MT%d: %s\r\n", u, what); + if ((mt_dev.dctrl & DBG_DLOC) != 0) + fprintf(DBGOUT, "MT%d: Inst: %llu\r\n", u, Instructions); +} + +/* MT trace routine (DSA mode) */ + +void mt_DSAtrace(UNIT *uptr, const char *what) +{ + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, "MT%d: DSA %s - CWA: 0x%04X, LWA: 0x%04X\r\n", + u, what, MTdev.iod_CWA, MTdev.iod_LWA); +} + +/* Tape library routine trace */ + +void mtio_trace(UNIT *uptr, const char *what, t_stat st, t_bool lvalid, t_mtrlnt len) +{ + int32 u = uptr - mt_dev.units; + t_bool bot = FALSE, eot = FALSE; + const char *status = "Unknown"; + + if (st != MTSE_UNATT) { + bot = sim_tape_bot(uptr); + eot = sim_tape_eot(uptr); + } + + switch (st) { + case MTSE_OK: + status = "OK"; + break; + + case MTSE_TMK: + status = "Tape mark"; + break; + + case MTSE_UNATT: + status = "Unattached"; + break; + + case MTSE_IOERR: + status = "IO error"; + break; + + case MTSE_INVRL: + status = "Invalid record length"; + break; + + case MTSE_FMT: + status = "Invalid format"; + break; + + case MTSE_BOT: + status = "Beginning of tape"; + break; + + case MTSE_EOM: + status = "End of medium"; + break; + + case MTSE_RECE: + status = "Error in record"; + break; + + case MTSE_WRP: + status = "Write protected"; + break; + + case MTSE_LEOT: + status = "Logical end of tape"; + break; + + case MTSE_RUNAWAY: + status = "Tape runaway"; + break; + } + fprintf(DBGOUT, "MT%d: MTIO [%s %s] %s - %s\r\n", + u, bot ? "BOT" : "", eot ? "EOT" : "", what, status); + if (lvalid) + fprintf(DBGOUT, + "MT%d: MTIO Record len: %u, Mode: 0x%04X\r\n", + u, len, MTdev.iod_mode); +} + + +/* Dump MT buffer */ + +char chars[128] = { + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', '|', '^', '_', + ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', ' ', '}', '~', ' ' +}; + +void mt_dump(void) +{ + t_mtrlnt offset = 0, count = MTremain; + char msg[80], text[16]; + + if (MTremain > 0) { + printf("Dump of MTbuf:\r\n"); + + while (count > 0) { + t_mtrlnt remain = count >= 10 ? 10 : count; + t_mtrlnt i; + + msg[0] = '\0'; + + for (i = 0; i < remain; i++) { + char temp[8]; + + text[i] = chars[MTbuf[offset] & 0x7F]; + + sprintf(temp, "0x%02x", MTbuf[offset++]); + if (msg[0] != '\0') + strcat(msg, " "); + strcat(msg, temp); + } + text[remain] = '\0'; + + printf("%-55s%s\r\n", msg, text); + + count -= remain; + } + } +} + +/* + * Dump the current internal state of the MT device. + */ +const char *MTstateStr[] = { + "Idle", "Reading", "Writing", "Read Timeout", "Write Timeout", "DSA Done" +}; + +void MTstate(const char *where, DEVICE *dev, IO_DEVICE *iod) +{ + char device[16]; + + strcpy(device, "None"); + if (iod->iod_unit != NULL) { + int32 u = iod->iod_unit - dev->units; + + sprintf(device, "MT%u", u); + } + + fprintf(DBGOUT, + "%s[%s %s: Func: %04X, Sta: %04X, Sta2: %04X, Ena: %04X]\r\n", + INTprefix, dev->name, where, + iod->FUNCTION, iod->STATUS, iod->STATUS2, iod->IENABLE); + fprintf(DBGOUT, + "%s[%s %s: Sel: %s, %s%s]\r\n", + INTprefix, dev->name, where, device, MTstateStr[MTmode], + iod->iod_wasWriting ? ", Was writing" : ""); +} + +void mt_data(UNIT *uptr, t_bool output, uint16 data) +{ + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, "MT%d: %s - 0x%04x\r\n", u, output ? "wrote" : "read", data); +} + +t_stat mt_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + switch (MTdev.iod_type) { + case DEVTYPE_1732_A: + fprintf(st, "1732-A"); + break; + + case DEVTYPE_1732_3: + fprintf(st, "1733-3"); + break; + + default: + return SCPE_IERR; + } + return SCPE_OK; +} + +t_stat mt_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) + return SCPE_IERR; + if ((uptr->flags & UNIT_ATT) != 0) + return SCPE_ALATT; + + if (!strcmp(cptr, "1732-A")) { + MTdev.iod_type = DEVTYPE_1732_A; + MTdev.iod_model = "1732-A"; + MTdev.iod_flags &= ~AQ_ONLY; + mt_dev.registers = mt_reg_1732_A; + buildDCtables(); + } else { + if (!strcmp(cptr, "1732-3")) { + MTdev.iod_type = DEVTYPE_1732_3; + MTdev.iod_model = "1732-3"; + MTdev.iod_flags |= AQ_ONLY; + mt_dev.registers = mt_reg_1732_3; + buildDCtables(); + } else return SCPE_ARG; + } + return SCPE_OK; +} + +/* + * Display magtape transport + */ +t_stat mt_show_transport(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (uptr == NULL) + return SCPE_IERR; + + if (MTdev.iod_type == DEVTYPE_1732_A) { + if ((uptr->flags & UNIT_7TRACK) != 0) + fprintf(st, "7-track 608 transport"); + else fprintf(st, "9-track 609 transport"); + } else { + if ((uptr->flags & UNIT_7TRACK) != 0) + fprintf(st, "7-track 6173 transport"); + else fprintf(st, "9-track 6193 transport"); + } + return SCPE_OK; +} + +/* + * Compute the delay time between new data being available from tape. This + * will be dependent on the density of the tape and the speed of the drive + * (in this case we assume 37.5 inches per sec). + */ +int32 mt_densityTimeout(t_bool loose) +{ + int32 result; + + switch (MTdev.STATUS2 & (IO_ST2_556 | IO_ST2_800)) { + case 0: + result = MT_200_WAIT; + break; + + case IO_ST2_556: + result = MT_556_WAIT; + break; + + case IO_ST2_800: + result = MT_800_WAIT; + break; + } + + if (MTdev.iod_type == DEVTYPE_1732_3) + if ((MTdev.STATUS2 & IO_ST2_1600) != 0) + result = MT_1600_WAIT; + + if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) + result *= 2; + + return loose ? LOOSETIMING(result) : result; +} + +/* Unit service */ + +t_stat mt_svc(UNIT *uptr) +{ + uint16 mask = IO_1732_STMSK; + uint16 delay = MTdev.iod_delay; + uint16 result; + t_stat status; + t_mtrlnt temp; + int32 tmo; + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "mt_svc", (t_stat)-1, FALSE); + + MTdev.iod_delay = 0; + + /* + * Check for local (internal) delays. + */ + if ((delay & IO_LOCAL_MASK) != 0) { + switch (delay) { + case IO_DELAY_RDATA: + fw_IOintr(FALSE, &mt_dev, &MTdev, IO_ST_DATA, 0, 0xFFFF, "Read Ready"); + tmo = mt_densityTimeout(TRUE); + MTdev.iod_event = Instructions + tmo; + + MTdev.iod_delay = IO_DELAY_RTMO; + sim_activate(uptr, tmo); + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Delayed IO_ST_DATA for read", (t_stat)-1, FALSE); + break; + + case IO_DELAY_WDATA: + fw_IOintr(FALSE, &mt_dev, &MTdev, IO_ST_DATA, 0, 0xFFFF, "Write Ready"); + tmo = mt_densityTimeout(TRUE); + MTdev.iod_event = Instructions + tmo; + + MTdev.iod_delay = IO_DELAY_WTMO; + sim_activate(uptr, tmo); + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Delayed IO_ST_DATA for write", (t_stat)-1, FALSE); + break; + + case IO_DELAY_RTMO: + MTmode = MT_READTMO; + + /* + * Drop DATA and schedule EOP completion + */ + MTdev.STATUS &= ~IO_ST_DATA; + MTdev.iod_delay = IO_DELAY_EOP; + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Read buffer timed out", (t_stat)-1, FALSE); + + if (MTremain != 0) { + MTdev.STATUS |= IO_ST_ALARM | IO_ST_LOST; + MTdev.iod_reason = "Read timed out - data lost"; + MTdev.iod_oldienable = MTdev.iod_ienable; + MTdev.iod_ienable &= ~IO_DIR_DATA; + } else MTdev.iod_reason = "Read timed out"; + + MTremain = 0; + sim_activate(uptr, MT_EOP_WAIT); + break; + + case IO_DELAY_WTMO: + MTmode = MT_WRITETMO; + status = sim_tape_wrrecf(uptr, MTbuf, MToffset); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "wrrecf", status, TRUE, MToffset); + + /* + * Drop DATA and schedule EOP completion + */ + MTdev.STATUS &= ~IO_ST_DATA; + MTdev.iod_delay = IO_DELAY_EOP; + MTdev.iod_reason = "Write timed out"; + sim_activate(uptr, MT_EOP_WAIT); + + if ((mt_dev.dctrl & DBG_OPS) != 0) { + MTremain = MToffset; + mt_trace(uptr, "Write buffer timed out", status, TRUE); + } + break; + + case IO_DELAY_EOP: + MTmode = MT_IDLE; + fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, ~IO_1732_ACTIVE, MTdev.iod_reason); + MTdev.iod_reason = NULL; + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Delayed EOP", (t_stat)-1, FALSE); + break; + + case IO_DSA_READ: + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_DSAtrace(uptr, "read"); + + if (MTdev.iod_CWA == MTdev.iod_LWA) { + /* + * DSA read transfer complete. + */ + MTmode = MT_DSADONE; + MTdev.iod_delay = IO_DELAY_EOP; + MTdev.iod_reason = "DSA read complete"; + sim_activate(uptr, MT_EOP_WAIT); + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "DSA read complete", (t_stat)-1, FALSE); + break; + } + + /* + * If there is no data available, terminate the transfer + */ + if (MTremain == 0) { + /* + * DSA read complete - no more data. + */ + MTmode = MT_DSADONE; + MTdev.iod_delay = IO_DELAY_EOP; + MTdev.iod_reason = "DSA read complete - no data"; + sim_activate(uptr, MT_EOP_WAIT); + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "DSA read complete - no data", (t_stat)-1, FALSE); + break; + } + + if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) { + if (MTremain >= 2) { + result = (MTbuf[MToffset] << 8) | MTbuf[MToffset + 1]; + MToffset += 2; + MTremain -= 2; + } else { + MTdev.STATUS |= IO_1732_FILL; + result = MTbuf[MToffset] << 8; + MToffset++; + MTremain--; + } + } else { + result = MTbuf[MToffset]; + MToffset++; + MTremain--; + } + + if ((uptr->flags & UNIT_7TRACK) != 0) + result &= 0x3F3F; + + if (!IOStoreToMem(MTdev.iod_CWA, result, TRUE)) { + /*** TODO: generate device protect error ***/ + } + MTdev.iod_CWA++; + MTdev.iod_delay = IO_DSA_READ; + sim_activate(uptr, mt_densityTimeout(FALSE)); + break; + + case IO_DSA_WRITE: + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_DSAtrace(uptr, "write"); + + if (MTdev.iod_CWA == MTdev.iod_LWA) { + /* + * DSA write transfer complete. + */ + status = sim_tape_wrrecf(uptr, MTbuf, MToffset); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "wrrecf", status, TRUE, MToffset); + + MTmode = MT_DSADONE; + MTdev.iod_delay = IO_DELAY_EOP; + MTdev.iod_reason = "DSA write complete"; + sim_activate(uptr, MT_EOP_WAIT); + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "DSA write complete", (t_stat)-1, FALSE); + break; + } + + result = LoadFromMem(MTdev.iod_CWA); + + if ((uptr->flags & UNIT_7TRACK) != 0) + result &= 0x3F3F; + + MTdev.iod_CWA++; + + if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) { + MTbuf[MToffset] = (result >> 8) & 0xFF; + MTbuf[MToffset + 1] = result & 0xFF; + MToffset += 2; + } else { + MTbuf[MToffset] = result & 0xFF; + MToffset += 1; + } + + MTdev.iod_delay = IO_DSA_WRITE; + sim_activate(uptr, mt_densityTimeout(FALSE)); + break; + } + return SCPE_OK; + } + + /* + * Check if we need to write a tape mark before processing the request. + */ + if (MTdev.iod_wasWriting) + switch (delay) { + case IO_1732_BACKSP: + case IO_1732_REWL: + case IO_1732A_REWU: + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Forced TM (BACKSP, REWL, REWU)", (t_stat)-1, FALSE); + status = sim_tape_wrtmk(uptr); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "wrtmk", status, FALSE, 0); + break; + } + + /* + * Command specific processing + */ + switch (delay) { + /* + * The following commands normally do not set "end of operation". "read + * motion" does set "end of operation" if a tape mark or end of tape + * is detected. + */ + case IO_1732_READ: + MTremain = 0; + status = sim_tape_rdrecf(uptr, MTbuf, &MTremain, MTSIZ); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "rdrecf", status, TRUE, MTremain); + + switch (status) { + case MTSE_OK: + break; + + case MTSE_TMK: + MTdev.STATUS |= IO_ST_ALARM | IO_1732_FMARK; + break; + + case MTSE_EOM: + MTdev.STATUS |= IO_ST_ALARM | IO_1732_EOT; + break; + + case MTSE_RECE: + MTdev.STATUS |= IO_ST_ALARM | IO_ST_PARITY; + MTremain = 0; + break; + } + MToffset = 0; + + if ((MTdev.STATUS & (IO_1732_FMARK | IO_1732_EOT | IO_ST_PARITY)) == 0) + mask &= ~IO_ST_EOP; + + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "READ", status, TRUE); + if ((mt_dev.dctrl & DBG_READ) != 0) + mt_dump(); + + if (MTremain > 0) { + if (MTdev.iod_DSApending) { + MTdev.iod_DSApending = FALSE; + MTdev.iod_delay = IO_DSA_READ; + sim_activate(uptr, mt_densityTimeout(FALSE)); + if ((mt_dev.dctrl & DBG_OPS) != 0) { + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, + "[MT%d: DSA Read started, CWA: 0x%04X, LWA: 0x%04X, Mode: 0x%X\r\n", + u, MTdev.iod_CWA, MTdev.iod_LWA, MTdev.iod_mode); + } + return SCPE_OK; + } + MTdev.iod_delay = IO_DELAY_RDATA; + sim_activate(uptr, MT_MIN_WAIT); + return SCPE_OK; + } + MTmode = MT_IDLE; + break; + + case IO_1732_WRITE: + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "WRITE", (t_stat)-1, FALSE); + + if (MTdev.iod_DSApending) { + MTdev.iod_DSApending = FALSE; + MTdev.iod_delay = IO_DSA_WRITE; + sim_activate(uptr, mt_densityTimeout(FALSE)); + if ((mt_dev.dctrl & DBG_OPS) != 0) { + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, + "[MT%d: DSA Write started, CWA: 0x%04X, LWA: 0x%04X, Mode: 0x%X\r\n", + u, MTdev.iod_CWA, MTdev.iod_LWA, MTdev.iod_mode); + } + return SCPE_OK; + } + MTdev.iod_delay = IO_DELAY_WDATA; + sim_activate(uptr, MT_MIN_WAIT); + return SCPE_OK; + + case IO_1732A_REWU: + status = sim_tape_rewind(uptr); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "rewind & unload", status, FALSE, 0); + + MTdev.STATUS |= IO_1732_BOT; + mt_detach(uptr); + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "REWU", status, FALSE); + + mask &= ~IO_ST_EOP; + break; + + /* + * The following commands set "end of operation" when the command + * completes. + */ + case IO_1732_BACKSP: + status = sim_tape_sprecr(uptr, &temp); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "sprecr", status, FALSE, 0); + + if (status == MTSE_TMK) + MTdev.STATUS |= IO_1732_FMARK; + if (sim_tape_bot(uptr)) + MTdev.STATUS |= IO_1732_BOT; + if (sim_tape_eot(uptr)) + MTdev.STATUS |= IO_1732_EOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "BACKSP", status, FALSE); + break; + + case IO_1732_WFM: + status = sim_tape_wrtmk(uptr); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "wrtmk", status, FALSE, 0); + +#if 0 + MTdev.STATUS |= IO_ST_ALARM | IO_1732_FMARK; +#endif + if (sim_tape_eot(uptr)) + MTdev.STATUS |= IO_1732_EOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "WFM", status, FALSE); + break; + + case IO_1732_SFWD: + while (!sim_tape_eot(uptr)) { + status = sim_tape_sprecf(uptr, &temp); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "sprecf", status, FALSE, 0); + + if (status == MTSE_TMK) { + MTdev.STATUS |= IO_1732_FMARK; + break; + } + } + if (sim_tape_bot(uptr)) + MTdev.STATUS |= IO_1732_BOT; + if (sim_tape_eot(uptr)) + MTdev.STATUS |= IO_1732_EOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "SFWD", status, FALSE); + break; + + case IO_1732_SBACK: + while (!sim_tape_bot(uptr)) { + status = sim_tape_sprecr(uptr, &temp); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "sprecr", status, FALSE, 0); + + if (status == MTSE_TMK) { + MTdev.STATUS |= IO_1732_FMARK; + break; + } + } + if (sim_tape_bot(uptr)) + MTdev.STATUS |= IO_1732_BOT; + if (sim_tape_eot(uptr)) + MTdev.STATUS |= IO_1732_EOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "SBACK", status, FALSE); + break; + + case IO_1732_REWL: + status = sim_tape_rewind(uptr); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "rewind", status, FALSE, 0); + + MTdev.STATUS |= IO_1732_BOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "REWL", status, FALSE); + break; + } + + /* + * If we are at a tape mark or end of tape, no data is available. + */ + if ((MTdev.STATUS & (IO_1732_FMARK | IO_1732_EOT)) != 0) + mask &= ~IO_ST_DATA; + + /* + * Controller is no longer active. + */ + mask &= ~IO_1732_ACTIVE; + + /* + * I/O is now complete. + */ + fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, mask, "Operation Complete"); + return SCPE_OK; +} + +/* Reset routine */ + +t_stat mt_reset(DEVICE *dptr) +{ + t_stat r; + + if (MTdev.iod_type == IOtype_default) { + /* + * Setup the default device type. + */ + MTdev.iod_type = DEVTYPE_1732_A; + MTdev.iod_model = "1732-A"; + MTdev.iod_flags &= ~AQ_ONLY; + mt_dev.registers = mt_reg_1732_A; + buildDCtables(); + } + + if (IOFWinitialized) + if ((dptr->flags & DEV_DIS) == 0) + if ((r = checkReset(dptr, MTdev.iod_equip)) != SCPE_OK) + return r; + + DEVRESET(&MTdev); + + MTdev.STATUS = 0; + MTdev.STATUS2 = 0; + + MTdev.iod_mode = 0; + MTdev.iod_unit = NULL; + MTdev.iod_delay = 0; + MTdev.iod_wasWriting = FALSE; + MTdev.iod_CWA = MTdev.iod_LWA = 0; + MTdev.iod_DSApending = FALSE; + MTmode = MT_IDLE; + + return SCPE_OK; +} + +/* Boot routine */ + +t_stat mt_boot(int32 unitno, DEVICE *dptr) +{ + if (unitno != 0) { + sim_printf("Can only boot from drive 0\n"); + return SCPE_ARG; + } + + if ((sim_switches & SWMASK('S')) != 0) { + /* + * Special bootstrap for System Maintenance Monitor (SMM17) + */ + uint16 base, equip; + + base = ((cpu_unit.capac - 1) & 0xF000) | 0xFE0; + loadBootstrap(smm17boot9, SMM17BOOTLEN9, base, base); + + /* + * Compute the equipment address to use and patch it into memory. + */ + equip = (MTdev.iod_equip << 7) | 2; + if ((sim_switches & SWMASK('D')) != 0) + equip |= 0x1000; + + M[base + 2] = equip; + + return SCPE_OK; + } + + loadBootstrap(mtbootstrap9, MTBOOTLEN9, 0, 0); + + /* + * Set A register according to the amount of memory installed. + */ + Areg = 0x5000; + if (cpu_unit.capac < 32768) + Areg = 0x4000; + if (cpu_unit.capac < 24576) + Areg = 0x2000; + + return SCPE_OK; +} + +/* Attach routine */ + +t_stat mt_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + r = sim_tape_attach(uptr, cptr); + if (r != SCPE_OK) + return r; + + uptr->flags &= ~UNIT_WPROT; + if (sim_switches & SWMASK('R')) + uptr->flags |= UNIT_WPROT; + + uptr->DENS = IO_ST2_800; + + /* + * If this units is currently selected, make it accessible. + */ + if (MTdev.iod_unit == uptr) { + MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800); + if ((uptr->flags & UNIT_WPROT) != 0) + MTdev.STATUS2 &= ~IO_ST2_WENABLE; + else MTdev.STATUS2 |= IO_ST2_WENABLE; + if ((uptr->flags & UNIT_7TRACK) != 0) + MTdev.STATUS2 |= IO_ST2_7TRACK; + else MTdev.STATUS2 &= ~IO_ST2_7TRACK; + fw_setForced(&MTdev, IO_ST_READY); + } + return r; +} + +/* Detach routine */ + +t_stat mt_detach(UNIT *uptr) +{ + t_stat st; + + if ((uptr->flags & UNIT_ATT) == 0) + return SCPE_OK; + + sim_cancel(uptr); + + st = sim_tape_detach(uptr); + if (st == MTSE_OK) { + if (MTdev.iod_unit == uptr) + fw_clearForced(&MTdev, IO_ST_READY); + } + return st; +} + +/* Write lock/enable routine */ + +t_stat mt_vlock(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (((uptr->flags & UNIT_ATT) != 0) && (val || sim_tape_wrp(uptr))) + uptr->flags |= UNIT_WPROT; + else uptr->flags &= ~UNIT_WPROT; + + return SCPE_OK; +} + +/* + * Perform a "Clear Controller" operation. Basically this is similar to a + * device reset except it does not forget the currently selected transport. + */ +void MTclear(DEVICE *dptr) +{ + UNIT *uptr; + + DEVRESET(&MTdev); + + MTdev.STATUS = 0; + MTdev.STATUS2 = 0; + + MTdev.iod_mode = 0; + MTdev.iod_delay = 0; + MTdev.iod_wasWriting = FALSE; + MTmode = MT_IDLE; + + if ((uptr = MTdev.iod_unit) != NULL) { + fw_setForced(&MTdev, IO_ST_READY); + + MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800); + if ((uptr->flags & UNIT_WPROT) != 0) + MTdev.STATUS2 &= ~IO_ST2_WENABLE; + else MTdev.STATUS2 |= IO_ST2_WENABLE; + if ((uptr->flags & UNIT_7TRACK) != 0) + MTdev.STATUS2 |= IO_ST2_7TRACK; + else MTdev.STATUS2 &= ~IO_ST2_7TRACK; + } +} + +/* + * Check if I/O should be rejected. I/O allowed if: + * + * Reg. Write (OUT) Read (INP) + * + * 00 Ready and writing active Ready and data available + * 01 Controller dependent Always allowed + * Always allow Clear Interrupts/Controller + * 1732-A: Reject if controller active + * 1732-3: Always allow + * 10 Controller active clear Always allowed + * 11 Controller busy and EOP clear Always allowed + * + * If a data I/O (register 0) is performed after the tape motion has timed + * out, we need to generate an ALARM + LOST data status. + */ +t_bool MTreject(IO_DEVICE *iod, t_bool output, uint8 reg) +{ + switch (reg) { + case 0: + if (output) + return ((iod->STATUS & (IO_ST_DATA | IO_ST_READY)) != + (IO_ST_DATA | IO_ST_READY)) || (MTmode != MT_WRITING); + + return ((iod->STATUS & (IO_ST_DATA | IO_ST_READY)) != + (IO_ST_DATA | IO_ST_READY)) || (MTremain == 0); + + case 1: + if (output) { + if (MTdev.iod_type == DEVTYPE_1732_A) + if ((IOAreg & (IO_DIR_CCONT | IO_DIR_CINT)) == 0) + return ((iod->STATUS & IO_1732_ACTIVE) != 0); + } + break; + + case 2: + if (output) + return ((iod->STATUS & IO_1732_ACTIVE) != 0); + break; + + case 3: + if (MTdev.iod_type != DEVTYPE_1732_3) + return TRUE; + if (output) + return ((iod->STATUS & (IO_ST_EOP | IO_ST_BUSY)) == IO_ST_BUSY); + break; + } + return FALSE; +} + +/* Perform an input operation on a selected drive. This can be performed + by issuing a command directly to the device or via a 1706 */ + +enum IOstatus doMTIn(UNIT *uptr, uint16 *data, t_bool via1706) +{ + uint16 result; + + /* + * Reject the request if we are not reading or data is not available + */ + if ((MTmode != MT_READING) || ((MTdev.STATUS & IO_ST_DATA) == 0)) + return IO_REJECT; + + sim_cancel(uptr); + + if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) { + if (MTremain >= 2) { + result = (MTbuf[MToffset] << 8) | MTbuf[MToffset + 1]; + MToffset += 2; + MTremain -= 2; + } else { + MTdev.STATUS |= IO_1732_FILL; + result = MTbuf[MToffset] << 8; + MToffset++; + MTremain--; + } + } else { + result = MTbuf[MToffset]; + MToffset++; + MTremain--; + } + if ((uptr->flags & UNIT_7TRACK) != 0) + result &= 0x3F3F; + + if ((mt_dev.dctrl & DBG_RDATA) != 0) + mt_data(uptr, FALSE, result); + + fw_IOintr(FALSE, &mt_dev, &MTdev, 0, IO_ST_DATA, 0xFFFF, NULL); + + if (MTremain != 0) { + MTdev.iod_delay = IO_DELAY_RDATA; + sim_activate(uptr, (int32)(MTdev.iod_event - Instructions)); + } else { + MTmode = MT_IDLE; + MTdev.STATUS |= IO_ST_EOP; + MTdev.STATUS &= ~(IO_1732_ACTIVE | IO_ST_BUSY); + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "Consumed read buffer", (t_stat)-1, FALSE); + } + + *data = result; + return IO_REPLY; +} + +/* Perform an output operation on a selected drive. This can be performed + by issuing a command directly to the device or via a 1706 */ + +enum IOstatus doMTOut(UNIT *uptr, uint16 *data, t_bool via1706) +{ + uint16 temp = *data; + t_mtrlnt need = ((MTdev.iod_mode & IO_1732_ASSEM) != 0) ? 2 : 1; + + /* + * Reject the request if we are not writing or data cannot be written. + */ + if ((MTmode != MT_WRITING) || (MTdev.STATUS & IO_ST_DATA) == 0) + return IO_REJECT; + + sim_cancel(uptr); + + if ((uptr->flags & UNIT_7TRACK) != 0) + temp &= 0x3F3F; + + if (MTremain < need) + return IO_REJECT; + + if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) { + MTbuf[MToffset] = (temp >> 8) & 0xFF; + MTbuf[MToffset + 1] = temp & 0xFF; + MToffset += 2; + MTremain -= 2; + } else { + MTbuf[MToffset] = temp & 0xFF; + MToffset += 1; + MTremain -= 1; + } + + if ((mt_dev.dctrl & DBG_WDATA) != 0) + mt_data(uptr, TRUE, temp); + + fw_IOintr(FALSE, &mt_dev, &MTdev, 0, IO_ST_DATA, 0xFFFF, NULL); + MTdev.iod_delay = IO_DELAY_WDATA; + sim_activate(uptr, (int32)(MTdev.iod_event - Instructions)); + + return IO_REPLY; +} + +/* Perform control function */ + +enum IOstatus doMTFunction(DEVICE *dev) +{ + UNIT *uptr; + t_stat st; + t_mtrlnt count = 1; + + /* + * Handle commands in the following order: + * + * 1. Handle clears + * 2. Handle interrupt selections + * 3. Handle motion control + */ + switch (IOAreg & IO_1732_MOTION) { + case 0: + case IO_1732_WRITE: + case IO_1732_READ: + case IO_1732_BACKSP: + case IO_1732_WFM: + case IO_1732_SFWD: + case IO_1732_SBACK: + case IO_1732_REWL: + break; + + case IO_1732A_REWU: + if (MTdev.iod_type == DEVTYPE_1732_3) + return IO_REJECT; + break; + + default: + return IO_REJECT; + } + + if (doDirectorFunc(&mt_dev, TRUE)) { + /* + * The device interrupt mask has been explicitly changed. If the device + * state is such that an interrupt can occur, generate it now. + */ + fw_IOintr(FALSE, &mt_dev, &MTdev, 0, 0, 0xFFFF, "Mask change interrupt"); + } + + /* + * All done if there is no motion control requested. + */ + if ((IOAreg & IO_1732_MOTION) == 0) + return IO_REPLY; + + /* + * Drive must be selected to perform a motion operation + */ + if ((uptr = MTdev.iod_unit) == NULL) + return IO_REJECT; + + /* + * We now know we have a valid motion command. + */ + MTdev.iod_wasWriting = MTmode == MT_WRITING; + + /* + * If we are currently writing to the tape, terminate the current + * record before initiating the new tape motion command. + */ + if (MTmode == MT_WRITING) { + st = sim_tape_wrrecf(uptr, MTbuf, MToffset); + + if ((mt_dev.dctrl & DBG_MTIO) != 0) + mtio_trace(uptr, "wrrecf", st, TRUE, MToffset); + + MTmode = MT_IDLE; + MTdev.STATUS &= ~IO_1732_ACTIVE; + } + + /* + * Clear ALARM, LOST data, FILL and any position information on a motion + * operation + */ + if ((IOAreg & IO_1732_MOTION) != 0) { + MTdev.STATUS &= ~IO_ST_ALARM; + MTdev.STATUS &= ~(IO_ST_LOST | IO_1732_FILL); + MTdev.STATUS &= ~(IO_1732_FMARK | IO_1732_EOT | IO_1732_BOT); + } + + switch (IOAreg & IO_1732_MOTION) { + case IO_1732_READ: + MTmode = MT_READING; + goto active; + + case IO_1732_WRITE: + MTmode = MT_WRITING; + MToffset = 0; + MTremain = MTSIZ; + goto active; + + case IO_1732_BACKSP: + case IO_1732_WFM: + case IO_1732_SFWD: + case IO_1732_SBACK: + active: + MTdev.STATUS |= IO_1732_ACTIVE; + break; + + case IO_1732_REWL: + if (!MTdev.iod_wasWriting && sim_tape_bot(uptr)) { + /* + * If we are currently standing at the load point, complete the + * request immediately. Diagnostic 0F (BD2) relies on this + * behaviour. + */ + MTdev.STATUS |= IO_1732_BOT; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_trace(uptr, "REWL", (t_stat)-1, FALSE); + + fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, 0xFFFF, "Rewind complete"); + return IO_REPLY; + } + /* FALLTHROUGH */ + + case IO_1732A_REWU: + break; + } + + /* + * Mark I/O underway and activate a delayed operation. + */ + fw_IOunderwayEOP(&MTdev, 0); + + sim_cancel(uptr); + MTdev.iod_delay = Areg & IO_1732_MOTION; + sim_activate(uptr, MT_MOTION_WAIT); + return IO_REPLY; +} + +/* Perform I/O */ + +enum IOstatus MTin(IO_DEVICE *iod, uint8 reg) +{ + UNIT *uptr = MTdev.iod_unit; + + /* + * The framework only passes INP operations for the data register (0x380). + */ + if (uptr != NULL) { + if (((MTdev.STATUS & IO_ST_READY) != 0) && (MTremain != 0)) { + return doMTIn(uptr, &Areg, FALSE); + } + } + return IO_REJECT; +} + +enum IOstatus MTout(IO_DEVICE *iod, uint8 reg) +{ + UNIT *uptr = MTdev.iod_unit; + uint16 unit; + + switch (reg) { + case 0x00: + if (uptr != NULL) { + if ((MTdev.STATUS & IO_ST_READY) != 0) + return doMTOut(uptr, &Areg, FALSE); + } + return IO_REJECT; + + case 0x01: + return doMTFunction(MTdev.iod_outdev); + + case 0x02: + /* + * Check for invalid bit combinations. + */ + if ((Areg & IO_1732_PARITY) == IO_1732_PARITY) + return IO_REJECT; + + if ((Areg & IO_1732_DESEL) != 0) + if ((Areg & ~IO_1732_DESEL) != 0) + return IO_REJECT; + + if ((Areg & IO_1732_SEL) != 0) { + /* + * Check for illegal unit select. + */ + unit = MTdev.iod_type == DEVTYPE_1732_3 ? IO_1732_UNIT : IO_1732A_UNIT; + unit = (unit & Areg) >> 7; + + if (unit >= mt_dev.numunits) + return IO_REJECT; + } + + switch (Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800)) { + case IO_1732_1600: /* IO_1732A_200 on 1732-A */ + case IO_1732_556: + case IO_1732_800: + if (uptr != NULL) + if ((mt_dev.dctrl & DBG_DENS) != 0) { + DEVICE *dptr = find_dev_from_unit(uptr); + int32 u = uptr - dptr->units; + + fprintf(DBGOUT, + "MT%d: Density changed to %04X\r\n", + u, Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800)); + } + /* FALLTHROUGH */ + + case 0: /* No change in density */ + break; + + default: + return IO_REJECT; + } + + /* + * Process the select/deselect operation. + */ + if ((Areg & IO_1732_DESEL) != 0) { + /*** TODO: Implement protected device support ***/ + if ((mt_dev.dctrl & DBG_SELECT) != 0) + if (MTdev.iod_unit != NULL) { + DEVICE *dptr = find_dev_from_unit(uptr); + int32 u = uptr - dptr->units; + + fprintf(DBGOUT, "MT%d - Deselected\r\n", u); + } + + MTdev.iod_unit = NULL; + fw_clearForced(&MTdev, IO_ST_READY); + MTdev.STATUS2 = 0; + return IO_REPLY; + } + + if ((Areg & IO_1732_SEL) != 0) { + MTdev.iod_unit = NULL; + MTdev.STATUS &= ~(IO_1732_STCINT | IO_1732_FMARK | IO_1732_EOT); + fw_clearForced(&MTdev, IO_ST_READY); + + uptr = &mt_unit[unit]; + + if ((uptr->flags & UNIT_ATT) != 0) { + MTdev.iod_unit = uptr; + fw_setForced(&MTdev, IO_ST_READY); + + if (sim_tape_bot(uptr)) + MTdev.STATUS |= IO_1732_BOT; + if (sim_tape_eot(uptr)) + MTdev.STATUS |= IO_1732_EOT; + } + if ((mt_dev.dctrl & DBG_SELECT) != 0) + fprintf(DBGOUT, "MT%d Selected\r\n", unit); + + MTdev.STATUS2 = 0; + } + + /* + * Remember the current mode of operation. + */ + MTdev.iod_mode = Areg; + + if ((uptr = MTdev.iod_unit) != NULL) { + /* + * If this operation modifies the density, remember it for later. + */ + if ((Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800)) != 0) { + if ((uptr->flags & UNIT_7TRACK) != 0) { + uptr->DENS &= ~(IO_ST2_556 | IO_ST2_800 | IO_ST2_1600); + if ((Areg & IO_1732_556) != 0) + uptr->DENS |= IO_ST2_556; + if ((Areg & IO_1732_800) != 0) + uptr->DENS |= IO_ST2_800; + if (MTdev.iod_type == DEVTYPE_1732_3) + if ((Areg & IO_1732_1600) != 0) + uptr->DENS |= IO_ST2_1600; + } + } + /* + * Make sure STATUS2 values are consistent with actual drive status. + */ + MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800); + if ((uptr->flags & UNIT_WPROT) != 0) + MTdev.STATUS2 &= ~IO_ST2_WENABLE; + else MTdev.STATUS2 |= IO_ST2_WENABLE; + if ((uptr->flags & UNIT_7TRACK) != 0) + MTdev.STATUS2 |= IO_ST2_7TRACK; + else MTdev.STATUS2 &= ~IO_ST2_7TRACK; + } + return IO_REPLY; + + case 0x03: + if ((uptr == NULL) || (MTdev.iod_type == DEVTYPE_1732_A)) + return IO_REJECT; + MTdev.iod_LWA = LoadFromMem(IOAreg); + MTdev.iod_CWA = ++IOAreg; + MTdev.iod_DSApending = TRUE; + if ((mt_dev.dctrl & DBG_OPS) != 0) + mt_DSAtrace(uptr, "setup"); + + return IO_REPLY; + } + return IO_REJECT; +} + +/* Perform I/O initiated through a 1706 buffered data channel */ + +enum IOstatus MTBDCin(IO_DEVICE *iod, uint16 *data, uint8 reg) +{ + UNIT *uptr = MTdev.iod_unit; + + if ((mt_dev.dctrl & DBG_DTRACE) != 0) { + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, + "%sMT%d: BDC input to register %d\r\n", + INTprefix, u, reg); + } + + /* + * The framework only passes INP operations for the data register (0x380). + */ + if (uptr != NULL) { + if ((MTdev.STATUS & IO_ST_DATA) != 0) + if (((MTdev.STATUS & IO_ST_READY) != 0) && (MTremain != 0)) + return doMTIn(uptr, data, TRUE); + } + return IO_REJECT; +} + +enum IOstatus MTBDCout(IO_DEVICE *iod, uint16 *data, uint8 reg) +{ + UNIT *uptr = MTdev.iod_unit; + + if ((mt_dev.dctrl & DBG_DTRACE) != 0) { + int32 u = uptr - mt_dev.units; + + fprintf(DBGOUT, + "%sMT%d: BDC output, %04X from register %d\r\n", + INTprefix, u, IOAreg, reg); + } + + switch (reg) { + case 0x00: + if (uptr != NULL) { + if ((MTdev.STATUS & IO_ST_READY) != 0) + return doMTOut(uptr, data, TRUE); + } + return IO_REJECT; + + case 0x01: + return doMTFunction(MTdev.iod_outdev); + + case 0x02: + break; + } + return IO_REJECT; +} diff --git a/CDC1700/cdc1700_rtc.c b/CDC1700/cdc1700_rtc.c new file mode 100644 index 00000000..ddc5dc1e --- /dev/null +++ b/CDC1700/cdc1700_rtc.c @@ -0,0 +1,341 @@ +/* + + 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_rtc.c: 10336-1 Real-time clock support + * Simh devices: rtc + */ +#include "cdc1700_defs.h" + +#define HOLDREG iod_writeR[0] /* Holding register */ +#define COUNTER iod_readR[1] /* Counter */ + +extern char INTprefix[]; + +extern t_stat checkReset(DEVICE *, uint8); + +extern t_stat show_debug(FILE *, UNIT *, int32, CONST void *); +extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *); + +extern t_stat set_equipment(UNIT *, int32, CONST char *, void *); + +extern void RaiseExternalInterrupt(DEVICE *); +extern void rebuildPending(void); + +extern uint16 Areg; + +extern t_bool IOFWinitialized; + +t_stat rtc_show_rate(FILE *, UNIT *, int32, CONST void *); +t_stat rtc_set_rate(UNIT *, int32, CONST char *, void *); + +void RTCstate(char *, DEVICE *, IO_DEVICE *); +uint16 RTCraised(DEVICE *); +enum IOstatus RTCin(IO_DEVICE *, uint8); +enum IOstatus RTCout(IO_DEVICE *, uint8); + +t_stat rtc_svc(UNIT *); +t_stat rtc_reset(DEVICE *); + +/* + 10336-1 Real-Time Clock + + Addresses + Computer Instruction + Q Register Output From A Input to A + (Bits 01-00) + + 00 Load Register + 01 Director Function Read Counter + + Operations: + + Director Function 1 + + 15 14 7 6 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | X | X | X | X | X | X | | | X | X | X | X | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | | | | | | + | | | | | Clr Controller + | | | | Ack. Interrupt + | | | Stop Clock + | | Start Clock + | Disable Interrupt + Enable Interrupt + + The counter and register values are unsigned 16-bit values. +*/ + +IO_DEVICE RTCdev = IODEV(NULL, "10336-1", 10336, 13, 0xFF, 0, + NULL, RTCin, RTCout, NULL, NULL, + NULL, NULL, RTCraised, NULL, + 0x7F, 2, + MASK_REGISTER0 | MASK_REGISTER1, + MASK_REGISTER1, + MASK_REGISTER0, 0, + AQ_ONLY, 0, NULL); + +/* + * Define usage for "private" IO_DEVICE data areas. + */ +#define iod_RTCstate iod_private +#define iod_RTCraised iod_private4 + +/* + * Current state of the device. + */ +#define IODP_RTCIDLE 0x0000 +#define IODP_RTCRUNNING 0x0001 +#define IODP_RTCINTR 0x0002 + +/* + * The RTC operates at a user determined frequency (via a jumper plug). + * Basic time periods are: + * + * 1 uSec, 10 uSec, 100 uSec, 1 mSec, 10 mSec, 100 mSec and 1 second. + * + * We use CPU instruction execution as a proxy for generating these + * frequencies. If we assume an average execution time of 1.25 uSec (1784-2 + * processor), each time period will be represented by the following + * instruction counts: + * + * 1, 8, 80, 800, 8000, 80000, 800000 + */ +#define RTC_1USEC 1 +#define RTC_10USEC 8 +#define RTC_100USEC 80 +#define RTC_1MSEC 800 +#define RTC_10MSEC 8000 +#define RTC_100MSEC 80000 +#define RTC_1SEC 800000 + +struct RTCtimebase { + const char *name; + const char *rate; + int32 icount; +} timeBase[] = { + { "1USEC", "1 uSec", RTC_1USEC }, + { "10USEC", "10 uSec", RTC_10USEC }, + { "100USEC", "100 uSec", RTC_100USEC }, + { "1MSEC", "1 mSec", RTC_1MSEC }, + { "10MSEC", "10 mSec", RTC_10MSEC }, + { "100MSEC", "100 mSec", RTC_100MSEC }, + { "1SEC", "1 Seccond", RTC_1SEC }, + { NULL } +}; + +/* RTC data structures + + rtc_dev RTC device descriptor + rtc_unit RTC unit descriptor + rtc_reg RTC register list + rtc_mod RTC modifiers list +*/ + +UNIT rtc_unit = { + UDATA(&rtc_svc, 0, 0), RTC_10MSEC +}; + +REG rtc_reg[] = { + { HRDATA(FUNCTION, RTCdev.FUNCTION, 16) }, + { HRDATA(COUNTER, RTCdev.COUNTER, 16) }, + { HRDATA(HOLDING, RTCdev.HOLDREG, 16) }, + { NULL } +}; + +MTAB rtc_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "10336-1", NULL, NULL, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "RATE", &rtc_set_rate, NULL, NULL, + "val={1usec|10usec|100usec|1msec|10msec|100msec|1second}" }, + { MTAB_XTD|MTAB_VDV, 0, "RATE", NULL, NULL, &rtc_show_rate, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", NULL, NULL, &show_addr, NULL }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, NULL, "EQUIPMENT", &set_equipment, NULL, NULL }, + { MTAB_XTD|MTAB_VDV, 0, "DEBUG", NULL, NULL, &show_debug, NULL }, + { 0 } +}; + +DEBTAB rtc_deb[] = { + { "TRACE", DBG_DTRACE }, + { NULL } +}; + +DEVICE rtc_dev = { + "RTC", &rtc_unit, rtc_reg, rtc_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &rtc_reset, + NULL, NULL, NULL, + &RTCdev, + DEV_DEBUG | DEV_DISABLE, 0, rtc_deb, + NULL, + NULL +}; + +t_stat rtc_show_rate(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + struct RTCtimebase *tb = timeBase; + + while (tb->name != NULL) { + if (tb->icount == rtc_unit.wait) { + fprintf(st, "Timebase rate: %s", tb->rate); + return SCPE_OK; + } + tb++; + } + return SCPE_IERR; +} + +t_stat rtc_set_rate(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (cptr) { + struct RTCtimebase *tb = timeBase; + + while (tb->name != NULL) { + if (!strcmp(cptr, tb->name)) { + rtc_unit.wait = tb->icount; + return SCPE_OK; + } + tb++; + } + } + return SCPE_IERR; +} + +/* + * Determine if the clock interrupt is asserted, returning the appropriate + * interrupt bit or 0. + */ +uint16 RTCraised(DEVICE *dptr) +{ + IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt; + + return iod->iod_RTCraised ? iod->iod_interrupt : 0; +} + +/* Unit service */ + +t_stat rtc_svc(UNIT *uptr) +{ + if (RTCdev.iod_RTCstate != IODP_RTCIDLE) { + if ((RTCdev.iod_RTCstate & IODP_RTCRUNNING) != 0) { + RTCdev.COUNTER++; + + if ((RTCdev.iod_RTCstate & IODP_RTCINTR) != 0) { + if (RTCdev.COUNTER == RTCdev.HOLDREG) { + RTCdev.COUNTER = 0; + RTCdev.iod_RTCraised = TRUE; + RaiseExternalInterrupt(&rtc_dev); + } + } + sim_activate(&rtc_unit, rtc_unit.wait); + } + } + return SCPE_OK; +} + +/* Reset routine */ + +t_stat rtc_reset(DEVICE * dptr) +{ + t_stat r; + + if (IOFWinitialized) + if ((dptr->flags & DEV_DIS) == 0) + if ((r = checkReset(dptr, RTCdev.iod_equip)) != SCPE_OK) + return r; + + RTCdev.iod_RTCstate = IODP_RTCIDLE; + RTCdev.iod_RTCraised = FALSE; + + return SCPE_OK; +} + +/* Perform I/O */ + +enum IOstatus RTCin(IO_DEVICE *iod, uint8 reg) +{ + /* + * The framework only passes IN operations for the data register. + */ + return IO_REJECT; +} + +enum IOstatus RTCout(IO_DEVICE *iod, uint8 reg) +{ + switch (reg) { + case 0x00: + RTCdev.HOLDREG = Areg; + break; + + case 0x01: + /* + * Check for illegal bit combinations + */ + if (((Areg & (IO_10336_ENA | IO_10336_DIS)) == + (IO_10336_ENA | IO_10336_DIS)) || + ((Areg & (IO_10336_START | IO_10336_STOP)) == + (IO_10336_START | IO_10336_STOP))) + return IO_REJECT; + + if ((Areg & IO_DIR_CCONT) != 0) { + sim_cancel(&rtc_unit); + + RTCdev.iod_RTCstate = IODP_RTCIDLE; + RTCdev.iod_RTCraised = FALSE; + rebuildPending(); + + RTCdev.HOLDREG = 0; + RTCdev.COUNTER = 0; + } + + if ((Areg & IO_10336_STOP) != 0) { + RTCdev.iod_RTCstate &= ~IODP_RTCRUNNING; + sim_cancel(&rtc_unit); + } + + if ((Areg & IO_10336_START) != 0) { + RTCdev.COUNTER = 0; + RTCdev.iod_RTCstate |= IODP_RTCRUNNING; + sim_activate(&rtc_unit, rtc_unit.wait); + } + + if ((Areg & IO_10336_ACK) != 0) { + RTCdev.iod_RTCraised = FALSE; + rebuildPending(); + } + + if ((Areg & IO_10336_DIS) != 0) { + RTCdev.iod_RTCstate &= ~IODP_RTCINTR; + RTCdev.iod_RTCraised = FALSE; + rebuildPending(); + } + + if ((Areg & IO_10336_ENA) != 0) + RTCdev.iod_RTCstate |= IODP_RTCINTR; + break; + } + return IO_REPLY; +} diff --git a/CDC1700/cdc1700_sym.c b/CDC1700/cdc1700_sym.c new file mode 100644 index 00000000..f77881ba --- /dev/null +++ b/CDC1700/cdc1700_sym.c @@ -0,0 +1,339 @@ +/* + + 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_sym.c: symbolic assembler input for "deposit" command + */ + +#include "cdc1700_defs.h" +#include + +extern UNIT cpu_unit; + +extern uint16 doADDinternal(uint16, uint16); + +/* + * Symbol tables + */ +#define I_DATA 0x10000 /* Data transmission */ +#define I_ARITH 0x20000 /* Arithmetic */ +#define I_LOG 0x30000 /* Logical */ +#define I_JUMP 0x40000 /* Jumps */ +#define I_REG 0x50000 /* Register reference */ +#define I_SKIP 0x60000 /* Skip */ +#define I_INTER 0x70000 /* Inter-register */ +#define I_SHIFT 0x80000 /* Shift */ +#define I_MASK 0xF0000 + +#define I_DMASK 0xFFFF + +/* + * Modifiers for I_REG addressing. + */ +#define I_NONE 0x000000 /* No argument expected */ +#define I_REL 0x100000 /* 8-bit relative address */ +#define I_ABS 0x200000 /* 8-bit absolute value */ +#define I_SIGNED 0x300000 /* 8-bit signed value */ +#define I_MASK2 0x300000 + +#define I_NOARG I_REG + I_NONE + +static const char *opcode[] = { + "ADQ", "LDQ", "RAO", "LDA", + "EOR", "AND", "SUB", "ADD", + "SPA", "STA", "RTJ", "STQ", + "DVI", "MUI", "JMP", "SLS", + "SAZ", "SAN", "SAP", "SAM", + "SQZ", "SQN", "SQP", "SQM", + "SWS", "SWN", "SOV", "SNO", + "SPE", "SNP", "SPF", "SNF", + "INP", "OUT", "EIN", "IIN", + "ECA", "DCA", "SPB", "CPB", + "AAM", "AAQ", "AAB", "CLR", + "TCM", "TCQ", "TCB", "TCA", + "EAM", "EAQ", "EAB", "SET", + "TRM", "TRQ", "TRB", "TRA", + "LAM", "LAQ", "LAB", "CAM", + "CAQ", "CAB", "INA", "ENA", + "NOP", "ENQ", "INQ", "EXI", + "QRS", "ARS", "LRS", "QLS", + "ALS", "LLS", + NULL +}; + +static const int32 opc_val[] = { + OPC_ADQ + I_ARITH, OPC_LDQ + I_DATA, OPC_RAO + I_ARITH, OPC_LDA + I_DATA, + OPC_EOR + I_LOG, OPC_AND + I_LOG, OPC_SUB + I_ARITH, OPC_ADD + I_ARITH, + OPC_SPA + I_DATA, OPC_STA + I_DATA, OPC_RTJ + I_JUMP, OPC_STQ + I_DATA, + OPC_DVI + I_ARITH, OPC_MUI + I_ARITH, OPC_JMP + I_JUMP, OPC_SLS + I_NOARG, + OPC_SAZ + I_SKIP, OPC_SAN + I_SKIP, OPC_SAP + I_SKIP, OPC_SAM + I_SKIP, + OPC_SQZ + I_SKIP, OPC_SQN + I_SKIP, OPC_SQP + I_SKIP, OPC_SQM + I_SKIP, + OPC_SWS + I_SKIP, OPC_SWN + I_SKIP, OPC_SOV + I_SKIP, OPC_SNO + I_SKIP, + OPC_SPE + I_SKIP, OPC_SNP + I_SKIP, OPC_SPF + I_SKIP, OPC_SNF + I_SKIP, + OPC_INP + I_REG + I_REL, OPC_OUT + I_REG + I_REL, OPC_EIN + I_NOARG, OPC_IIN + I_NOARG, + OPC_ECA + I_NOARG, OPC_DCA + I_NOARG, OPC_SPB + I_NOARG, OPC_CPB + I_NOARG, + OPC_AAM + I_INTER, OPC_AAQ + I_INTER, OPC_AAB + I_INTER, OPC_CLR + I_INTER, + OPC_TCM + I_INTER, OPC_TCQ + I_INTER, OPC_TCB + I_INTER, OPC_TCA + I_INTER, + OPC_EAM + I_INTER, OPC_EAQ + I_INTER, OPC_EAB + I_INTER, OPC_SET + I_INTER, + OPC_TRM + I_INTER, OPC_TRQ + I_INTER, OPC_TRB + I_INTER, OPC_TRA + I_INTER, + OPC_LAM + I_INTER, OPC_LAQ + I_INTER, OPC_LAB + I_INTER, OPC_CAM + I_INTER, + OPC_CAQ + I_INTER, OPC_CAB + I_INTER, OPC_INA + I_REG + I_SIGNED, OPC_ENA + I_REG + I_SIGNED, + OPC_NOP + I_NOARG, OPC_ENQ + I_REG + I_SIGNED, OPC_INQ + I_REG + I_SIGNED, OPC_EXI + I_REG + I_ABS, + OPC_QRS + I_SHIFT, OPC_ARS + I_SHIFT, OPC_LRS + I_SHIFT, OPC_QLS + I_SHIFT, + OPC_ALS + I_SHIFT, OPC_LLS + I_SHIFT, +}; + +/* + * Register (and pseudo-register) names. + */ +static const char *regname[] = { + "A", "Q", "M", "I", "B", + NULL +}; + +/* + * Usage value for each usage type (0 means invalid). + */ +static uint16 instIndex[] = { + 0x0000, MOD_I1, 0x0000, MOD_I2, MOD_I1 | MOD_I2 +}; + +static uint16 instInter[] = { + MOD_D_A, MOD_D_Q, MOD_D_M, 0x0000, 0x0000 +}; + +#define NEXTSYMBOL(mchar) \ + cptr = get_glyph(cptr, gbuf, mchar); \ + for (j = 0; (regname[j] != NULL) && (strcmp(regname[j], gbuf) != 0); j++); \ + if (regname[j] == NULL) return SCPE_ARG + +t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) +{ + int32 cflag, i, j, l, rdx; + t_bool neg, cons; + t_value temp; + t_stat r, sta = SCPE_OK; + char gbuf[CBUFSIZE], mode; + const char *cptr2; + + cflag = (uptr == NULL) || (uptr == &cpu_unit); + while (isspace(*cptr)) + cptr++; + + if ((sw & SWMASK('A')) || ((*cptr == '\'') && cptr++)) { + /* ASCII character */ + if (cptr[0] == 0) + return SCPE_ARG; + val[0] = (t_value)cptr[0] | 0200; + return SCPE_OK; + } + + if ((sw & SWMASK('C')) || ((*cptr == '"') && cptr++)) { + /* Packed ASCII characters (2 to a word) */ + if (cptr[0] == 0) + + val[0] = (((t_value)cptr[0] | 0200) << 8) | ((t_value)cptr[1] | 0200); + return SCPE_OK; + } + + cptr = get_glyph(cptr, gbuf, 0); + l = strlen(gbuf); + if ((gbuf[l - 1] == '*') || (gbuf[l - 1] == '-') || (gbuf[l - 1] == '+')) { + mode = gbuf[l - 1]; + gbuf[l - 1] = '\0'; + } else mode = 0; + + for (i = 0; (opcode[i] != NULL) && (strcmp(opcode[i], gbuf) != 0); i++); + if (opcode[i] == NULL) + return SCPE_ARG; + + val[0] = opc_val[i] & I_DMASK; + while (isspace(*cptr)) + cptr++; + + cons = neg = FALSE; + rdx = 10; + + switch (opc_val[i] & I_MASK) { + case I_DATA: + case I_ARITH: + case I_LOG: + if (*cptr == '=') { + cons = TRUE; + cptr++; + + if (*cptr == '-') { + neg = TRUE; + cptr++; + } + + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = get_uint(cptr, rdx, MAXNEG, &r); + if (r != SCPE_OK) + return r; + if (neg) { + if (temp > MAXPOS) + return SCPE_ARG; + temp = (~temp) & 0xFFFF; + } + + if ((mode == '*') || (mode == '-')) + return SCPE_ARG; + + /* + * Constant addressing mode always occupies 2 words. + */ + val[1] = temp; + return -1; + } + /* FALLTHROUGH */ + + case I_JUMP: + if (*cptr == '(') { + cptr++; + + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = strtotv(cptr, &cptr2, rdx); + if ((cptr == cptr2) || (*cptr2++ != ')')) + return SCPE_ARG; + cptr = (char *)cptr2; + val[0] |= MOD_IN; + } else { + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = strtotv(cptr, &cptr2, rdx); + if (cptr == cptr2) + return SCPE_ARG; + cptr = (char *)cptr2; + } + + if (mode == '*') { + temp = doADDinternal(temp, ~addr); + if (CANEXTEND8(temp)) + temp &= 0xFF; + val[0] |= MOD_RE; + } + + if ((mode == '-') && ((temp & 0xFF00) != 0)) + return SCPE_ARG; + + /* + * Check for indexing modifier + */ + if (*cptr++ == ',') { + NEXTSYMBOL(0); + if (instIndex[j] == 0) + return SCPE_ARG; + + val[0] |= instIndex[j]; + } + + if (((temp & 0xFF00) != 0) || (mode == '+')) { + val[1] = temp; + return -1; + } + val[0] |= temp; + return SCPE_OK; + + case I_REG: + switch (opc_val[i] & I_MASK2) { + case I_NONE: + return SCPE_OK; + + case I_REL: + case I_SIGNED: + if (*cptr == '-') { + neg = TRUE; + cptr++; + } + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = get_uint(cptr, rdx, 127, &r); + if (r != SCPE_OK) + return r; + if (neg) + temp = (~temp) & 0xFF; + val[0] |= temp; + return SCPE_OK; + + case I_ABS: + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = get_uint(cptr, rdx, 255, &r); + if (r != SCPE_OK) + return r; + val[0] |= temp; + return SCPE_OK; + } + break; + + case I_SKIP: + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = get_uint(cptr, rdx, 15, &r); + if (r != SCPE_OK) + return r; + val[0] |= temp; + return SCPE_OK; + + case I_INTER: + if (*cptr != 0) { + do { + NEXTSYMBOL(','); + if (instInter[j] == 0) + return SCPE_ARG; + val[0] |= instInter[j]; + } while (*cptr != 0); + } + return SCPE_OK; + + case I_SHIFT: + if (*cptr == '$') { + rdx = 16; + cptr++; + } + temp = get_uint(cptr, rdx, 31, &r); + if (r != SCPE_OK) + return r; + val[0] |= temp; + return SCPE_OK; + } + return SCPE_ARG; +} diff --git a/CDC1700/cdc1700_sys.c b/CDC1700/cdc1700_sys.c new file mode 100644 index 00000000..a178db2a --- /dev/null +++ b/CDC1700/cdc1700_sys.c @@ -0,0 +1,342 @@ +/* + + 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_sys.c: CDC1700 system description + */ + +#include "cdc1700_defs.h" +#include + +extern void buildDCtables(void); +extern void buildIOtable(void); + +extern int disassem(char *, uint16, t_bool, t_bool, t_bool); + +extern uint16 M[]; +extern REG cpu_reg[]; +extern DEVICE cpu_dev, dca_dev, dcb_dev, dcc_dev, + tti_dev, tto_dev, ptr_dev, ptp_dev, cdr_dev, mt_dev, lp_dev, dp_dev, + cd_dev, rtc_dev; +extern UNIT cpu_unit; + +t_stat autoload(int32, CONST char *); +t_stat CDautoload(void); +t_stat DPautoload(void); + +t_bool RelValid = FALSE; +uint16 RelBase; + +/* SCP data structures and interface routines + + sim_name simulator name string + sim_PC pointer to saved PC register descriptor + sim_emax number of words for examine + sim_devices array of pointers to simulated devices + sim_stop_messages array of pointers to stop messages + sim_load binary loader +*/ + +char sim_name[] = "CDC1700"; + +REG *sim_PC = &cpu_reg[0]; + +int32 sim_emax = 2; + +DEVICE *sim_devices[] = { + &cpu_dev, + &rtc_dev, + &dca_dev, + &dcb_dev, + &dcc_dev, + &tti_dev, + &tto_dev, + &ptr_dev, + &ptp_dev, +#if 0 + &cdr_dev, +#endif + &mt_dev, + &lp_dev, + &dp_dev, + &cd_dev, + NULL +}; + +const char *sim_stop_messages[] = { + "OK", + "Indirect addressing loop count exceeded", + "Selective Stop", + "Invalid bits set in EXI instruction", + "Breakpoint", + "Stop on reject" +}; + +/* + * New top-level command(s) for the CDC1700 + */ +CTAB cdc1700_cmd[] = { + { "AUTOLOAD", &autoload, 0, + "a{utoload} Autoload from default device on controller\n" + " Loads track 0 to location 0\n" + }, + { NULL } +}; + +/* + * Command post-processing routine. + */ +static void postUpdate(t_bool from_scp) +{ + /* + * Rebuild the I/O device and buffered data channel tables in case the + * command the configuration. + */ + buildIOtable(); + buildDCtables(); + + RelValid = FALSE; +} + +/* + * Special address print routine for "Relative" display. + */ +static void printAddress(FILE *st, DEVICE *dptr, t_addr addr) +{ + if ((dptr == sim_devices[0]) && ((sim_switches & SWMASK('R')) != 0)) { + if (!RelValid) { + RelBase = (uint16)addr; + RelValid = TRUE; + } + addr -= RelBase; + } + fprint_val(st, addr, dptr->aradix, dptr->awidth, PV_RZRO); +} + +/* + * Once-only VM initialization + */ +static void VMinit(void) +{ + sim_vm_fprint_addr = &printAddress; + sim_vm_post = &postUpdate; + sim_vm_cmd = cdc1700_cmd; + + /* + * Initialize the "CPU" device to make sure the data structures are + * correctly initialized. + */ + (cpu_dev.reset)(&cpu_dev); +} +void (*sim_vm_init)(void) = &VMinit; + +/* + * Check for duplicate equipment addresses. + */ +static t_bool checkDuplicate(DEVICE *dptr, uint8 equipment) +{ + int i = 0; + DEVICE *dptr2; + + while ((dptr2 = sim_devices[i++]) != NULL) { + if ((dptr2 != dptr) && ((dptr2->flags & DEV_DIS) == 0)) { + IO_DEVICE *iod = (IO_DEVICE *)dptr2->ctxt; + + if (iod->iod_equip == equipment) + return TRUE; + } + } + return FALSE; +} + +/* + * Common routine to change the equipment address of a peripheral. Some + * devices (e.g. TT, PTR etc) cannot have their equipment address changed. + */ +t_stat set_equipment(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + IO_DEVICE *iod; + t_value v; + t_stat r; + + if (cptr == NULL) + return SCPE_ARG; + + v = get_uint(cptr, DEV_RDX, 15, &r); + if (r != SCPE_OK) + return r; + if (v == 0) + return SCPE_ARG; + + /* + * Check to see if any other, non-disabled device is already using this + * address. + */ + if ((dptr = find_dev_from_unit(uptr)) != NULL) { + if (checkDuplicate(dptr, v)) + return sim_messagef(SCPE_ARG, "Equipment address already in use\n"); + + iod = (IO_DEVICE *)dptr->ctxt; + iod->iod_equip = v; + iod->iod_interrupt = 1 << v; + return SCPE_OK; + } + return SCPE_NXDEV; +} +/* + * Check for a duplicate address when a device is reset. If a duplicate is + * found, the device being reset is disabled. + */ +t_stat checkReset(DEVICE *dptr, uint8 equipment) +{ + if (checkDuplicate(dptr, equipment)) { + dptr->flags |= DEV_DIS; + return sim_messagef(SCPE_ARG, "Equipment address already in use\n"); + } + return SCPE_OK; +} + +t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fname, int flag) +{ + t_addr lo, hi; + + if (flag == 0) + return SCPE_ARG; + + /* + * We want to write the memory in some device-dependent format. sim_switches + * contains the command switches which will be used to determine the + * format: + * + * -p Paper tape format + * + * Command syntax is: + * + * dump -p - + */ + if ((sim_switches & SWMASK('P')) != 0) { + const char *tptr; + t_addr addr; + int temp, count = 0; + + tptr = get_range(NULL, cptr, &lo, &hi, cpu_dev.aradix, cpu_unit.capac - 1, 0); + if (tptr != NULL) { + /* + * Output a couple of NULL frames to start the dump + */ + fputc(0, fileref); + fputc(0, fileref); + + for (addr = lo; addr <= hi; addr++) { + temp = M[addr]; + + /* + * If the data is 0, map it to -0 (0xFFFF) since 0 terminates the + * sequence. We also count the number of times this happens and + * report it at the end. + */ + if (temp == 0) { + temp =0xFFFF; + count++; + } + fputc((temp >> 8) & 0xFF, fileref); + fputc(temp & 0xFF, fileref); + } + /* + * Terminate the dump with 2 more NULL frames + */ + fputc(0, fileref); + fputc(0, fileref); + + if (count != 0) + printf("%d zero word translated to 0xFFFF\n", count); + + return SCPE_OK; + } + } + return SCPE_ARG; +} + +/* + * Symbolic decode + */ +#define FMTASC(x) ((x) < 040) ? "<%03o>" : "%c", (x) + +t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw) +{ + int32 inst = val[0]; + t_bool target = (sw & SWMASK('T')) != 0; + char buf[128]; + int consume; + + if ((sw & SWMASK('A')) != 0) { + /* ASCII character */ + if (inst > 0377) + return SCPE_ARG; + fprintf(of, FMTASC(inst & 0177)); + return SCPE_OK; + } + + if ((sw & SWMASK('C')) != 0) { + unsigned char c1 = (inst >> 8) & 0xFF, c2 = inst & 0xFF; + + fprintf(of, FMTASC(c1 & 0177)); + fprintf(of, FMTASC(c2 & 0177)); + return SCPE_OK; + } + + if ((sw & SWMASK('M')) == 0) + return SCPE_ARG; + + consume = disassem(buf, (uint16)addr, FALSE, target, FALSE); + fprintf(of, "%s", buf); + return -(consume - 1); +} + +/* + * Autoload top-level command routine + */ +t_stat autoload(int32 flag, CONST char *ptr) +{ + char gbuf[CBUFSIZE]; + DEVICE *dptr; + + if (!ptr || !*ptr) + return SCPE_2FARG; + + get_glyph(ptr, gbuf, 0); + dptr = find_dev(gbuf); + if (dptr == NULL) + return SCPE_ARG; + + if (dptr == &cd_dev) + return CDautoload(); + if (dptr == &dp_dev) + return DPautoload(); + + return SCPE_NOFNC; +} diff --git a/README.md b/README.md index 03eff5aa..7528baaf 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ #### Intel Systems 8010 and 8020 simulators from Bill Beech +#### CDC 1700 simulator from John Forecast + ### New Host Platform support - HP-UX and AIX ### Simulator Front Panel API diff --git a/Visual Studio Projects/CDC1700.vcproj b/Visual Studio Projects/CDC1700.vcproj new file mode 100644 index 00000000..c1e97cd0 --- /dev/null +++ b/Visual Studio Projects/CDC1700.vcproj @@ -0,0 +1,357 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Visual Studio Projects/Simh.sln b/Visual Studio Projects/Simh.sln index 91e25e5e..860a156b 100644 --- a/Visual Studio Projects/Simh.sln +++ b/Visual Studio Projects/Simh.sln @@ -228,6 +228,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HP3000", "HP3000.vcproj", " {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CDC1700", "CDC1700.vcproj", "{2D532F83-02F3-4169-9778-23E52D951FDE}" + ProjectSection(ProjectDependencies) = postProject + {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -418,6 +423,10 @@ Global {B3E35063-CB41-4F77-BFCA-49BB316B0EDB}.Debug|Win32.Build.0 = Debug|Win32 {B3E35063-CB41-4F77-BFCA-49BB316B0EDB}.Release|Win32.ActiveCfg = Release|Win32 {B3E35063-CB41-4F77-BFCA-49BB316B0EDB}.Release|Win32.Build.0 = Release|Win32 + {2D532F83-02F3-4169-9778-23E52D951FDE}.Debug|Win32.ActiveCfg = Debug|Win32 + {2D532F83-02F3-4169-9778-23E52D951FDE}.Debug|Win32.Build.0 = Debug|Win32 + {2D532F83-02F3-4169-9778-23E52D951FDE}.Release|Win32.ActiveCfg = Release|Win32 + {2D532F83-02F3-4169-9778-23E52D951FDE}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/descrip.mms b/descrip.mms index 2d60605f..4625c388 100644 --- a/descrip.mms +++ b/descrip.mms @@ -20,6 +20,7 @@ # ALTAIRZ80 Just Build The MITS Altair Z80. # BESM6 Just Build The BESM-6. # B5500 Just Build The B5500. +# CDC1700 Just Build The CDC1700. # ECLIPSE Just Build The Data General Eclipse. # GRI Just Build The GRI Corporation GRI-909. # LGP Just Build The Royal-McBee LGP-30. @@ -675,6 +676,18 @@ B5500_SOURCE = $(B5500_DIR)B5500_CPU.C,$(B5500_DIR)B5500_DK.C,$(B5500_DIR)B5500_ $(B5500_DIR)B5500_SYS.C,$(B5500_DIR)B5500_UREC.C,$(SIMH_DIR)SIM_CARD.C B5500_OPTIONS = /INCL=($(SIMH_DIR),$(B5500_DIR))/DEF=($(CC_DEFS),"USE_INT64=1","USE_SIM_CARD=1") +# +# CDC1700 +# +CDC1700_DIR = SYS$DISK:[.CDC1700] +CDC1700_LIB = $(LIB_DIR)CDC1700-$(ARCH).OLB +CDC1700_SOURCE = $(CDC1700_DIR)CDC1700_CPU.C,$(CDC1700_DIR)CDC1700_DIS.C,$(CDC1700_DIR)CDC1700_IO.C,\ + $(CDC1700_DIR)CDC1700_SYS.C,$(CDC1700_DIR)CDC1700_DEV1.C,$(CDC1700_DIR)CDC1700_MT.C,\ + $(CDC1700_DIR)CDC1700_DC.C,$(CDC1700_DIR)CDC1700_IOFW.C,$(CDC1700_DIR)CDC1700_LP.C,\ + $(CDC1700_DIR)CDC1700_DP.C,$(CDC1700_DIR)CDC1700_CD.C,$(CDC1700_DIR)CDC1700_SYM.C,\ + $(CDC1700_DIR)CDC1700_RTC.C +CDC1700_OPTIONS = /INCL=($(SIMH_DIR),$(CDC1700_DIR))/DEF=($(CC_DEFS)) + # # Digital Equipment VAX 3900 Simulator Definitions. # @@ -934,8 +947,8 @@ I7094_OPTIONS = /INCL=($(SIMH_DIR),$(I7094_DIR))/DEF=($(CC_DEFS)) # If we're not a VAX, Build Everything # .IFDEF ALPHA_OR_IA64 -ALL : ALTAIR ALTAIRZ80 ECLIPSE GRI LGP H316 HP2100 HP3000 I1401 I1620 IBM1130 ID16 \ - ID32 NOVA PDP1 PDP4 PDP7 PDP8 PDP9 PDP10 PDP11 PDP15 S3 \ +ALL : ALTAIR ALTAIRZ80 CDC1700 ECLIPSE GRI LGP H316 HP2100 HP3000 I1401 I1620 \ + IBM1130 ID16 ID32 NOVA PDP1 PDP4 PDP7 PDP8 PDP9 PDP10 PDP11 PDP15 S3 \ VAX MICROVAX3900 MICROVAX1 RTVAX1000 MICROVAX2 VAX730 VAX750 VAX780 VAX8600 \ SDS I7094 SWTP6800MP-A SWTP6800MP-A2 SSEM BESM6 B5500 $! No further actions necessary @@ -943,7 +956,7 @@ ALL : ALTAIR ALTAIRZ80 ECLIPSE GRI LGP H316 HP2100 HP3000 I1401 I1620 IBM1130 ID # # Else We Are On VAX And Build Everything EXCEPT the 64b simulators # -ALL : ALTAIR ALTAIRZ80 GRI H316 HP2100 I1401 I1620 IBM1130 ID16 ID32 \ +ALL : ALTAIR ALTAIRZ80 CDC1700 GRI H316 HP2100 I1401 I1620 IBM1130 ID16 ID32 \ NOVA PDP1 PDP4 PDP7 PDP8 PDP9 PDP11 PDP15 S3 \ VAX MICROVAX3900 MICROVAX1 RTVAX1000 MICROVAX2 VAX730 VAX750 VAX780 VAX8600 \ SDS SWTP6800MP-A SWTP6800MP-A2 SSEM @@ -1430,6 +1443,17 @@ $(B5500_LIB) : $! $(MMS$TARGET) Library On VAX. .ENDIF +$(CDC1700_LIB) : $(CDC1700_SOURCE) + $! + $! Building The $(CDC1700_LIB) Library. + $! + $ $(CC)$(CDC1700_OPTIONS) - + /OBJ=$(BLD_DIR) $(MMS$CHANGED_LIST) + $ IF (F$SEARCH("$(MMS$TARGET)").EQS."") THEN - + LIBRARY/CREATE $(MMS$TARGET) + $ LIBRARY/REPLACE $(MMS$TARGET) $(BLD_DIR)*.OBJ + $ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;* + $(VAX_LIB1) : $(VAX_SOURCE1) $! $! Building The $(VAX_LIB1) Library. @@ -2059,6 +2083,18 @@ B5500 : .ENDIF +CDC1700 : $(BIN_DIR)CDC1700-$(ARCH).EXE + $! CDC1700 done + +$(BIN_DIR)CDC1700-$(ARCH).EXE : $(SIMH_MAIN) $(SIMH_NONET_LIB) $(CDC1700_LIB) + $! + $! Building The $(BIN_DIR)CDC1700-$(ARCH).EXE Simulator. + $! + $ $(CC)$(CDC1700_OPTIONS)/OBJ=$(BLD_DIR) SCP.C + $ LINK $(LINK_DEBUG)/EXE=$(BIN_DIR)CDC1700-$(ARCH).EXE - + $(BLD_DIR)SCP.OBJ,$(CDC1700_LIB)/LIBRARY,$(SIMH_NONET_LIB)/LIBRARY + $ DELETE/NOLOG/NOCONFIRM $(BLD_DIR)*.OBJ;* + VAX : MICROVAX3900 $! MICROVAX3900 aka VAX done diff --git a/makefile b/makefile index 111e6960..b3c1f37d 100644 --- a/makefile +++ b/makefile @@ -1367,6 +1367,16 @@ B5500_OPT = -I.. -DUSE_INT64 -DB5500 -DUSE_SIM_CARD ### Experimental simulators ### +CDC1700D = CDC1700 +CDC1700 = ${CDC1700D}/cdc1700_cpu.c ${CDC1700D}/cdc1700_dis.c \ + ${CDC1700D}/cdc1700_io.c ${CDC1700D}/cdc1700_sys.c \ + ${CDC1700D}/cdc1700_dev1.c ${CDC1700D}/cdc1700_mt.c \ + ${CDC1700D}/cdc1700_dc.c ${CDC1700D}/cdc1700_iofw.c \ + ${CDC1700D}/cdc1700_lp.c ${CDC1700D}/cdc1700_dp.c \ + ${CDC1700D}/cdc1700_cd.c ${CDC1700D}/cdc1700_sym.c \ + ${CDC1700D}/cdc1700_rtc.c +CDC1700_OPT = -I ${CDC1700D} + BESM6D = BESM6 BESM6 = ${BESM6D}/besm6_cpu.c ${BESM6D}/besm6_sys.c ${BESM6D}/besm6_mmu.c \ ${BESM6D}/besm6_arith.c ${BESM6D}/besm6_disk.c ${BESM6D}/besm6_drum.c \ @@ -1450,7 +1460,7 @@ PDQ3_OPT = -I ${PDQ3D} -DUSE_SIM_IMD ALL = pdp1 pdp4 pdp7 pdp8 pdp9 pdp15 pdp11 pdp10 \ vax microvax3900 microvax1 rtvax1000 microvax2 vax730 vax750 vax780 vax8600 \ nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri \ - i7094 ibm1130 id16 id32 sds lgp h316 \ + i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \ swtp6800mp-a swtp6800mp-a2 tx-0 ssem isys8010 isys8020 \ b5500 @@ -1745,6 +1755,12 @@ ${BIN}ssem${EXE} : ${SSEM} ${SIM} ${MKDIRBIN} ${CC} ${SSEM} ${SIM} ${SSEM_OPT} $(CC_OUTSPEC) ${LDFLAGS} +cdc1700 : ${BIN}cdc1700${EXE} + +${BIN}cdc1700${EXE} : ${CDC1700} ${SIM} + ${MKDIRBIN} + ${CC} ${CDC1700} ${SIM} ${CDC1700_OPT} ${CC_OUTSPEC} ${LDFLAGS} + besm6 : ${BIN}besm6${EXE} ${BIN}besm6${EXE} : ${BESM6} ${SIM}